metal-orm 1.0.2 → 1.0.4

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 (37) hide show
  1. package/README.md +20 -0
  2. package/package.json +1 -1
  3. package/src/ast/expression.ts +433 -175
  4. package/src/ast/join.ts +8 -1
  5. package/src/ast/query.ts +64 -9
  6. package/src/builder/hydration-manager.ts +42 -11
  7. package/src/builder/hydration-planner.ts +80 -31
  8. package/src/builder/operations/column-selector.ts +37 -1
  9. package/src/builder/operations/cte-manager.ts +16 -0
  10. package/src/builder/operations/filter-manager.ts +32 -0
  11. package/src/builder/operations/join-manager.ts +17 -7
  12. package/src/builder/operations/pagination-manager.ts +19 -0
  13. package/src/builder/operations/relation-manager.ts +58 -3
  14. package/src/builder/query-ast-service.ts +100 -29
  15. package/src/builder/relation-conditions.ts +30 -1
  16. package/src/builder/relation-projection-helper.ts +43 -1
  17. package/src/builder/relation-service.ts +68 -13
  18. package/src/builder/relation-types.ts +6 -0
  19. package/src/builder/select-query-builder-deps.ts +64 -3
  20. package/src/builder/select-query-state.ts +72 -0
  21. package/src/builder/select.ts +166 -0
  22. package/src/codegen/typescript.ts +142 -44
  23. package/src/constants/sql-operator-config.ts +36 -0
  24. package/src/constants/sql.ts +125 -58
  25. package/src/dialect/abstract.ts +97 -22
  26. package/src/dialect/mssql/index.ts +27 -0
  27. package/src/dialect/mysql/index.ts +22 -0
  28. package/src/dialect/postgres/index.ts +22 -0
  29. package/src/dialect/sqlite/index.ts +22 -0
  30. package/src/runtime/als.ts +15 -1
  31. package/src/runtime/hydration.ts +20 -15
  32. package/src/schema/column.ts +45 -5
  33. package/src/schema/relation.ts +49 -2
  34. package/src/schema/table.ts +27 -3
  35. package/src/utils/join-node.ts +20 -0
  36. package/src/utils/raw-column-parser.ts +32 -0
  37. package/src/utils/relation-alias.ts +43 -0
@@ -17,17 +17,35 @@ import {
17
17
  BetweenExpressionNode
18
18
  } from '../ast/expression';
19
19
 
20
+ /**
21
+ * Context for SQL compilation with parameter management
22
+ */
20
23
  export interface CompilerContext {
24
+ /** Array of parameters */
21
25
  params: unknown[];
26
+ /** Function to add a parameter and get its placeholder */
22
27
  addParameter(value: unknown): string;
23
28
  }
24
29
 
30
+ /**
31
+ * Result of SQL compilation
32
+ */
25
33
  export interface CompiledQuery {
34
+ /** Generated SQL string */
26
35
  sql: string;
36
+ /** Parameters for the query */
27
37
  params: unknown[];
28
38
  }
29
39
 
