@danceroutine/tango-migrations 0.1.0 → 1.0.0

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 (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/dist/{CollectingBuilder-C6qnwyrb.js → CollectingBuilder--4fqDQdE.js} +17 -2
  4. package/dist/CollectingBuilder--4fqDQdE.js.map +1 -0
  5. package/dist/{CompilerStrategy-Cv1woBmO.js → CompilerStrategy-yKw-Egxv.js} +19 -8
  6. package/dist/CompilerStrategy-yKw-Egxv.js.map +1 -0
  7. package/dist/InternalColumnType-G9zV9StN.js +16 -0
  8. package/dist/InternalColumnType-G9zV9StN.js.map +1 -0
  9. package/dist/InternalOperationKind-Bt6Weuon.js +19 -0
  10. package/dist/InternalOperationKind-Bt6Weuon.js.map +1 -0
  11. package/dist/{IntrospectorStrategy-BM1Eizfc.js → IntrospectorStrategy-blvwSU3_.js} +13 -4
  12. package/dist/IntrospectorStrategy-blvwSU3_.js.map +1 -0
  13. package/dist/Migration-DYQ0hUG7.js +30 -0
  14. package/dist/Migration-DYQ0hUG7.js.map +1 -0
  15. package/dist/{MigrationGenerator-Z39LTKmC.js → MigrationGenerator-B1p0jHnx.js} +20 -12
  16. package/dist/MigrationGenerator-B1p0jHnx.js.map +1 -0
  17. package/dist/{MigrationRunner-CCFuPUlr.js → MigrationRunner-D1ZfbbS-.js} +52 -9
  18. package/dist/MigrationRunner-D1ZfbbS-.js.map +1 -0
  19. package/dist/MigrationSqlSafetyAdapter-CGRbB2k2.js +62 -0
  20. package/dist/MigrationSqlSafetyAdapter-CGRbB2k2.js.map +1 -0
  21. package/dist/{SqliteCompilerFactory-DwMwO7xY.js → SqliteCompilerFactory-BAodJW9n.js} +73 -51
  22. package/dist/SqliteCompilerFactory-BAodJW9n.js.map +1 -0
  23. package/dist/{SqliteIntrospector-BRdNt6KG.js → SqliteIntrospector-CWwPWhmA.js} +49 -5
  24. package/dist/SqliteIntrospector-CWwPWhmA.js.map +1 -0
  25. package/dist/builder/contracts/ColumnSpec.d.ts +2 -1
  26. package/dist/builder/contracts/UpdateReferentialAction.d.ts +1 -1
  27. package/dist/builder/index.js +4 -4
  28. package/dist/builder/ops/OpBuilder.d.ts +48 -7
  29. package/dist/builder/runtime/CollectingBuilder.d.ts +19 -1
  30. package/dist/{builder-Dtk8oP_Y.js → builder-xJ-Bq2pk.js} +51 -21
  31. package/dist/builder-xJ-Bq2pk.js.map +1 -0
  32. package/dist/cli-DhCn8xiS.js +313 -0
  33. package/dist/cli-DhCn8xiS.js.map +1 -0
  34. package/dist/cli.js +17 -169
  35. package/dist/cli.js.map +1 -1
  36. package/dist/commands/cli.d.ts +5 -0
  37. package/dist/commands/index.d.ts +4 -0
  38. package/dist/commands/index.js +17 -0
  39. package/dist/commands-DIJepqNg.js +10 -0
  40. package/dist/commands-DIJepqNg.js.map +1 -0
  41. package/dist/compilers/contracts/CompilerFactory.d.ts +4 -0
  42. package/dist/compilers/contracts/SQLCompiler.d.ts +4 -0
  43. package/dist/compilers/dialects/PostgresCompiler.d.ts +10 -1
  44. package/dist/compilers/dialects/SqliteCompiler.d.ts +10 -0
  45. package/dist/compilers/factories/PostgresCompilerFactory.d.ts +9 -0
  46. package/dist/compilers/factories/SqliteCompilerFactory.d.ts +9 -0
  47. package/dist/compilers/index.js +5 -4
  48. package/dist/{compilers-D8DJuTnQ.js → compilers-dRN0Hzev.js} +2 -2
  49. package/dist/{compilers-D8DJuTnQ.js.map → compilers-dRN0Hzev.js.map} +1 -1
  50. package/dist/diff/diffSchema.d.ts +6 -1
  51. package/dist/diff/index.js +6 -6
  52. package/dist/{diff-Cs0TPEGR.js → diff-CZZbXAPN.js} +2 -2
  53. package/dist/{diff-Cs0TPEGR.js.map → diff-CZZbXAPN.js.map} +1 -1
  54. package/dist/{diffSchema-KgGHP-s3.js → diffSchema-D4oemTWS.js} +5 -4
  55. package/dist/diffSchema-D4oemTWS.js.map +1 -0
  56. package/dist/domain/Migration.d.ts +17 -0
  57. package/dist/domain/MigrationOperation.d.ts +14 -13
  58. package/dist/domain/index.js +2 -2
  59. package/dist/domain/internal/InternalColumnType.d.ts +11 -10
  60. package/dist/domain/internal/InternalDialect.d.ts +5 -4
  61. package/dist/domain/internal/InternalMigrationMode.d.ts +5 -4
  62. package/dist/domain/internal/InternalOperationKind.d.ts +14 -13
  63. package/dist/domain/internal/InternalReferentialAction.d.ts +7 -6
  64. package/dist/{domain-BXVlG0C0.js → domain-CwR-kUNS.js} +2 -2
  65. package/dist/{domain-BXVlG0C0.js.map → domain-CwR-kUNS.js.map} +1 -1
  66. package/dist/generator/MigrationGenerator.d.ts +13 -0
  67. package/dist/generator/index.js +3 -3
  68. package/dist/{generator-3yC60b1u.js → generator-DK-_f-PF.js} +2 -2
  69. package/dist/{generator-3yC60b1u.js.map → generator-DK-_f-PF.js.map} +1 -1
  70. package/dist/index.d.ts +4 -0
  71. package/dist/index.js +24 -20
  72. package/dist/internal/MigrationSqlSafetyAdapter.d.ts +24 -0
  73. package/dist/introspect/DatabaseIntrospector.d.ts +8 -0
  74. package/dist/introspect/PostgresIntrospector.d.ts +14 -0
  75. package/dist/introspect/SqliteIntrospector.d.ts +13 -0
  76. package/dist/introspect/index.js +3 -2
  77. package/dist/{introspect-ks-QSodq.js → introspect-TPv_jeD6.js} +2 -2
  78. package/dist/{introspect-ks-QSodq.js.map → introspect-TPv_jeD6.js.map} +1 -1
  79. package/dist/runner/MigrationRunner.d.ts +7 -1
  80. package/dist/runner/index.js +9 -8
  81. package/dist/{runner-BOs-tItW.js → runner-C97xT8_W.js} +2 -2
  82. package/dist/{runner-BOs-tItW.js.map → runner-C97xT8_W.js.map} +1 -1
  83. package/dist/runtime/loadModule.d.ts +15 -0
  84. package/dist/strategies/CompilerStrategy.d.ts +19 -1
  85. package/dist/strategies/IntrospectorStrategy.d.ts +16 -1
  86. package/dist/strategies/index.js +8 -7
  87. package/dist/{strategies-BvHwf4as.js → strategies-D9ymSvbG.js} +3 -3
  88. package/dist/{strategies-BvHwf4as.js.map → strategies-D9ymSvbG.js.map} +1 -1
  89. package/package.json +98 -92
  90. package/dist/CollectingBuilder-C6qnwyrb.js.map +0 -1
  91. package/dist/CompilerStrategy-Cv1woBmO.js.map +0 -1
  92. package/dist/InternalColumnType-_YAz7RqI.js +0 -17
  93. package/dist/InternalColumnType-_YAz7RqI.js.map +0 -1
  94. package/dist/InternalOperationKind-BPVoOQwD.js +0 -20
  95. package/dist/InternalOperationKind-BPVoOQwD.js.map +0 -1
  96. package/dist/IntrospectorStrategy-BM1Eizfc.js.map +0 -1
  97. package/dist/Migration-D9J6ZbLP.js +0 -25
  98. package/dist/Migration-D9J6ZbLP.js.map +0 -1
  99. package/dist/MigrationGenerator-Z39LTKmC.js.map +0 -1
  100. package/dist/MigrationRunner-CCFuPUlr.js.map +0 -1
  101. package/dist/SqliteCompilerFactory-DwMwO7xY.js.map +0 -1
  102. package/dist/SqliteIntrospector-BRdNt6KG.js.map +0 -1
  103. package/dist/builder/ops/OpBuilder.js +0 -173
  104. package/dist/builder-Dtk8oP_Y.js.map +0 -1
  105. package/dist/diffSchema-KgGHP-s3.js.map +0 -1
  106. package/dist/domain/MigrationOperation.js +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Pedro Del Moral Lopez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # @danceroutine/tango-migrations
2
+
3
+ `@danceroutine/tango-migrations` manages schema evolution for Tango applications.
4
+
5
+ This package exists because model metadata and database schema do not stay aligned on their own. Once a project begins to change, teams need a disciplined way to describe schema changes, compare model intent with the actual database, review what will happen next, and apply those changes in a predictable order. Tango keeps that workflow in one package so that migrations remain a first-class part of application development rather than an afterthought bolted onto the ORM.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @danceroutine/tango-migrations
11
+ ```
12
+
13
+ Install the database driver for the dialect you use:
14
+
15
+ ```bash
16
+ pnpm add pg
17
+ # or
18
+ pnpm add better-sqlite3
19
+ ```
20
+
21
+ If you want the `tango` executable for generation and apply workflows, also install:
22
+
23
+ ```bash
24
+ pnpm add -D @danceroutine/tango-cli
25
+ ```
26
+
27
+ ## How the migration workflow fits together
28
+
29
+ The package supports three related jobs:
30
+
31
+ 1. describe schema changes with migration classes and operation builders
32
+ 2. compare model metadata with a live database schema and generate a migration plan
33
+ 3. apply migrations in order and record what has already run
34
+
35
+ That gives you a workflow that moves from intent to review to execution, rather than asking the database schema to evolve through ad hoc scripts.
36
+
37
+ ## Quick start
38
+
39
+ ```ts
40
+ import { Migration, op } from '@danceroutine/tango-migrations';
41
+
42
+ export default class CreatePosts extends Migration {
43
+ id = '20260302_create_posts';
44
+
45
+ up(m) {
46
+ m.run(
47
+ op.table('posts').create((cols) => {
48
+ cols.add('id', (b) => b.serial().primaryKey());
49
+ cols.add('title', (b) => b.text().notNull());
50
+ })
51
+ );
52
+ }
53
+
54
+ down(m) {
55
+ m.run(op.table('posts').drop());
56
+ }
57
+ }
58
+ ```
59
+
60
+ This class-based structure is the core of the package. A migration names a change set and defines how to apply it and, when possible, how to reverse it.
61
+
62
+ ## Using the CLI
63
+
64
+ Most application developers should use this package through the shared `tango` CLI:
65
+
66
+ ```bash
67
+ tango make:migrations --dialect sqlite --models ./src/models.ts --dir ./migrations --name add_posts
68
+ tango plan --dialect sqlite --dir ./migrations --db ./app.sqlite
69
+ tango migrate --dialect sqlite --dir ./migrations --db ./app.sqlite
70
+ tango status --dialect sqlite --dir ./migrations --db ./app.sqlite
71
+ ```
72
+
73
+ That command surface is provided by `@danceroutine/tango-cli`, but the migration behavior itself still lives in this package.
74
+
75
+ ## Public API
76
+
77
+ The root export gives you the main concepts you need:
78
+
79
+ - `Migration`, the base class for migration files
80
+ - `op`, `OpBuilder`, and `CollectingBuilder`, which describe migration operations
81
+ - `diffSchema()`, which compares model metadata to an introspected schema
82
+ - `MigrationGenerator` and `MigrationRunner`
83
+ - SQL compilers, introspectors, and dialect strategies for PostgreSQL and SQLite
84
+ - `registerMigrationsCommands()`, which mounts the migration command tree into the CLI
85
+
86
+ The package also exposes subpaths such as `builder`, `runner`, `generator`, `diff`, `compilers`, `introspect`, `strategies`, and `commands` when you want a narrower import boundary.
87
+
88
+ ## Documentation
89
+
90
+ - Official documentation: <https://tangowebframework.dev>
91
+ - Migrations topic: <https://tangowebframework.dev/topics/migrations>
92
+ - Generate and apply migrations: <https://tangowebframework.dev/how-to/generate-and-apply-migrations>
93
+
94
+ ## Development
95
+
96
+ ```bash
97
+ pnpm --filter @danceroutine/tango-migrations build
98
+ pnpm --filter @danceroutine/tango-migrations typecheck
99
+ pnpm --filter @danceroutine/tango-migrations test
100
+ ```
101
+
102
+ The package also has integration coverage for dialect-specific behavior:
103
+
104
+ ```bash
105
+ pnpm --filter @danceroutine/tango-migrations test:integration
106
+ ```
107
+
108
+ For the wider contributor workflow, use:
109
+
110
+ - <https://tangowebframework.dev/contributing>
111
+
112
+ ## License
113
+
114
+ MIT
@@ -2,22 +2,37 @@
2
2
  //#region src/builder/runtime/CollectingBuilder.ts
3
3
  var CollectingBuilder = class CollectingBuilder {
4
4
  static BRAND = "tango.migrations.collecting_builder";
5
- mode;
6
5
  __tangoBrand = CollectingBuilder.BRAND;
7
6
  ops = [];
8
7
  dataFns = [];
8
+ mode;
9
+ /**
10
+ * Narrow an unknown value to the in-memory builder used during migration collection.
11
+ */
9
12
  static isCollectingBuilder(value) {
10
13
  return typeof value === "object" && value !== null && value.__tangoBrand === CollectingBuilder.BRAND;
11
14
  }
15
+ /**
16
+ * Append schema operations to the collection.
17
+ */
12
18
  run(...ops) {
13
19
  this.ops.push(...ops);
14
20
  }
21
+ /**
22
+ * Register a data migration callback.
23
+ */
15
24
  data(fn) {
16
25
  this.dataFns.push(fn);
17
26
  }
27
+ /**
28
+ * Set execution options for the migration.
29
+ */
18
30
  options(o) {
19
31
  this.mode = o.mode;
20
32
  }
33
+ /**
34
+ * Get the currently configured migration mode.
35
+ */
21
36
  getMode() {
22
37
  return this.mode;
23
38
  }
@@ -25,4 +40,4 @@ var CollectingBuilder = class CollectingBuilder {
25
40
 
26
41
  //#endregion
27
42
  export { CollectingBuilder };
28
- //# sourceMappingURL=CollectingBuilder-C6qnwyrb.js.map
43
+ //# sourceMappingURL=CollectingBuilder--4fqDQdE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollectingBuilder--4fqDQdE.js","names":["value: unknown","fn: (ctx: { query(sql: string, params?: readonly unknown[]): Promise<void> }) => Promise<void>","o: { mode?: MigrationMode }"],"sources":["../src/builder/runtime/CollectingBuilder.ts"],"sourcesContent":["import type { Builder } from '../contracts/Builder';\nimport type { MigrationOperation } from '../../domain/MigrationOperation';\nimport type { MigrationMode } from '../../domain/MigrationMode';\n\n/**\n * In-memory builder that collects migration operations and data callbacks.\n */\nexport class CollectingBuilder implements Builder {\n static readonly BRAND = 'tango.migrations.collecting_builder' as const;\n readonly __tangoBrand: typeof CollectingBuilder.BRAND = CollectingBuilder.BRAND;\n ops: MigrationOperation[] = [];\n dataFns: Array<(ctx: { query(sql: string, params?: readonly unknown[]): Promise<void> }) => Promise<void>> = [];\n private mode?: MigrationMode;\n\n /**\n * Narrow an unknown value to the in-memory builder used during migration collection.\n */\n static isCollectingBuilder(value: unknown): value is CollectingBuilder {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === CollectingBuilder.BRAND\n );\n }\n\n /**\n * Append schema operations to the collection.\n */\n run(...ops: MigrationOperation[]): void {\n this.ops.push(...ops);\n }\n\n /**\n * Register a data migration callback.\n */\n data(fn: (ctx: { query(sql: string, params?: readonly unknown[]): Promise<void> }) => Promise<void>): void {\n this.dataFns.push(fn);\n }\n\n /**\n * Set execution options for the migration.\n */\n options(o: { mode?: MigrationMode }): void {\n this.mode = o.mode;\n }\n\n /**\n * Get the currently configured migration mode.\n */\n getMode(): MigrationMode | undefined {\n return this.mode;\n }\n}\n"],"mappings":";;IAOa,oBAAN,MAAM,kBAAqC;CAC9C,OAAgB,QAAQ;CACxB,eAAwD,kBAAkB;CAC1E,MAA4B,CAAE;CAC9B,UAA6G,CAAE;CAC/G;;;;CAKA,OAAO,oBAAoBA,OAA4C;AACnE,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,kBAAkB;CAEhF;;;;CAKD,IAAI,GAAG,KAAiC;AACpC,OAAK,IAAI,KAAK,GAAG,IAAI;CACxB;;;;CAKD,KAAKC,IAAsG;AACvG,OAAK,QAAQ,KAAK,GAAG;CACxB;;;;CAKD,QAAQC,GAAmC;AACvC,OAAK,OAAO,EAAE;CACjB;;;;CAKD,UAAqC;AACjC,SAAO,KAAK;CACf;AACJ"}
@@ -1,25 +1,30 @@
1
- import { PostgresCompilerFactory, SqliteCompilerFactory } from "./SqliteCompilerFactory-DwMwO7xY.js";
1
+ import { PostgresCompilerFactory, SqliteCompilerFactory } from "./SqliteCompilerFactory-BAodJW9n.js";
2
2
 
3
3
  //#region src/domain/internal/InternalDialect.ts
4
- let InternalDialect = function(InternalDialect$1) {
5
- InternalDialect$1["POSTGRES"] = "postgres";
6
- InternalDialect$1["SQLITE"] = "sqlite";
7
- return InternalDialect$1;
8
- }({});
4
+ const InternalDialect = {
5
+ POSTGRES: "postgres",
6
+ SQLITE: "sqlite"
7
+ };
9
8
 
10
9
  //#endregion
11
10
  //#region src/strategies/CompilerStrategy.ts
12
11
  var CompilerStrategy = class CompilerStrategy {
13
12
  static BRAND = "tango.migrations.compiler_strategy";
13
+ __tangoBrand = CompilerStrategy.BRAND;
14
14
  compilerCache = new Map();
15
15
  customHandlers = new Map();
16
- __tangoBrand = CompilerStrategy.BRAND;
17
16
  constructor(factories) {
18
17
  this.factories = factories;
19
18
  }
19
+ /**
20
+ * Narrow an unknown value to the dialect-aware migration compiler strategy.
21
+ */
20
22
  static isCompilerStrategy(value) {
21
23
  return typeof value === "object" && value !== null && value.__tangoBrand === CompilerStrategy.BRAND;
22
24
  }
25
+ /**
26
+ * Compile a migration operation to SQL for a target dialect.
27
+ */
23
28
  compile(dialect, operation) {
24
29
  if (operation.kind === "custom") {
25
30
  const handler = this.customHandlers.get(operation.name);
@@ -29,10 +34,16 @@ var CompilerStrategy = class CompilerStrategy {
29
34
  const compiler = this.getCompiler(dialect);
30
35
  return compiler.compile(operation);
31
36
  }
37
+ /**
38
+ * Register a handler for custom migration operations.
39
+ */
32
40
  registerCustomHandler(name, handler) {
33
41
  this.customHandlers.set(name, handler);
34
42
  return this;
35
43
  }
44
+ /**
45
+ * Resolve and cache a compiler instance for a dialect.
46
+ */
36
47
  getCompiler(dialect) {
37
48
  const cached = this.compilerCache.get(dialect);
38
49
  if (cached) return cached;
@@ -52,4 +63,4 @@ function createDefaultCompilerStrategy() {
52
63
 
53
64
  //#endregion
54
65
  export { CompilerStrategy, InternalDialect, createDefaultCompilerStrategy };
55
- //# sourceMappingURL=CompilerStrategy-Cv1woBmO.js.map
66
+ //# sourceMappingURL=CompilerStrategy-yKw-Egxv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CompilerStrategy-yKw-Egxv.js","names":["factories: CompilerFactoryRegistry","value: unknown","dialect: Dialect","operation: MigrationOperation","name: TName","handler: (dialect: Dialect, op: CustomMigrationOperation<TName, TArgs>) => SQL[]"],"sources":["../src/domain/internal/InternalDialect.ts","../src/strategies/CompilerStrategy.ts"],"sourcesContent":["export const InternalDialect = {\n POSTGRES: 'postgres',\n SQLITE: 'sqlite',\n} as const;\n\nexport type InternalDialect = (typeof InternalDialect)[keyof typeof InternalDialect];\n","import { PostgresCompilerFactory } from '../compilers/factories/PostgresCompilerFactory';\nimport { SqliteCompilerFactory } from '../compilers/factories/SqliteCompilerFactory';\nimport type { CompilerFactory } from '../compilers/contracts/CompilerFactory';\nimport type { Dialect } from '../domain/Dialect';\nimport type { CustomMigrationOperation, MigrationOperation } from '../domain/MigrationOperation';\nimport type { SQL } from '../compilers/contracts/SQL';\nimport type { SQLCompiler } from '../compilers/contracts/SQLCompiler';\nimport { InternalDialect } from '../domain/internal/InternalDialect';\n\ntype CompilerFactoryRegistry = Record<Dialect, CompilerFactory>;\n\n/**\n * Dialect-aware SQL compiler orchestration with optional custom-op handlers.\n */\nexport class CompilerStrategy {\n static readonly BRAND = 'tango.migrations.compiler_strategy' as const;\n readonly __tangoBrand: typeof CompilerStrategy.BRAND = CompilerStrategy.BRAND;\n private readonly compilerCache = new Map<Dialect, SQLCompiler>();\n private readonly customHandlers = new Map<string, (dialect: Dialect, op: CustomMigrationOperation) => SQL[]>();\n\n constructor(private readonly factories: CompilerFactoryRegistry) {}\n\n /**\n * Narrow an unknown value to the dialect-aware migration compiler strategy.\n */\n static isCompilerStrategy(value: unknown): value is CompilerStrategy {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === CompilerStrategy.BRAND\n );\n }\n\n /**\n * Compile a migration operation to SQL for a target dialect.\n */\n compile(dialect: Dialect, operation: MigrationOperation): SQL[] {\n if (operation.kind === 'custom') {\n const handler = this.customHandlers.get(operation.name);\n if (!handler) {\n throw new Error(`Unsupported custom migration op: ${operation.name}`);\n }\n return handler(dialect, operation);\n }\n const compiler = this.getCompiler(dialect);\n return compiler.compile(operation);\n }\n\n /**\n * Register a handler for custom migration operations.\n */\n registerCustomHandler<TName extends string, TArgs extends object>(\n name: TName,\n handler: (dialect: Dialect, op: CustomMigrationOperation<TName, TArgs>) => SQL[]\n ): this {\n this.customHandlers.set(name, handler as (dialect: Dialect, op: CustomMigrationOperation) => SQL[]);\n return this;\n }\n\n /**\n * Resolve and cache a compiler instance for a dialect.\n */\n getCompiler(dialect: Dialect): SQLCompiler {\n const cached = this.compilerCache.get(dialect);\n if (cached) {\n return cached;\n }\n\n const factory = this.factories[dialect];\n if (!factory) {\n throw new Error(`No SQL compiler factory registered for dialect: ${String(dialect)}`);\n }\n const compiler = factory.create();\n this.compilerCache.set(dialect, compiler);\n return compiler;\n }\n}\n\n/**\n * Create the default compiler strategy with built-in dialect factories.\n */\nexport function createDefaultCompilerStrategy(): CompilerStrategy {\n return new CompilerStrategy({\n [InternalDialect.POSTGRES]: new PostgresCompilerFactory(),\n [InternalDialect.SQLITE]: new SqliteCompilerFactory(),\n });\n}\n"],"mappings":";;;MAAa,kBAAkB;CAC3B,UAAU;CACV,QAAQ;AACX;;;;ICWY,mBAAN,MAAM,iBAAiB;CAC1B,OAAgB,QAAQ;CACxB,eAAuD,iBAAiB;CACxE,gBAAiC,IAAI;CACrC,iBAAkC,IAAI;CAEtC,YAA6BA,WAAoC;AAAA,OAApC,YAAA;CAAsC;;;;CAKnE,OAAO,mBAAmBC,OAA2C;AACjE,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,iBAAiB;CAE/E;;;;CAKD,QAAQC,SAAkBC,WAAsC;AAC5D,MAAI,UAAU,SAAS,UAAU;GAC7B,MAAM,UAAU,KAAK,eAAe,IAAI,UAAU,KAAK;AACvD,QAAK,QACD,OAAM,IAAI,OAAO,mCAAmC,UAAU,KAAK;AAEvE,UAAO,QAAQ,SAAS,UAAU;EACrC;EACD,MAAM,WAAW,KAAK,YAAY,QAAQ;AAC1C,SAAO,SAAS,QAAQ,UAAU;CACrC;;;;CAKD,sBACIC,MACAC,SACI;AACJ,OAAK,eAAe,IAAI,MAAM,QAAqE;AACnG,SAAO;CACV;;;;CAKD,YAAYH,SAA+B;EACvC,MAAM,SAAS,KAAK,cAAc,IAAI,QAAQ;AAC9C,MAAI,OACA,QAAO;EAGX,MAAM,UAAU,KAAK,UAAU;AAC/B,OAAK,QACD,OAAM,IAAI,OAAO,kDAAkD,OAAO,QAAQ,CAAC;EAEvF,MAAM,WAAW,QAAQ,QAAQ;AACjC,OAAK,cAAc,IAAI,SAAS,SAAS;AACzC,SAAO;CACV;AACJ;AAKM,SAAS,gCAAkD;AAC9D,QAAO,IAAI,iBAAiB;GACvB,gBAAgB,WAAW,IAAI;GAC/B,gBAAgB,SAAS,IAAI;CACjC;AACJ"}
@@ -0,0 +1,16 @@
1
+
2
+ //#region src/domain/internal/InternalColumnType.ts
3
+ const InternalColumnType = {
4
+ SERIAL: "serial",
5
+ INT: "int",
6
+ BIGINT: "bigint",
7
+ TEXT: "text",
8
+ BOOL: "bool",
9
+ TIMESTAMPTZ: "timestamptz",
10
+ JSONB: "jsonb",
11
+ UUID: "uuid"
12
+ };
13
+
14
+ //#endregion
15
+ export { InternalColumnType };
16
+ //# sourceMappingURL=InternalColumnType-G9zV9StN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InternalColumnType-G9zV9StN.js","names":[],"sources":["../src/domain/internal/InternalColumnType.ts"],"sourcesContent":["export const InternalColumnType = {\n SERIAL: 'serial',\n INT: 'int',\n BIGINT: 'bigint',\n TEXT: 'text',\n BOOL: 'bool',\n TIMESTAMPTZ: 'timestamptz',\n JSONB: 'jsonb',\n UUID: 'uuid',\n} as const;\n\nexport type InternalColumnType = (typeof InternalColumnType)[keyof typeof InternalColumnType];\n"],"mappings":";;MAAa,qBAAqB;CAC9B,QAAQ;CACR,KAAK;CACL,QAAQ;CACR,MAAM;CACN,MAAM;CACN,aAAa;CACb,OAAO;CACP,MAAM;AACT"}
@@ -0,0 +1,19 @@
1
+
2
+ //#region src/domain/internal/InternalOperationKind.ts
3
+ const InternalOperationKind = {
4
+ TABLE_CREATE: "table.create",
5
+ TABLE_DROP: "table.drop",
6
+ COLUMN_ADD: "column.add",
7
+ COLUMN_DROP: "column.drop",
8
+ COLUMN_ALTER: "column.alter",
9
+ COLUMN_RENAME: "column.rename",
10
+ INDEX_CREATE: "index.create",
11
+ INDEX_DROP: "index.drop",
12
+ FK_CREATE: "fk.create",
13
+ FK_VALIDATE: "fk.validate",
14
+ FK_DROP: "fk.drop"
15
+ };
16
+
17
+ //#endregion
18
+ export { InternalOperationKind };
19
+ //# sourceMappingURL=InternalOperationKind-Bt6Weuon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InternalOperationKind-Bt6Weuon.js","names":[],"sources":["../src/domain/internal/InternalOperationKind.ts"],"sourcesContent":["export const InternalOperationKind = {\n TABLE_CREATE: 'table.create',\n TABLE_DROP: 'table.drop',\n COLUMN_ADD: 'column.add',\n COLUMN_DROP: 'column.drop',\n COLUMN_ALTER: 'column.alter',\n COLUMN_RENAME: 'column.rename',\n INDEX_CREATE: 'index.create',\n INDEX_DROP: 'index.drop',\n FK_CREATE: 'fk.create',\n FK_VALIDATE: 'fk.validate',\n FK_DROP: 'fk.drop',\n} as const;\n\nexport type InternalOperationKind = (typeof InternalOperationKind)[keyof typeof InternalOperationKind];\n"],"mappings":";;MAAa,wBAAwB;CACjC,cAAc;CACd,YAAY;CACZ,YAAY;CACZ,aAAa;CACb,cAAc;CACd,eAAe;CACf,cAAc;CACd,YAAY;CACZ,WAAW;CACX,aAAa;CACb,SAAS;AACZ"}
@@ -1,21 +1,30 @@
1
- import { InternalDialect } from "./CompilerStrategy-Cv1woBmO.js";
2
- import { PostgresIntrospector, SqliteIntrospector } from "./SqliteIntrospector-BRdNt6KG.js";
1
+ import { InternalDialect } from "./CompilerStrategy-yKw-Egxv.js";
2
+ import { PostgresIntrospector, SqliteIntrospector } from "./SqliteIntrospector-CWwPWhmA.js";
3
3
 
4
4
  //#region src/strategies/IntrospectorStrategy.ts
5
5
  var IntrospectorStrategy = class IntrospectorStrategy {
6
6
  static BRAND = "tango.migrations.introspector_strategy";
7
- introspectorCache = new Map();
8
7
  __tangoBrand = IntrospectorStrategy.BRAND;
8
+ introspectorCache = new Map();
9
9
  constructor(factories) {
10
10
  this.factories = factories;
11
11
  }
12
+ /**
13
+ * Narrow an unknown value to the dialect-aware schema introspection strategy.
14
+ */
12
15
  static isIntrospectorStrategy(value) {
13
16
  return typeof value === "object" && value !== null && value.__tangoBrand === IntrospectorStrategy.BRAND;
14
17
  }
18
+ /**
19
+ * Introspect database schema using a dialect-specific introspector.
20
+ */
15
21
  introspect(dialect, client) {
16
22
  const introspector = this.getIntrospector(dialect);
17
23
  return introspector.introspect(client);
18
24
  }
25
+ /**
26
+ * Resolve and cache an introspector instance for a dialect.
27
+ */
19
28
  getIntrospector(dialect) {
20
29
  const cached = this.introspectorCache.get(dialect);
21
30
  if (cached) return cached;
@@ -35,4 +44,4 @@ function createDefaultIntrospectorStrategy() {
35
44
 
36
45
  //#endregion
37
46
  export { IntrospectorStrategy, createDefaultIntrospectorStrategy };
38
- //# sourceMappingURL=IntrospectorStrategy-BM1Eizfc.js.map
47
+ //# sourceMappingURL=IntrospectorStrategy-blvwSU3_.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IntrospectorStrategy-blvwSU3_.js","names":["factories: IntrospectorFactoryRegistry","value: unknown","dialect: Dialect","client: DBClient"],"sources":["../src/strategies/IntrospectorStrategy.ts"],"sourcesContent":["import type { DBClient, DatabaseIntrospector } from '../introspect/DatabaseIntrospector';\nimport type { Dialect } from '../domain/Dialect';\nimport { InternalDialect } from '../domain/internal/InternalDialect';\nimport type { DbSchema } from '../introspect/PostgresIntrospector';\nimport { PostgresIntrospector } from '../introspect/PostgresIntrospector';\nimport { SqliteIntrospector } from '../introspect/SqliteIntrospector';\n\ntype IntrospectorFactory = {\n create(): DatabaseIntrospector;\n};\n\ntype IntrospectorFactoryRegistry = Record<Dialect, IntrospectorFactory>;\n\n/**\n * Dialect-aware schema introspection orchestration.\n */\nexport class IntrospectorStrategy {\n static readonly BRAND = 'tango.migrations.introspector_strategy' as const;\n readonly __tangoBrand: typeof IntrospectorStrategy.BRAND = IntrospectorStrategy.BRAND;\n private readonly introspectorCache = new Map<Dialect, DatabaseIntrospector>();\n\n constructor(private readonly factories: IntrospectorFactoryRegistry) {}\n\n /**\n * Narrow an unknown value to the dialect-aware schema introspection strategy.\n */\n static isIntrospectorStrategy(value: unknown): value is IntrospectorStrategy {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === IntrospectorStrategy.BRAND\n );\n }\n\n /**\n * Introspect database schema using a dialect-specific introspector.\n */\n introspect(dialect: Dialect, client: DBClient): Promise<DbSchema> {\n const introspector = this.getIntrospector(dialect);\n return introspector.introspect(client);\n }\n\n /**\n * Resolve and cache an introspector instance for a dialect.\n */\n getIntrospector(dialect: Dialect): DatabaseIntrospector {\n const cached = this.introspectorCache.get(dialect);\n if (cached) {\n return cached;\n }\n\n const factory = this.factories[dialect];\n if (!factory) {\n throw new Error(`No database introspector factory registered for dialect: ${String(dialect)}`);\n }\n\n const introspector = factory.create();\n this.introspectorCache.set(dialect, introspector);\n return introspector;\n }\n}\n\n/**\n * Create the default introspector strategy with built-in dialect support.\n */\nexport function createDefaultIntrospectorStrategy(): IntrospectorStrategy {\n return new IntrospectorStrategy({\n [InternalDialect.POSTGRES]: { create: () => new PostgresIntrospector() },\n [InternalDialect.SQLITE]: { create: () => new SqliteIntrospector() },\n });\n}\n"],"mappings":";;;;IAgBa,uBAAN,MAAM,qBAAqB;CAC9B,OAAgB,QAAQ;CACxB,eAA2D,qBAAqB;CAChF,oBAAqC,IAAI;CAEzC,YAA6BA,WAAwC;AAAA,OAAxC,YAAA;CAA0C;;;;CAKvE,OAAO,uBAAuBC,OAA+C;AACzE,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,qBAAqB;CAEnF;;;;CAKD,WAAWC,SAAkBC,QAAqC;EAC9D,MAAM,eAAe,KAAK,gBAAgB,QAAQ;AAClD,SAAO,aAAa,WAAW,OAAO;CACzC;;;;CAKD,gBAAgBD,SAAwC;EACpD,MAAM,SAAS,KAAK,kBAAkB,IAAI,QAAQ;AAClD,MAAI,OACA,QAAO;EAGX,MAAM,UAAU,KAAK,UAAU;AAC/B,OAAK,QACD,OAAM,IAAI,OAAO,2DAA2D,OAAO,QAAQ,CAAC;EAGhG,MAAM,eAAe,QAAQ,QAAQ;AACrC,OAAK,kBAAkB,IAAI,SAAS,aAAa;AACjD,SAAO;CACV;AACJ;AAKM,SAAS,oCAA0D;AACtE,QAAO,IAAI,qBAAqB;GAC3B,gBAAgB,WAAW,EAAE,QAAQ,MAAM,IAAI,uBAAwB;GACvE,gBAAgB,SAAS,EAAE,QAAQ,MAAM,IAAI,qBAAsB;CACvE;AACJ"}
@@ -0,0 +1,30 @@
1
+
2
+ //#region src/domain/Migration.ts
3
+ var Migration = class Migration {
4
+ static BRAND = "tango.migration";
5
+ static CONSTRUCTOR_BRAND = "tango.migration.constructor";
6
+ static __tangoConstructorBrand = Migration.CONSTRUCTOR_BRAND;
7
+ __tangoBrand = Migration.BRAND;
8
+ /** Optional execution mode override (`online`/`offline`). */
9
+ mode;
10
+ /**
11
+ * Narrow an unknown value to a migration instance.
12
+ */
13
+ static isMigration(value) {
14
+ return typeof value === "object" && value !== null && value.__tangoBrand === Migration.BRAND;
15
+ }
16
+ /**
17
+ * Narrow an unknown value to a migration constructor.
18
+ */
19
+ static isMigrationConstructor(value) {
20
+ if (typeof value !== "function") return false;
21
+ const prototype = value.prototype;
22
+ if (typeof prototype !== "object" || prototype === null) return false;
23
+ if (typeof prototype.up !== "function" || typeof prototype.down !== "function") return false;
24
+ return value.__tangoConstructorBrand === Migration.CONSTRUCTOR_BRAND;
25
+ }
26
+ };
27
+
28
+ //#endregion
29
+ export { Migration };
30
+ //# sourceMappingURL=Migration-DYQ0hUG7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Migration-DYQ0hUG7.js","names":["value: unknown"],"sources":["../src/domain/Migration.ts"],"sourcesContent":["import type { Builder } from '../builder/contracts/Builder';\nimport type { MigrationMode } from './MigrationMode';\n\n/**\n * Base migration contract.\n *\n * Concrete migrations provide a stable `id` and define reversible schema/data\n * operations through `up` and `down`.\n */\nexport abstract class Migration {\n static readonly BRAND = 'tango.migration' as const;\n static readonly CONSTRUCTOR_BRAND = 'tango.migration.constructor' as const;\n static readonly __tangoConstructorBrand: typeof Migration.CONSTRUCTOR_BRAND = Migration.CONSTRUCTOR_BRAND;\n readonly __tangoBrand: typeof Migration.BRAND = Migration.BRAND;\n\n abstract id: string;\n /** Optional execution mode override (`online`/`offline`). */\n mode?: MigrationMode;\n /** Apply migration operations. */\n abstract up(m: Builder): void | Promise<void>;\n /** Revert migration operations. */\n abstract down(m: Builder): void | Promise<void>;\n\n /**\n * Narrow an unknown value to a migration instance.\n */\n static isMigration(value: unknown): value is Migration {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === Migration.BRAND\n );\n }\n\n /**\n * Narrow an unknown value to a migration constructor.\n */\n static isMigrationConstructor(value: unknown): value is new () => Migration {\n if (typeof value !== 'function') {\n return false;\n }\n\n const prototype = (value as { prototype?: unknown }).prototype;\n if (typeof prototype !== 'object' || prototype === null) {\n return false;\n }\n\n if (\n typeof (prototype as { up?: unknown }).up !== 'function' ||\n typeof (prototype as { down?: unknown }).down !== 'function'\n ) {\n return false;\n }\n\n return (value as { __tangoConstructorBrand?: unknown }).__tangoConstructorBrand === Migration.CONSTRUCTOR_BRAND;\n }\n}\n"],"mappings":";;IASsB,YAAf,MAAe,UAAU;CAC5B,OAAgB,QAAQ;CACxB,OAAgB,oBAAoB;CACpC,OAAgB,0BAA8D,UAAU;CACxF,eAAgD,UAAU;;CAI1D;;;;CASA,OAAO,YAAYA,OAAoC;AACnD,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,UAAU;CAExE;;;;CAKD,OAAO,uBAAuBA,OAA8C;AACxE,aAAW,UAAU,WACjB,QAAO;EAGX,MAAM,YAAa,MAAkC;AACrD,aAAW,cAAc,YAAY,cAAc,KAC/C,QAAO;AAGX,aACY,UAA+B,OAAO,qBACtC,UAAiC,SAAS,WAElD,QAAO;AAGX,SAAQ,MAAgD,4BAA4B,UAAU;CACjG;AACJ"}
@@ -1,11 +1,15 @@
1
- import { InternalOperationKind } from "./InternalOperationKind-BPVoOQwD.js";
2
- import { join } from "node:path";
1
+ import { InternalOperationKind } from "./InternalOperationKind-Bt6Weuon.js";
3
2
  import { mkdir, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { isTrustedSqlFragment } from "@danceroutine/tango-core";
4
5
 
5
6
  //#region src/generator/MigrationGenerator.ts
6
7
  var MigrationGenerator = class MigrationGenerator {
7
8
  static BRAND = "tango.migrations.generator";
8
9
  __tangoBrand = MigrationGenerator.BRAND;
10
+ /**
11
+ * Narrow an unknown value to `MigrationGenerator`.
12
+ */
9
13
  static isMigrationGenerator(value) {
10
14
  return typeof value === "object" && value !== null && value.__tangoBrand === MigrationGenerator.BRAND;
11
15
  }
@@ -30,15 +34,16 @@ var MigrationGenerator = class MigrationGenerator {
30
34
  */
31
35
  render(id, operations) {
32
36
  const upOps = operations.map((operation) => this.renderOperation(operation));
33
- const downOps = operations.map((operation) => this.renderReverseOperation(operation)).reverse();
37
+ const downOps = operations.map((operation) => this.renderReverseOperation(operation));
38
+ downOps.reverse();
34
39
  const className = this.renderClassName(id);
35
40
  const lines = [
36
- `import { Migration, op } from '@danceroutine/tango-migrations';`,
41
+ `import { Migration, op, trustedSql, type Builder } from '@danceroutine/tango-migrations';`,
37
42
  ``,
38
43
  `export default class ${className} extends Migration {`,
39
44
  ` id = '${id}';`,
40
45
  ``,
41
- ` up(m) {`,
46
+ ` up(m: Builder) {`,
42
47
  ` m.run(`,
43
48
  ...upOps.map((code, index) => {
44
49
  const comma = index < upOps.length - 1 ? "," : "";
@@ -47,7 +52,7 @@ var MigrationGenerator = class MigrationGenerator {
47
52
  ` );`,
48
53
  ` }`,
49
54
  ``,
50
- ` down(m) {`,
55
+ ` down(m: Builder) {`,
51
56
  ` m.run(`,
52
57
  ...downOps.map((code, index) => {
53
58
  const comma = index < downOps.length - 1 ? "," : "";
@@ -126,8 +131,8 @@ var MigrationGenerator = class MigrationGenerator {
126
131
  if (operation.to.notNull !== undefined) parts.push(`notNull: ${operation.to.notNull}`);
127
132
  if (operation.to.default !== undefined) {
128
133
  if (operation.to.default === null) parts.push(`default: null`);
129
- else if (typeof operation.to.default === "string") parts.push(`default: '${operation.to.default}'`);
130
- else if (typeof operation.to.default === "object" && operation.to.default.now) parts.push(`default: { now: true }`);
134
+ else if (this.isNowDefault(operation.to.default)) parts.push(`default: { now: true }`);
135
+ else if (isTrustedSqlFragment(operation.to.default)) parts.push(`default: trustedSql(${JSON.stringify(operation.to.default.sql)})`);
131
136
  }
132
137
  return `op.table('${operation.table}').alterColumn('${operation.column}', { ${parts.join(", ")} })`;
133
138
  }
@@ -141,7 +146,7 @@ else if (typeof operation.to.default === "object" && operation.to.default.now) p
141
146
  `on: [${operation.on.map((c) => `'${c}'`).join(", ")}]`
142
147
  ];
143
148
  if (operation.unique) parts.push(`unique: true`);
144
- if (operation.where) parts.push(`where: '${operation.where}'`);
149
+ if (operation.where) parts.push(`where: trustedSql(${JSON.stringify(operation.where.sql)})`);
145
150
  if (operation.concurrently) parts.push(`concurrently: true`);
146
151
  return `op.index.create({ ${parts.join(", ")} })`;
147
152
  }
@@ -168,8 +173,8 @@ else if (typeof operation.to.default === "object" && operation.to.default.now) p
168
173
  if (col.type) parts.push(`.${col.type}()`);
169
174
  if (col.notNull) parts.push(`.notNull()`);
170
175
  if (col.default !== undefined && col.default !== null) {
171
- if (typeof col.default === "string") parts.push(`.default('${col.default}')`);
172
- else if (typeof col.default === "object" && col.default.now) parts.push(`.defaultNow()`);
176
+ if (this.isNowDefault(col.default)) parts.push(`.defaultNow()`);
177
+ else if (isTrustedSqlFragment(col.default)) parts.push(`.default(trustedSql(${JSON.stringify(col.default.sql)}))`);
173
178
  } else if (col.default === null) parts.push(`.default(null)`);
174
179
  if (col.primaryKey) parts.push(`.primaryKey()`);
175
180
  if (col.unique) parts.push(`.unique()`);
@@ -192,8 +197,11 @@ else if (typeof col.default === "object" && col.default.now) parts.push(`.defaul
192
197
  const seconds = String(now.getSeconds()).padStart(2, "0");
193
198
  return `${year}${month}${day}${hours}${minutes}${seconds}`;
194
199
  }
200
+ isNowDefault(value) {
201
+ return typeof value === "object" && value !== null && "now" in value && value.now === true;
202
+ }
195
203
  };
196
204
 
197
205
  //#endregion
198
206
  export { MigrationGenerator };
199
- //# sourceMappingURL=MigrationGenerator-Z39LTKmC.js.map
207
+ //# sourceMappingURL=MigrationGenerator-B1p0jHnx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MigrationGenerator-B1p0jHnx.js","names":["value: unknown","options: GenerateMigrationOptions","id: string","operations: MigrationOperation[]","code: string","index: number","operation: MigrationOperation","operation: TableCreate","operation: TableDrop","operation: ColumnAdd","operation: ColumnDrop","operation: ColumnAlter","parts: string[]","operation: ColumnRename","operation: IndexCreate","operation: IndexDrop","operation: ForeignKeyCreate","operation: ForeignKeyDrop","col: ColumnSpec","refParts: string[]","value: ColumnSpec['default']"],"sources":["../src/generator/MigrationGenerator.ts"],"sourcesContent":["import { isTrustedSqlFragment } from '@danceroutine/tango-core';\nimport type {\n MigrationOperation,\n TableCreate,\n TableDrop,\n ColumnAdd,\n ColumnDrop,\n ColumnAlter,\n ColumnRename,\n IndexCreate,\n IndexDrop,\n ForeignKeyCreate,\n ForeignKeyDrop,\n} from '../domain/MigrationOperation';\nimport type { ColumnSpec } from '../builder/contracts/ColumnSpec';\nimport { InternalOperationKind } from '../domain/internal/InternalOperationKind';\nimport { writeFile, mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\n/**\n * Input contract for generating a migration source file.\n */\nexport interface GenerateMigrationOptions {\n /** Human-readable suffix used in file name/id generation. */\n name: string;\n /** Ordered migration operations to render. */\n operations: MigrationOperation[];\n /** Output directory for generated migration files. */\n directory: string;\n}\n\n/**\n * Source generator for class-based migration files.\n */\nexport class MigrationGenerator {\n static readonly BRAND = 'tango.migrations.generator' as const;\n readonly __tangoBrand: typeof MigrationGenerator.BRAND = MigrationGenerator.BRAND;\n\n /**\n * Narrow an unknown value to `MigrationGenerator`.\n */\n static isMigrationGenerator(value: unknown): value is MigrationGenerator {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === MigrationGenerator.BRAND\n );\n }\n\n /**\n * Generate a migration file and write it to disk.\n * Returns the file path of the created migration.\n */\n async generate(options: GenerateMigrationOptions): Promise<string> {\n const { name, operations, directory } = options;\n\n if (operations.length === 0) {\n throw new Error('No operations to generate — models and database are in sync');\n }\n\n const timestamp = this.timestamp();\n const id = `${timestamp}_${name}`;\n const filename = `${id}.ts`;\n const filepath = join(directory, filename);\n\n const source = this.render(id, operations);\n\n await mkdir(directory, { recursive: true });\n await writeFile(filepath, source, 'utf-8');\n\n return filepath;\n }\n\n /**\n * Render migration operations to a TypeScript source string without writing to disk.\n */\n render(id: string, operations: MigrationOperation[]): string {\n const upOps = operations.map((operation) => this.renderOperation(operation));\n const downOps = operations.map((operation) => this.renderReverseOperation(operation));\n downOps.reverse();\n const className = this.renderClassName(id);\n\n const lines = [\n `import { Migration, op, trustedSql, type Builder } from '@danceroutine/tango-migrations';`,\n ``,\n `export default class ${className} extends Migration {`,\n ` id = '${id}';`,\n ``,\n ` up(m: Builder) {`,\n ` m.run(`,\n ...upOps.map((code: string, index: number) => {\n const comma = index < upOps.length - 1 ? ',' : '';\n return ` ${code}${comma}`;\n }),\n ` );`,\n ` }`,\n ``,\n ` down(m: Builder) {`,\n ` m.run(`,\n ...downOps.map((code: string, index: number) => {\n const comma = index < downOps.length - 1 ? ',' : '';\n return ` ${code}${comma}`;\n }),\n ` );`,\n ` }`,\n `}`,\n ``,\n ];\n\n return lines.join('\\n');\n }\n\n private renderClassName(id: string): string {\n const normalized = id.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^(\\d)/, '_$1');\n return `Migration_${normalized}`;\n }\n\n private renderOperation(operation: MigrationOperation): string {\n switch (operation.kind) {\n case InternalOperationKind.TABLE_CREATE:\n return this.renderTableCreate(operation);\n case InternalOperationKind.TABLE_DROP:\n return this.renderTableDrop(operation);\n case InternalOperationKind.COLUMN_ADD:\n return this.renderColumnAdd(operation);\n case InternalOperationKind.COLUMN_DROP:\n return this.renderColumnDrop(operation);\n case InternalOperationKind.COLUMN_ALTER:\n return this.renderColumnAlter(operation);\n case InternalOperationKind.COLUMN_RENAME:\n return this.renderColumnRename(operation);\n case InternalOperationKind.INDEX_CREATE:\n return this.renderIndexCreate(operation);\n case InternalOperationKind.INDEX_DROP:\n return this.renderIndexDrop(operation);\n case InternalOperationKind.FK_CREATE:\n return this.renderForeignKeyCreate(operation);\n case InternalOperationKind.FK_DROP:\n return this.renderForeignKeyDrop(operation);\n case InternalOperationKind.FK_VALIDATE:\n return `op.foreignKeyValidate({ table: '${operation.table}', name: '${operation.name}' })`;\n case 'custom':\n return `/* custom operation '${operation.name}' cannot be code-generated */`;\n default:\n return `/* unsupported operation */`;\n }\n }\n\n private renderReverseOperation(operation: MigrationOperation): string {\n switch (operation.kind) {\n case InternalOperationKind.TABLE_CREATE:\n return `op.table('${operation.table}').drop()`;\n case InternalOperationKind.TABLE_DROP:\n return `/* manual reverse required: recreate dropped table '${operation.table}' */`;\n case InternalOperationKind.COLUMN_ADD:\n return `op.table('${operation.table}').dropColumn('${operation.column.name}')`;\n case InternalOperationKind.COLUMN_DROP:\n return `/* manual reverse required: restore dropped column '${operation.column}' */`;\n case InternalOperationKind.COLUMN_ALTER:\n return `/* manual reverse required: revert ALTER COLUMN '${operation.column}' on '${operation.table}' */`;\n case InternalOperationKind.COLUMN_RENAME:\n return `op.table('${operation.table}').renameColumn('${operation.to}', '${operation.from}')`;\n case InternalOperationKind.INDEX_CREATE:\n return `op.index.drop({ name: '${operation.name}', table: '${operation.table}' })`;\n case InternalOperationKind.INDEX_DROP:\n return `/* manual reverse required: recreate dropped index '${operation.name}' */`;\n case InternalOperationKind.FK_CREATE:\n return `op.foreignKeyDrop({ table: '${operation.table}', name: '${operation.name ?? `${operation.table}_${operation.columns.join('_')}_fkey`}' })`;\n case InternalOperationKind.FK_DROP:\n return `/* manual reverse required: recreate dropped FK '${operation.name}' */`;\n case InternalOperationKind.FK_VALIDATE:\n return `/* no reverse needed for FK_VALIDATE */`;\n case 'custom':\n return `/* manual reverse required: custom operation '${operation.name}' */`;\n default:\n return `/* unsupported reverse operation */`;\n }\n }\n\n private renderTableCreate(operation: TableCreate): string {\n const columnLines = operation.columns.map((col) => {\n const chain = this.renderColumnChain(col);\n return ` cols.add('${col.name}', (b) => b${chain});`;\n });\n\n return [`op.table('${operation.table}').create((cols) => {`, ...columnLines, ` })`].join('\\n');\n }\n\n private renderTableDrop(operation: TableDrop): string {\n if (operation.cascade) {\n return `op.table('${operation.table}').drop({ cascade: true })`;\n }\n return `op.table('${operation.table}').drop()`;\n }\n\n private renderColumnAdd(operation: ColumnAdd): string {\n const chain = this.renderColumnChain(operation.column);\n return `op.table('${operation.table}').addColumn('${operation.column.name}', (b) => b${chain})`;\n }\n\n private renderColumnDrop(operation: ColumnDrop): string {\n return `op.table('${operation.table}').dropColumn('${operation.column}')`;\n }\n\n private renderColumnAlter(operation: ColumnAlter): string {\n const parts: string[] = [];\n if (operation.to.type) {\n parts.push(`type: '${operation.to.type}'`);\n }\n if (operation.to.notNull !== undefined) {\n parts.push(`notNull: ${operation.to.notNull}`);\n }\n if (operation.to.default !== undefined) {\n if (operation.to.default === null) {\n parts.push(`default: null`);\n } else if (this.isNowDefault(operation.to.default)) {\n parts.push(`default: { now: true }`);\n } else if (isTrustedSqlFragment(operation.to.default)) {\n parts.push(`default: trustedSql(${JSON.stringify(operation.to.default.sql)})`);\n }\n }\n return `op.table('${operation.table}').alterColumn('${operation.column}', { ${parts.join(', ')} })`;\n }\n\n private renderColumnRename(operation: ColumnRename): string {\n return `op.table('${operation.table}').renameColumn('${operation.from}', '${operation.to}')`;\n }\n\n private renderIndexCreate(operation: IndexCreate): string {\n const parts: string[] = [\n `name: '${operation.name}'`,\n `table: '${operation.table}'`,\n `on: [${operation.on.map((c) => `'${c}'`).join(', ')}]`,\n ];\n if (operation.unique) {\n parts.push(`unique: true`);\n }\n if (operation.where) {\n parts.push(`where: trustedSql(${JSON.stringify(operation.where.sql)})`);\n }\n if (operation.concurrently) {\n parts.push(`concurrently: true`);\n }\n return `op.index.create({ ${parts.join(', ')} })`;\n }\n\n private renderIndexDrop(operation: IndexDrop): string {\n return `op.index.drop({ name: '${operation.name}', table: '${operation.table}' })`;\n }\n\n private renderForeignKeyCreate(operation: ForeignKeyCreate): string {\n const parts: string[] = [\n `table: '${operation.table}'`,\n `columns: [${operation.columns.map((c) => `'${c}'`).join(', ')}]`,\n `references: { table: '${operation.refTable}', columns: [${operation.refColumns.map((c) => `'${c}'`).join(', ')}] }`,\n ];\n if (operation.name) {\n parts.push(`name: '${operation.name}'`);\n }\n if (operation.onDelete) {\n parts.push(`onDelete: '${operation.onDelete}'`);\n }\n if (operation.onUpdate) {\n parts.push(`onUpdate: '${operation.onUpdate}'`);\n }\n if (operation.notValid) {\n parts.push(`notValid: true`);\n }\n return `op.foreignKey({ ${parts.join(', ')} })`;\n }\n\n private renderForeignKeyDrop(operation: ForeignKeyDrop): string {\n return `op.foreignKeyDrop({ table: '${operation.table}', name: '${operation.name}' })`;\n }\n\n private renderColumnChain(col: ColumnSpec): string {\n const parts: string[] = [];\n\n if (col.type) {\n parts.push(`.${col.type}()`);\n }\n if (col.notNull) {\n parts.push(`.notNull()`);\n }\n if (col.default !== undefined && col.default !== null) {\n if (this.isNowDefault(col.default)) {\n parts.push(`.defaultNow()`);\n } else if (isTrustedSqlFragment(col.default)) {\n parts.push(`.default(trustedSql(${JSON.stringify(col.default.sql)}))`);\n }\n } else if (col.default === null) {\n parts.push(`.default(null)`);\n }\n if (col.primaryKey) {\n parts.push(`.primaryKey()`);\n }\n if (col.unique) {\n parts.push(`.unique()`);\n }\n if (col.references) {\n const refParts: string[] = [];\n if (col.references.onDelete) {\n refParts.push(`onDelete: '${col.references.onDelete}'`);\n }\n if (col.references.onUpdate) {\n refParts.push(`onUpdate: '${col.references.onUpdate}'`);\n }\n const opts = refParts.length > 0 ? `, { ${refParts.join(', ')} }` : '';\n parts.push(`.references('${col.references.table}', '${col.references.column}'${opts})`);\n }\n\n return parts.join('');\n }\n\n private timestamp(): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n const hours = String(now.getHours()).padStart(2, '0');\n const minutes = String(now.getMinutes()).padStart(2, '0');\n const seconds = String(now.getSeconds()).padStart(2, '0');\n return `${year}${month}${day}${hours}${minutes}${seconds}`;\n }\n\n private isNowDefault(value: ColumnSpec['default']): value is { now: true } {\n return typeof value === 'object' && value !== null && 'now' in value && value.now === true;\n }\n}\n"],"mappings":"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"}
@@ -1,21 +1,58 @@
1
- import { CollectingBuilder } from "./CollectingBuilder-C6qnwyrb.js";
2
- import { Migration } from "./Migration-D9J6ZbLP.js";
3
- import { InternalDialect, createDefaultCompilerStrategy } from "./CompilerStrategy-Cv1woBmO.js";
4
- import { resolve } from "node:path";
1
+ import { CollectingBuilder } from "./CollectingBuilder--4fqDQdE.js";
2
+ import { Migration } from "./Migration-DYQ0hUG7.js";
3
+ import { InternalDialect, createDefaultCompilerStrategy } from "./CompilerStrategy-yKw-Egxv.js";
5
4
  import { readdir } from "node:fs/promises";
5
+ import { extname, resolve, resolve as resolve$1 } from "node:path";
6
+ import { isError } from "@danceroutine/tango-core";
7
+ import { pathToFileURL } from "node:url";
8
+ import { createJiti } from "jiti";
6
9
 
10
+ //#region src/runtime/loadModule.ts
11
+ const TS_EXTENSIONS = new Set([
12
+ ".ts",
13
+ ".tsx",
14
+ ".mts",
15
+ ".cts"
16
+ ]);
17
+ function toAbsolutePath(modulePath, projectRoot) {
18
+ return resolve$1(projectRoot, modulePath);
19
+ }
20
+ function isTypeScriptModule(modulePath) {
21
+ return TS_EXTENSIONS.has(extname(modulePath).toLowerCase());
22
+ }
23
+ async function loadModule(modulePath, options) {
24
+ const projectRoot = options?.projectRoot ?? process.cwd();
25
+ const absolutePath = toAbsolutePath(modulePath, projectRoot);
26
+ if (isTypeScriptModule(absolutePath)) {
27
+ const jiti = createJiti(resolve$1(projectRoot, "tango.config.ts"), {
28
+ interopDefault: true,
29
+ moduleCache: true
30
+ });
31
+ return await jiti.import(absolutePath);
32
+ }
33
+ return await import(pathToFileURL(absolutePath).href);
34
+ }
35
+ async function loadDefaultExport(modulePath, options) {
36
+ const loaded = await loadModule(modulePath, options);
37
+ return loaded.default ?? loaded;
38
+ }
39
+
40
+ //#endregion
7
41
  //#region src/runner/MigrationRunner.ts
8
42
  const JOURNAL = "_tango_migrations";
9
43
  var MigrationRunner = class MigrationRunner {
10
44
  static BRAND = "tango.migrations.runner";
11
- compilerStrategy;
12
45
  __tangoBrand = MigrationRunner.BRAND;
46
+ compilerStrategy;
13
47
  constructor(client, dialect, migrationsDir = "migrations", compilerStrategy) {
14
48
  this.client = client;
15
49
  this.dialect = dialect;
16
50
  this.migrationsDir = migrationsDir;
17
51
  this.compilerStrategy = compilerStrategy ?? createDefaultCompilerStrategy();
18
52
  }
53
+ /**
54
+ * Narrow an unknown value to `MigrationRunner`.
55
+ */
19
56
  static isMigrationRunner(value) {
20
57
  return typeof value === "object" && value !== null && value.__tangoBrand === MigrationRunner.BRAND;
21
58
  }
@@ -86,8 +123,14 @@ var MigrationRunner = class MigrationRunner {
86
123
  const files = (await readdir(this.migrationsDir)).filter((f) => f.endsWith(".ts") || f.endsWith(".js")).sort();
87
124
  const migrations = [];
88
125
  for (const file of files) {
89
- const mod = await import(resolve(this.migrationsDir, file));
90
- const loaded = mod.default;
126
+ const absolutePath = resolve(this.migrationsDir, file);
127
+ let loaded;
128
+ try {
129
+ loaded = await loadDefaultExport(absolutePath, { projectRoot: process.cwd() });
130
+ } catch (error) {
131
+ const reason = isError(error) ? error.message : String(error);
132
+ throw new Error(`Failed to load migration module '${file}': ${reason}`, { cause: error });
133
+ }
91
134
  if (Migration.isMigration(loaded)) {
92
135
  migrations.push(loaded);
93
136
  continue;
@@ -140,5 +183,5 @@ var MigrationRunner = class MigrationRunner {
140
183
  };
141
184
 
142
185
  //#endregion
143
- export { MigrationRunner };
144
- //# sourceMappingURL=MigrationRunner-CCFuPUlr.js.map
186
+ export { MigrationRunner, loadModule };
187
+ //# sourceMappingURL=MigrationRunner-D1ZfbbS-.js.map