@dothift/amarok-lite 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +18 -0
- package/README.md +90 -0
- package/amarok-lite.win32-x64-msvc.node +0 -0
- package/dist/amarok-lite.js +315 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +28 -0
- package/dist/event-log.d.ts +3 -0
- package/dist/event-log.js +31 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/jsonb.d.ts +16 -0
- package/dist/jsonb.js +24 -0
- package/dist/rows.d.ts +21 -0
- package/dist/rows.js +45 -0
- package/dist/store.d.ts +77 -0
- package/dist/store.js +212 -0
- package/dist/transaction.d.ts +28 -0
- package/dist/transaction.js +66 -0
- package/index.d.ts +35 -0
- package/index.js +315 -0
- package/package.json +62 -0
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store — Amarok-Lite embedded database engine for Node.js.
|
|
3
|
+
*
|
|
4
|
+
* Backed by the native `amarok-core` Rust engine via napi-rs.
|
|
5
|
+
* Provides the Amarok API surface so that code written for the Lite SDK
|
|
6
|
+
* is compatible with the full Amarok server SDK.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { Store } from '@dothift/amarok-lite'
|
|
10
|
+
* const db = Store.open('./myapp-data')
|
|
11
|
+
* db.exec("CREATE TABLE IF NOT EXISTS tickets (id TEXT PRIMARY KEY, data TEXT)")
|
|
12
|
+
* db.exec("INSERT INTO tickets VALUES ('t-001', '{\"title\": \"My ticket\"}')")
|
|
13
|
+
* const rows = db.query("SELECT * FROM tickets WHERE id = 't-001'")
|
|
14
|
+
* console.log(rows.fetchall())
|
|
15
|
+
* db.close()
|
|
16
|
+
*/
|
|
17
|
+
import { Rows } from './rows.js';
|
|
18
|
+
import { Tx } from './transaction.js';
|
|
19
|
+
/** Row type — a record of column name → value. */
|
|
20
|
+
export type Row = Record<string, unknown>;
|
|
21
|
+
/** Options for opening an Amarok-Lite database. */
|
|
22
|
+
export interface Options {
|
|
23
|
+
/** Default database name (default: `"dhs"`). */
|
|
24
|
+
defaultDb?: string;
|
|
25
|
+
/** Create Phase 2 event log stub tables (default: `true`). */
|
|
26
|
+
enableEventLog?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare class Store {
|
|
29
|
+
private readonly _path;
|
|
30
|
+
private readonly _opts;
|
|
31
|
+
/** @internal */
|
|
32
|
+
private readonly _native;
|
|
33
|
+
private _closed;
|
|
34
|
+
private constructor();
|
|
35
|
+
/**
|
|
36
|
+
* Open or create a database at *path*. Returns a Store handle synchronously.
|
|
37
|
+
*
|
|
38
|
+
* @param path - Filesystem path to the database directory.
|
|
39
|
+
* @param opts - Optional configuration.
|
|
40
|
+
*/
|
|
41
|
+
static open(path: string, opts?: Options): Store;
|
|
42
|
+
/** Filesystem path to the database directory. */
|
|
43
|
+
get path(): string;
|
|
44
|
+
/** Default database name. */
|
|
45
|
+
get defaultDb(): string;
|
|
46
|
+
/** Execute a SQL statement (DDL or DML). Returns rows affected. */
|
|
47
|
+
exec(sql: string): number;
|
|
48
|
+
/** Execute a SQL statement against a named database. Returns rows affected. */
|
|
49
|
+
execDb(db: string, sql: string): number;
|
|
50
|
+
/** Execute a SQL query and return result rows. */
|
|
51
|
+
query(sql: string): Rows;
|
|
52
|
+
/** Execute a SQL query against a named database. */
|
|
53
|
+
queryDb(db: string, sql: string): Rows;
|
|
54
|
+
/** Async wrapper for exec(). */
|
|
55
|
+
execAsync(sql: string): Promise<number>;
|
|
56
|
+
/** Async wrapper for query(). */
|
|
57
|
+
queryAsync(sql: string): Promise<Rows>;
|
|
58
|
+
/** Start a transaction. Use the transaction() helper instead when possible. */
|
|
59
|
+
begin(): Tx;
|
|
60
|
+
/**
|
|
61
|
+
* Run *fn* inside a transaction. Commits on success; rolls back on error.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* db.transaction(tx => {
|
|
66
|
+
* tx.exec('INSERT INTO ...')
|
|
67
|
+
* })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
transaction<T>(fn: (tx: Tx) => T): T;
|
|
71
|
+
/** Flush the WAL to disk. */
|
|
72
|
+
checkpoint(): void;
|
|
73
|
+
/** Close the database. Safe to call exactly once. */
|
|
74
|
+
close(): void;
|
|
75
|
+
private _checkOpen;
|
|
76
|
+
private _wrap;
|
|
77
|
+
}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store — Amarok-Lite embedded database engine for Node.js.
|
|
3
|
+
*
|
|
4
|
+
* Backed by the native `amarok-core` Rust engine via napi-rs.
|
|
5
|
+
* Provides the Amarok API surface so that code written for the Lite SDK
|
|
6
|
+
* is compatible with the full Amarok server SDK.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { Store } from '@dothift/amarok-lite'
|
|
10
|
+
* const db = Store.open('./myapp-data')
|
|
11
|
+
* db.exec("CREATE TABLE IF NOT EXISTS tickets (id TEXT PRIMARY KEY, data TEXT)")
|
|
12
|
+
* db.exec("INSERT INTO tickets VALUES ('t-001', '{\"title\": \"My ticket\"}')")
|
|
13
|
+
* const rows = db.query("SELECT * FROM tickets WHERE id = 't-001'")
|
|
14
|
+
* console.log(rows.fetchall())
|
|
15
|
+
* db.close()
|
|
16
|
+
*/
|
|
17
|
+
import { AmarokIntegrityError, AmarokOperationalError, AmarokProgrammingError } from './errors.js';
|
|
18
|
+
import { ensureEventLog } from './event-log.js';
|
|
19
|
+
import { Rows } from './rows.js';
|
|
20
|
+
import { Tx } from './transaction.js';
|
|
21
|
+
// Attempt to load the native napi-rs module. The module name is set by
|
|
22
|
+
// `napi.name` in package.json. When running from TypeScript source (tests),
|
|
23
|
+
// napi-rs also generates a JS shim that loads the .node binary.
|
|
24
|
+
let NativeStore;
|
|
25
|
+
try {
|
|
26
|
+
// Dynamic import so that TypeScript source can still load without the binary.
|
|
27
|
+
const mod = await import('./amarok-lite.js').catch(() => undefined);
|
|
28
|
+
NativeStore = mod?.AmarokStore;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
NativeStore = undefined;
|
|
32
|
+
}
|
|
33
|
+
const DEFAULT_OPTIONS = {
|
|
34
|
+
defaultDb: 'dhs',
|
|
35
|
+
enableEventLog: true,
|
|
36
|
+
};
|
|
37
|
+
export class Store {
|
|
38
|
+
_path;
|
|
39
|
+
_opts;
|
|
40
|
+
/** @internal */
|
|
41
|
+
_native;
|
|
42
|
+
_closed = false;
|
|
43
|
+
constructor(_path, native, _opts) {
|
|
44
|
+
this._path = _path;
|
|
45
|
+
this._opts = _opts;
|
|
46
|
+
this._native = native;
|
|
47
|
+
}
|
|
48
|
+
// ── Constructors ────────────────────────────────────────────────────────
|
|
49
|
+
/**
|
|
50
|
+
* Open or create a database at *path*. Returns a Store handle synchronously.
|
|
51
|
+
*
|
|
52
|
+
* @param path - Filesystem path to the database directory.
|
|
53
|
+
* @param opts - Optional configuration.
|
|
54
|
+
*/
|
|
55
|
+
static open(path, opts) {
|
|
56
|
+
if (!NativeStore) {
|
|
57
|
+
throw new AmarokOperationalError('amarok-lite native module not found. ' +
|
|
58
|
+
'Run `npm run build` inside sdk/node-lite/ to build it.');
|
|
59
|
+
}
|
|
60
|
+
const resolved = { ...DEFAULT_OPTIONS, ...opts };
|
|
61
|
+
let native;
|
|
62
|
+
try {
|
|
63
|
+
native = NativeStore.open(path, resolved.defaultDb);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
67
|
+
throw new AmarokOperationalError(`Cannot open database at ${path}: ${msg}`);
|
|
68
|
+
}
|
|
69
|
+
const store = new Store(path, native, resolved);
|
|
70
|
+
if (resolved.enableEventLog) {
|
|
71
|
+
ensureEventLog(store);
|
|
72
|
+
}
|
|
73
|
+
return store;
|
|
74
|
+
}
|
|
75
|
+
/** Filesystem path to the database directory. */
|
|
76
|
+
get path() {
|
|
77
|
+
return this._path;
|
|
78
|
+
}
|
|
79
|
+
/** Default database name. */
|
|
80
|
+
get defaultDb() {
|
|
81
|
+
return this._opts.defaultDb;
|
|
82
|
+
}
|
|
83
|
+
// ── Sync exec/query ─────────────────────────────────────────────────────
|
|
84
|
+
/** Execute a SQL statement (DDL or DML). Returns rows affected. */
|
|
85
|
+
exec(sql) {
|
|
86
|
+
this._checkOpen();
|
|
87
|
+
try {
|
|
88
|
+
return Number(this._native.exec(sql));
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
return this._wrap(e);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Execute a SQL statement against a named database. Returns rows affected. */
|
|
95
|
+
execDb(db, sql) {
|
|
96
|
+
this._checkOpen();
|
|
97
|
+
try {
|
|
98
|
+
return Number(this._native.execDb(db, sql));
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
return this._wrap(e);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Execute a SQL query and return result rows. */
|
|
105
|
+
query(sql) {
|
|
106
|
+
this._checkOpen();
|
|
107
|
+
try {
|
|
108
|
+
const rawRows = this._native.query(sql);
|
|
109
|
+
const columns = rawRows.length > 0 ? Object.keys(rawRows[0]) : [];
|
|
110
|
+
return new Rows(rawRows, columns);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
return this._wrap(e);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Execute a SQL query against a named database. */
|
|
117
|
+
queryDb(db, sql) {
|
|
118
|
+
this._checkOpen();
|
|
119
|
+
try {
|
|
120
|
+
const rawRows = this._native.queryDb(db, sql);
|
|
121
|
+
const columns = rawRows.length > 0 ? Object.keys(rawRows[0]) : [];
|
|
122
|
+
return new Rows(rawRows, columns);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
return this._wrap(e);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// ── Async wrappers ──────────────────────────────────────────────────────
|
|
129
|
+
// The native engine is synchronous; these wrappers let callers use await.
|
|
130
|
+
/** Async wrapper for exec(). */
|
|
131
|
+
async execAsync(sql) {
|
|
132
|
+
return this.exec(sql);
|
|
133
|
+
}
|
|
134
|
+
/** Async wrapper for query(). */
|
|
135
|
+
async queryAsync(sql) {
|
|
136
|
+
return this.query(sql);
|
|
137
|
+
}
|
|
138
|
+
// ── Transactions ────────────────────────────────────────────────────────
|
|
139
|
+
/** Start a transaction. Use the transaction() helper instead when possible. */
|
|
140
|
+
begin() {
|
|
141
|
+
this._checkOpen();
|
|
142
|
+
try {
|
|
143
|
+
this._native.exec('BEGIN');
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
this._wrap(e);
|
|
147
|
+
}
|
|
148
|
+
return new Tx(this._native, () => { this._native.exec('COMMIT'); }, () => { this._native.exec('ROLLBACK'); });
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Run *fn* inside a transaction. Commits on success; rolls back on error.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* db.transaction(tx => {
|
|
156
|
+
* tx.exec('INSERT INTO ...')
|
|
157
|
+
* })
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
transaction(fn) {
|
|
161
|
+
this._checkOpen();
|
|
162
|
+
const tx = this.begin();
|
|
163
|
+
try {
|
|
164
|
+
const result = fn(tx);
|
|
165
|
+
if (!tx['_done'])
|
|
166
|
+
tx.commit();
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
tx.rollback();
|
|
171
|
+
throw e;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
175
|
+
/** Flush the WAL to disk. */
|
|
176
|
+
checkpoint() {
|
|
177
|
+
this._checkOpen();
|
|
178
|
+
try {
|
|
179
|
+
this._native.checkpoint();
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
this._wrap(e);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Close the database. Safe to call exactly once. */
|
|
186
|
+
close() {
|
|
187
|
+
if (this._closed)
|
|
188
|
+
return;
|
|
189
|
+
try {
|
|
190
|
+
this._native.close();
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
this._closed = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ── Internal ─────────────────────────────────────────────────────────────
|
|
197
|
+
_checkOpen() {
|
|
198
|
+
if (this._closed)
|
|
199
|
+
throw new AmarokOperationalError('Store is closed');
|
|
200
|
+
}
|
|
201
|
+
_wrap(e) {
|
|
202
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
203
|
+
const lower = msg.toLowerCase();
|
|
204
|
+
if (lower.includes('unique') || lower.includes('constraint')) {
|
|
205
|
+
throw new AmarokIntegrityError(msg);
|
|
206
|
+
}
|
|
207
|
+
if (lower.includes('parse') || lower.includes('syntax') || lower.includes('plan')) {
|
|
208
|
+
throw new AmarokProgrammingError(msg);
|
|
209
|
+
}
|
|
210
|
+
throw new AmarokOperationalError(msg);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Rows } from './rows.js';
|
|
2
|
+
import type { Row } from './store.js';
|
|
3
|
+
/** @internal Native store interface used by Tx. */
|
|
4
|
+
interface NativeExec {
|
|
5
|
+
exec(sql: string): bigint | number;
|
|
6
|
+
query(sql: string): Row[];
|
|
7
|
+
}
|
|
8
|
+
/** An active database transaction. */
|
|
9
|
+
export declare class Tx {
|
|
10
|
+
private readonly _native;
|
|
11
|
+
private readonly _commitFn;
|
|
12
|
+
private readonly _rollbackFn;
|
|
13
|
+
/** @internal */
|
|
14
|
+
_done: boolean;
|
|
15
|
+
/** @internal */
|
|
16
|
+
constructor(_native: NativeExec, _commitFn: () => void, _rollbackFn: () => void);
|
|
17
|
+
/** Execute a SQL statement. Returns rows affected. */
|
|
18
|
+
exec(sql: string): number;
|
|
19
|
+
/** Execute a SQL query and return result rows. */
|
|
20
|
+
query(sql: string): Rows;
|
|
21
|
+
/** Commit the transaction. */
|
|
22
|
+
commit(): void;
|
|
23
|
+
/** Roll back the transaction. */
|
|
24
|
+
rollback(): void;
|
|
25
|
+
private _checkOpen;
|
|
26
|
+
private _wrap;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { AmarokIntegrityError, AmarokOperationalError, AmarokProgrammingError } from './errors.js';
|
|
2
|
+
import { Rows } from './rows.js';
|
|
3
|
+
/** An active database transaction. */
|
|
4
|
+
export class Tx {
|
|
5
|
+
_native;
|
|
6
|
+
_commitFn;
|
|
7
|
+
_rollbackFn;
|
|
8
|
+
/** @internal */
|
|
9
|
+
_done = false;
|
|
10
|
+
/** @internal */
|
|
11
|
+
constructor(_native, _commitFn, _rollbackFn) {
|
|
12
|
+
this._native = _native;
|
|
13
|
+
this._commitFn = _commitFn;
|
|
14
|
+
this._rollbackFn = _rollbackFn;
|
|
15
|
+
}
|
|
16
|
+
/** Execute a SQL statement. Returns rows affected. */
|
|
17
|
+
exec(sql) {
|
|
18
|
+
this._checkOpen();
|
|
19
|
+
try {
|
|
20
|
+
return Number(this._native.exec(sql));
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
return this._wrap(e);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Execute a SQL query and return result rows. */
|
|
27
|
+
query(sql) {
|
|
28
|
+
this._checkOpen();
|
|
29
|
+
try {
|
|
30
|
+
const rawRows = this._native.query(sql);
|
|
31
|
+
const columns = rawRows.length > 0 ? Object.keys(rawRows[0]) : [];
|
|
32
|
+
return new Rows(rawRows, columns);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
return this._wrap(e);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Commit the transaction. */
|
|
39
|
+
commit() {
|
|
40
|
+
this._checkOpen();
|
|
41
|
+
this._commitFn();
|
|
42
|
+
this._done = true;
|
|
43
|
+
}
|
|
44
|
+
/** Roll back the transaction. */
|
|
45
|
+
rollback() {
|
|
46
|
+
if (!this._done) {
|
|
47
|
+
this._rollbackFn();
|
|
48
|
+
this._done = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
_checkOpen() {
|
|
52
|
+
if (this._done)
|
|
53
|
+
throw new AmarokOperationalError('Transaction is already closed');
|
|
54
|
+
}
|
|
55
|
+
_wrap(e) {
|
|
56
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
57
|
+
const lower = msg.toLowerCase();
|
|
58
|
+
if (lower.includes('unique') || lower.includes('constraint')) {
|
|
59
|
+
throw new AmarokIntegrityError(msg);
|
|
60
|
+
}
|
|
61
|
+
if (lower.includes('parse') || lower.includes('syntax') || lower.includes('plan')) {
|
|
62
|
+
throw new AmarokProgrammingError(msg);
|
|
63
|
+
}
|
|
64
|
+
throw new AmarokOperationalError(msg);
|
|
65
|
+
}
|
|
66
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/* auto-generated by NAPI-RS */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Embedded Amarok database handle.
|
|
8
|
+
*
|
|
9
|
+
* Open with `AmarokStore.open(path)`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class AmarokStore {
|
|
12
|
+
/** Filesystem path to the database directory. */
|
|
13
|
+
readonly path: string
|
|
14
|
+
/**
|
|
15
|
+
* Open or create a database at *path*.
|
|
16
|
+
*
|
|
17
|
+
* @param path - Filesystem path to the database directory.
|
|
18
|
+
* @param defaultDb - Active database name. Defaults to `"dhs"`.
|
|
19
|
+
*/
|
|
20
|
+
static open(path: string, defaultDb?: string | undefined | null): AmarokStore
|
|
21
|
+
/** Execute a DML or DDL statement. Returns rows affected. */
|
|
22
|
+
exec(sql: string): number
|
|
23
|
+
/** Execute a SQL statement against a named database. Returns rows affected. */
|
|
24
|
+
execDb(db: string, sql: string): number
|
|
25
|
+
/** Execute a SELECT and return rows as an array of JSON objects. */
|
|
26
|
+
query(sql: string): Array<any>
|
|
27
|
+
/** Execute a SELECT against a named database. Returns rows as JSON objects. */
|
|
28
|
+
queryDb(db: string, sql: string): Array<any>
|
|
29
|
+
/** Flush the WAL to disk. */
|
|
30
|
+
checkpoint(): void
|
|
31
|
+
/** Checkpoint and mark the store as closed. */
|
|
32
|
+
close(): void
|
|
33
|
+
/** Return the default database name. */
|
|
34
|
+
get defaultDb(): string
|
|
35
|
+
}
|