40
+ /**
41
+ * Abstract base class for SQL dialect implementations
42
+ */
30
43
  export abstract class Dialect {
44
+ /**
45
+ * Compiles a SELECT query AST to SQL
46
+ * @param ast - Query AST to compile
47
+ * @returns Compiled query with SQL and parameters
48
+ */
31
49
  compileSelect(ast: SelectQueryNode): CompiledQuery {
32
50
  const ctx = this.createCompilerContext();
33
51
  const rawSql = this.compileSelectAst(ast, ctx).trim();
@@ -38,20 +56,40 @@ export abstract class Dialect {
38
56
  };
39
57
  }
40
58
 
59
+ /**
60
+ * Compiles SELECT query AST to SQL (to be implemented by concrete dialects)
61
+ * @param ast - Query AST
62
+ * @param ctx - Compiler context
63
+ * @returns SQL string
64
+ */
41
65
  protected abstract compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string;
42
66
 
67
+ /**
68
+ * Quotes an SQL identifier (to be implemented by concrete dialects)
69
+ * @param id - Identifier to quote
70
+ * @returns Quoted identifier
71
+ */
43
72
  abstract quoteIdentifier(id: string): string;
44
73
 
74
+ /**
75
+ * Compiles a WHERE clause
76
+ * @param where - WHERE expression
77
+ * @param ctx - Compiler context
78
+ * @returns SQL WHERE clause or empty string
79
+ */
45
80
  protected compileWhere(where: ExpressionNode | undefined, ctx: CompilerContext): string {
46
81
  if (!where) return '';
47
82
  return ` WHERE ${this.compileExpression(where, ctx)}`;
48
83
  }
49
84
 
50
85
  /**
51
- * Gera a subquery para EXISTS.
52
- * Regra: sempre força SELECT 1, ignorando a lista de colunas.
53
- * Mantém FROM, JOINs, WHERE, GROUP BY, ORDER BY, LIMIT/OFFSET.
54
- * Não adiciona ';' no final.
86
+ * Generates subquery for EXISTS expressions
87
+ * Rule: Always forces SELECT 1, ignoring column list
88
+ * Maintains FROM, JOINs, WHERE, GROUP BY, ORDER BY, LIMIT/OFFSET
89
+ * Does not add ';' at the end
90
+ * @param ast - Query AST
91
+ * @param ctx - Compiler context
92
+ * @returns SQL for EXISTS subquery
55
93
  */
56
94
  protected compileSelectForExists(ast: SelectQueryNode, ctx: CompilerContext): string {
57
95
  const full = this.compileSelectAst(ast, ctx).trim().replace(/;$/, '');
@@ -65,6 +103,10 @@ export abstract class Dialect {
65
103
  return `SELECT 1${tail}`;
66
104
  }
67
105
 
106
+ /**
107
+ * Creates a new compiler context
108
+ * @returns Compiler context with parameter management
109
+ */
68
110
  protected createCompilerContext(): CompilerContext {
69
111
  const params: unknown[] = [];
70
112
  let counter = 0;
@@ -78,6 +120,11 @@ export abstract class Dialect {
78
120
  };
79
121
  }
80
122
 
123
+ /**
124
+ * Formats a parameter placeholder
125
+ * @param index - Parameter index
126
+ * @returns Formatted placeholder string
127
+ */
81
128
  protected formatPlaceholder(index: number): string {
82
129
  return '?';
83
130
  }
@@ -92,35 +139,63 @@ export abstract class Dialect {
92
139
  this.registerDefaultExpressionCompilers();
93
140
  }
94
141
 
142
+ /**
143
+ * Registers an expression compiler for a specific node type
144
+ * @param type - Expression node type
145
+ * @param compiler - Compiler function
146
+ */
95
147
  protected registerExpressionCompiler<T extends ExpressionNode>(type: T['type'], compiler: (node: T, ctx: CompilerContext) => string): void {
96
148
  this.expressionCompilers.set(type, compiler as (node: ExpressionNode, ctx: CompilerContext) => string);
97
149
  }
98
150
 
151
+ /**
152
+ * Registers an operand compiler for a specific node type
153
+ * @param type - Operand node type
154
+ * @param compiler - Compiler function
155
+ */
99
156
  protected registerOperandCompiler<T extends OperandNode>(type: T['type'], compiler: (node: T, ctx: CompilerContext) => string): void {
100
157
  this.operandCompilers.set(type, compiler as (node: OperandNode, ctx: CompilerContext) => string);
101
158
  }
102
159
 
103
- protected compileExpression(node: ExpressionNode, ctx: CompilerContext): string {
104
- const compiler = this.expressionCompilers.get(node.type);
105
- return compiler ? compiler(node, ctx) : '';
106
- }
160
+ /**
161
+ * Compiles an expression node
162
+ * @param node - Expression node to compile
163
+ * @param ctx - Compiler context
164
+ * @returns Compiled SQL expression
165
+ */
166
+ protected compileExpression(node: ExpressionNode, ctx: CompilerContext): string {
167
+ const compiler = this.expressionCompilers.get(node.type);
168
+ if (!compiler) {
169
+ throw new Error(`Unsupported expression node type "${node.type}" for ${this.constructor.name}`);
170
+ }
171
+ return compiler(node, ctx);
172
+ }
107
173
 
108
- protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
109
- const compiler = this.operandCompilers.get(node.type);
110
- return compiler ? compiler(node, ctx) : '';
111
- }
174
+ /**
175
+ * Compiles an operand node
176
+ * @param node - Operand node to compile
177
+ * @param ctx - Compiler context
178
+ * @returns Compiled SQL operand
179
+ */
180
+ protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
181
+ const compiler = this.operandCompilers.get(node.type);
182
+ if (!compiler) {
183
+ throw new Error(`Unsupported operand node type "${node.type}" for ${this.constructor.name}`);
184
+ }
185
+ return compiler(node, ctx);
186
+ }
112
187
 
