quiverdb 0.10.2 → 0.10.3

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 CHANGED
@@ -64,6 +64,7 @@ bun run example.ts
64
64
 
65
65
  - `Database.fromSchema(dbPath, schemaPath)` -- Create database from SQL schema file
66
66
  - `Database.fromMigrations(dbPath, migrationsPath)` -- Create database from migrations directory
67
+ - `Database.validateMigrations(migrationsPath)` -- Validate a migrations directory (every `up.sql`, then every `down.sql`, ending with no table left behind) in-memory; throws on failure
67
68
  - `close()` -- Close the database connection
68
69
 
69
70
  ### Create / Delete
@@ -77,24 +78,30 @@ bun run example.ts
77
78
  ### Read (bulk)
78
79
 
79
80
  - `readScalarIntegers(collection, attribute)` -- Read all integer scalars
81
+ - `readScalarBooleans(collection, attribute)` -- Read INTEGER-backed boolean scalars
80
82
  - `readScalarFloats(collection, attribute)` -- Read all float scalars
81
83
  - `readScalarStrings(collection, attribute)` -- Read all string scalars
82
84
  - `readVectorIntegers(collection, attribute)` -- Read all integer vectors
85
+ - `readVectorBooleans(collection, attribute)` -- Read INTEGER-backed boolean vectors
83
86
  - `readVectorFloats(collection, attribute)` -- Read all float vectors
84
87
  - `readVectorStrings(collection, attribute)` -- Read all string vectors
85
88
  - `readSetIntegers(collection, attribute)` -- Read all integer sets
89
+ - `readSetBooleans(collection, attribute)` -- Read INTEGER-backed boolean sets
86
90
  - `readSetFloats(collection, attribute)` -- Read all float sets
87
91
  - `readSetStrings(collection, attribute)` -- Read all string sets
88
92
 
89
93
  ### Read (by ID)
90
94
 
91
95
  - `readScalarIntegerById(collection, attribute, id)` -- Read integer or null
96
+ - `readScalarBooleanById(collection, attribute, id)` -- Read INTEGER-backed boolean or null
92
97
  - `readScalarFloatById(collection, attribute, id)` -- Read float or null
93
98
  - `readScalarStringById(collection, attribute, id)` -- Read string or null
94
99
  - `readVectorIntegersById(collection, attribute, id)` -- Read integer vector
100
+ - `readVectorBooleansById(collection, attribute, id)` -- Read INTEGER-backed boolean vector
95
101
  - `readVectorFloatsById(collection, attribute, id)` -- Read float vector
96
102
  - `readVectorStringsById(collection, attribute, id)` -- Read string vector
97
103
  - `readSetIntegersById(collection, attribute, id)` -- Read integer set
104
+ - `readSetBooleansById(collection, attribute, id)` -- Read INTEGER-backed boolean set
98
105
  - `readSetFloatsById(collection, attribute, id)` -- Read float set
99
106
  - `readSetStringsById(collection, attribute, id)` -- Read string set
100
107
 
@@ -123,9 +130,11 @@ bun run example.ts
123
130
 
124
131
  - `queryString(sql, parameters?)` -- Query returning string or null
125
132
  - `queryInteger(sql, parameters?)` -- Query returning integer or null
133
+ - `queryBoolean(sql, parameters?)` -- Query returning an INTEGER-backed boolean or null
126
134
  - `queryFloat(sql, parameters?)` -- Query returning float or null
127
135
 
128
- Parameters are passed as an array of `number | string | null`.
136
+ Parameters are passed as an array of `number | boolean | string | null` (a `boolean` binds as the
137
+ INTEGER 1 or 0).
129
138
 
130
139
  ### Transaction
