@lobomfz/db 0.3.9 → 0.4.1

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.
@@ -39,13 +39,67 @@ export type DropIndexOp = {
39
39
  index: string;
40
40
  };
41
41
 
42
+ export type FtsTriggerSpec = {
43
+ name: string;
44
+ sql: string;
45
+ };
46
+
47
+ export type CreateFtsOp = {
48
+ type: "CreateFts";
49
+ name: string;
50
+ createSql: string;
51
+ triggers: FtsTriggerSpec[];
52
+ };
53
+
54
+ export type DropFtsOp = {
55
+ type: "DropFts";
56
+ name: string;
57
+ triggerNames: string[];
58
+ };
59
+
60
+ export type RebuildFtsOp = {
61
+ type: "RebuildFts";
62
+ name: string;
63
+ rebuildSql: string;
64
+ };
65
+
66
+ export type VectorTriggerSpec = {
67
+ name: string;
68
+ sql: string;
69
+ };
70
+
71
+ export type CreateVectorIndexOp = {
72
+ type: "CreateVectorIndex";
73
+ name: string;
74
+ createSql: string;
75
+ triggers: VectorTriggerSpec[];
76
+ };
77
+
78
+ export type DropVectorIndexOp = {
79
+ type: "DropVectorIndex";
80
+ name: string;
81
+ triggerNames: string[];
82
+ };
83
+
84
+ export type RebuildVectorIndexOp = {
85
+ type: "RebuildVectorIndex";
86
+ name: string;
87
+ rebuildSql: string;
88
+ };
89
+
42
90
  export type MigrationOp =
43
91
  | CreateTableOp
44
92
  | DropTableOp
45
93
  | AddColumnOp
46
94
  | RebuildTableOp
47
95
  | CreateIndexOp
48
- | DropIndexOp;
96
+ | DropIndexOp
97
+ | CreateFtsOp
98
+ | DropFtsOp
99
+ | RebuildFtsOp
100
+ | CreateVectorIndexOp
101
+ | DropVectorIndexOp
102
+ | RebuildVectorIndexOp;
49
103
 