113
188
  private registerDefaultExpressionCompilers(): void {
114
- this.registerExpressionCompiler('BinaryExpression', (binary: BinaryExpressionNode, ctx) => {
115
- const left = this.compileOperand(binary.left, ctx);
116
- const right = this.compileOperand(binary.right, ctx);
117
- const base = `${left} ${binary.operator} ${right}`;
118
- if (binary.escape) {
119
- const escapeOperand = this.compileOperand(binary.escape, ctx);
120
- return `${base} ESCAPE ${escapeOperand}`;
121
- }
122
- return base;
123
- });
189
+ this.registerExpressionCompiler('BinaryExpression', (binary: BinaryExpressionNode, ctx) => {
190
+ const left = this.compileOperand(binary.left, ctx);
191
+ const right = this.compileOperand(binary.right, ctx);
192
+ const base = `${left} ${binary.operator} ${right}`;
193
+ if (binary.escape) {
194
+ const escapeOperand = this.compileOperand(binary.escape, ctx);
195
+ return `${base} ESCAPE ${escapeOperand}`;
196
+ }
197
+ return base;
198
+ });
124
199
 
125
200
  this.registerExpressionCompiler('LogicalExpression', (logical: LogicalExpressionNode, ctx) => {
126
201
  if (logical.operands.length === 0) return '';
@@ -2,25 +2,52 @@ import { CompilerContext, Dialect } from '../abstract';
2
2
  import { SelectQueryNode } from '../../ast/query';
3
3
  import { JsonPathNode } from '../../ast/expression';
4
4
 
5
+ /**
6
+ * Microsoft SQL Server dialect implementation
7
+ */
5
8
  export class SqlServerDialect extends Dialect {
9
+ /**
10
+ * Creates a new SqlServerDialect instance
11
+ */
6
12
  public constructor() {
7
13
  super();
8
14
  }
9
15
 
16
+ /**
17
+ * Quotes an identifier using SQL Server bracket syntax
18
+ * @param id - Identifier to quote
19
+ * @returns Quoted identifier
20
+ */
10
21
  quoteIdentifier(id: string): string {
11
22
  return `[${id}]`;
12
23
  }
13
24
 
25
+ /**
26
+ * Compiles JSON path expression using SQL Server syntax
27
+ * @param node - JSON path node
28
+ * @returns SQL Server JSON path expression
29
+ */
14
30
  protected compileJsonPath(node: JsonPathNode): string {
15
31
  const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
16
32
  // SQL Server uses JSON_VALUE(col, '$.path')
17
33
  return `JSON_VALUE(${col}, '${node.path}')`;
18
34
  }
19
35
 
36
+ /**
37
+ * Formats parameter placeholders using SQL Server named parameter syntax
38
+ * @param index - Parameter index
39
+ * @returns Named parameter placeholder
40
+ */
20
41
  protected formatPlaceholder(index: number): string {
21
42
  return `@p${index}`;
22
43
  }
23
44
 
45
+ /**
46
+ * Compiles SELECT query AST to SQL Server SQL
47
+ * @param ast - Query AST
48
+ * @param ctx - Compiler context
49
+ * @returns SQL Server SQL string
50
+ */
24
51
  protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
25
52
  const columns = ast.columns.map(c => {
26
53
  let expr = '';
@@ -2,21 +2,43 @@ import { CompilerContext, Dialect } from '../abstract';
2
2
  import { SelectQueryNode } from '../../ast/query';
3
3
  import { JsonPathNode } from '../../ast/expression';
4
4
 
5
+ /**
6
+ * MySQL dialect implementation
7
+ */
5
8
  export class MySqlDialect extends Dialect {
9
+ /**
10
+ * Creates a new MySqlDialect instance
11
+ */
6
12
  public constructor() {
7
13
  super();
8
14
  }
9
15
 
16
+ /**
17
+ * Quotes an identifier using MySQL backtick syntax
18
+ * @param id - Identifier to quote
19
+ * @returns Quoted identifier
20
+ */
10
21
  quoteIdentifier(id: string): string {
11
22
  return `\`${id}\``;
12
23
  }
13
24
 
25
+ /**
26
+ * Compiles JSON path expression using MySQL syntax
27
+ * @param node - JSON path node
28
+ * @returns MySQL JSON path expression
29
+ */
14
30
  protected compileJsonPath(node: JsonPathNode): string {
15
31
  const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
16
32
  // MySQL 5.7+ uses col->'$.path'
17
33
  return `${col}->'${node.path}'`;
18
34
  }
19
35
 
36
+ /**
37
+ * Compiles SELECT query AST to MySQL SQL
38
+ * @param ast - Query AST
39
+ * @param ctx - Compiler context
40
+ * @returns MySQL SQL string
41
+ */
20
42
  protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
21
43
  const columns = ast.columns.map(c => {
22
44
  let expr = '';
@@ -2,21 +2,43 @@ import { CompilerContext, Dialect } from '../abstract';
2
2
  import { SelectQueryNode } from '../../ast/query';
3
3
  import { JsonPathNode } from '../../ast/expression';
4
4
 
5
+ /**
6
+ * PostgreSQL dialect implementation
7
+ */
5
8
  export class PostgresDialect extends Dialect {
9
+ /**
10
+ * Creates a new PostgresDialect instance
11
+ */
6
12
  public constructor() {
7
13
  super();
8
14
  }
9
15
 
16
+ /**
17
+ * Quotes an identifier using PostgreSQL double-quote syntax
18
+ * @param id - Identifier to quote
19
+ * @returns Quoted identifier
20
+ */
10
21
  quoteIdentifier(id: string): string {
11
22
  return `"${id}"`;
12
23
  }
13
24
 
25
+ /**
26
+ * Compiles JSON path expression using PostgreSQL syntax
27
+ * @param node - JSON path node
28
+ * @returns PostgreSQL JSON path expression
29
+ */
14
30
  protected compileJsonPath(node: JsonPathNode): string {
15
31
  const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
16
32
  // Postgres uses col->>'path' for text extraction
17
33
  return `${col}->>'${node.path}'`;
18
34
  }
19
35
 
36
+ /**
37
+ * Compiles SELECT query AST to PostgreSQL SQL
38
+ * @param ast - Query AST
39
+ * @param ctx - Compiler context
40
+ * @returns PostgreSQL SQL string
41
+ */
20
42
  protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
21
43
  const columns = ast.columns.map(c => {
22
44
  let expr = '';
@@ -2,21 +2,43 @@ import { CompilerContext, Dialect } from '../abstract';
2
2
  import { SelectQueryNode } from '../../ast/query';
3
3
  import { JsonPathNode } from '../../ast/expression';
4
4
 
5
+ /**
6
+ * SQLite dialect implementation
7
+ */
5
8
  export class SqliteDialect extends Dialect {
9
+ /**
10
+ * Creates a new SqliteDialect instance
11
+ */
6
12
  public constructor() {
7
13
  super();
8
14
  }
9
15
 
16
+ /**
17
+ * Quotes an identifier using SQLite double-quote syntax
18
+ * @param id - Identifier to quote
19
+ * @returns Quoted identifier
20
+ */
10
21
  quoteIdentifier(id: string): string {
11
22
  return `"${id}"`;
12
23
  }
13
24
 
25
+ /**
26
+ * Compiles JSON path expression using SQLite syntax
27
+ * @param node - JSON path node
28
+ * @returns SQLite JSON path expression
29
+ */
14
30
  protected compileJsonPath(node: JsonPathNode): string {
15
31
  const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
16
32
  // SQLite uses json_extract(col, '$.path')
17
33
  return `json_extract(${col}, '${node.path}')`;
18
34
  }
19
35
 
36
+ /**
37
+ * Compiles SELECT query AST to SQLite SQL
38
+ * @param ast - Query AST
39
+ * @param ctx - Compiler context
40
+ * @returns SQLite SQL string
41
+ */
20
42
  protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
21
43
  const columns = ast.columns.map(c => {
22
44
  let expr = '';
@@ -1,9 +1,19 @@
1
1
  // In a real Node environment: import { AsyncLocalStorage } from 'node:async_hooks';
2
2
 
3
- // Browser Shim
3
+ /**
4
+ * Browser-compatible implementation of AsyncLocalStorage
5
+ * Provides a simple in-memory store for browser environments
6
+ * @typeParam T - Type of the stored data
7
+ */
4
8
  export class AsyncLocalStorage<T> {
5
9
  private store: T | undefined;
6
10
 
11
+ /**
12
+ * Executes a callback with the specified store value
13
+ * @param store - Value to store during callback execution
14
+ * @param callback - Function to execute with the store value
15
+ * @returns Result of the callback function
16
+ */
7
17
  run<R>(store: T, callback: () => R): R {
8
18
  this.store = store;
9
19
  try {
@@ -13,6 +23,10 @@ export class AsyncLocalStorage<T> {
13
23
  }
14
24
  }
15
25
 
26
+ /**
27
+ * Gets the currently stored value
28
+ * @returns Current store value or undefined if none exists
29
+ */
16
30
  getStore(): T | undefined {
17
31
  return this.store;
18
32
  }
@@ -1,7 +1,12 @@
1
- import { HydrationPlan } from '../ast/query';
2
-
3
- const isRelationKey = (key: string) => key.includes('__');
4
-
1
+ import { HydrationPlan } from '../ast/query';
2
+ import { isRelationAlias, makeRelationAlias } from '../utils/relation-alias';
3
+
4
+ /**
5
+ * Hydrates query results according to a hydration plan
6
+ * @param rows - Raw database rows
7
+ * @param plan - Hydration plan
8
+ * @returns Hydrated result objects with nested relations
9
+ */
5
10
  export const hydrateRows = (rows: Record<string, any>[], plan?: HydrationPlan): Record<string, any>[] => {
6
11
  if (!plan || !rows.length) return rows;
7
12
 
@@ -12,28 +17,28 @@ export const hydrateRows = (rows: Record<string, any>[], plan?: HydrationPlan):
12
17
  if (rootId === undefined) return;
13
18
 
14
19
  if (!rootMap.has(rootId)) {
15
- const base: Record<string, any> = {};
16
- const baseKeys = plan.rootColumns.length ? plan.rootColumns : Object.keys(row).filter(k => !isRelationKey(k));
17
- baseKeys.forEach(key => { base[key] = row[key]; });
18
- plan.relations.forEach(rel => { base[rel.name] = []; });
19
- rootMap.set(rootId, base);
20
+ const base: Record<string, any> = {};
21
+ const baseKeys = plan.rootColumns.length ? plan.rootColumns : Object.keys(row).filter(k => !isRelationAlias(k));
22
+ baseKeys.forEach(key => { base[key] = row[key]; });
23
+ plan.relations.forEach(rel => { base[rel.name] = []; });
24
+ rootMap.set(rootId, base);
20
25
  }
21
26
 
22
27
  const parent = rootMap.get(rootId);
23
28
 
24
29
  plan.relations.forEach(rel => {
25
- const childPkKey = `${rel.aliasPrefix}__${rel.targetPrimaryKey}`;
26
- const childPk = row[childPkKey];
30
+ const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
31
+ const childPk = row[childPkKey];
27
32
  if (childPk === null || childPk === undefined) return;
28
33
 
29
34
  const bucket = parent[rel.name] as any[];
30
35
  if (bucket.some(item => item[rel.targetPrimaryKey] === childPk)) return;
31
36
 
32
37
  const child: Record<string, any> = {};
33
- rel.columns.forEach(col => {
34
- const key = `${rel.aliasPrefix}__${col}`;
35
- child[col] = row[key];
36
- });
38
+ rel.columns.forEach(col => {
39
+ const key = makeRelationAlias(rel.aliasPrefix, col);
40
+ child[col] = row[key];
41
+ });
37
42
 
38
43
  bucket.push(child);
39
44
  });
@@ -1,19 +1,59 @@
1
+ /**
2
+ * Supported column data types for database schema definitions
3
+ */
1
4
  export type ColumnType = 'INT' | 'VARCHAR' | 'JSON' | 'ENUM' | 'BOOLEAN';
2
5
 
6
+ /**
7
+ * Definition of a database column
8
+ */
3
9
  export interface ColumnDef {
4
- name: string; // filled at runtime by defineTable
10
+ /** Column name (filled at runtime by defineTable) */
11
+ name: string;
12
+ /** Data type of the column */
5
13
  type: ColumnType;
14
+ /** Whether this column is a primary key */
6
15
  primary?: boolean;
16
+ /** Whether this column cannot be null */
7
17
  notNull?: boolean;
8
- args?: any[]; // for varchar length etc
9
- table?: string; // Filled at runtime by defineTable
18
+ /** Additional arguments for the column type (e.g., VARCHAR length) */
19
+ args?: any[];
20
+ /** Table name this column belongs to (filled at runtime by defineTable) */
21
+ table?: string;
10
22
  }
11
23
 
12
- // Column Factory
24
+ /**
25
+ * Factory for creating column definitions with common data types
26
+ */
13
27
  export const col = {
28
+ /**
29
+ * Creates an integer column definition
30
+ * @returns ColumnDef with INT type
31
+ */
14
32
  int: (): ColumnDef => ({ name: '', type: 'INT' }),
33
+
34
+ /**
35
+ * Creates a variable character column definition
36
+ * @param length - Maximum length of the string
37
+ * @returns ColumnDef with VARCHAR type
38
+ */
15
39
  varchar: (length: number): ColumnDef => ({ name: '', type: 'VARCHAR', args: [length] }),
40
+
41
+ /**
42
+ * Creates a JSON column definition
43
+ * @returns ColumnDef with JSON type
44
+ */
16
45
  json: (): ColumnDef => ({ name: '', type: 'JSON' }),
46
+
47
+ /**
48
+ * Creates a boolean column definition
49
+ * @returns ColumnDef with BOOLEAN type
50
+ */
17
51
  boolean: (): ColumnDef => ({ name: '', type: 'BOOLEAN' }),
52
+
53
+ /**
54
+ * Marks a column definition as a primary key
55
+ * @param def - Column definition to modify
56
+ * @returns Modified ColumnDef with primary: true
57
+ */
18
58
  primaryKey: (def: ColumnDef): ColumnDef => ({ ...def, primary: true })
19
- };
59
+ };
@@ -1,28 +1,63 @@
1
1
  import { TableDef } from './table';
2
2
 
3
+ /**
4
+ * Types of relationships supported between tables
5
+ */
3
6
  export const RelationKinds = {
7
+ /** One-to-many relationship */
4
8
  HasMany: 'HAS_MANY',
9
+ /** Many-to-one relationship */
5
10
  BelongsTo: 'BELONGS_TO',
6
11
  } as const;
7
12
 
13
+ /**
14
+ * Type representing the supported relationship kinds
15
+ */
8
16
  export type RelationType = (typeof RelationKinds)[keyof typeof RelationKinds];
9
17
 
18
+ /**
19
+ * Base properties common to all relationship types
20
+ */
10
21
  interface BaseRelation {
22
+ /** Target table of the relationship */
11
23
  target: TableDef;
12
- foreignKey: string; // The column on the child table
13
- localKey?: string; // Usually 'id'
24
+ /** Foreign key column name on the child table */
25
+ foreignKey: string;
26
+ /** Local key column name (usually 'id') */
27
+ localKey?: string;
14
28
  }
15
29
 
30
+ /**
31
+ * One-to-many relationship definition
32
+ */
16
33
  export interface HasManyRelation extends BaseRelation {
17
34
  type: typeof RelationKinds.HasMany;
18
35
  }
19
36
 
37
+ /**
38
+ * Many-to-one relationship definition
39
+ */
20
40
  export interface BelongsToRelation extends BaseRelation {
21
41
  type: typeof RelationKinds.BelongsTo;
22
42
  }
23
43
 
44
+ /**
45
+ * Union type representing any supported relationship definition
46
+ */
24
47
  export type RelationDef = HasManyRelation | BelongsToRelation;
25
48
 
49
+ /**
50
+ * Creates a one-to-many relationship definition
51
+ * @param target - Target table of the relationship
52
+ * @param foreignKey - Foreign key column name on the child table
53
+ * @param localKey - Local key column name (defaults to 'id')
54
+ * @returns HasManyRelation definition
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * hasMany(usersTable, 'user_id')
59
+ * ```
60
+ */
26
61
  export const hasMany = (target: TableDef, foreignKey: string, localKey: string = 'id'): HasManyRelation => ({
27
62
  type: RelationKinds.HasMany,
28
63
  target,
@@ -30,6 +65,18 @@ export const hasMany = (target: TableDef, foreignKey: string, localKey: string =
30
65
  localKey,
31
66
  });
32
67
 
68
+ /**
69
+ * Creates a many-to-one relationship definition
70
+ * @param target - Target table of the relationship
71
+ * @param foreignKey - Foreign key column name on the child table
72
+ * @param localKey - Local key column name (defaults to 'id')
73
+ * @returns BelongsToRelation definition
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * belongsTo(usersTable, 'user_id')
78
+ * ```
79
+ */
33
80
  export const belongsTo = (target: TableDef, foreignKey: string, localKey: string = 'id'): BelongsToRelation => ({
34
81
  type: RelationKinds.BelongsTo,
35
82
  target,
@@ -1,14 +1,38 @@
1
1
  import { ColumnDef } from './column';
2
2
  import { RelationDef } from './relation';
3
3
 
4
+ /**
5
+ * Definition of a database table with its columns and relationships
6
+ * @typeParam T - Type of the columns record
7
+ */
4
8
  export interface TableDef<T extends Record<string, ColumnDef> = Record<string, ColumnDef>> {
9
+ /** Name of the table */
5
10
  name: string;
11
+ /** Record of column definitions keyed by column name */
6
12
  columns: T;
13
+ /** Record of relationship definitions keyed by relation name */
7
14
  relations: Record<string, RelationDef>;
8
15
  }
9
16
 
17
+ /**
18
+ * Creates a table definition with columns and relationships
19
+ * @typeParam T - Type of the columns record
20
+ * @param name - Name of the table
21
+ * @param columns - Record of column definitions
22
+ * @param relations - Record of relationship definitions (optional)
23
+ * @returns Complete table definition with runtime-filled column metadata
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const usersTable = defineTable('users', {
28
+ * id: col.primaryKey(col.int()),
29
+ * name: col.varchar(255),
30
+ * email: col.varchar(255)
31
+ * });
32
+ * ```
33
+ */
10
34
  export const defineTable = <T extends Record<string, ColumnDef>>(
11
- name: string,
35
+ name: string,
12
36
  columns: T,
13
37
  relations: Record<string, RelationDef> = {}
14
38
  ): TableDef<T> => {
@@ -17,6 +41,6 @@ export const defineTable = <T extends Record<string, ColumnDef>>(
17
41
  (acc as any)[key] = { ...def, name: key, table: name };
18
42
  return acc;
19
43
  }, {} as T);
20
-
44
+
21
45
  return { name, columns: colsWithNames, relations };
22
- };
46
+ };
@@ -0,0 +1,20 @@
1
+ import { JoinNode } from '../ast/join';
2
+ import { ExpressionNode } from '../ast/expression';
3
+ import { JoinKind } from '../constants/sql';
4
+
5
+ /**
6
+ * Creates a JoinNode ready for AST insertion.
7
+ * Centralizing this avoids copy/pasted object literals when multiple services need to synthesize joins.
8
+ */
9
+ export const createJoinNode = (
10
+ kind: JoinKind,
11
+ tableName: string,
12
+ condition: ExpressionNode,
13
+ relationName?: string
14
+ ): JoinNode => ({
15
+ type: 'Join',
16
+ kind,
17
+ table: { type: 'Table', name: tableName },
18
+ condition,
19
+ relationName
20
+ });