131
140
 
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quiverdb",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/src/boolean.ts ADDED
@@ -0,0 +1,20 @@
1
+ export function integerToBoolean(value: number, collection?: string, attribute?: string): boolean;
2
+ export function integerToBoolean(value: null, collection?: string, attribute?: string): null;
3
+ export function integerToBoolean(
4
+ value: number | null,
5
+ collection?: string,
6
+ attribute?: string,
7
+ ): boolean | null;
8
+ export function integerToBoolean(
9
+ value: number | null,
10
+ collection?: string,
11
+ attribute?: string,
12
+ ): boolean | null {
13
+ if (value === null) return null;
14
+ if (value === 0) return false;
15
+ if (value === 1) return true;
16
+ const source = collection ? ` in '${collection}.${attribute}'` : "";
17
+ // A RangeError, not a QuiverError: the message is crafted here, not read from
18
+ // quiver_get_last_error — these readers are a binding-only convenience.
19
+ throw new RangeError(`Cannot convert integer ${value} to boolean${source}: expected 0 or 1`);
20
+ }
package/src/create.ts CHANGED
@@ -35,6 +35,12 @@ function setElementArray(
35
35
  return;
36
36
  }
37
37
 
38
+ if (typeof first === "boolean") {
39
+ const arr = allocNativeInt64((values as boolean[]).map((v) => (v ? 1 : 0)));
40
+ check(lib.quiver_element_set_array_integer(elemPtr, nameBuf.buf, arr.buf, values.length, null));
41
+ return;
42
+ }
43
+
38
44
  if (typeof first === "number") {
39
45
  const allIntegers = (values as number[]).every((v) => Number.isInteger(v));
40
46
  if (allIntegers) {
@@ -73,6 +79,11 @@ function setElementField(lib: Symbols, elemPtr: NativePointer, name: string, val
73
79
  return;
74
80
  }
75
81
 
82
+ if (typeof value === "boolean") {
83
+ check(lib.quiver_element_set_integer(elemPtr, nameBuf.buf, value ? 1n : 0n));
84
+ return;
85
+ }
86
+
76
87
  if (typeof value === "number") {
77
88
  if (Number.isInteger(value)) {
78
89
  check(lib.quiver_element_set_integer(elemPtr, nameBuf.buf, BigInt(value)));
@@ -175,6 +186,53 @@ Database.prototype.updateElementByLabel = function (
175
186
  }
176
187
  };
177
188
 
189
+ /**
190
+ * Points one scalar foreign-key relation at the element labeled `targetLabel`; `null` clears it.
191
+ * The column is derived as `collectionTo.toLowerCase() + "_" + relationType`.
192
+ */
193
+ Database.prototype.updateRelation = function (
194
+ this: Database,
195
+ collectionFrom: string,
196
+ collectionTo: string,
197
+ relationType: string,
198
+ id: number,
199
+ targetLabel: string | null,
200
+ ): void {
201
+ const lib = getSymbols();
202
+ check(
203
+ lib.quiver_database_update_relation(
204
+ this._handle,
205
+ toCString(collectionFrom).buf,
206
+ toCString(collectionTo).buf,
207
+ toCString(relationType).buf,
208
+ BigInt(id),
209
+ targetLabel === null ? null : toCString(targetLabel).buf,
210
+ ),
211
+ );
212
+ };
213
+
214
+ /** Label-addressed counterpart of updateRelation. */
215
+ Database.prototype.updateRelationByLabel = function (
216
+ this: Database,
217
+ collectionFrom: string,
218
+ collectionTo: string,
219
+ relationType: string,
220
+ label: string,
221
+ targetLabel: string | null,
222
+ ): void {
223
+ const lib = getSymbols();
224
+ check(
225
+ lib.quiver_database_update_relation_by_label(
226
+ this._handle,
227
+ toCString(collectionFrom).buf,
228
+ toCString(collectionTo).buf,
229
+ toCString(relationType).buf,
230
+ toCString(label).buf,
231
+ targetLabel === null ? null : toCString(targetLabel).buf,
232
+ ),
233
+ );
234
+ };
235
+
178
236
  Database.prototype.deleteElement = function (this: Database, collection: string, id: number): void {
179
237
  const lib = getSymbols();
180
238
  const collBuf = toCString(collection);
package/src/database.ts CHANGED
@@ -64,6 +64,17 @@ export class Database {
64
64
  return new Database(readPtrOut(outDb));
65
65
  }
66
66
 
67
+ /**
68
+ * Applies every migration's up.sql in order, then every down.sql in reverse.
69
+ * Throws on failure, including a round trip that leaves any table behind.
70
+ */
71
+ static validateMigrations(migrationsPath: string): void {
72
+ const lib = getSymbols();
73
+ const migrPathBuf = toCString(migrationsPath);
74
+
75
+ check(lib.quiver_database_validate_migrations(migrPathBuf.buf));
76
+ }
77
+
67
78
  close(): void {
68
79
  if (this._closed) return;
69
80
  const lib = getSymbols();
@@ -89,9 +100,24 @@ export class Database {
89
100
  declare updateElementByLabel: (collection: string, label: string, data: ElementData) => void;
90
101
  declare deleteElement: (collection: string, id: number) => void;
91
102
  declare deleteElementByLabel: (collection: string, label: string) => void;
103
+ declare updateRelation: (
104
+ collectionFrom: string,
105
+ collectionTo: string,
106
+ relationType: string,
107
+ id: number,
108
+ targetLabel: string | null,
109
+ ) => void;
110
+ declare updateRelationByLabel: (
111
+ collectionFrom: string,
112
+ collectionTo: string,
113
+ relationType: string,
114
+ label: string,
115
+ targetLabel: string | null,
116
+ ) => void;
92
117
 
93
118
  // --- Reads (implemented in read.ts) ---
94
119
  declare readScalarIntegers: (collection: string, attribute: string) => (number | null)[];
120
+ declare readScalarBooleans: (collection: string, attribute: string) => (boolean | null)[];
95
121
  declare readScalarFloats: (collection: string, attribute: string) => (number | null)[];
96
122
  declare readScalarStrings: (collection: string, attribute: string) => (string | null)[];
97
123
  declare readScalarIntegerById: (
@@ -99,6 +125,11 @@ export class Database {
99
125
  attribute: string,
100
126
  id: number,
101
127
  ) => number | null;
128
+ declare readScalarBooleanById: (
129
+ collection: string,
130
+ attribute: string,
131
+ id: number,
132
+ ) => boolean | null;
102
133
  declare readScalarFloatById: (collection: string, attribute: string, id: number) => number | null;
103
134
  declare readScalarStringById: (
104
135
  collection: string,
@@ -108,21 +139,26 @@ export class Database {
108
139
  declare readElementIds: (collection: string) => number[];
109
140
  declare numberOfElements: (collection: string) => number;
110
141
  declare readVectorIntegers: (collection: string, attribute: string) => number[][];
142
+ declare readVectorBooleans: (collection: string, attribute: string) => boolean[][];
111
143
  declare readVectorFloats: (collection: string, attribute: string) => number[][];
112
144
  declare readVectorStrings: (collection: string, attribute: string) => string[][];
113
145
  declare readVectorIntegersById: (collection: string, attribute: string, id: number) => number[];
146
+ declare readVectorBooleansById: (collection: string, attribute: string, id: number) => boolean[];
114
147
  declare readVectorFloatsById: (collection: string, attribute: string, id: number) => number[];
115
148
  declare readVectorStringsById: (collection: string, attribute: string, id: number) => string[];
116
149
  declare readSetIntegers: (collection: string, attribute: string) => number[][];
150
+ declare readSetBooleans: (collection: string, attribute: string) => boolean[][];
117
151
  declare readSetFloats: (collection: string, attribute: string) => number[][];
118
152
  declare readSetStrings: (collection: string, attribute: string) => string[][];
119
153
  declare readSetIntegersById: (collection: string, attribute: string, id: number) => number[];
154
+ declare readSetBooleansById: (collection: string, attribute: string, id: number) => boolean[];
120
155
  declare readSetFloatsById: (collection: string, attribute: string, id: number) => number[];
121
156
  declare readSetStringsById: (collection: string, attribute: string, id: number) => string[];
122
157
 
123
158
  // --- Queries (implemented in query.ts) ---
124
159
  declare queryString: (sql: string, parameters?: QueryParam[]) => string | null;
125
160
  declare queryInteger: (sql: string, parameters?: QueryParam[]) => number | null;
161
+ declare queryBoolean: (sql: string, parameters?: QueryParam[]) => boolean | null;
126
162
  declare queryFloat: (sql: string, parameters?: QueryParam[]) => number | null;
127
163
 
128
164
  // --- Transactions (implemented in transaction.ts) ---
package/src/loader.ts CHANGED
@@ -35,6 +35,7 @@ const lifecycleSymbols = {
35
35
  // struct in JS.
36
36
  quiver_database_from_schema: { args: [BUF, BUF, BUF, P], returns: I32 },
37
37
  quiver_database_from_migrations: { args: [BUF, BUF, BUF, P], returns: I32 },
38
+ quiver_database_validate_migrations: { args: [BUF], returns: I32 },
38
39
  quiver_database_open: { args: [BUF, BUF, P], returns: I32 },
39
40
  quiver_database_close: { args: [P], returns: I32 },
40
41
  quiver_database_is_healthy: { args: [P, P], returns: I32 },
@@ -61,6 +62,8 @@ const crudSymbols = {
61
62
  quiver_database_update_element_by_label: { args: [P, BUF, BUF, P], returns: I32 },
62
63
  quiver_database_delete_element: { args: [P, BUF, I64], returns: I32 },
63
64
  quiver_database_delete_element_by_label: { args: [P, BUF, BUF], returns: I32 },
65
+ quiver_database_update_relation: { args: [P, BUF, BUF, BUF, I64, BUF], returns: I32 },
66
+ quiver_database_update_relation_by_label: { args: [P, BUF, BUF, BUF, BUF, BUF], returns: I32 },
64
67
  } as const;
65
68
 
66
69
  const readSymbols = {
package/src/lua-api.ts CHANGED
@@ -10,8 +10,9 @@
10
10
  // and whether the prose is semantically true.
11
11
  //
12
12
  // NOTE: the binary/expression subsystems are bound in the native binding and documented below.
13
- // File-touching operations (db:open_file, db:bin_to_csv, db:csv_to_bin, expr:save) are sandboxed
14
- // to the database file's directory; the pure-metadata builders stay under the quiver.* global.
13
+ // File-touching operations (db:open_file, db:bin_to_csv, db:csv_to_bin, db:validate_migrations,
14
+ // expr:save) are sandboxed to the database file's directory; the pure-metadata builders stay under
15
+ // the quiver.* global.
15
16
  //
16
17
  // FORMAT CONVENTION: every db: method appears at least once as the literal token
17
18
  // `db:<snake_case_name>`, and every quiver.* function as `quiver.<name>`, so coverage is greppable
@@ -50,7 +51,7 @@ Lua values map to Quiver column values as follows:
50
51
  | integer | INTEGER | Also accepted for REAL columns (coerced to real). |
51
52
  | number (float) | REAL | A float is rejected for an INTEGER column. |
52
53
  | string | TEXT | Also used for \`date_time\` columns (ISO 8601). |
53
- | \`nil\` | NULL | In query params, file paths, and ts rows. |
54
+ | \`nil\` | NULL | In query params, file paths, ts rows, relations.|
54
55
  | table (1-indexed) | array | Used for vectors/sets and column-oriented data.|
55
56
 
56
57
  **Unsupported types throw.** Passing a boolean, a function, or a nested table where a scalar is
@@ -59,15 +60,21 @@ attributes, time-series rows, and query parameters. A skipped positional query p
59
60
  shift every later parameter and bind NULL to the trailing placeholder, so this is rejected loudly.
60
61
 
61
62
  Dates are plain strings in ISO 8601 format: \`YYYY-MM-DDTHH:MM:SS\`. (Lua keeps a string-based
62
- datetime surface — there are no DateTime wrapper helpers, unlike Julia/Dart/Python.)
63
+ datetime surface — there are no DateTime wrapper helpers, unlike Julia/Dart/Python.) The time part
64
+ is optional, so \`"2024-01-15"\` is also valid, and a space may replace the \`T\`. Every field is
65
+ fixed-width and zero-padded. Anything shorter or malformed — \`"2005"\`, \`"2005-01"\`,
66
+ \`"2024-02-31"\`, \`"2024-1-5"\`, \`"2024-01-15T10:30"\` — is **rejected when you write it**, not
67
+ silently stored. The value is stored exactly as written; a date-only value is not padded to
68
+ midnight.
63
69
 
64
70
  ---
65
71
 
66
72
  ## Critical rules
67
73
 
68
74
  - **Type coercion.** An integer is accepted for a REAL column (coerced to real on insert); a float
69
- is rejected for an INTEGER column. Other type mismatches raise a validation error and roll the
70
- whole script back.
75
+ is rejected for an INTEGER column. A string bound to a \`date_*\` column must parse as ISO 8601
76
+ (\`YYYY-MM-DD\`, optionally \`THH:MM:SS\` or \` HH:MM:SS\`). Other type mismatches raise a
77
+ validation error and roll the whole script back.
71
78
  - **Errors abort the script.** Any error thrown by a \`db:\` call stops the script and surfaces as
72
79
  \`Failed to run Lua script: <message>\`. Validation failures roll back whatever the current
73
80
  transaction covered.
@@ -76,16 +83,16 @@ datetime surface — there are no DateTime wrapper helpers, unlike Julia/Dart/Py
76
83
  and \`dofile\`/\`loadfile\` are removed (string-form \`load\` stays available). Integer division is
77
84
  the Lua 5.4 \`//\` operator — a language operator, unrelated to \`math\`.
78
85
  - **Filesystem sandbox.** Every file-touching operation (\`db:export_csv\`, \`db:import_csv\`,
79
- \`db:open_file\`, \`db:bin_to_csv\`, \`db:csv_to_bin\`, \`expr:save\`) resolves relative paths against
80
- the directory containing the database file and rejects anything outside it (subdirectories are
81
- fine; \`..\` escapes and outside absolute paths throw \`Cannot <op>: path '...' escapes the
86
+ \`db:open_file\`, \`db:bin_to_csv\`, \`db:csv_to_bin\`, \`db:validate_migrations\`, \`expr:save\`) resolves
87
+ relative paths against the directory containing the database file and rejects anything outside it
88
+ (subdirectories are fine; \`..\` escapes and outside absolute paths throw \`Cannot <op>: path '...' escapes the
82
89
  database directory ...\`). On an in-memory database these operations throw
83
90
  \`Cannot <op>: database is in-memory, file operations are unavailable\`.
84
91
  - **Output.** A script can \`return\` one value and the host receives it as JSON — prefer this over
85
92
  \`print()\` when you need structured data back (\`print()\` still works and is captured). Only the
86
93
  **first** returned value is encoded. Arrays are 1-indexed (iterate with \`ipairs\`); reading a NULL
87
94
  yields \`nil\`, writing \`nil\` stores NULL where NULL is accepted (query params, ts rows, file
88
- columns — but NOT element scalar attributes; see CRUD).
95
+ columns, relation targets — but NOT element scalar attributes; see CRUD).
89
96
 
90
97
  \`\`\`lua
91
98
  return { ids = db:read_element_ids("Collection"), total = 3 }
@@ -134,10 +141,15 @@ db:describe() -- string: whole-DB text report (returns it,
134
141
  db:describe_collection(collection) -- string: one collection's structure (text report)
135
142
  db:summarize_collection(collection)-- string: per-scalar null/non-null counts, low-cardinality
136
143
  -- integer value distributions, per-group sizes
144
+ db:validate_migrations(path) -- validate a migrations dir (up then down) in-memory; no return
137
145
  \`\`\`
138
146
 
139
147
  All three \`describe*\`/\`summarize*\` methods **return** a string — \`print()\` it to see it.
140
148
 
149
+ \`db:validate_migrations(path)\` applies every \`up.sql\` in version order, then every \`down.sql\` in
150
+ reverse, against a throwaway in-memory database — nothing in \`db\` itself is touched. The round trip
151
+ must end with an empty database; leftover tables are named in the error.
152
+
141
153
  ---
142
154
 
143
155
  ## Transactions
@@ -215,6 +227,9 @@ db:update_element(collection, id, element_table)
215
227
  db:update_element_by_label(collection, label, element_table) -- same update, addressed by label
216
228
  db:delete_element(collection, id)
217
229
  db:delete_element_by_label(collection, label) -- same delete, addressed by label
230
+
231
+ db:update_relation(collection_from, collection_to, relation_type, id, target_label)
232
+ db:update_relation_by_label(collection_from, collection_to, relation_type, label, target_label)
218
233
  \`\`\`
219
234
 
220
235
  The element table holds scalar attributes as \`key = value\`, and vector/set attributes as
@@ -254,7 +269,18 @@ Notes:
254
269
  **throws** (\`...must have at least one scalar attribute\` on create, \`...at least one attribute
255
270
  to update\` on update). To leave a column unchanged, omit the key — you cannot set a scalar to
256
271
  NULL via the element table. (\`nil\` → NULL is only accepted by
257
- \`upsert_time_series_row\` and \`update_time_series_files\`.)
272
+ \`upsert_time_series_row\`, \`update_time_series_files\` and \`update_relation\`.)
273
+ - **\`update_relation\` points one scalar foreign-key relation at another element**, named by the
274
+ target's label. The column is derived from the naming convention —
275
+ \`lowercase(collection_to) .. "_" .. relation_type\`, so
276
+ \`db:update_relation("Child", "Parent", "id", id, "Parent A")\` writes \`Child.parent_id\`. A
277
+ \`nil\` or omitted \`target_label\` clears the relation; anything that is not a string throws
278
+ (\`target_label has unsupported Lua type\`). The derived column must exist and be a
279
+ foreign key to \`collection_to\`, otherwise \`Cannot update_relation: ...\`. The write delegates
280
+ to \`update_element\`, so a missing id reports that method's error;
281
+ \`update_relation_by_label\` takes a label in place of the id, with
282
+ \`update_element_by_label\`'s resolution and miss semantics. A relation living in a vector, set
283
+ or time-series group is a list of targets — use that group's writer instead.
258
284
 
259
285
  ---
260
286
 
package/src/query.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ptr } from "bun:ffi";
2
+ import { integerToBoolean } from "./boolean.ts";
2
3
  import { Database } from "./database.ts";
3
4
  import { check, QuiverError } from "./errors.ts";
4
5
  import {
@@ -38,6 +39,11 @@ function marshalParams(parameters: QueryParam[]): {
38
39
  if (p === null) {
39
40
  typesDv.setInt32(i * 4, DATA_TYPE_NULL, true);
40
41
  valuesDv.setBigInt64(i * 8, 0n, true);
42
+ } else if (typeof p === "boolean") {
43
+ typesDv.setInt32(i * 4, DATA_TYPE_INTEGER, true);
44
+ const native = allocNativeInt64([p ? 1 : 0]);
45
+ keepalive.push(native);
46
+ valuesDv.setBigInt64(i * 8, nativeAddress(native.ptr), true);
41
47
  } else if (typeof p === "number") {
42
48
  if (Number.isInteger(p)) {
43
49
  typesDv.setInt32(i * 4, DATA_TYPE_INTEGER, true);
@@ -130,6 +136,14 @@ Database.prototype.queryInteger = function (
130
136
  return Number(new DataView(outValue.buffer).getBigInt64(0, true));
131
137
  };
132
138
 
139
+ Database.prototype.queryBoolean = function (
140
+ this: Database,
141
+ sql: string,
142
+ parameters?: QueryParam[],
143
+ ): boolean | null {
144
+ return integerToBoolean(this.queryInteger(sql, parameters));
145
+ };
146
+
133
147
  Database.prototype.queryFloat = function (
134
148
  this: Database,
135
149
  sql: string,
package/src/read.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CString, type Pointer, toArrayBuffer } from "bun:ffi";
2
+ import { integerToBoolean } from "./boolean.ts";
2
3
  import { Database } from "./database.ts";
3
4
  import { check } from "./errors.ts";
4
5
  import {
@@ -52,6 +53,16 @@ Database.prototype.readScalarIntegers = function (
52
53
  return result;
53
54
  };
54
55
 
56
+ Database.prototype.readScalarBooleans = function (
57
+ this: Database,
58
+ collection: string,
59
+ attribute: string,
60
+ ): (boolean | null)[] {
61
+ return this.readScalarIntegers(collection, attribute).map((value) =>
62
+ integerToBoolean(value, collection, attribute),
63
+ );
64
+ };
65
+
55
66
  Database.prototype.readScalarFloats = function (
56
67
  this: Database,
57
68
  collection: string,
@@ -143,6 +154,19 @@ Database.prototype.readScalarIntegerById = function (
143
154
  return Number(new DataView(outValBuf.buffer).getBigInt64(0, true));
144
155
  };
145
156
 
157
+ Database.prototype.readScalarBooleanById = function (
158
+ this: Database,
159
+ collection: string,
160
+ attribute: string,
161
+ id: number,
162
+ ): boolean | null {
163
+ return integerToBoolean(
164
+ this.readScalarIntegerById(collection, attribute, id),
165
+ collection,
166
+ attribute,
167
+ );
168
+ };
169
+
146
170
  Database.prototype.readScalarFloatById = function (
147
171
  this: Database,
148
172
  collection: string,
@@ -342,6 +366,19 @@ Database.prototype.readVectorIntegers = function (
342
366
  attribute,
343
367
  );
344
368
  };
369
+ /**
370
+ * NULL cells are dropped and only elements that own rows are returned, so the result is not
371
+ * positionally aligned with `readElementIds` (unlike `readScalarBooleans`).
372
+ */
373
+ Database.prototype.readVectorBooleans = function (
374
+ this: Database,
375
+ collection: string,
376
+ attribute: string,
377
+ ): boolean[][] {
378
+ return this.readVectorIntegers(collection, attribute).map((values) =>
379
+ values.map((value) => integerToBoolean(value, collection, attribute)),
380
+ );
381
+ };
345
382
  Database.prototype.readVectorFloats = function (
346
383
  this: Database,
347
384
  collection: string,
@@ -381,6 +418,16 @@ Database.prototype.readSetIntegers = function (
381
418
  attribute,
382
419
  );
383
420
  };
421
+ /** Same alignment caveat as `readVectorBooleans`: NULL cells dropped, only ids that own rows. */
422
+ Database.prototype.readSetBooleans = function (
423
+ this: Database,
424
+ collection: string,
425
+ attribute: string,
426
+ ): boolean[][] {
427
+ return this.readSetIntegers(collection, attribute).map((values) =>
428
+ values.map((value) => integerToBoolean(value, collection, attribute)),
429
+ );
430
+ };
384
431
  Database.prototype.readSetFloats = function (
385
432
  this: Database,
386
433
  collection: string,
@@ -515,6 +562,16 @@ Database.prototype.readVectorIntegersById = function (
515
562
  id,
516
563
  );
517
564
  };
565
+ Database.prototype.readVectorBooleansById = function (
566
+ this: Database,
567
+ collection: string,
568
+ attribute: string,
569
+ id: number,
570
+ ): boolean[] {
571
+ return this.readVectorIntegersById(collection, attribute, id).map((value) =>
572
+ integerToBoolean(value, collection, attribute),
573
+ );
574
+ };
518
575
  Database.prototype.readVectorFloatsById = function (
519
576
  this: Database,
520
577
  collection: string,
@@ -560,6 +617,16 @@ Database.prototype.readSetIntegersById = function (
560
617
  id,
561
618
  );
562
619
  };
620
+ Database.prototype.readSetBooleansById = function (
621
+ this: Database,
622
+ collection: string,
623
+ attribute: string,
624
+ id: number,
625
+ ): boolean[] {
626
+ return this.readSetIntegersById(collection, attribute, id).map((value) =>
627
+ integerToBoolean(value, collection, attribute),
628
+ );
629
+ };
563
630
  Database.prototype.readSetFloatsById = function (
564
631
  this: Database,
565
632
  collection: string,
package/src/types.ts CHANGED
@@ -21,11 +21,12 @@ export type DatabaseOptions = {
21
21
  consoleLevel?: number;
22
22
  };
23
23
 
24
- export type ScalarValue = number | bigint | string | null;
25
- export type ArrayValue = number[] | bigint[] | string[];
24
+ /** A `boolean` is stored as the INTEGER 1 or 0 (see `readScalarBooleans` for the read side). */
25
+ export type ScalarValue = number | bigint | boolean | string | null;
26
+ export type ArrayValue = number[] | bigint[] | boolean[] | string[];
26
27
  export type Value = ScalarValue | ArrayValue;
27
28
  export type ElementData = Record<string, Value | undefined>;
28
- export type QueryParam = number | string | null;
29
+ export type QueryParam = number | boolean | string | null;
29
30
 
30
31
  /** Native memory allocation result. Callers MUST hold `buf` in scope to prevent GC. */
31
32
  export type Allocation = {