@lobomfz/db 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,362 @@
1
+ import type { Type } from "arktype";
2
+
3
+ import type { DbFieldMeta } from "./env.js";
4
+ import type { FtsTableInfo } from "./fts/resolve.js";
5
+ import type { GeneratedPreset } from "./generated.js";
6
+ import type { DesiredColumn, DesiredTable } from "./migration/types.js";
7
+ import type { IndexDefinition, StructureProp } from "./types.js";
8
+ import type { VectorAnchorInfo } from "./vector-index/resolve.js";
9
+
10
+ type Prop = {
11
+ key: string;
12
+ kind: "required" | "optional";
13
+ domain?: string;
14
+ nullable?: boolean;
15
+ isBoolean?: boolean;
16
+ isInteger?: boolean;
17
+ isDate?: boolean;
18
+ isBlob?: boolean;
19
+ isJson?: boolean;
20
+ isVector?: boolean;
21
+ vectorDim?: number;
22
+ jsonSchema?: Type;
23
+ meta?: DbFieldMeta;
24
+ generated?: GeneratedPreset;
25
+ defaultValue?: unknown;
26
+ };
27
+
28
+ const typeMap: Record<string, string> = {
29
+ string: "TEXT",
30
+ number: "REAL",
31
+ };
32
+
33
+ export class SchemaCompiler {
34
+ compileTable(name: string, schema: Type, indexDefs: IndexDefinition[]) {
35
+ const props = this.parseSchemaProps(schema);
36
+ const columns = props.map((prop) => this.compileColumn(prop));
37
+
38
+ const indexes = indexDefs.map((indexDef) => ({
39
+ name: this.generateIndexName(name, indexDef.columns, indexDef.unique ?? false),
40
+ columns: indexDef.columns,
41
+ sql: this.generateCreateIndexSQL(name, indexDef),
42
+ }));
43
+
44
+ const table: DesiredTable = {
45
+ name,
46
+ sql: this.generateCreateTableSQL(name, props),
47
+ columns,
48
+ indexes,
49
+ };
50
+
51
+ return {
52
+ table,
53
+ tableInfo: this.buildTableInfo(props),
54
+ vectorAnchor: this.buildVectorAnchorInfo(props),
55
+ };
56
+ }
57
+
58
+ createWriteSchema(schema: Type) {
59
+ const autoIncrementColumns = this.parseSchemaProps(schema)
60
+ .filter((prop) => prop.generated === "autoincrement")
61
+ .map((prop) => prop.key);
62
+
63
+ if (autoIncrementColumns.length === 0) {
64
+ return schema;
65
+ }
66
+
67
+ return (schema as any).omit(...autoIncrementColumns) as Type;
68
+ }
69
+
70
+ private parseSchemaProps(schema: Type) {
71
+ const structureProps = (schema as any).structure?.props as StructureProp[] | undefined;
72
+
73
+ if (!structureProps) {
74
+ return [];
75
+ }
76
+
77
+ return structureProps.map((p) => this.normalizeProp(p, schema));
78
+ }
79
+
80
+ private normalizeProp(structureProp: StructureProp, parentSchema: Type) {
81
+ const { key, value: v, inner } = structureProp;
82
+ const kind: Prop["kind"] = structureProp.required ? "required" : "optional";
83
+ const defaultValue = inner.default;
84
+
85
+ const concrete = v.branches.filter((b) => b.unit !== null && b.domain !== "undefined");
86
+ const nullable = concrete.length < v.branches.length;
87
+
88
+ const branchMeta = v.branches.find((b) => b.meta && Object.keys(b.meta).length > 0)?.meta;
89
+ const meta = { ...branchMeta, ...v.meta };
90
+ const generated = meta._generated === "vector" ? undefined : meta._generated;
91
+
92
+ if (meta._generated === "vector") {
93
+ return {
94
+ key,
95
+ kind,
96
+ nullable,
97
+ isVector: true,
98
+ vectorDim: meta._vectorDim,
99
+ meta,
100
+ defaultValue,
101
+ };
102
+ }
103
+
104
+ if (v.proto === Date || concrete.some((b) => b.proto === Date)) {
105
+ return { key, kind, nullable, isDate: true, generated, defaultValue };
106
+ }
107
+
108
+ if (v.proto === Uint8Array || concrete.some((b) => b.proto === Uint8Array)) {
109
+ return {
110
+ key,
111
+ kind,
112
+ nullable,
113
+ isBlob: true,
114
+ meta,
115
+ generated,
116
+ defaultValue,
117
+ };
118
+ }
119
+
120
+ if (concrete.length > 0 && concrete.every((b) => b.domain === "boolean")) {
121
+ return { key, kind, nullable, isBoolean: true, generated, defaultValue };
122
+ }
123
+
124
+ if (concrete.some((b) => !!b.structure)) {
125
+ return {
126
+ key,
127
+ kind,
128
+ nullable,
129
+ isJson: true,
130
+ jsonSchema: (parentSchema as any).get(key) as Type,
131
+ meta,
132
+ generated,
133
+ defaultValue,
134
+ };
135
+ }
136
+
137
+ const branch = concrete[0];
138
+
139
+ return {
140
+ key,
141
+ kind,
142
+ nullable,
143
+ domain: branch?.domain,
144
+ isInteger: !!branch?.inner?.divisor,
145
+ meta,
146
+ generated,
147
+ defaultValue,
148
+ };
149
+ }
150
+
151
+ private compileColumn(prop: Prop): DesiredColumn {
152
+ const isNotNull = this.columnConstraint(prop) === "NOT NULL";
153
+ const defaultClause = this.defaultClause(prop);
154
+ const hasLiteralDefault = prop.generated !== "now" && defaultClause !== null;
155
+
156
+ return {
157
+ name: prop.key,
158
+ addable: !isNotNull || hasLiteralDefault,
159
+ columnDef: this.addColumnDef(prop),
160
+ nullableAddDef: this.nullableAddColumnDef(prop),
161
+ type: this.sqlType(prop),
162
+ notnull: isNotNull,
163
+ defaultValue: defaultClause
164
+ ? defaultClause.replace("DEFAULT ", "").replace(/^\((.+)\)$/, "$1")
165
+ : null,
166
+ unique: !!prop.meta?.unique,
167
+ references: prop.meta?.references ?? null,
168
+ onDelete: prop.meta?.onDelete?.toUpperCase() ?? null,
169
+ };
170
+ }
171
+
172
+ private sqlType(prop: Prop) {
173
+ if (prop.isJson) {
174
+ return "TEXT";
175
+ }
176
+
177
+ if (prop.isBlob || prop.isVector) {
178
+ return "BLOB";
179
+ }
180
+
181
+ if (prop.isDate || prop.isBoolean || prop.isInteger) {
182
+ return "INTEGER";
183
+ }
184
+
185
+ if (prop.domain) {
186
+ return typeMap[prop.domain] ?? "TEXT";
187
+ }
188
+
189
+ return "TEXT";
190
+ }
191
+
192
+ private columnConstraint(prop: Prop) {
193
+ if (prop.generated === "autoincrement") {
194
+ return "PRIMARY KEY AUTOINCREMENT";
195
+ }
196
+
197
+ if (prop.meta?.primaryKey) {
198
+ return "PRIMARY KEY";
199
+ }
200
+
201
+ if (prop.kind === "required" && !prop.nullable) {
202
+ return "NOT NULL";
203
+ }
204
+
205
+ return null;
206
+ }
207
+
208
+ private defaultClause(prop: Prop) {
209
+ if (prop.generated === "now") {
210
+ return "DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER))";
211
+ }
212
+
213
+ if (prop.defaultValue === undefined || prop.generated === "autoincrement") {
214
+ return null;
215
+ }
216
+
217
+ if (prop.defaultValue === null) {
218
+ return "DEFAULT NULL";
219
+ }
220
+
221
+ if (typeof prop.defaultValue === "string") {
222
+ return `DEFAULT '${prop.defaultValue}'`;
223
+ }
224
+
225
+ if (typeof prop.defaultValue === "number" || typeof prop.defaultValue === "boolean") {
226
+ return `DEFAULT ${prop.defaultValue}`;
227
+ }
228
+
229
+ throw new Error(
230
+ `Unsupported default value type: ${typeof prop.defaultValue} ${JSON.stringify(prop)}`,
231
+ );
232
+ }
233
+
234
+ private columnDef(prop: Prop) {
235
+ return [
236
+ `"${prop.key}"`,
237
+ this.sqlType(prop),
238
+ this.columnConstraint(prop),
239
+ prop.meta?.unique ? "UNIQUE" : null,
240
+ this.defaultClause(prop),
241
+ ]
242
+ .filter(Boolean)
243
+ .join(" ");
244
+ }
245
+
246
+ private referencesClause(prop: Prop) {
247
+ const ref = prop.meta?.references;
248
+
249
+ if (!ref) {
250
+ return null;
251
+ }
252
+
253
+ const [table, column] = ref.split(".");
254
+
255
+ let clause = ` REFERENCES "${table}"("${column}")`;
256
+
257
+ if (prop.meta?.onDelete) {
258
+ clause += ` ON DELETE ${prop.meta.onDelete.toUpperCase()}`;
259
+ }
260
+
261
+ return clause;
262
+ }
263
+
264
+ private foreignKey(prop: Prop) {
265
+ const clause = this.referencesClause(prop);
266
+
267
+ if (!clause) {
268
+ return null;
269
+ }
270
+
271
+ return `FOREIGN KEY ("${prop.key}")${clause}`;
272
+ }
273
+
274
+ private addColumnDef(prop: Prop) {
275
+ return `${this.columnDef(prop)}${this.referencesClause(prop) ?? ""}`;
276
+ }
277
+
278
+ private nullableAddColumnDef(prop: Prop) {
279
+ return `"${prop.key}" ${this.sqlType(prop)}${this.referencesClause(prop) ?? ""}`;
280
+ }
281
+
282
+ private generateCreateTableSQL(tableName: string, props: Prop[]) {
283
+ const columns: string[] = [];
284
+ const fks: string[] = [];
285
+
286
+ for (const prop of props) {
287
+ columns.push(this.columnDef(prop));
288
+
289
+ const fk = this.foreignKey(prop);
290
+
291
+ if (fk) {
292
+ fks.push(fk);
293
+ }
294
+ }
295
+
296
+ return `CREATE TABLE IF NOT EXISTS "${tableName}" (${columns.concat(fks).join(", ")})`;
297
+ }
298
+
299
+ private generateIndexName(tableName: string, columns: string[], unique: boolean) {
300
+ const prefix = unique ? "ux" : "ix";
301
+
302
+ return `${prefix}_${tableName}_${columns.join("_")}`;
303
+ }
304
+
305
+ private generateCreateIndexSQL(tableName: string, indexDef: IndexDefinition) {
306
+ const indexName = this.generateIndexName(tableName, indexDef.columns, indexDef.unique ?? false);
307
+ const unique = indexDef.unique ? "UNIQUE " : "";
308
+ const columns = indexDef.columns.map((c) => `"${c}"`).join(", ");
309
+
310
+ return `CREATE ${unique}INDEX "${indexName}" ON "${tableName}" (${columns})`;
311
+ }
312
+
313
+ private buildTableInfo(props: Prop[]): FtsTableInfo {
314
+ const pkProp = props.find(
315
+ (p) => p.generated === "autoincrement" || p.meta?.primaryKey === true,
316
+ );
317
+
318
+ return {
319
+ columns: new Set(props.map((p) => p.key)),
320
+ pkColumn: pkProp?.key ?? null,
321
+ pkIsInteger: pkProp ? this.sqlType(pkProp) === "INTEGER" : false,
322
+ foreignKeys: props.flatMap((p) => {
323
+ const ref = p.meta?.references;
324
+
325
+ if (!ref) {
326
+ return [];
327
+ }
328
+
329
+ const [referencedTable, referencedColumn] = ref.split(".");
330
+
331
+ if (!referencedTable || !referencedColumn) {
332
+ throw new Error(
333
+ `Invalid references format "${ref}" for column "${p.key}": expected "table.column"`,
334
+ );
335
+ }
336
+
337
+ return [{ column: p.key, referencedTable, referencedColumn }];
338
+ }),
339
+ };
340
+ }
341
+
342
+ private buildVectorAnchorInfo(props: Prop[]): VectorAnchorInfo {
343
+ const pkProp = props.find(
344
+ (p) => p.generated === "autoincrement" || p.meta?.primaryKey === true,
345
+ );
346
+
347
+ const vectorColumns = new Map<string, number>();
348
+
349
+ for (const p of props) {
350
+ if (p.isVector && typeof p.vectorDim === "number") {
351
+ vectorColumns.set(p.key, p.vectorDim);
352
+ }
353
+ }
354
+
355
+ return {
356
+ columns: new Set(props.map((p) => p.key)),
357
+ pkColumn: pkProp?.key ?? null,
358
+ pkIsInteger: pkProp ? this.sqlType(pkProp) === "INTEGER" : false,
359
+ vectorColumns,
360
+ };
361
+ }
362
+ }
package/src/types.ts CHANGED
@@ -1,11 +1,15 @@
1
- import type { Generated } from "kysely";
1
+ import type { ColumnType, Generated, Kysely } from "kysely";
2
2
  import type { Type } from "arktype";
