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
@@ -3,13 +3,58 @@ import { queryRows, shouldIncludeTable } from './utils.js';
3
3
  import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
4
4
  import { DbExecutor } from '../../execution/db-executor.js';
5
5
 
6
+ /** Row type for MSSQL column information. */
7
+ type MssqlColumnRow = {
8
+ table_schema: string;
9
+ table_name: string;
10
+ column_name: string;
11
+ data_type: string;
12
+ is_nullable: boolean | number;
13
+ is_identity: boolean | number;
14
+ column_default: string | null;
15
+ };
16
+
17
+ /** Row type for MSSQL primary key information. */
18
+ type MssqlPrimaryKeyRow = {
19
+ table_schema: string;
20
+ table_name: string;
21
+ column_name: string;
22
+ key_ordinal: number;
23
+ };
24
+
25
+ /** Row type for MSSQL index information. */
26
+ type MssqlIndexRow = {
27
+ table_schema: string;
28
+ table_name: string;
29
+ index_name: string;
30
+ is_unique: boolean | number;
31
+ has_filter: boolean | number;
32
+ filter_definition: string | null;
33
+ };
34
+
35
+ /** Row type for MSSQL index column information. */
36
+ type MssqlIndexColumnRow = {
37
+ table_schema: string;
38
+ table_name: string;
39
+ index_name: string;
40
+ column_name: string;
41
+ key_ordinal: number;
42
+ };
43
+
44
+ /** MSSQL schema introspector implementation. */
6
45
  export const mssqlIntrospector: SchemaIntrospector = {
46
+ /**
47
+ * Introspects the MSSQL database schema.
48
+ * @param ctx - The introspection context containing the database executor.
49
+ * @param options - Options for introspection, such as schema filter.
50
+ * @returns A promise that resolves to the introspected database schema.
51
+ */
7
52
  async introspect(ctx: { executor: DbExecutor }, options: IntrospectOptions): Promise<DatabaseSchema> {
8
53
  const schema = options.schema;
9
54
  const filterSchema = schema ? 'sch.name = @p1' : '1=1';
10
55
  const params = schema ? [schema] : [];
11
56
 
12
- const columnRows = await queryRows(
57
+ const columnRows = (await queryRows(
13
58
  ctx.executor,
14
59
  `
15
60
  SELECT
@@ -27,9 +72,9 @@ export const mssqlIntrospector: SchemaIntrospector = {
27
72
  WHERE t.is_ms_shipped = 0 AND ${filterSchema}
28
73
  `,
29
74
  params
30
- );
75
+ )) as MssqlColumnRow[];
31
76
 
32
- const pkRows = await queryRows(
77
+ const pkRows = (await queryRows(
33
78
  ctx.executor,
34
79
  `
35
80
  SELECT
@@ -46,7 +91,7 @@ export const mssqlIntrospector: SchemaIntrospector = {
46
91
  ORDER BY ic.key_ordinal
47
92
  `,
48
93
  params
49
- );
94
+ )) as MssqlPrimaryKeyRow[];
50
95
 
51
96
  const pkMap = new Map<string, string[]>();
52
97
  pkRows.forEach(r => {
@@ -56,7 +101,7 @@ export const mssqlIntrospector: SchemaIntrospector = {
56
101
  pkMap.set(key, list);
57
102
  });
58
103
 
59
- const indexRows = await queryRows(
104
+ const indexRows = (await queryRows(
60
105
  ctx.executor,
61
106
  `
62
107
  SELECT
@@ -72,9 +117,9 @@ export const mssqlIntrospector: SchemaIntrospector = {
72
117
  WHERE i.is_primary_key = 0 AND i.is_hypothetical = 0 AND ${filterSchema}
73
118
  `,
74
119
  params
75
- );
120
+ )) as MssqlIndexRow[];
76
121
 
77
- const indexColsRows = await queryRows(
122
+ const indexColsRows = (await queryRows(
78
123
  ctx.executor,
79
124
  `
80
125
  SELECT
@@ -92,7 +137,7 @@ export const mssqlIntrospector: SchemaIntrospector = {
92
137
  ORDER BY ic.key_ordinal
93
138
  `,
94
139
  params
95
- );
140
+ )) as MssqlIndexColumnRow[];
96
141
 
97
142
  const indexColumnsMap = new Map<string, { column: string; order: number }[]>();
98
143
  indexColsRows.forEach(r => {
@@ -3,13 +3,39 @@ import { queryRows, shouldIncludeTable } from './utils.js';
3
3
  import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
4
4
  import { DbExecutor } from '../../execution/db-executor.js';
5
5
 
6
+ /** Row type for MySQL column information. */
7
+ type MysqlColumnRow = {
8
+ table_schema: string;
9
+ table_name: string;
10
+ column_name: string;
11
+ data_type: string;
12
+ is_nullable: string;
13
+ column_default: string | null;
14
+ extra: string | null;
15
+ };
16
+
17
+ type MysqlPrimaryKeyRow = {
18
+ table_schema: string;
19
+ table_name: string;
20
+ column_name: string;
21
+ };
22
+
23
+ type MysqlIndexRow = {
24
+ table_schema: string;
25
+ table_name: string;
26
+ index_name: string;
27
+ non_unique: number;
28
+ cols: string | null;
29
+ };
30
+
31
+ /** MySQL schema introspector. */
6
32
  export const mysqlIntrospector: SchemaIntrospector = {
7
33
  async introspect(ctx: { executor: DbExecutor }, options: IntrospectOptions): Promise<DatabaseSchema> {
8
34
  const schema = options.schema;
9
35
  const filterClause = schema ? 'table_schema = ?' : 'table_schema = database()';
10
36
  const params = schema ? [schema] : [];
11
37
 
12
- const columnRows = await queryRows(
38
+ const columnRows = (await queryRows(
13
39
  ctx.executor,
14
40
  `
15
41
  SELECT table_schema, table_name, column_name, data_type, is_nullable, column_default, extra
@@ -18,9 +44,9 @@ export const mysqlIntrospector: SchemaIntrospector = {
18
44
  ORDER BY table_name, ordinal_position
19
45
  `,
20
46
  params
21
- );
47
+ )) as MysqlColumnRow[];
22
48
 
23
- const pkRows = await queryRows(
49
+ const pkRows = (await queryRows(
24
50
  ctx.executor,
25
51
  `
26
52
  SELECT table_schema, table_name, column_name
@@ -29,7 +55,7 @@ export const mysqlIntrospector: SchemaIntrospector = {
29
55
  ORDER BY ordinal_position
30
56
  `,
31
57
  params
32
- );
58
+ )) as MysqlPrimaryKeyRow[];
33
59
 
34
60
  const pkMap = new Map<string, string[]>();
35
61
  pkRows.forEach(r => {
@@ -39,7 +65,7 @@ export const mysqlIntrospector: SchemaIntrospector = {
39
65
  pkMap.set(key, list);
40
66
  });
41
67
 
42
- const indexRows = await queryRows(
68
+ const indexRows = (await queryRows(
43
69
  ctx.executor,
44
70
  `
45
71
  SELECT
@@ -53,7 +79,7 @@ export const mysqlIntrospector: SchemaIntrospector = {
53
79
  GROUP BY table_schema, table_name, index_name, non_unique
54
80
  `,
55
81
  params
56
- );
82
+ )) as MysqlIndexRow[];
57
83
 
58
84
  const tablesByKey = new Map<string, DatabaseTable>();
59
85
 
@@ -1,10 +1,10 @@
1
1
  import type { SchemaIntrospector, IntrospectOptions } from './types.js';
2
- import { queryRows, shouldIncludeTable } from './utils.js';
2
+ import { shouldIncludeTable } from './utils.js';
3
3
  import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
4
- import type { DbExecutor } from '../../execution/db-executor.js';
4
+ import type { ReferentialAction } from '../../../schema/column.js';
5
5
  import type { IntrospectContext } from './context.js';
6
6
  import { PgInformationSchemaColumns } from './catalogs/postgres.js';
7
- import { PgKeyColumnUsage, PgTableConstraints, PgConstraintColumnUsage, PgReferentialConstraints, PgIndex, PgClass, PgNamespace, PgAttribute } from './catalogs/postgres.js';
7
+ import { PgKeyColumnUsage, PgTableConstraints, PgConstraintColumnUsage, PgReferentialConstraints } from './catalogs/postgres.js';
8
8
  import { SelectQueryBuilder } from '../../../query-builder/select.js';
9
9
  import { eq, and } from '../../ast/expression-builders.js';
10
10
  import type { SelectQueryNode, TableNode } from '../../ast/query.js';
@@ -13,13 +13,81 @@ import type { ColumnNode, ExpressionNode } from '../../ast/expression-nodes.js';
13
13
  import { fnTable } from '../../ast/builders.js';
14
14
  import { runSelect, runSelectNode } from './run-select.js';
15
15
 
16
+ /** Row type for PostgreSQL column introspection from information_schema.columns. */
17
+ type ColumnIntrospectRow = {
18
+ table_schema: string;
19
+ table_name: string;
20
+ column_name: string;
21
+ data_type: string;
22
+ is_nullable: string;
23
+ column_default: string | null;
24
+ ordinal_position: number | null;
25
+ };
26
+
27
+ /** Row type for PostgreSQL primary key introspection from key_column_usage and table_constraints. */
28
+ type PrimaryKeyIntrospectRow = {
29
+ table_schema: string;
30
+ table_name: string;
31
+ column_name: string;
32
+ ordinal_position: number | null;
33
+ constraint_name: string;
34
+ };
35
+
36
+ /** Row type for PostgreSQL foreign key introspection from various constraint tables. */
37
+ type ForeignKeyIntrospectRow = {
38
+ table_schema: string;
39
+ table_name: string;
40
+ column_name: string;
41
+ constraint_name: string;
42
+ foreign_table_schema: string;
43
+ foreign_table_name: string;
44
+ foreign_column_name: string;
45
+ };
46
+
47
+ /** Represents a foreign key reference entry with optional referential actions. */
48
+ type ForeignKeyEntry = {
49
+ table: string;
50
+ column: string;
51
+ onDelete?: ReferentialAction;
52
+ onUpdate?: ReferentialAction;
53
+ };
54
+
55
+ /** Row type for PostgreSQL index query results from pg_catalog tables. */
56
+ type IndexQueryRow = {
57
+ table_schema: string;
58
+ table_name: string;
59
+ index_name: string;
60
+ is_unique: boolean;
61
+ predicate: string | null;
62
+ attname: string | null;
63
+ ord: number | null;
64
+ };
65
+
66
+ /** Grouped index information used for aggregating index columns. */
67
+ type IndexGroup = {
68
+ table_schema: string;
69
+ table_name: string;
70
+ index_name: string;
71
+ is_unique: boolean;
72
+ predicate: string | null;
73
+ cols: { ord: number; att: string | null }[];
74
+ };
75
+
76
+ /** PostgreSQL schema introspector. */
16
77
  export const postgresIntrospector: SchemaIntrospector = {
78
+ /**
79
+ * Introspects the PostgreSQL database schema by querying information_schema and pg_catalog.
80
+ * Builds tables with columns, primary keys, foreign keys, and indexes.
81
+ * @param ctx - The introspection context with database executor.
82
+ * @param options - Options for schema selection and table filtering.
83
+ * @returns A promise resolving to the complete database schema.
84
+ */
17
85
  async introspect(ctx: IntrospectContext, options: IntrospectOptions): Promise<DatabaseSchema> {
18
86
  const schema = options.schema || 'public';
19
87
  const tables: DatabaseTable[] = [];
20
88
 
21
89
  // Columns query
22
- const qbColumns = new SelectQueryBuilder(PgInformationSchemaColumns as any)
90
+ const qbColumns = new SelectQueryBuilder(PgInformationSchemaColumns)
23
91
  .select({
24
92
  table_schema: PgInformationSchemaColumns.columns.table_schema,
25
93
  table_name: PgInformationSchemaColumns.columns.table_name,
@@ -33,10 +101,10 @@ export const postgresIntrospector: SchemaIntrospector = {
33
101
  .orderBy(PgInformationSchemaColumns.columns.table_name)
34
102
  .orderBy(PgInformationSchemaColumns.columns.ordinal_position);
35
103
 
36
- const columnRows = await runSelect(qbColumns, ctx);
104
+ const columnRows = await runSelect<ColumnIntrospectRow>(qbColumns, ctx);
37
105
 
38
106
  // Primary key columns query
39
- const qbPk = new SelectQueryBuilder(PgKeyColumnUsage as any)
107
+ const qbPk = new SelectQueryBuilder(PgKeyColumnUsage)
40
108
  .select({
41
109
  table_schema: PgKeyColumnUsage.columns.table_schema,
42
110
  table_name: PgKeyColumnUsage.columns.table_name,
@@ -44,13 +112,13 @@ export const postgresIntrospector: SchemaIntrospector = {
44
112
  ordinal_position: PgKeyColumnUsage.columns.ordinal_position,
45
113
  constraint_name: PgKeyColumnUsage.columns.constraint_name
46
114
  })
47
- .innerJoin(PgTableConstraints as any, eq(PgTableConstraints.columns.constraint_name, PgKeyColumnUsage.columns.constraint_name))
115
+ .innerJoin(PgTableConstraints, eq(PgTableConstraints.columns.constraint_name, PgKeyColumnUsage.columns.constraint_name))
48
116
  .where(eq(PgTableConstraints.columns.constraint_type, 'PRIMARY KEY'))
49
117
  .where(eq(PgKeyColumnUsage.columns.table_schema, schema))
50
118
  .orderBy(PgKeyColumnUsage.columns.table_name)
51
119
  .orderBy(PgKeyColumnUsage.columns.ordinal_position);
52
120
 
53
- const pkRows = await runSelect(qbPk, ctx);
121
+ const pkRows = await runSelect<PrimaryKeyIntrospectRow>(qbPk, ctx);
54
122
 
55
123
  // Build primary key map (grouped by table, ordered by ordinal_position)
56
124
  const pkMap = new Map<string, string[]>();
@@ -67,7 +135,7 @@ export const postgresIntrospector: SchemaIntrospector = {
67
135
  }
68
136
 
69
137
  // Foreign key columns query
70
- const qbFk = new SelectQueryBuilder(PgKeyColumnUsage as any)
138
+ const qbFk = new SelectQueryBuilder(PgKeyColumnUsage)
71
139
  .select({
72
140
  table_schema: PgKeyColumnUsage.columns.table_schema,
73
141
  table_name: PgKeyColumnUsage.columns.table_name,
@@ -77,16 +145,16 @@ export const postgresIntrospector: SchemaIntrospector = {
77
145
  foreign_table_name: PgConstraintColumnUsage.columns.table_name,
78
146
  foreign_column_name: PgConstraintColumnUsage.columns.column_name
79
147
  })
80
- .innerJoin(PgTableConstraints as any, eq(PgTableConstraints.columns.constraint_name, PgKeyColumnUsage.columns.constraint_name))
81
- .innerJoin(PgConstraintColumnUsage as any, eq(PgConstraintColumnUsage.columns.constraint_name, PgTableConstraints.columns.constraint_name))
82
- .innerJoin(PgReferentialConstraints as any, eq(PgReferentialConstraints.columns.constraint_name, PgTableConstraints.columns.constraint_name))
148
+ .innerJoin(PgTableConstraints, eq(PgTableConstraints.columns.constraint_name, PgKeyColumnUsage.columns.constraint_name))
149
+ .innerJoin(PgConstraintColumnUsage, eq(PgConstraintColumnUsage.columns.constraint_name, PgTableConstraints.columns.constraint_name))
150
+ .innerJoin(PgReferentialConstraints, eq(PgReferentialConstraints.columns.constraint_name, PgTableConstraints.columns.constraint_name))
83
151
  .where(eq(PgTableConstraints.columns.constraint_type, 'FOREIGN KEY'))
84
152
  .where(eq(PgKeyColumnUsage.columns.table_schema, schema));
85
153
 
86
- const fkRows = await runSelect(qbFk, ctx);
154
+ const fkRows = await runSelect<ForeignKeyIntrospectRow>(qbFk, ctx);
87
155
 
88
156
  // Build foreign key map
89
- const fkMap = new Map<string, any[]>();
157
+ const fkMap = new Map<string, ForeignKeyEntry[]>();
90
158
  for (const r of fkRows) {
91
159
  const key = `${r.table_schema}.${r.table_name}.${r.column_name}`;
92
160
  const existing = fkMap.get(key) ?? [];
@@ -138,10 +206,10 @@ export const postgresIntrospector: SchemaIntrospector = {
138
206
  {
139
207
  type: 'Join',
140
208
  kind: 'INNER',
141
- table: fnTable('unnest', [{ type: 'Column', table: 'i', name: 'indkey' } as ColumnNode], 'arr', {
142
- lateral: true,
143
- withOrdinality: true,
144
- columnAliases: ['attnum', 'idx']
209
+ table: fnTable('unnest', [{ type: 'Column', table: 'i', name: 'indkey' } as ColumnNode], 'arr', {
210
+ lateral: true,
211
+ withOrdinality: true,
212
+ columnAliases: ['attnum', 'idx']
145
213
  }),
146
214
  condition: { type: 'BinaryExpression', left: { type: 'Literal', value: 1 }, operator: '=', right: { type: 'Literal', value: 1 } } as unknown as ExpressionNode
147
215
  } as JoinNode,
@@ -158,23 +226,23 @@ export const postgresIntrospector: SchemaIntrospector = {
158
226
  ],
159
227
  where: and(
160
228
  eq({ table: 'ns', name: 'nspname' }, schema) as ExpressionNode,
161
- eq({ table: 'i', name: 'indisprimary' }, { type: 'Literal', value: false } as any) as ExpressionNode
229
+ eq({ table: 'i', name: 'indisprimary' }, false) as ExpressionNode
162
230
  ) as ExpressionNode
163
231
  };
164
232
 
165
- const indexQueryRows = await runSelectNode(indexQuery, ctx);
166
-
233
+ const indexQueryRows = await runSelectNode<IndexQueryRow>(indexQuery, ctx);
234
+
167
235
  // Aggregate index rows by table/index to build final index list
168
- const indexGrouped = new Map<string, { table_schema: string; table_name: string; index_name: string; is_unique: any; predicate: any; cols: { ord: number; att: string | null }[] }>();
236
+ const indexGrouped = new Map<string, IndexGroup>();
169
237
  for (const r of indexQueryRows) {
170
238
  const key = `${r.table_schema}.${r.table_name}.${r.index_name}`;
171
- const entry = indexGrouped.get(key) ?? {
172
- table_schema: r.table_schema,
173
- table_name: r.table_name,
174
- index_name: r.index_name,
175
- is_unique: r.is_unique,
176
- predicate: r.predicate,
177
- cols: []
239
+ const entry = indexGrouped.get(key) ?? {
240
+ table_schema: r.table_schema,
241
+ table_name: r.table_name,
242
+ index_name: r.index_name,
243
+ is_unique: r.is_unique,
244
+ predicate: r.predicate,
245
+ cols: []
178
246
  };
179
247
  entry.cols.push({ ord: r.ord ?? 0, att: r.attname ?? null });
180
248
  indexGrouped.set(key, entry);
@@ -215,11 +283,11 @@ export const postgresIntrospector: SchemaIntrospector = {
215
283
  default: r.column_default ?? undefined,
216
284
  references: fk
217
285
  ? {
218
- table: fk.table,
219
- column: fk.column,
220
- onDelete: fk.onDelete,
221
- onUpdate: fk.onUpdate
222
- }
286
+ table: fk.table,
287
+ column: fk.column,
288
+ onDelete: fk.onDelete,
289
+ onUpdate: fk.onUpdate
290
+ }
223
291
  : undefined
224
292
  };
225
293
  cols.columns.push(column);
@@ -5,8 +5,12 @@ import { mysqlIntrospector } from './mysql.js';
5
5
  import { sqliteIntrospector } from './sqlite.js';
6
6
  import { mssqlIntrospector } from './mssql.js';
7
7
 
8
+ /** Registry mapping dialect names to their corresponding schema introspectors. */
8
9
  const registry = new Map<DialectName, SchemaIntrospector>();
9
10
 
11
+ /**
12
+ * Registers the built-in schema introspectors for all supported database dialects.
13
+ */
10
14
  const registerBuiltInIntrospectors = () => {
11
15
  registry.set('postgres', postgresIntrospector);
12
16
  registry.set('mysql', mysqlIntrospector);
@@ -16,10 +20,20 @@ const registerBuiltInIntrospectors = () => {
16
20
 
17
21
  registerBuiltInIntrospectors();
18
22
 
23
+ /**
24
+ * Registers a schema introspector for a dialect.
25
+ * @param dialect - The dialect name.
26
+ * @param introspector - The schema introspector.
27
+ */
19
28
  export const registerSchemaIntrospector = (dialect: DialectName, introspector: SchemaIntrospector): void => {
20
29
  registry.set(dialect, introspector);
21
30
  };
22
31
 
32
+ /**
33
+ * Gets the schema introspector for a dialect.
34
+ * @param dialect - The dialect name.
35
+ * @returns The schema introspector or undefined if not found.
36
+ */
23
37
  export const getSchemaIntrospector = (dialect: DialectName): SchemaIntrospector | undefined => {
24
38
  return registry.get(dialect);
25
39
  };
@@ -1,10 +1,19 @@
1
- import type { SelectQueryBuilder } from '../../../query-builder/select.js';
2
1
  import type { IntrospectContext } from './context.js';
2
+ import type { SelectQueryNode } from '../../ast/query.js';
3
3
 
4
4
  import { toRows } from './utils.js';
5
5
 
6
- export async function runSelect<T = Record<string, any>>(
7
- qb: SelectQueryBuilder<any, any>,
6
+ /** A source that can provide a select query AST. */
7
+ type SelectQuerySource = { getAST(): SelectQueryNode };
8
+
9
+ /**
10
+ * Runs a select query from a query builder and returns the results.
11
+ * @param qb - The query builder.
12
+ * @param ctx - The introspection context.
13
+ * @returns The query results.
14
+ */
15
+ export async function runSelect<T = Record<string, unknown>>(
16
+ qb: SelectQuerySource,
8
17
  ctx: IntrospectContext
9
18
  ): Promise<T[]> {
10
19
  const ast = qb.getAST();
@@ -17,7 +26,13 @@ export async function runSelect<T = Record<string, any>>(
17
26
 
18
27
  export default runSelect;
19
28
 
20
- export async function runSelectNode<T = Record<string, any>>(ast: any, ctx: IntrospectContext): Promise<T[]> {
29
+ /**
30
+ * Runs a select query from an AST node and returns the results.
31
+ * @param ast - The select query AST.
32
+ * @param ctx - The introspection context.
33
+ * @returns The query results.
34
+ */
35
+ export async function runSelectNode<T = Record<string, unknown>>(ast: SelectQueryNode, ctx: IntrospectContext): Promise<T[]> {
21
36
  const compiled = ctx.dialect.compileSelect(ast);
22
37
  const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
23
38
  const [first] = results;
@@ -1,24 +1,91 @@
1
1
  import { SchemaIntrospector, IntrospectOptions } from './types.js';
2
2
  import { queryRows, shouldIncludeTable } from './utils.js';
3
3
  import { DatabaseSchema, DatabaseTable, DatabaseIndex } from '../schema-types.js';
4
+ import { ReferentialAction } from '../../../schema/column.js';
4
5
  import { DbExecutor } from '../../execution/db-executor.js';
5
6
 
7
+ /** Row type for SQLite table list from sqlite_master. */
8
+ type SqliteTableRow = {
9
+ name: string;
10
+ };
11
+
12
+ /** Row type for SQLite table column information from PRAGMA table_info. */
13
+ type SqliteTableInfoRow = {
14
+ name: string;
15
+ type: string;
16
+ notnull: number;
17
+ dflt_value: string | null;
18
+ pk: number;
19
+ };
20
+
21
+ /** Row type for SQLite foreign key information from PRAGMA foreign_key_list. */
22
+ type SqliteForeignKeyRow = {
23
+ table: string;
24
+ from: string;
25
+ to: string;
26
+ on_delete: string | null;
27
+ on_update: string | null;
28
+ };
29
+
30
+ /** Row type for SQLite index list from PRAGMA index_list. */
31
+ type SqliteIndexListRow = {
32
+ name: string;
33
+ unique: number;
34
+ };
35
+
36
+ /** Row type for SQLite index column information from PRAGMA index_info. */
37
+ type SqliteIndexInfoRow = {
38
+ name: string;
39
+ };
40
+
41
+ /**
42
+ * Converts a SQLite referential action string to a ReferentialAction enum value.
43
+ * @param value - The string value from SQLite pragma (e.g., 'CASCADE', 'SET NULL').
44
+ * @returns The corresponding ReferentialAction enum value, or undefined if the value is invalid or null.
45
+ */
46
+ const toReferentialAction = (value: string | null | undefined): ReferentialAction | undefined => {
47
+ if (!value) return undefined;
48
+ const normalized = value.toUpperCase();
49
+ if (
50
+ normalized === 'NO ACTION' ||
51
+ normalized === 'RESTRICT' ||
52
+ normalized === 'CASCADE' ||
53
+ normalized === 'SET NULL' ||
54
+ normalized === 'SET DEFAULT'
55
+ ) {
56
+ return normalized as ReferentialAction;
57
+ }
58
+ return undefined;
59
+ };
60
+
61
+ /**
62
+ * Escapes single quotes in a string for safe inclusion in SQL queries.
63
+ * @param name - The string to escape.
64
+ * @returns The escaped string with single quotes doubled.
65
+ */
6
66
  const escapeSingleQuotes = (name: string) => name.replace(/'/g, "''");
7
67
 
68
+ /** SQLite schema introspector. */
8
69
  export const sqliteIntrospector: SchemaIntrospector = {
70
+ /**
71
+ * Introspects the SQLite database schema by querying sqlite_master and various PRAGMAs.
72
+ * @param ctx - The database execution context containing the DbExecutor.
73
+ * @param options - Options controlling which tables and schemas to include.
74
+ * @returns A promise that resolves to the introspected DatabaseSchema.
75
+ */
9
76
  async introspect(ctx: { executor: DbExecutor }, options: IntrospectOptions): Promise<DatabaseSchema> {
10
77
  const tables: DatabaseTable[] = [];
11
- const tableRows = await queryRows(
78
+ const tableRows = (await queryRows(
12
79
  ctx.executor,
13
80
  `SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%';`
14
- );
81
+ )) as SqliteTableRow[];
15
82
 
16
83
  for (const row of tableRows) {
17
- const name = row.name as string;
84
+ const name = row.name;
18
85
  if (!shouldIncludeTable(name, options)) continue;
19
86
  const table: DatabaseTable = { name, columns: [], primaryKey: [], indexes: [] };
20
87
 
21
- const cols = await queryRows(ctx.executor, `PRAGMA table_info('${escapeSingleQuotes(name)}');`);
88
+ const cols = (await queryRows(ctx.executor, `PRAGMA table_info('${escapeSingleQuotes(name)}');`)) as SqliteTableInfoRow[];
22
89
  cols.forEach(c => {
23
90
  table.columns.push({
24
91
  name: c.name,
@@ -33,26 +100,26 @@ export const sqliteIntrospector: SchemaIntrospector = {
33
100
  }
34
101
  });
35
102
 
36
- const fkRows = await queryRows(ctx.executor, `PRAGMA foreign_key_list('${escapeSingleQuotes(name)}');`);
103
+ const fkRows = (await queryRows(ctx.executor, `PRAGMA foreign_key_list('${escapeSingleQuotes(name)}');`)) as SqliteForeignKeyRow[];
37
104
  fkRows.forEach(fk => {
38
105
  const col = table.columns.find(c => c.name === fk.from);
39
106
  if (col) {
40
107
  col.references = {
41
108
  table: fk.table,
42
109
  column: fk.to,
43
- onDelete: fk.on_delete?.toUpperCase(),
44
- onUpdate: fk.on_update?.toUpperCase()
110
+ onDelete: toReferentialAction(fk.on_delete),
111
+ onUpdate: toReferentialAction(fk.on_update)
45
112
  };
46
113
  }
47
114
  });
48
115
 
49
- const idxList = await queryRows(ctx.executor, `PRAGMA index_list('${escapeSingleQuotes(name)}');`);
116
+ const idxList = (await queryRows(ctx.executor, `PRAGMA index_list('${escapeSingleQuotes(name)}');`)) as SqliteIndexListRow[];
50
117
  for (const idx of idxList) {
51
- const idxName = idx.name as string;
52
- const columnsInfo = await queryRows(ctx.executor, `PRAGMA index_info('${escapeSingleQuotes(idxName)}');`);
118
+ const idxName = idx.name;
119
+ const columnsInfo = (await queryRows(ctx.executor, `PRAGMA index_info('${escapeSingleQuotes(idxName)}');`)) as SqliteIndexInfoRow[];
53
120
  const idxEntry: DatabaseIndex = {
54
121
  name: idxName,
55
- columns: columnsInfo.map(ci => ({ column: ci.name as string })),
122
+ columns: columnsInfo.map(ci => ({ column: ci.name })),
56
123
  unique: idx.unique === 1
57
124
  };
58
125
  table.indexes!.push(idxEntry);
@@ -1,4 +1,3 @@
1
- import type { DbExecutor } from '../../execution/db-executor.js';
2
1
  import { DatabaseSchema } from '../schema-types.js';
3
2
  import type { IntrospectContext } from './context.js';
4
3
 
@@ -1,25 +1,43 @@
1
1
  import { DbExecutor, QueryResult } from '../../execution/db-executor.js';
2
2
  import { IntrospectOptions } from './types.js';
3
3
 
4
- export const toRows = (result: QueryResult | undefined): Record<string, any>[] => {
4
+ /**
5
+ * Converts a query result to an array of row objects.
6
+ * @param result - The query result.
7
+ * @returns The array of rows.
8
+ */
9
+ export const toRows = (result: QueryResult | undefined): Record<string, unknown>[] => {
5
10
  if (!result) return [];
6
11
  return result.values.map(row =>
7
- result.columns.reduce<Record<string, any>>((acc, col, idx) => {
12
+ result.columns.reduce<Record<string, unknown>>((acc, col, idx) => {
8
13
  acc[col] = row[idx];
9
14
  return acc;
10
15
  }, {})
11
16
  );
12
17
  };
13
18
 
19
+ /**
20
+ * Executes a SQL query and returns the rows.
21
+ * @param executor - The database executor.
22
+ * @param sql - The SQL query.
23
+ * @param params - The query parameters.
24
+ * @returns The array of rows.
25
+ */
14
26
  export const queryRows = async (
15
27
  executor: DbExecutor,
16
28
  sql: string,
17
29
  params: unknown[] = []
18
- ): Promise<Record<string, any>[]> => {
30
+ ): Promise<Record<string, unknown>[]> => {
19
31
  const [first] = await executor.executeSql(sql, params);
20
32
  return toRows(first);
21
33
  };
22
34
 
35
+ /**
36
+ * Checks if a table should be included in introspection based on options.
37
+ * @param name - The table name.
38
+ * @param options - The introspection options.
39
+ * @returns True if the table should be included.
40
+ */
23
41
  export const shouldIncludeTable = (name: string, options: IntrospectOptions): boolean => {
24
42
  if (options.includeTables && !options.includeTables.includes(name)) return false;
25
43
  if (options.excludeTables && options.excludeTables.includes(name)) return false;
@@ -1,5 +1,11 @@
1
1
  import type { TableDef, IndexDef } from '../../schema/table.js';
2
2
 
3
+ /**
4
+ * Derives the name for an index based on the table and index definition.
5
+ * @param table - The table definition.
6
+ * @param index - The index definition.
7
+ * @returns The derived index name.
8
+ */
3
9
  export const deriveIndexName = (table: TableDef, index: IndexDef): string => {
4
10
  const base = (index.columns ?? [])
5
11
  .map(col => (typeof col === 'string' ? col : col.column))