quiverdb 0.6.2 → 0.9.2
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/LICENSE +21 -0
- package/README.md +95 -17
- package/libs/{linux-x64 → linux-x86_64}/libquiver.so +0 -0
- package/libs/{linux-x64 → linux-x86_64}/libquiver.so.0 +0 -0
- package/libs/linux-x86_64/libquiver_c.so +0 -0
- package/libs/{win32-x64 → windows-x86_64}/libquiver.dll +0 -0
- package/libs/windows-x86_64/libquiver_c.dll +0 -0
- package/mod.ts +25 -0
- package/package.json +17 -31
- package/src/composites.ts +89 -0
- package/src/create.ts +152 -0
- package/src/csv.ts +118 -0
- package/src/database.ts +171 -0
- package/src/errors.ts +21 -0
- package/src/ffi-helpers.ts +157 -0
- package/src/index.ts +17 -0
- package/src/introspection.ts +33 -0
- package/src/loader.ts +285 -0
- package/src/lua-runner.ts +48 -0
- package/src/metadata.ts +218 -0
- package/src/query.ts +160 -0
- package/src/read.ts +572 -0
- package/src/time-series.ts +291 -0
- package/src/transaction.ts +27 -0
- package/{dist/types.d.ts → src/types.ts} +8 -1
- package/dist/composites.d.ts +0 -9
- package/dist/composites.d.ts.map +0 -1
- package/dist/composites.js +0 -69
- package/dist/composites.js.map +0 -1
- package/dist/create.d.ts +0 -9
- package/dist/create.d.ts.map +0 -1
- package/dist/create.js +0 -104
- package/dist/create.js.map +0 -1
- package/dist/csv.d.ts +0 -11
- package/dist/csv.d.ts.map +0 -1
- package/dist/csv.js +0 -67
- package/dist/csv.js.map +0 -1
- package/dist/database.d.ts +0 -13
- package/dist/database.d.ts.map +0 -1
- package/dist/database.js +0 -42
- package/dist/database.js.map +0 -1
- package/dist/errors.d.ts +0 -5
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -18
- package/dist/errors.js.map +0 -1
- package/dist/ffi-helpers.d.ts +0 -57
- package/dist/ffi-helpers.d.ts.map +0 -1
- package/dist/ffi-helpers.js +0 -136
- package/dist/ffi-helpers.js.map +0 -1
- package/dist/index.d.ts +0 -17
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -13
- package/dist/index.js.map +0 -1
- package/dist/introspection.d.ts +0 -10
- package/dist/introspection.d.ts.map +0 -1
- package/dist/introspection.js +0 -27
- package/dist/introspection.js.map +0 -1
- package/dist/loader.d.ts +0 -7
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js +0 -192
- package/dist/loader.js.map +0 -1
- package/dist/lua-runner.d.ts +0 -11
- package/dist/lua-runner.d.ts.map +0 -1
- package/dist/lua-runner.js +0 -43
- package/dist/lua-runner.js.map +0 -1
- package/dist/metadata.d.ts +0 -28
- package/dist/metadata.d.ts.map +0 -1
- package/dist/metadata.js +0 -100
- package/dist/metadata.js.map +0 -1
- package/dist/query.d.ts +0 -9
- package/dist/query.d.ts.map +0 -1
- package/dist/query.js +0 -90
- package/dist/query.js.map +0 -1
- package/dist/read.d.ts +0 -25
- package/dist/read.d.ts.map +0 -1
- package/dist/read.js +0 -220
- package/dist/read.js.map +0 -1
- package/dist/time-series.d.ts +0 -12
- package/dist/time-series.d.ts.map +0 -1
- package/dist/time-series.js +0 -155
- package/dist/time-series.js.map +0 -1
- package/dist/transaction.d.ts +0 -10
- package/dist/transaction.d.ts.map +0 -1
- package/dist/transaction.js +0 -22
- package/dist/transaction.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/libs/linux-x64/libquiver.so.0.6.2 +0 -0
- package/libs/linux-x64/libquiver_c.so +0 -0
- package/libs/linux-x64/libquiver_c.so.0 +0 -0
- package/libs/linux-x64/libquiver_c.so.0.6.2 +0 -0
- package/libs/win32-x64/libquiver_c.dll +0 -0
package/src/database.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { CsvOptions } from "./csv.ts";
|
|
2
|
+
import { check, QuiverError } from "./errors.ts";
|
|
3
|
+
import { allocPtrOut, makeDefaultOptions, readPtrOut, toCString } from "./ffi-helpers.ts";
|
|
4
|
+
import type { NativePointer } from "./loader.ts";
|
|
5
|
+
import { getSymbols } from "./loader.ts";
|
|
6
|
+
import type { GroupMetadata, ScalarMetadata } from "./metadata.ts";
|
|
7
|
+
import type { TimeSeriesData } from "./time-series.ts";
|
|
8
|
+
import type { ElementData, QueryParam } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
export class Database {
|
|
11
|
+
private _ptr: NativePointer;
|
|
12
|
+
private _closed = false;
|
|
13
|
+
|
|
14
|
+
private constructor(nativePtr: NativePointer) {
|
|
15
|
+
this._ptr = nativePtr;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static fromSchema(dbPath: string, schemaPath: string): Database {
|
|
19
|
+
const lib = getSymbols();
|
|
20
|
+
const options = makeDefaultOptions();
|
|
21
|
+
const outDb = allocPtrOut();
|
|
22
|
+
const dbPathBuf = toCString(dbPath);
|
|
23
|
+
const schemaPathBuf = toCString(schemaPath);
|
|
24
|
+
|
|
25
|
+
check(
|
|
26
|
+
lib.quiver_database_from_schema(dbPathBuf.buf, schemaPathBuf.buf, options.buf, outDb.ptr),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return new Database(readPtrOut(outDb));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static fromMigrations(dbPath: string, migrationsPath: string): Database {
|
|
33
|
+
const lib = getSymbols();
|
|
34
|
+
const options = makeDefaultOptions();
|
|
35
|
+
const outDb = allocPtrOut();
|
|
36
|
+
const dbPathBuf = toCString(dbPath);
|
|
37
|
+
const migrPathBuf = toCString(migrationsPath);
|
|
38
|
+
|
|
39
|
+
check(
|
|
40
|
+
lib.quiver_database_from_migrations(dbPathBuf.buf, migrPathBuf.buf, options.buf, outDb.ptr),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return new Database(readPtrOut(outDb));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static open(dbPath: string): Database {
|
|
47
|
+
const lib = getSymbols();
|
|
48
|
+
const options = makeDefaultOptions();
|
|
49
|
+
const outDb = allocPtrOut();
|
|
50
|
+
const dbPathBuf = toCString(dbPath);
|
|
51
|
+
|
|
52
|
+
check(lib.quiver_database_open(dbPathBuf.buf, options.buf, outDb.ptr));
|
|
53
|
+
|
|
54
|
+
return new Database(readPtrOut(outDb));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
close(): void {
|
|
58
|
+
if (this._closed) return;
|
|
59
|
+
const lib = getSymbols();
|
|
60
|
+
check(lib.quiver_database_close(this._ptr));
|
|
61
|
+
this._closed = true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private ensureOpen(): void {
|
|
65
|
+
if (this._closed) {
|
|
66
|
+
throw new QuiverError("Database is closed");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @internal */
|
|
71
|
+
get _handle(): NativePointer {
|
|
72
|
+
this.ensureOpen();
|
|
73
|
+
return this._ptr;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- Element CRUD (implemented in create.ts) ---
|
|
77
|
+
declare createElement: (collection: string, data: ElementData) => number;
|
|
78
|
+
declare updateElement: (collection: string, id: number, data: ElementData) => void;
|
|
79
|
+
declare deleteElement: (collection: string, id: number) => void;
|
|
80
|
+
|
|
81
|
+
// --- Reads (implemented in read.ts) ---
|
|
82
|
+
declare readScalarIntegers: (collection: string, attribute: string) => number[];
|
|
83
|
+
declare readScalarFloats: (collection: string, attribute: string) => number[];
|
|
84
|
+
declare readScalarStrings: (collection: string, attribute: string) => string[];
|
|
85
|
+
declare readScalarIntegerById: (
|
|
86
|
+
collection: string,
|
|
87
|
+
attribute: string,
|
|
88
|
+
id: number,
|
|
89
|
+
) => number | null;
|
|
90
|
+
declare readScalarFloatById: (collection: string, attribute: string, id: number) => number | null;
|
|
91
|
+
declare readScalarStringById: (
|
|
92
|
+
collection: string,
|
|
93
|
+
attribute: string,
|
|
94
|
+
id: number,
|
|
95
|
+
) => string | null;
|
|
96
|
+
declare readElementIds: (collection: string) => number[];
|
|
97
|
+
declare readVectorIntegers: (collection: string, attribute: string) => number[][];
|
|
98
|
+
declare readVectorFloats: (collection: string, attribute: string) => number[][];
|
|
99
|
+
declare readVectorStrings: (collection: string, attribute: string) => string[][];
|
|
100
|
+
declare readVectorIntegersById: (collection: string, attribute: string, id: number) => number[];
|
|
101
|
+
declare readVectorFloatsById: (collection: string, attribute: string, id: number) => number[];
|
|
102
|
+
declare readVectorStringsById: (collection: string, attribute: string, id: number) => string[];
|
|
103
|
+
declare readSetIntegers: (collection: string, attribute: string) => number[][];
|
|
104
|
+
declare readSetFloats: (collection: string, attribute: string) => number[][];
|
|
105
|
+
declare readSetStrings: (collection: string, attribute: string) => string[][];
|
|
106
|
+
declare readSetIntegersById: (collection: string, attribute: string, id: number) => number[];
|
|
107
|
+
declare readSetFloatsById: (collection: string, attribute: string, id: number) => number[];
|
|
108
|
+
declare readSetStringsById: (collection: string, attribute: string, id: number) => string[];
|
|
109
|
+
|
|
110
|
+
// --- Queries (implemented in query.ts) ---
|
|
111
|
+
declare queryString: (sql: string, parameters?: QueryParam[]) => string | null;
|
|
112
|
+
declare queryInteger: (sql: string, parameters?: QueryParam[]) => number | null;
|
|
113
|
+
declare queryFloat: (sql: string, parameters?: QueryParam[]) => number | null;
|
|
114
|
+
|
|
115
|
+
// --- Transactions (implemented in transaction.ts) ---
|
|
116
|
+
declare beginTransaction: () => void;
|
|
117
|
+
declare commit: () => void;
|
|
118
|
+
declare rollback: () => void;
|
|
119
|
+
declare inTransaction: () => boolean;
|
|
120
|
+
|
|
121
|
+
// --- Metadata (implemented in metadata.ts) ---
|
|
122
|
+
declare getScalarMetadata: (collection: string, attribute: string) => ScalarMetadata;
|
|
123
|
+
declare getVectorMetadata: (collection: string, groupName: string) => GroupMetadata;
|
|
124
|
+
declare getSetMetadata: (collection: string, groupName: string) => GroupMetadata;
|
|
125
|
+
declare getTimeSeriesMetadata: (collection: string, groupName: string) => GroupMetadata;
|
|
126
|
+
declare listScalarAttributes: (collection: string) => ScalarMetadata[];
|
|
127
|
+
declare listVectorGroups: (collection: string) => GroupMetadata[];
|
|
128
|
+
declare listSetGroups: (collection: string) => GroupMetadata[];
|
|
129
|
+
declare listTimeSeriesGroups: (collection: string) => GroupMetadata[];
|
|
130
|
+
|
|
131
|
+
// --- Time series (implemented in time-series.ts) ---
|
|
132
|
+
declare readTimeSeriesGroup: (collection: string, group: string, id: number) => TimeSeriesData;
|
|
133
|
+
declare updateTimeSeriesGroup: (
|
|
134
|
+
collection: string,
|
|
135
|
+
group: string,
|
|
136
|
+
id: number,
|
|
137
|
+
data: TimeSeriesData,
|
|
138
|
+
) => void;
|
|
139
|
+
declare hasTimeSeriesFiles: (collection: string) => boolean;
|
|
140
|
+
declare listTimeSeriesFilesColumns: (collection: string) => string[];
|
|
141
|
+
declare readTimeSeriesFiles: (collection: string) => Record<string, string | null>;
|
|
142
|
+
declare updateTimeSeriesFiles: (collection: string, data: Record<string, string | null>) => void;
|
|
143
|
+
|
|
144
|
+
// --- CSV (implemented in csv.ts) ---
|
|
145
|
+
declare exportCsv: (
|
|
146
|
+
collection: string,
|
|
147
|
+
group: string,
|
|
148
|
+
filePath: string,
|
|
149
|
+
options?: CsvOptions,
|
|
150
|
+
) => void;
|
|
151
|
+
declare importCsv: (
|
|
152
|
+
collection: string,
|
|
153
|
+
group: string,
|
|
154
|
+
filePath: string,
|
|
155
|
+
options?: CsvOptions,
|
|
156
|
+
) => void;
|
|
157
|
+
|
|
158
|
+
// --- Introspection (implemented in introspection.ts) ---
|
|
159
|
+
declare isHealthy: () => boolean;
|
|
160
|
+
declare currentVersion: () => number;
|
|
161
|
+
declare path: () => string;
|
|
162
|
+
declare describe: () => void;
|
|
163
|
+
|
|
164
|
+
// --- Composite helpers (implemented in composites.ts) ---
|
|
165
|
+
declare readScalarsById: (
|
|
166
|
+
collection: string,
|
|
167
|
+
id: number,
|
|
168
|
+
) => Record<string, number | string | null>;
|
|
169
|
+
declare readVectorsById: (collection: string, id: number) => Record<string, number[] | string[]>;
|
|
170
|
+
declare readSetsById: (collection: string, id: number) => Record<string, number[] | string[]>;
|
|
171
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CString } from "bun:ffi";
|
|
2
|
+
import { getSymbols } from "./loader.ts";
|
|
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 ptr = lib.quiver_get_last_error();
|
|
15
|
+
if (ptr) {
|
|
16
|
+
const detail = new CString(ptr).toString();
|
|
17
|
+
throw new QuiverError(detail);
|
|
18
|
+
}
|
|
19
|
+
throw new QuiverError("Unknown error");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { CString, type Pointer, ptr, read, toArrayBuffer } from "bun:ffi";
|
|
2
|
+
import type { Allocation } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
const encoder = new TextEncoder();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Construct the 8-byte quiver_database_options_t struct as an Allocation.
|
|
8
|
+
* Layout: offset 0 = int32 read_only (default 0), offset 4 = int32 console_level (default 1 = QUIVER_LOG_INFO).
|
|
9
|
+
*/
|
|
10
|
+
export function makeDefaultOptions(): Allocation {
|
|
11
|
+
const buf = new Uint8Array(8);
|
|
12
|
+
const dv = new DataView(buf.buffer);
|
|
13
|
+
dv.setInt32(0, 0, true); // read_only = false
|
|
14
|
+
dv.setInt32(4, 1, true); // console_level = QUIVER_LOG_INFO
|
|
15
|
+
return { ptr: ptr(buf), buf };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Allocate an 8-byte buffer for a pointer out-parameter. */
|
|
19
|
+
export function allocPtrOut(): Allocation {
|
|
20
|
+
const buf = new Uint8Array(8);
|
|
21
|
+
return { ptr: ptr(buf), buf };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Read a pointer from an out-parameter buffer. */
|
|
25
|
+
export function readPtrOut(alloc: Allocation): Pointer | null {
|
|
26
|
+
const dv = new DataView(alloc.buf.buffer);
|
|
27
|
+
const raw = dv.getBigUint64(0, true);
|
|
28
|
+
return raw === 0n ? null : (Number(raw) as Pointer);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Allocate an 8-byte buffer for a uint64 out-parameter. */
|
|
32
|
+
export function allocUint64Out(): Allocation {
|
|
33
|
+
const buf = new Uint8Array(8);
|
|
34
|
+
return { ptr: ptr(buf), buf };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Read a uint64 value from an out-parameter buffer. */
|
|
38
|
+
export function readUint64Out(alloc: Allocation): number {
|
|
39
|
+
const dv = new DataView(alloc.buf.buffer);
|
|
40
|
+
return Number(dv.getBigUint64(0, true));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read a null-terminated C string from a pointer stored in an out-parameter buffer.
|
|
45
|
+
* Decodes the char* pointer via readPtrOut, then reads the C string with CString.
|
|
46
|
+
*/
|
|
47
|
+
export function decodeStringFromBuf(alloc: Allocation): string {
|
|
48
|
+
const p = readPtrOut(alloc);
|
|
49
|
+
if (!p) return "";
|
|
50
|
+
return new CString(p).toString();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Read an array of int64 values from a native pointer, returning JS numbers. */
|
|
54
|
+
export function decodeInt64Array(p: Pointer | null, count: number): number[] {
|
|
55
|
+
if (!p || count === 0) return [];
|
|
56
|
+
const i64 = new BigInt64Array(toArrayBuffer(p, 0, count * 8));
|
|
57
|
+
return Array.from(i64, (v) => Number(v));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Read an array of float64 values from a native pointer. */
|
|
61
|
+
export function decodeFloat64Array(p: Pointer | null, count: number): number[] {
|
|
62
|
+
if (!p || count === 0) return [];
|
|
63
|
+
const f64 = new Float64Array(toArrayBuffer(p, 0, count * 8));
|
|
64
|
+
return Array.from(f64);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Read an array of uint64 values from a native pointer, returning JS numbers. */
|
|
68
|
+
export function decodeUint64Array(p: Pointer | null, count: number): number[] {
|
|
69
|
+
if (!p || count === 0) return [];
|
|
70
|
+
const u64 = new BigUint64Array(toArrayBuffer(p, 0, count * 8));
|
|
71
|
+
return Array.from(u64, (v) => Number(v));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Read an array of pointers from a native pointer using offset-based reading. */
|
|
75
|
+
export function decodePtrArray(p: Pointer | null, count: number): (Pointer | null)[] {
|
|
76
|
+
if (!p || count === 0) return [];
|
|
77
|
+
const result: (Pointer | null)[] = new Array(count);
|
|
78
|
+
for (let i = 0; i < count; i++) {
|
|
79
|
+
const v = read.ptr(p, i * 8);
|
|
80
|
+
result[i] = v === 0 ? null : (v as Pointer);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Read an array of C strings from a char** pointer. */
|
|
86
|
+
export function decodeStringArray(p: Pointer | null, count: number): string[] {
|
|
87
|
+
if (!p || count === 0) return [];
|
|
88
|
+
const result: string[] = new Array(count);
|
|
89
|
+
for (let i = 0; i < count; i++) {
|
|
90
|
+
const strPtr = read.ptr(p, i * 8);
|
|
91
|
+
result[i] = strPtr === 0 ? "" : new CString(strPtr as Pointer).toString();
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Encode a JS string as a null-terminated C string buffer. */
|
|
97
|
+
export function toCString(str: string): Allocation {
|
|
98
|
+
const encoded = encoder.encode(str + "\0");
|
|
99
|
+
const buf = new Uint8Array(encoded.length);
|
|
100
|
+
buf.set(encoded);
|
|
101
|
+
return { ptr: ptr(buf), buf };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Allocate native memory for an array of int64 values. */
|
|
105
|
+
export function allocNativeInt64(values: number[]): Allocation {
|
|
106
|
+
const buf = new Uint8Array(values.length * 8);
|
|
107
|
+
const dv = new DataView(buf.buffer);
|
|
108
|
+
for (let i = 0; i < values.length; i++) {
|
|
109
|
+
dv.setBigInt64(i * 8, BigInt(values[i]), true);
|
|
110
|
+
}
|
|
111
|
+
return { ptr: ptr(buf), buf };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Allocate native memory for an array of float64 values. */
|
|
115
|
+
export function allocNativeFloat64(values: number[]): Allocation {
|
|
116
|
+
const buf = new Uint8Array(values.length * 8);
|
|
117
|
+
const dv = new DataView(buf.buffer);
|
|
118
|
+
for (let i = 0; i < values.length; i++) {
|
|
119
|
+
dv.setFloat64(i * 8, values[i], true);
|
|
120
|
+
}
|
|
121
|
+
return { ptr: ptr(buf), buf };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build a native pointer table from an array of native pointers.
|
|
126
|
+
* The returned allocation contains an array of void* addresses.
|
|
127
|
+
*/
|
|
128
|
+
export function allocNativePtrTable(ptrs: (Pointer | null)[]): Allocation {
|
|
129
|
+
const buf = new Uint8Array(ptrs.length * 8);
|
|
130
|
+
const dv = new DataView(buf.buffer);
|
|
131
|
+
for (let i = 0; i < ptrs.length; i++) {
|
|
132
|
+
const addr = ptrs[i] ? BigInt(ptrs[i] as number) : 0n;
|
|
133
|
+
dv.setBigUint64(i * 8, addr, true);
|
|
134
|
+
}
|
|
135
|
+
return { ptr: ptr(buf), buf };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Allocate a native string (null-terminated) and return its allocation. */
|
|
139
|
+
export function allocNativeString(str: string): Allocation {
|
|
140
|
+
const buf = encoder.encode(str + "\0");
|
|
141
|
+
return { ptr: ptr(buf), buf };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Build a native string pointer table (const char* const*) from an array of strings. */
|
|
145
|
+
export function allocNativeStringArray(strings: string[]): {
|
|
146
|
+
table: Allocation;
|
|
147
|
+
keepalive: Allocation[];
|
|
148
|
+
} {
|
|
149
|
+
const strAllocs = strings.map((s) => allocNativeString(s));
|
|
150
|
+
const table = allocNativePtrTable(strAllocs.map((a) => a.ptr));
|
|
151
|
+
return { table, keepalive: strAllocs };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Get the native address of a pointer as BigInt. */
|
|
155
|
+
export function nativeAddress(p: Pointer | null): bigint {
|
|
156
|
+
return p ? BigInt(p as number) : 0n;
|
|
157
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "./create.ts";
|
|
2
|
+
import "./read.ts";
|
|
3
|
+
import "./query.ts";
|
|
4
|
+
import "./transaction.ts";
|
|
5
|
+
import "./metadata.ts";
|
|
6
|
+
import "./time-series.ts";
|
|
7
|
+
import "./csv.ts";
|
|
8
|
+
import "./introspection.ts";
|
|
9
|
+
import "./composites.ts";
|
|
10
|
+
|
|
11
|
+
export type { CsvOptions } from "./csv.ts";
|
|
12
|
+
export { Database } from "./database.ts";
|
|
13
|
+
export { QuiverError } from "./errors.ts";
|
|
14
|
+
export { LuaRunner } from "./lua-runner.ts";
|
|
15
|
+
export type { GroupMetadata, ScalarMetadata } from "./metadata.ts";
|
|
16
|
+
export type { TimeSeriesData } from "./time-series.ts";
|
|
17
|
+
export type { ArrayValue, ElementData, QueryParam, ScalarValue, Value } from "./types.ts";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ptr } from "bun:ffi";
|
|
2
|
+
import { Database } from "./database.ts";
|
|
3
|
+
import { check } from "./errors.ts";
|
|
4
|
+
import { allocPtrOut, decodeStringFromBuf, toCString } from "./ffi-helpers.ts";
|
|
5
|
+
import { getSymbols } from "./loader.ts";
|
|
6
|
+
|
|
7
|
+
Database.prototype.isHealthy = function (this: Database): boolean {
|
|
8
|
+
const lib = getSymbols();
|
|
9
|
+
const outBuf = new Uint8Array(4);
|
|
10
|
+
const outPtr = ptr(outBuf);
|
|
11
|
+
check(lib.quiver_database_is_healthy(this._handle, outPtr));
|
|
12
|
+
return new DataView(outBuf.buffer).getInt32(0, true) !== 0;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
Database.prototype.currentVersion = function (this: Database): number {
|
|
16
|
+
const lib = getSymbols();
|
|
17
|
+
const outBuf = new Uint8Array(8);
|
|
18
|
+
const outPtr = ptr(outBuf);
|
|
19
|
+
check(lib.quiver_database_current_version(this._handle, outPtr));
|
|
20
|
+
return Number(new DataView(outBuf.buffer).getBigInt64(0, true));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
Database.prototype.path = function (this: Database): string {
|
|
24
|
+
const lib = getSymbols();
|
|
25
|
+
const outPath = allocPtrOut();
|
|
26
|
+
check(lib.quiver_database_path(this._handle, outPath.ptr));
|
|
27
|
+
return decodeStringFromBuf(outPath);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
Database.prototype.describe = function (this: Database): void {
|
|
31
|
+
const lib = getSymbols();
|
|
32
|
+
check(lib.quiver_database_describe(this._handle));
|
|
33
|
+
};
|
package/src/loader.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { dlopen, type Library, type Pointer, suffix } from "bun:ffi";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { QuiverError } from "./errors.ts";
|
|
5
|
+
|
|
6
|
+
// Bun FFI type shorthand constants. Deno's "buffer" parameter type (pass a
|
|
7
|
+
// TypedArray, auto-converted to a pointer) has no Bun equivalent -- Bun rejects
|
|
8
|
+
// `FFIType.buffer` as an argument ABI type -- so buffer args use "pointer";
|
|
9
|
+
// Bun accepts a TypedArray for a pointer argument and pins it for the call.
|
|
10
|
+
const P = "pointer" as const;
|
|
11
|
+
const BUF = "pointer" as const;
|
|
12
|
+
const I32 = "i32" as const;
|
|
13
|
+
const I64 = "i64" as const;
|
|
14
|
+
const USIZE = "usize" as const;
|
|
15
|
+
const F64 = "f64" as const;
|
|
16
|
+
const V = "void" as const;
|
|
17
|
+
|
|
18
|
+
// Platform constants. `suffix` from bun:ffi is the platform-correct shared
|
|
19
|
+
// library extension ("dll" | "dylib" | "so").
|
|
20
|
+
const EXT = suffix;
|
|
21
|
+
const C_API_LIB = `libquiver_c.${EXT}`;
|
|
22
|
+
|
|
23
|
+
// Opaque pointer type for native handles.
|
|
24
|
+
export type NativePointer = Pointer | null;
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Symbol definitions grouped by C API domain
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
const lifecycleSymbols = {
|
|
31
|
+
quiver_version: { args: [], returns: P },
|
|
32
|
+
quiver_get_last_error: { args: [], returns: P },
|
|
33
|
+
quiver_clear_last_error: { args: [], returns: V },
|
|
34
|
+
// quiver_database_options_default is intentionally omitted: it returns a
|
|
35
|
+
// struct by value, which Bun FFI does not support (oven-sh/bun#6139). It was
|
|
36
|
+
// never called -- makeDefaultOptions() in ffi-helpers.ts builds the options
|
|
37
|
+
// struct in JS.
|
|
38
|
+
quiver_database_from_schema: { args: [BUF, BUF, BUF, P], returns: I32 },
|
|
39
|
+
quiver_database_from_migrations: { args: [BUF, BUF, BUF, P], returns: I32 },
|
|
40
|
+
quiver_database_open: { args: [BUF, BUF, P], returns: I32 },
|
|
41
|
+
quiver_database_close: { args: [P], returns: I32 },
|
|
42
|
+
quiver_database_is_healthy: { args: [P, P], returns: I32 },
|
|
43
|
+
quiver_database_path: { args: [P, P], returns: I32 },
|
|
44
|
+
quiver_database_current_version: { args: [P, P], returns: I32 },
|
|
45
|
+
quiver_database_describe: { args: [P], returns: I32 },
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
const elementSymbols = {
|
|
49
|
+
quiver_element_create: { args: [P], returns: I32 },
|
|
50
|
+
quiver_element_destroy: { args: [P], returns: I32 },
|
|
51
|
+
quiver_element_set_integer: { args: [P, BUF, I64], returns: I32 },
|
|
52
|
+
quiver_element_set_float: { args: [P, BUF, F64], returns: I32 },
|
|
53
|
+
quiver_element_set_string: { args: [P, BUF, BUF], returns: I32 },
|
|
54
|
+
quiver_element_set_null: { args: [P, BUF], returns: I32 },
|
|
55
|
+
quiver_element_set_array_integer: { args: [P, BUF, P, I32], returns: I32 },
|
|
56
|
+
quiver_element_set_array_float: { args: [P, BUF, P, I32], returns: I32 },
|
|
57
|
+
quiver_element_set_array_string: { args: [P, BUF, P, I32], returns: I32 },
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
const crudSymbols = {
|
|
61
|
+
quiver_database_create_element: { args: [P, BUF, P, P], returns: I32 },
|
|
62
|
+
quiver_database_update_element: { args: [P, BUF, I64, P], returns: I32 },
|
|
63
|
+
quiver_database_delete_element: { args: [P, BUF, I64], returns: I32 },
|
|
64
|
+
} as const;
|
|
65
|
+
|
|
66
|
+
const readSymbols = {
|
|
67
|
+
quiver_database_read_scalar_integers: { args: [P, BUF, BUF, P, P], returns: I32 },
|
|
68
|
+
quiver_database_read_scalar_floats: { args: [P, BUF, BUF, P, P], returns: I32 },
|
|
69
|
+
quiver_database_read_scalar_strings: { args: [P, BUF, BUF, P, P], returns: I32 },
|
|
70
|
+
quiver_database_read_scalar_integer_by_id: { args: [P, BUF, BUF, I64, P, P], returns: I32 },
|
|
71
|
+
quiver_database_read_scalar_float_by_id: { args: [P, BUF, BUF, I64, P, P], returns: I32 },
|
|
72
|
+
quiver_database_read_scalar_string_by_id: { args: [P, BUF, BUF, I64, P, P], returns: I32 },
|
|
73
|
+
quiver_database_read_vector_integers: { args: [P, BUF, BUF, P, P, P], returns: I32 },
|
|
74
|
+
quiver_database_read_vector_floats: { args: [P, BUF, BUF, P, P, P], returns: I32 },
|
|
75
|
+
quiver_database_read_vector_strings: { args: [P, BUF, BUF, P, P, P], returns: I32 },
|
|
76
|
+
quiver_database_read_set_integers: { args: [P, BUF, BUF, P, P, P], returns: I32 },
|
|
77
|
+
quiver_database_read_set_floats: { args: [P, BUF, BUF, P, P, P], returns: I32 },
|
|
78
|
+
quiver_database_read_set_strings: { args: [P, BUF, BUF, P, P, P], returns: I32 },
|
|
79
|
+
quiver_database_read_vector_integers_by_id: { args: [P, BUF, BUF, I64, P, P], returns: I32 },
|
|
80
|
+
quiver_database_read_vector_floats_by_id: { args: [P, BUF, BUF, I64, P, P], returns: I32 },
|
|
81
|
+
quiver_database_read_vector_strings_by_id: { args: [P, BUF, BUF, I64, P, P], returns: I32 },
|
|
82
|
+
quiver_database_read_set_integers_by_id: { args: [P, BUF, BUF, I64, P, P], returns: I32 },
|
|
83
|
+
quiver_database_read_set_floats_by_id: { args: [P, BUF, BUF, I64, P, P], returns: I32 },
|
|
84
|
+
quiver_database_read_set_strings_by_id: { args: [P, BUF, BUF, I64, P, P], returns: I32 },
|
|
85
|
+
quiver_database_read_element_ids: { args: [P, BUF, P, P], returns: I32 },
|
|
86
|
+
} as const;
|
|
87
|
+
|
|
88
|
+
const querySymbols = {
|
|
89
|
+
quiver_database_query_string: { args: [P, BUF, P, P], returns: I32 },
|
|
90
|
+
quiver_database_query_integer: { args: [P, BUF, P, P], returns: I32 },
|
|
91
|
+
quiver_database_query_float: { args: [P, BUF, P, P], returns: I32 },
|
|
92
|
+
quiver_database_query_string_params: { args: [P, BUF, P, P, USIZE, P, P], returns: I32 },
|
|
93
|
+
quiver_database_query_integer_params: { args: [P, BUF, P, P, USIZE, P, P], returns: I32 },
|
|
94
|
+
quiver_database_query_float_params: { args: [P, BUF, P, P, USIZE, P, P], returns: I32 },
|
|
95
|
+
} as const;
|
|
96
|
+
|
|
97
|
+
const transactionSymbols = {
|
|
98
|
+
quiver_database_begin_transaction: { args: [P], returns: I32 },
|
|
99
|
+
quiver_database_commit: { args: [P], returns: I32 },
|
|
100
|
+
quiver_database_rollback: { args: [P], returns: I32 },
|
|
101
|
+
quiver_database_in_transaction: { args: [P, P], returns: I32 },
|
|
102
|
+
} as const;
|
|
103
|
+
|
|
104
|
+
const metadataSymbols = {
|
|
105
|
+
quiver_database_get_scalar_metadata: { args: [P, BUF, BUF, P], returns: I32 },
|
|
106
|
+
quiver_database_get_vector_metadata: { args: [P, BUF, BUF, P], returns: I32 },
|
|
107
|
+
quiver_database_get_set_metadata: { args: [P, BUF, BUF, P], returns: I32 },
|
|
108
|
+
quiver_database_get_time_series_metadata: { args: [P, BUF, BUF, P], returns: I32 },
|
|
109
|
+
quiver_database_free_scalar_metadata: { args: [P], returns: I32 },
|
|
110
|
+
quiver_database_free_group_metadata: { args: [P], returns: I32 },
|
|
111
|
+
quiver_database_list_scalar_attributes: { args: [P, BUF, P, P], returns: I32 },
|
|
112
|
+
quiver_database_list_vector_groups: { args: [P, BUF, P, P], returns: I32 },
|
|
113
|
+
quiver_database_list_set_groups: { args: [P, BUF, P, P], returns: I32 },
|
|
114
|
+
quiver_database_list_time_series_groups: { args: [P, BUF, P, P], returns: I32 },
|
|
115
|
+
} as const;
|
|
116
|
+
|
|
117
|
+
const timeSeriesSymbols = {
|
|
118
|
+
quiver_database_read_time_series_group: { args: [P, BUF, BUF, I64, P, P, P, P, P], returns: I32 },
|
|
119
|
+
quiver_database_update_time_series_group: {
|
|
120
|
+
args: [P, BUF, BUF, I64, P, P, P, USIZE, USIZE],
|
|
121
|
+
returns: I32,
|
|
122
|
+
},
|
|
123
|
+
quiver_database_free_time_series_data: { args: [P, P, P, USIZE, USIZE], returns: I32 },
|
|
124
|
+
quiver_database_has_time_series_files: { args: [P, BUF, P], returns: I32 },
|
|
125
|
+
quiver_database_list_time_series_files_columns: { args: [P, BUF, P, P], returns: I32 },
|
|
126
|
+
quiver_database_read_time_series_files: { args: [P, BUF, P, P, P], returns: I32 },
|
|
127
|
+
quiver_database_update_time_series_files: { args: [P, BUF, P, P, USIZE], returns: I32 },
|
|
128
|
+
quiver_database_free_time_series_files: { args: [P, P, USIZE], returns: I32 },
|
|
129
|
+
} as const;
|
|
130
|
+
|
|
131
|
+
const csvSymbols = {
|
|
132
|
+
quiver_database_export_csv: { args: [P, BUF, BUF, BUF, P], returns: I32 },
|
|
133
|
+
quiver_database_import_csv: { args: [P, BUF, BUF, BUF, P], returns: I32 },
|
|
134
|
+
} as const;
|
|
135
|
+
|
|
136
|
+
const freeSymbols = {
|
|
137
|
+
quiver_database_free_integer_array: { args: [P], returns: I32 },
|
|
138
|
+
quiver_database_free_float_array: { args: [P], returns: I32 },
|
|
139
|
+
quiver_database_free_string_array: { args: [P, USIZE], returns: I32 },
|
|
140
|
+
quiver_database_free_string: { args: [P], returns: I32 },
|
|
141
|
+
quiver_database_free_integer_vectors: { args: [P, P, USIZE], returns: I32 },
|
|
142
|
+
quiver_database_free_float_vectors: { args: [P, P, USIZE], returns: I32 },
|
|
143
|
+
quiver_database_free_string_vectors: { args: [P, P, USIZE], returns: I32 },
|
|
144
|
+
quiver_database_free_scalar_metadata_array: { args: [P, USIZE], returns: I32 },
|
|
145
|
+
quiver_database_free_group_metadata_array: { args: [P, USIZE], returns: I32 },
|
|
146
|
+
} as const;
|
|
147
|
+
|
|
148
|
+
const luaSymbols = {
|
|
149
|
+
quiver_lua_runner_new: { args: [P, P], returns: I32 },
|
|
150
|
+
quiver_lua_runner_free: { args: [P], returns: I32 },
|
|
151
|
+
quiver_lua_runner_run: { args: [P, BUF], returns: I32 },
|
|
152
|
+
quiver_lua_runner_get_error: { args: [P, P], returns: I32 },
|
|
153
|
+
} as const;
|
|
154
|
+
|
|
155
|
+
// Combined symbol map for dlopen.
|
|
156
|
+
const allSymbols = {
|
|
157
|
+
...lifecycleSymbols,
|
|
158
|
+
...elementSymbols,
|
|
159
|
+
...crudSymbols,
|
|
160
|
+
...readSymbols,
|
|
161
|
+
...querySymbols,
|
|
162
|
+
...transactionSymbols,
|
|
163
|
+
...metadataSymbols,
|
|
164
|
+
...timeSeriesSymbols,
|
|
165
|
+
...csvSymbols,
|
|
166
|
+
...freeSymbols,
|
|
167
|
+
...luaSymbols,
|
|
168
|
+
} as const;
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Library search and loading
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
// `import.meta.dir` is the absolute directory of this module on disk.
|
|
175
|
+
const __dirname: string = import.meta.dir;
|
|
176
|
+
|
|
177
|
+
// Map Bun's process.platform/arch onto the `libs/{os}-{arch}/` directory names
|
|
178
|
+
// used by the bundled native libraries (e.g. "windows-x86_64", "linux-x86_64").
|
|
179
|
+
const OS_NAMES: Record<string, string> = { win32: "windows", darwin: "darwin", linux: "linux" };
|
|
180
|
+
const ARCH_NAMES: Record<string, string> = { x64: "x86_64", arm64: "aarch64" };
|
|
181
|
+
const osName = OS_NAMES[process.platform] ?? process.platform;
|
|
182
|
+
const archName = ARCH_NAMES[process.arch] ?? process.arch;
|
|
183
|
+
const platformKey = `${osName}-${archName}`;
|
|
184
|
+
const isWindows = process.platform === "win32";
|
|
185
|
+
|
|
186
|
+
function getBundledLibDir(): string | null {
|
|
187
|
+
const libDir = join(__dirname, "..", "libs", platformKey);
|
|
188
|
+
if (existsSync(join(libDir, C_API_LIB))) {
|
|
189
|
+
return libDir;
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getSearchPaths(): string[] {
|
|
195
|
+
const paths: string[] = [];
|
|
196
|
+
let dir = __dirname;
|
|
197
|
+
for (let i = 0; i < 5; i++) {
|
|
198
|
+
const candidate = join(dir, "build", "bin");
|
|
199
|
+
if (existsSync(join(candidate, C_API_LIB))) {
|
|
200
|
+
paths.push(candidate);
|
|
201
|
+
}
|
|
202
|
+
dir = dirname(dir);
|
|
203
|
+
}
|
|
204
|
+
return paths;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
type QuiverLib = Library<typeof allSymbols>;
|
|
208
|
+
|
|
209
|
+
// On Windows, libquiver_c.dll depends on libquiver.dll (the C++ core). The OS
|
|
210
|
+
// loader resolves that sibling dependency via PATH, so put the library's own
|
|
211
|
+
// directory on PATH before loading. (Unlike Deno, Bun's dlopen cannot preload
|
|
212
|
+
// the core lib directly -- it rejects an empty symbol map.)
|
|
213
|
+
function ensureCoreOnPath(dir: string): void {
|
|
214
|
+
if (!isWindows) return;
|
|
215
|
+
const current = process.env.PATH ?? "";
|
|
216
|
+
if (!current.split(";").includes(dir)) {
|
|
217
|
+
process.env.PATH = `${dir};${current}`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function openLibrary(dir: string): QuiverLib {
|
|
222
|
+
ensureCoreOnPath(dir);
|
|
223
|
+
return dlopen(join(dir, C_API_LIB), allSymbols);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function initLibrary(): QuiverLib {
|
|
227
|
+
let lastError: unknown;
|
|
228
|
+
|
|
229
|
+
// Tier 1: Bundled libs/{os}-{arch}/ next to the loader (npm install / dev install).
|
|
230
|
+
const bundledDir = getBundledLibDir();
|
|
231
|
+
if (bundledDir) {
|
|
232
|
+
try {
|
|
233
|
+
return openLibrary(bundledDir);
|
|
234
|
+
} catch (e) {
|
|
235
|
+
lastError = e; // Bundled libs found but failed to load -- fall through.
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Tier 2: Dev mode -- walk up directories looking for build/bin/.
|
|
240
|
+
for (const dir of getSearchPaths()) {
|
|
241
|
+
try {
|
|
242
|
+
return openLibrary(dir);
|
|
243
|
+
} catch (e) {
|
|
244
|
+
lastError = e; // Try next path.
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Tier 3: System PATH fallback -- the core lib is expected to be discoverable
|
|
249
|
+
// on PATH alongside the C API lib.
|
|
250
|
+
try {
|
|
251
|
+
return dlopen(C_API_LIB, allSymbols);
|
|
252
|
+
} catch (e) {
|
|
253
|
+
lastError = e; // Fall through to error.
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const searched = [
|
|
257
|
+
join(__dirname, "..", "libs", platformKey),
|
|
258
|
+
...getSearchPaths(),
|
|
259
|
+
"system PATH",
|
|
260
|
+
].join(", ");
|
|
261
|
+
const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
|
|
262
|
+
throw new QuiverError(
|
|
263
|
+
`Cannot load native library '${C_API_LIB}'. Searched: ${searched}${detail}`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Resolve the native library lazily on first use. Initializing eagerly at
|
|
268
|
+
// module-evaluation time would run during the loader<->errors import cycle,
|
|
269
|
+
// before `errors.ts` has declared `QuiverError` -- making the not-found throw
|
|
270
|
+
// hit a temporal dead zone. Deferring to first call lets the module graph
|
|
271
|
+
// settle first; the result is cached, so callers still get it synchronously.
|
|
272
|
+
let _lib: QuiverLib | null = null;
|
|
273
|
+
|
|
274
|
+
export function loadLibrary(): QuiverLib {
|
|
275
|
+
if (_lib === null) {
|
|
276
|
+
_lib = initLibrary();
|
|
277
|
+
}
|
|
278
|
+
return _lib;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function getSymbols() {
|
|
282
|
+
return loadLibrary().symbols;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export type Symbols = ReturnType<typeof getSymbols>;
|