quiverdb 0.6.1 → 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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -17
  3. package/libs/linux-x86_64/libquiver.so +0 -0
  4. package/libs/linux-x86_64/libquiver.so.0 +0 -0
  5. package/libs/linux-x86_64/libquiver_c.so +0 -0
  6. package/libs/windows-x86_64/libquiver.dll +0 -0
  7. package/libs/windows-x86_64/libquiver_c.dll +0 -0
  8. package/mod.ts +25 -0
  9. package/package.json +17 -30
  10. package/src/composites.ts +89 -0
  11. package/src/create.ts +152 -0
  12. package/src/csv.ts +118 -0
  13. package/src/database.ts +171 -0
  14. package/src/errors.ts +21 -0
  15. package/src/ffi-helpers.ts +157 -0
  16. package/src/index.ts +17 -0
  17. package/src/introspection.ts +33 -0
  18. package/src/loader.ts +285 -0
  19. package/src/lua-runner.ts +48 -0
  20. package/src/metadata.ts +218 -0
  21. package/src/query.ts +160 -0
  22. package/src/read.ts +572 -0
  23. package/src/time-series.ts +291 -0
  24. package/src/transaction.ts +27 -0
  25. package/{dist/types.d.ts → src/types.ts} +8 -1
  26. package/dist/composites.d.ts +0 -9
  27. package/dist/composites.d.ts.map +0 -1
  28. package/dist/composites.js +0 -69
  29. package/dist/composites.js.map +0 -1
  30. package/dist/create.d.ts +0 -9
  31. package/dist/create.d.ts.map +0 -1
  32. package/dist/create.js +0 -104
  33. package/dist/create.js.map +0 -1
  34. package/dist/csv.d.ts +0 -11
  35. package/dist/csv.d.ts.map +0 -1
  36. package/dist/csv.js +0 -67
  37. package/dist/csv.js.map +0 -1
  38. package/dist/database.d.ts +0 -13
  39. package/dist/database.d.ts.map +0 -1
  40. package/dist/database.js +0 -42
  41. package/dist/database.js.map +0 -1
  42. package/dist/errors.d.ts +0 -5
  43. package/dist/errors.d.ts.map +0 -1
  44. package/dist/errors.js +0 -18
  45. package/dist/errors.js.map +0 -1
  46. package/dist/ffi-helpers.d.ts +0 -57
  47. package/dist/ffi-helpers.d.ts.map +0 -1
  48. package/dist/ffi-helpers.js +0 -136
  49. package/dist/ffi-helpers.js.map +0 -1
  50. package/dist/index.d.ts +0 -17
  51. package/dist/index.d.ts.map +0 -1
  52. package/dist/index.js +0 -13
  53. package/dist/index.js.map +0 -1
  54. package/dist/introspection.d.ts +0 -10
  55. package/dist/introspection.d.ts.map +0 -1
  56. package/dist/introspection.js +0 -27
  57. package/dist/introspection.js.map +0 -1
  58. package/dist/loader.d.ts +0 -7
  59. package/dist/loader.d.ts.map +0 -1
  60. package/dist/loader.js +0 -165
  61. package/dist/loader.js.map +0 -1
  62. package/dist/lua-runner.d.ts +0 -11
  63. package/dist/lua-runner.d.ts.map +0 -1
  64. package/dist/lua-runner.js +0 -43
  65. package/dist/lua-runner.js.map +0 -1
  66. package/dist/metadata.d.ts +0 -28
  67. package/dist/metadata.d.ts.map +0 -1
  68. package/dist/metadata.js +0 -100
  69. package/dist/metadata.js.map +0 -1
  70. package/dist/query.d.ts +0 -9
  71. package/dist/query.d.ts.map +0 -1
  72. package/dist/query.js +0 -90
  73. package/dist/query.js.map +0 -1
  74. package/dist/read.d.ts +0 -25
  75. package/dist/read.d.ts.map +0 -1
  76. package/dist/read.js +0 -220
  77. package/dist/read.js.map +0 -1
  78. package/dist/time-series.d.ts +0 -12
  79. package/dist/time-series.d.ts.map +0 -1
  80. package/dist/time-series.js +0 -155
  81. package/dist/time-series.js.map +0 -1
  82. package/dist/transaction.d.ts +0 -10
  83. package/dist/transaction.d.ts.map +0 -1
  84. package/dist/transaction.js +0 -22
  85. package/dist/transaction.js.map +0 -1
  86. package/dist/types.d.ts.map +0 -1
  87. package/dist/types.js +0 -2
  88. package/dist/types.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PSR
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,29 +1,41 @@
1
1
  # quiverdb
