quiverdb 0.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/README.md +108 -0
- package/package.json +39 -0
- package/src/create.ts +128 -0
- package/src/database.ts +68 -0
- package/src/errors.ts +24 -0
- package/src/ffi-helpers.ts +48 -0
- package/src/index.ts +8 -0
- package/src/loader.ts +286 -0
- package/src/query.ts +164 -0
- package/src/read.ts +231 -0
- package/src/transaction.ts +35 -0
- package/src/types.ts +5 -0
- package/tsconfig.json +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# quiverdb
|
|
2
|
+
|
|
3
|
+
Bun-native SQLite wrapper binding for Quiver via bun:ffi.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Bun >= 1.0.0
|
|
8
|
+
- `libquiver.dll` and `libquiver_c.dll` in system PATH
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bun add quiverdb
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The native Quiver DLLs must be compiled separately and available in your system PATH. See the Quiver project build instructions for details.
|
|
17
|
+
|
|
18
|
+
## DLL Setup
|
|
19
|
+
|
|
20
|
+
This binding loads the Quiver C API library (`libquiver_c.dll`) at runtime via `bun:ffi`. The core library (`libquiver.dll`) is a dependency that must also be loadable.
|
|
21
|
+
|
|
22
|
+
Options for making the DLLs available:
|
|
23
|
+
|
|
24
|
+
1. Add the `build/bin/` directory to your system PATH
|
|
25
|
+
2. Copy both DLLs to a directory already in PATH
|
|
26
|
+
3. Place both DLLs alongside your application entry point
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { Database } from "quiverdb";
|
|
32
|
+
|
|
33
|
+
const db = Database.fromSchema("my.db", "schema.sql");
|
|
34
|
+
db.createElement("Items", { label: "Item 1", value: 42 });
|
|
35
|
+
|
|
36
|
+
const values = db.readScalarIntegers("Items", "value");
|
|
37
|
+
console.log(values); // [42]
|
|
38
|
+
|
|
39
|
+
db.close();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## API Methods
|
|
43
|
+
|
|
44
|
+
### Lifecycle
|
|
45
|
+
|
|
46
|
+
- `Database.fromSchema(dbPath, schemaPath)` -- Create database from SQL schema file
|
|
47
|
+
- `Database.fromMigrations(dbPath, migrationsPath)` -- Create database from migrations directory
|
|
48
|
+
- `close()` -- Close the database connection
|
|
49
|
+
|
|
50
|
+
### Create / Delete
|
|
51
|
+
|
|
52
|
+
- `createElement(collection, data)` -- Create element, returns numeric ID
|
|
53
|
+
- `deleteElement(collection, id)` -- Delete element by ID
|
|
54
|
+
|
|
55
|
+
### Read (bulk)
|
|
56
|
+
|
|
57
|
+
- `readScalarIntegers(collection, attribute)` -- Read all integer scalars
|
|
58
|
+
- `readScalarFloats(collection, attribute)` -- Read all float scalars
|
|
59
|
+
- `readScalarStrings(collection, attribute)` -- Read all string scalars
|
|
60
|
+
|
|
61
|
+
### Read (by ID)
|
|
62
|
+
|
|
63
|
+
- `readScalarIntegerById(collection, attribute, id)` -- Read integer or null
|
|
64
|
+
- `readScalarFloatById(collection, attribute, id)` -- Read float or null
|
|
65
|
+
- `readScalarStringById(collection, attribute, id)` -- Read string or null
|
|
66
|
+
|
|
67
|
+
### Read (IDs)
|
|
68
|
+
|
|
69
|
+
- `readElementIds(collection)` -- Read all element IDs in a collection
|
|
70
|
+
|
|
71
|
+
### Query
|
|
72
|
+
|
|
73
|
+
- `queryString(sql, params?)` -- Query returning string or null
|
|
74
|
+
- `queryInteger(sql, params?)` -- Query returning integer or null
|
|
75
|
+
- `queryFloat(sql, params?)` -- Query returning float or null
|
|
76
|
+
|
|
77
|
+
Parameters are passed as an array of `number | string | null`.
|
|
78
|
+
|
|
79
|
+
### Transaction
|
|
80
|
+
|
|
81
|
+
- `beginTransaction()` -- Begin explicit transaction
|
|
82
|
+
- `commit()` -- Commit current transaction
|
|
83
|
+
- `rollback()` -- Rollback current transaction
|
|
84
|
+
- `inTransaction()` -- Check if transaction is active
|
|
85
|
+
|
|
86
|
+
## Types
|
|
87
|
+
|
|
88
|
+
Exported types available for TypeScript consumers:
|
|
89
|
+
|
|
90
|
+
- `ScalarValue` -- `number | bigint | string | null`
|
|
91
|
+
- `ArrayValue` -- `number[] | bigint[] | string[]`
|
|
92
|
+
- `Value` -- `ScalarValue | ArrayValue`
|
|
93
|
+
- `ElementData` -- `Record<string, Value | undefined>`
|
|
94
|
+
- `QueryParam` -- `number | string | null`
|
|
95
|
+
- `QuiverError` -- Error class for all Quiver operations
|
|
96
|
+
|
|
97
|
+
## Development
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
bun test # Run all tests
|
|
101
|
+
bun run typecheck # TypeScript type checking (tsc --noEmit)
|
|
102
|
+
bun run lint # Biome linting
|
|
103
|
+
bun run format # Biome formatting (auto-fix)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "quiverdb",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Bun-native SQLite wrapper binding for Quiver via bun:ffi",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src/",
|
|
13
|
+
"package.json",
|
|
14
|
+
"tsconfig.json",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"bun": ">=1.0.0"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"sqlite",
|
|
23
|
+
"database",
|
|
24
|
+
"ffi",
|
|
25
|
+
"bun",
|
|
26
|
+
"quiver"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "bun test",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"lint": "biome check",
|
|
32
|
+
"format": "biome format --write"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@biomejs/biome": "^2.4.6",
|
|
36
|
+
"@types/bun": "^1.3.10",
|
|
37
|
+
"typescript": "^5.9.3"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/create.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { type Pointer, ptr } from "bun:ffi";
|
|
2
|
+
import { Database } from "./database";
|
|
3
|
+
import { check, QuiverError } from "./errors";
|
|
4
|
+
import { allocPointerOut, readPointerOut, toCString } from "./ffi-helpers";
|
|
5
|
+
import { getSymbols } from "./loader";
|
|
6
|
+
import type { ElementData, Value } from "./types";
|
|
7
|
+
|
|
8
|
+
declare module "./database" {
|
|
9
|
+
interface Database {
|
|
10
|
+
createElement(collection: string, data: ElementData): number;
|
|
11
|
+
deleteElement(collection: string, id: number): void;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Symbols = ReturnType<typeof getSymbols>;
|
|
16
|
+
|
|
17
|
+
function setElementArray(lib: Symbols, elemPtr: Pointer, name: string, values: unknown[]): void {
|
|
18
|
+
const cName = toCString(name);
|
|
19
|
+
|
|
20
|
+
if (values.length === 0) {
|
|
21
|
+
// Empty array: pass null pointer with count 0
|
|
22
|
+
check(lib.quiver_element_set_array_integer(elemPtr, cName, null, 0));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const first = values[0];
|
|
27
|
+
|
|
28
|
+
if (typeof first === "bigint") {
|
|
29
|
+
const arr = BigInt64Array.from(values as bigint[], (v) => BigInt(v));
|
|
30
|
+
check(lib.quiver_element_set_array_integer(elemPtr, cName, ptr(arr), values.length));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof first === "number") {
|
|
35
|
+
const allIntegers = (values as number[]).every((v) => Number.isInteger(v));
|
|
36
|
+
if (allIntegers) {
|
|
37
|
+
const arr = BigInt64Array.from(values as number[], (v) => BigInt(v));
|
|
38
|
+
check(lib.quiver_element_set_array_integer(elemPtr, cName, ptr(arr), values.length));
|
|
39
|
+
} else {
|
|
40
|
+
const arr = Float64Array.from(values as number[]);
|
|
41
|
+
check(lib.quiver_element_set_array_float(elemPtr, cName, ptr(arr), values.length));
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof first === "string") {
|
|
47
|
+
// Build pointer table: BigInt64Array of pointers to each string buffer
|
|
48
|
+
const buffers = (values as string[]).map((s) => toCString(s));
|
|
49
|
+
const ptrTable = new BigInt64Array(buffers.length);
|
|
50
|
+
for (let i = 0; i < buffers.length; i++) {
|
|
51
|
+
ptrTable[i] = BigInt(ptr(buffers[i]));
|
|
52
|
+
}
|
|
53
|
+
check(lib.quiver_element_set_array_string(elemPtr, cName, ptr(ptrTable), values.length));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
throw new QuiverError(`Unsupported array element type for '${name}': ${typeof first}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function setElementField(lib: Symbols, elemPtr: Pointer, name: string, value: Value): void {
|
|
61
|
+
const cName = toCString(name);
|
|
62
|
+
|
|
63
|
+
if (value === null) {
|
|
64
|
+
check(lib.quiver_element_set_null(elemPtr, cName));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof value === "bigint") {
|
|
69
|
+
check(lib.quiver_element_set_integer(elemPtr, cName, value));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof value === "number") {
|
|
74
|
+
if (Number.isInteger(value)) {
|
|
75
|
+
check(lib.quiver_element_set_integer(elemPtr, cName, value));
|
|
76
|
+
} else {
|
|
77
|
+
check(lib.quiver_element_set_float(elemPtr, cName, value));
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (typeof value === "string") {
|
|
83
|
+
check(lib.quiver_element_set_string(elemPtr, cName, toCString(value)));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
setElementArray(lib, elemPtr, name, value);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
throw new QuiverError(`Unsupported value type for '${name}': ${typeof value}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Database.prototype.createElement = function (
|
|
96
|
+
this: Database,
|
|
97
|
+
collection: string,
|
|
98
|
+
data: ElementData,
|
|
99
|
+
): number {
|
|
100
|
+
const lib = getSymbols();
|
|
101
|
+
const handle = this._handle; // triggers ensureOpen
|
|
102
|
+
|
|
103
|
+
// Create element handle
|
|
104
|
+
const outElem = allocPointerOut();
|
|
105
|
+
check(lib.quiver_element_create(ptr(outElem)));
|
|
106
|
+
const elemPtr = readPointerOut(outElem);
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// Set fields from data object
|
|
110
|
+
for (const [key, value] of Object.entries(data)) {
|
|
111
|
+
if (value === undefined) continue;
|
|
112
|
+
setElementField(lib, elemPtr, key, value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Create element in database
|
|
116
|
+
const outId = new BigInt64Array(1);
|
|
117
|
+
check(lib.quiver_database_create_element(handle, toCString(collection), elemPtr, ptr(outId)));
|
|
118
|
+
|
|
119
|
+
return Number(outId[0]);
|
|
120
|
+
} finally {
|
|
121
|
+
lib.quiver_element_destroy(elemPtr);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
Database.prototype.deleteElement = function (this: Database, collection: string, id: number): void {
|
|
126
|
+
const lib = getSymbols();
|
|
127
|
+
check(lib.quiver_database_delete_element(this._handle, toCString(collection), id));
|
|
128
|
+
};
|
package/src/database.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type Pointer, ptr } from "bun:ffi";
|
|
2
|
+
import { check, QuiverError } from "./errors";
|
|
3
|
+
import { allocPointerOut, makeDefaultOptions, readPointerOut, toCString } from "./ffi-helpers";
|
|
4
|
+
import { getSymbols } from "./loader";
|
|
5
|
+
|
|
6
|
+
export class Database {
|
|
7
|
+
private _ptr: Pointer;
|
|
8
|
+
private _closed = false;
|
|
9
|
+
|
|
10
|
+
private constructor(nativePtr: Pointer) {
|
|
11
|
+
this._ptr = nativePtr;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static fromSchema(dbPath: string, schemaPath: string): Database {
|
|
15
|
+
const lib = getSymbols();
|
|
16
|
+
const options = makeDefaultOptions();
|
|
17
|
+
const outDb = allocPointerOut();
|
|
18
|
+
|
|
19
|
+
check(
|
|
20
|
+
lib.quiver_database_from_schema(
|
|
21
|
+
toCString(dbPath),
|
|
22
|
+
toCString(schemaPath),
|
|
23
|
+
ptr(options),
|
|
24
|
+
ptr(outDb),
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const dbPtr = readPointerOut(outDb);
|
|
29
|
+
return new Database(dbPtr);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static fromMigrations(dbPath: string, migrationsPath: string): Database {
|
|
33
|
+
const lib = getSymbols();
|
|
34
|
+
const options = makeDefaultOptions();
|
|
35
|
+
const outDb = allocPointerOut();
|
|
36
|
+
|
|
37
|
+
check(
|
|
38
|
+
lib.quiver_database_from_migrations(
|
|
39
|
+
toCString(dbPath),
|
|
40
|
+
toCString(migrationsPath),
|
|
41
|
+
ptr(options),
|
|
42
|
+
ptr(outDb),
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const dbPtr = readPointerOut(outDb);
|
|
47
|
+
return new Database(dbPtr);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
close(): void {
|
|
51
|
+
if (this._closed) return;
|
|
52
|
+
const lib = getSymbols();
|
|
53
|
+
check(lib.quiver_database_close(this._ptr));
|
|
54
|
+
this._closed = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private ensureOpen(): void {
|
|
58
|
+
if (this._closed) {
|
|
59
|
+
throw new QuiverError("Database is closed");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @internal */
|
|
64
|
+
get _handle(): Pointer {
|
|
65
|
+
this.ensureOpen();
|
|
66
|
+
return this._ptr;
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CString } from "bun:ffi";
|
|
2
|
+
import { getSymbols } from "./loader";
|
|
3
|
+
|
|
4
|
+
export class QuiverError extends Error {
|
|
5
|
+
constructor(message: string) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "QuiverError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function check(err: number): void {
|
|
12
|
+
if (err !== 0) {
|
|
13
|
+
const lib = getSymbols();
|
|
14
|
+
const errPtr = lib.quiver_get_last_error();
|
|
15
|
+
if (errPtr) {
|
|
16
|
+
const detail = new CString(errPtr).toString();
|
|
17
|
+
if (detail) {
|
|
18
|
+
throw new QuiverError(detail);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
console.warn("check: C API returned error but quiver_get_last_error() is empty");
|
|
22
|
+
throw new QuiverError("Unknown error");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Pointer, ptr, read } from "bun:ffi";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Construct the 8-byte quiver_database_options_t struct manually.
|
|
5
|
+
* Layout: offset 0 = int32 read_only (default 0), offset 4 = int32 console_level (default 1 = QUIVER_LOG_INFO).
|
|
6
|
+
* DO NOT call quiver_database_options_default() -- it returns struct by value which bun:ffi cannot handle.
|
|
7
|
+
*/
|
|
8
|
+
export function makeDefaultOptions(): Uint8Array {
|
|
9
|
+
const buf = new ArrayBuffer(8);
|
|
10
|
+
const view = new Int32Array(buf);
|
|
11
|
+
view[0] = 0; // read_only = false
|
|
12
|
+
view[1] = 1; // console_level = QUIVER_LOG_INFO
|
|
13
|
+
return new Uint8Array(buf);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Allocate a 1-element BigInt64Array (8 bytes) to receive a quiver_database_t** out-parameter.
|
|
18
|
+
* Caller passes ptr(result) to the FFI function.
|
|
19
|
+
*/
|
|
20
|
+
export function allocPointerOut(): BigInt64Array {
|
|
21
|
+
return new BigInt64Array(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read the pointer value from an out-parameter buffer.
|
|
26
|
+
* Dereferences the pointer stored at offset 0 of the typed array.
|
|
27
|
+
* Returns a branded Pointer suitable for passing to FFI functions.
|
|
28
|
+
* read.ptr() returns number at runtime; the cast to Pointer satisfies bun:ffi type signatures.
|
|
29
|
+
*/
|
|
30
|
+
export function readPointerOut(buf: BigInt64Array): Pointer {
|
|
31
|
+
return read.ptr(ptr(buf), 0) as unknown as Pointer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Read a pointer at the given byte offset from a base pointer.
|
|
36
|
+
* Wraps read.ptr() with a cast to the branded Pointer type.
|
|
37
|
+
*/
|
|
38
|
+
export function readPtrAt(base: Pointer, byteOffset: number): Pointer {
|
|
39
|
+
return read.ptr(base, byteOffset) as unknown as Pointer;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Encode a JS string as null-terminated UTF-8 for the C API.
|
|
44
|
+
* Returns a Buffer suitable for passing as FFIType.ptr.
|
|
45
|
+
*/
|
|
46
|
+
export function toCString(str: string): Buffer {
|
|
47
|
+
return Buffer.from(`${str}\0`);
|
|
48
|
+
}
|
package/src/index.ts
ADDED
package/src/loader.ts
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { dlopen, FFIType, suffix } from "bun:ffi";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { QuiverError } from "./errors";
|
|
5
|
+
|
|
6
|
+
const CORE_LIB = `libquiver.${suffix}`;
|
|
7
|
+
const C_API_LIB = `libquiver_c.${suffix}`;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pre-load a DLL on Windows using kernel32 LoadLibraryA.
|
|
11
|
+
* bun:ffi dlopen() requires at least one symbol, so it cannot be used
|
|
12
|
+
* for dependency-only pre-loading (libquiver.dll has no C API symbols).
|
|
13
|
+
* This is the equivalent of Python's os.add_dll_directory() approach.
|
|
14
|
+
*/
|
|
15
|
+
function preloadWindows(dllPath: string): void {
|
|
16
|
+
const kernel32 = dlopen("kernel32.dll", {
|
|
17
|
+
LoadLibraryA: { args: [FFIType.ptr], returns: FFIType.ptr },
|
|
18
|
+
});
|
|
19
|
+
kernel32.symbols.LoadLibraryA(Buffer.from(`${dllPath}\0`));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const symbols = {
|
|
23
|
+
quiver_version: {
|
|
24
|
+
args: [],
|
|
25
|
+
returns: FFIType.cstring,
|
|
26
|
+
},
|
|
27
|
+
quiver_get_last_error: {
|
|
28
|
+
args: [],
|
|
29
|
+
returns: FFIType.ptr,
|
|
30
|
+
},
|
|
31
|
+
quiver_clear_last_error: {
|
|
32
|
+
args: [],
|
|
33
|
+
returns: FFIType.void,
|
|
34
|
+
},
|
|
35
|
+
quiver_database_from_schema: {
|
|
36
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
37
|
+
returns: FFIType.i32,
|
|
38
|
+
},
|
|
39
|
+
quiver_database_from_migrations: {
|
|
40
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
41
|
+
returns: FFIType.i32,
|
|
42
|
+
},
|
|
43
|
+
quiver_database_close: {
|
|
44
|
+
args: [FFIType.ptr],
|
|
45
|
+
returns: FFIType.i32,
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Element lifecycle
|
|
49
|
+
quiver_element_create: {
|
|
50
|
+
args: [FFIType.ptr],
|
|
51
|
+
returns: FFIType.i32,
|
|
52
|
+
},
|
|
53
|
+
quiver_element_destroy: {
|
|
54
|
+
args: [FFIType.ptr],
|
|
55
|
+
returns: FFIType.i32,
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Element scalar setters
|
|
59
|
+
quiver_element_set_integer: {
|
|
60
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.i64],
|
|
61
|
+
returns: FFIType.i32,
|
|
62
|
+
},
|
|
63
|
+
quiver_element_set_float: {
|
|
64
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.f64],
|
|
65
|
+
returns: FFIType.i32,
|
|
66
|
+
},
|
|
67
|
+
quiver_element_set_string: {
|
|
68
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
69
|
+
returns: FFIType.i32,
|
|
70
|
+
},
|
|
71
|
+
quiver_element_set_null: {
|
|
72
|
+
args: [FFIType.ptr, FFIType.ptr],
|
|
73
|
+
returns: FFIType.i32,
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// Element array setters
|
|
77
|
+
quiver_element_set_array_integer: {
|
|
78
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.i32],
|
|
79
|
+
returns: FFIType.i32,
|
|
80
|
+
},
|
|
81
|
+
quiver_element_set_array_float: {
|
|
82
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.i32],
|
|
83
|
+
returns: FFIType.i32,
|
|
84
|
+
},
|
|
85
|
+
quiver_element_set_array_string: {
|
|
86
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.i32],
|
|
87
|
+
returns: FFIType.i32,
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
// Database CRUD
|
|
91
|
+
quiver_database_create_element: {
|
|
92
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
93
|
+
returns: FFIType.i32,
|
|
94
|
+
},
|
|
95
|
+
quiver_database_delete_element: {
|
|
96
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.i64],
|
|
97
|
+
returns: FFIType.i32,
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// Read scalar arrays
|
|
101
|
+
quiver_database_read_scalar_integers: {
|
|
102
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
103
|
+
returns: FFIType.i32,
|
|
104
|
+
},
|
|
105
|
+
quiver_database_read_scalar_floats: {
|
|
106
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
107
|
+
returns: FFIType.i32,
|
|
108
|
+
},
|
|
109
|
+
quiver_database_read_scalar_strings: {
|
|
110
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
111
|
+
returns: FFIType.i32,
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// Read scalar by ID
|
|
115
|
+
quiver_database_read_scalar_integer_by_id: {
|
|
116
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.i64, FFIType.ptr, FFIType.ptr],
|
|
117
|
+
returns: FFIType.i32,
|
|
118
|
+
},
|
|
119
|
+
quiver_database_read_scalar_float_by_id: {
|
|
120
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.i64, FFIType.ptr, FFIType.ptr],
|
|
121
|
+
returns: FFIType.i32,
|
|
122
|
+
},
|
|
123
|
+
quiver_database_read_scalar_string_by_id: {
|
|
124
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.i64, FFIType.ptr, FFIType.ptr],
|
|
125
|
+
returns: FFIType.i32,
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// Read element IDs
|
|
129
|
+
quiver_database_read_element_ids: {
|
|
130
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
131
|
+
returns: FFIType.i32,
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
// Free functions
|
|
135
|
+
quiver_database_free_integer_array: {
|
|
136
|
+
args: [FFIType.ptr],
|
|
137
|
+
returns: FFIType.i32,
|
|
138
|
+
},
|
|
139
|
+
quiver_database_free_float_array: {
|
|
140
|
+
args: [FFIType.ptr],
|
|
141
|
+
returns: FFIType.i32,
|
|
142
|
+
},
|
|
143
|
+
quiver_database_free_string_array: {
|
|
144
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
145
|
+
returns: FFIType.i32,
|
|
146
|
+
},
|
|
147
|
+
quiver_database_free_string: {
|
|
148
|
+
args: [FFIType.ptr],
|
|
149
|
+
returns: FFIType.i32,
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// Query operations (plain)
|
|
153
|
+
quiver_database_query_string: {
|
|
154
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
155
|
+
returns: FFIType.i32,
|
|
156
|
+
},
|
|
157
|
+
quiver_database_query_integer: {
|
|
158
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
159
|
+
returns: FFIType.i32,
|
|
160
|
+
},
|
|
161
|
+
quiver_database_query_float: {
|
|
162
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
163
|
+
returns: FFIType.i32,
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// Query operations (parameterized)
|
|
167
|
+
quiver_database_query_string_params: {
|
|
168
|
+
args: [
|
|
169
|
+
FFIType.ptr,
|
|
170
|
+
FFIType.ptr,
|
|
171
|
+
FFIType.ptr,
|
|
172
|
+
FFIType.ptr,
|
|
173
|
+
FFIType.u64,
|
|
174
|
+
FFIType.ptr,
|
|
175
|
+
FFIType.ptr,
|
|
176
|
+
],
|
|
177
|
+
returns: FFIType.i32,
|
|
178
|
+
},
|
|
179
|
+
quiver_database_query_integer_params: {
|
|
180
|
+
args: [
|
|
181
|
+
FFIType.ptr,
|
|
182
|
+
FFIType.ptr,
|
|
183
|
+
FFIType.ptr,
|
|
184
|
+
FFIType.ptr,
|
|
185
|
+
FFIType.u64,
|
|
186
|
+
FFIType.ptr,
|
|
187
|
+
FFIType.ptr,
|
|
188
|
+
],
|
|
189
|
+
returns: FFIType.i32,
|
|
190
|
+
},
|
|
191
|
+
quiver_database_query_float_params: {
|
|
192
|
+
args: [
|
|
193
|
+
FFIType.ptr,
|
|
194
|
+
FFIType.ptr,
|
|
195
|
+
FFIType.ptr,
|
|
196
|
+
FFIType.ptr,
|
|
197
|
+
FFIType.u64,
|
|
198
|
+
FFIType.ptr,
|
|
199
|
+
FFIType.ptr,
|
|
200
|
+
],
|
|
201
|
+
returns: FFIType.i32,
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// Transaction control
|
|
205
|
+
quiver_database_begin_transaction: {
|
|
206
|
+
args: [FFIType.ptr],
|
|
207
|
+
returns: FFIType.i32,
|
|
208
|
+
},
|
|
209
|
+
quiver_database_commit: {
|
|
210
|
+
args: [FFIType.ptr],
|
|
211
|
+
returns: FFIType.i32,
|
|
212
|
+
},
|
|
213
|
+
quiver_database_rollback: {
|
|
214
|
+
args: [FFIType.ptr],
|
|
215
|
+
returns: FFIType.i32,
|
|
216
|
+
},
|
|
217
|
+
quiver_database_in_transaction: {
|
|
218
|
+
args: [FFIType.ptr, FFIType.ptr],
|
|
219
|
+
returns: FFIType.i32,
|
|
220
|
+
},
|
|
221
|
+
} as const;
|
|
222
|
+
|
|
223
|
+
type Library = ReturnType<typeof dlopen<typeof symbols>>;
|
|
224
|
+
|
|
225
|
+
let _library: Library | null = null;
|
|
226
|
+
|
|
227
|
+
function getSearchPaths(): string[] {
|
|
228
|
+
const paths: string[] = [];
|
|
229
|
+
|
|
230
|
+
// Walk up from import.meta.dir (bindings/js/src/) looking for build/bin/
|
|
231
|
+
let dir = import.meta.dir;
|
|
232
|
+
for (let i = 0; i < 5; i++) {
|
|
233
|
+
const candidate = join(dir, "build", "bin");
|
|
234
|
+
if (existsSync(join(candidate, C_API_LIB))) {
|
|
235
|
+
paths.push(candidate);
|
|
236
|
+
}
|
|
237
|
+
dir = dirname(dir);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return paths;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function loadLibrary(): Library {
|
|
244
|
+
if (_library) return _library;
|
|
245
|
+
|
|
246
|
+
const searchPaths = getSearchPaths();
|
|
247
|
+
|
|
248
|
+
for (const dir of searchPaths) {
|
|
249
|
+
const corePath = join(dir, CORE_LIB);
|
|
250
|
+
const cApiPath = join(dir, C_API_LIB);
|
|
251
|
+
if (!existsSync(cApiPath)) continue;
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
// Windows: pre-load core library (dependency chain)
|
|
255
|
+
if (suffix === "dll" && existsSync(corePath)) {
|
|
256
|
+
preloadWindows(corePath);
|
|
257
|
+
}
|
|
258
|
+
_library = dlopen(cApiPath, symbols);
|
|
259
|
+
return _library;
|
|
260
|
+
} catch {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Fallback: try system PATH (bare library name)
|
|
264
|
+
try {
|
|
265
|
+
if (suffix === "dll") {
|
|
266
|
+
try {
|
|
267
|
+
preloadWindows(CORE_LIB);
|
|
268
|
+
} catch {
|
|
269
|
+
// Core lib may already be loaded or not needed on this platform
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
_library = dlopen(C_API_LIB, symbols);
|
|
273
|
+
return _library;
|
|
274
|
+
} catch {
|
|
275
|
+
// Fall through to error
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const searched =
|
|
279
|
+
searchPaths.length > 0 ? `${searchPaths.join(", ")}, system PATH` : "system PATH";
|
|
280
|
+
|
|
281
|
+
throw new QuiverError(`Cannot load native library '${C_API_LIB}'. Searched: ${searched}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function getSymbols(): Library["symbols"] {
|
|
285
|
+
return loadLibrary().symbols;
|
|
286
|
+
}
|
package/src/query.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { CString, ptr } from "bun:ffi";
|
|
2
|
+
import { Database } from "./database";
|
|
3
|
+
import { check } from "./errors";
|
|
4
|
+
import { allocPointerOut, readPointerOut, toCString } from "./ffi-helpers";
|
|
5
|
+
import { getSymbols } from "./loader";
|
|
6
|
+
import type { QueryParam } from "./types";
|
|
7
|
+
|
|
8
|
+
declare module "./database" {
|
|
9
|
+
interface Database {
|
|
10
|
+
queryString(sql: string, params?: QueryParam[]): string | null;
|
|
11
|
+
queryInteger(sql: string, params?: QueryParam[]): number | null;
|
|
12
|
+
queryFloat(sql: string, params?: QueryParam[]): number | null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// C API data type enum values (from include/quiver/c/database.h)
|
|
17
|
+
const DATA_TYPE_INTEGER = 0;
|
|
18
|
+
const DATA_TYPE_FLOAT = 1;
|
|
19
|
+
const DATA_TYPE_STRING = 2;
|
|
20
|
+
const DATA_TYPE_NULL = 4;
|
|
21
|
+
|
|
22
|
+
interface MarshaledParams {
|
|
23
|
+
types: Int32Array;
|
|
24
|
+
values: BigInt64Array;
|
|
25
|
+
_keepalive: unknown[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function marshalParams(params: QueryParam[]): MarshaledParams {
|
|
29
|
+
const n = params.length;
|
|
30
|
+
const types = new Int32Array(n);
|
|
31
|
+
const values = new BigInt64Array(n);
|
|
32
|
+
const keepalive: unknown[] = [];
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < n; i++) {
|
|
35
|
+
const p = params[i];
|
|
36
|
+
if (p === null) {
|
|
37
|
+
types[i] = DATA_TYPE_NULL;
|
|
38
|
+
values[i] = 0n;
|
|
39
|
+
} else if (typeof p === "number") {
|
|
40
|
+
if (Number.isInteger(p)) {
|
|
41
|
+
types[i] = DATA_TYPE_INTEGER;
|
|
42
|
+
const buf = new BigInt64Array([BigInt(p)]);
|
|
43
|
+
keepalive.push(buf);
|
|
44
|
+
values[i] = BigInt(ptr(buf));
|
|
45
|
+
} else {
|
|
46
|
+
types[i] = DATA_TYPE_FLOAT;
|
|
47
|
+
const buf = new Float64Array([p]);
|
|
48
|
+
keepalive.push(buf);
|
|
49
|
+
values[i] = BigInt(ptr(buf));
|
|
50
|
+
}
|
|
51
|
+
} else if (typeof p === "string") {
|
|
52
|
+
types[i] = DATA_TYPE_STRING;
|
|
53
|
+
const buf = toCString(p);
|
|
54
|
+
keepalive.push(buf);
|
|
55
|
+
values[i] = BigInt(ptr(buf));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { types, values, _keepalive: keepalive };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Database.prototype.queryString = function (
|
|
63
|
+
this: Database,
|
|
64
|
+
sql: string,
|
|
65
|
+
params?: QueryParam[],
|
|
66
|
+
): string | null {
|
|
67
|
+
const lib = getSymbols();
|
|
68
|
+
const handle = this._handle;
|
|
69
|
+
|
|
70
|
+
const outValue = allocPointerOut();
|
|
71
|
+
const outHasValue = new Int32Array(1);
|
|
72
|
+
|
|
73
|
+
if (params && params.length > 0) {
|
|
74
|
+
const marshaled = marshalParams(params);
|
|
75
|
+
check(
|
|
76
|
+
lib.quiver_database_query_string_params(
|
|
77
|
+
handle,
|
|
78
|
+
toCString(sql),
|
|
79
|
+
ptr(marshaled.types),
|
|
80
|
+
ptr(marshaled.values),
|
|
81
|
+
params.length,
|
|
82
|
+
ptr(outValue),
|
|
83
|
+
ptr(outHasValue),
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
check(
|
|
88
|
+
lib.quiver_database_query_string(handle, toCString(sql), ptr(outValue), ptr(outHasValue)),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (outHasValue[0] === 0) return null;
|
|
93
|
+
|
|
94
|
+
const strPtr = readPointerOut(outValue);
|
|
95
|
+
const result = new CString(strPtr).toString();
|
|
96
|
+
lib.quiver_database_free_string(strPtr);
|
|
97
|
+
return result;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
Database.prototype.queryInteger = function (
|
|
101
|
+
this: Database,
|
|
102
|
+
sql: string,
|
|
103
|
+
params?: QueryParam[],
|
|
104
|
+
): number | null {
|
|
105
|
+
const lib = getSymbols();
|
|
106
|
+
const handle = this._handle;
|
|
107
|
+
|
|
108
|
+
const outValue = new BigInt64Array(1);
|
|
109
|
+
const outHasValue = new Int32Array(1);
|
|
110
|
+
|
|
111
|
+
if (params && params.length > 0) {
|
|
112
|
+
const marshaled = marshalParams(params);
|
|
113
|
+
check(
|
|
114
|
+
lib.quiver_database_query_integer_params(
|
|
115
|
+
handle,
|
|
116
|
+
toCString(sql),
|
|
117
|
+
ptr(marshaled.types),
|
|
118
|
+
ptr(marshaled.values),
|
|
119
|
+
params.length,
|
|
120
|
+
ptr(outValue),
|
|
121
|
+
ptr(outHasValue),
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
check(
|
|
126
|
+
lib.quiver_database_query_integer(handle, toCString(sql), ptr(outValue), ptr(outHasValue)),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (outHasValue[0] === 0) return null;
|
|
131
|
+
return Number(outValue[0]);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
Database.prototype.queryFloat = function (
|
|
135
|
+
this: Database,
|
|
136
|
+
sql: string,
|
|
137
|
+
params?: QueryParam[],
|
|
138
|
+
): number | null {
|
|
139
|
+
const lib = getSymbols();
|
|
140
|
+
const handle = this._handle;
|
|
141
|
+
|
|
142
|
+
const outValue = new Float64Array(1);
|
|
143
|
+
const outHasValue = new Int32Array(1);
|
|
144
|
+
|
|
145
|
+
if (params && params.length > 0) {
|
|
146
|
+
const marshaled = marshalParams(params);
|
|
147
|
+
check(
|
|
148
|
+
lib.quiver_database_query_float_params(
|
|
149
|
+
handle,
|
|
150
|
+
toCString(sql),
|
|
151
|
+
ptr(marshaled.types),
|
|
152
|
+
ptr(marshaled.values),
|
|
153
|
+
params.length,
|
|
154
|
+
ptr(outValue),
|
|
155
|
+
ptr(outHasValue),
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
check(lib.quiver_database_query_float(handle, toCString(sql), ptr(outValue), ptr(outHasValue)));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (outHasValue[0] === 0) return null;
|
|
163
|
+
return outValue[0];
|
|
164
|
+
};
|
package/src/read.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { CString, ptr, toArrayBuffer } from "bun:ffi";
|
|
2
|
+
import { Database } from "./database";
|
|
3
|
+
import { check } from "./errors";
|
|
4
|
+
import { allocPointerOut, readPointerOut, readPtrAt, toCString } from "./ffi-helpers";
|
|
5
|
+
import { getSymbols } from "./loader";
|
|
6
|
+
|
|
7
|
+
declare module "./database" {
|
|
8
|
+
interface Database {
|
|
9
|
+
readScalarIntegers(collection: string, attribute: string): number[];
|
|
10
|
+
readScalarFloats(collection: string, attribute: string): number[];
|
|
11
|
+
readScalarStrings(collection: string, attribute: string): string[];
|
|
12
|
+
readScalarIntegerById(collection: string, attribute: string, id: number): number | null;
|
|
13
|
+
readScalarFloatById(collection: string, attribute: string, id: number): number | null;
|
|
14
|
+
readScalarStringById(collection: string, attribute: string, id: number): string | null;
|
|
15
|
+
readElementIds(collection: string): number[];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
Database.prototype.readScalarIntegers = function (
|
|
20
|
+
this: Database,
|
|
21
|
+
collection: string,
|
|
22
|
+
attribute: string,
|
|
23
|
+
): number[] {
|
|
24
|
+
const lib = getSymbols();
|
|
25
|
+
const handle = this._handle;
|
|
26
|
+
|
|
27
|
+
const outValues = allocPointerOut();
|
|
28
|
+
const outCount = new BigUint64Array(1);
|
|
29
|
+
|
|
30
|
+
check(
|
|
31
|
+
lib.quiver_database_read_scalar_integers(
|
|
32
|
+
handle,
|
|
33
|
+
toCString(collection),
|
|
34
|
+
toCString(attribute),
|
|
35
|
+
ptr(outValues),
|
|
36
|
+
ptr(outCount),
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const count = Number(outCount[0]);
|
|
41
|
+
if (count === 0) return [];
|
|
42
|
+
|
|
43
|
+
const valuesPtr = readPointerOut(outValues);
|
|
44
|
+
const buffer = toArrayBuffer(valuesPtr, 0, count * 8);
|
|
45
|
+
const bigInts = new BigInt64Array(buffer);
|
|
46
|
+
const result: number[] = new Array(count);
|
|
47
|
+
for (let i = 0; i < count; i++) {
|
|
48
|
+
result[i] = Number(bigInts[i]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
lib.quiver_database_free_integer_array(valuesPtr);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
Database.prototype.readScalarFloats = function (
|
|
56
|
+
this: Database,
|
|
57
|
+
collection: string,
|
|
58
|
+
attribute: string,
|
|
59
|
+
): number[] {
|
|
60
|
+
const lib = getSymbols();
|
|
61
|
+
const handle = this._handle;
|
|
62
|
+
|
|
63
|
+
const outValues = allocPointerOut();
|
|
64
|
+
const outCount = new BigUint64Array(1);
|
|
65
|
+
|
|
66
|
+
check(
|
|
67
|
+
lib.quiver_database_read_scalar_floats(
|
|
68
|
+
handle,
|
|
69
|
+
toCString(collection),
|
|
70
|
+
toCString(attribute),
|
|
71
|
+
ptr(outValues),
|
|
72
|
+
ptr(outCount),
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const count = Number(outCount[0]);
|
|
77
|
+
if (count === 0) return [];
|
|
78
|
+
|
|
79
|
+
const valuesPtr = readPointerOut(outValues);
|
|
80
|
+
const buffer = toArrayBuffer(valuesPtr, 0, count * 8);
|
|
81
|
+
const result = Array.from(new Float64Array(buffer));
|
|
82
|
+
|
|
83
|
+
lib.quiver_database_free_float_array(valuesPtr);
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
Database.prototype.readScalarStrings = function (
|
|
88
|
+
this: Database,
|
|
89
|
+
collection: string,
|
|
90
|
+
attribute: string,
|
|
91
|
+
): string[] {
|
|
92
|
+
const lib = getSymbols();
|
|
93
|
+
const handle = this._handle;
|
|
94
|
+
|
|
95
|
+
const outValues = allocPointerOut();
|
|
96
|
+
const outCount = new BigUint64Array(1);
|
|
97
|
+
|
|
98
|
+
check(
|
|
99
|
+
lib.quiver_database_read_scalar_strings(
|
|
100
|
+
handle,
|
|
101
|
+
toCString(collection),
|
|
102
|
+
toCString(attribute),
|
|
103
|
+
ptr(outValues),
|
|
104
|
+
ptr(outCount),
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const count = Number(outCount[0]);
|
|
109
|
+
if (count === 0) return [];
|
|
110
|
+
|
|
111
|
+
const arrPtr = readPointerOut(outValues);
|
|
112
|
+
const result: string[] = new Array(count);
|
|
113
|
+
for (let i = 0; i < count; i++) {
|
|
114
|
+
const strPtr = readPtrAt(arrPtr, i * 8);
|
|
115
|
+
result[i] = new CString(strPtr).toString();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
lib.quiver_database_free_string_array(arrPtr, count);
|
|
119
|
+
return result;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
Database.prototype.readScalarIntegerById = function (
|
|
123
|
+
this: Database,
|
|
124
|
+
collection: string,
|
|
125
|
+
attribute: string,
|
|
126
|
+
id: number,
|
|
127
|
+
): number | null {
|
|
128
|
+
const lib = getSymbols();
|
|
129
|
+
const handle = this._handle;
|
|
130
|
+
|
|
131
|
+
const outValue = new BigInt64Array(1);
|
|
132
|
+
const outHasValue = new Int32Array(1);
|
|
133
|
+
|
|
134
|
+
check(
|
|
135
|
+
lib.quiver_database_read_scalar_integer_by_id(
|
|
136
|
+
handle,
|
|
137
|
+
toCString(collection),
|
|
138
|
+
toCString(attribute),
|
|
139
|
+
id,
|
|
140
|
+
ptr(outValue),
|
|
141
|
+
ptr(outHasValue),
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (outHasValue[0] === 0) return null;
|
|
146
|
+
return Number(outValue[0]);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
Database.prototype.readScalarFloatById = function (
|
|
150
|
+
this: Database,
|
|
151
|
+
collection: string,
|
|
152
|
+
attribute: string,
|
|
153
|
+
id: number,
|
|
154
|
+
): number | null {
|
|
155
|
+
const lib = getSymbols();
|
|
156
|
+
const handle = this._handle;
|
|
157
|
+
|
|
158
|
+
const outValue = new Float64Array(1);
|
|
159
|
+
const outHasValue = new Int32Array(1);
|
|
160
|
+
|
|
161
|
+
check(
|
|
162
|
+
lib.quiver_database_read_scalar_float_by_id(
|
|
163
|
+
handle,
|
|
164
|
+
toCString(collection),
|
|
165
|
+
toCString(attribute),
|
|
166
|
+
id,
|
|
167
|
+
ptr(outValue),
|
|
168
|
+
ptr(outHasValue),
|
|
169
|
+
),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
if (outHasValue[0] === 0) return null;
|
|
173
|
+
return outValue[0];
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
Database.prototype.readScalarStringById = function (
|
|
177
|
+
this: Database,
|
|
178
|
+
collection: string,
|
|
179
|
+
attribute: string,
|
|
180
|
+
id: number,
|
|
181
|
+
): string | null {
|
|
182
|
+
const lib = getSymbols();
|
|
183
|
+
const handle = this._handle;
|
|
184
|
+
|
|
185
|
+
const outValue = allocPointerOut();
|
|
186
|
+
const outHasValue = new Int32Array(1);
|
|
187
|
+
|
|
188
|
+
check(
|
|
189
|
+
lib.quiver_database_read_scalar_string_by_id(
|
|
190
|
+
handle,
|
|
191
|
+
toCString(collection),
|
|
192
|
+
toCString(attribute),
|
|
193
|
+
id,
|
|
194
|
+
ptr(outValue),
|
|
195
|
+
ptr(outHasValue),
|
|
196
|
+
),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (outHasValue[0] === 0) return null;
|
|
200
|
+
|
|
201
|
+
const strPtr = readPointerOut(outValue);
|
|
202
|
+
const result = new CString(strPtr).toString();
|
|
203
|
+
lib.quiver_database_free_string(strPtr);
|
|
204
|
+
return result;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
Database.prototype.readElementIds = function (this: Database, collection: string): number[] {
|
|
208
|
+
const lib = getSymbols();
|
|
209
|
+
const handle = this._handle;
|
|
210
|
+
|
|
211
|
+
const outIds = allocPointerOut();
|
|
212
|
+
const outCount = new BigUint64Array(1);
|
|
213
|
+
|
|
214
|
+
check(
|
|
215
|
+
lib.quiver_database_read_element_ids(handle, toCString(collection), ptr(outIds), ptr(outCount)),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const count = Number(outCount[0]);
|
|
219
|
+
if (count === 0) return [];
|
|
220
|
+
|
|
221
|
+
const idsPtr = readPointerOut(outIds);
|
|
222
|
+
const buffer = toArrayBuffer(idsPtr, 0, count * 8);
|
|
223
|
+
const bigInts = new BigInt64Array(buffer);
|
|
224
|
+
const result: number[] = new Array(count);
|
|
225
|
+
for (let i = 0; i < count; i++) {
|
|
226
|
+
result[i] = Number(bigInts[i]);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
lib.quiver_database_free_integer_array(idsPtr);
|
|
230
|
+
return result;
|
|
231
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ptr } from "bun:ffi";
|
|
2
|
+
import { Database } from "./database";
|
|
3
|
+
import { check } from "./errors";
|
|
4
|
+
import { getSymbols } from "./loader";
|
|
5
|
+
|
|
6
|
+
declare module "./database" {
|
|
7
|
+
interface Database {
|
|
8
|
+
beginTransaction(): void;
|
|
9
|
+
commit(): void;
|
|
10
|
+
rollback(): void;
|
|
11
|
+
inTransaction(): boolean;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Database.prototype.beginTransaction = function (this: Database): void {
|
|
16
|
+
const lib = getSymbols();
|
|
17
|
+
check(lib.quiver_database_begin_transaction(this._handle));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
Database.prototype.commit = function (this: Database): void {
|
|
21
|
+
const lib = getSymbols();
|
|
22
|
+
check(lib.quiver_database_commit(this._handle));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
Database.prototype.rollback = function (this: Database): void {
|
|
26
|
+
const lib = getSymbols();
|
|
27
|
+
check(lib.quiver_database_rollback(this._handle));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
Database.prototype.inTransaction = function (this: Database): boolean {
|
|
31
|
+
const lib = getSymbols();
|
|
32
|
+
const outActive = new Uint8Array(1);
|
|
33
|
+
check(lib.quiver_database_in_transaction(this._handle, ptr(outActive)));
|
|
34
|
+
return outActive[0] !== 0;
|
|
35
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type ScalarValue = number | bigint | string | null;
|
|
2
|
+
export type ArrayValue = number[] | bigint[] | string[];
|
|
3
|
+
export type Value = ScalarValue | ArrayValue;
|
|
4
|
+
export type ElementData = Record<string, Value | undefined>;
|
|
5
|
+
export type QueryParam = number | string | null;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"types": ["@types/bun"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*.ts", "test/**/*.ts"]
|
|
13
|
+
}
|