@danceroutine/tango-migrations 1.4.0 → 1.6.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.
@@ -7,8 +7,10 @@ import { access, mkdir } from "node:fs/promises";
7
7
  import { constants as fsConstants } from "node:fs";
8
8
  import { dirname, resolve } from "node:path";
9
9
  import { getLogger } from "@danceroutine/tango-core";
10
- import { ModelRegistry } from "@danceroutine/tango-schema";
10
+ import { GENERATED_RELATION_REGISTRY_DIRNAME } from "@danceroutine/tango-schema";
11
11
  import { loadConfig } from "@danceroutine/tango-config";
12
+ import { loadProjectModule } from "@danceroutine/tango-codegen/commands";
13
+ import { writeRelationRegistryArtifacts } from "@danceroutine/tango-codegen/generators";
12
14
 
13
15
  //#region src/commands/cli.ts
14
16
  const logger = getLogger("tango.migrations");
@@ -63,26 +65,35 @@ async function resolveCommandInputs(argv) {
63
65
  autoApply: defaults.autoApply ?? true
64
66
  };
65
67
  }
68
+ function isModelContainerLike(value) {
69
+ return typeof value === "object" && value !== null && "metadata" in value;
70
+ }
71
+ function collectExportedModels(moduleValue) {
72
+ if (!moduleValue || typeof moduleValue !== "object") return [];
73
+ const models = [];
74
+ for (const value of Object.values(moduleValue)) {
75
+ if (isModelContainerLike(value)) {
76
+ models.push(value.metadata);
77
+ continue;
78
+ }
79
+ if (!value || typeof value !== "object") continue;
80
+ for (const nestedValue of Object.values(value)) if (isModelContainerLike(nestedValue)) models.push(nestedValue.metadata);
81
+ }
82
+ return models;
83
+ }
66
84
  async function loadModels(modelsPath) {
67
- const registry = new ModelRegistry();
68
- const mod = await loadModule(modelsPath, {
85
+ const { loaded: mod, registry, modelTypeAccessors } = await loadProjectModule(modelsPath, {
69
86
  projectRoot: process.cwd(),
70
- registry,
71
- moduleCache: false
87
+ outputDir: resolve(process.cwd(), GENERATED_RELATION_REGISTRY_DIRNAME)
72
88
  });
73
89
  const moduleValue = mod.default ?? mod;
74
- const models = [];
75
- if (moduleValue && typeof moduleValue === "object" && "metadata" in moduleValue) {
76
- const direct = moduleValue;
77
- models.push(direct.metadata);
78
- }
79
- const exportedValues = moduleValue && typeof moduleValue === "object" ? Object.values(moduleValue) : [];
80
- for (const value of exportedValues) if (value && typeof value === "object" && "metadata" in value) {
81
- const model = value;
82
- models.push(model.metadata);
83
- }
90
+ const models = isModelContainerLike(moduleValue) ? [moduleValue.metadata, ...collectExportedModels(moduleValue)] : collectExportedModels(moduleValue);
84
91
  if (models.length === 0) throw new Error(`No models found in '${modelsPath}'. Ensure the module exports Model() definitions.`);
85
- return models;
92
+ return {
93
+ models,
94
+ registry,
95
+ modelTypeAccessors
96
+ };
86
97
  }
87
98
  async function connectAndIntrospect(dbUrl, dialect) {
88
99
  const dbClient = await connectDbClient(dbUrl, dialect);
@@ -220,14 +231,23 @@ function registerMigrationsCommands(yargsBuilder) {
220
231
  config: argv.config,
221
232
  env: argv.env
222
233
  });
223
- const models = await loadModels(argv.models);
224
- logger.info(`Found ${models.length} model(s): ${models.map((m) => m.table).join(", ")}`);
234
+ const loaded = await loadModels(argv.models);
235
+ logger.info(`Found ${loaded.models.length} model(s): ${loaded.models.map((m) => m.table).join(", ")}`);
236
+ try {
237
+ await writeRelationRegistryArtifacts({
238
+ registry: loaded.registry,
239
+ modelTypeAccessors: loaded.modelTypeAccessors,
240
+ outputDir: resolve(process.cwd(), GENERATED_RELATION_REGISTRY_DIRNAME)
241
+ });
242
+ } catch (error) {
243
+ logger.warn(`Unable to refresh generated relation registry during make:migrations. Continuing without updated relation artifacts: ${error instanceof Error ? error.message : String(error)}`);
244
+ }
225
245
  let dbState;
226
246
  if (resolved.db) {
227
247
  logger.info("Introspecting database...");
228
248
  dbState = await connectAndIntrospect(resolved.db, resolved.dialect);
229
249
  } else dbState = { tables: {} };
230
- const operations = diffSchema(dbState, models);
250
+ const operations = diffSchema(dbState, loaded.models);
231
251
  if (operations.length === 0) {
232
252
  logger.info("No changes detected — models and database are in sync");
233
253
  return;
@@ -316,4 +336,4 @@ else statuses.forEach((statusItem) => {
316
336
 
317
337
  //#endregion
318
338
  export { registerMigrationsCommands };
319
- //# sourceMappingURL=cli-NLIdWE-y.js.map
339
+ //# sourceMappingURL=cli-DOBziuSJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-DOBziuSJ.js","names":["modulePath: string","configPathArg: string | undefined","configEnvArg: ConfigEnvironment | undefined","db: {\n adapter: string;\n url?: string;\n filename?: string;\n host?: string;\n port?: number;\n database?: string;\n user?: string;\n password?: string;\n}","argv: {\n dialect?: string;\n dir?: string;\n db?: string;\n config?: string;\n env?: ConfigEnvironment;\n}","value: unknown","moduleValue: unknown","models: ModelMetadataLike[]","modelsPath: string","dbUrl: string","dialect: string","db: string","dialect: Dialect","sql: string","params?: readonly unknown[]","filename: string","yargsBuilder: Argv","dbState: DbSchema"],"sources":["../src/commands/cli.ts"],"sourcesContent":["import { access, mkdir } from 'node:fs/promises';\nimport { constants as fsConstants } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { Argv } from 'yargs';\nimport { MigrationRunner } from '../runner/MigrationRunner';\nimport { MigrationGenerator } from '../generator/MigrationGenerator';\nimport { diffSchema } from '../diff/diffSchema';\nimport type { DbSchema } from '../introspect/PostgresIntrospector';\nimport type { Dialect } from '../domain/Dialect';\nimport type { ColumnType } from '../builder/contracts/ColumnType';\nimport type { DeleteReferentialAction } from '../builder/contracts/DeleteReferentialAction';\nimport type { UpdateReferentialAction } from '../builder/contracts/UpdateReferentialAction';\nimport { createDefaultIntrospectorStrategy } from '../strategies/IntrospectorStrategy';\nimport { InternalDialect } from '../domain/internal/InternalDialect';\nimport { loadConfig } from '@danceroutine/tango-config';\nimport { loadProjectModule } from '@danceroutine/tango-codegen/commands';\nimport { writeRelationRegistryArtifacts } from '@danceroutine/tango-codegen/generators';\nimport { getLogger } from '@danceroutine/tango-core';\nimport { GENERATED_RELATION_REGISTRY_DIRNAME, ModelRegistry } from '@danceroutine/tango-schema';\nimport { loadModule } from '../runtime/loadModule';\n\nconst logger = getLogger('tango.migrations');\n\ntype ConfigEnvironment = 'development' | 'test' | 'production';\n\ntype OptionalMigrationDefaults = {\n dialect?: Dialect;\n db?: string;\n dir?: string;\n autoApply?: boolean;\n};\n\ntype ModelMetadataLike = {\n table: string;\n fields: Array<{\n name: string;\n type: ColumnType;\n notNull?: boolean;\n default?: string | { now: true } | null;\n primaryKey?: boolean;\n unique?: boolean;\n references?: {\n table: string;\n column: string;\n onDelete?: DeleteReferentialAction;\n onUpdate?: UpdateReferentialAction;\n };\n }>;\n indexes?: Array<{ name: string; on: string[]; unique?: boolean }>;\n};\n\ntype CliDbClient = {\n query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }>;\n close(): Promise<void>;\n};\n\ntype LoadedModelsResult = {\n models: ModelMetadataLike[];\n registry: ModelRegistry;\n modelTypeAccessors: Record<string, string>;\n};\n\ntype ModelContainerLike = {\n metadata: ModelMetadataLike;\n};\n\nasync function importModule(modulePath: string): Promise<Record<string, unknown>> {\n return loadModule(modulePath, { projectRoot: process.cwd() });\n}\n\nasync function tryLoadMigrationDefaults(\n configPathArg: string | undefined,\n configEnvArg: ConfigEnvironment | undefined\n): Promise<OptionalMigrationDefaults> {\n const explicitConfigPath = typeof configPathArg === 'string' && configPathArg.trim().length > 0;\n const resolvedPath = resolve(process.cwd(), configPathArg?.trim() || './tango.config.ts');\n\n try {\n await access(resolvedPath, fsConstants.F_OK);\n } catch (error) {\n if (explicitConfigPath) {\n throw new Error(`Config file not found: ${resolvedPath}`, { cause: error });\n }\n return {};\n }\n\n const module = await importModule(resolvedPath);\n const fileConfig = (module.default ?? module) as { current?: ConfigEnvironment } & Record<string, unknown>;\n\n const loaded = loadConfig(() => ({\n ...fileConfig,\n ...(configEnvArg ? { current: configEnvArg } : {}),\n }));\n\n const { db, migrations } = loaded.current;\n const inferredDialect = db.adapter as Dialect;\n const inferredDb = resolveDbTarget(db);\n\n return {\n dialect: inferredDialect,\n db: inferredDb,\n dir: migrations.dir,\n autoApply: (migrations as { autoApply?: boolean }).autoApply,\n };\n}\n\nfunction resolveDbTarget(db: {\n adapter: string;\n url?: string;\n filename?: string;\n host?: string;\n port?: number;\n database?: string;\n user?: string;\n password?: string;\n}): string | undefined {\n if (db.adapter === InternalDialect.SQLITE) {\n return db.filename ?? db.url;\n }\n\n if (db.url) {\n return db.url;\n }\n\n if (!db.database) {\n return undefined;\n }\n\n const host = db.host ?? 'localhost';\n const port = db.port ?? 5432;\n const encodedUser = db.user ? encodeURIComponent(db.user) : '';\n const encodedPassword = db.password ? encodeURIComponent(db.password) : '';\n const userInfo =\n encodedUser.length > 0\n ? encodedPassword.length > 0\n ? `${encodedUser}:${encodedPassword}@`\n : `${encodedUser}@`\n : '';\n\n return `postgres://${userInfo}${host}:${String(port)}/${db.database}`;\n}\n\nasync function resolveCommandInputs(argv: {\n dialect?: string;\n dir?: string;\n db?: string;\n config?: string;\n env?: ConfigEnvironment;\n}): Promise<{ dialect: Dialect; dir: string; db?: string; autoApply: boolean }> {\n const defaults = await tryLoadMigrationDefaults(argv.config, argv.env);\n\n const resolvedDialect = (argv.dialect as Dialect | undefined) ?? defaults.dialect ?? InternalDialect.POSTGRES;\n const resolvedDir = argv.dir ?? defaults.dir ?? 'migrations';\n const resolvedDb = argv.db ?? defaults.db;\n\n return {\n dialect: resolvedDialect,\n dir: resolvedDir,\n db: resolvedDb,\n autoApply: defaults.autoApply ?? true,\n };\n}\n\nfunction isModelContainerLike(value: unknown): value is ModelContainerLike {\n return typeof value === 'object' && value !== null && 'metadata' in value;\n}\n\nfunction collectExportedModels(moduleValue: unknown): ModelMetadataLike[] {\n if (!moduleValue || typeof moduleValue !== 'object') {\n return [];\n }\n\n const models: ModelMetadataLike[] = [];\n for (const value of Object.values(moduleValue as Record<string, unknown>)) {\n if (isModelContainerLike(value)) {\n models.push(value.metadata);\n continue;\n }\n\n if (!value || typeof value !== 'object') {\n continue;\n }\n\n for (const nestedValue of Object.values(value as Record<string, unknown>)) {\n if (isModelContainerLike(nestedValue)) {\n models.push(nestedValue.metadata);\n }\n }\n }\n\n return models;\n}\n\nasync function loadModels(modelsPath: string): Promise<LoadedModelsResult> {\n const {\n loaded: mod,\n registry,\n modelTypeAccessors,\n } = await loadProjectModule(modelsPath, {\n projectRoot: process.cwd(),\n outputDir: resolve(process.cwd(), GENERATED_RELATION_REGISTRY_DIRNAME),\n });\n const moduleValue = (mod.default ?? mod) as unknown;\n\n const models = isModelContainerLike(moduleValue)\n ? [moduleValue.metadata, ...collectExportedModels(moduleValue)]\n : collectExportedModels(moduleValue);\n\n if (models.length === 0) {\n throw new Error(`No models found in '${modelsPath}'. Ensure the module exports Model() definitions.`);\n }\n\n return {\n models,\n registry,\n modelTypeAccessors,\n };\n}\n\nasync function connectAndIntrospect(dbUrl: string, dialect: string): Promise<DbSchema> {\n const dbClient = await connectDbClient(dbUrl, dialect as Dialect);\n\n try {\n const strategy = createDefaultIntrospectorStrategy();\n return await strategy.introspect(dialect as Dialect, dbClient);\n } finally {\n await dbClient.close();\n }\n}\n\nasync function connectDbClient(db: string, dialect: Dialect): Promise<CliDbClient> {\n if (dialect === InternalDialect.POSTGRES) {\n const pg = await import('pg');\n const client = new pg.default.Client({ connectionString: db });\n await client.connect();\n\n return {\n async query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }> {\n const result = await client.query(sql, params as unknown[] | undefined);\n return { rows: result.rows as T[] };\n },\n async close(): Promise<void> {\n await client.end();\n },\n };\n }\n\n if (dialect === InternalDialect.SQLITE) {\n const sqlite = await import('better-sqlite3');\n const DatabaseCtor = (sqlite.default ?? sqlite) as new (filename: string) => {\n prepare(sql: string): {\n all(...params: unknown[]): unknown[];\n run(...params: unknown[]): unknown;\n };\n close(): void;\n };\n\n const filename = normalizeSqliteFilename(db);\n await ensureSqliteParentDirectory(filename);\n const connection = new DatabaseCtor(filename);\n\n return {\n async query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }> {\n const statement = connection.prepare(sql);\n const values = [...(params ?? [])];\n const isSelectLike = /^\\s*(SELECT|PRAGMA|WITH)\\b/i.test(sql);\n if (isSelectLike) {\n return { rows: statement.all(...values) as T[] };\n }\n\n statement.run(...values);\n return { rows: [] as T[] };\n },\n async close(): Promise<void> {\n connection.close();\n },\n };\n }\n\n throw new Error(`Unsupported dialect: ${dialect}`);\n}\n\nasync function ensureSqliteParentDirectory(filename: string): Promise<void> {\n if (filename === ':memory:' || filename === 'file::memory:') {\n return;\n }\n const directory = dirname(filename);\n if (directory === '.' || directory.length === 0) {\n return;\n }\n await mkdir(directory, { recursive: true });\n}\n\nfunction normalizeSqliteFilename(db: string): string {\n if (db.startsWith('sqlite://')) {\n return db.slice('sqlite://'.length);\n }\n return db;\n}\n\n/**\n * Register Tango's migration commands on an existing yargs parser.\n */\nexport function registerMigrationsCommands(yargsBuilder: Argv): Argv {\n return yargsBuilder\n .command(\n 'migrate',\n 'Apply pending migrations to the database',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n })\n .option('to', {\n type: 'string',\n describe: 'Target migration ID (apply up to this migration)',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n\n if (!resolved.autoApply) {\n logger.info('Auto-migration disabled (autoApply: false). Skipping.');\n return;\n }\n\n if (!resolved.db) {\n throw new Error('No database target provided. Pass --db or define db settings in tango.config.ts.');\n }\n\n const dbClient = await connectDbClient(resolved.db, resolved.dialect);\n\n const runner = new MigrationRunner(dbClient, resolved.dialect, resolved.dir);\n await runner.apply(argv.to);\n\n await dbClient.close();\n logger.info('Migrations applied successfully');\n }\n )\n .command(\n 'make:migrations',\n 'Generate migration file by comparing models to database',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('name', {\n type: 'string',\n demandOption: true,\n describe: 'Migration name (e.g. \"create_users\")',\n })\n .option('models', {\n type: 'string',\n demandOption: true,\n describe: 'Path to module exporting Model definitions (e.g. \"./src/models.ts\")',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL for introspection (omit for initial migration)',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n const loaded = await loadModels(argv.models);\n logger.info(`Found ${loaded.models.length} model(s): ${loaded.models.map((m) => m.table).join(', ')}`);\n try {\n await writeRelationRegistryArtifacts({\n registry: loaded.registry,\n modelTypeAccessors: loaded.modelTypeAccessors,\n outputDir: resolve(process.cwd(), GENERATED_RELATION_REGISTRY_DIRNAME),\n });\n } catch (error) {\n logger.warn(\n `Unable to refresh generated relation registry during make:migrations. Continuing without updated relation artifacts: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n let dbState: DbSchema;\n if (resolved.db) {\n logger.info('Introspecting database...');\n dbState = await connectAndIntrospect(resolved.db, resolved.dialect);\n } else {\n dbState = { tables: {} };\n }\n\n const operations = diffSchema(dbState, loaded.models);\n\n if (operations.length === 0) {\n logger.info('No changes detected — models and database are in sync');\n return;\n }\n\n const generator = new MigrationGenerator();\n const filepath = await generator.generate({\n name: argv.name,\n operations,\n directory: resolved.dir,\n });\n\n logger.info(`Generated migration: ${filepath}`);\n logger.info(` ${operations.length} operation(s)`);\n }\n )\n .command(\n 'plan',\n 'Print migration SQL without applying',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n const runner = new MigrationRunner(\n { query: async () => ({ rows: [] }), close: async () => {} },\n resolved.dialect,\n resolved.dir\n );\n const output = await runner.plan();\n logger.info(output);\n }\n )\n .command(\n 'status',\n 'Show applied/pending status of all migrations',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n if (!resolved.db) {\n throw new Error('No database target provided. Pass --db or define db settings in tango.config.ts.');\n }\n\n const dbClient = await connectDbClient(resolved.db, resolved.dialect);\n\n const runner = new MigrationRunner(dbClient, resolved.dialect, resolved.dir);\n const statuses = await runner.status();\n\n if (statuses.length === 0) {\n logger.info('No migrations found');\n } else {\n statuses.forEach((statusItem) => {\n const marker = statusItem.applied ? '[x]' : '[ ]';\n logger.info(` ${marker} ${statusItem.id}`);\n });\n }\n\n await dbClient.close();\n }\n );\n}\n"],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import "./builder-xJ-Bq2pk.js";
12
12
  import "./diffSchema-D4oemTWS.js";
13
13
  import "./SqliteIntrospector-CWwPWhmA.js";
14
14
  import "./IntrospectorStrategy-blvwSU3_.js";
15
- import { registerMigrationsCommands } from "./cli-NLIdWE-y.js";
15
+ import { registerMigrationsCommands } from "./cli-DOBziuSJ.js";
16
16
  import yargs from "yargs";
17
17
  import { hideBin } from "yargs/helpers";
18
18
 
@@ -11,7 +11,7 @@ import "../builder-xJ-Bq2pk.js";
11
11
  import "../diffSchema-D4oemTWS.js";
12
12
  import "../SqliteIntrospector-CWwPWhmA.js";
13
13
  import "../IntrospectorStrategy-blvwSU3_.js";
14
- import { registerMigrationsCommands } from "../cli-NLIdWE-y.js";
14
+ import { registerMigrationsCommands } from "../cli-DOBziuSJ.js";
15
15
  import "../commands-DIJepqNg.js";
16
16
 
17
17
  export { registerMigrationsCommands };
@@ -1,5 +1,5 @@
1
1
  import { __export } from "./chunk-BkvOhyD0.js";
2
- import { registerMigrationsCommands } from "./cli-NLIdWE-y.js";
2
+ import { registerMigrationsCommands } from "./cli-DOBziuSJ.js";
3
3
 
4
4
  //#region src/commands/index.ts
5
5
  var commands_exports = {};
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { OpBuilder, applyFieldType, builder_exports } from "./builder-xJ-Bq2pk.j
11
11
  import { diffSchema } from "./diffSchema-D4oemTWS.js";
12
12
  import { PostgresIntrospector, SqliteIntrospector } from "./SqliteIntrospector-CWwPWhmA.js";
13
13
  import { IntrospectorStrategy, createDefaultIntrospectorStrategy } from "./IntrospectorStrategy-blvwSU3_.js";
14
- import { registerMigrationsCommands } from "./cli-NLIdWE-y.js";
14
+ import { registerMigrationsCommands } from "./cli-DOBziuSJ.js";
15
15
  import { domain_exports } from "./domain-CwR-kUNS.js";
16
16
  import { runner_exports } from "./runner-C97xT8_W.js";
17
17
  import { generator_exports } from "./generator-DK-_f-PF.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danceroutine/tango-migrations",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Auto-migration system with ops DSL for Tango",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -67,9 +67,10 @@
67
67
  "jiti": "^2.6.1",
68
68
  "kleur": "^4.1.5",
69
69
  "yargs": "^17.7.2",
70
- "@danceroutine/tango-config": "1.4.0",
71
- "@danceroutine/tango-core": "1.4.0",
72
- "@danceroutine/tango-schema": "1.4.0"
70
+ "@danceroutine/tango-config": "1.6.0",
71
+ "@danceroutine/tango-codegen": "1.6.0",
72
+ "@danceroutine/tango-core": "1.6.0",
73
+ "@danceroutine/tango-schema": "1.6.0"
73
74
  },
74
75
  "peerDependencies": {
75
76
  "pg": "^8.13.1",
@@ -93,8 +94,8 @@
93
94
  "tsdown": "^0.4.0",
94
95
  "typescript": "^5.6.3",
95
96
  "vitest": "^4.0.6",
96
- "@danceroutine/tango-testing": "1.4.0",
97
- "@danceroutine/tango-orm": "1.4.0"
97
+ "@danceroutine/tango-testing": "1.6.0",
98
+ "@danceroutine/tango-orm": "1.6.0"
98
99
  },
99
100
  "scripts": {
100
101
  "build": "tsdown",
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli-NLIdWE-y.js","names":["modulePath: string","configPathArg: string | undefined","configEnvArg: ConfigEnvironment | undefined","db: {\n adapter: string;\n url?: string;\n filename?: string;\n host?: string;\n port?: number;\n database?: string;\n user?: string;\n password?: string;\n}","argv: {\n dialect?: string;\n dir?: string;\n db?: string;\n config?: string;\n env?: ConfigEnvironment;\n}","modelsPath: string","models: ModelMetadataLike[]","dbUrl: string","dialect: string","db: string","dialect: Dialect","sql: string","params?: readonly unknown[]","filename: string","yargsBuilder: Argv","dbState: DbSchema"],"sources":["../src/commands/cli.ts"],"sourcesContent":["import { access, mkdir } from 'node:fs/promises';\nimport { constants as fsConstants } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { Argv } from 'yargs';\nimport { MigrationRunner } from '../runner/MigrationRunner';\nimport { MigrationGenerator } from '../generator/MigrationGenerator';\nimport { diffSchema } from '../diff/diffSchema';\nimport type { DbSchema } from '../introspect/PostgresIntrospector';\nimport type { Dialect } from '../domain/Dialect';\nimport type { ColumnType } from '../builder/contracts/ColumnType';\nimport type { DeleteReferentialAction } from '../builder/contracts/DeleteReferentialAction';\nimport type { UpdateReferentialAction } from '../builder/contracts/UpdateReferentialAction';\nimport { createDefaultIntrospectorStrategy } from '../strategies/IntrospectorStrategy';\nimport { InternalDialect } from '../domain/internal/InternalDialect';\nimport { loadConfig } from '@danceroutine/tango-config';\nimport { getLogger } from '@danceroutine/tango-core';\nimport { ModelRegistry } from '@danceroutine/tango-schema';\nimport { loadModule } from '../runtime/loadModule';\n\nconst logger = getLogger('tango.migrations');\n\ntype ConfigEnvironment = 'development' | 'test' | 'production';\n\ntype OptionalMigrationDefaults = {\n dialect?: Dialect;\n db?: string;\n dir?: string;\n autoApply?: boolean;\n};\n\ntype ModelMetadataLike = {\n table: string;\n fields: Array<{\n name: string;\n type: ColumnType;\n notNull?: boolean;\n default?: string | { now: true } | null;\n primaryKey?: boolean;\n unique?: boolean;\n references?: {\n table: string;\n column: string;\n onDelete?: DeleteReferentialAction;\n onUpdate?: UpdateReferentialAction;\n };\n }>;\n indexes?: Array<{ name: string; on: string[]; unique?: boolean }>;\n};\n\ntype CliDbClient = {\n query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }>;\n close(): Promise<void>;\n};\n\nasync function importModule(modulePath: string): Promise<Record<string, unknown>> {\n return loadModule(modulePath, { projectRoot: process.cwd() });\n}\n\nasync function tryLoadMigrationDefaults(\n configPathArg: string | undefined,\n configEnvArg: ConfigEnvironment | undefined\n): Promise<OptionalMigrationDefaults> {\n const explicitConfigPath = typeof configPathArg === 'string' && configPathArg.trim().length > 0;\n const resolvedPath = resolve(process.cwd(), configPathArg?.trim() || './tango.config.ts');\n\n try {\n await access(resolvedPath, fsConstants.F_OK);\n } catch (error) {\n if (explicitConfigPath) {\n throw new Error(`Config file not found: ${resolvedPath}`, { cause: error });\n }\n return {};\n }\n\n const module = await importModule(resolvedPath);\n const fileConfig = (module.default ?? module) as { current?: ConfigEnvironment } & Record<string, unknown>;\n\n const loaded = loadConfig(() => ({\n ...fileConfig,\n ...(configEnvArg ? { current: configEnvArg } : {}),\n }));\n\n const { db, migrations } = loaded.current;\n const inferredDialect = db.adapter as Dialect;\n const inferredDb = resolveDbTarget(db);\n\n return {\n dialect: inferredDialect,\n db: inferredDb,\n dir: migrations.dir,\n autoApply: (migrations as { autoApply?: boolean }).autoApply,\n };\n}\n\nfunction resolveDbTarget(db: {\n adapter: string;\n url?: string;\n filename?: string;\n host?: string;\n port?: number;\n database?: string;\n user?: string;\n password?: string;\n}): string | undefined {\n if (db.adapter === InternalDialect.SQLITE) {\n return db.filename ?? db.url;\n }\n\n if (db.url) {\n return db.url;\n }\n\n if (!db.database) {\n return undefined;\n }\n\n const host = db.host ?? 'localhost';\n const port = db.port ?? 5432;\n const encodedUser = db.user ? encodeURIComponent(db.user) : '';\n const encodedPassword = db.password ? encodeURIComponent(db.password) : '';\n const userInfo =\n encodedUser.length > 0\n ? encodedPassword.length > 0\n ? `${encodedUser}:${encodedPassword}@`\n : `${encodedUser}@`\n : '';\n\n return `postgres://${userInfo}${host}:${String(port)}/${db.database}`;\n}\n\nasync function resolveCommandInputs(argv: {\n dialect?: string;\n dir?: string;\n db?: string;\n config?: string;\n env?: ConfigEnvironment;\n}): Promise<{ dialect: Dialect; dir: string; db?: string; autoApply: boolean }> {\n const defaults = await tryLoadMigrationDefaults(argv.config, argv.env);\n\n const resolvedDialect = (argv.dialect as Dialect | undefined) ?? defaults.dialect ?? InternalDialect.POSTGRES;\n const resolvedDir = argv.dir ?? defaults.dir ?? 'migrations';\n const resolvedDb = argv.db ?? defaults.db;\n\n return {\n dialect: resolvedDialect,\n dir: resolvedDir,\n db: resolvedDb,\n autoApply: defaults.autoApply ?? true,\n };\n}\n\nasync function loadModels(modelsPath: string): Promise<ModelMetadataLike[]> {\n const registry = new ModelRegistry();\n const mod = await loadModule(modelsPath, {\n projectRoot: process.cwd(),\n registry,\n moduleCache: false,\n });\n const moduleValue = (mod.default ?? mod) as unknown;\n\n const models: ModelMetadataLike[] = [];\n if (moduleValue && typeof moduleValue === 'object' && 'metadata' in moduleValue) {\n const direct = moduleValue as { metadata: ModelMetadataLike };\n models.push(direct.metadata);\n }\n const exportedValues =\n moduleValue && typeof moduleValue === 'object' ? Object.values(moduleValue as Record<string, unknown>) : [];\n\n for (const value of exportedValues) {\n if (value && typeof value === 'object' && 'metadata' in value) {\n const model = value as { metadata: ModelMetadataLike };\n models.push(model.metadata);\n }\n }\n\n if (models.length === 0) {\n throw new Error(`No models found in '${modelsPath}'. Ensure the module exports Model() definitions.`);\n }\n\n return models;\n}\n\nasync function connectAndIntrospect(dbUrl: string, dialect: string): Promise<DbSchema> {\n const dbClient = await connectDbClient(dbUrl, dialect as Dialect);\n\n try {\n const strategy = createDefaultIntrospectorStrategy();\n return await strategy.introspect(dialect as Dialect, dbClient);\n } finally {\n await dbClient.close();\n }\n}\n\nasync function connectDbClient(db: string, dialect: Dialect): Promise<CliDbClient> {\n if (dialect === InternalDialect.POSTGRES) {\n const pg = await import('pg');\n const client = new pg.default.Client({ connectionString: db });\n await client.connect();\n\n return {\n async query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }> {\n const result = await client.query(sql, params as unknown[] | undefined);\n return { rows: result.rows as T[] };\n },\n async close(): Promise<void> {\n await client.end();\n },\n };\n }\n\n if (dialect === InternalDialect.SQLITE) {\n const sqlite = await import('better-sqlite3');\n const DatabaseCtor = (sqlite.default ?? sqlite) as new (filename: string) => {\n prepare(sql: string): {\n all(...params: unknown[]): unknown[];\n run(...params: unknown[]): unknown;\n };\n close(): void;\n };\n\n const filename = normalizeSqliteFilename(db);\n await ensureSqliteParentDirectory(filename);\n const connection = new DatabaseCtor(filename);\n\n return {\n async query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }> {\n const statement = connection.prepare(sql);\n const values = [...(params ?? [])];\n const isSelectLike = /^\\s*(SELECT|PRAGMA|WITH)\\b/i.test(sql);\n if (isSelectLike) {\n return { rows: statement.all(...values) as T[] };\n }\n\n statement.run(...values);\n return { rows: [] as T[] };\n },\n async close(): Promise<void> {\n connection.close();\n },\n };\n }\n\n throw new Error(`Unsupported dialect: ${dialect}`);\n}\n\nasync function ensureSqliteParentDirectory(filename: string): Promise<void> {\n if (filename === ':memory:' || filename === 'file::memory:') {\n return;\n }\n const directory = dirname(filename);\n if (directory === '.' || directory.length === 0) {\n return;\n }\n await mkdir(directory, { recursive: true });\n}\n\nfunction normalizeSqliteFilename(db: string): string {\n if (db.startsWith('sqlite://')) {\n return db.slice('sqlite://'.length);\n }\n return db;\n}\n\n/**\n * Register Tango's migration commands on an existing yargs parser.\n */\nexport function registerMigrationsCommands(yargsBuilder: Argv): Argv {\n return yargsBuilder\n .command(\n 'migrate',\n 'Apply pending migrations to the database',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n })\n .option('to', {\n type: 'string',\n describe: 'Target migration ID (apply up to this migration)',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n\n if (!resolved.autoApply) {\n logger.info('Auto-migration disabled (autoApply: false). Skipping.');\n return;\n }\n\n if (!resolved.db) {\n throw new Error('No database target provided. Pass --db or define db settings in tango.config.ts.');\n }\n\n const dbClient = await connectDbClient(resolved.db, resolved.dialect);\n\n const runner = new MigrationRunner(dbClient, resolved.dialect, resolved.dir);\n await runner.apply(argv.to);\n\n await dbClient.close();\n logger.info('Migrations applied successfully');\n }\n )\n .command(\n 'make:migrations',\n 'Generate migration file by comparing models to database',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('name', {\n type: 'string',\n demandOption: true,\n describe: 'Migration name (e.g. \"create_users\")',\n })\n .option('models', {\n type: 'string',\n demandOption: true,\n describe: 'Path to module exporting Model definitions (e.g. \"./src/models.ts\")',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL for introspection (omit for initial migration)',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n const models = await loadModels(argv.models);\n logger.info(`Found ${models.length} model(s): ${models.map((m) => m.table).join(', ')}`);\n\n let dbState: DbSchema;\n if (resolved.db) {\n logger.info('Introspecting database...');\n dbState = await connectAndIntrospect(resolved.db, resolved.dialect);\n } else {\n dbState = { tables: {} };\n }\n\n const operations = diffSchema(dbState, models);\n\n if (operations.length === 0) {\n logger.info('No changes detected — models and database are in sync');\n return;\n }\n\n const generator = new MigrationGenerator();\n const filepath = await generator.generate({\n name: argv.name,\n operations,\n directory: resolved.dir,\n });\n\n logger.info(`Generated migration: ${filepath}`);\n logger.info(` ${operations.length} operation(s)`);\n }\n )\n .command(\n 'plan',\n 'Print migration SQL without applying',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n const runner = new MigrationRunner(\n { query: async () => ({ rows: [] }), close: async () => {} },\n resolved.dialect,\n resolved.dir\n );\n const output = await runner.plan();\n logger.info(output);\n }\n )\n .command(\n 'status',\n 'Show applied/pending status of all migrations',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n if (!resolved.db) {\n throw new Error('No database target provided. Pass --db or define db settings in tango.config.ts.');\n }\n\n const dbClient = await connectDbClient(resolved.db, resolved.dialect);\n\n const runner = new MigrationRunner(dbClient, resolved.dialect, resolved.dir);\n const statuses = await runner.status();\n\n if (statuses.length === 0) {\n logger.info('No migrations found');\n } else {\n statuses.forEach((statusItem) => {\n const marker = statusItem.applied ? '[x]' : '[ ]';\n logger.info(` ${marker} ${statusItem.id}`);\n });\n }\n\n await dbClient.close();\n }\n );\n}\n"],"mappings":"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"}