metal-orm 1.0.41 → 1.0.43

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 (87) hide show
  1. package/README.md +74 -20
  2. package/dist/index.cjs +180 -74
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +142 -96
  5. package/dist/index.d.ts +142 -96
  6. package/dist/index.js +177 -74
  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/builders.ts +7 -2
  12. package/src/core/ast/expression-builders.ts +0 -2
  13. package/src/core/ast/expression-nodes.ts +14 -5
  14. package/src/core/ast/expression-visitor.ts +11 -8
  15. package/src/core/ast/expression.ts +2 -2
  16. package/src/core/ast/join-node.ts +1 -1
  17. package/src/core/ast/query.ts +6 -6
  18. package/src/core/ast/window-functions.ts +10 -2
  19. package/src/core/ddl/dialects/base-schema-dialect.ts +30 -3
  20. package/src/core/ddl/dialects/mssql-schema-dialect.ts +4 -0
  21. package/src/core/ddl/dialects/mysql-schema-dialect.ts +2 -0
  22. package/src/core/ddl/dialects/postgres-schema-dialect.ts +13 -1
  23. package/src/core/ddl/dialects/render-reference.test.ts +69 -0
  24. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +9 -0
  25. package/src/core/ddl/introspect/mssql.ts +42 -8
  26. package/src/core/ddl/introspect/mysql.ts +30 -6
  27. package/src/core/ddl/introspect/postgres.ts +88 -34
  28. package/src/core/ddl/introspect/run-select.ts +6 -4
  29. package/src/core/ddl/introspect/sqlite.ts +56 -11
  30. package/src/core/ddl/introspect/types.ts +0 -1
  31. package/src/core/ddl/introspect/utils.ts +3 -3
  32. package/src/core/ddl/schema-dialect.ts +1 -0
  33. package/src/core/ddl/schema-generator.ts +4 -12
  34. package/src/core/ddl/sql-writing.ts +4 -4
  35. package/src/core/dialect/abstract.ts +18 -6
  36. package/src/core/dialect/base/function-table-formatter.ts +3 -2
  37. package/src/core/dialect/base/join-compiler.ts +5 -3
  38. package/src/core/dialect/base/returning-strategy.ts +1 -0
  39. package/src/core/dialect/base/sql-dialect.ts +3 -3
  40. package/src/core/dialect/mssql/functions.ts +24 -25
  41. package/src/core/dialect/mssql/index.ts +1 -4
  42. package/src/core/dialect/mysql/functions.ts +0 -1
  43. package/src/core/dialect/postgres/functions.ts +33 -34
  44. package/src/core/dialect/postgres/index.ts +1 -0
  45. package/src/core/dialect/sqlite/functions.ts +18 -19
  46. package/src/core/dialect/sqlite/index.ts +2 -0
  47. package/src/core/execution/db-executor.ts +1 -1
  48. package/src/core/execution/executors/mysql-executor.ts +2 -2
  49. package/src/core/execution/executors/postgres-executor.ts +1 -1
  50. package/src/core/execution/pooling/pool.ts +2 -0
  51. package/src/core/functions/datetime.ts +1 -1
  52. package/src/core/functions/numeric.ts +1 -1
  53. package/src/core/functions/text.ts +1 -1
  54. package/src/decorators/bootstrap.ts +27 -8
  55. package/src/decorators/column.ts +3 -11
  56. package/src/decorators/decorator-metadata.ts +3 -9
  57. package/src/decorators/entity.ts +21 -5
  58. package/src/decorators/relations.ts +2 -11
  59. package/src/orm/entity-context.ts +8 -8
  60. package/src/orm/entity-meta.ts +8 -8
  61. package/src/orm/entity-metadata.ts +11 -9
  62. package/src/orm/entity.ts +28 -29
  63. package/src/orm/execute.ts +4 -4
  64. package/src/orm/hydration.ts +42 -39
  65. package/src/orm/identity-map.ts +1 -1
  66. package/src/orm/lazy-batch.ts +9 -9
  67. package/src/orm/orm-session.ts +24 -23
  68. package/src/orm/orm.ts +2 -5
  69. package/src/orm/relation-change-processor.ts +12 -11
  70. package/src/orm/relations/belongs-to.ts +11 -11
  71. package/src/orm/relations/has-many.ts +10 -10
  72. package/src/orm/relations/has-one.ts +8 -7
  73. package/src/orm/relations/many-to-many.ts +13 -13
  74. package/src/orm/runtime-types.ts +4 -4
  75. package/src/orm/save-graph.ts +31 -25
  76. package/src/orm/unit-of-work.ts +17 -17
  77. package/src/query-builder/delete.ts +4 -3
  78. package/src/query-builder/hydration-manager.ts +6 -5
  79. package/src/query-builder/insert.ts +12 -8
  80. package/src/query-builder/query-ast-service.ts +2 -2
  81. package/src/query-builder/raw-column-parser.ts +2 -1
  82. package/src/query-builder/select-helpers.ts +2 -2
  83. package/src/query-builder/select.ts +31 -31
  84. package/src/query-builder/update.ts +4 -3
  85. package/src/schema/column.ts +26 -26
  86. package/src/schema/table.ts +239 -115
  87. package/src/schema/types.ts +22 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -28,7 +28,9 @@