2
2
 
3
- SQLite wrapper binding for Quiver via koffi FFI. Works with Node.js, Deno, and Bun.
3
+ SQLite wrapper binding for Quiver via Bun FFI (`bun:ffi`).
4
4
 
5
5
  ## Requirements
6
6
 
7
- - Node.js >= 20, Deno >= 2.0, or Bun >= 1.0
8
- - `libquiver.dll` and `libquiver_c.dll` (or `.so`/`.dylib`) in system PATH
7
+ - [Bun](https://bun.sh) >= 1.1
8
+ - `libquiver.dll` and `libquiver_c.dll` (or `.so`/`.dylib`) accessible (see [Native Library Setup](#native-library-setup))
9
9
 
10
10
  ## Installation
11
11
 
12
12
  ```bash
13
- npm install quiverdb
13
+ bun add quiverdb
14
14
  ```
15
15
 
16
- The native Quiver libraries must be compiled separately and available in your system PATH. See the Quiver project build instructions for details.
16
+ The bundled native libraries ship inside the package, so no separate download
17
+ step is required on supported platforms. To build the native libraries from
18
+ source, see the Quiver project build instructions.
19
+
20
+ ```typescript
21
+ import { Database } from "quiverdb";
22
+ ```
23
+
24
+ Bun loads native code via FFI without any permission flags — unlike Deno, there
25
+ is no `--allow-ffi`/`--allow-read`/`--allow-write` to pass.
17
26
 
18
27
  ## Native Library Setup
19
28
 
20
- This binding loads the Quiver C API library (`libquiver_c`) at runtime via koffi. The core library (`libquiver`) is a dependency that must also be loadable.
29
+ The loader searches for native libraries in three tiers:
21
30
 
22
- Options for making the libraries available:
31
+ 1. **Bundled**: `libs/{os}-{arch}/` directory next to the binding source (shipped
32
+ inside the published package).
33
+ 2. **Dev mode**: Walks up directories looking for `build/bin/` (auto-discovered
34
+ during in-tree development against a local C++ build).
35
+ 3. **System PATH**: Falls back to loading by library name.
23
36
 
24
- 1. Add the `build/bin/` directory to your system PATH
25
- 2. Copy both libraries to a directory already in PATH
26
- 3. Place both libraries alongside your application entry point
37
+ Currently prebuilt binaries ship for `linux-x86_64` and `windows-x86_64`. On other
38
+ platforms, build from source and point to `build/bin/` via tier 2.
27
39
 
28
40
  ## Quick Start
29
41
 
@@ -39,6 +51,12 @@ console.log(values); // [42]
39
51
  db.close();
40
52
  ```
41
53
 
54
+ Run with:
55
+
56
+ ```bash
57
+ bun run example.ts
58
+ ```
59
+
42
60
  ## API Methods
43
61
 
44
62
  ### Lifecycle
@@ -50,6 +68,7 @@ db.close();
50
68
  ### Create / Delete
51
69
 
52
70
  - `createElement(collection, data)` -- Create element, returns numeric ID
71
+ - `updateElement(collection, id, data)` -- Update element by ID
53
72
  - `deleteElement(collection, id)` -- Delete element by ID
54
73
 
55
74
  ### Read (bulk)
@@ -57,22 +76,50 @@ db.close();
57
76
  - `readScalarIntegers(collection, attribute)` -- Read all integer scalars
58
77
  - `readScalarFloats(collection, attribute)` -- Read all float scalars
59
78
  - `readScalarStrings(collection, attribute)` -- Read all string scalars
79
+ - `readVectorIntegers(collection, attribute)` -- Read all integer vectors
80
+ - `readVectorFloats(collection, attribute)` -- Read all float vectors
81
+ - `readVectorStrings(collection, attribute)` -- Read all string vectors
82
+ - `readSetIntegers(collection, attribute)` -- Read all integer sets
83
+ - `readSetFloats(collection, attribute)` -- Read all float sets
84
+ - `readSetStrings(collection, attribute)` -- Read all string sets
60
85
 
61
86
  ### Read (by ID)
62
87
 
63
88
  - `readScalarIntegerById(collection, attribute, id)` -- Read integer or null
64
89
  - `readScalarFloatById(collection, attribute, id)` -- Read float or null
65
90
  - `readScalarStringById(collection, attribute, id)` -- Read string or null
91
+ - `readVectorIntegersById(collection, attribute, id)` -- Read integer vector
92
+ - `readVectorFloatsById(collection, attribute, id)` -- Read float vector
93
+ - `readVectorStringsById(collection, attribute, id)` -- Read string vector
94
+ - `readSetIntegersById(collection, attribute, id)` -- Read integer set
95
+ - `readSetFloatsById(collection, attribute, id)` -- Read float set
96
+ - `readSetStringsById(collection, attribute, id)` -- Read string set
66
97
 
67
98
  ### Read (IDs)
68
99
 
69
100
  - `readElementIds(collection)` -- Read all element IDs in a collection
70
101
 
102
+ ### Metadata
103
+
104
+ - `getScalarMetadata(collection, attribute)` -- Get scalar attribute metadata
105
+ - `getVectorMetadata(collection, attribute)` -- Get vector group metadata
106
+ - `getSetMetadata(collection, attribute)` -- Get set group metadata
107
+ - `getTimeSeriesMetadata(collection, attribute)` -- Get time series group metadata
108
+ - `listScalarAttributes(collection)` -- List all scalar attributes
109
+ - `listVectorGroups(collection)` -- List all vector groups
110
+ - `listSetGroups(collection)` -- List all set groups
111
+ - `listTimeSeriesGroups(collection)` -- List all time series groups
112
+
113
+ ### Time Series
114
+
115
+ - `readTimeSeriesGroup(collection, group, id)` -- Read time series data for an element
116
+ - `updateTimeSeriesGroup(collection, group, id, data)` -- Write time series data for an element
117
+
71
118
  ### Query
72
119
 
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
120
+ - `queryString(sql, parameters?)` -- Query returning string or null
121
+ - `queryInteger(sql, parameters?)` -- Query returning integer or null
122
+ - `queryFloat(sql, parameters?)` -- Query returning float or null
76
123
 
77
124
  Parameters are passed as an array of `number | string | null`.
78
125
 
@@ -83,6 +130,30 @@ Parameters are passed as an array of `number | string | null`.
83
130
  - `rollback()` -- Rollback current transaction
84
131
  - `inTransaction()` -- Check if transaction is active
85
132
 
133
+ ### CSV
134
+
135
+ - `exportCsv(collection, group, filePath, options?)` -- Export collection group to CSV file
136
+ - `importCsv(collection, group, filePath, options?)` -- Import CSV file into collection group
137
+
138
+ ### Composites
139
+
140
+ - `readScalarsById(collection, id)` -- Read all scalar attributes for an element
141
+ - `readVectorsById(collection, id)` -- Read all vector groups for an element
142
+ - `readSetsById(collection, id)` -- Read all set groups for an element
143
+
144
+ ### Introspection
145
+
146
+ - `describe()` -- Print schema info to stdout
147
+ - `isHealthy()` -- Check database health
148
+ - `path()` -- Get database file path
149
+ - `currentVersion()` -- Get current schema version
150
+
151
+ ### Lua
152
+
153
+ - `LuaRunner(db)` -- Create Lua script runner with database access
154
+ - `run(script)` -- Execute a Lua script
155
+ - `close()` -- Close the Lua runner
156
+
86
157
  ## Types
87
158
 
88
159
  Exported types available for TypeScript consumers:
@@ -93,16 +164,23 @@ Exported types available for TypeScript consumers:
93
164
  - `ElementData` -- `Record<string, Value | undefined>`
94
165
  - `QueryParam` -- `number | string | null`
95
166
  - `QuiverError` -- Error class for all Quiver operations
167
+ - `ScalarMetadata` -- Scalar attribute metadata
168
+ - `GroupMetadata` -- Vector/set/time series group metadata
169
+ - `TimeSeriesData` -- Time series column data
170
+ - `CsvOptions` -- CSV import/export options
96
171
 
97
172
  ## Development
98
173
 
99
174
  ```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)
175
+ bun install # Install dev dependencies (@types/bun, biome)
176
+ bun test test # Run all tests
177
+ bun run lint # Biome linting
178
+ bun run format # Biome formatting (auto-fix)
104
179
  ```
105
180
 
181
+ Tests discover the native library from a local `build/bin/` (tier 2). On Windows,
182
+ prepend `build/bin` to `PATH` first (the `test/test.bat` helper does this for you).
183
+
106
184
  ## License
107
185
 
108
186
  MIT
Binary file
Binary file
Binary file
package/mod.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @module
3
+ * Quiver - SQLite wrapper with typed FFI bindings for Bun.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { Database } from "quiverdb";
8
+ *
9
+ * const db = Database.fromSchema("my.db", "schema.sql");
10
+ * db.close();
11
+ * ```
12
+ */
13
+
14
+ export type {
15
+ ArrayValue,
16
+ CsvOptions,
17
+ ElementData,
18
+ GroupMetadata,
19
+ QueryParam,
20
+ ScalarMetadata,
21
+ ScalarValue,
22
+ TimeSeriesData,
23
+ Value,
24
+ } from "./src/index.ts";
25
+ export { Database, LuaRunner, QuiverError } from "./src/index.ts";
package/package.json CHANGED
@@ -1,47 +1,34 @@
1
1
  {
2
2
  "name": "quiverdb",
3
- "version": "0.6.1",
4
- "description": "SQLite wrapper binding for Quiver via koffi FFI",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
12
- }
13
- },
14
- "files": [
15
- "dist/"
16
- ],
3
+ "version": "0.9.2",
17
4
  "license": "MIT",
18
5
  "repository": {
19
6
  "type": "git",
20
- "url": "https://github.com/psrenergy/quiver",
7
+ "url": "git+https://github.com/psrenergy/quiver.git",
21
8
  "directory": "bindings/js"
22
9
  },
23
- "keywords": [
24
- "sqlite",
25
- "database",
26
- "ffi",
27
- "node",
28
- "deno",
29
- "quiver"
10
+ "type": "module",
11
+ "exports": {
12
+ ".": "./mod.ts"
13
+ },
14
+ "types": "./mod.ts",
15
+ "files": [
16
+ "mod.ts",
17
+ "src/**/*.ts",
18
+ "libs/**",
19
+ "LICENSE"
30
20
  ],
31
21
  "scripts": {
32
- "build": "tsc",
33
- "test": "vitest run",
34
- "typecheck": "tsc --noEmit",
22
+ "test": "bun test test",
35
23
  "lint": "biome check",
36
24
  "format": "biome format --write"
37
25
  },
38
- "dependencies": {
39
- "koffi": "^2.9.0"
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "provenance": true
40
29
  },
41
30
  "devDependencies": {
42
31
  "@biomejs/biome": "^2.4.6",
43
- "@types/node": "^22.0.0",
44
- "typescript": "^5.9.3",
45
- "vitest": "^3.2.1"
32
+ "@types/bun": "latest"
46
33
  }
47
34
  }
@@ -0,0 +1,89 @@
1
+ import { Database } from "./database.ts";
2
+
3
+ Database.prototype.readScalarsById = function (
4
+ this: Database,
5
+ collection: string,
6
+ id: number,
7
+ ): Record<string, number | string | null> {
8
+ const attributes = this.listScalarAttributes(collection);
9
+ const result: Record<string, number | string | null> = {};
10
+
11
+ for (const attr of attributes) {
12
+ switch (attr.dataType) {
13
+ case 0: // INTEGER
14
+ result[attr.name] = this.readScalarIntegerById(collection, attr.name, id);
15
+ break;
16
+ case 1: // FLOAT
17
+ result[attr.name] = this.readScalarFloatById(collection, attr.name, id);
18
+ break;
19
+ case 2: // STRING
20
+ case 3: // DATE_TIME
21
+ result[attr.name] = this.readScalarStringById(collection, attr.name, id);
22
+ break;
23
+ default:
24
+ throw new Error(`Unknown data type ${attr.dataType} for attribute '${attr.name}'`);
25
+ }
26
+ }
27
+
28
+ return result;
29
+ };
30
+
31
+ Database.prototype.readVectorsById = function (
32
+ this: Database,
33
+ collection: string,
34
+ id: number,
35
+ ): Record<string, number[] | string[]> {
36
+ const groups = this.listVectorGroups(collection);
37
+ const result: Record<string, number[] | string[]> = {};
38
+
39
+ for (const group of groups) {
40
+ for (const col of group.valueColumns) {
41
+ switch (col.dataType) {
42
+ case 0: // INTEGER
43
+ result[col.name] = this.readVectorIntegersById(collection, col.name, id);
44
+ break;
45
+ case 1: // FLOAT
46
+ result[col.name] = this.readVectorFloatsById(collection, col.name, id);
47
+ break;
48
+ case 2: // STRING
49
+ case 3: // DATE_TIME
50
+ result[col.name] = this.readVectorStringsById(collection, col.name, id);
51
+ break;
52
+ default:
53
+ throw new Error(`Unknown data type ${col.dataType} for column '${col.name}'`);
54
+ }
55
+ }
56
+ }
57
+
58
+ return result;
59
+ };
60
+
61
+ Database.prototype.readSetsById = function (
62
+ this: Database,
63
+ collection: string,
64
+ id: number,
65
+ ): Record<string, number[] | string[]> {
66
+ const groups = this.listSetGroups(collection);
67
+ const result: Record<string, number[] | string[]> = {};
68
+
69
+ for (const group of groups) {
70
+ for (const col of group.valueColumns) {
71
+ switch (col.dataType) {
72
+ case 0: // INTEGER
73
+ result[col.name] = this.readSetIntegersById(collection, col.name, id);
74
+ break;
75
+ case 1: // FLOAT
76
+ result[col.name] = this.readSetFloatsById(collection, col.name, id);
77
+ break;
78
+ case 2: // STRING
79
+ case 3: // DATE_TIME
80
+ result[col.name] = this.readSetStringsById(collection, col.name, id);
81
+ break;
82
+ default:
83
+ throw new Error(`Unknown data type ${col.dataType} for column '${col.name}'`);
84
+ }
85
+ }
86
+ }
87
+
88
+ return result;
89
+ };
package/src/create.ts ADDED
@@ -0,0 +1,152 @@
1
+ import { ptr } from "bun:ffi";
2
+ import { Database } from "./database.ts";
3
+ import { check, QuiverError } from "./errors.ts";
4
+ import {
5
+ allocNativeFloat64,
6
+ allocNativeInt64,
7
+ allocNativeStringArray,
8
+ allocPtrOut,
9
+ readPtrOut,
10
+ toCString,
11
+ } from "./ffi-helpers.ts";
12
+ import { getSymbols, type NativePointer } from "./loader.ts";
13
+ import type { ElementData, Value } from "./types.ts";
14
+
15
+ type Symbols = ReturnType<typeof getSymbols>;
16
+
17
+ function setElementArray(
18
+ lib: Symbols,
19
+ elemPtr: NativePointer,
20
+ name: string,
21
+ values: unknown[],
22
+ ): void {
23
+ const nameBuf = toCString(name);
24
+
25
+ if (values.length === 0) {
26
+ check(lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, null, 0));
27
+ return;
28
+ }
29
+
30
+ const first = values[0];
31
+
32
+ if (typeof first === "bigint") {
33
+ const arr = allocNativeInt64((values as bigint[]).map(Number));
34
+ check(lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, arr.ptr, values.length));
35
+ return;
36
+ }
37
+
38
+ if (typeof first === "number") {
39
+ const allIntegers = (values as number[]).every((v) => Number.isInteger(v));
40
+ if (allIntegers) {
41
+ const arr = allocNativeInt64(values as number[]);
42
+ check(lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, arr.ptr, values.length));
43
+ } else {
44
+ const arr = allocNativeFloat64(values as number[]);
45
+ check(lib.quiver_element_set_array_float(elemPtr, nameBuf.buf, arr.ptr, values.length));
46
+ }
47
+ return;
48
+ }
49
+
50
+ if (typeof first === "string") {
51
+ const { table, keepalive: _keepalive } = allocNativeStringArray(values as string[]);
52
+ check(lib.quiver_element_set_array_string(elemPtr, nameBuf.buf, table.ptr, values.length));
53
+ return;
54
+ }
55
+
56
+ throw new QuiverError(`Unsupported array element type for '${name}': ${typeof first}`);
57
+ }
58
+
59
+ function setElementField(lib: Symbols, elemPtr: NativePointer, name: string, value: Value): void {
60
+ const nameBuf = toCString(name);
61
+
62
+ if (value === null) {
63
+ check(lib.quiver_element_set_null(elemPtr, nameBuf.buf));
64
+ return;
65
+ }
66
+
67
+ if (typeof value === "bigint") {
68
+ check(lib.quiver_element_set_integer(elemPtr, nameBuf.buf, value));
69
+ return;
70
+ }
71
+
72
+ if (typeof value === "number") {
73
+ if (Number.isInteger(value)) {
74
+ check(lib.quiver_element_set_integer(elemPtr, nameBuf.buf, BigInt(value)));
75
+ } else {
76
+ check(lib.quiver_element_set_float(elemPtr, nameBuf.buf, value));
77
+ }
78
+ return;
79
+ }
80
+
81
+ if (typeof value === "string") {
82
+ const valBuf = toCString(value);
83
+ check(lib.quiver_element_set_string(elemPtr, nameBuf.buf, valBuf.buf));
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;
102
+
103
+ const outElem = allocPtrOut();
104
+ check(lib.quiver_element_create(outElem.ptr));
105
+ const elemPtr = readPtrOut(outElem);
106
+
107
+ try {
108
+ for (const [key, value] of Object.entries(data)) {
109
+ if (value === undefined) continue;
110
+ setElementField(lib, elemPtr, key, value);
111
+ }
112
+
113
+ const outIdBuf = new Uint8Array(8);
114
+ const outIdPtr = ptr(outIdBuf);
115
+ const collBuf = toCString(collection);
116
+ check(lib.quiver_database_create_element(handle, collBuf.buf, elemPtr, outIdPtr));
117
+ return Number(new DataView(outIdBuf.buffer).getBigInt64(0, true));
118
+ } finally {
119
+ lib.quiver_element_destroy(elemPtr);
120
+ }
121
+ };
122
+
123
+ Database.prototype.updateElement = function (
124
+ this: Database,
125
+ collection: string,
126
+ id: number,
127
+ data: ElementData,
128
+ ): void {
129
+ const lib = getSymbols();
130
+ const handle = this._handle;
131
+
132
+ const outElem = allocPtrOut();
133
+ check(lib.quiver_element_create(outElem.ptr));
134
+ const elemPtr = readPtrOut(outElem);
135
+
136
+ try {
137
+ for (const [key, value] of Object.entries(data)) {
138
+ if (value === undefined) continue;
139
+ setElementField(lib, elemPtr, key, value);
140
+ }
141
+ const collBuf = toCString(collection);
142
+ check(lib.quiver_database_update_element(handle, collBuf.buf, BigInt(id), elemPtr));
143
+ } finally {
144
+ lib.quiver_element_destroy(elemPtr);
145
+ }
146
+ };
147
+
148
+ Database.prototype.deleteElement = function (this: Database, collection: string, id: number): void {
149
+ const lib = getSymbols();
150
+ const collBuf = toCString(collection);
151
+ check(lib.quiver_database_delete_element(this._handle, collBuf.buf, BigInt(id)));
152
+ };
package/src/csv.ts ADDED
@@ -0,0 +1,118 @@
1
+ import { ptr } from "bun:ffi";
2
+ import { Database } from "./database.ts";
3
+ import { check } from "./errors.ts";
4
+ import {
5
+ allocNativeInt64,
6
+ allocNativeString,
7
+ allocNativeStringArray,
8
+ nativeAddress,
9
+ toCString,
10
+ } from "./ffi-helpers.ts";
11
+ import { getSymbols } from "./loader.ts";
12
+ import type { Allocation } from "./types.ts";
13
+
14
+ export interface CsvOptions {
15
+ dateTimeFormat?: string;
16
+ enumLabels?: Record<string, Record<string, Record<string, number>>>;
17
+ }
18
+
19
+ /**
20
+ * Build the 56-byte quiver_csv_options_t struct as an Allocation.
21
+ * Returns [structAllocation, keepalive] where keepalive prevents GC of native allocations.
22
+ */
23
+ function buildCsvOptionsBuffer(options?: CsvOptions): [Allocation, Allocation[]] {
24
+ const buf = new Uint8Array(56);
25
+ const dv = new DataView(buf.buffer);
26
+ const keepalive: Allocation[] = [];
27
+
28
+ const dtfStr = allocNativeString(options?.dateTimeFormat ?? "");
29
+ keepalive.push(dtfStr);
30
+ dv.setBigUint64(0, nativeAddress(dtfStr.ptr), true);
31
+
32
+ if (!options?.enumLabels || Object.keys(options.enumLabels).length === 0) {
33
+ return [{ ptr: ptr(buf), buf }, keepalive];
34
+ }
35
+
36
+ const groupAttrNames: string[] = [];
37
+ const groupLocaleNames: string[] = [];
38
+ const groupEntryCounts: number[] = [];
39
+ const allLabels: string[] = [];
40
+ const allValues: number[] = [];
41
+
42
+ for (const [attrName, locales] of Object.entries(options.enumLabels)) {
43
+ for (const [localeName, entries] of Object.entries(locales)) {
44
+ groupAttrNames.push(attrName);
45
+ groupLocaleNames.push(localeName);
46
+ groupEntryCounts.push(Object.keys(entries).length);
47
+ for (const [label, value] of Object.entries(entries)) {
48
+ allLabels.push(label);
49
+ allValues.push(value);
50
+ }
51
+ }
52
+ }
53
+
54
+ const groupCount = groupAttrNames.length;
55
+
56
+ const { table: attrTable, keepalive: attrPtrs } = allocNativeStringArray(groupAttrNames);
57
+ keepalive.push(attrTable, ...attrPtrs);
58
+
59
+ const { table: localeTable, keepalive: localePtrs } = allocNativeStringArray(groupLocaleNames);
60
+ keepalive.push(localeTable, ...localePtrs);
61
+
62
+ const entryCountsBuf = new Uint8Array(groupCount * 8);
63
+ const entryCountsDv = new DataView(entryCountsBuf.buffer);
64
+ for (let i = 0; i < groupCount; i++) {
65
+ entryCountsDv.setBigUint64(i * 8, BigInt(groupEntryCounts[i]), true);
66
+ }
67
+ const entryCounts: Allocation = { ptr: ptr(entryCountsBuf), buf: entryCountsBuf };
68
+ keepalive.push(entryCounts);
69
+
70
+ const { table: labelsTable, keepalive: labelPtrs } = allocNativeStringArray(allLabels);
71
+ keepalive.push(labelsTable, ...labelPtrs);
72
+
73
+ const valuesArr = allocNativeInt64(allValues);
74
+ keepalive.push(valuesArr);
75
+
76
+ dv.setBigUint64(8, nativeAddress(attrTable.ptr), true);
77
+ dv.setBigUint64(16, nativeAddress(localeTable.ptr), true);
78
+ dv.setBigUint64(24, nativeAddress(entryCounts.ptr), true);
79
+ dv.setBigUint64(32, nativeAddress(labelsTable.ptr), true);
80
+ dv.setBigUint64(40, nativeAddress(valuesArr.ptr), true);
81
+ dv.setBigUint64(48, BigInt(groupCount), true);
82
+
83
+ return [{ ptr: ptr(buf), buf }, keepalive];
84
+ }
85
+
86
+ Database.prototype.exportCsv = function (
87
+ this: Database,
88
+ collection: string,
89
+ group: string,
90
+ filePath: string,
91
+ options?: CsvOptions,
92
+ ): void {
93
+ const lib = getSymbols();
94
+ const collBuf = toCString(collection);
95
+ const grpBuf = toCString(group);
96
+ const pathBuf = toCString(filePath);
97
+ const [optsBuf, _keepalive] = buildCsvOptionsBuffer(options);
98
+ check(
99
+ lib.quiver_database_export_csv(this._handle, collBuf.buf, grpBuf.buf, pathBuf.buf, optsBuf.ptr),
100
+ );
101
+ };
102
+
103
+ Database.prototype.importCsv = function (
104
+ this: Database,
105
+ collection: string,
106
+ group: string,
107
+ filePath: string,
108
+ options?: CsvOptions,
109
+ ): void {
110
+ const lib = getSymbols();
111
+ const collBuf = toCString(collection);
112
+ const grpBuf = toCString(group);
113
+ const pathBuf = toCString(filePath);
114
+ const [optsBuf, _keepalive] = buildCsvOptionsBuffer(options);
115
+ check(
116
+ lib.quiver_database_import_csv(this._handle, collBuf.buf, grpBuf.buf, pathBuf.buf, optsBuf.ptr),
117
+ );
118
+ };