@danceroutine/tango-migrations 0.1.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.
- package/dist/CollectingBuilder-C6qnwyrb.js +28 -0
- package/dist/CollectingBuilder-C6qnwyrb.js.map +1 -0
- package/dist/CompilerStrategy-Cv1woBmO.js +55 -0
- package/dist/CompilerStrategy-Cv1woBmO.js.map +1 -0
- package/dist/InternalColumnType-_YAz7RqI.js +17 -0
- package/dist/InternalColumnType-_YAz7RqI.js.map +1 -0
- package/dist/InternalOperationKind-BPVoOQwD.js +20 -0
- package/dist/InternalOperationKind-BPVoOQwD.js.map +1 -0
- package/dist/IntrospectorStrategy-BM1Eizfc.js +38 -0
- package/dist/IntrospectorStrategy-BM1Eizfc.js.map +1 -0
- package/dist/Migration-D9J6ZbLP.js +25 -0
- package/dist/Migration-D9J6ZbLP.js.map +1 -0
- package/dist/MigrationGenerator-Z39LTKmC.js +199 -0
- package/dist/MigrationGenerator-Z39LTKmC.js.map +1 -0
- package/dist/MigrationRunner-CCFuPUlr.js +144 -0
- package/dist/MigrationRunner-CCFuPUlr.js.map +1 -0
- package/dist/SqliteCompilerFactory-DwMwO7xY.js +303 -0
- package/dist/SqliteCompilerFactory-DwMwO7xY.js.map +1 -0
- package/dist/SqliteIntrospector-BRdNt6KG.js +121 -0
- package/dist/SqliteIntrospector-BRdNt6KG.js.map +1 -0
- package/dist/builder/contracts/Builder.d.ts +11 -0
- package/dist/builder/contracts/ColumnSpec.d.ts +19 -0
- package/dist/builder/contracts/ColumnType.d.ts +2 -0
- package/dist/builder/contracts/DeleteReferentialAction.d.ts +2 -0
- package/dist/builder/contracts/UpdateReferentialAction.d.ts +2 -0
- package/dist/builder/contracts/index.d.ts +8 -0
- package/dist/builder/index.d.ts +10 -0
- package/dist/builder/index.js +6 -0
- package/dist/builder/ops/OpBuilder.d.ts +88 -0
- package/dist/builder/ops/OpBuilder.js +173 -0
- package/dist/builder/ops/index.d.ts +4 -0
- package/dist/builder/runtime/CollectingBuilder.d.ts +21 -0
- package/dist/builder/runtime/index.d.ts +4 -0
- package/dist/builder-Dtk8oP_Y.js +236 -0
- package/dist/builder-Dtk8oP_Y.js.map +1 -0
- package/dist/chunk-BkvOhyD0.js +12 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +178 -0
- package/dist/cli.js.map +1 -0
- package/dist/compilers/contracts/CompilerFactory.d.ts +4 -0
- package/dist/compilers/contracts/SQL.d.ts +4 -0
- package/dist/compilers/contracts/SQLCompiler.d.ts +5 -0
- package/dist/compilers/contracts/index.d.ts +6 -0
- package/dist/compilers/dialects/PostgresCompiler.d.ts +17 -0
- package/dist/compilers/dialects/SqliteCompiler.d.ts +10 -0
- package/dist/compilers/dialects/index.d.ts +5 -0
- package/dist/compilers/factories/PostgresCompilerFactory.d.ts +8 -0
- package/dist/compilers/factories/SqliteCompilerFactory.d.ts +8 -0
- package/dist/compilers/factories/index.d.ts +5 -0
- package/dist/compilers/index.d.ts +10 -0
- package/dist/compilers/index.js +6 -0
- package/dist/compilers-D8DJuTnQ.js +38 -0
- package/dist/compilers-D8DJuTnQ.js.map +1 -0
- package/dist/diff/diffSchema.d.ts +33 -0
- package/dist/diff/index.d.ts +4 -0
- package/dist/diff/index.js +8 -0
- package/dist/diff-Cs0TPEGR.js +10 -0
- package/dist/diff-Cs0TPEGR.js.map +1 -0
- package/dist/diffSchema-KgGHP-s3.js +86 -0
- package/dist/diffSchema-KgGHP-s3.js.map +1 -0
- package/dist/domain/Dialect.d.ts +2 -0
- package/dist/domain/Migration.d.ts +12 -0
- package/dist/domain/MigrationMode.d.ts +2 -0
- package/dist/domain/MigrationOperation.d.ts +76 -0
- package/dist/domain/MigrationOperation.js +1 -0
- package/dist/domain/index.d.ts +7 -0
- package/dist/domain/index.js +4 -0
- package/dist/domain/internal/InternalColumnType.d.ts +10 -0
- package/dist/domain/internal/InternalDialect.d.ts +4 -0
- package/dist/domain/internal/InternalMigrationMode.d.ts +4 -0
- package/dist/domain/internal/InternalOperationKind.d.ts +13 -0
- package/dist/domain/internal/InternalReferentialAction.d.ts +6 -0
- package/dist/domain-BXVlG0C0.js +10 -0
- package/dist/domain-BXVlG0C0.js.map +1 -0
- package/dist/generator/MigrationGenerator.d.ts +35 -0
- package/dist/generator/index.d.ts +4 -0
- package/dist/generator/index.js +5 -0
- package/dist/generator-3yC60b1u.js +10 -0
- package/dist/generator-3yC60b1u.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +21 -0
- package/dist/introspect/DatabaseIntrospector.d.ts +9 -0
- package/dist/introspect/PostgresIntrospector.d.ts +42 -0
- package/dist/introspect/SqliteIntrospector.d.ts +40 -0
- package/dist/introspect/index.d.ts +6 -0
- package/dist/introspect/index.js +4 -0
- package/dist/introspect-ks-QSodq.js +13 -0
- package/dist/introspect-ks-QSodq.js.map +1 -0
- package/dist/runner/MigrationRunner.d.ts +73 -0
- package/dist/runner/index.d.ts +4 -0
- package/dist/runner/index.js +10 -0
- package/dist/runner-BOs-tItW.js +10 -0
- package/dist/runner-BOs-tItW.js.map +1 -0
- package/dist/strategies/CompilerStrategy.d.ts +20 -0
- package/dist/strategies/IntrospectorStrategy.d.ts +19 -0
- package/dist/strategies/index.d.ts +5 -0
- package/dist/strategies/index.js +9 -0
- package/dist/strategies-BvHwf4as.js +16 -0
- package/dist/strategies-BvHwf4as.js.map +1 -0
- package/package.json +101 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/builder/runtime/CollectingBuilder.ts
|
|
3
|
+
var CollectingBuilder = class CollectingBuilder {
|
|
4
|
+
static BRAND = "tango.migrations.collecting_builder";
|
|
5
|
+
mode;
|
|
6
|
+
__tangoBrand = CollectingBuilder.BRAND;
|
|
7
|
+
ops = [];
|
|
8
|
+
dataFns = [];
|
|
9
|
+
static isCollectingBuilder(value) {
|
|
10
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === CollectingBuilder.BRAND;
|
|
11
|
+
}
|
|
12
|
+
run(...ops) {
|
|
13
|
+
this.ops.push(...ops);
|
|
14
|
+
}
|
|
15
|
+
data(fn) {
|
|
16
|
+
this.dataFns.push(fn);
|
|
17
|
+
}
|
|
18
|
+
options(o) {
|
|
19
|
+
this.mode = o.mode;
|
|
20
|
+
}
|
|
21
|
+
getMode() {
|
|
22
|
+
return this.mode;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { CollectingBuilder };
|
|
28
|
+
//# sourceMappingURL=CollectingBuilder-C6qnwyrb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CollectingBuilder-C6qnwyrb.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';\nexport class CollectingBuilder implements Builder {\n static readonly BRAND = 'tango.migrations.collecting_builder' as const;\n private mode?: MigrationMode;\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\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 run(...ops: MigrationOperation[]): void {\n this.ops.push(...ops);\n }\n\n data(fn: (ctx: { query(sql: string, params?: readonly unknown[]): Promise<void> }) => Promise<void>): void {\n this.dataFns.push(fn);\n }\n\n options(o: { mode?: MigrationMode }): void {\n this.mode = o.mode;\n }\n\n getMode(): MigrationMode | undefined {\n return this.mode;\n }\n}\n"],"mappings":";;IAGa,oBAAN,MAAM,kBAAqC;CAC9C,OAAgB,QAAQ;CACxB;CACA,eAAwD,kBAAkB;CAC1E,MAA4B,CAAE;CAC9B,UAA6G,CAAE;CAE/G,OAAO,oBAAoBA,OAA4C;AACnE,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,kBAAkB;CAEhF;CAED,IAAI,GAAG,KAAiC;AACpC,OAAK,IAAI,KAAK,GAAG,IAAI;CACxB;CAED,KAAKC,IAAsG;AACvG,OAAK,QAAQ,KAAK,GAAG;CACxB;CAED,QAAQC,GAAmC;AACvC,OAAK,OAAO,EAAE;CACjB;CAED,UAAqC;AACjC,SAAO,KAAK;CACf;AACJ"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { PostgresCompilerFactory, SqliteCompilerFactory } from "./SqliteCompilerFactory-DwMwO7xY.js";
|
|
2
|
+
|
|
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
|
+
}({});
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/strategies/CompilerStrategy.ts
|
|
12
|
+
var CompilerStrategy = class CompilerStrategy {
|
|
13
|
+
static BRAND = "tango.migrations.compiler_strategy";
|
|
14
|
+
compilerCache = new Map();
|
|
15
|
+
customHandlers = new Map();
|
|
16
|
+
__tangoBrand = CompilerStrategy.BRAND;
|
|
17
|
+
constructor(factories) {
|
|
18
|
+
this.factories = factories;
|
|
19
|
+
}
|
|
20
|
+
static isCompilerStrategy(value) {
|
|
21
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === CompilerStrategy.BRAND;
|
|
22
|
+
}
|
|
23
|
+
compile(dialect, operation) {
|
|
24
|
+
if (operation.kind === "custom") {
|
|
25
|
+
const handler = this.customHandlers.get(operation.name);
|
|
26
|
+
if (!handler) throw new Error(`Unsupported custom migration op: ${operation.name}`);
|
|
27
|
+
return handler(dialect, operation);
|
|
28
|
+
}
|
|
29
|
+
const compiler = this.getCompiler(dialect);
|
|
30
|
+
return compiler.compile(operation);
|
|
31
|
+
}
|
|
32
|
+
registerCustomHandler(name, handler) {
|
|
33
|
+
this.customHandlers.set(name, handler);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
getCompiler(dialect) {
|
|
37
|
+
const cached = this.compilerCache.get(dialect);
|
|
38
|
+
if (cached) return cached;
|
|
39
|
+
const factory = this.factories[dialect];
|
|
40
|
+
if (!factory) throw new Error(`No SQL compiler factory registered for dialect: ${String(dialect)}`);
|
|
41
|
+
const compiler = factory.create();
|
|
42
|
+
this.compilerCache.set(dialect, compiler);
|
|
43
|
+
return compiler;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
function createDefaultCompilerStrategy() {
|
|
47
|
+
return new CompilerStrategy({
|
|
48
|
+
[InternalDialect.POSTGRES]: new PostgresCompilerFactory(),
|
|
49
|
+
[InternalDialect.SQLITE]: new SqliteCompilerFactory()
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { CompilerStrategy, InternalDialect, createDefaultCompilerStrategy };
|
|
55
|
+
//# sourceMappingURL=CompilerStrategy-Cv1woBmO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompilerStrategy-Cv1woBmO.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 enum InternalDialect {\n POSTGRES = 'postgres',\n SQLITE = 'sqlite',\n}\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\nexport class CompilerStrategy {\n static readonly BRAND = 'tango.migrations.compiler_strategy' as const;\n private readonly compilerCache = new Map<Dialect, SQLCompiler>();\n private readonly customHandlers = new Map<string, (dialect: Dialect, op: CustomMigrationOperation) => SQL[]>();\n readonly __tangoBrand: typeof CompilerStrategy.BRAND = CompilerStrategy.BRAND;\n\n constructor(private readonly factories: CompilerFactoryRegistry) {}\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 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 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 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\nexport function createDefaultCompilerStrategy(): CompilerStrategy {\n return new CompilerStrategy({\n [InternalDialect.POSTGRES]: new PostgresCompilerFactory(),\n [InternalDialect.SQLITE]: new SqliteCompilerFactory(),\n });\n}\n"],"mappings":";;;IAAY,kBAAA,SAAA,mBAAL;AACH,mBAAA,cAAA;AACA,mBAAA,YAAA;AAAA,QAAA;AACH,EAAA,CAAA,EAAA;;;;ICQY,mBAAN,MAAM,iBAAiB;CAC1B,OAAgB,QAAQ;CACxB,gBAAiC,IAAI;CACrC,iBAAkC,IAAI;CACtC,eAAuD,iBAAiB;CAExE,YAA6BA,WAAoC;AAAA,OAApC,YAAA;CAAsC;CAEnE,OAAO,mBAAmBC,OAA2C;AACjE,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,iBAAiB;CAE/E;CAED,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;CAED,sBACIC,MACAC,SACI;AACJ,OAAK,eAAe,IAAI,MAAM,QAAqE;AACnG,SAAO;CACV;CAED,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;AAEM,SAAS,gCAAkD;AAC9D,QAAO,IAAI,iBAAiB;GACvB,gBAAgB,WAAW,IAAI;GAC/B,gBAAgB,SAAS,IAAI;CACjC;AACJ"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/domain/internal/InternalColumnType.ts
|
|
3
|
+
let InternalColumnType = function(InternalColumnType$1) {
|
|
4
|
+
InternalColumnType$1["SERIAL"] = "serial";
|
|
5
|
+
InternalColumnType$1["INT"] = "int";
|
|
6
|
+
InternalColumnType$1["BIGINT"] = "bigint";
|
|
7
|
+
InternalColumnType$1["TEXT"] = "text";
|
|
8
|
+
InternalColumnType$1["BOOL"] = "bool";
|
|
9
|
+
InternalColumnType$1["TIMESTAMPTZ"] = "timestamptz";
|
|
10
|
+
InternalColumnType$1["JSONB"] = "jsonb";
|
|
11
|
+
InternalColumnType$1["UUID"] = "uuid";
|
|
12
|
+
return InternalColumnType$1;
|
|
13
|
+
}({});
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { InternalColumnType };
|
|
17
|
+
//# sourceMappingURL=InternalColumnType-_YAz7RqI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InternalColumnType-_YAz7RqI.js","names":[],"sources":["../src/domain/internal/InternalColumnType.ts"],"sourcesContent":["export enum 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}\n"],"mappings":";;IAAY,qBAAA,SAAA,sBAAL;AACH,sBAAA,YAAA;AACA,sBAAA,SAAA;AACA,sBAAA,YAAA;AACA,sBAAA,UAAA;AACA,sBAAA,UAAA;AACA,sBAAA,iBAAA;AACA,sBAAA,WAAA;AACA,sBAAA,UAAA;AAAA,QAAA;AACH,EAAA,CAAA,EAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/domain/internal/InternalOperationKind.ts
|
|
3
|
+
let InternalOperationKind = function(InternalOperationKind$1) {
|
|
4
|
+
InternalOperationKind$1["TABLE_CREATE"] = "table.create";
|
|
5
|
+
InternalOperationKind$1["TABLE_DROP"] = "table.drop";
|
|
6
|
+
InternalOperationKind$1["COLUMN_ADD"] = "column.add";
|
|
7
|
+
InternalOperationKind$1["COLUMN_DROP"] = "column.drop";
|
|
8
|
+
InternalOperationKind$1["COLUMN_ALTER"] = "column.alter";
|
|
9
|
+
InternalOperationKind$1["COLUMN_RENAME"] = "column.rename";
|
|
10
|
+
InternalOperationKind$1["INDEX_CREATE"] = "index.create";
|
|
11
|
+
InternalOperationKind$1["INDEX_DROP"] = "index.drop";
|
|
12
|
+
InternalOperationKind$1["FK_CREATE"] = "fk.create";
|
|
13
|
+
InternalOperationKind$1["FK_VALIDATE"] = "fk.validate";
|
|
14
|
+
InternalOperationKind$1["FK_DROP"] = "fk.drop";
|
|
15
|
+
return InternalOperationKind$1;
|
|
16
|
+
}({});
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { InternalOperationKind };
|
|
20
|
+
//# sourceMappingURL=InternalOperationKind-BPVoOQwD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InternalOperationKind-BPVoOQwD.js","names":[],"sources":["../src/domain/internal/InternalOperationKind.ts"],"sourcesContent":["export enum 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}\n"],"mappings":";;IAAY,wBAAA,SAAA,yBAAL;AACH,yBAAA,kBAAA;AACA,yBAAA,gBAAA;AACA,yBAAA,gBAAA;AACA,yBAAA,iBAAA;AACA,yBAAA,kBAAA;AACA,yBAAA,mBAAA;AACA,yBAAA,kBAAA;AACA,yBAAA,gBAAA;AACA,yBAAA,eAAA;AACA,yBAAA,iBAAA;AACA,yBAAA,aAAA;AAAA,QAAA;AACH,EAAA,CAAA,EAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { InternalDialect } from "./CompilerStrategy-Cv1woBmO.js";
|
|
2
|
+
import { PostgresIntrospector, SqliteIntrospector } from "./SqliteIntrospector-BRdNt6KG.js";
|
|
3
|
+
|
|
4
|
+
//#region src/strategies/IntrospectorStrategy.ts
|
|
5
|
+
var IntrospectorStrategy = class IntrospectorStrategy {
|
|
6
|
+
static BRAND = "tango.migrations.introspector_strategy";
|
|
7
|
+
introspectorCache = new Map();
|
|
8
|
+
__tangoBrand = IntrospectorStrategy.BRAND;
|
|
9
|
+
constructor(factories) {
|
|
10
|
+
this.factories = factories;
|
|
11
|
+
}
|
|
12
|
+
static isIntrospectorStrategy(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === IntrospectorStrategy.BRAND;
|
|
14
|
+
}
|
|
15
|
+
introspect(dialect, client) {
|
|
16
|
+
const introspector = this.getIntrospector(dialect);
|
|
17
|
+
return introspector.introspect(client);
|
|
18
|
+
}
|
|
19
|
+
getIntrospector(dialect) {
|
|
20
|
+
const cached = this.introspectorCache.get(dialect);
|
|
21
|
+
if (cached) return cached;
|
|
22
|
+
const factory = this.factories[dialect];
|
|
23
|
+
if (!factory) throw new Error(`No database introspector factory registered for dialect: ${String(dialect)}`);
|
|
24
|
+
const introspector = factory.create();
|
|
25
|
+
this.introspectorCache.set(dialect, introspector);
|
|
26
|
+
return introspector;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function createDefaultIntrospectorStrategy() {
|
|
30
|
+
return new IntrospectorStrategy({
|
|
31
|
+
[InternalDialect.POSTGRES]: { create: () => new PostgresIntrospector() },
|
|
32
|
+
[InternalDialect.SQLITE]: { create: () => new SqliteIntrospector() }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { IntrospectorStrategy, createDefaultIntrospectorStrategy };
|
|
38
|
+
//# sourceMappingURL=IntrospectorStrategy-BM1Eizfc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntrospectorStrategy-BM1Eizfc.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>;\nexport class IntrospectorStrategy {\n static readonly BRAND = 'tango.migrations.introspector_strategy' as const;\n private readonly introspectorCache = new Map<Dialect, DatabaseIntrospector>();\n readonly __tangoBrand: typeof IntrospectorStrategy.BRAND = IntrospectorStrategy.BRAND;\n\n constructor(private readonly factories: IntrospectorFactoryRegistry) {}\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 introspect(dialect: Dialect, client: DBClient): Promise<DbSchema> {\n const introspector = this.getIntrospector(dialect);\n return introspector.introspect(client);\n }\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\nexport function createDefaultIntrospectorStrategy(): IntrospectorStrategy {\n return new IntrospectorStrategy({\n [InternalDialect.POSTGRES]: { create: () => new PostgresIntrospector() },\n [InternalDialect.SQLITE]: { create: () => new SqliteIntrospector() },\n });\n}\n"],"mappings":";;;;IAYa,uBAAN,MAAM,qBAAqB;CAC9B,OAAgB,QAAQ;CACxB,oBAAqC,IAAI;CACzC,eAA2D,qBAAqB;CAEhF,YAA6BA,WAAwC;AAAA,OAAxC,YAAA;CAA0C;CAEvE,OAAO,uBAAuBC,OAA+C;AACzE,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,qBAAqB;CAEnF;CAED,WAAWC,SAAkBC,QAAqC;EAC9D,MAAM,eAAe,KAAK,gBAAgB,QAAQ;AAClD,SAAO,aAAa,WAAW,OAAO;CACzC;CAED,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;AAEM,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,25 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/domain/Migration.ts
|
|
3
|
+
var Migration = class Migration {
|
|
4
|
+
static BRAND = "tango.migration";
|
|
5
|
+
__tangoBrand = Migration.BRAND;
|
|
6
|
+
mode;
|
|
7
|
+
static isMigration(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === Migration.BRAND;
|
|
9
|
+
}
|
|
10
|
+
static isMigrationConstructor(value) {
|
|
11
|
+
if (typeof value !== "function") return false;
|
|
12
|
+
const prototype = value.prototype;
|
|
13
|
+
if (typeof prototype !== "object" || prototype === null) return false;
|
|
14
|
+
let current = prototype;
|
|
15
|
+
while (current) {
|
|
16
|
+
if (current === Migration.prototype) return true;
|
|
17
|
+
current = Object.getPrototypeOf(current);
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { Migration };
|
|
25
|
+
//# sourceMappingURL=Migration-D9J6ZbLP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Migration-D9J6ZbLP.js","names":["value: unknown","current: object | null"],"sources":["../src/domain/Migration.ts"],"sourcesContent":["import type { Builder } from '../builder/contracts/Builder';\nimport type { MigrationMode } from './MigrationMode';\n\nexport abstract class Migration {\n static readonly BRAND = 'tango.migration' as const;\n readonly __tangoBrand: typeof Migration.BRAND = Migration.BRAND;\n\n abstract id: string;\n mode?: MigrationMode;\n abstract up(m: Builder): void | Promise<void>;\n abstract down(m: Builder): void | Promise<void>;\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 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 let current: object | null = prototype as object;\n while (current) {\n if (current === Migration.prototype) {\n return true;\n }\n current = Object.getPrototypeOf(current) as object | null;\n }\n return false;\n }\n}\n"],"mappings":";;IAGsB,YAAf,MAAe,UAAU;CAC5B,OAAgB,QAAQ;CACxB,eAAgD,UAAU;CAG1D;CAIA,OAAO,YAAYA,OAAoC;AACnD,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,UAAU;CAExE;CAED,OAAO,uBAAuBA,OAA8C;AACxE,aAAW,UAAU,WACjB,QAAO;EAGX,MAAM,YAAa,MAAkC;AACrD,aAAW,cAAc,YAAY,cAAc,KAC/C,QAAO;EAEX,IAAIC,UAAyB;AAC7B,SAAO,SAAS;AACZ,OAAI,YAAY,UAAU,UACtB,QAAO;AAEX,aAAU,OAAO,eAAe,QAAQ;EAC3C;AACD,SAAO;CACV;AACJ"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { InternalOperationKind } from "./InternalOperationKind-BPVoOQwD.js";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
|
|
5
|
+
//#region src/generator/MigrationGenerator.ts
|
|
6
|
+
var MigrationGenerator = class MigrationGenerator {
|
|
7
|
+
static BRAND = "tango.migrations.generator";
|
|
8
|
+
__tangoBrand = MigrationGenerator.BRAND;
|
|
9
|
+
static isMigrationGenerator(value) {
|
|
10
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === MigrationGenerator.BRAND;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate a migration file and write it to disk.
|
|
14
|
+
* Returns the file path of the created migration.
|
|
15
|
+
*/
|
|
16
|
+
async generate(options) {
|
|
17
|
+
const { name, operations, directory } = options;
|
|
18
|
+
if (operations.length === 0) throw new Error("No operations to generate — models and database are in sync");
|
|
19
|
+
const timestamp = this.timestamp();
|
|
20
|
+
const id = `${timestamp}_${name}`;
|
|
21
|
+
const filename = `${id}.ts`;
|
|
22
|
+
const filepath = join(directory, filename);
|
|
23
|
+
const source = this.render(id, operations);
|
|
24
|
+
await mkdir(directory, { recursive: true });
|
|
25
|
+
await writeFile(filepath, source, "utf-8");
|
|
26
|
+
return filepath;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Render migration operations to a TypeScript source string without writing to disk.
|
|
30
|
+
*/
|
|
31
|
+
render(id, operations) {
|
|
32
|
+
const upOps = operations.map((operation) => this.renderOperation(operation));
|
|
33
|
+
const downOps = operations.map((operation) => this.renderReverseOperation(operation)).reverse();
|
|
34
|
+
const className = this.renderClassName(id);
|
|
35
|
+
const lines = [
|
|
36
|
+
`import { Migration, op } from '@danceroutine/tango-migrations';`,
|
|
37
|
+
``,
|
|
38
|
+
`export default class ${className} extends Migration {`,
|
|
39
|
+
` id = '${id}';`,
|
|
40
|
+
``,
|
|
41
|
+
` up(m) {`,
|
|
42
|
+
` m.run(`,
|
|
43
|
+
...upOps.map((code, index) => {
|
|
44
|
+
const comma = index < upOps.length - 1 ? "," : "";
|
|
45
|
+
return ` ${code}${comma}`;
|
|
46
|
+
}),
|
|
47
|
+
` );`,
|
|
48
|
+
` }`,
|
|
49
|
+
``,
|
|
50
|
+
` down(m) {`,
|
|
51
|
+
` m.run(`,
|
|
52
|
+
...downOps.map((code, index) => {
|
|
53
|
+
const comma = index < downOps.length - 1 ? "," : "";
|
|
54
|
+
return ` ${code}${comma}`;
|
|
55
|
+
}),
|
|
56
|
+
` );`,
|
|
57
|
+
` }`,
|
|
58
|
+
`}`,
|
|
59
|
+
``
|
|
60
|
+
];
|
|
61
|
+
return lines.join("\n");
|
|
62
|
+
}
|
|
63
|
+
renderClassName(id) {
|
|
64
|
+
const normalized = id.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^(\d)/, "_$1");
|
|
65
|
+
return `Migration_${normalized}`;
|
|
66
|
+
}
|
|
67
|
+
renderOperation(operation) {
|
|
68
|
+
switch (operation.kind) {
|
|
69
|
+
case InternalOperationKind.TABLE_CREATE: return this.renderTableCreate(operation);
|
|
70
|
+
case InternalOperationKind.TABLE_DROP: return this.renderTableDrop(operation);
|
|
71
|
+
case InternalOperationKind.COLUMN_ADD: return this.renderColumnAdd(operation);
|
|
72
|
+
case InternalOperationKind.COLUMN_DROP: return this.renderColumnDrop(operation);
|
|
73
|
+
case InternalOperationKind.COLUMN_ALTER: return this.renderColumnAlter(operation);
|
|
74
|
+
case InternalOperationKind.COLUMN_RENAME: return this.renderColumnRename(operation);
|
|
75
|
+
case InternalOperationKind.INDEX_CREATE: return this.renderIndexCreate(operation);
|
|
76
|
+
case InternalOperationKind.INDEX_DROP: return this.renderIndexDrop(operation);
|
|
77
|
+
case InternalOperationKind.FK_CREATE: return this.renderForeignKeyCreate(operation);
|
|
78
|
+
case InternalOperationKind.FK_DROP: return this.renderForeignKeyDrop(operation);
|
|
79
|
+
case InternalOperationKind.FK_VALIDATE: return `op.foreignKeyValidate({ table: '${operation.table}', name: '${operation.name}' })`;
|
|
80
|
+
case "custom": return `/* custom operation '${operation.name}' cannot be code-generated */`;
|
|
81
|
+
default: return `/* unsupported operation */`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
renderReverseOperation(operation) {
|
|
85
|
+
switch (operation.kind) {
|
|
86
|
+
case InternalOperationKind.TABLE_CREATE: return `op.table('${operation.table}').drop()`;
|
|
87
|
+
case InternalOperationKind.TABLE_DROP: return `/* manual reverse required: recreate dropped table '${operation.table}' */`;
|
|
88
|
+
case InternalOperationKind.COLUMN_ADD: return `op.table('${operation.table}').dropColumn('${operation.column.name}')`;
|
|
89
|
+
case InternalOperationKind.COLUMN_DROP: return `/* manual reverse required: restore dropped column '${operation.column}' */`;
|
|
90
|
+
case InternalOperationKind.COLUMN_ALTER: return `/* manual reverse required: revert ALTER COLUMN '${operation.column}' on '${operation.table}' */`;
|
|
91
|
+
case InternalOperationKind.COLUMN_RENAME: return `op.table('${operation.table}').renameColumn('${operation.to}', '${operation.from}')`;
|
|
92
|
+
case InternalOperationKind.INDEX_CREATE: return `op.index.drop({ name: '${operation.name}', table: '${operation.table}' })`;
|
|
93
|
+
case InternalOperationKind.INDEX_DROP: return `/* manual reverse required: recreate dropped index '${operation.name}' */`;
|
|
94
|
+
case InternalOperationKind.FK_CREATE: return `op.foreignKeyDrop({ table: '${operation.table}', name: '${operation.name ?? `${operation.table}_${operation.columns.join("_")}_fkey`}' })`;
|
|
95
|
+
case InternalOperationKind.FK_DROP: return `/* manual reverse required: recreate dropped FK '${operation.name}' */`;
|
|
96
|
+
case InternalOperationKind.FK_VALIDATE: return `/* no reverse needed for FK_VALIDATE */`;
|
|
97
|
+
case "custom": return `/* manual reverse required: custom operation '${operation.name}' */`;
|
|
98
|
+
default: return `/* unsupported reverse operation */`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
renderTableCreate(operation) {
|
|
102
|
+
const columnLines = operation.columns.map((col) => {
|
|
103
|
+
const chain = this.renderColumnChain(col);
|
|
104
|
+
return ` cols.add('${col.name}', (b) => b${chain});`;
|
|
105
|
+
});
|
|
106
|
+
return [
|
|
107
|
+
`op.table('${operation.table}').create((cols) => {`,
|
|
108
|
+
...columnLines,
|
|
109
|
+
` })`
|
|
110
|
+
].join("\n");
|
|
111
|
+
}
|
|
112
|
+
renderTableDrop(operation) {
|
|
113
|
+
if (operation.cascade) return `op.table('${operation.table}').drop({ cascade: true })`;
|
|
114
|
+
return `op.table('${operation.table}').drop()`;
|
|
115
|
+
}
|
|
116
|
+
renderColumnAdd(operation) {
|
|
117
|
+
const chain = this.renderColumnChain(operation.column);
|
|
118
|
+
return `op.table('${operation.table}').addColumn('${operation.column.name}', (b) => b${chain})`;
|
|
119
|
+
}
|
|
120
|
+
renderColumnDrop(operation) {
|
|
121
|
+
return `op.table('${operation.table}').dropColumn('${operation.column}')`;
|
|
122
|
+
}
|
|
123
|
+
renderColumnAlter(operation) {
|
|
124
|
+
const parts = [];
|
|
125
|
+
if (operation.to.type) parts.push(`type: '${operation.to.type}'`);
|
|
126
|
+
if (operation.to.notNull !== undefined) parts.push(`notNull: ${operation.to.notNull}`);
|
|
127
|
+
if (operation.to.default !== undefined) {
|
|
128
|
+
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 }`);
|
|
131
|
+
}
|
|
132
|
+
return `op.table('${operation.table}').alterColumn('${operation.column}', { ${parts.join(", ")} })`;
|
|
133
|
+
}
|
|
134
|
+
renderColumnRename(operation) {
|
|
135
|
+
return `op.table('${operation.table}').renameColumn('${operation.from}', '${operation.to}')`;
|
|
136
|
+
}
|
|
137
|
+
renderIndexCreate(operation) {
|
|
138
|
+
const parts = [
|
|
139
|
+
`name: '${operation.name}'`,
|
|
140
|
+
`table: '${operation.table}'`,
|
|
141
|
+
`on: [${operation.on.map((c) => `'${c}'`).join(", ")}]`
|
|
142
|
+
];
|
|
143
|
+
if (operation.unique) parts.push(`unique: true`);
|
|
144
|
+
if (operation.where) parts.push(`where: '${operation.where}'`);
|
|
145
|
+
if (operation.concurrently) parts.push(`concurrently: true`);
|
|
146
|
+
return `op.index.create({ ${parts.join(", ")} })`;
|
|
147
|
+
}
|
|
148
|
+
renderIndexDrop(operation) {
|
|
149
|
+
return `op.index.drop({ name: '${operation.name}', table: '${operation.table}' })`;
|
|
150
|
+
}
|
|
151
|
+
renderForeignKeyCreate(operation) {
|
|
152
|
+
const parts = [
|
|
153
|
+
`table: '${operation.table}'`,
|
|
154
|
+
`columns: [${operation.columns.map((c) => `'${c}'`).join(", ")}]`,
|
|
155
|
+
`references: { table: '${operation.refTable}', columns: [${operation.refColumns.map((c) => `'${c}'`).join(", ")}] }`
|
|
156
|
+
];
|
|
157
|
+
if (operation.name) parts.push(`name: '${operation.name}'`);
|
|
158
|
+
if (operation.onDelete) parts.push(`onDelete: '${operation.onDelete}'`);
|
|
159
|
+
if (operation.onUpdate) parts.push(`onUpdate: '${operation.onUpdate}'`);
|
|
160
|
+
if (operation.notValid) parts.push(`notValid: true`);
|
|
161
|
+
return `op.foreignKey({ ${parts.join(", ")} })`;
|
|
162
|
+
}
|
|
163
|
+
renderForeignKeyDrop(operation) {
|
|
164
|
+
return `op.foreignKeyDrop({ table: '${operation.table}', name: '${operation.name}' })`;
|
|
165
|
+
}
|
|
166
|
+
renderColumnChain(col) {
|
|
167
|
+
const parts = [];
|
|
168
|
+
if (col.type) parts.push(`.${col.type}()`);
|
|
169
|
+
if (col.notNull) parts.push(`.notNull()`);
|
|
170
|
+
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()`);
|
|
173
|
+
} else if (col.default === null) parts.push(`.default(null)`);
|
|
174
|
+
if (col.primaryKey) parts.push(`.primaryKey()`);
|
|
175
|
+
if (col.unique) parts.push(`.unique()`);
|
|
176
|
+
if (col.references) {
|
|
177
|
+
const refParts = [];
|
|
178
|
+
if (col.references.onDelete) refParts.push(`onDelete: '${col.references.onDelete}'`);
|
|
179
|
+
if (col.references.onUpdate) refParts.push(`onUpdate: '${col.references.onUpdate}'`);
|
|
180
|
+
const opts = refParts.length > 0 ? `, { ${refParts.join(", ")} }` : "";
|
|
181
|
+
parts.push(`.references('${col.references.table}', '${col.references.column}'${opts})`);
|
|
182
|
+
}
|
|
183
|
+
return parts.join("");
|
|
184
|
+
}
|
|
185
|
+
timestamp() {
|
|
186
|
+
const now = new Date();
|
|
187
|
+
const year = now.getFullYear();
|
|
188
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
189
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
190
|
+
const hours = String(now.getHours()).padStart(2, "0");
|
|
191
|
+
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
192
|
+
const seconds = String(now.getSeconds()).padStart(2, "0");
|
|
193
|
+
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
//#endregion
|
|
198
|
+
export { MigrationGenerator };
|
|
199
|
+
//# sourceMappingURL=MigrationGenerator-Z39LTKmC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MigrationGenerator-Z39LTKmC.js","names":["value: unknown","options: GenerateMigrationOptions","id: string","operations: MigrationOperation[]","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[]"],"sources":["../src/generator/MigrationGenerator.ts"],"sourcesContent":["import 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\nexport interface GenerateMigrationOptions {\n name: string;\n operations: MigrationOperation[];\n directory: string;\n}\nexport class MigrationGenerator {\n static readonly BRAND = 'tango.migrations.generator' as const;\n readonly __tangoBrand: typeof MigrationGenerator.BRAND = MigrationGenerator.BRAND;\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)).reverse();\n const className = this.renderClassName(id);\n\n const lines = [\n `import { Migration, op } from '@danceroutine/tango-migrations';`,\n ``,\n `export default class ${className} extends Migration {`,\n ` id = '${id}';`,\n ``,\n ` up(m) {`,\n ` m.run(`,\n ...upOps.map((code, index) => {\n const comma = index < upOps.length - 1 ? ',' : '';\n return ` ${code}${comma}`;\n }),\n ` );`,\n ` }`,\n ``,\n ` down(m) {`,\n ` m.run(`,\n ...downOps.map((code, index) => {\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 (typeof operation.to.default === 'string') {\n parts.push(`default: '${operation.to.default}'`);\n } else if (typeof operation.to.default === 'object' && operation.to.default.now) {\n parts.push(`default: { now: true }`);\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: '${operation.where}'`);\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 (typeof col.default === 'string') {\n parts.push(`.default('${col.default}')`);\n } else if (typeof col.default === 'object' && col.default.now) {\n parts.push(`.defaultNow()`);\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"],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
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";
|
|
5
|
+
import { readdir } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
//#region src/runner/MigrationRunner.ts
|
|
8
|
+
const JOURNAL = "_tango_migrations";
|
|
9
|
+
var MigrationRunner = class MigrationRunner {
|
|
10
|
+
static BRAND = "tango.migrations.runner";
|
|
11
|
+
compilerStrategy;
|
|
12
|
+
__tangoBrand = MigrationRunner.BRAND;
|
|
13
|
+
constructor(client, dialect, migrationsDir = "migrations", compilerStrategy) {
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.dialect = dialect;
|
|
16
|
+
this.migrationsDir = migrationsDir;
|
|
17
|
+
this.compilerStrategy = compilerStrategy ?? createDefaultCompilerStrategy();
|
|
18
|
+
}
|
|
19
|
+
static isMigrationRunner(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === MigrationRunner.BRAND;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Apply all pending migrations, optionally stopping at a specific migration ID.
|
|
24
|
+
* Migrations are applied in file-sort order. Already-applied migrations are skipped.
|
|
25
|
+
* Non-online migrations are wrapped in a transaction on Postgres.
|
|
26
|
+
*/
|
|
27
|
+
async apply(toId) {
|
|
28
|
+
await this.ensureJournal();
|
|
29
|
+
const applied = await this.listApplied();
|
|
30
|
+
const migrations = await this.loadMigrations();
|
|
31
|
+
for (const migration of migrations) {
|
|
32
|
+
if (toId && migration.id > toId) break;
|
|
33
|
+
if (applied.has(migration.id)) continue;
|
|
34
|
+
await this.applyMigration(migration);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Generate a dry-run SQL plan for all migrations without executing anything.
|
|
39
|
+
* Useful for reviewing what SQL would be run before applying.
|
|
40
|
+
*/
|
|
41
|
+
async plan() {
|
|
42
|
+
const migrations = await this.loadMigrations();
|
|
43
|
+
let output = "";
|
|
44
|
+
migrations.forEach((migration) => {
|
|
45
|
+
const builder = new CollectingBuilder();
|
|
46
|
+
migration.up(builder);
|
|
47
|
+
const sqls = builder.ops.flatMap((op) => this.compileOperation(op));
|
|
48
|
+
output += `# ${migration.id}\n`;
|
|
49
|
+
sqls.forEach((statement) => {
|
|
50
|
+
output += statement.sql + ";\n";
|
|
51
|
+
});
|
|
52
|
+
if (builder.dataFns.length) output += "-- (data step present)\n";
|
|
53
|
+
output += "\n";
|
|
54
|
+
});
|
|
55
|
+
return output;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Return the applied/pending status of every migration found on disk.
|
|
59
|
+
*/
|
|
60
|
+
async status() {
|
|
61
|
+
const applied = await this.listApplied();
|
|
62
|
+
const migrations = await this.loadMigrations();
|
|
63
|
+
return migrations.map((m) => ({
|
|
64
|
+
id: m.id,
|
|
65
|
+
applied: applied.has(m.id)
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
async ensureJournal() {
|
|
69
|
+
const sql = this.dialect === InternalDialect.POSTGRES ? `CREATE TABLE IF NOT EXISTS "${JOURNAL}" (
|
|
70
|
+
id TEXT PRIMARY KEY,
|
|
71
|
+
applied_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
72
|
+
checksum TEXT NOT NULL
|
|
73
|
+
)` : `CREATE TABLE IF NOT EXISTS ${JOURNAL} (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
76
|
+
checksum TEXT NOT NULL
|
|
77
|
+
)`;
|
|
78
|
+
await this.client.query(sql);
|
|
79
|
+
}
|
|
80
|
+
async listApplied() {
|
|
81
|
+
const table = this.dialect === InternalDialect.POSTGRES ? `"${JOURNAL}"` : JOURNAL;
|
|
82
|
+
const { rows } = await this.client.query(`SELECT id FROM ${table}`);
|
|
83
|
+
return new Set(rows.map((r) => r.id));
|
|
84
|
+
}
|
|
85
|
+
async loadMigrations() {
|
|
86
|
+
const files = (await readdir(this.migrationsDir)).filter((f) => f.endsWith(".ts") || f.endsWith(".js")).sort();
|
|
87
|
+
const migrations = [];
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const mod = await import(resolve(this.migrationsDir, file));
|
|
90
|
+
const loaded = mod.default;
|
|
91
|
+
if (Migration.isMigration(loaded)) {
|
|
92
|
+
migrations.push(loaded);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (Migration.isMigrationConstructor(loaded)) {
|
|
96
|
+
migrations.push(new loaded());
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`Invalid migration module '${file}'. Default export must be a Migration subclass or instance.`);
|
|
100
|
+
}
|
|
101
|
+
return migrations;
|
|
102
|
+
}
|
|
103
|
+
async applyMigration(migration) {
|
|
104
|
+
const builder = new CollectingBuilder();
|
|
105
|
+
await migration.up(builder);
|
|
106
|
+
const sqls = builder.ops.flatMap((op) => this.compileOperation(op));
|
|
107
|
+
const checksum = String(this.hashJSON(builder.ops));
|
|
108
|
+
const isOnline = (migration.mode ?? builder.getMode()) === "online";
|
|
109
|
+
if (!isOnline && this.dialect === InternalDialect.POSTGRES) await this.client.query("BEGIN");
|
|
110
|
+
try {
|
|
111
|
+
for (const statement of sqls) await this.client.query(statement.sql, statement.params);
|
|
112
|
+
for (const fn of builder.dataFns) await fn({ query: (sql, params) => this.client.query(sql, params).then(() => {}) });
|
|
113
|
+
const table = this.dialect === InternalDialect.POSTGRES ? `"${JOURNAL}"` : JOURNAL;
|
|
114
|
+
const placeholder = this.dialect === InternalDialect.POSTGRES ? "$1, $2" : "?, ?";
|
|
115
|
+
await this.client.query(`INSERT INTO ${table} (id, checksum) VALUES (${placeholder})`, [migration.id, checksum]);
|
|
116
|
+
if (!isOnline && this.dialect === InternalDialect.POSTGRES) await this.client.query("COMMIT");
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (!isOnline && this.dialect === InternalDialect.POSTGRES) await this.client.query("ROLLBACK");
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Compute a simple hash of the migration's operation list.
|
|
124
|
+
* Stored alongside each applied migration in the journal table to detect
|
|
125
|
+
* if a migration file has been modified after it was already applied.
|
|
126
|
+
* Uses a djb2-like hash over the JSON-serialized operations.
|
|
127
|
+
*/
|
|
128
|
+
hashJSON(x) {
|
|
129
|
+
const s = JSON.stringify(x);
|
|
130
|
+
let h = 0;
|
|
131
|
+
for (let i = 0; i < s.length; i++) {
|
|
132
|
+
h = Math.imul(31, h) + s.charCodeAt(i);
|
|
133
|
+
h = h | 0;
|
|
134
|
+
}
|
|
135
|
+
return h >>> 0;
|
|
136
|
+
}
|
|
137
|
+
compileOperation(op) {
|
|
138
|
+
return this.compilerStrategy.compile(this.dialect, op);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
export { MigrationRunner };
|
|
144
|
+
//# sourceMappingURL=MigrationRunner-CCFuPUlr.js.map
|