28
28
  "gen:entities": "node scripts/generate-entities.mjs",
29
29
  "test": "vitest",
30
30
  "test:ui": "vitest --ui",
31
- "show-sql": "node scripts/show-sql.mjs"
31
+ "show-sql": "node scripts/show-sql.mjs",
32
+ "lint": "node scripts/run-eslint.mjs",
33
+ "lint:fix": "node scripts/run-eslint.mjs --fix"
32
34
  },
33
35
  "peerDependencies": {
34
36
  "mysql2": "^3.9.0",
@@ -52,6 +54,10 @@
52
54
  },
53
55
  "devDependencies": {
54
56
  "@vitest/ui": "^4.0.14",
57
+ "@typescript-eslint/eslint-plugin": "^8.20.0",
58
+ "@typescript-eslint/parser": "^8.20.0",
59
+ "eslint": "^8.57.0",
60
+ "eslint-plugin-deprecation": "^3.0.0",
55
61
  "mysql2": "^3.15.3",
56
62
  "pg": "^8.16.3",
57
63
  "sqlite3": "^5.1.7",
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+
5
+ const [, , ...userArgs] = process.argv;
6
+
7
+ const TARGETS = {
8
+ src: ['./src'],
9
+ all: ['./src', './tests', './scripts', './playground'],
10
+ playground: ['./playground'],
11
+ 'src/core': ['./src/core'],
12
+ };
13
+
14
+ const [firstArg, ...restArgs] = userArgs;
15
+ const hasTarget = firstArg && !firstArg.startsWith('-');
16
+
17
+ const targetKey = hasTarget ? firstArg : 'all';
18
+ const extraArgs = hasTarget ? restArgs : userArgs;
19
+
20
+ const paths = TARGETS[targetKey] ?? [targetKey];
21
+
22
+ if (paths.length === 0) {
23
+ console.error(`Unknown lint target: ${targetKey}`);
24
+ process.exit(1);
25
+ }
26
+
27
+ const eslintArgs = [...paths, '--ext', '.ts,.tsx,.mjs', ...extraArgs];
28
+
29
+ const eslintBinBase = path.join(process.cwd(), 'node_modules', '.bin', 'eslint');
30
+ const executable = process.platform === 'win32' ? `${eslintBinBase}.cmd` : eslintBinBase;
31
+
32
+ const child = spawn(executable, eslintArgs, { stdio: 'inherit', shell: true });
33
+
34
+ child.on('exit', (code) => process.exit(code));
@@ -1,4 +1,4 @@
1
- import { SelectQueryNode } from '../core/ast/query.js';
1
+ import { OrderingTerm, SelectQueryNode } from '../core/ast/query.js';
2
2
  import {
3
3
  ExpressionNode,
4
4
  OperandNode,
@@ -36,8 +36,15 @@ const assertNever = (value: never): never => {
36
36
  /**
37
37
  * Generates TypeScript code from query AST nodes
38
38
  */
39
+ type SelectionColumn =
40
+ | ColumnNode
41
+ | FunctionNode
42
+ | ScalarSubqueryNode
43
+ | CaseExpressionNode
44
+ | WindowFunctionNode;
45
+
39
46
  export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVisitor<string> {
40
- constructor(private namingStrategy: NamingStrategy = new DefaultNamingStrategy()) {}
47
+ constructor(private namingStrategy: NamingStrategy = new DefaultNamingStrategy()) { }
41
48
 
42
49
  /**
43
50
  * Generates TypeScript code from a query AST
@@ -62,12 +69,8 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
62
69
  const hydratedRelations = new Set(hydration?.relations?.map(r => r.name) ?? []);
63
70
 
64
71
  const selections = ast.columns
65
- .filter(col => !(hydration && isRelationAlias((col as any).alias)))
66
- .map(col => {
67
- const key = (col as any).alias || (col as any).name;
68
- const operand = col as OperandNode;
69
- return `${key}: ${this.printOperand(operand)}`;
70
- });
72
+ .filter((col): col is SelectionColumn => !(hydration && isRelationAlias(col.alias)))
73
+ .map((col, index) => `${this.getSelectionKey(col, index)}: ${this.printOperand(col)}`);
71
74
 
72
75
  lines.push(`db.select({`);
73
76
  selections.forEach((sel, index) => {
@@ -168,26 +171,40 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
168
171
  /**
169
172
  * Prints an ordering term (operand/expression/alias) to TypeScript code.
170
173
  */
171
- private printOrderingTerm(term: any): string {
172
- if (!term || !(term as any).type) {
174
+ private printOrderingTerm(term: OrderingTerm): string {
175
+ if (!term || !('type' in term)) {
173
176
  throw new Error('Unsupported ordering term');
174
177
  }
175
178
 
176
- switch ((term as any).type) {
179
+ switch (term.type) {
177
180
  case 'Column':
178
- return `${this.namingStrategy.tableToSymbol((term as any).table)}.${(term as any).name}`;
181
+ return `${this.namingStrategy.tableToSymbol(term.table)}.${term.name}`;
179
182
  case 'AliasRef':
180
- return this.visitAliasRef(term as any);
183
+ return this.visitAliasRef(term);
181
184
  case 'Literal':
182
185
  case 'Function':
183
186
  case 'JsonPath':
184
187
  case 'ScalarSubquery':
185
188
  case 'CaseExpression':
186
189
  case 'WindowFunction':
187
- return this.printOperand(term as OperandNode);
190
+ return this.printOperand(term);
188
191
  default:
189
- return this.printExpression(term as ExpressionNode);
192
+ return this.printExpression(term);
193
+ }
194
+ }
195
+
196
+ private getSelectionKey(selection: SelectionColumn, index: number): string {
197
+ if (selection.alias) {
198
+ return selection.alias;
199
+ }
200
+ if (this.isNamedSelection(selection)) {
201
+ return selection.name;
190
202
  }
203
+ return `selection${index + 1}`;
204
+ }
205
+
206
+ private isNamedSelection(selection: SelectionColumn): selection is ColumnNode | FunctionNode | WindowFunctionNode {
207
+ return 'name' in selection;
191
208
  }
192
209
 
193
210
  public visitBinaryExpression(binary: BinaryExpressionNode): string {
@@ -1,4 +1,4 @@
1
- import { ColumnNode } from './expression-nodes.js';
1
+ import { ColumnNode, OperandNode } from './expression-nodes.js';
2
2
  import { TableNode, FunctionTableNode, DerivedTableNode } from './query.js';
3
3
  import { ColumnRef, TableRef } from './types.js';
4
4
 
@@ -50,7 +50,12 @@ export const createTableNode = (table: TableRef): TableNode => ({
50
50
  /**
51
51
  * Creates a FunctionTable node for expressions like `function_name(args...)` used in FROM
52
52
  */
53
- export const fnTable = (name: string, args: any[] = [], alias?: string, opts?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }): FunctionTableNode => ({
53
+ export const fnTable = (
54
+ name: string,
55
+ args: OperandNode[] = [],
56
+ alias?: string,
57
+ opts?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
58
+ ): FunctionTableNode => ({
54
59
  type: 'FunctionTable',
55
60
  name,
56
61
  args,
@@ -3,12 +3,10 @@ import { SqlOperator } from '../sql/sql.js';
3
3
  import { ColumnRef } from './types.js';
4
4
  import {
5
5
  ColumnNode,
6
- FunctionNode,
7
6
  LiteralNode,
8
7
  JsonPathNode,
9
8
  OperandNode,
10
9
  CaseExpressionNode,
11
- WindowFunctionNode,
12
10
  BinaryExpressionNode,
13
11
  ExpressionNode,
14
12
  LogicalExpressionNode,
@@ -146,11 +146,20 @@ const operandTypes = new Set<OperandNode['type']>([
146
146
  'WindowFunction'
147
147
  ]);
148
148
 
149
- export const isOperandNode = (node: any): node is OperandNode => node && operandTypes.has(node.type);
150
-
151
- export const isFunctionNode = (node: any): node is FunctionNode => node?.type === 'Function';
152
- export const isCaseExpressionNode = (node: any): node is CaseExpressionNode => node?.type === 'CaseExpression';
153
- export const isWindowFunctionNode = (node: any): node is WindowFunctionNode => node?.type === 'WindowFunction';
149
+ const hasTypeProperty = (value: unknown): value is { type?: string } =>
150
+ typeof value === 'object' && value !== null && 'type' in value;
151
+
152
+ export const isOperandNode = (node: unknown): node is OperandNode => {
153
+ if (!hasTypeProperty(node)) return false;
154
+ return operandTypes.has(node.type as OperandNode['type']);
155
+ };
156
+
157
+ export const isFunctionNode = (node: unknown): node is FunctionNode =>
158
+ isOperandNode(node) && node.type === 'Function';
159
+ export const isCaseExpressionNode = (node: unknown): node is CaseExpressionNode =>
160
+ isOperandNode(node) && node.type === 'CaseExpression';
161
+ export const isWindowFunctionNode = (node: unknown): node is WindowFunctionNode =>
162
+ isOperandNode(node) && node.type === 'WindowFunction';
154
163
  export const isExpressionSelectionNode = (
155
164
  node: ColumnRef | FunctionNode | CaseExpressionNode | WindowFunctionNode
156
165
  ): node is FunctionNode | CaseExpressionNode | WindowFunctionNode =>
@@ -47,8 +47,8 @@ export interface OperandVisitor<R> {
47
47
  otherwise?(node: OperandNode): R;
48
48
  }
49
49
 
50
- type ExpressionDispatch = <R>(node: any, visitor: ExpressionVisitor<R>) => R;
51
- type OperandDispatch = <R>(node: any, visitor: OperandVisitor<R>) => R;
50
+ type ExpressionDispatch = <R>(node: ExpressionNode, visitor: ExpressionVisitor<R>) => R;
51
+ type OperandDispatch = <R>(node: OperandNode, visitor: OperandVisitor<R>) => R;
52
52
 
53
53
  const expressionDispatchers = new Map<string, ExpressionDispatch>();
54
54
  const operandDispatchers = new Map<string, OperandDispatch>();
@@ -75,12 +75,15 @@ export const registerOperandDispatcher = (type: string, dispatcher: OperandDispa
75
75
  export const clearExpressionDispatchers = (): void => expressionDispatchers.clear();
76
76
  export const clearOperandDispatchers = (): void => operandDispatchers.clear();
77
77
 
78
+ const getNodeType = (node: { type?: string } | null | undefined): string | undefined =>
79
+ typeof node === 'object' && node !== null && typeof node.type === 'string' ? node.type : undefined;
80
+
78
81
  const unsupportedExpression = (node: ExpressionNode): never => {
79
- throw new Error(`Unsupported expression type "${(node as any)?.type ?? 'unknown'}"`);
82
+ throw new Error(`Unsupported expression type "${getNodeType(node) ?? 'unknown'}"`);
80
83
  };
81
84
 
82
85
  const unsupportedOperand = (node: OperandNode): never => {
83
- throw new Error(`Unsupported operand type "${(node as any)?.type ?? 'unknown'}"`);
86
+ throw new Error(`Unsupported operand type "${getNodeType(node) ?? 'unknown'}"`);
84
87
  };
85
88
  /**
86
89
  * Dispatches an expression node to the visitor
@@ -88,8 +91,8 @@ const unsupportedOperand = (node: OperandNode): never => {
88
91
  * @param visitor - Visitor implementation
89
92
  */
90
93
  export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisitor<R>): R => {
91
- const dynamic = expressionDispatchers.get((node as any)?.type);
92
- if (dynamic) return dynamic(node as any, visitor);
94
+ const dynamic = expressionDispatchers.get(node.type);
95
+ if (dynamic) return dynamic(node, visitor);
93
96
 
94
97
  switch (node.type) {
95
98
  case 'BinaryExpression':
@@ -126,8 +129,8 @@ export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisi
126
129
  * @param visitor - Visitor implementation
127
130
  */
128
131
  export const visitOperand = <R>(node: OperandNode, visitor: OperandVisitor<R>): R => {
129
- const dynamic = operandDispatchers.get((node as any)?.type);
130
- if (dynamic) return dynamic(node as any, visitor);
132
+ const dynamic = operandDispatchers.get(node.type);
133
+ if (dynamic) return dynamic(node, visitor);
131
134
 
132
135
  switch (node.type) {
133
136
  case 'Column':
@@ -1,7 +1,7 @@
1
- export * from './expression-nodes.js';
1
+ export * from './expression-nodes.js';
2
2
  export * from './expression-builders.js';
3
3
  export * from './window-functions.js';
4
4
  export * from './aggregate-functions.js';
5
5
  export * from './expression-visitor.js';
6
- export * from './types.js';
6
+ export type { ColumnRef, TableRef as AstTableRef } from './types.js';
7
7
  export * from './adapters.js';
@@ -2,7 +2,7 @@ import { JoinNode } from './join.js';
2
2
  import { ExpressionNode } from './expression.js';
3
3
  import { JoinKind } from '../sql/sql.js';
4
4
  import { JoinMetadata } from './join-metadata.js';
5
- import { TableSourceNode, TableNode, FunctionTableNode } from './query.js';
5
+ import { TableSourceNode, TableNode } from './query.js';
6
6
 
7
7
  /**
8
8
  * Creates a JoinNode ready for AST insertion.
@@ -1,12 +1,12 @@
1
1
  import {
2
+ AliasRefNode,
3
+ CaseExpressionNode,
2
4
  ColumnNode,
3
- FunctionNode,
4
5
  ExpressionNode,
5
- ScalarSubqueryNode,
6
- CaseExpressionNode,
7
- WindowFunctionNode,
6
+ FunctionNode,
8
7
  OperandNode,
9
- AliasRefNode
8
+ ScalarSubqueryNode,
9
+ WindowFunctionNode
10
10
  } from './expression.js';
11
11
  import { JoinNode } from './join.js';
12
12
  import { OrderDirection } from '../sql/sql.js';
@@ -34,7 +34,7 @@ export interface FunctionTableNode {
34
34
  /** Optional schema for the function (some dialects) */
35
35
  schema?: string;
36
36
  /** Function arguments as operand nodes */
37
- args?: any[]; // use any to avoid circular import here; caller should supply OperandNode
37
+ args?: OperandNode[];
38
38
  /** Optional alias for the function table */
39
39
  alias?: string;
40
40
  /** LATERAL flag */
@@ -60,7 +60,11 @@ export const ntile = (n: number): WindowFunctionNode =>
60
60
  * @param defaultValue - Default value if no row exists
61
61
  * @returns Window function node for LAG
62
62
  */
63
- export const lag = (col: ColumnRef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
63
+ export const lag = (
64
+ col: ColumnRef | ColumnNode,
65
+ offset: number = 1,
66
+ defaultValue?: LiteralNode['value']
67
+ ): WindowFunctionNode => {
64
68
  const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [
65
69
  columnOperand(col),
66
70
  { type: 'Literal', value: offset }
@@ -78,7 +82,11 @@ export const lag = (col: ColumnRef | ColumnNode, offset: number = 1, defaultValu
78
82
  * @param defaultValue - Default value if no row exists
79
83
  * @returns Window function node for LEAD
80
84
  */
81
- export const lead = (col: ColumnRef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
85
+ export const lead = (
86
+ col: ColumnRef | ColumnNode,
87
+ offset: number = 1,
88
+ defaultValue?: LiteralNode['value']
89
+ ): WindowFunctionNode => {
82
90
  const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [
83
91
  columnOperand(col),
84
92
  { type: 'Literal', value: offset }
@@ -1,5 +1,5 @@
1
1
  import { SchemaDialect, DialectName } from '../schema-dialect.js';
2
- import { formatLiteral, quoteQualified, createLiteralFormatter, LiteralFormatter } from '../sql-writing.js';
2
+ import { formatLiteral, quoteQualified, LiteralFormatter } from '../sql-writing.js';
3
3
  import { ColumnDef, ForeignKeyReference } from '../../../schema/column.js';
4
4
  import { IndexDef, TableDef } from '../../../schema/table.js';
5
5
  import { DatabaseTable, DatabaseColumn, ColumnDiff } from '../schema-types.js';
@@ -29,16 +29,26 @@ export abstract class BaseSchemaDialect implements SchemaDialect {
29
29
  abstract get literalFormatter(): LiteralFormatter;
30
30
 
31
31
  renderDefault(value: unknown, _column: ColumnDef): string {
32
+ void _column;
32
33
  return formatLiteral(this.literalFormatter, value);
33
34
  }
34
35
  renderReference(ref: ForeignKeyReference, _table: TableDef): string {
36
+ void _table;
35
37
  const parts = ['REFERENCES', quoteQualified(this, ref.table), `(${this.quoteIdentifier(ref.column)})`];
36
38
  if (ref.onDelete) parts.push('ON DELETE', ref.onDelete);
37
39
  if (ref.onUpdate) parts.push('ON UPDATE', ref.onUpdate);
38
- if (ref.deferrable && this.name === 'postgres') parts.push('DEFERRABLE INITIALLY DEFERRED');
40
+ const suffix = this.renderReferenceSuffix(ref, _table);
41
+ if (suffix) parts.push(suffix);
39
42
  return parts.join(' ');
40
43
  }
44
+
45
+ protected renderReferenceSuffix(ref: ForeignKeyReference, _table: TableDef): string | undefined {
46
+ void ref;
47
+ void _table;
48
+ return undefined;
49
+ }
41
50
  renderTableOptions(_table: TableDef): string | undefined {
51
+ void _table;
42
52
  return undefined;
43
53
  }
44
54
  dropTableSql(table: DatabaseTable): string[] {
@@ -51,12 +61,29 @@ export abstract class BaseSchemaDialect implements SchemaDialect {
51
61
  return [`DROP INDEX ${this.quoteIdentifier(index)};`];
52
62
  }
53
63
  warnDropColumn(_table: DatabaseTable, _column: string): string | undefined {
64
+ void _table;
65
+ void _column;
54
66
  return undefined;
55
67
  }
56
- alterColumnSql?(table: TableDef, column: ColumnDef, actualColumn: DatabaseColumn, diff: ColumnDiff): string[] {
68
+ alterColumnSql?(_table: TableDef, _column: ColumnDef, _actualColumn: DatabaseColumn, _diff: ColumnDiff): string[] {
69
+ void _table;
70
+ void _column;
71
+ void _actualColumn;
72
+ void _diff;
57
73
  return [];
58
74
  }
59
75
  warnAlterColumn?(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string | undefined {
76
+ void _table;
77
+ void _column;
78
+ void _actual;
79
+ void _diff;
60
80
  return undefined;
61
81
  }
82
+
83
+ preferInlinePkAutoincrement(_column: ColumnDef, _table: TableDef, _pk: string[]): boolean {
84
+ void _column;
85
+ void _table;
86
+ void _pk;
87
+ return false;
88
+ }
62
89
  }
@@ -120,6 +120,7 @@ export class MSSqlSchemaDialect extends BaseSchemaDialect {
120
120
  }
121
121
 
122
122
  alterColumnSql(table: TableDef, column: ColumnDef, _actual: DatabaseColumn, diff: ColumnDiff): string[] {
123
+ void _actual;
123
124
  const stmts: string[] = [];
124
125
  if (diff.typeChanged || diff.nullabilityChanged) {
125
126
  const nullability = column.notNull ? 'NOT NULL' : 'NULL';
@@ -131,6 +132,9 @@ export class MSSqlSchemaDialect extends BaseSchemaDialect {
131
132
  }
132
133
 
133
134
  warnAlterColumn(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, diff: ColumnDiff): string | undefined {
135
+ void _table;
136
+ void _column;
137
+ void _actual;
134
138
  if (diff.defaultChanged || diff.autoIncrementChanged) {
135
139
  return 'Altering defaults or identity on MSSQL is not automated (requires dropping/adding default or identity constraints manually).';
136
140
  }
@@ -125,6 +125,8 @@ export class MySqlSchemaDialect extends BaseSchemaDialect {
125
125
  }
126
126
 
127
127
  alterColumnSql(table: TableDef, column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string[] {
128
+ void _actual;
129
+ void _diff;
128
130
  const rendered = renderColumnDefinition(table, column, this);
129
131
  return [`ALTER TABLE ${this.formatTableName(table)} MODIFY COLUMN ${rendered.sql};`];
130
132
  }
@@ -1,7 +1,7 @@
1
1
  import { BaseSchemaDialect } from './base-schema-dialect.js';
2
2
  import { deriveIndexName } from '../naming-strategy.js';
3
3
  import { renderIndexColumns, createLiteralFormatter } from '../sql-writing.js';
4
- import { ColumnDef } from '../../../schema/column.js';
4
+ import { ColumnDef, ForeignKeyReference } from '../../../schema/column.js';
5
5
  import { IndexDef, TableDef } from '../../../schema/table.js';
6
6
  import { ColumnDiff, DatabaseColumn, DatabaseTable } from '../schema-types.js';
7
7
  import { DialectName } from '../schema-dialect.js';
@@ -102,6 +102,14 @@ export class PostgresSchemaDialect extends BaseSchemaDialect {
102
102
  return true;
103
103
  }
104
104
 
105
+ protected renderReferenceSuffix(ref: ForeignKeyReference, _table: TableDef): string | undefined {
106
+ void _table;
107
+ if (ref.deferrable) {
108
+ return 'DEFERRABLE INITIALLY DEFERRED';
109
+ }
110
+ return undefined;
111
+ }
112
+
105
113
  dropColumnSql(table: DatabaseTable, column: string): string[] {
106
114
  return [`ALTER TABLE ${this.formatTableName(table)} DROP COLUMN ${this.quoteIdentifier(column)};`];
107
115
  }
@@ -114,6 +122,7 @@ export class PostgresSchemaDialect extends BaseSchemaDialect {
114
122
  }
115
123
 
116
124
  alterColumnSql(table: TableDef, column: ColumnDef, actualColumn: DatabaseColumn, diff: ColumnDiff): string[] {
125
+ void actualColumn;
117
126
  const stmts: string[] = [];
118
127
  const tableName = this.formatTableName(table);
119
128
  const colName = this.quoteIdentifier(column.name);
@@ -145,6 +154,9 @@ export class PostgresSchemaDialect extends BaseSchemaDialect {
145
154
  }
146
155
 
147
156
  warnAlterColumn(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, diff: ColumnDiff): string | undefined {
157
+ void _table;
158
+ void _column;
159
+ void _actual;
148
160
  if (diff.autoIncrementChanged) {
149
161
  return 'Altering identity properties may fail if an existing sequence is attached; verify generated column state.';
150
162
  }
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { BaseSchemaDialect } from './base-schema-dialect.js';
3
+ import { PostgresSchemaDialect } from './postgres-schema-dialect.js';
4
+ import { TableDef } from '../../../schema/table.js';
5
+ import { ForeignKeyReference } from '../../../schema/column.js';
6
+ import { createLiteralFormatter } from '../sql-writing.js';
7
+
8
+ class DummySchemaDialect extends BaseSchemaDialect {
9
+ readonly name = 'sqlite';
10
+ private readonly formatter = createLiteralFormatter({
11
+ booleanTrue: '1',
12
+ booleanFalse: '0',
13
+ });
14
+
15
+ get literalFormatter() {
16
+ return this.formatter;
17
+ }
18
+
19
+ quoteIdentifier(id: string): string {
20
+ return `"${id}"`;
21
+ }
22
+
23
+ renderColumnType(): string {
24
+ return 'INTEGER';
25
+ }
26
+
27
+ renderAutoIncrement(): string | undefined {
28
+ return undefined;
29
+ }
30
+
31
+ renderIndex(): string {
32
+ return 'CREATE INDEX dummy;';
33
+ }
34
+ }
35
+
36
+ const table: TableDef = {
37
+ name: 'child',
38
+ columns: {},
39
+ relations: {},
40
+ };
41
+
42
+ const deferrableReference: ForeignKeyReference = {
43
+ table: 'parent',
44
+ column: 'id',
45
+ deferrable: true,
46
+ onDelete: 'CASCADE',
47
+ onUpdate: 'NO ACTION',
48
+ };
49
+
50
+ describe('renderReference deferrable handling', () => {
51
+ it('base dialect remains agnostic to deferrable flags', () => {
52
+ const dialect = new DummySchemaDialect();
53
+ const sql = dialect.renderReference(deferrableReference, table);
54
+ expect(sql).toContain('REFERENCES "parent"');
55
+ expect(sql).not.toContain('DEFERRABLE INITIALLY DEFERRED');
56
+ });
57
+
58
+ it('Postgres dialect renders the deferrable clause', () => {
59
+ const dialect = new PostgresSchemaDialect();
60
+ const sql = dialect.renderReference(deferrableReference, table);
61
+ expect(sql).toContain('DEFERRABLE INITIALLY DEFERRED');
62
+ });
63
+
64
+ it('Postgres dialect skips the clause when the flag is missing', () => {
65
+ const dialect = new PostgresSchemaDialect();
66
+ const sql = dialect.renderReference({ table: 'parent', column: 'id' }, table);
67
+ expect(sql).not.toContain('DEFERRABLE INITIALLY DEFERRED');
68
+ });
69
+ });
@@ -103,10 +103,13 @@ export class SQLiteSchemaDialect extends BaseSchemaDialect {
103
103
  }
104
104
 
105
105
  dropColumnSql(_table: DatabaseTable, _column: string): string[] {
106
+ void _table;
107
+ void _column;
106
108
  return [];
107
109
  }
108
110
 
109
111
  dropIndexSql(_table: DatabaseTable, index: string): string[] {
112
+ void _table;
110
113
  return [`DROP INDEX IF EXISTS ${this.quoteIdentifier(index)};`];
111
114
  }
112
115
 
@@ -116,10 +119,16 @@ export class SQLiteSchemaDialect extends BaseSchemaDialect {
116
119
  }
117
120
 
118
121
  alterColumnSql(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string[] {
122
+ void _table;
123
+ void _column;
124
+ void _actual;
125
+ void _diff;
119
126
  return [];
120
127
  }
121
128
 
122
129
  warnAlterColumn(table: TableDef, column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string | undefined {
130
+ void _actual;
131
+ void _diff;
123
132
  const key = table.schema ? `${table.schema}.${table.name}` : table.name;
124
133
  return `SQLite ALTER COLUMN is not supported; rebuild table ${key} to change column ${column.name}.`;
125
134
  }