metal-orm 1.0.42 → 1.0.44

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.
Files changed (122) hide show
  1. package/README.md +195 -37
  2. package/dist/index.cjs +1014 -538
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1267 -371
  5. package/dist/index.d.ts +1267 -371
  6. package/dist/index.js +1012 -536
  7. package/dist/index.js.map +1 -1
  8. package/package.json +8 -2
  9. package/scripts/run-eslint.mjs +34 -0
  10. package/src/codegen/typescript.ts +32 -15
  11. package/src/core/ast/adapters.ts +8 -2
  12. package/src/core/ast/builders.ts +105 -76
  13. package/src/core/ast/expression-builders.ts +430 -392
  14. package/src/core/ast/expression-nodes.ts +14 -5
  15. package/src/core/ast/expression-visitor.ts +56 -14
  16. package/src/core/ast/helpers.ts +23 -0
  17. package/src/core/ast/join-node.ts +18 -2
  18. package/src/core/ast/query.ts +6 -6
  19. package/src/core/ast/window-functions.ts +10 -2
  20. package/src/core/ddl/dialects/base-schema-dialect.ts +37 -4
  21. package/src/core/ddl/dialects/index.ts +1 -0
  22. package/src/core/ddl/dialects/mssql-schema-dialect.ts +5 -0
  23. package/src/core/ddl/dialects/mysql-schema-dialect.ts +3 -0
  24. package/src/core/ddl/dialects/postgres-schema-dialect.ts +14 -1
  25. package/src/core/ddl/dialects/render-reference.test.ts +69 -0
  26. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +10 -0
  27. package/src/core/ddl/introspect/catalogs/index.ts +1 -0
  28. package/src/core/ddl/introspect/catalogs/postgres.ts +2 -0
  29. package/src/core/ddl/introspect/context.ts +6 -0
  30. package/src/core/ddl/introspect/functions/postgres.ts +13 -0
  31. package/src/core/ddl/introspect/mssql.ts +53 -8
  32. package/src/core/ddl/introspect/mysql.ts +32 -6
  33. package/src/core/ddl/introspect/postgres.ts +102 -34
  34. package/src/core/ddl/introspect/registry.ts +14 -0
  35. package/src/core/ddl/introspect/run-select.ts +19 -4
  36. package/src/core/ddl/introspect/sqlite.ts +78 -11
  37. package/src/core/ddl/introspect/types.ts +0 -1
  38. package/src/core/ddl/introspect/utils.ts +21 -3
  39. package/src/core/ddl/naming-strategy.ts +6 -0
  40. package/src/core/ddl/schema-dialect.ts +20 -6
  41. package/src/core/ddl/schema-diff.ts +22 -0
  42. package/src/core/ddl/schema-generator.ts +26 -12
  43. package/src/core/ddl/schema-plan-executor.ts +6 -0
  44. package/src/core/ddl/schema-types.ts +6 -0
  45. package/src/core/ddl/sql-writing.ts +4 -4
  46. package/src/core/dialect/abstract.ts +19 -7
  47. package/src/core/dialect/base/function-table-formatter.ts +3 -2
  48. package/src/core/dialect/base/join-compiler.ts +5 -3
  49. package/src/core/dialect/base/returning-strategy.ts +1 -0
  50. package/src/core/dialect/base/sql-dialect.ts +3 -3
  51. package/src/core/dialect/mssql/functions.ts +24 -25
  52. package/src/core/dialect/mssql/index.ts +1 -4
  53. package/src/core/dialect/mysql/functions.ts +0 -1
  54. package/src/core/dialect/postgres/functions.ts +33 -34
  55. package/src/core/dialect/postgres/index.ts +1 -0
  56. package/src/core/dialect/sqlite/functions.ts +18 -19
  57. package/src/core/dialect/sqlite/index.ts +2 -0
  58. package/src/core/execution/db-executor.ts +1 -1
  59. package/src/core/execution/executors/mysql-executor.ts +2 -2
  60. package/src/core/execution/executors/postgres-executor.ts +1 -1
  61. package/src/core/execution/pooling/pool.ts +12 -5
  62. package/src/core/functions/datetime.ts +58 -34
  63. package/src/core/functions/numeric.ts +96 -31
  64. package/src/core/functions/standard-strategy.ts +35 -0
  65. package/src/core/functions/text.ts +84 -23
  66. package/src/core/functions/types.ts +23 -8
  67. package/src/decorators/bootstrap.ts +42 -11
  68. package/src/decorators/column.ts +20 -11
  69. package/src/decorators/decorator-metadata.ts +30 -9
  70. package/src/decorators/entity.ts +29 -5
  71. package/src/decorators/index.ts +3 -0
  72. package/src/decorators/relations.ts +34 -11
  73. package/src/orm/als.ts +34 -9
  74. package/src/orm/entity-context.ts +62 -8
  75. package/src/orm/entity-meta.ts +8 -8
  76. package/src/orm/entity-metadata.ts +131 -16
  77. package/src/orm/entity.ts +28 -29
  78. package/src/orm/execute.ts +19 -4
  79. package/src/orm/hydration.ts +42 -39
  80. package/src/orm/identity-map.ts +1 -1
  81. package/src/orm/lazy-batch.ts +74 -104
  82. package/src/orm/orm-session.ts +24 -23
  83. package/src/orm/orm.ts +2 -5
  84. package/src/orm/relation-change-processor.ts +12 -11
  85. package/src/orm/relations/belongs-to.ts +11 -11
  86. package/src/orm/relations/has-many.ts +54 -10
  87. package/src/orm/relations/has-one.ts +8 -7
  88. package/src/orm/relations/many-to-many.ts +13 -13
  89. package/src/orm/runtime-types.ts +4 -4
  90. package/src/orm/save-graph.ts +31 -25
  91. package/src/orm/unit-of-work.ts +17 -17
  92. package/src/query/index.ts +74 -0
  93. package/src/query/target.ts +46 -0
  94. package/src/query-builder/delete-query-state.ts +30 -0
  95. package/src/query-builder/delete.ts +64 -18
  96. package/src/query-builder/hydration-manager.ts +52 -5
  97. package/src/query-builder/insert-query-state.ts +30 -0
  98. package/src/query-builder/insert.ts +58 -10
  99. package/src/query-builder/query-ast-service.ts +7 -2
  100. package/src/query-builder/query-resolution.ts +78 -0
  101. package/src/query-builder/raw-column-parser.ts +7 -1
  102. package/src/query-builder/relation-alias.ts +7 -0
  103. package/src/query-builder/relation-conditions.ts +61 -48
  104. package/src/query-builder/relation-service.ts +68 -63
  105. package/src/query-builder/relation-utils.ts +3 -0
  106. package/src/query-builder/select/cte-facet.ts +40 -0
  107. package/src/query-builder/select/from-facet.ts +80 -0
  108. package/src/query-builder/select/join-facet.ts +62 -0
  109. package/src/query-builder/select/predicate-facet.ts +103 -0
  110. package/src/query-builder/select/projection-facet.ts +69 -0
  111. package/src/query-builder/select/relation-facet.ts +81 -0
  112. package/src/query-builder/select/setop-facet.ts +36 -0
  113. package/src/query-builder/select-helpers.ts +15 -2
  114. package/src/query-builder/select-query-builder-deps.ts +19 -1
  115. package/src/query-builder/select-query-state.ts +2 -1
  116. package/src/query-builder/select.ts +795 -1163
  117. package/src/query-builder/update-query-state.ts +52 -0
  118. package/src/query-builder/update.ts +69 -18
  119. package/src/schema/column.ts +26 -26
  120. package/src/schema/table-guards.ts +31 -0
  121. package/src/schema/table.ts +47 -18
  122. package/src/schema/types.ts +22 -22
