metal-orm 1.1.19 → 1.1.21
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/dist/index.cjs +193 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -10
- package/dist/index.d.ts +88 -10
- package/dist/index.js +184 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +9 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +5 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +23 -2
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +9 -0
- package/src/core/dialect/mssql/functions.ts +10 -0
- package/src/core/dialect/mysql/functions.ts +9 -0
- package/src/core/dialect/postgres/functions.ts +21 -0
- package/src/core/dialect/sqlite/functions.ts +10 -0
- package/src/core/functions/vector.ts +141 -0
- package/src/index.ts +1 -0
- package/src/orm/relation-change-processor.ts +15 -11
- package/src/schema/column-types.ts +34 -1
- package/src/schema/table.ts +6 -0
package/package.json
CHANGED
|
@@ -73,6 +73,15 @@ export class MSSqlSchemaDialect extends BaseSchemaDialect {
|
|
|
73
73
|
return 'VARBINARY(MAX)';
|
|
74
74
|
case 'enum':
|
|
75
75
|
return 'NVARCHAR(255)';
|
|
76
|
+
case 'vector': {
|
|
77
|
+
const dim = column.vectorOptions?.dimensions ?? column.args?.[0] ?? 3;
|
|
78
|
+
const elemType = column.vectorOptions?.elementType;
|
|
79
|
+
return elemType ? `VECTOR(${dim}, ${elemType})` : `VECTOR(${dim})`;
|
|
80
|
+
}
|
|
81
|
+
case 'halfvec': {
|
|
82
|
+
const dim = column.vectorOptions?.dimensions ?? column.args?.[0] ?? 3;
|
|
83
|
+
return `VECTOR(${dim}, float16)`;
|
|
84
|
+
}
|
|
76
85
|
default:
|
|
77
86
|
return renderTypeWithArgs(String(type).toUpperCase(), column.args);
|
|
78
87
|
}
|
|
@@ -71,6 +71,11 @@ export class MySqlSchemaDialect extends BaseSchemaDialect {
|
|
|
71
71
|
return column.args && Array.isArray(column.args) && column.args.length
|
|
72
72
|
? `ENUM(${column.args.map((v: string) => `'${escapeSqlString(v)}'`).join(',')})`
|
|
73
73
|
: 'ENUM';
|
|
74
|
+
case 'vector':
|
|
75
|
+
case 'halfvec': {
|
|
76
|
+
const dim = column.vectorOptions?.dimensions ?? column.args?.[0] ?? 3;
|
|
77
|
+
return `VECTOR(${dim})`;
|
|
78
|
+
}
|
|
74
79
|
default:
|
|
75
80
|
return renderTypeWithArgs(String(type).toUpperCase(), column.args);
|
|
76
81
|
}
|
|
@@ -66,6 +66,14 @@ export class PostgresSchemaDialect extends BaseSchemaDialect {
|
|
|
66
66
|
case 'blob':
|
|
67
67
|
case 'bytea':
|
|
68
68
|
return 'bytea';
|
|
69
|
+
case 'vector':
|
|
70
|
+
return column.vectorOptions?.elementType === 'float16'
|
|
71
|
+
? `halfvec(${column.vectorOptions.dimensions})`
|
|
72
|
+
: column.args?.length
|
|
73
|
+
? `vector(${column.args[0]})`
|
|
74
|
+
: 'vector';
|
|
75
|
+
case 'halfvec':
|
|
76
|
+
return column.args?.length ? `halfvec(${column.args[0]})` : 'halfvec';
|
|
69
77
|
default:
|
|
70
78
|
return renderTypeWithArgs(String(type).toLowerCase(), column.args);
|
|
71
79
|
}
|
|
@@ -79,10 +87,23 @@ export class PostgresSchemaDialect extends BaseSchemaDialect {
|
|
|
79
87
|
|
|
80
88
|
renderIndex(table: TableDef, index: IndexDef): string {
|
|
81
89
|
const name = index.name || deriveIndexName(table, index);
|
|
82
|
-
|
|
90
|
+
let cols = renderIndexColumns(this, index.columns);
|
|
91
|
+
if (index.ops) {
|
|
92
|
+
cols = `${cols} ${index.ops}`;
|
|
93
|
+
}
|
|
83
94
|
const unique = index.unique ? 'UNIQUE ' : '';
|
|
95
|
+
const using = index.using ? ` USING ${index.using}` : '';
|
|
96
|
+
let withClause = '';
|
|
97
|
+
if (index.with) {
|
|
98
|
+
if (typeof index.with === 'string') {
|
|
99
|
+
withClause = ` WITH (${index.with})`;
|
|
100
|
+
} else {
|
|
101
|
+
const params = Object.entries(index.with).map(([k, v]) => `${k} = ${v}`).join(', ');
|
|
102
|
+
withClause = ` WITH (${params})`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
84
105
|
const where = index.where ? ` WHERE ${index.where}` : '';
|
|
85
|
-
return `CREATE ${unique}INDEX IF NOT EXISTS ${this.quoteIdentifier(name)} ON ${this.formatTableName(table)} (${cols})${where};`;
|
|
106
|
+
return `CREATE ${unique}INDEX IF NOT EXISTS ${this.quoteIdentifier(name)} ON ${this.formatTableName(table)}${using} (${cols})${withClause}${where};`;
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
supportsPartialIndexes(): boolean {
|
|
@@ -57,6 +57,15 @@ export class SQLiteSchemaDialect extends BaseSchemaDialect {
|
|
|
57
57
|
case 'blob':
|
|
58
58
|
case 'bytea':
|
|
59
59
|
return 'BLOB';
|
|
60
|
+
case 'halfvec': {
|
|
61
|
+
const dim = column.vectorOptions?.dimensions ?? column.args?.[0] ?? 3;
|
|
62
|
+
return `float16[${dim}]`;
|
|
63
|
+
}
|
|
64
|
+
case 'vector': {
|
|
65
|
+
const dim = column.vectorOptions?.dimensions ?? column.args?.[0] ?? 3;
|
|
66
|
+
const elemType = column.vectorOptions?.elementType === 'float16' ? 'float16' : 'float32';
|
|
67
|
+
return `${elemType}[${dim}]`;
|
|
68
|
+
}
|
|
60
69
|
default:
|
|
61
70
|
return 'TEXT';
|
|
62
71
|
}
|
|
@@ -143,5 +143,15 @@ export class MssqlFunctionStrategy extends StandardFunctionStrategy {
|
|
|
143
143
|
this.add('ARRAY_APPEND', () => {
|
|
144
144
|
throw new Error('ARRAY_APPEND is not supported on SQL Server');
|
|
145
145
|
});
|
|
146
|
+
|
|
147
|
+
this.add('VECTOR_DISTANCE', ({ node, compiledArgs }) => {
|
|
148
|
+
if (compiledArgs.length !== 3) throw new Error('VECTOR_DISTANCE expects 3 arguments (metric, v1, v2)');
|
|
149
|
+
let metric = node.args[0]?.type === 'Literal' ? String(node.args[0].value).toLowerCase() : compiledArgs[0].replace(/['"]/g, '').toLowerCase();
|
|
150
|
+
if (metric === 'l2') metric = 'euclidean';
|
|
151
|
+
if (metric === 'l1') metric = 'manhattan';
|
|
152
|
+
if (metric === 'inner_product') metric = 'dot';
|
|
153
|
+
const [, v1, v2] = compiledArgs;
|
|
154
|
+
return `VECTOR_DISTANCE('${metric}', ${v1}, ${v2})`;
|
|
155
|
+
});
|
|
146
156
|
}
|
|
147
157
|
}
|
|
@@ -110,5 +110,14 @@ export class MysqlFunctionStrategy extends StandardFunctionStrategy {
|
|
|
110
110
|
if (compiledArgs.length !== 2) throw new Error('ARRAY_APPEND expects 2 arguments (array, value)');
|
|
111
111
|
return `JSON_ARRAY_APPEND(${compiledArgs[0]}, '$', ${compiledArgs[1]})`;
|
|
112
112
|
});
|
|
113
|
+
|
|
114
|
+
this.add('VECTOR_DISTANCE', ({ node, compiledArgs }) => {
|
|
115
|
+
if (compiledArgs.length !== 3) throw new Error('VECTOR_DISTANCE expects 3 arguments (metric, v1, v2)');
|
|
116
|
+
let metric = node.args[0]?.type === 'Literal' ? String(node.args[0].value).toUpperCase() : compiledArgs[0].replace(/['"]/g, '').toUpperCase();
|
|
117
|
+
if (metric === 'L2') metric = 'EUCLIDEAN';
|
|
118
|
+
if (metric === 'INNER_PRODUCT') metric = 'DOT';
|
|
119
|
+
const [, v1, v2] = compiledArgs;
|
|
120
|
+
return `DISTANCE(${v1}, ${v2}, '${metric}')`;
|
|
121
|
+
});
|
|
113
122
|
}
|
|
114
123
|
}
|
|
@@ -140,6 +140,27 @@ export class PostgresFunctionStrategy extends StandardFunctionStrategy {
|
|
|
140
140
|
const pathArray = this.formatJsonbPathArray(pathNode);
|
|
141
141
|
return `jsonb_set(${compiledArgs[0]}, ${pathArray}, ${compiledArgs[2]}::jsonb, true)`;
|
|
142
142
|
});
|
|
143
|
+
|
|
144
|
+
this.add('VECTOR_DISTANCE', ({ node, compiledArgs }) => {
|
|
145
|
+
if (compiledArgs.length !== 3) throw new Error('VECTOR_DISTANCE expects 3 arguments (metric, v1, v2)');
|
|
146
|
+
const metric = node.args[0]?.type === 'Literal' ? String(node.args[0].value).toLowerCase() : compiledArgs[0].replace(/['"]/g, '').toLowerCase();
|
|
147
|
+
const [, v1, v2] = compiledArgs;
|
|
148
|
+
switch (metric) {
|
|
149
|
+
case 'cosine':
|
|
150
|
+
return `(${v1} <=> ${v2})`;
|
|
151
|
+
case 'euclidean':
|
|
152
|
+
case 'l2':
|
|
153
|
+
return `(${v1} <-> ${v2})`;
|
|
154
|
+
case 'dot':
|
|
155
|
+
case 'inner_product':
|
|
156
|
+
return `(${v1} <#> ${v2})`;
|
|
157
|
+
case 'manhattan':
|
|
158
|
+
case 'l1':
|
|
159
|
+
return `(${v1} <~> ${v2})`;
|
|
160
|
+
default:
|
|
161
|
+
return `(${v1} <=> ${v2})`;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
143
164
|
}
|
|
144
165
|
|
|
145
166
|
private formatJsonbPathArray(pathNode: LiteralNode): string {
|
|
@@ -151,5 +151,15 @@ export class SqliteFunctionStrategy extends StandardFunctionStrategy {
|
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
this.add('CHR', ({ compiledArgs }) => `CHAR(${compiledArgs[0]})`);
|
|
154
|
+
|
|
155
|
+
this.add('VECTOR_DISTANCE', ({ node, compiledArgs }) => {
|
|
156
|
+
if (compiledArgs.length !== 3) throw new Error('VECTOR_DISTANCE expects 3 arguments (metric, v1, v2)');
|
|
157
|
+
const metric = node.args[0]?.type === 'Literal' ? String(node.args[0].value).toLowerCase() : compiledArgs[0].replace(/['"]/g, '').toLowerCase();
|
|
158
|
+
const [, v1, v2] = compiledArgs;
|
|
159
|
+
if (metric === 'euclidean' || metric === 'l2') {
|
|
160
|
+
return `vec_distance_L2(${v1}, ${v2})`;
|
|
161
|
+
}
|
|
162
|
+
return `vec_distance_cosine(${v1}, ${v2})`;
|
|
163
|
+
});
|
|
154
164
|
}
|
|
155
165
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Pure AST Builders - No Dialect Logic Here!
|
|
2
|
+
|
|
3
|
+
import { ColumnDef } from '../../schema/column-types.js';
|
|
4
|
+
import { columnOperand, valueToOperand } from '../ast/expression-builders.js';
|
|
5
|
+
import { FunctionNode, OperandNode, isOperandNode, TypedExpression, asType } from '../ast/expression.js';
|
|
6
|
+
import { BinaryExpressionNode, LogicalExpressionNode } from '../ast/expression-nodes.js';
|
|
7
|
+
import { SqlOperator } from '../sql/sql.js';
|
|
8
|
+
|
|
9
|
+
export type VectorMetric =
|
|
10
|
+
| 'cosine'
|
|
11
|
+
| 'euclidean'
|
|
12
|
+
| 'l2'
|
|
13
|
+
| 'dot'
|
|
14
|
+
| 'inner_product'
|
|
15
|
+
| 'manhattan'
|
|
16
|
+
| 'l1';
|
|
17
|
+
|
|
18
|
+
export type VectorInput =
|
|
19
|
+
| OperandNode
|
|
20
|
+
| ColumnDef
|
|
21
|
+
| number[]
|
|
22
|
+
| Float32Array
|
|
23
|
+
| string;
|
|
24
|
+
|
|
25
|
+
const isColumnDef = (val: unknown): val is ColumnDef =>
|
|
26
|
+
!!val && typeof val === 'object' && 'type' in val && 'name' in val;
|
|
27
|
+
|
|
28
|
+
const toOperand = (input: VectorInput): OperandNode => {
|
|
29
|
+
if (isOperandNode(input)) return input;
|
|
30
|
+
if (isColumnDef(input)) return columnOperand(input);
|
|
31
|
+
|
|
32
|
+
if (Array.isArray(input) || input instanceof Float32Array) {
|
|
33
|
+
const formatted = `[${Array.from(input).join(', ')}]`;
|
|
34
|
+
return valueToOperand(formatted);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return valueToOperand(input);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const fn = (key: string, args: OperandNode[]): FunctionNode => ({
|
|
41
|
+
type: 'Function',
|
|
42
|
+
name: key,
|
|
43
|
+
fn: key,
|
|
44
|
+
args
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Calculates vector distance using a specific distance metric ('cosine', 'euclidean', 'l2', 'dot', 'inner_product', 'manhattan', 'l1').
|
|
49
|
+
* Compiles to dialect-native vector functions / operators:
|
|
50
|
+
* - SQL Server: VECTOR_DISTANCE('cosine', v1, v2)
|
|
51
|
+
* - MySQL: DISTANCE(v1, v2, 'COSINE')
|
|
52
|
+
* - PostgreSQL: (v1 <=> v2), (v1 <-> v2), (v1 <#> v2), (v1 <~> v2)
|
|
53
|
+
* - SQLite: vec_distance_cosine(v1, v2), vec_distance_L2(v1, v2)
|
|
54
|
+
*/
|
|
55
|
+
export const vectorDistance = (
|
|
56
|
+
metric: VectorMetric,
|
|
57
|
+
v1: VectorInput,
|
|
58
|
+
v2: VectorInput
|
|
59
|
+
): TypedExpression<number> => {
|
|
60
|
+
const metricOp = valueToOperand(metric.toLowerCase());
|
|
61
|
+
return asType<number>(fn('VECTOR_DISTANCE', [metricOp, toOperand(v1), toOperand(v2)]));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Calculates Cosine distance between two vectors.
|
|
66
|
+
*/
|
|
67
|
+
export const cosineDistance = (v1: VectorInput, v2: VectorInput): TypedExpression<number> =>
|
|
68
|
+
vectorDistance('cosine', v1, v2);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Calculates Euclidean (L2) distance between two vectors.
|
|
72
|
+
*/
|
|
73
|
+
export const l2Distance = (v1: VectorInput, v2: VectorInput): TypedExpression<number> =>
|
|
74
|
+
vectorDistance('euclidean', v1, v2);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Alias for l2Distance. Calculates Euclidean distance between two vectors.
|
|
78
|
+
*/
|
|
79
|
+
export const euclideanDistance = (v1: VectorInput, v2: VectorInput): TypedExpression<number> =>
|
|
80
|
+
vectorDistance('euclidean', v1, v2);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Calculates Inner / Dot product distance between two vectors.
|
|
84
|
+
*/
|
|
85
|
+
export const innerProduct = (v1: VectorInput, v2: VectorInput): TypedExpression<number> =>
|
|
86
|
+
vectorDistance('dot', v1, v2);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Alias for innerProduct. Calculates Dot product distance between two vectors.
|
|
90
|
+
*/
|
|
91
|
+
export const dotProduct = (v1: VectorInput, v2: VectorInput): TypedExpression<number> =>
|
|
92
|
+
vectorDistance('dot', v1, v2);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Calculates Manhattan (L1) distance between two vectors.
|
|
96
|
+
*/
|
|
97
|
+
export const l1Distance = (v1: VectorInput, v2: VectorInput): TypedExpression<number> =>
|
|
98
|
+
vectorDistance('manhattan', v1, v2);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Alias for l1Distance. Calculates Manhattan distance between two vectors.
|
|
102
|
+
*/
|
|
103
|
+
export const manhattanDistance = (v1: VectorInput, v2: VectorInput): TypedExpression<number> =>
|
|
104
|
+
vectorDistance('manhattan', v1, v2);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Builds a SQLite `sqlite-vec` KNN virtual table query predicate:
|
|
108
|
+
* `col MATCH '[...]' AND k = n`
|
|
109
|
+
*
|
|
110
|
+
* @param column - The vector virtual table column to match against.
|
|
111
|
+
* @param vector - The query vector(s) to match (raw array/string is inlined as a literal).
|
|
112
|
+
* @param k - Number of nearest neighbors to return.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* where(vectorMatch(items.embedding, [0.1, 2, 30], 5))
|
|
117
|
+
* // => "embedding" MATCH '[0.1, 2, 30]' AND k = 5
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export const vectorMatch = (
|
|
121
|
+
column: VectorInput,
|
|
122
|
+
vector: VectorInput,
|
|
123
|
+
k: number
|
|
124
|
+
): LogicalExpressionNode => {
|
|
125
|
+
const match: BinaryExpressionNode = {
|
|
126
|
+
type: 'BinaryExpression',
|
|
127
|
+
left: toOperand(column),
|
|
128
|
+
operator: 'MATCH' as SqlOperator,
|
|
129
|
+
right: toOperand(vector)
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const kNode: BinaryExpressionNode = {
|
|
133
|
+
type: 'BinaryExpression',
|
|
134
|
+
left: valueToOperand('k'),
|
|
135
|
+
operator: '=',
|
|
136
|
+
right: valueToOperand(k)
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return { type: 'LogicalExpression', operator: 'AND', operands: [match, kNode] };
|
|
140
|
+
};
|
|
141
|
+
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ export * from './core/functions/datetime.js';
|
|
|
32
32
|
export * from './core/functions/control-flow.js';
|
|
33
33
|
export * from './core/functions/json.js';
|
|
34
34
|
export * from './core/functions/array.js';
|
|
35
|
+
export * from './core/functions/vector.js';
|
|
35
36
|
export * from './orm/als.js';
|
|
36
37
|
export * from './orm/hydration.js';
|
|
37
38
|
export * from './codegen/typescript.js';
|
|
@@ -7,7 +7,6 @@ import { UpdateQueryBuilder } from '../query-builder/update.js';
|
|
|
7
7
|
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
8
8
|
import type { BelongsToManyRelation, HasManyRelation, HasOneRelation, MorphOneRelation, MorphManyRelation, MorphToRelation } from '../schema/relation.js';
|
|
9
9
|
import { RelationKinds } from '../schema/relation.js';
|
|
10
|
-
import type { TableDef } from '../schema/table.js';
|
|
11
10
|
import type { DbExecutor } from '../core/execution/db-executor.js';
|
|
12
11
|
import type { RelationChangeEntry } from './runtime-types.js';
|
|
13
12
|
import { UnitOfWork } from './unit-of-work.js';
|
|
@@ -153,7 +152,10 @@ export class RelationChangeProcessor {
|
|
|
153
152
|
const rootId = entry.root[rootKey];
|
|
154
153
|
if (rootId === undefined || rootId === null) return;
|
|
155
154
|
|
|
156
|
-
const targetId = this.
|
|
155
|
+
const targetId = this.resolveBelongsToManyTargetValue(
|
|
156
|
+
entry.change.entity as Record<string, unknown>,
|
|
157
|
+
relation
|
|
158
|
+
);
|
|
157
159
|
if (targetId === null) return;
|
|
158
160
|
|
|
159
161
|
const pivotPayload = this.buildPivotPayload(relation, entry.change.pivot);
|
|
@@ -235,7 +237,7 @@ export class RelationChangeProcessor {
|
|
|
235
237
|
* Inserts a pivot row for belongs-to-many relations.
|
|
236
238
|
* @param relation - The belongs-to-many relation
|
|
237
239
|
* @param rootId - The root entity's primary key value
|
|
238
|
-
* @param targetId - The target entity's
|
|
240
|
+
* @param targetId - The target entity's relation key value
|
|
239
241
|
*/
|
|
240
242
|
private async insertPivotRow(
|
|
241
243
|
relation: BelongsToManyRelation,
|
|
@@ -258,7 +260,7 @@ export class RelationChangeProcessor {
|
|
|
258
260
|
* Updates a pivot row for belongs-to-many relations.
|
|
259
261
|
* @param relation - The belongs-to-many relation
|
|
260
262
|
* @param rootId - The root entity's primary key value
|
|
261
|
-
* @param targetId - The target entity's
|
|
263
|
+
* @param targetId - The target entity's relation key value
|
|
262
264
|
* @param pivotPayload - The pivot columns to update
|
|
263
265
|
*/
|
|
264
266
|
private async updatePivotRow(
|
|
@@ -282,7 +284,7 @@ export class RelationChangeProcessor {
|
|
|
282
284
|
* Deletes a pivot row for belongs-to-many relations.
|
|
283
285
|
* @param relation - The belongs-to-many relation
|
|
284
286
|
* @param rootId - The root entity's primary key value
|
|
285
|
-
* @param targetId - The target entity's
|
|
287
|
+
* @param targetId - The target entity's relation key value
|
|
286
288
|
*/
|
|
287
289
|
private async deletePivotRow(relation: BelongsToManyRelation, rootId: string | number, targetId: string | number): Promise<void> {
|
|
288
290
|
const rootCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
|
|
@@ -297,14 +299,16 @@ export class RelationChangeProcessor {
|
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
/**
|
|
300
|
-
* Resolves the
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* @returns The primary key value or null
|
|
302
|
+
* Resolves the target-side key value used by a belongs-to-many relation.
|
|
303
|
+
* The declared targetKey is part of the relation contract and takes
|
|
304
|
+
* precedence over the target table primary key.
|
|
304
305
|
*/
|
|
305
|
-
private
|
|
306
|
+
private resolveBelongsToManyTargetValue(
|
|
307
|
+
entity: Record<string, unknown>,
|
|
308
|
+
relation: BelongsToManyRelation
|
|
309
|
+
): string | number | null {
|
|
306
310
|
if (!entity) return null;
|
|
307
|
-
const key = findPrimaryKey(
|
|
311
|
+
const key = relation.targetKey || findPrimaryKey(relation.target);
|
|
308
312
|
const value = entity[key];
|
|
309
313
|
if (value === undefined || value === null) return null;
|
|
310
314
|
return (value as string | number | null | undefined) ?? null;
|
|
@@ -21,7 +21,9 @@ export const STANDARD_COLUMN_TYPES = [
|
|
|
21
21
|
'DATETIME',
|
|
22
22
|
'TIMESTAMP',
|
|
23
23
|
'TIMESTAMPTZ',
|
|
24
|
-
'BOOLEAN'
|
|
24
|
+
'BOOLEAN',
|
|
25
|
+
'VECTOR',
|
|
26
|
+
'HALFVEC'
|
|
25
27
|
] as const;
|
|
26
28
|
|
|
27
29
|
/** Known logical types the ORM understands. */
|
|
@@ -113,6 +115,11 @@ export interface ColumnDef<T extends ColumnType = ColumnType, TRuntime = unknown
|
|
|
113
115
|
comment?: string;
|
|
114
116
|
/** Additional arguments for the column type (e.g., VARCHAR length) */
|
|
115
117
|
args?: (string | number)[];
|
|
118
|
+
/** Options specific to vector columns (dimensions, float16 vs float32, etc.) */
|
|
119
|
+
vectorOptions?: {
|
|
120
|
+
dimensions: number;
|
|
121
|
+
elementType?: 'float16' | 'float32' | 'int8' | 'bit';
|
|
122
|
+
};
|
|
116
123
|
/** Table name this column belongs to (filled at runtime by defineTable) */
|
|
117
124
|
table?: string;
|
|
118
125
|
}
|
|
@@ -236,6 +243,32 @@ export const col = {
|
|
|
236
243
|
*/
|
|
237
244
|
enum: (values: string[]): ColumnDef<'ENUM'> => ({ name: '', type: 'ENUM', args: values }),
|
|
238
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Creates a vector column definition
|
|
248
|
+
* @param dimensions - Vector dimensions
|
|
249
|
+
* @param options - Vector options (e.g. elementType: 'float16' | 'float32')
|
|
250
|
+
*/
|
|
251
|
+
vector: (
|
|
252
|
+
dimensions: number,
|
|
253
|
+
options?: { elementType?: 'float16' | 'float32' | 'int8' | 'bit' }
|
|
254
|
+
): ColumnDef<'VECTOR', number[]> => ({
|
|
255
|
+
name: '',
|
|
256
|
+
type: 'VECTOR',
|
|
257
|
+
args: [dimensions],
|
|
258
|
+
vectorOptions: { dimensions, ...options }
|
|
259
|
+
}),
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Creates a half-precision (float16) vector column definition (pgvector halfvec / SQL Server float16 vector / sqlite-vec float16)
|
|
263
|
+
* @param dimensions - Vector dimensions
|
|
264
|
+
*/
|
|
265
|
+
halfvec: (dimensions: number): ColumnDef<'HALFVEC', number[]> => ({
|
|
266
|
+
name: '',
|
|
267
|
+
type: 'HALFVEC',
|
|
268
|
+
args: [dimensions],
|
|
269
|
+
vectorOptions: { dimensions, elementType: 'float16' }
|
|
270
|
+
}),
|
|
271
|
+
|
|
239
272
|
/**
|
|
240
273
|
* Creates a column definition with a custom SQL type.
|
|
241
274
|
* Useful for dialect-specific types without polluting the standard set.
|
package/src/schema/table.ts
CHANGED
|
@@ -12,6 +12,12 @@ export interface IndexDef {
|
|
|
12
12
|
columns: (string | IndexColumn)[];
|
|
13
13
|
unique?: boolean;
|
|
14
14
|
where?: string;
|
|
15
|
+
/** Index method / access method, e.g. 'hnsw', 'ivfflat', 'btree' */
|
|
16
|
+
using?: string;
|
|
17
|
+
/** Operator class for vector distance, e.g. 'vector_cosine_ops', 'vector_l2_ops', 'halfvec_cosine_ops' */
|
|
18
|
+
ops?: string;
|
|
19
|
+
/** Index parameters / WITH clause options, e.g. { m: 16, ef_construction: 64 } */
|
|
20
|
+
with?: Record<string, unknown> | string;
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
export interface CheckConstraint {
|