50
104
  export type ColumnSchema = {
51
105
  name: string;
@@ -72,3 +126,16 @@ export type IntrospectedTable = {
72
126
  indexes: IntrospectedIndex[];
73
127
  hasData: boolean;
74
128
  };
129
+
130
+ export type IntrospectedFts = {
131
+ name: string;
132
+ columns: string[];
133
+ sql: string;
134
+ triggers: Map<string, string>;
135
+ };
136
+
137
+ export type IntrospectedVectorIndex = {
138
+ name: string;
139
+ sql: string;
140
+ triggers: Map<string, string>;
141
+ };
package/src/plugin.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  type OperationNode,
6
6
  type QueryId,
7
7
  type RootOperationNode,
8
+ type SelectionNode as KyselySelectionNode,
8
9
  type UnknownRow,
9
10
  AggregateFunctionNode,
10
11
  AliasNode,
@@ -12,16 +13,22 @@ import {
12
13
  ColumnNode,
13
14
  IdentifierNode,
14
15
  ParensNode,
16
+ RawNode,
15
17
  ReferenceNode,
18
+ SelectionNode,
16
19
  SelectQueryNode,
17
20
  TableNode,
18
21
  } from "kysely";
19
22
 
20
23
  import { JsonParseError } from "./errors.js";
21
- import { JsonValidationError } from "./validation-error.js";
24
+ import type { StructureProp } from "./types.js";
25
+ import { ValidationError } from "./validation-error.js";
22
26
 
23
- export type ColumnCoercion = "boolean" | "date" | { type: "json"; schema: Type };
24
- export type ColumnsMap = Map<string, Map<string, ColumnCoercion>>;
27
+ type ColumnCoercion =
28
+ | "boolean"
29
+ | "date"
30
+ | { type: "json"; schema: Type }
31
+ | { type: "vector"; dim: number };
25
32
 
26
33
  type ResolvedCoercion = { table: string; coercion: ColumnCoercion };
27
34
 
@@ -79,13 +86,90 @@ const jsonObjectFromFragments = [
79
86
  const typePreservingAggregateFunctions = new Set(["max", "min"]);
80
87
 
81
88
  export class ResultHydrationPlugin implements KyselyPlugin {
89
+ private columns = new Map<string, Map<string, ColumnCoercion>>();
90
+ private tableColumns = new Map<string, Set<string>>();
82
91
  private queryPlans = new WeakMap<QueryId, QueryPlan>();
83
92
 
84
93
  constructor(
85
- private columns: ColumnsMap,
86
- private tableColumns: Map<string, Set<string>>,
94
+ schemas: Map<string, Type>,
87
95
  private validation: { onRead: boolean },
88
- ) {}
96
+ ) {
97
+ this.registerSchemas(schemas);
98
+ }
99
+
100
+ private registerSchemas(schemas: Map<string, Type>) {
101
+ for (const [table, schema] of schemas) {
102
+ const structureProps = (schema as any).structure?.props as StructureProp[] | undefined;
103
+
104
+ if (!structureProps) {
105
+ continue;
106
+ }
107
+
108
+ this.tableColumns.set(
109
+ table,
110
+ new Set(structureProps.map((prop) => prop.key)),
111
+ );
112
+
113
+ const columns = new Map<string, ColumnCoercion>();
114
+
115
+ for (const prop of structureProps) {
116
+ const coercion = this.getColumnCoercion(prop, schema);
117
+
118
+ if (!coercion) {
119
+ continue;
120
+ }
121
+
122
+ columns.set(prop.key, coercion);
123
+ }
124
+
125
+ if (columns.size > 0) {
126
+ this.columns.set(table, columns);
127
+ }
128
+ }
129
+ }
130
+
131
+ private extractFieldMeta(prop: StructureProp) {
132
+ const rootMeta = (prop.value as any).meta as
133
+ | { _generated?: string; _vectorDim?: number }
134
+ | undefined;
135
+ const branchWithMeta = prop.value.branches.find(
136
+ (b) => (b as any).meta && Object.keys((b as any).meta).length > 0,
137
+ );
138
+ const fromBranch = (branchWithMeta as any)?.meta as
139
+ | { _generated?: string; _vectorDim?: number }
140
+ | undefined;
141
+
142
+ return { ...fromBranch, ...rootMeta };
143
+ }
144
+
145
+ private getColumnCoercion(prop: StructureProp, parentSchema: Type) {
146
+ const meta = this.extractFieldMeta(prop);
147
+
148
+ if (meta._generated === "vector" && typeof meta._vectorDim === "number") {
149
+ return { type: "vector", dim: meta._vectorDim } satisfies ColumnCoercion;
150
+ }
151
+
152
+ const concrete = prop.value.branches.filter(
153
+ (branch) => branch.unit !== null && branch.domain !== "undefined",
154
+ );
155
+
156
+ if (prop.value.proto === Date || concrete.some((branch) => branch.proto === Date)) {
157
+ return "date" satisfies ColumnCoercion;
158
+ }
159
+
160
+ if (concrete.length > 0 && concrete.every((branch) => branch.domain === "boolean")) {
161
+ return "boolean" satisfies ColumnCoercion;
162
+ }
163
+
164
+ if (concrete.some((branch) => !!branch.structure)) {
165
+ return {
166
+ type: "json",
167
+ schema: (parentSchema as any).get(prop.key) as Type,
168
+ } satisfies ColumnCoercion;
169
+ }
170
+
171
+ return null;
172
+ }
89
173
 
90
174
  transformQuery: KyselyPlugin["transformQuery"] = (args) => {
91
175
  this.queryPlans.set(args.queryId, {
@@ -93,9 +177,151 @@ export class ResultHydrationPlugin implements KyselyPlugin {
93
177
  selectionPlans: this.getSelectionPlans(args.node),
94
178
  });
95
179
 
96
- return args.node;
180
+ return this.rewriteVectorsInJsonHelpers(args.node);
97
181
  };
98
182
 
183
+ private rewriteVectorsInJsonHelpers(node: RootOperationNode): RootOperationNode {
184
+ if (!SelectQueryNode.is(node)) {
185
+ return node;
186
+ }
187
+
188
+ return this.rewriteSelectQueryNode(node) as RootOperationNode;
189
+ }
190
+
191
+ private rewriteSelectQueryNode(node: SelectQueryNode): SelectQueryNode {
192
+ if (!node.selections) {
193
+ return node;
194
+ }
195
+
196
+ let changed = false;
197
+
198
+ const newSelections = node.selections.map((sel) => {
199
+ const rewritten = this.rewriteTopLevelSelection(sel);
200
+
201
+ if (rewritten !== sel) {
202
+ changed = true;
203
+ }
204
+
205
+ return rewritten;
206
+ });
207
+
208
+ if (!changed) {
209
+ return node;
210
+ }
211
+
212
+ return Object.freeze({ ...node, selections: Object.freeze(newSelections) });
213
+ }
214
+
215
+ private rewriteTopLevelSelection(sel: KyselySelectionNode): KyselySelectionNode {
216
+ const rewritten = this.rewriteNodeForJsonHelpers(sel.selection);
217
+
218
+ if (rewritten === sel.selection) {
219
+ return sel;
220
+ }
221
+
222
+ return SelectionNode.create(rewritten as any);
223
+ }
224
+
225
+ private rewriteNodeForJsonHelpers(node: OperationNode): OperationNode {
226
+ if (AliasNode.is(node)) {
227
+ const rewrittenInner = this.rewriteNodeForJsonHelpers(node.node);
228
+
229
+ if (rewrittenInner === node.node) {
230
+ return node;
231
+ }
232
+
233
+ return AliasNode.create(rewrittenInner, node.alias as any);
234
+ }
235
+
236
+ if (!this.isRawNode(node)) {
237
+ return node;
238
+ }
239
+
240
+ const helper = this.getJsonHelper(node);
241
+
242
+ if (!helper) {
243
+ return node;
244
+ }
245
+
246
+ return this.rewriteJsonHelper(node, helper.query);
247
+ }
248
+
249
+ private rewriteJsonHelper(rawNode: RawOperationNode, subquery: SelectQueryNode) {
250
+ const scope = this.getTableScope(subquery);
251
+
252
+ if (!subquery.selections) {
253
+ return rawNode;
254
+ }
255
+
256
+ let changed = false;
257
+
258
+ const newSubquerySelections = subquery.selections.map((sel) => {
259
+ const rewrittenNested = this.rewriteNodeForJsonHelpers(sel.selection);
260
+ const withNested =
261
+ rewrittenNested === sel.selection
262
+ ? sel
263
+ : SelectionNode.create(rewrittenNested as any);
264
+
265
+ if (withNested !== sel) {
266
+ changed = true;
267
+ }
268
+
269
+ const wrapped = this.wrapVectorSelectionWithHex(withNested, scope);
270
+
271
+ if (wrapped !== withNested) {
272
+ changed = true;
273
+ }
274
+
275
+ return wrapped;
276
+ });
277
+
278
+ if (!changed) {
279
+ return rawNode;
280
+ }
281
+
282
+ const newSubquery = Object.freeze({
283
+ ...subquery,
284
+ selections: Object.freeze(newSubquerySelections),
285
+ });
286
+ const newParameters = [...rawNode.parameters];
287
+ newParameters[1] = newSubquery;
288
+
289
+ return Object.freeze({
290
+ ...rawNode,
291
+ parameters: Object.freeze(newParameters),
292
+ });
293
+ }
294
+
295
+ private wrapVectorSelectionWithHex(sel: KyselySelectionNode, scope: Map<string, string>) {
296
+ const selectionNode = sel.selection;
297
+ const output = this.getSelectionOutputName(selectionNode);
298
+
299
+ if (!output) {
300
+ return sel;
301
+ }
302
+
303
+ const inner = AliasNode.is(selectionNode) ? selectionNode.node : selectionNode;
304
+
305
+ if (!ReferenceNode.is(inner) && !ColumnNode.is(inner)) {
306
+ return sel;
307
+ }
308
+
309
+ const resolved = this.resolveReferenceCoercion(inner, scope);
310
+
311
+ if (!resolved) {
312
+ return sel;
313
+ }
314
+
315
+ if (typeof resolved.coercion !== "object" || resolved.coercion.type !== "vector") {
316
+ return sel;
317
+ }
318
+
319
+ const hexNode = RawNode.create(["hex(", ")"], [inner]);
320
+ const aliased = AliasNode.create(hexNode, IdentifierNode.create(output));
321
+
322
+ return SelectionNode.create(aliased);
323
+ }
324
+
99
325
  private getTableFromNode(node: RootOperationNode) {
100
326
  switch (node.kind) {
101
327
  case "InsertQueryNode":
@@ -141,7 +367,7 @@ export class ResultHydrationPlugin implements KyselyPlugin {
141
367
  const result = schema(value);
142
368
 
143
369
  if (result instanceof type.errors) {
144
- throw new JsonValidationError(table, col, result.summary);
370
+ throw new ValidationError(table, result.summary, col);
145
371
  }
146
372
  }
147
373
 
@@ -168,12 +394,16 @@ export class ResultHydrationPlugin implements KyselyPlugin {
168
394
 
169
395
  if (plan.coercion === "date") {
170
396
  if (typeof value === "number") {
171
- return new Date(value * 1000);
397
+ return new Date(value);
172
398
  }
173
399
 
174
400
  return value;
175
401
  }
176
402
 
403
+ if (plan.coercion.type === "vector") {
404
+ return this.hydrateVector(value);
405
+ }
406
+
177
407
  const parsed =
178
408
  typeof value === "string" ? this.parseJson(plan.table, plan.column, value) : value;
179
409
 
@@ -184,6 +414,36 @@ export class ResultHydrationPlugin implements KyselyPlugin {
184
414
  return parsed;
185
415
  }
186
416
 
417
+ private hydrateVector(value: unknown) {
418
+ if (value instanceof Float32Array) {
419
+ return value;
420
+ }
421
+
422
+ if (value instanceof Uint8Array) {
423
+ return new Float32Array(
424
+ value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength),
425
+ );
426
+ }
427
+
428
+ if (typeof value === "string") {
429
+ return this.hexToFloat32Array(value);
430
+ }
431
+
432
+ return value;
433
+ }
434
+
435
+ private hexToFloat32Array(hex: string) {
436
+ const byteLength = hex.length / 2;
437
+ const buffer = new ArrayBuffer(byteLength);
438
+ const bytes = new Uint8Array(buffer);
439
+
440
+ for (let i = 0; i < byteLength; i++) {
441
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
442
+ }
443
+
444
+ return new Float32Array(buffer);
445
+ }
446
+
187
447
  private parseStructuredValue(table: string, column: string, value: unknown) {
188
448
  if (value === null || value === undefined) {
189
449
  return value;
package/src/types.ts CHANGED
@@ -1,6 +1,23 @@
1
- import type { Generated } from "kysely";
1
+ import type { ColumnType, Generated } from "kysely";
2
2
  import type { Type } from "arktype";
3
3
 
4
+ export type ArkBranch = {
5
+ domain?: string;
6
+ proto?: unknown;
7
+ unit?: unknown;
8
+ structure?: unknown;
9
+ };
10
+
11
+ export type StructureProp = {
12
+ key: string;
13
+ required: boolean;
14
+ inner: { default?: unknown };
15
+ value: Type & {
16
+ branches: ArkBranch[];
17
+ proto?: unknown;
18
+ };
19
+ };
20
+
4
21
  type ExtractInput<T> = T extends { inferIn: infer I } ? I : never;
5
22
  type ExtractOutput<T> = T extends { infer: infer O } ? O : never;
6
23
 
@@ -8,12 +25,21 @@ type IsOptional<T, K extends keyof T> = undefined extends T[K] ? true : false;
8
25
 
9
26
  type TransformColumn<T> = T extends (infer U)[] ? U[] : T;
10
27
 
28
+ type IsVectorColumn<TOutput, K extends keyof TOutput> =
29
+ NonNullable<TOutput[K]> extends Float32Array ? true : false;
30
+
31
+ type VectorInsert = Float32Array | number[];
32
+
11
33
  type TransformField<TOutput, TInput, K extends keyof TOutput & keyof TInput> =
12
- IsOptional<TInput, K> extends true
34
+ IsVectorColumn<TOutput, K> extends true
13
35
  ? IsOptional<TOutput, K> extends true
14
- ? TransformColumn<NonNullable<TOutput[K]>> | null
15
- : Generated<TransformColumn<NonNullable<TOutput[K]>>>
16
- : TransformColumn<TOutput[K]>;
36
+ ? ColumnType<Float32Array | null, VectorInsert | null, VectorInsert | null>
37
+ : ColumnType<Float32Array, VectorInsert, VectorInsert>
38
+ : IsOptional<TInput, K> extends true
39
+ ? IsOptional<TOutput, K> extends true
40
+ ? TransformColumn<NonNullable<TOutput[K]>> | null
41
+ : Generated<TransformColumn<NonNullable<TOutput[K]>>>
42
+ : TransformColumn<TOutput[K]>;
17
43
 
18
44
  type TransformTable<TOutput, TInput> = {
19
45
  [K in keyof TOutput & keyof TInput]: TransformField<TOutput, TInput, K>;
@@ -31,12 +57,12 @@ export type SqliteMasterRow = {
31
57
  sql: string | null;
32
58
  };
33
59
 
34
- export type TablesFromSchemas<T extends SchemaRecord> = {
35
- [K in keyof T]: InferTableType<T[K]>;
36
- } & { sqlite_master: SqliteMasterRow };
37
-
38
60
  type TableColumns<T extends SchemaRecord, K extends keyof T> = keyof ExtractOutput<T[K]> & string;
39
61
 
62
+ type VectorColumnName<T extends SchemaRecord, K extends keyof T> = {
63
+ [C in TableColumns<T, K>]: NonNullable<ExtractOutput<T[K]>[C]> extends Float32Array ? C : never;
64
+ }[TableColumns<T, K>];
65
+
40
66
  export type IndexDefinition<Columns extends string = string> = {
41
67
  columns: Columns[];
42
68
  unique?: boolean;
@@ -53,19 +79,91 @@ export type DatabasePragmas = {
53
79
  busy_timeout_ms?: number;
54
80
  };
55
81
 
56
- export type DatabaseSchema<T extends SchemaRecord> = {
82
+ export type FtsTokenizer =
83
+ | { type: "unicode61"; removeDiacritics?: 0 | 1 | 2; categories?: string }
84
+ | { type: "trigram" };
85
+
86
+ export type FtsFieldRef = string | { source: string; via?: string };
87
+
88
+ export type FtsEntry<T extends SchemaRecord> = {
89
+ anchor: keyof T & string;
90
+ fields: Record<string, FtsFieldRef>;
91
+ tokenizer?: FtsTokenizer;
92
+ };
93
+
94
+ export type FtsConfig<T extends SchemaRecord> = Record<string, FtsEntry<T>>;
95
+
96
+ export type FtsRowType<E extends FtsEntry<any>> = { rowid: number } & {
97
+ [K in keyof E["fields"]]: string;
98
+ };
99
+
100
+ export type VectorMetric = "cosine" | "l2" | "l1";
101
+
102
+ export type VectorIndexEntry<T extends SchemaRecord> = {
103
+ [K in keyof T & string]: {
104
+ anchor: K;
105
+ column: VectorColumnName<T, K>;
106
+ metric?: VectorMetric;
107
+ };
108
+ }[keyof T & string];
109
+
110
+ export type VectorIndexConfig<T extends SchemaRecord> = Record<string, VectorIndexEntry<T>>;
111
+
112
+ export type DatabaseExtension = { kind: "sqlite-vec"; path?: string };
113
+
114
+ export type DatabaseSchema<
115
+ T extends SchemaRecord,
116
+ F extends FtsConfig<T> = FtsConfig<T>,
117
+ V extends VectorIndexConfig<T> = VectorIndexConfig<T>,
118
+ > = {
57
119
  tables: T;
58
120
  indexes?: IndexesConfig<T>;
121
+ fts?: F;
122
+ vectorIndexes?: V;
123
+ };
124
+
125
+ type FtsTables<F extends FtsConfig<any>> = string extends keyof F
126
+ ? unknown
127
+ : { [K in keyof F]: FtsRowType<F[K]> };
128
+
129
+ type VectorIndexRow = {
130
+ rowid: ColumnType<number, never, never>;
131
+ distance: ColumnType<number, never, never>;
59
132
  };
60
133
 
134
+ type VectorIndexTables<V extends VectorIndexConfig<any>> = string extends keyof V
135
+ ? unknown
136
+ : { [K in keyof V]: VectorIndexRow };
137
+
138
+ export type TablesFromSchemas<
139
+ T extends SchemaRecord,
140
+ F extends FtsConfig<T> = FtsConfig<T>,
141
+ V extends VectorIndexConfig<T> = VectorIndexConfig<T>,
142
+ > = {
143
+ [K in keyof T]: InferTableType<T[K]>;
144
+ } & FtsTables<F> &
145
+ VectorIndexTables<V> & { sqlite_master: SqliteMasterRow };
146
+
147
+ export type FtsApi<F extends FtsConfig<any>> = string extends keyof F
148
+ ? never
149
+ : { rebuild(name: keyof F & string): void };
150
+
151
+ export type VectorIndexApi<V extends VectorIndexConfig<any>> = string extends keyof V
152
+ ? never
153
+ : { rebuild(name: keyof V & string): void };
154
+
61
155
  export type JsonValidation = {
62
156
  onRead?: boolean;
63
- onWrite?: boolean;
64
157
  };
65
158
 
66
- export type DatabaseOptions<T extends SchemaRecord> = {
159
+ export type DatabaseOptions<
160
+ T extends SchemaRecord,
161
+ F extends FtsConfig<T> = FtsConfig<T>,
162
+ V extends VectorIndexConfig<T> = VectorIndexConfig<T>,
163
+ > = {
67
164
  path: string;
68
- schema: DatabaseSchema<T>;
165
+ schema: DatabaseSchema<T, F, V>;
69
166
  pragmas?: DatabasePragmas;
70
167
  validation?: JsonValidation;
168
+ extensions?: DatabaseExtension[];
71
169
  };
@@ -1,11 +1,15 @@
1
- export class JsonValidationError extends Error {
1
+ export class ValidationError extends Error {
2
2
  constructor(
3
3
  readonly table: string,
4
- readonly column: string,
5
4
  readonly summary: string,
5
+ readonly column: string | null = null,
6
6
  ) {
7
- super(`JSON validation failed for ${table}.${column}: ${summary}`);
7
+ super(
8
+ column
9
+ ? `Validation failed for ${table}.${column}: ${summary}`
10
+ : `Validation failed for ${table}: ${summary}`,
11
+ );
8
12
 
9
- this.name = "JsonValidationError";
13
+ this.name = "ValidationError";
10
14
  }
11
15
  }
@@ -0,0 +1,67 @@
1
+ import type { Database as BunDatabase } from "bun:sqlite";
2
+ import { createRequire } from "node:module";
3
+
4
+ import type { DatabaseExtension } from "../types.js";
5
+
6
+ export class ExtensionLoadError extends Error {
7
+ readonly path: string;
8
+ override readonly cause: unknown;
9
+
10
+ constructor(path: string, cause: unknown, hint: string) {
11
+ const message =
12
+ cause instanceof Error ? cause.message : typeof cause === "string" ? cause : String(cause);
13
+
14
+ super(`Failed to load sqlite-vec extension at "${path}": ${message}. ${hint}`);
15
+ this.name = "ExtensionLoadError";
16
+ this.path = path;
17
+ this.cause = cause;
18
+ }
19
+ }
20
+
21
+ const installHint = "Install it with `bun add sqlite-vec` (or npm install sqlite-vec).";
22
+
23
+ function resolveSqliteVecPath(explicit: string | undefined) {
24
+ if (explicit) {
25
+ return explicit;
26
+ }
27
+
28
+ const require = createRequire(import.meta.url);
29
+
30
+ let mod: { getLoadablePath?: () => string };
31
+
32
+ try {
33
+ mod = require("sqlite-vec");
34
+ } catch (cause: any) {
35
+ throw new ExtensionLoadError("sqlite-vec", cause, installHint);
36
+ }
37
+
38
+ if (typeof mod.getLoadablePath !== "function") {
39
+ throw new ExtensionLoadError(
40
+ "sqlite-vec",
41
+ new Error("sqlite-vec does not export getLoadablePath"),
42
+ installHint,
43
+ );
44
+ }
45
+
46
+ try {
47
+ return mod.getLoadablePath();
48
+ } catch (cause: any) {
49
+ throw new ExtensionLoadError("sqlite-vec", cause, installHint);
50
+ }
51
+ }
52
+
53
+ export function loadExtensions(sqlite: BunDatabase, extensions: DatabaseExtension[]) {
54
+ for (const ext of extensions) {
55
+ if (ext.kind !== "sqlite-vec") {
56
+ continue;
57
+ }
58
+
59
+ const path = resolveSqliteVecPath(ext.path);
60
+
61
+ try {
62
+ sqlite.loadExtension(path);
63
+ } catch (cause: any) {
64
+ throw new ExtensionLoadError(path, cause, installHint);
65
+ }
66
+ }
67
+ }