@@ -14,9 +14,21 @@ import {
14
14
  import { JoinNode } from '../core/ast/join.js';
15
15
  import { createTableNode } from '../core/ast/builders.js';
16
16
 
17
+ /**
18
+ * Literal values that can be used in UPDATE statements
19
+ */
17
20
  type LiteralValue = string | number | boolean | null;
21
+
22
+ /**
23
+ * Values allowed in UPDATE SET clauses
24
+ */
18
25
  type UpdateValue = OperandNode | LiteralValue;
19
26
 
27
+ /**
28
+ * Type guard to check if a value is valid for UPDATE operations
29
+ * @param value - Value to check
30
+ * @returns True if value is a valid update value
31
+ */
20
32
  const isUpdateValue = (value: unknown): value is UpdateValue => {
21
33
  if (value === null) return true;
22
34
  switch (typeof value) {
@@ -36,6 +48,11 @@ export class UpdateQueryState {
36
48
  public readonly table: TableDef;
37
49
  public readonly ast: UpdateQueryNode;
38
50
 
51
+ /**
52
+ * Creates a new UpdateQueryState instance
53
+ * @param table - Table definition for the update
54
+ * @param ast - Optional existing AST
55
+ */
39
56
  constructor(table: TableDef, ast?: UpdateQueryNode) {
40
57
  this.table = table;
41
58
  this.ast = ast ?? {
@@ -46,10 +63,20 @@ export class UpdateQueryState {
46
63
  };
47
64
  }
48
65
 
66
+ /**
67
+ * Creates a new UpdateQueryState with updated AST
68
+ * @param nextAst - Updated AST
69
+ * @returns New UpdateQueryState instance
70
+ */
49
71
  private clone(nextAst: UpdateQueryNode): UpdateQueryState {
50
72
  return new UpdateQueryState(this.table, nextAst);
51
73
  }
52
74
 
75
+ /**
76
+ * Sets the columns to update with their new values
77
+ * @param values - Record of column names to values
78
+ * @returns New UpdateQueryState with SET clause
79
+ */
53
80
  withSet(values: Record<string, unknown>): UpdateQueryState {
54
81
  const assignments: UpdateAssignmentNode[] = Object.entries(values).map(([column, rawValue]) => {
55
82
  if (!isUpdateValue(rawValue)) {
@@ -74,6 +101,11 @@ export class UpdateQueryState {
74
101
  });
75
102
  }
76
103
 
104
+ /**
105
+ * Adds a WHERE condition to the update query
106
+ * @param expr - WHERE expression
107
+ * @returns New UpdateQueryState with WHERE clause
108
+ */
77
109
  withWhere(expr: ExpressionNode): UpdateQueryState {
78
110
  return this.clone({
79
111
  ...this.ast,
@@ -81,6 +113,11 @@ export class UpdateQueryState {
81
113
  });
82
114
  }
83
115
 
116
+ /**
117
+ * Adds a RETURNING clause to the update query
118
+ * @param columns - Columns to return
119
+ * @returns New UpdateQueryState with RETURNING clause
120
+ */
84
121
  withReturning(columns: ColumnNode[]): UpdateQueryState {
85
122
  return this.clone({
86
123
  ...this.ast,
@@ -88,6 +125,11 @@ export class UpdateQueryState {
88
125
  });
89
126
  }
90
127
 
128
+ /**
129
+ * Sets the FROM clause for the update query
130
+ * @param from - Table source for FROM
131
+ * @returns New UpdateQueryState with FROM clause
132
+ */
91
133
  withFrom(from: TableSourceNode): UpdateQueryState {
92
134
  return this.clone({
93
135
  ...this.ast,
@@ -95,6 +137,11 @@ export class UpdateQueryState {
95
137
  });
96
138
  }
97
139
 
140
+ /**
141
+ * Adds a JOIN to the update query
142
+ * @param join - Join node to add
143
+ * @returns New UpdateQueryState with JOIN
144
+ */
98
145
  withJoin(join: JoinNode): UpdateQueryState {
99
146
  return this.clone({
100
147
  ...this.ast,
@@ -102,6 +149,11 @@ export class UpdateQueryState {
102
149
  });
103
150
  }
104
151
 
152
+ /**
153
+ * Applies an alias to the table being updated
154
+ * @param alias - Alias for the table
155
+ * @returns New UpdateQueryState with table alias
156
+ */
105
157
  withTableAlias(alias: string): UpdateQueryState {
106
158
  return this.clone({
107
159
  ...this.ast,
@@ -2,12 +2,14 @@ import { TableDef } from '../schema/table.js';
2
2
  import { ColumnDef } from '../schema/column.js';
3
3
  import { ColumnNode, ExpressionNode } from '../core/ast/expression.js';
4
4
  import { JOIN_KINDS, JoinKind } from '../core/sql/sql.js';
5
- import { CompiledQuery, UpdateCompiler, Dialect } from '../core/dialect/abstract.js';
5
+ import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
6
6
  import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
7
7
  import { TableSourceNode, UpdateQueryNode } from '../core/ast/query.js';
8
8
  import { UpdateQueryState } from './update-query-state.js';
9
9
  import { createJoinNode } from '../core/ast/join-node.js';
10
10
  import { buildColumnNode } from '../core/ast/builders.js';
11
+ import { OrmSession } from '../orm/orm-session.js';
12
+ import { QueryResult } from '../core/execution/db-executor.js';
11
13
 
12
14
  type UpdateDialectInput = Dialect | DialectKey;
13
15
 
@@ -18,6 +20,11 @@ export class UpdateQueryBuilder<T> {
18
20
  private readonly table: TableDef;
19
21
  private readonly state: UpdateQueryState;
20
22
 
23
+ /**
24
+ * Creates a new UpdateQueryBuilder instance
25
+ * @param table - The table definition for the UPDATE query
26
+ * @param state - Optional initial query state, defaults to a new UpdateQueryState
27
+ */
21
28
  constructor(table: TableDef, state?: UpdateQueryState) {
22
29
  this.table = table;
23
30
  this.state = state ?? new UpdateQueryState(table);
@@ -27,15 +34,33 @@ export class UpdateQueryBuilder<T> {
27
34
  return new UpdateQueryBuilder(this.table, state);
28
35
  }
29
36
 
37
+ /**
38
+ * Sets an alias for the table in the UPDATE query
39
+ * @param alias - The alias to assign to the table
40
+ * @returns A new UpdateQueryBuilder with the table alias set
41
+ */
30
42
  as(alias: string): UpdateQueryBuilder<T> {
31
43
  return this.clone(this.state.withTableAlias(alias));
32
44
  }
33
45
 
46
+ /**
47
+ * Adds a FROM clause to the UPDATE query
48
+ * @param source - The table source to use in the FROM clause
49
+ * @returns A new UpdateQueryBuilder with the FROM clause added
50
+ */
34
51
  from(source: TableDef | TableSourceNode): UpdateQueryBuilder<T> {
35
52
  const tableSource = this.resolveTableSource(source);
36
53
  return this.clone(this.state.withFrom(tableSource));
37
54
  }
38
55
 
56
+ /**
57
+ * Adds a JOIN clause to the UPDATE query
58
+ * @param table - The table to join with
59
+ * @param condition - The join condition expression
60
+ * @param kind - The type of join (defaults to INNER)
61
+ * @param relationName - Optional name for the relation
62
+ * @returns A new UpdateQueryBuilder with the JOIN clause added
63
+ */
39
64
  join(
40
65
  table: TableDef | TableSourceNode | string,
41
66
  condition: ExpressionNode,
@@ -47,14 +72,29 @@ export class UpdateQueryBuilder<T> {
47
72
  return this.clone(this.state.withJoin(joinNode));
48
73
  }
49
74
 
75
+ /**
76
+ * Adds a SET clause to the UPDATE query
77
+ * @param values - The column-value pairs to update
78
+ * @returns A new UpdateQueryBuilder with the SET clause added
79
+ */
50
80
  set(values: Record<string, unknown>): UpdateQueryBuilder<T> {
51
81
  return this.clone(this.state.withSet(values));
52
82
  }
53
83
 
84
+ /**
85
+ * Adds a WHERE clause to the UPDATE query
86
+ * @param expr - The expression to use as the WHERE condition
87
+ * @returns A new UpdateQueryBuilder with the WHERE clause added
88
+ */
54
89
  where(expr: ExpressionNode): UpdateQueryBuilder<T> {
55
90
  return this.clone(this.state.withWhere(expr));
56
91
  }
57
92
 
93
+ /**
94
+ * Adds a RETURNING clause to the UPDATE query
95
+ * @param columns - Columns to return after update
96
+ * @returns A new UpdateQueryBuilder with the RETURNING clause added
97
+ */
58
98
  returning(...columns: (ColumnDef | ColumnNode)[]): UpdateQueryBuilder<T> {
59
99
  if (!columns.length) return this;
60
100
  const nodes = columns.map(column => buildColumnNode(this.table, column));
@@ -73,28 +113,39 @@ export class UpdateQueryBuilder<T> {
73
113
  return this.resolveTableSource(table);
74
114
  }
75
115
 
76
- // Existing compiler-based compile stays, but we add a new overload.
77
-
78
- // 1) Keep the old behavior (used internally / tests, if any):
79
- compile(compiler: UpdateCompiler): CompiledQuery;
80
- // 2) New ergonomic overload:
81
- compile(dialect: UpdateDialectInput): CompiledQuery;
82
-
83
- compile(arg: UpdateCompiler | UpdateDialectInput): CompiledQuery {
84
- if (typeof (arg as any).compileUpdate === 'function') {
85
- // UpdateCompiler path – old behavior
86
- return (arg as UpdateCompiler).compileUpdate(this.state.ast);
87
- }
116
+ /**
117
+ * Compiles the UPDATE query for the specified dialect
118
+ * @param dialect - The SQL dialect to compile for
119
+ * @returns The compiled query with SQL and parameters
120
+ */
121
+ compile(dialect: UpdateDialectInput): CompiledQuery {
122
+ const resolved = resolveDialectInput(dialect);
123
+ return resolved.compileUpdate(this.state.ast);
124
+ }
88
125
 
89
- // Dialect | string path – new behavior
90
- const dialect = resolveDialectInput(arg as UpdateDialectInput);
91
- return dialect.compileUpdate(this.state.ast);
126
+ /**
127
+ * Returns the SQL string for the UPDATE query
128
+ * @param dialect - The SQL dialect to generate SQL for
129
+ * @returns The SQL string representation of the query
130
+ */
131
+ toSql(dialect: UpdateDialectInput): string {
132
+ return this.compile(dialect).sql;
92
133
  }
93
134
 
94
- toSql(arg: UpdateCompiler | UpdateDialectInput): string {
95
- return this.compile(arg as any).sql;
135
+ /**
136
+ * Executes the UPDATE query using the provided session
137
+ * @param session - The ORM session to execute the query with
138
+ * @returns A promise that resolves to the query results
139
+ */
140
+ async execute(session: OrmSession): Promise<QueryResult[]> {
141
+ const compiled = this.compile(session.dialect);
142
+ return session.executor.executeSql(compiled.sql, compiled.params);
96
143
  }
97
144
 
145
+ /**
146
+ * Returns the Abstract Syntax Tree (AST) representation of the query
147
+ * @returns The AST node for the UPDATE query
148
+ */
98
149
  getAST(): UpdateQueryNode {
99
150
  return this.state.ast;
100
151
  }
@@ -100,7 +100,7 @@ export interface ColumnDef<T extends ColumnType = ColumnType, TRuntime = unknown
100
100
  /** Column comment/description */
101
101
  comment?: string;
102
102
  /** Additional arguments for the column type (e.g., VARCHAR length) */
103
- args?: any[];
103
+ args?: unknown[];
104
104
  /** Table name this column belongs to (filled at runtime by defineTable) */
105
105
  table?: string;
106
106
  }
@@ -234,28 +234,28 @@ export const col = {
234
234
  * Marks a column as UNIQUE
235
235
  */
236
236
  unique: <T extends ColumnType>(def: ColumnDef<T>, name?: string): ColumnDef<T> =>
237
- ({
238
- ...def,
239
- unique: name ?? true
240
- }),
237
+ ({
238
+ ...def,
239
+ unique: name ?? true
240
+ }),
241
241
 
242
242
  /**
243
243
  * Sets a default value for the column
244
244
  */
245
245
  default: <T extends ColumnType>(def: ColumnDef<T>, value: unknown): ColumnDef<T> =>
246
- ({
247
- ...def,
248
- default: value
249
- }),
246
+ ({
247
+ ...def,
248
+ default: value
249
+ }),
250
250
 
251
251
  /**
252
252
  * Sets a raw SQL default value for the column
253
253
  */
254
254
  defaultRaw: <T extends ColumnType>(def: ColumnDef<T>, expression: string): ColumnDef<T> =>
255
- ({
256
- ...def,
257
- default: { raw: expression }
258
- }),
255
+ ({
256
+ ...def,
257
+ default: { raw: expression }
258
+ }),
259
259
 
260
260
  /**
261
261
  * Marks a column as auto-increment / identity
@@ -264,27 +264,27 @@ export const col = {
264
264
  def: ColumnDef<T>,
265
265
  strategy: ColumnDef['generated'] = 'byDefault'
266
266
  ): ColumnDef<T> =>
267
- ({
268
- ...def,
269
- autoIncrement: true,
270
- generated: strategy
271
- }),
267
+ ({
268
+ ...def,
269
+ autoIncrement: true,
270
+ generated: strategy
271
+ }),
272
272
 
273
273
  /**
274
274
  * Adds a foreign key reference
275
275
  */
276
276
  references: <T extends ColumnType>(def: ColumnDef<T>, ref: ForeignKeyReference): ColumnDef<T> =>
277
- ({
278
- ...def,
279
- references: ref
280
- }),
277
+ ({
278
+ ...def,
279
+ references: ref
280
+ }),
281
281
 
282
282
  /**
283
283
  * Adds a check constraint to the column
284
284
  */
285
285
  check: <T extends ColumnType>(def: ColumnDef<T>, expression: string): ColumnDef<T> =>
286
- ({
287
- ...def,
288
- check: expression
289
- })
286
+ ({
287
+ ...def,
288
+ check: expression
289
+ })
290
290
  };
@@ -0,0 +1,31 @@
1
+ import { ColumnDef } from './column.js';
2
+ import type { TableDef } from './table.js';
3
+
4
+ const isColumnsRecord = (columns: unknown): columns is Record<string, ColumnDef> => {
5
+ return typeof columns === 'object' && columns !== null;
6
+ };
7
+
8
+ const isRelationsRecord = (relations: unknown): relations is Record<string, unknown> => {
9
+ return typeof relations === 'object' && relations !== null;
10
+ };
11
+
12
+ export const isTableDef = (value: unknown): value is TableDef => {
13
+ if (typeof value !== 'object' || value === null) {
14
+ return false;
15
+ }
16
+
17
+ const candidate = value as Partial<TableDef>;
18
+ if (typeof candidate.name !== 'string') {
19
+ return false;
20
+ }
21
+
22
+ if (!isColumnsRecord(candidate.columns)) {
23
+ return false;
24
+ }
25
+
26
+ if (!isRelationsRecord(candidate.relations)) {
27
+ return false;
28
+ }
29
+
30
+ return true;
31
+ };
@@ -31,12 +31,12 @@ export interface TableOptions {
31
31
  }
32
32
 
33
33
  export interface TableHooks {
34
- beforeInsert?(ctx: unknown, entity: any): Promise<void> | void;
35
- afterInsert?(ctx: unknown, entity: any): Promise<void> | void;
36
- beforeUpdate?(ctx: unknown, entity: any): Promise<void> | void;
37
- afterUpdate?(ctx: unknown, entity: any): Promise<void> | void;
38
- beforeDelete?(ctx: unknown, entity: any): Promise<void> | void;
39
- afterDelete?(ctx: unknown, entity: any): Promise<void> | void;
34
+ beforeInsert?(ctx: unknown, entity: unknown): Promise<void> | void;
35
+ afterInsert?(ctx: unknown, entity: unknown): Promise<void> | void;
36
+ beforeUpdate?(ctx: unknown, entity: unknown): Promise<void> | void;
37
+ afterUpdate?(ctx: unknown, entity: unknown): Promise<void> | void;
38
+ beforeDelete?(ctx: unknown, entity: unknown): Promise<void> | void;
39
+ afterDelete?(ctx: unknown, entity: unknown): Promise<void> | void;
40
40
  }
41
41
 
42
42
  /**
@@ -94,7 +94,8 @@ export const defineTable = <T extends Record<string, ColumnDef>>(
94
94
  ): TableDef<T> => {
95
95
  // Runtime mutability to assign names to column definitions for convenience
96
96
  const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
97
- (acc as any)[key] = { ...def, name: key, table: name };
97
+ const colDef = { ...def, name: key, table: name };
98
+ (acc as Record<string, unknown>)[key] = colDef;
98
99
  return acc;
99
100
  }, {} as T);
100
101
 
@@ -128,36 +129,39 @@ export type TableRef<T extends TableDef> =
128
129
  $: T["columns"];
129
130
  };
130
131
 
131
- const TABLE_REF_CACHE: WeakMap<object, any> = new WeakMap();
132
+ const TABLE_REF_CACHE: WeakMap<object, unknown> = new WeakMap();
132
133
 
133
134
  const withColumnProps = <T extends TableDef>(table: T): TableRef<T> => {
134
- const cached = TABLE_REF_CACHE.get(table as any);
135
+ const cached = TABLE_REF_CACHE.get(table);
135
136
  if (cached) return cached as TableRef<T>;
136
137
 
137
- const proxy = new Proxy(table as any, {
138
+ const proxy = new Proxy(table as object, {
138
139
  get(target, prop, receiver) {
139
- if (prop === "$") return target.columns;
140
+ const t = target as TableDef;
141
+ if (prop === "$") return t.columns;
140
142
 
141
143
  // Prefer real table fields first (prevents collision surprises)
142
144
  if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver);
143
145
 
144
146
  // Fall back to columns bag
145
- if (typeof prop === "string" && prop in target.columns) return target.columns[prop];
147
+ if (typeof prop === "string" && prop in t.columns) return t.columns[prop];
146
148
 
147
149
  return undefined;
148
150
  },
149
151
 
150
152
  has(target, prop) {
153
+ const t = target as TableDef;
151
154
  return (
152
155
  prop === "$" ||
153
156
  Reflect.has(target, prop) ||
154
- (typeof prop === "string" && prop in target.columns)
157
+ (typeof prop === "string" && prop in t.columns)
155
158
  );
156
159
  },
157
160
 
158
161
  ownKeys(target) {
162
+ const t = target as TableDef;
159
163
  const base = Reflect.ownKeys(target);
160
- const cols = Object.keys(target.columns);
164
+ const cols = Object.keys(t.columns);
161
165
 
162
166
  for (const k of cols) {
163
167
  if (!base.includes(k)) base.push(k);
@@ -172,20 +176,20 @@ const withColumnProps = <T extends TableDef>(table: T): TableRef<T> => {
172
176
  configurable: true,
173
177
  enumerable: false,
174
178
  get() {
175
- return target.columns;
179
+ return (target as TableDef).columns;
176
180
  },
177
181
  };
178
182
  }
179
183
 
180
184
  if (
181
185
  typeof prop === "string" &&
182
- prop in target.columns &&
186
+ prop in (target as TableDef).columns &&
183
187
  !Reflect.has(target, prop)
184
188
  ) {
185
189
  return {
186
190
  configurable: true,
187
191
  enumerable: true,
188
- value: target.columns[prop],
192
+ value: (target as TableDef).columns[prop],
189
193
  writable: false,
190
194
  };
191
195
  }
@@ -194,7 +198,7 @@ const withColumnProps = <T extends TableDef>(table: T): TableRef<T> => {
194
198
  },
195
199
  }) as TableRef<T>;
196
200
 
197
- TABLE_REF_CACHE.set(table as any, proxy);
201
+ TABLE_REF_CACHE.set(table, proxy);
198
202
  return proxy;
199
203
  };
200
204
 
@@ -208,3 +212,28 @@ const withColumnProps = <T extends TableDef>(table: T): TableRef<T> => {
208
212
  * t.$.name is the "name" column (escape hatch)
209
213
  */
210
214
  export const tableRef = <T extends TableDef>(table: T): TableRef<T> => withColumnProps(table);
215
+
216
+ /**
217
+ * Public API: dynamic column lookup by string key.
218
+ *
219
+ * Useful when the column name is only known at runtime, or when a column name
220
+ * collides with a real table field (e.g. "name").
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * const t = tableRef(todos);
225
+ * const key = runtimeKey();
226
+ * where(eq(getColumn(t, key), 123));
227
+ * // or: t.$.name (escape hatch)
228
+ * ```
229
+ */
230
+ export function getColumn<T extends TableDef, K extends keyof T['columns'] & string>(table: T, key: K): T['columns'][K];
231
+ export function getColumn<T extends TableDef>(table: T, key: string): ColumnDef;
232
+ export function getColumn<T extends TableDef>(table: T, key: string): ColumnDef {
233
+ const col = table.columns[key] as ColumnDef | undefined;
234
+ if (!col) {
235
+ const tableName = table.name || '<unknown>';
236
+ throw new Error(`Column '${key}' does not exist on table '${tableName}'`);
237
+ }
238
+ return col;
239
+ }
@@ -23,15 +23,15 @@ export type RelationTargetTable<TRel extends RelationDef> =
23
23
  */
24
24
  export type ColumnToTs<T extends ColumnDef> =
25
25
  [unknown] extends [T['tsType']]
26
- ? T['type'] extends 'INT' | 'INTEGER' | 'int' | 'integer' ? number :
27
- T['type'] extends 'BIGINT' | 'bigint' ? number | bigint :
28
- T['type'] extends 'DECIMAL' | 'decimal' | 'FLOAT' | 'float' | 'DOUBLE' | 'double' ? number :
29
- T['type'] extends 'BOOLEAN' | 'boolean' ? boolean :
30
- T['type'] extends 'JSON' | 'json' ? unknown :
31
- T['type'] extends 'BLOB' | 'blob' | 'BINARY' | 'binary' | 'VARBINARY' | 'varbinary' | 'BYTEA' | 'bytea' ? Buffer :
32
- T['type'] extends 'DATE' | 'date' | 'DATETIME' | 'datetime' | 'TIMESTAMP' | 'timestamp' | 'TIMESTAMPTZ' | 'timestamptz' ? string :
33
- string
34
- : Exclude<T['tsType'], undefined>;
26
+ ? T['type'] extends 'INT' | 'INTEGER' | 'int' | 'integer' ? number :
27
+ T['type'] extends 'BIGINT' | 'bigint' ? number | bigint :
28
+ T['type'] extends 'DECIMAL' | 'decimal' | 'FLOAT' | 'float' | 'DOUBLE' | 'double' ? number :
29
+ T['type'] extends 'BOOLEAN' | 'boolean' ? boolean :
30
+ T['type'] extends 'JSON' | 'json' ? unknown :
31
+ T['type'] extends 'BLOB' | 'blob' | 'BINARY' | 'binary' | 'VARBINARY' | 'varbinary' | 'BYTEA' | 'bytea' ? Buffer :
32
+ T['type'] extends 'DATE' | 'date' | 'DATETIME' | 'datetime' | 'TIMESTAMP' | 'timestamp' | 'TIMESTAMPTZ' | 'timestamptz' ? string :
33
+ string
34
+ : Exclude<T['tsType'], undefined>;
35
35
 
36
36
  /**
37
37
  * Infers a row shape from a table definition
@@ -41,10 +41,10 @@ export type InferRow<TTable extends TableDef> = {
41
41
  };
42
42
 
43
43
  type RelationResult<T extends RelationDef> =
44
- T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
45
- T extends HasOneRelation<infer TTarget> ? InferRow<TTarget> | null :
46
- T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
47
- T extends BelongsToManyRelation<infer TTarget> ? (InferRow<TTarget> & { _pivot?: any })[] :
44
+ T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
45
+ T extends HasOneRelation<infer TTarget> ? InferRow<TTarget> | null :
46
+ T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
47
+ T extends BelongsToManyRelation<infer TTarget> ? (InferRow<TTarget> & { _pivot?: unknown })[] :
48
48
  never;
49
49
 
50
50
  /**
@@ -88,15 +88,15 @@ export type EntityInstance<
88
88
  TRow = InferRow<TTable>
89
89
  > = TRow & {
90
90
  [K in keyof RelationMap<TTable>]:
91
- TTable['relations'][K] extends HasManyRelation<infer TTarget>
92
- ? HasManyCollection<EntityInstance<TTarget>>
93
- : TTable['relations'][K] extends HasOneRelation<infer TTarget>
94
- ? HasOneReference<EntityInstance<TTarget>>
95
- : TTable['relations'][K] extends BelongsToManyRelation<infer TTarget>
96
- ? ManyToManyCollection<EntityInstance<TTarget>>
97
- : TTable['relations'][K] extends BelongsToRelation<infer TTarget>
98
- ? BelongsToReference<EntityInstance<TTarget>>
99
- : never;
91
+ TTable['relations'][K] extends HasManyRelation<infer TTarget>
92
+ ? HasManyCollection<EntityInstance<TTarget>>
93
+ : TTable['relations'][K] extends HasOneRelation<infer TTarget>
94
+ ? HasOneReference<EntityInstance<TTarget>>
95
+ : TTable['relations'][K] extends BelongsToManyRelation<infer TTarget>
96
+ ? ManyToManyCollection<EntityInstance<TTarget>>
97
+ : TTable['relations'][K] extends BelongsToRelation<infer TTarget>
98
+ ? BelongsToReference<EntityInstance<TTarget>>
99
+ : never;
100
100
  } & {
101
101
  $load<K extends keyof RelationMap<TTable>>(relation: K): Promise<RelationMap<TTable>[K]>;
102
102
  };