3
3
 
4
+ import type { DbFieldMeta } from "./env.js";
5
+
4
6
  export type ArkBranch = {
5
7
  domain?: string;
6
8
  proto?: unknown;
7
9
  unit?: unknown;
8
10
  structure?: unknown;
11
+ inner?: { divisor?: unknown };
12
+ meta?: DbFieldMeta;
9
13
  };
10
14
 
11
15
  export type StructureProp = {
@@ -15,6 +19,7 @@ export type StructureProp = {
15
19
  value: Type & {
16
20
  branches: ArkBranch[];
17
21
  proto?: unknown;
22
+ meta: DbFieldMeta;
18
23
  };
19
24
  };
20
25
 
@@ -25,12 +30,21 @@ type IsOptional<T, K extends keyof T> = undefined extends T[K] ? true : false;
25
30
 
26
31
  type TransformColumn<T> = T extends (infer U)[] ? U[] : T;
27
32
 
33
+ type IsVectorColumn<TOutput, K extends keyof TOutput> =
34
+ NonNullable<TOutput[K]> extends Float32Array ? true : false;
35
+
36
+ type VectorInsert = Float32Array | number[];
37
+
28
38
  type TransformField<TOutput, TInput, K extends keyof TOutput & keyof TInput> =
29
- IsOptional<TInput, K> extends true
39
+ IsVectorColumn<TOutput, K> extends true
30
40
  ? IsOptional<TOutput, K> extends true
31
- ? TransformColumn<NonNullable<TOutput[K]>> | null
32
- : Generated<TransformColumn<NonNullable<TOutput[K]>>>
33
- : TransformColumn<TOutput[K]>;
41
+ ? ColumnType<Float32Array | null, VectorInsert | null, VectorInsert | null>
42
+ : ColumnType<Float32Array, VectorInsert, VectorInsert>
43
+ : IsOptional<TInput, K> extends true
44
+ ? IsOptional<TOutput, K> extends true
45
+ ? TransformColumn<NonNullable<TOutput[K]>> | null
46
+ : Generated<TransformColumn<NonNullable<TOutput[K]>>>
47
+ : TransformColumn<TOutput[K]>;
34
48
 
35
49
  type TransformTable<TOutput, TInput> = {
36
50
  [K in keyof TOutput & keyof TInput]: TransformField<TOutput, TInput, K>;
@@ -48,12 +62,12 @@ export type SqliteMasterRow = {
48
62
  sql: string | null;
49
63
  };
50
64
 
51
- export type TablesFromSchemas<T extends SchemaRecord> = {
52
- [K in keyof T]: InferTableType<T[K]>;
53
- } & { sqlite_master: SqliteMasterRow };
54
-
55
65
  type TableColumns<T extends SchemaRecord, K extends keyof T> = keyof ExtractOutput<T[K]> & string;
56
66
 
67
+ type VectorColumnName<T extends SchemaRecord, K extends keyof T> = {
68
+ [C in TableColumns<T, K>]: NonNullable<ExtractOutput<T[K]>[C]> extends Float32Array ? C : never;
69
+ }[TableColumns<T, K>];
70
+
57
71
  export type IndexDefinition<Columns extends string = string> = {
58
72
  columns: Columns[];
59
73
  unique?: boolean;
@@ -70,18 +84,102 @@ export type DatabasePragmas = {
70
84
  busy_timeout_ms?: number;
71
85
  };
72
86
 
73
- export type DatabaseSchema<T extends SchemaRecord> = {
87
+ export type FtsTokenizer =
88
+ | { type: "unicode61"; removeDiacritics?: 0 | 1 | 2; categories?: string }
89
+ | { type: "trigram" };
90
+
91
+ export type FtsFieldRef = string | { source: string; via?: string };
92
+
93
+ export type FtsEntry<T extends SchemaRecord> = {
94
+ anchor: keyof T & string;
95
+ fields: Record<string, FtsFieldRef>;
96
+ tokenizer?: FtsTokenizer;
97
+ };
98
+
99
+ export type FtsConfig<T extends SchemaRecord> = Record<string, FtsEntry<T>>;
100
+
101
+ export type FtsRowType<E extends FtsEntry<any>> = { rowid: number } & {
102
+ [K in keyof E["fields"]]: string;
103
+ };
104
+
105
+ export type VectorMetric = "cosine" | "l2" | "l1";
106
+
107
+ export type VectorIndexEntry<T extends SchemaRecord> = {
108
+ [K in keyof T & string]: {
109
+ anchor: K;
110
+ column: VectorColumnName<T, K>;
111
+ metric?: VectorMetric;
112
+ };
113
+ }[keyof T & string];
114
+
115
+ export type VectorIndexConfig<T extends SchemaRecord> = Record<string, VectorIndexEntry<T>>;
116
+
117
+ export type DatabaseExtension = { kind: "sqlite-vec"; path?: string };
118
+
119
+ export type DatabaseSchema<
120
+ T extends SchemaRecord,
121
+ F extends FtsConfig<T> = FtsConfig<T>,
122
+ V extends VectorIndexConfig<T> = VectorIndexConfig<T>,
123
+ > = {
74
124
  tables: T;
75
125
  indexes?: IndexesConfig<T>;
126
+ fts?: F;
127
+ vectorIndexes?: V;
128
+ };
129
+
130
+ type FtsTables<F extends FtsConfig<any>> = string extends keyof F
131
+ ? unknown
132
+ : { [K in keyof F]: FtsRowType<F[K]> };
133
+
134
+ type VectorIndexRow = {
135
+ rowid: ColumnType<number, never, never>;
136
+ distance: ColumnType<number, never, never>;
76
137
  };
77
138
 
139
+ type VectorIndexTables<V extends VectorIndexConfig<any>> = string extends keyof V
140
+ ? unknown
141
+ : { [K in keyof V]: VectorIndexRow };
142
+
143
+ export type TablesFromSchemas<
144
+ T extends SchemaRecord,
145
+ F extends FtsConfig<T> = FtsConfig<T>,
146
+ V extends VectorIndexConfig<T> = VectorIndexConfig<T>,
147
+ > = {
148
+ [K in keyof T]: InferTableType<T[K]>;
149
+ } & FtsTables<F> &
150
+ VectorIndexTables<V> & { sqlite_master: SqliteMasterRow };
151
+
152
+ export type FtsApi<F extends FtsConfig<any>> = string extends keyof F
153
+ ? never
154
+ : { rebuild(name: keyof F & string): void };
155
+
156
+ export type VectorIndexApi<V extends VectorIndexConfig<any>> = string extends keyof V
157
+ ? never
158
+ : { rebuild(name: keyof V & string): void };
159
+
78
160
  export type JsonValidation = {
79
161
  onRead?: boolean;
80
162
  };
81
163
 
82
- export type DatabaseOptions<T extends SchemaRecord> = {
164
+ export type DataMigration<
165
+ T extends SchemaRecord,
166
+ F extends FtsConfig<T> = FtsConfig<T>,
167
+ V extends VectorIndexConfig<T> = VectorIndexConfig<T>,
168
+ > = {
169
+ id: string;
170
+ run: (ctx: { db: Kysely<TablesFromSchemas<T, F, V>> }) => Promise<void> | void;
171
+ };
172
+
173
+ export type DatabaseOptions<
174
+ T extends SchemaRecord,
175
+ F extends FtsConfig<T> = FtsConfig<T>,
176
+ V extends VectorIndexConfig<T> = VectorIndexConfig<T>,
177
+ > = {
83
178
  path: string;
84
- schema: DatabaseSchema<T>;
179
+ schema: DatabaseSchema<T, F, V>;
85
180
  pragmas?: DatabasePragmas;
86
181
  validation?: JsonValidation;
182
+ extensions?: DatabaseExtension[];
183
+ failOnDataLoss?: boolean;
184
+ migration?: DataMigration<T, F, V>;
87
185
  };
@@ -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
+ }
@@ -0,0 +1,68 @@
1
+ import { sql, type SelectQueryBuilder } from "kysely";
2
+
3
+ import type { ResolvedVectorIndex } from "./resolve.js";
4
+
5
+ type IndexMeta = Pick<ResolvedVectorIndex, "anchor" | "anchorPkColumn" | "column" | "dim">;
6
+
7
+ const registry = new Map<string, IndexMeta>();
8
+
9
+ export function registerVectorIndex(index: ResolvedVectorIndex) {
10
+ registry.set(index.name, {
11
+ anchor: index.anchor,
12
+ anchorPkColumn: index.anchorPkColumn,
13
+ column: index.column,
14
+ dim: index.dim,
15
+ });
16
+ }
17
+
18
+ export function unregisterVectorIndex(name: string) {
19
+ registry.delete(name);
20
+ }
21
+
22
+ export type NearestOptions<IndexName extends string = string> = {
23
+ indexName: IndexName;
24
+ vector: Float32Array | number[];
25
+ k: number;
26
+ };
27
+
28
+ type WithDistance<O> = O & { distance: number };
29
+
30
+ function vectorToBytes(v: Float32Array | number[]) {
31
+ const f = v instanceof Float32Array ? v : Float32Array.from(v);
32
+
33
+ return new Uint8Array(f.buffer, f.byteOffset, f.byteLength);
34
+ }
35
+
36
+ export function nearest<IndexName extends string>(options: NearestOptions<IndexName>) {
37
+ return <DB extends { [K in IndexName]: unknown }, TB extends keyof DB & string, O>(
38
+ qb: SelectQueryBuilder<DB, TB, O>,
39
+ ): SelectQueryBuilder<DB, TB | (IndexName & keyof DB & string), WithDistance<O>> => {
40
+ const meta = registry.get(options.indexName);
41
+
42
+ if (!meta) {
43
+ throw new Error(
44
+ `Vector index "${options.indexName}" is not registered. Declare it in the schema's vectorIndexes and construct the Database before calling nearest.`,
45
+ );
46
+ }
47
+
48
+ const bytes = vectorToBytes(options.vector);
49
+ const indexName = options.indexName;
50
+ const column = meta.column;
51
+ const anchor = meta.anchor;
52
+ const anchorPk = meta.anchorPkColumn;
53
+
54
+ const anyQb = qb as unknown as {
55
+ innerJoin: (a: any, b: any, c: any) => any;
56
+ where: (expr: any) => any;
57
+ select: (expr: any) => any;
58
+ orderBy: (expr: any) => any;
59
+ };
60
+
61
+ return anyQb
62
+ .innerJoin(indexName, `${indexName}.rowid`, `${anchor}.${anchorPk}`)
63
+ .where(sql<boolean>`${sql.ref(`${indexName}.${column}`)} MATCH ${bytes}`)
64
+ .where(sql<boolean>`${sql.ref(`${indexName}.k`)} = ${options.k}`)
65
+ .select(sql<number>`${sql.ref(`${indexName}.distance`)}`.as("distance"))
66
+ .orderBy(`${indexName}.distance`);
67
+ };
68
+ }