metal-orm 1.1.18 → 1.1.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.1.18",
3
+ "version": "1.1.20",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "types": "./dist/index.d.ts",
10
10
  "engines": {
11
- "node": ">=20.0.0"
11
+ "node": ">=22.0.0"
12
12
  },
13
13
  "bin": {
14
14
  "metal-orm-gen": "./scripts/generate-entities.mjs"
@@ -50,7 +50,7 @@
50
50
  "mysql2": "^3.22.5",
51
51
  "pg": "^8.22.0",
52
52
  "sqlite3": "^6.0.1",
53
- "tedious": "^19.2.1"
53
+ "tedious": "^20.0.0"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "better-sqlite3": {
@@ -77,8 +77,8 @@
77
77
  },
78
78
  "devDependencies": {
79
79
  "@electric-sql/pglite": "^0.5.3",
80
- "@typescript-eslint/eslint-plugin": "^8.62.0",
81
- "@typescript-eslint/parser": "^8.62.0",
80
+ "@typescript-eslint/eslint-plugin": "^8.62.1",
81
+ "@typescript-eslint/parser": "^8.62.1",
82
82
  "@vitest/ui": "^4.1.9",
83
83
  "better-sqlite3": "^12.11.1",
84
84
  "eslint": "^10.6.0",
@@ -91,7 +91,7 @@
91
91
  "pg": "^8.22.0",
92
92
  "sqlite3": "^6.0.1",
93
93
  "supertest": "^7.2.2",
94
- "tedious": "^19.2.1",
94
+ "tedious": "^20.0.0",
95
95
  "tsup": "^8.5.1",
96
96
  "tsx": "^4.22.4",
97
97
  "typescript": "^5.9.3",
@@ -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
- const cols = renderIndexColumns(this, index.columns);
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';
@@ -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.
@@ -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 {