drizzle-orm 1.0.0-beta.22-41a7d21 → 1.0.0-beta.22-bf0fa46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/bun-sql/sqlite/migrator.cjs +1 -1
  2. package/bun-sql/sqlite/migrator.cjs.map +1 -1
  3. package/bun-sql/sqlite/migrator.js +1 -1
  4. package/bun-sql/sqlite/migrator.js.map +1 -1
  5. package/d1/migrator.cjs +1 -1
  6. package/d1/migrator.cjs.map +1 -1
  7. package/d1/migrator.js +1 -1
  8. package/d1/migrator.js.map +1 -1
  9. package/durable-sqlite/migrator.cjs +1 -1
  10. package/durable-sqlite/migrator.js +1 -1
  11. package/libsql/migrator.cjs +1 -1
  12. package/libsql/migrator.cjs.map +1 -1
  13. package/libsql/migrator.js +1 -1
  14. package/libsql/migrator.js.map +1 -1
  15. package/mysql-proxy/migrator.cjs +2 -2
  16. package/mysql-proxy/migrator.cjs.map +1 -1
  17. package/mysql-proxy/migrator.js +2 -2
  18. package/mysql-proxy/migrator.js.map +1 -1
  19. package/op-sqlite/migrator.cjs +1 -1
  20. package/op-sqlite/migrator.cjs.map +1 -1
  21. package/op-sqlite/migrator.js +1 -1
  22. package/op-sqlite/migrator.js.map +1 -1
  23. package/package.json +67 -13
  24. package/pg-proxy/migrator.cjs +2 -2
  25. package/pg-proxy/migrator.cjs.map +1 -1
  26. package/pg-proxy/migrator.js +2 -2
  27. package/pg-proxy/migrator.js.map +1 -1
  28. package/singlestore-proxy/migrator.cjs +2 -2
  29. package/singlestore-proxy/migrator.cjs.map +1 -1
  30. package/singlestore-proxy/migrator.js +2 -2
  31. package/singlestore-proxy/migrator.js.map +1 -1
  32. package/sqlite-cloud/migrator.cjs +1 -1
  33. package/sqlite-cloud/migrator.cjs.map +1 -1
  34. package/sqlite-cloud/migrator.js +1 -1
  35. package/sqlite-cloud/migrator.js.map +1 -1
  36. package/sqlite-core/dialect.cjs +7 -7
  37. package/sqlite-core/dialect.cjs.map +1 -1
  38. package/sqlite-core/dialect.d.cts +2 -1
  39. package/sqlite-core/dialect.d.ts +2 -1
  40. package/sqlite-core/dialect.js +7 -7
  41. package/sqlite-core/dialect.js.map +1 -1
  42. package/sqlite-proxy/migrator.cjs +40 -2
  43. package/sqlite-proxy/migrator.cjs.map +1 -1
  44. package/sqlite-proxy/migrator.js +39 -2
  45. package/sqlite-proxy/migrator.js.map +1 -1
  46. package/tursodatabase/migrator.cjs +1 -1
  47. package/tursodatabase/migrator.cjs.map +1 -1
  48. package/tursodatabase/migrator.js +1 -1
  49. package/tursodatabase/migrator.js.map +1 -1
  50. package/up-migrations/cockroach.cjs +6 -12
  51. package/up-migrations/cockroach.cjs.map +1 -1
  52. package/up-migrations/cockroach.d.cts +1 -5
  53. package/up-migrations/cockroach.d.ts +1 -5
  54. package/up-migrations/cockroach.js +6 -12
  55. package/up-migrations/cockroach.js.map +1 -1
  56. package/up-migrations/effect-pg.cjs +6 -12
  57. package/up-migrations/effect-pg.cjs.map +1 -1
  58. package/up-migrations/effect-pg.d.cts +1 -5
  59. package/up-migrations/effect-pg.d.ts +1 -5
  60. package/up-migrations/effect-pg.js +6 -12
  61. package/up-migrations/effect-pg.js.map +1 -1
  62. package/up-migrations/mssql.cjs +6 -12
  63. package/up-migrations/mssql.cjs.map +1 -1
  64. package/up-migrations/mssql.d.cts +1 -5
  65. package/up-migrations/mssql.d.ts +1 -5
  66. package/up-migrations/mssql.js +6 -12
  67. package/up-migrations/mssql.js.map +1 -1
  68. package/up-migrations/mysql-proxy.cjs +73 -0
  69. package/up-migrations/mysql-proxy.cjs.map +1 -0
  70. package/up-migrations/mysql-proxy.d.cts +16 -0
  71. package/up-migrations/mysql-proxy.d.ts +16 -0
  72. package/up-migrations/mysql-proxy.js +71 -0
  73. package/up-migrations/mysql-proxy.js.map +1 -0
  74. package/up-migrations/mysql.cjs +12 -28
  75. package/up-migrations/mysql.cjs.map +1 -1
  76. package/up-migrations/mysql.d.cts +1 -5
  77. package/up-migrations/mysql.d.ts +1 -5
  78. package/up-migrations/mysql.js +12 -28
  79. package/up-migrations/mysql.js.map +1 -1
  80. package/up-migrations/pg-proxy.cjs +79 -0
  81. package/up-migrations/pg-proxy.cjs.map +1 -0
  82. package/up-migrations/pg-proxy.d.cts +16 -0
  83. package/up-migrations/pg-proxy.d.ts +16 -0
  84. package/up-migrations/pg-proxy.js +77 -0
  85. package/up-migrations/pg-proxy.js.map +1 -0
  86. package/up-migrations/pg.cjs +9 -13
  87. package/up-migrations/pg.cjs.map +1 -1
  88. package/up-migrations/pg.d.cts +2 -6
  89. package/up-migrations/pg.d.ts +2 -6
  90. package/up-migrations/pg.js +9 -13
  91. package/up-migrations/pg.js.map +1 -1
  92. package/up-migrations/singlestore-proxy.cjs +73 -0
  93. package/up-migrations/singlestore-proxy.cjs.map +1 -0
  94. package/up-migrations/singlestore-proxy.d.cts +16 -0
  95. package/up-migrations/singlestore-proxy.d.ts +16 -0
  96. package/up-migrations/singlestore-proxy.js +71 -0
  97. package/up-migrations/singlestore-proxy.js.map +1 -0
  98. package/up-migrations/singlestore.cjs +9 -25
  99. package/up-migrations/singlestore.cjs.map +1 -1
  100. package/up-migrations/singlestore.d.cts +1 -5
  101. package/up-migrations/singlestore.d.ts +1 -5
  102. package/up-migrations/singlestore.js +9 -25
  103. package/up-migrations/singlestore.js.map +1 -1
  104. package/up-migrations/sqlite-proxy.cjs +80 -0
  105. package/up-migrations/sqlite-proxy.cjs.map +1 -0
  106. package/up-migrations/sqlite-proxy.d.cts +16 -0
  107. package/up-migrations/sqlite-proxy.d.ts +16 -0
  108. package/up-migrations/sqlite-proxy.js +78 -0
  109. package/up-migrations/sqlite-proxy.js.map +1 -0
  110. package/up-migrations/sqlite.cjs +17 -48
  111. package/up-migrations/sqlite.cjs.map +1 -1
  112. package/up-migrations/sqlite.d.cts +3 -10
  113. package/up-migrations/sqlite.d.ts +5 -12
  114. package/up-migrations/sqlite.js +17 -48
  115. package/up-migrations/sqlite.js.map +1 -1
  116. package/up-migrations/utils.cjs +47 -0
  117. package/up-migrations/utils.cjs.map +1 -0
  118. package/up-migrations/utils.d.cts +25 -0
  119. package/up-migrations/utils.d.ts +25 -0
  120. package/up-migrations/utils.js +44 -0
  121. package/up-migrations/utils.js.map +1 -0
  122. package/sqlite-proxy/migrator.internal.cjs +0 -61
  123. package/sqlite-proxy/migrator.internal.cjs.map +0 -1
  124. package/sqlite-proxy/migrator.internal.d.cts +0 -26
  125. package/sqlite-proxy/migrator.internal.d.ts +0 -26
  126. package/sqlite-proxy/migrator.internal.js +0 -59
  127. package/sqlite-proxy/migrator.internal.js.map +0 -1
@@ -1,11 +1,7 @@
1
+ import { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS } from "./utils.js";
1
2
  import { sql } from "../sql/sql.js";
2
3
 
3
4
  //#region src/up-migrations/cockroach.ts
4
- const CURRENT_MIGRATION_TABLE_VERSION = 1;
5
- function getVersion(columns) {
6
- if (columns.includes("name")) return 1;
7
- return 0;
8
- }
9
5
  /**
10
6
  * Map of upgrade functions. Each key is the version being upgraded FROM,
11
7
  * and the function upgrades the table to the next version.
@@ -54,7 +50,7 @@ async function upgradeIfNeeded(migrationsSchema, migrationsTable, session, local
54
50
  if ((await session.all(sql`SELECT 1 FROM information_schema.tables
55
51
  WHERE table_schema = ${migrationsSchema}
56
52
  AND table_name = ${migrationsTable}`)).length === 0) return { newDb: true };
57
- let version = getVersion((await session.all(sql`SELECT
53
+ const rows = await session.all(sql`SELECT
58
54
  n.nspname AS "schema",
59
55
  c.relname AS "table_name",
60
56
  a.attname AS "column_name",
@@ -68,16 +64,14 @@ async function upgradeIfNeeded(migrationsSchema, migrationsTable, session, local
68
64
  AND NOT a.attisdropped
69
65
  AND n.nspname = ${migrationsSchema}
70
66
  AND c.relname = ${migrationsTable}
71
- ORDER BY a.attnum;`)).map((r) => r.column_name));
72
- for (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {
67
+ ORDER BY a.attnum;`);
68
+ let version = GET_VERSION_FOR.cockroach(rows.map((r) => r.column_name));
69
+ for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.cockroach; v++) {
73
70
  const upgradeFn = upgradeFunctions[v];
74
71
  if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
75
72
  await upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);
76
73
  }
77
- return {
78
- prevVersion: version,
79
- currentVersion: CURRENT_MIGRATION_TABLE_VERSION
80
- };
74
+ return { newDb: false };
81
75
  }
82
76
 
83
77
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"cockroach.js","names":[],"sources":["../../src/up-migrations/cockroach.ts"],"sourcesContent":["import type { CockroachSession } from '~/cockroach-core/session.ts';\nimport type { MigrationMeta } from '~/migrator.ts';\nimport { sql } from '~/sql/sql.ts';\n\nconst CURRENT_MIGRATION_TABLE_VERSION = 1;\n\ninterface UpgradeResult {\n\tnewDb?: boolean;\n\tprevVersion?: number;\n\tcurrentVersion?: number;\n}\n\nfunction getVersion(columns: string[]) {\n\tif (columns.includes('name')) return 1;\n\treturn 0;\n}\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: CockroachSession,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, session, localMigrations) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await session.all<{ id: number; hash: string; created_at: string }>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tawait session.transaction(async (tx) => {\n\t\t\t// 1. Add new columns\n\t\t\tawait tx.execute(sql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`);\n\t\t\tawait tx.execute(\n\t\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t} timestamp with time zone DEFAULT now()`,\n\t\t\t);\n\n\t\t\tfor (const backfillEntry of toApply) {\n\t\t\t\tawait tx.execute(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tsession: CockroachSession,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst tableExists = await session.all<{ 1: '1' }>(\n\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t);\n\n\tif (tableExists.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await session.all<{ schema: string; table_name: string; column_name: string; type: string }>(\n\t\tsql`SELECT\n n.nspname AS \"schema\",\n c.relname AS \"table_name\",\n a.attname AS \"column_name\",\n pg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n FROM\n pg_catalog.pg_attribute a\n JOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n WHERE\n a.attnum > 0\n AND NOT a.attisdropped\n AND n.nspname = ${migrationsSchema}\n AND c.relname = ${migrationsTable}\n ORDER BY a.attnum;`,\n\t);\n\n\tlet version = getVersion(rows.map((r) => r.column_name));\n\n\tfor (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);\n\t}\n\n\treturn { prevVersion: version, currentVersion: CURRENT_MIGRATION_TABLE_VERSION };\n}\n"],"mappings":";;;AAIA,MAAM,kCAAkC;AAQxC,SAAS,WAAW,SAAmB;AACtC,KAAI,QAAQ,SAAS,OAAO,CAAE,QAAO;AACrC,QAAO;;;;;;AAOR,MAAM,mBAQF,EAUH,GAAG,OAAO,kBAAkB,iBAAiB,SAAS,oBAAoB;CACzE,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,iBAAiB,CAAC,GAAG,IAAI,WAAW,gBAAgB;CAIvF,MAAM,SAAS,MAAM,QAAQ,IAC5B,GAAG,oCAAoC,MAAM,kBAC7C;AAGD,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,QAAQ;EAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;AAIF,OAAM,QAAQ,YAAY,OAAO,OAAO;AAEvC,QAAM,GAAG,QAAQ,GAAG,eAAe,MAAM,4BAA4B,IAAI,WAAW,OAAO,CAAC,OAAO;AACnG,QAAM,GAAG,QACR,GAAG,eAAe,MAAM,4BACvB,IAAI,WAAW,aAAa,CAC5B,yCACD;AAED,OAAK,MAAM,iBAAiB,QAC3B,OAAM,GAAG,QACR,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxE,IAAI,WAAW,aAAa,CAC5B,gBAAgB,IAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;GAED;GAEH;;;;;;;AAQD,eAAsB,gBACrB,kBACA,iBACA,SACA,iBACyB;AAQzB,MANoB,MAAM,QAAQ,IACjC,GAAG;0BACqB,iBAAiB;sBACrB,kBACpB,EAEe,WAAW,EAC1B,QAAO,EAAE,OAAO,MAAM;CAsBvB,IAAI,UAAU,YAlBD,MAAM,QAAQ,IAC1B,GAAG;;;;;;;;;;;;8BAYyB,iBAAiB;8BACjB,gBAAgB;4BAE5C,EAE6B,KAAK,MAAM,EAAE,YAAY,CAAC;AAExD,MAAK,IAAI,IAAI,SAAS,IAAI,iCAAiC,KAAK;EAC/D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB;;AAG7E,QAAO;EAAE,aAAa;EAAS,gBAAgB;EAAiC"}
1
+ {"version":3,"file":"cockroach.js","names":[],"sources":["../../src/up-migrations/cockroach.ts"],"sourcesContent":["import type { CockroachSession } from '~/cockroach-core/session.ts';\nimport type { MigrationMeta } from '~/migrator.ts';\nimport { sql } from '~/sql/sql.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: CockroachSession,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, session, localMigrations) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await session.all<{ id: number; hash: string; created_at: string }>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tawait session.transaction(async (tx) => {\n\t\t\t// 1. Add new columns\n\t\t\tawait tx.execute(sql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`);\n\t\t\tawait tx.execute(\n\t\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t} timestamp with time zone DEFAULT now()`,\n\t\t\t);\n\n\t\t\tfor (const backfillEntry of toApply) {\n\t\t\t\tawait tx.execute(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tsession: CockroachSession,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst tableExists = await session.all<{ 1: '1' }>(\n\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t);\n\n\tif (tableExists.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await session.all<{ schema: string; table_name: string; column_name: string; type: string }>(\n\t\tsql`SELECT\n n.nspname AS \"schema\",\n c.relname AS \"table_name\",\n a.attname AS \"column_name\",\n pg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n FROM\n pg_catalog.pg_attribute a\n JOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n WHERE\n a.attnum > 0\n AND NOT a.attisdropped\n AND n.nspname = ${migrationsSchema}\n AND c.relname = ${migrationsTable}\n ORDER BY a.attnum;`,\n\t);\n\n\tlet version = GET_VERSION_FOR.cockroach(rows.map((r) => r.column_name));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.cockroach; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":";;;;;;;;AASA,MAAM,mBAQF,EAUH,GAAG,OAAO,kBAAkB,iBAAiB,SAAS,oBAAoB;CACzE,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,iBAAiB,CAAC,GAAG,IAAI,WAAW,gBAAgB;CAIvF,MAAM,SAAS,MAAM,QAAQ,IAC5B,GAAG,oCAAoC,MAAM,kBAC7C;AAGD,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,QAAQ;EAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;AAIF,OAAM,QAAQ,YAAY,OAAO,OAAO;AAEvC,QAAM,GAAG,QAAQ,GAAG,eAAe,MAAM,4BAA4B,IAAI,WAAW,OAAO,CAAC,OAAO;AACnG,QAAM,GAAG,QACR,GAAG,eAAe,MAAM,4BACvB,IAAI,WAAW,aAAa,CAC5B,yCACD;AAED,OAAK,MAAM,iBAAiB,QAC3B,OAAM,GAAG,QACR,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxE,IAAI,WAAW,aAAa,CAC5B,gBAAgB,IAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;GAED;GAEH;;;;;;;AAQD,eAAsB,gBACrB,kBACA,iBACA,SACA,iBACyB;AAQzB,MANoB,MAAM,QAAQ,IACjC,GAAG;0BACqB,iBAAiB;sBACrB,kBACpB,EAEe,WAAW,EAC1B,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,QAAQ,IAC1B,GAAG;;;;;;;;;;;;8BAYyB,iBAAiB;8BACjB,gBAAgB;4BAE5C;CAED,IAAI,UAAU,gBAAgB,UAAU,KAAK,KAAK,MAAM,EAAE,YAAY,CAAC;AAEvE,MAAK,IAAI,IAAI,SAAS,IAAI,0BAA0B,WAAW,KAAK;EACnE,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB;;AAG7E,QAAO,EAAE,OAAO,OAAO"}
@@ -1,14 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
3
+ const require_up_migrations_utils = require('./utils.cjs');
3
4
  let __sql_sql_ts = require("../sql/sql.cjs");
4
5
  let effect = require("effect");
5
6
 
6
7
  //#region src/up-migrations/effect-pg.ts
7
- const CURRENT_MIGRATION_TABLE_VERSION = 1;
8
- function getVersion(columns) {
9
- if (columns.includes("name")) return 1;
10
- return 0;
11
- }
12
8
  /**
13
9
  * Map of upgrade functions. Each key is the version being upgraded FROM,
14
10
  * and the function upgrades the table to the next version.
@@ -59,7 +55,7 @@ const upgradeIfNeeded = effect.Effect.fn("upgradeIfNeeded")(function* (migration
59
55
  if ((yield* session.all(__sql_sql_ts.sql`SELECT 1 FROM information_schema.tables
60
56
  WHERE table_schema = ${migrationsSchema}
61
57
  AND table_name = ${migrationsTable}`)).length === 0) return { newDb: true };
62
- const version = getVersion((yield* session.all(__sql_sql_ts.sql`SELECT
58
+ const rows = yield* session.all(__sql_sql_ts.sql`SELECT
63
59
  n.nspname AS "schema",
64
60
  c.relname AS "table_name",
65
61
  a.attname AS "column_name",
@@ -73,16 +69,14 @@ const upgradeIfNeeded = effect.Effect.fn("upgradeIfNeeded")(function* (migration
73
69
  AND NOT a.attisdropped
74
70
  AND n.nspname = ${migrationsSchema}
75
71
  AND c.relname = ${migrationsTable}
76
- ORDER BY a.attnum;`)).map((r) => r.column_name));
77
- for (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {
72
+ ORDER BY a.attnum;`);
73
+ const version = require_up_migrations_utils.GET_VERSION_FOR.effect(rows.map((r) => r.column_name));
74
+ for (let v = version; v < require_up_migrations_utils.MIGRATIONS_TABLE_VERSIONS.effect; v++) {
78
75
  const upgradeFn = upgradeFunctions[v];
79
76
  if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
80
77
  yield* upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);
81
78
  }
82
- return {
83
- prevVersion: version,
84
- currentVersion: CURRENT_MIGRATION_TABLE_VERSION
85
- };
79
+ return { newDb: false };
86
80
  });
87
81
 
88
82
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"effect-pg.cjs","names":["Effect","sql"],"sources":["../../src/up-migrations/effect-pg.ts"],"sourcesContent":["import { Effect } from 'effect';\nimport type { QueryEffectHKTBase } from '~/effect-core/query-effect.ts';\nimport type { MigrationMeta } from '~/migrator.ts';\nimport type { PgEffectSession } from '~/pg-core/effect/session.ts';\nimport { sql } from '~/sql/sql.ts';\n\nconst CURRENT_MIGRATION_TABLE_VERSION = 1;\n\ninterface UpgradeResult {\n\tnewDb?: boolean;\n\tprevVersion?: number;\n\tcurrentVersion?: number;\n}\n\nfunction getVersion(columns: string[]) {\n\tif (columns.includes('name')) return 1;\n\treturn 0;\n}\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t<TEffectHKT extends QueryEffectHKTBase>(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: PgEffectSession<TEffectHKT>,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Effect.Effect<void, TEffectHKT['error'], TEffectHKT['context']>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: (migrationsSchema, migrationsTable, session, localMigrations) => {\n\t\treturn Effect.gen(function*() {\n\t\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t\t// 1. Read all existing DB migrations\n\t\t\t// Sort them by ids asc (order how they were applied)\n\t\t\tconst dbRows = yield* session.all<{ id: number; hash: string; created_at: string }>(\n\t\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t\t);\n\n\t\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\t\tlocalMigrations.sort((a, b) =>\n\t\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t\t);\n\n\t\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\t\tfor (const lm of localMigrations) {\n\t\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t\t}\n\t\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\t\tbyHash.set(lm.hash, lm);\n\t\t\t}\n\n\t\t\t// 3. Match each DB row to a local migration\n\t\t\t// Priority: millis -> hash\n\t\t\tconst toApply: { id: number; name: string }[] = [];\n\t\t\tlet unmatchedIds: number[] = [];\n\n\t\t\tfor (const dbRow of dbRows) {\n\t\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\t\tmatched = candidates[0];\n\t\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t\t} else {\n\t\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t\t}\n\n\t\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t\t}\n\n\t\t\t// 4. Check for unmatched\n\t\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\t\tif (unmatchedIds.length > 0) {\n\t\t\t\tthrow Error(\n\t\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// 5. Create extra column and backfill names for matched migrations\n\t\t\tyield* session.transaction((tx) =>\n\t\t\t\tEffect.gen(function*() {\n\t\t\t\t\tyield* tx.execute(sql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`);\n\t\t\t\t\tyield* tx.execute(\n\t\t\t\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t\t} timestamp with time zone DEFAULT now()`,\n\t\t\t\t\t);\n\n\t\t\t\t\tfor (const backfillEntry of toApply) {\n\t\t\t\t\t\tyield* tx.execute(\n\t\t\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t);\n\t\t});\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport const upgradeIfNeeded: <TEffectHKT extends QueryEffectHKTBase>(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tsession: PgEffectSession<TEffectHKT>,\n\tlocalMigrations: MigrationMeta[],\n) => Effect.Effect<UpgradeResult, TEffectHKT['error'], TEffectHKT['context']> = Effect.fn('upgradeIfNeeded')(\n\tfunction*<TEffectHKT extends QueryEffectHKTBase>(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: PgEffectSession<TEffectHKT>,\n\t\tlocalMigrations: MigrationMeta[],\n\t) {\n\t\t// Check if the table exists at all\n\t\tconst result = yield* session.all(\n\t\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t\t);\n\n\t\tif (result.length === 0) {\n\t\t\treturn { newDb: true };\n\t\t}\n\n\t\t// Table exists, check table shape\n\t\tconst rows = yield* session.all<{ schema: string; table_name: string; column_name: string; type: string }>(\n\t\t\tsql`SELECT\n\t\t\tn.nspname AS \"schema\",\n\t\t\tc.relname AS \"table_name\",\n\t\t\ta.attname AS \"column_name\",\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n\t\tFROM\n\t\t\tpg_catalog.pg_attribute a\n\t\t\tJOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\tWHERE\n\t\t\ta.attnum > 0\n\t\t\tAND NOT a.attisdropped\n\t\t\tAND n.nspname = ${migrationsSchema}\n\t\t\tAND c.relname = ${migrationsTable}\n\t\tORDER BY a.attnum;`,\n\t\t);\n\n\t\tconst version = getVersion(rows.map((r) => r.column_name));\n\n\t\tfor (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {\n\t\t\tconst upgradeFn = upgradeFunctions[v];\n\t\t\tif (!upgradeFn) {\n\t\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t\t}\n\t\t\tyield* upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);\n\t\t}\n\n\t\treturn { prevVersion: version, currentVersion: CURRENT_MIGRATION_TABLE_VERSION };\n\t},\n);\n"],"mappings":";;;;;;AAMA,MAAM,kCAAkC;AAQxC,SAAS,WAAW,SAAmB;AACtC,KAAI,QAAQ,SAAS,OAAO,CAAE,QAAO;AACrC,QAAO;;;;;;AAOR,MAAM,mBAQF,EAUH,IAAI,kBAAkB,iBAAiB,SAAS,oBAAoB;AACnE,QAAOA,cAAO,IAAI,aAAY;EAC7B,MAAM,QAAQ,gBAAG,GAAGC,iBAAI,WAAW,iBAAiB,CAAC,GAAGA,iBAAI,WAAW,gBAAgB;EAIvF,MAAM,SAAS,OAAO,QAAQ,IAC7B,gBAAG,oCAAoC,MAAM,kBAC7C;AAGD,kBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;EAED,MAAM,2BAAW,IAAI,KAA8B;EACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,OAAK,MAAM,MAAM,iBAAiB;AACjC,OAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,YAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,UAAO,IAAI,GAAG,MAAM,GAAG;;EAKxB,MAAM,UAA0C,EAAE;EAClD,IAAI,eAAyB,EAAE;AAE/B,OAAK,MAAM,SAAS,QAAQ;GAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;GAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;GAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;GAEvC,IAAI;AAEJ,OAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;YACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;OAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,OAAI,QAAS,SAAQ,KAAK;IAAE,IAAI,MAAM;IAAI,MAAM,QAAQ;IAAM,CAAC;OAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,MAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;AAIF,SAAO,QAAQ,aAAa,OAC3BD,cAAO,IAAI,aAAY;AACtB,UAAO,GAAG,QAAQ,gBAAG,eAAe,MAAM,4BAA4BC,iBAAI,WAAW,OAAO,CAAC,OAAO;AACpG,UAAO,GAAG,QACT,gBAAG,eAAe,MAAM,4BACvBA,iBAAI,WAAW,aAAa,CAC5B,yCACD;AAED,QAAK,MAAM,iBAAiB,QAC3B,QAAO,GAAG,QACT,gBAAG,UAAU,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxEA,iBAAI,WAAW,aAAa,CAC5B,gBAAgBA,iBAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;IAED,CACF;GACA;GAEH;;;;;;;AAQD,MAAa,kBAKmED,cAAO,GAAG,kBAAkB,CAC3G,WACC,kBACA,iBACA,SACA,iBACC;AAQD,MANe,OAAO,QAAQ,IAC7B,gBAAG;0BACoB,iBAAiB;sBACrB,kBACnB,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAsBvB,MAAM,UAAU,YAlBH,OAAO,QAAQ,IAC3B,gBAAG;;;;;;;;;;;;qBAYe,iBAAiB;qBACjB,gBAAgB;sBAElC,EAE+B,KAAK,MAAM,EAAE,YAAY,CAAC;AAE1D,MAAK,IAAI,IAAI,SAAS,IAAI,iCAAiC,KAAK;EAC/D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,SAAO,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB;;AAG9E,QAAO;EAAE,aAAa;EAAS,gBAAgB;EAAiC;EAEjF"}
1
+ {"version":3,"file":"effect-pg.cjs","names":["Effect","sql","GET_VERSION_FOR","MIGRATIONS_TABLE_VERSIONS"],"sources":["../../src/up-migrations/effect-pg.ts"],"sourcesContent":["import { Effect } from 'effect';\nimport type { QueryEffectHKTBase } from '~/effect-core/query-effect.ts';\nimport type { MigrationMeta } from '~/migrator.ts';\nimport type { PgEffectSession } from '~/pg-core/effect/session.ts';\nimport { sql } from '~/sql/sql.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t<TEffectHKT extends QueryEffectHKTBase>(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: PgEffectSession<TEffectHKT>,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Effect.Effect<void, TEffectHKT['error'], TEffectHKT['context']>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: (migrationsSchema, migrationsTable, session, localMigrations) => {\n\t\treturn Effect.gen(function*() {\n\t\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t\t// 1. Read all existing DB migrations\n\t\t\t// Sort them by ids asc (order how they were applied)\n\t\t\tconst dbRows = yield* session.all<{ id: number; hash: string; created_at: string }>(\n\t\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t\t);\n\n\t\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\t\tlocalMigrations.sort((a, b) =>\n\t\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t\t);\n\n\t\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\t\tfor (const lm of localMigrations) {\n\t\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t\t}\n\t\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\t\tbyHash.set(lm.hash, lm);\n\t\t\t}\n\n\t\t\t// 3. Match each DB row to a local migration\n\t\t\t// Priority: millis -> hash\n\t\t\tconst toApply: { id: number; name: string }[] = [];\n\t\t\tlet unmatchedIds: number[] = [];\n\n\t\t\tfor (const dbRow of dbRows) {\n\t\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\t\tmatched = candidates[0];\n\t\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t\t} else {\n\t\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t\t}\n\n\t\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t\t}\n\n\t\t\t// 4. Check for unmatched\n\t\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\t\tif (unmatchedIds.length > 0) {\n\t\t\t\tthrow Error(\n\t\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// 5. Create extra column and backfill names for matched migrations\n\t\t\tyield* session.transaction((tx) =>\n\t\t\t\tEffect.gen(function*() {\n\t\t\t\t\tyield* tx.execute(sql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`);\n\t\t\t\t\tyield* tx.execute(\n\t\t\t\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t\t} timestamp with time zone DEFAULT now()`,\n\t\t\t\t\t);\n\n\t\t\t\t\tfor (const backfillEntry of toApply) {\n\t\t\t\t\t\tyield* tx.execute(\n\t\t\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t);\n\t\t});\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport const upgradeIfNeeded: <TEffectHKT extends QueryEffectHKTBase>(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tsession: PgEffectSession<TEffectHKT>,\n\tlocalMigrations: MigrationMeta[],\n) => Effect.Effect<UpgradeResult, TEffectHKT['error'], TEffectHKT['context']> = Effect.fn('upgradeIfNeeded')(\n\tfunction*<TEffectHKT extends QueryEffectHKTBase>(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: PgEffectSession<TEffectHKT>,\n\t\tlocalMigrations: MigrationMeta[],\n\t) {\n\t\t// Check if the table exists at all\n\t\tconst result = yield* session.all(\n\t\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t\t);\n\n\t\tif (result.length === 0) {\n\t\t\treturn { newDb: true };\n\t\t}\n\n\t\t// Table exists, check table shape\n\t\tconst rows = yield* session.all<{ schema: string; table_name: string; column_name: string; type: string }>(\n\t\t\tsql`SELECT\n\t\t\tn.nspname AS \"schema\",\n\t\t\tc.relname AS \"table_name\",\n\t\t\ta.attname AS \"column_name\",\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n\t\tFROM\n\t\t\tpg_catalog.pg_attribute a\n\t\t\tJOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\tWHERE\n\t\t\ta.attnum > 0\n\t\t\tAND NOT a.attisdropped\n\t\t\tAND n.nspname = ${migrationsSchema}\n\t\t\tAND c.relname = ${migrationsTable}\n\t\tORDER BY a.attnum;`,\n\t\t);\n\n\t\tconst version = GET_VERSION_FOR.effect(rows.map((r) => r.column_name));\n\n\t\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.effect; v++) {\n\t\t\tconst upgradeFn = upgradeFunctions[v];\n\t\t\tif (!upgradeFn) {\n\t\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t\t}\n\t\t\tyield* upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);\n\t\t}\n\n\t\treturn { newDb: false };\n\t},\n);\n"],"mappings":";;;;;;;;;;;AAWA,MAAM,mBAQF,EAUH,IAAI,kBAAkB,iBAAiB,SAAS,oBAAoB;AACnE,QAAOA,cAAO,IAAI,aAAY;EAC7B,MAAM,QAAQ,gBAAG,GAAGC,iBAAI,WAAW,iBAAiB,CAAC,GAAGA,iBAAI,WAAW,gBAAgB;EAIvF,MAAM,SAAS,OAAO,QAAQ,IAC7B,gBAAG,oCAAoC,MAAM,kBAC7C;AAGD,kBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;EAED,MAAM,2BAAW,IAAI,KAA8B;EACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,OAAK,MAAM,MAAM,iBAAiB;AACjC,OAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,YAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,UAAO,IAAI,GAAG,MAAM,GAAG;;EAKxB,MAAM,UAA0C,EAAE;EAClD,IAAI,eAAyB,EAAE;AAE/B,OAAK,MAAM,SAAS,QAAQ;GAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;GAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;GAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;GAEvC,IAAI;AAEJ,OAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;YACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;OAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,OAAI,QAAS,SAAQ,KAAK;IAAE,IAAI,MAAM;IAAI,MAAM,QAAQ;IAAM,CAAC;OAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,MAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;AAIF,SAAO,QAAQ,aAAa,OAC3BD,cAAO,IAAI,aAAY;AACtB,UAAO,GAAG,QAAQ,gBAAG,eAAe,MAAM,4BAA4BC,iBAAI,WAAW,OAAO,CAAC,OAAO;AACpG,UAAO,GAAG,QACT,gBAAG,eAAe,MAAM,4BACvBA,iBAAI,WAAW,aAAa,CAC5B,yCACD;AAED,QAAK,MAAM,iBAAiB,QAC3B,QAAO,GAAG,QACT,gBAAG,UAAU,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxEA,iBAAI,WAAW,aAAa,CAC5B,gBAAgBA,iBAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;IAED,CACF;GACA;GAEH;;;;;;;AAQD,MAAa,kBAKmED,cAAO,GAAG,kBAAkB,CAC3G,WACC,kBACA,iBACA,SACA,iBACC;AAQD,MANe,OAAO,QAAQ,IAC7B,gBAAG;0BACoB,iBAAiB;sBACrB,kBACnB,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,OAAO,QAAQ,IAC3B,gBAAG;;;;;;;;;;;;qBAYe,iBAAiB;qBACjB,gBAAgB;sBAElC;CAED,MAAM,UAAUE,4CAAgB,OAAO,KAAK,KAAK,MAAM,EAAE,YAAY,CAAC;AAEtE,MAAK,IAAI,IAAI,SAAS,IAAIC,sDAA0B,QAAQ,KAAK;EAChE,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,SAAO,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB;;AAG9E,QAAO,EAAE,OAAO,OAAO;EAExB"}
@@ -1,14 +1,10 @@
1
+ import { UpgradeResult } from "./utils.cjs";
1
2
  import { MigrationMeta } from "../migrator.cjs";
2
3
  import { QueryEffectHKTBase } from "../effect-core/query-effect.cjs";
3
4
  import { PgEffectSession } from "../pg-core/effect/session.cjs";
4
5
  import { Effect } from "effect";
5
6
 
6
7
  //#region src/up-migrations/effect-pg.d.ts
7
- interface UpgradeResult {
8
- newDb?: boolean;
9
- prevVersion?: number;
10
- currentVersion?: number;
11
- }
12
8
  /**
13
9
  * Detects the current version of the migrations table schema and upgrades it if needed.
14
10
  *
@@ -1,14 +1,10 @@
1
+ import { UpgradeResult } from "./utils.js";
1
2
  import { MigrationMeta } from "../migrator.js";
2
3
  import { Effect } from "effect";
3
4
  import { PgEffectSession } from "../pg-core/effect/session.js";
4
5
  import { QueryEffectHKTBase } from "../effect-core/query-effect.js";
5
6
 
6
7
  //#region src/up-migrations/effect-pg.d.ts
7
- interface UpgradeResult {
8
- newDb?: boolean;
9
- prevVersion?: number;
10
- currentVersion?: number;
11
- }
12
8
  /**
13
9
  * Detects the current version of the migrations table schema and upgrades it if needed.
14
10
  *
@@ -1,12 +1,8 @@
1
+ import { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS } from "./utils.js";
1
2
  import { sql } from "../sql/sql.js";
2
3
  import { Effect } from "effect";
3
4
 
4
5
  //#region src/up-migrations/effect-pg.ts
5
- const CURRENT_MIGRATION_TABLE_VERSION = 1;
6
- function getVersion(columns) {
7
- if (columns.includes("name")) return 1;
8
- return 0;
9
- }
10
6
  /**
11
7
  * Map of upgrade functions. Each key is the version being upgraded FROM,
12
8
  * and the function upgrades the table to the next version.
@@ -57,7 +53,7 @@ const upgradeIfNeeded = Effect.fn("upgradeIfNeeded")(function* (migrationsSchema
57
53
  if ((yield* session.all(sql`SELECT 1 FROM information_schema.tables
58
54
  WHERE table_schema = ${migrationsSchema}
59
55
  AND table_name = ${migrationsTable}`)).length === 0) return { newDb: true };
60
- const version = getVersion((yield* session.all(sql`SELECT
56
+ const rows = yield* session.all(sql`SELECT
61
57
  n.nspname AS "schema",
62
58
  c.relname AS "table_name",
63
59
  a.attname AS "column_name",
@@ -71,16 +67,14 @@ const upgradeIfNeeded = Effect.fn("upgradeIfNeeded")(function* (migrationsSchema
71
67
  AND NOT a.attisdropped
72
68
  AND n.nspname = ${migrationsSchema}
73
69
  AND c.relname = ${migrationsTable}
74
- ORDER BY a.attnum;`)).map((r) => r.column_name));
75
- for (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {
70
+ ORDER BY a.attnum;`);
71
+ const version = GET_VERSION_FOR.effect(rows.map((r) => r.column_name));
72
+ for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.effect; v++) {
76
73
  const upgradeFn = upgradeFunctions[v];
77
74
  if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
78
75
  yield* upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);
79
76
  }
80
- return {
81
- prevVersion: version,
82
- currentVersion: CURRENT_MIGRATION_TABLE_VERSION
83
- };
77
+ return { newDb: false };
84
78
  });
85
79
 
86
80
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"effect-pg.js","names":[],"sources":["../../src/up-migrations/effect-pg.ts"],"sourcesContent":["import { Effect } from 'effect';\nimport type { QueryEffectHKTBase } from '~/effect-core/query-effect.ts';\nimport type { MigrationMeta } from '~/migrator.ts';\nimport type { PgEffectSession } from '~/pg-core/effect/session.ts';\nimport { sql } from '~/sql/sql.ts';\n\nconst CURRENT_MIGRATION_TABLE_VERSION = 1;\n\ninterface UpgradeResult {\n\tnewDb?: boolean;\n\tprevVersion?: number;\n\tcurrentVersion?: number;\n}\n\nfunction getVersion(columns: string[]) {\n\tif (columns.includes('name')) return 1;\n\treturn 0;\n}\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t<TEffectHKT extends QueryEffectHKTBase>(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: PgEffectSession<TEffectHKT>,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Effect.Effect<void, TEffectHKT['error'], TEffectHKT['context']>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: (migrationsSchema, migrationsTable, session, localMigrations) => {\n\t\treturn Effect.gen(function*() {\n\t\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t\t// 1. Read all existing DB migrations\n\t\t\t// Sort them by ids asc (order how they were applied)\n\t\t\tconst dbRows = yield* session.all<{ id: number; hash: string; created_at: string }>(\n\t\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t\t);\n\n\t\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\t\tlocalMigrations.sort((a, b) =>\n\t\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t\t);\n\n\t\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\t\tfor (const lm of localMigrations) {\n\t\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t\t}\n\t\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\t\tbyHash.set(lm.hash, lm);\n\t\t\t}\n\n\t\t\t// 3. Match each DB row to a local migration\n\t\t\t// Priority: millis -> hash\n\t\t\tconst toApply: { id: number; name: string }[] = [];\n\t\t\tlet unmatchedIds: number[] = [];\n\n\t\t\tfor (const dbRow of dbRows) {\n\t\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\t\tmatched = candidates[0];\n\t\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t\t} else {\n\t\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t\t}\n\n\t\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t\t}\n\n\t\t\t// 4. Check for unmatched\n\t\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\t\tif (unmatchedIds.length > 0) {\n\t\t\t\tthrow Error(\n\t\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// 5. Create extra column and backfill names for matched migrations\n\t\t\tyield* session.transaction((tx) =>\n\t\t\t\tEffect.gen(function*() {\n\t\t\t\t\tyield* tx.execute(sql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`);\n\t\t\t\t\tyield* tx.execute(\n\t\t\t\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t\t} timestamp with time zone DEFAULT now()`,\n\t\t\t\t\t);\n\n\t\t\t\t\tfor (const backfillEntry of toApply) {\n\t\t\t\t\t\tyield* tx.execute(\n\t\t\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t);\n\t\t});\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport const upgradeIfNeeded: <TEffectHKT extends QueryEffectHKTBase>(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tsession: PgEffectSession<TEffectHKT>,\n\tlocalMigrations: MigrationMeta[],\n) => Effect.Effect<UpgradeResult, TEffectHKT['error'], TEffectHKT['context']> = Effect.fn('upgradeIfNeeded')(\n\tfunction*<TEffectHKT extends QueryEffectHKTBase>(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: PgEffectSession<TEffectHKT>,\n\t\tlocalMigrations: MigrationMeta[],\n\t) {\n\t\t// Check if the table exists at all\n\t\tconst result = yield* session.all(\n\t\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t\t);\n\n\t\tif (result.length === 0) {\n\t\t\treturn { newDb: true };\n\t\t}\n\n\t\t// Table exists, check table shape\n\t\tconst rows = yield* session.all<{ schema: string; table_name: string; column_name: string; type: string }>(\n\t\t\tsql`SELECT\n\t\t\tn.nspname AS \"schema\",\n\t\t\tc.relname AS \"table_name\",\n\t\t\ta.attname AS \"column_name\",\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n\t\tFROM\n\t\t\tpg_catalog.pg_attribute a\n\t\t\tJOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\tWHERE\n\t\t\ta.attnum > 0\n\t\t\tAND NOT a.attisdropped\n\t\t\tAND n.nspname = ${migrationsSchema}\n\t\t\tAND c.relname = ${migrationsTable}\n\t\tORDER BY a.attnum;`,\n\t\t);\n\n\t\tconst version = getVersion(rows.map((r) => r.column_name));\n\n\t\tfor (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {\n\t\t\tconst upgradeFn = upgradeFunctions[v];\n\t\t\tif (!upgradeFn) {\n\t\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t\t}\n\t\t\tyield* upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);\n\t\t}\n\n\t\treturn { prevVersion: version, currentVersion: CURRENT_MIGRATION_TABLE_VERSION };\n\t},\n);\n"],"mappings":";;;;AAMA,MAAM,kCAAkC;AAQxC,SAAS,WAAW,SAAmB;AACtC,KAAI,QAAQ,SAAS,OAAO,CAAE,QAAO;AACrC,QAAO;;;;;;AAOR,MAAM,mBAQF,EAUH,IAAI,kBAAkB,iBAAiB,SAAS,oBAAoB;AACnE,QAAO,OAAO,IAAI,aAAY;EAC7B,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,iBAAiB,CAAC,GAAG,IAAI,WAAW,gBAAgB;EAIvF,MAAM,SAAS,OAAO,QAAQ,IAC7B,GAAG,oCAAoC,MAAM,kBAC7C;AAGD,kBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;EAED,MAAM,2BAAW,IAAI,KAA8B;EACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,OAAK,MAAM,MAAM,iBAAiB;AACjC,OAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,YAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,UAAO,IAAI,GAAG,MAAM,GAAG;;EAKxB,MAAM,UAA0C,EAAE;EAClD,IAAI,eAAyB,EAAE;AAE/B,OAAK,MAAM,SAAS,QAAQ;GAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;GAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;GAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;GAEvC,IAAI;AAEJ,OAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;YACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;OAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,OAAI,QAAS,SAAQ,KAAK;IAAE,IAAI,MAAM;IAAI,MAAM,QAAQ;IAAM,CAAC;OAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,MAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;AAIF,SAAO,QAAQ,aAAa,OAC3B,OAAO,IAAI,aAAY;AACtB,UAAO,GAAG,QAAQ,GAAG,eAAe,MAAM,4BAA4B,IAAI,WAAW,OAAO,CAAC,OAAO;AACpG,UAAO,GAAG,QACT,GAAG,eAAe,MAAM,4BACvB,IAAI,WAAW,aAAa,CAC5B,yCACD;AAED,QAAK,MAAM,iBAAiB,QAC3B,QAAO,GAAG,QACT,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxE,IAAI,WAAW,aAAa,CAC5B,gBAAgB,IAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;IAED,CACF;GACA;GAEH;;;;;;;AAQD,MAAa,kBAKmE,OAAO,GAAG,kBAAkB,CAC3G,WACC,kBACA,iBACA,SACA,iBACC;AAQD,MANe,OAAO,QAAQ,IAC7B,GAAG;0BACoB,iBAAiB;sBACrB,kBACnB,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAsBvB,MAAM,UAAU,YAlBH,OAAO,QAAQ,IAC3B,GAAG;;;;;;;;;;;;qBAYe,iBAAiB;qBACjB,gBAAgB;sBAElC,EAE+B,KAAK,MAAM,EAAE,YAAY,CAAC;AAE1D,MAAK,IAAI,IAAI,SAAS,IAAI,iCAAiC,KAAK;EAC/D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,SAAO,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB;;AAG9E,QAAO;EAAE,aAAa;EAAS,gBAAgB;EAAiC;EAEjF"}
1
+ {"version":3,"file":"effect-pg.js","names":[],"sources":["../../src/up-migrations/effect-pg.ts"],"sourcesContent":["import { Effect } from 'effect';\nimport type { QueryEffectHKTBase } from '~/effect-core/query-effect.ts';\nimport type { MigrationMeta } from '~/migrator.ts';\nimport type { PgEffectSession } from '~/pg-core/effect/session.ts';\nimport { sql } from '~/sql/sql.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t<TEffectHKT extends QueryEffectHKTBase>(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: PgEffectSession<TEffectHKT>,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Effect.Effect<void, TEffectHKT['error'], TEffectHKT['context']>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: (migrationsSchema, migrationsTable, session, localMigrations) => {\n\t\treturn Effect.gen(function*() {\n\t\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t\t// 1. Read all existing DB migrations\n\t\t\t// Sort them by ids asc (order how they were applied)\n\t\t\tconst dbRows = yield* session.all<{ id: number; hash: string; created_at: string }>(\n\t\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t\t);\n\n\t\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\t\tlocalMigrations.sort((a, b) =>\n\t\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t\t);\n\n\t\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\t\tfor (const lm of localMigrations) {\n\t\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t\t}\n\t\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\t\tbyHash.set(lm.hash, lm);\n\t\t\t}\n\n\t\t\t// 3. Match each DB row to a local migration\n\t\t\t// Priority: millis -> hash\n\t\t\tconst toApply: { id: number; name: string }[] = [];\n\t\t\tlet unmatchedIds: number[] = [];\n\n\t\t\tfor (const dbRow of dbRows) {\n\t\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\t\tmatched = candidates[0];\n\t\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t\t} else {\n\t\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t\t}\n\n\t\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t\t}\n\n\t\t\t// 4. Check for unmatched\n\t\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\t\tif (unmatchedIds.length > 0) {\n\t\t\t\tthrow Error(\n\t\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// 5. Create extra column and backfill names for matched migrations\n\t\t\tyield* session.transaction((tx) =>\n\t\t\t\tEffect.gen(function*() {\n\t\t\t\t\tyield* tx.execute(sql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`);\n\t\t\t\t\tyield* tx.execute(\n\t\t\t\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${\n\t\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t\t} timestamp with time zone DEFAULT now()`,\n\t\t\t\t\t);\n\n\t\t\t\t\tfor (const backfillEntry of toApply) {\n\t\t\t\t\t\tyield* tx.execute(\n\t\t\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t);\n\t\t});\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport const upgradeIfNeeded: <TEffectHKT extends QueryEffectHKTBase>(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tsession: PgEffectSession<TEffectHKT>,\n\tlocalMigrations: MigrationMeta[],\n) => Effect.Effect<UpgradeResult, TEffectHKT['error'], TEffectHKT['context']> = Effect.fn('upgradeIfNeeded')(\n\tfunction*<TEffectHKT extends QueryEffectHKTBase>(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: PgEffectSession<TEffectHKT>,\n\t\tlocalMigrations: MigrationMeta[],\n\t) {\n\t\t// Check if the table exists at all\n\t\tconst result = yield* session.all(\n\t\t\tsql`SELECT 1 FROM information_schema.tables\n\t\t\tWHERE table_schema = ${migrationsSchema}\n\t\t\tAND table_name = ${migrationsTable}`,\n\t\t);\n\n\t\tif (result.length === 0) {\n\t\t\treturn { newDb: true };\n\t\t}\n\n\t\t// Table exists, check table shape\n\t\tconst rows = yield* session.all<{ schema: string; table_name: string; column_name: string; type: string }>(\n\t\t\tsql`SELECT\n\t\t\tn.nspname AS \"schema\",\n\t\t\tc.relname AS \"table_name\",\n\t\t\ta.attname AS \"column_name\",\n\t\t\tpg_catalog.format_type(a.atttypid, a.atttypmod) AS \"type\"\n\t\tFROM\n\t\t\tpg_catalog.pg_attribute a\n\t\t\tJOIN pg_catalog.pg_class c ON c.oid = a.attrelid\n\t\t\tJOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n\t\tWHERE\n\t\t\ta.attnum > 0\n\t\t\tAND NOT a.attisdropped\n\t\t\tAND n.nspname = ${migrationsSchema}\n\t\t\tAND c.relname = ${migrationsTable}\n\t\tORDER BY a.attnum;`,\n\t\t);\n\n\t\tconst version = GET_VERSION_FOR.effect(rows.map((r) => r.column_name));\n\n\t\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.effect; v++) {\n\t\t\tconst upgradeFn = upgradeFunctions[v];\n\t\t\tif (!upgradeFn) {\n\t\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t\t}\n\t\t\tyield* upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);\n\t\t}\n\n\t\treturn { newDb: false };\n\t},\n);\n"],"mappings":";;;;;;;;;AAWA,MAAM,mBAQF,EAUH,IAAI,kBAAkB,iBAAiB,SAAS,oBAAoB;AACnE,QAAO,OAAO,IAAI,aAAY;EAC7B,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,iBAAiB,CAAC,GAAG,IAAI,WAAW,gBAAgB;EAIvF,MAAM,SAAS,OAAO,QAAQ,IAC7B,GAAG,oCAAoC,MAAM,kBAC7C;AAGD,kBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;EAED,MAAM,2BAAW,IAAI,KAA8B;EACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,OAAK,MAAM,MAAM,iBAAiB;AACjC,OAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,YAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,UAAO,IAAI,GAAG,MAAM,GAAG;;EAKxB,MAAM,UAA0C,EAAE;EAClD,IAAI,eAAyB,EAAE;AAE/B,OAAK,MAAM,SAAS,QAAQ;GAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;GAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;GAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;GAEvC,IAAI;AAEJ,OAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;YACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;OAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,OAAI,QAAS,SAAQ,KAAK;IAAE,IAAI,MAAM;IAAI,MAAM,QAAQ;IAAM,CAAC;OAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,MAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;AAIF,SAAO,QAAQ,aAAa,OAC3B,OAAO,IAAI,aAAY;AACtB,UAAO,GAAG,QAAQ,GAAG,eAAe,MAAM,4BAA4B,IAAI,WAAW,OAAO,CAAC,OAAO;AACpG,UAAO,GAAG,QACT,GAAG,eAAe,MAAM,4BACvB,IAAI,WAAW,aAAa,CAC5B,yCACD;AAED,QAAK,MAAM,iBAAiB,QAC3B,QAAO,GAAG,QACT,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxE,IAAI,WAAW,aAAa,CAC5B,gBAAgB,IAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;IAED,CACF;GACA;GAEH;;;;;;;AAQD,MAAa,kBAKmE,OAAO,GAAG,kBAAkB,CAC3G,WACC,kBACA,iBACA,SACA,iBACC;AAQD,MANe,OAAO,QAAQ,IAC7B,GAAG;0BACoB,iBAAiB;sBACrB,kBACnB,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,OAAO,QAAQ,IAC3B,GAAG;;;;;;;;;;;;qBAYe,iBAAiB;qBACjB,gBAAgB;sBAElC;CAED,MAAM,UAAU,gBAAgB,OAAO,KAAK,KAAK,MAAM,EAAE,YAAY,CAAC;AAEtE,MAAK,IAAI,IAAI,SAAS,IAAI,0BAA0B,QAAQ,KAAK;EAChE,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,SAAO,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB;;AAG9E,QAAO,EAAE,OAAO,OAAO;EAExB"}
@@ -1,13 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
3
+ const require_up_migrations_utils = require('./utils.cjs');
3
4
  let __sql_sql_ts = require("../sql/sql.cjs");
4
5
 
5
6
  //#region src/up-migrations/mssql.ts
6
- const CURRENT_MIGRATION_TABLE_VERSION = 1;
7
- function getVersion(columns) {
8
- if (columns.includes("name")) return 1;
9
- return 0;
10
- }
11
7
  /**
12
8
  * Map of upgrade functions. Each key is the version being upgraded FROM,
13
9
  * and the function upgrades the table to the next version.
@@ -56,20 +52,18 @@ async function upgradeIfNeeded(migrationsSchema, migrationsTable, session, local
56
52
  if ((await session.execute(__sql_sql_ts.sql`SELECT 1 FROM INFORMATION_SCHEMA.TABLES
57
53
  WHERE TABLE_SCHEMA = ${migrationsSchema}
58
54
  AND TABLE_NAME = ${migrationsTable}`)).recordset.length === 0) return { newDb: true };
59
- const version = getVersion((await session.execute(__sql_sql_ts.sql`SELECT COLUMN_NAME as [column_name]
55
+ const rows = await session.execute(__sql_sql_ts.sql`SELECT COLUMN_NAME as [column_name]
60
56
  FROM INFORMATION_SCHEMA.COLUMNS
61
57
  WHERE TABLE_SCHEMA = ${migrationsSchema}
62
58
  AND TABLE_NAME = ${migrationsTable}
63
- ORDER BY ORDINAL_POSITION`)).recordset.map((r) => r.column_name));
64
- for (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {
59
+ ORDER BY ORDINAL_POSITION`);
60
+ const version = require_up_migrations_utils.GET_VERSION_FOR.mssql(rows.recordset.map((r) => r.column_name));
61
+ for (let v = version; v < require_up_migrations_utils.MIGRATIONS_TABLE_VERSIONS.mssql; v++) {
65
62
  const upgradeFn = upgradeFunctions[v];
66
63
  if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
67
64
  await upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);
68
65
  }
69
- return {
70
- prevVersion: version,
71
- currentVersion: CURRENT_MIGRATION_TABLE_VERSION
72
- };
66
+ return { newDb: false };
73
67
  }
74
68
 
75
69
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"mssql.cjs","names":["sql"],"sources":["../../src/up-migrations/mssql.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { MsSqlSession } from '~/mssql-core/session.ts';\nimport { sql } from '~/sql/sql.ts';\n\nconst CURRENT_MIGRATION_TABLE_VERSION = 1;\n\ninterface UpgradeResult {\n\tnewDb?: boolean;\n\tprevVersion?: number;\n\tcurrentVersion?: number;\n}\n\nfunction getVersion(columns: string[]) {\n\tif (columns.includes('name')) return 1;\n\treturn 0;\n}\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: MsSqlSession,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, session, localMigrations) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await session.execute<{ recordset: { id: number; hash: string; created_at: string }[] }>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows.recordset) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tawait session.transaction(async (tx) => {\n\t\t\tawait tx.execute(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`);\n\t\t\tawait tx.execute(\n\t\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} datetime2 DEFAULT GETUTCDATE()`,\n\t\t\t);\n\t\t\tfor (const backfillEntry of toApply) {\n\t\t\t\tawait tx.execute(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tsession: MsSqlSession,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await session.execute<{ recordset: { [key: string]: unknown }[] }>(\n\t\tsql`SELECT 1 FROM INFORMATION_SCHEMA.TABLES \n\t\t\tWHERE TABLE_SCHEMA = ${migrationsSchema} \n\t\t\tAND TABLE_NAME = ${migrationsTable}`,\n\t);\n\n\tif (result.recordset.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await session.execute<{ recordset: { column_name: string }[] }>(\n\t\tsql`SELECT COLUMN_NAME as [column_name]\n\t\tFROM INFORMATION_SCHEMA.COLUMNS\n\t\tWHERE TABLE_SCHEMA = ${migrationsSchema}\n\t\tAND TABLE_NAME = ${migrationsTable}\n\t\tORDER BY ORDINAL_POSITION`,\n\t);\n\n\tconst version = getVersion(rows.recordset.map((r) => r.column_name));\n\n\tfor (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);\n\t}\n\n\treturn { prevVersion: version, currentVersion: CURRENT_MIGRATION_TABLE_VERSION };\n}\n"],"mappings":";;;;;AAIA,MAAM,kCAAkC;AAQxC,SAAS,WAAW,SAAmB;AACtC,KAAI,QAAQ,SAAS,OAAO,CAAE,QAAO;AACrC,QAAO;;;;;;AAOR,MAAM,mBAQF,EAUH,GAAG,OAAO,kBAAkB,iBAAiB,SAAS,oBAAoB;CACzE,MAAM,QAAQ,gBAAG,GAAGA,iBAAI,WAAW,iBAAiB,CAAC,GAAGA,iBAAI,WAAW,gBAAgB;CAIvF,MAAM,SAAS,MAAM,QAAQ,QAC5B,gBAAG,oCAAoC,MAAM,kBAC7C;AAGD,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,OAAO,WAAW;EACrC,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;AAIF,OAAM,QAAQ,YAAY,OAAO,OAAO;AACvC,QAAM,GAAG,QAAQ,gBAAG,eAAe,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,OAAO;AAC9E,QAAM,GAAG,QACR,gBAAG,eAAe,MAAM,OAAOA,iBAAI,WAAW,aAAa,CAAC,iCAC5D;AACD,OAAK,MAAM,iBAAiB,QAC3B,OAAM,GAAG,QACR,gBAAG,UAAU,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxEA,iBAAI,WAAW,aAAa,CAC5B,gBAAgBA,iBAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;GAED;GAEH;;;;;;;AAQD,eAAsB,gBACrB,kBACA,iBACA,SACA,iBACyB;AAQzB,MANe,MAAM,QAAQ,QAC5B,gBAAG;0BACqB,iBAAiB;sBACrB,kBACpB,EAEU,UAAU,WAAW,EAC/B,QAAO,EAAE,OAAO,MAAM;CAYvB,MAAM,UAAU,YARH,MAAM,QAAQ,QAC1B,gBAAG;;yBAEoB,iBAAiB;qBACrB,gBAAgB;6BAEnC,EAE+B,UAAU,KAAK,MAAM,EAAE,YAAY,CAAC;AAEpE,MAAK,IAAI,IAAI,SAAS,IAAI,iCAAiC,KAAK;EAC/D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB;;AAG7E,QAAO;EAAE,aAAa;EAAS,gBAAgB;EAAiC"}
1
+ {"version":3,"file":"mssql.cjs","names":["sql","GET_VERSION_FOR","MIGRATIONS_TABLE_VERSIONS"],"sources":["../../src/up-migrations/mssql.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { MsSqlSession } from '~/mssql-core/session.ts';\nimport { sql } from '~/sql/sql.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: MsSqlSession,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, session, localMigrations) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await session.execute<{ recordset: { id: number; hash: string; created_at: string }[] }>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows.recordset) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tawait session.transaction(async (tx) => {\n\t\t\tawait tx.execute(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`);\n\t\t\tawait tx.execute(\n\t\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} datetime2 DEFAULT GETUTCDATE()`,\n\t\t\t);\n\t\t\tfor (const backfillEntry of toApply) {\n\t\t\t\tawait tx.execute(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tsession: MsSqlSession,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await session.execute<{ recordset: { [key: string]: unknown }[] }>(\n\t\tsql`SELECT 1 FROM INFORMATION_SCHEMA.TABLES \n\t\t\tWHERE TABLE_SCHEMA = ${migrationsSchema} \n\t\t\tAND TABLE_NAME = ${migrationsTable}`,\n\t);\n\n\tif (result.recordset.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await session.execute<{ recordset: { column_name: string }[] }>(\n\t\tsql`SELECT COLUMN_NAME as [column_name]\n\t\tFROM INFORMATION_SCHEMA.COLUMNS\n\t\tWHERE TABLE_SCHEMA = ${migrationsSchema}\n\t\tAND TABLE_NAME = ${migrationsTable}\n\t\tORDER BY ORDINAL_POSITION`,\n\t);\n\n\tconst version = GET_VERSION_FOR.mssql(rows.recordset.map((r) => r.column_name));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.mssql; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":";;;;;;;;;;AASA,MAAM,mBAQF,EAUH,GAAG,OAAO,kBAAkB,iBAAiB,SAAS,oBAAoB;CACzE,MAAM,QAAQ,gBAAG,GAAGA,iBAAI,WAAW,iBAAiB,CAAC,GAAGA,iBAAI,WAAW,gBAAgB;CAIvF,MAAM,SAAS,MAAM,QAAQ,QAC5B,gBAAG,oCAAoC,MAAM,kBAC7C;AAGD,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,OAAO,WAAW;EACrC,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;AAIF,OAAM,QAAQ,YAAY,OAAO,OAAO;AACvC,QAAM,GAAG,QAAQ,gBAAG,eAAe,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,OAAO;AAC9E,QAAM,GAAG,QACR,gBAAG,eAAe,MAAM,OAAOA,iBAAI,WAAW,aAAa,CAAC,iCAC5D;AACD,OAAK,MAAM,iBAAiB,QAC3B,OAAM,GAAG,QACR,gBAAG,UAAU,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxEA,iBAAI,WAAW,aAAa,CAC5B,gBAAgBA,iBAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;GAED;GAEH;;;;;;;AAQD,eAAsB,gBACrB,kBACA,iBACA,SACA,iBACyB;AAQzB,MANe,MAAM,QAAQ,QAC5B,gBAAG;0BACqB,iBAAiB;sBACrB,kBACpB,EAEU,UAAU,WAAW,EAC/B,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,QAAQ,QAC1B,gBAAG;;yBAEoB,iBAAiB;qBACrB,gBAAgB;6BAEnC;CAED,MAAM,UAAUC,4CAAgB,MAAM,KAAK,UAAU,KAAK,MAAM,EAAE,YAAY,CAAC;AAE/E,MAAK,IAAI,IAAI,SAAS,IAAIC,sDAA0B,OAAO,KAAK;EAC/D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB;;AAG7E,QAAO,EAAE,OAAO,OAAO"}
@@ -1,12 +1,8 @@
1
+ import { UpgradeResult } from "./utils.cjs";
1
2
  import { MigrationMeta } from "../migrator.cjs";
2
3
  import { MsSqlSession } from "../mssql-core/session.cjs";
3
4
 
4
5
  //#region src/up-migrations/mssql.d.ts
5
- interface UpgradeResult {
6
- newDb?: boolean;
7
- prevVersion?: number;
8
- currentVersion?: number;
9
- }
10
6
  /**
11
7
  * Detects the current version of the migrations table schema and upgrades it if needed.
12
8
  *
@@ -1,12 +1,8 @@
1
+ import { UpgradeResult } from "./utils.js";
1
2
  import { MigrationMeta } from "../migrator.js";
2
3
  import { MsSqlSession } from "../mssql-core/session.js";
3
4
 
4
5
  //#region src/up-migrations/mssql.d.ts
5
- interface UpgradeResult {
6
- newDb?: boolean;
7
- prevVersion?: number;
8
- currentVersion?: number;
9
- }
10
6
  /**
11
7
  * Detects the current version of the migrations table schema and upgrades it if needed.
12
8
  *
@@ -1,11 +1,7 @@
1
+ import { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS } from "./utils.js";
1
2
  import { sql } from "../sql/sql.js";
2
3
 
3
4
  //#region src/up-migrations/mssql.ts
4
- const CURRENT_MIGRATION_TABLE_VERSION = 1;
5
- function getVersion(columns) {
6
- if (columns.includes("name")) return 1;
7
- return 0;
8
- }
9
5
  /**
10
6
  * Map of upgrade functions. Each key is the version being upgraded FROM,
11
7
  * and the function upgrades the table to the next version.
@@ -54,20 +50,18 @@ async function upgradeIfNeeded(migrationsSchema, migrationsTable, session, local
54
50
  if ((await session.execute(sql`SELECT 1 FROM INFORMATION_SCHEMA.TABLES
55
51
  WHERE TABLE_SCHEMA = ${migrationsSchema}
56
52
  AND TABLE_NAME = ${migrationsTable}`)).recordset.length === 0) return { newDb: true };
57
- const version = getVersion((await session.execute(sql`SELECT COLUMN_NAME as [column_name]
53
+ const rows = await session.execute(sql`SELECT COLUMN_NAME as [column_name]
58
54
  FROM INFORMATION_SCHEMA.COLUMNS
59
55
  WHERE TABLE_SCHEMA = ${migrationsSchema}
60
56
  AND TABLE_NAME = ${migrationsTable}
61
- ORDER BY ORDINAL_POSITION`)).recordset.map((r) => r.column_name));
62
- for (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {
57
+ ORDER BY ORDINAL_POSITION`);
58
+ const version = GET_VERSION_FOR.mssql(rows.recordset.map((r) => r.column_name));
59
+ for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.mssql; v++) {
63
60
  const upgradeFn = upgradeFunctions[v];
64
61
  if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
65
62
  await upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);
66
63
  }
67
- return {
68
- prevVersion: version,
69
- currentVersion: CURRENT_MIGRATION_TABLE_VERSION
70
- };
64
+ return { newDb: false };
71
65
  }
72
66
 
73
67
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"mssql.js","names":[],"sources":["../../src/up-migrations/mssql.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { MsSqlSession } from '~/mssql-core/session.ts';\nimport { sql } from '~/sql/sql.ts';\n\nconst CURRENT_MIGRATION_TABLE_VERSION = 1;\n\ninterface UpgradeResult {\n\tnewDb?: boolean;\n\tprevVersion?: number;\n\tcurrentVersion?: number;\n}\n\nfunction getVersion(columns: string[]) {\n\tif (columns.includes('name')) return 1;\n\treturn 0;\n}\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: MsSqlSession,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, session, localMigrations) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await session.execute<{ recordset: { id: number; hash: string; created_at: string }[] }>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows.recordset) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tawait session.transaction(async (tx) => {\n\t\t\tawait tx.execute(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`);\n\t\t\tawait tx.execute(\n\t\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} datetime2 DEFAULT GETUTCDATE()`,\n\t\t\t);\n\t\t\tfor (const backfillEntry of toApply) {\n\t\t\t\tawait tx.execute(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tsession: MsSqlSession,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await session.execute<{ recordset: { [key: string]: unknown }[] }>(\n\t\tsql`SELECT 1 FROM INFORMATION_SCHEMA.TABLES \n\t\t\tWHERE TABLE_SCHEMA = ${migrationsSchema} \n\t\t\tAND TABLE_NAME = ${migrationsTable}`,\n\t);\n\n\tif (result.recordset.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await session.execute<{ recordset: { column_name: string }[] }>(\n\t\tsql`SELECT COLUMN_NAME as [column_name]\n\t\tFROM INFORMATION_SCHEMA.COLUMNS\n\t\tWHERE TABLE_SCHEMA = ${migrationsSchema}\n\t\tAND TABLE_NAME = ${migrationsTable}\n\t\tORDER BY ORDINAL_POSITION`,\n\t);\n\n\tconst version = getVersion(rows.recordset.map((r) => r.column_name));\n\n\tfor (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);\n\t}\n\n\treturn { prevVersion: version, currentVersion: CURRENT_MIGRATION_TABLE_VERSION };\n}\n"],"mappings":";;;AAIA,MAAM,kCAAkC;AAQxC,SAAS,WAAW,SAAmB;AACtC,KAAI,QAAQ,SAAS,OAAO,CAAE,QAAO;AACrC,QAAO;;;;;;AAOR,MAAM,mBAQF,EAUH,GAAG,OAAO,kBAAkB,iBAAiB,SAAS,oBAAoB;CACzE,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,iBAAiB,CAAC,GAAG,IAAI,WAAW,gBAAgB;CAIvF,MAAM,SAAS,MAAM,QAAQ,QAC5B,GAAG,oCAAoC,MAAM,kBAC7C;AAGD,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,OAAO,WAAW;EACrC,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;AAIF,OAAM,QAAQ,YAAY,OAAO,OAAO;AACvC,QAAM,GAAG,QAAQ,GAAG,eAAe,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,OAAO;AAC9E,QAAM,GAAG,QACR,GAAG,eAAe,MAAM,OAAO,IAAI,WAAW,aAAa,CAAC,iCAC5D;AACD,OAAK,MAAM,iBAAiB,QAC3B,OAAM,GAAG,QACR,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxE,IAAI,WAAW,aAAa,CAC5B,gBAAgB,IAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;GAED;GAEH;;;;;;;AAQD,eAAsB,gBACrB,kBACA,iBACA,SACA,iBACyB;AAQzB,MANe,MAAM,QAAQ,QAC5B,GAAG;0BACqB,iBAAiB;sBACrB,kBACpB,EAEU,UAAU,WAAW,EAC/B,QAAO,EAAE,OAAO,MAAM;CAYvB,MAAM,UAAU,YARH,MAAM,QAAQ,QAC1B,GAAG;;yBAEoB,iBAAiB;qBACrB,gBAAgB;6BAEnC,EAE+B,UAAU,KAAK,MAAM,EAAE,YAAY,CAAC;AAEpE,MAAK,IAAI,IAAI,SAAS,IAAI,iCAAiC,KAAK;EAC/D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB;;AAG7E,QAAO;EAAE,aAAa;EAAS,gBAAgB;EAAiC"}
1
+ {"version":3,"file":"mssql.js","names":[],"sources":["../../src/up-migrations/mssql.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { MsSqlSession } from '~/mssql-core/session.ts';\nimport { sql } from '~/sql/sql.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tsession: MsSqlSession,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsSchema, migrationsTable, session, localMigrations) => {\n\t\tconst table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = await session.execute<{ recordset: { id: number; hash: string; created_at: string }[] }>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t);\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows.recordset) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tawait session.transaction(async (tx) => {\n\t\t\tawait tx.execute(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`);\n\t\t\tawait tx.execute(\n\t\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} datetime2 DEFAULT GETUTCDATE()`,\n\t\t\t);\n\t\t\tfor (const backfillEntry of toApply) {\n\t\t\t\tawait tx.execute(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tsession: MsSqlSession,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await session.execute<{ recordset: { [key: string]: unknown }[] }>(\n\t\tsql`SELECT 1 FROM INFORMATION_SCHEMA.TABLES \n\t\t\tWHERE TABLE_SCHEMA = ${migrationsSchema} \n\t\t\tAND TABLE_NAME = ${migrationsTable}`,\n\t);\n\n\tif (result.recordset.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await session.execute<{ recordset: { column_name: string }[] }>(\n\t\tsql`SELECT COLUMN_NAME as [column_name]\n\t\tFROM INFORMATION_SCHEMA.COLUMNS\n\t\tWHERE TABLE_SCHEMA = ${migrationsSchema}\n\t\tAND TABLE_NAME = ${migrationsTable}\n\t\tORDER BY ORDINAL_POSITION`,\n\t);\n\n\tconst version = GET_VERSION_FOR.mssql(rows.recordset.map((r) => r.column_name));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.mssql; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsSchema, migrationsTable, session, localMigrations);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":";;;;;;;;AASA,MAAM,mBAQF,EAUH,GAAG,OAAO,kBAAkB,iBAAiB,SAAS,oBAAoB;CACzE,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,iBAAiB,CAAC,GAAG,IAAI,WAAW,gBAAgB;CAIvF,MAAM,SAAS,MAAM,QAAQ,QAC5B,GAAG,oCAAoC,MAAM,kBAC7C;AAGD,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,OAAO,WAAW;EACrC,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;AAIF,OAAM,QAAQ,YAAY,OAAO,OAAO;AACvC,QAAM,GAAG,QAAQ,GAAG,eAAe,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,OAAO;AAC9E,QAAM,GAAG,QACR,GAAG,eAAe,MAAM,OAAO,IAAI,WAAW,aAAa,CAAC,iCAC5D;AACD,OAAK,MAAM,iBAAiB,QAC3B,OAAM,GAAG,QACR,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxE,IAAI,WAAW,aAAa,CAC5B,gBAAgB,IAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;GAED;GAEH;;;;;;;AAQD,eAAsB,gBACrB,kBACA,iBACA,SACA,iBACyB;AAQzB,MANe,MAAM,QAAQ,QAC5B,GAAG;0BACqB,iBAAiB;sBACrB,kBACpB,EAEU,UAAU,WAAW,EAC/B,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,QAAQ,QAC1B,GAAG;;yBAEoB,iBAAiB;qBACrB,gBAAgB;6BAEnC;CAED,MAAM,UAAU,gBAAgB,MAAM,KAAK,UAAU,KAAK,MAAM,EAAE,YAAY,CAAC;AAE/E,MAAK,IAAI,IAAI,SAAS,IAAI,0BAA0B,OAAO,KAAK;EAC/D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB;;AAG7E,QAAO,EAAE,OAAO,OAAO"}
@@ -0,0 +1,73 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
3
+ const require_up_migrations_utils = require('./utils.cjs');
4
+ let __sql_sql_ts = require("../sql/sql.cjs");
5
+
6
+ //#region src/up-migrations/mysql-proxy.ts
7
+ /**
8
+ * Map of upgrade functions. Each key is the version being upgraded FROM,
9
+ * and the function upgrades the table to the next version.
10
+ */
11
+ const upgradeFunctions = { 0: async (migrationsTable, db, callback, localMigrations) => {
12
+ const table = __sql_sql_ts.sql`${__sql_sql_ts.sql.identifier(migrationsTable)}`;
13
+ const dbRows = (await db.session.all(__sql_sql_ts.sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`)).map((it) => ({
14
+ id: it[0],
15
+ hash: it[1],
16
+ created_at: it[2]
17
+ }));
18
+ localMigrations.sort((a, b) => a.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? "").localeCompare(b.name ?? ""));
19
+ const byMillis = /* @__PURE__ */ new Map();
20
+ const byHash = /* @__PURE__ */ new Map();
21
+ for (const lm of localMigrations) {
22
+ if (!byMillis.has(lm.folderMillis)) byMillis.set(lm.folderMillis, []);
23
+ byMillis.get(lm.folderMillis).push(lm);
24
+ byHash.set(lm.hash, lm);
25
+ }
26
+ const toApply = [];
27
+ let unmatchedIds = [];
28
+ for (const dbRow of dbRows) {
29
+ const stringified = String(dbRow.created_at);
30
+ const millis = Number(stringified.substring(0, stringified.length - 3) + "000");
31
+ const candidates = byMillis.get(millis);
32
+ let matched;
33
+ if (candidates && candidates.length === 1) matched = candidates[0];
34
+ else if (candidates && candidates.length > 1) matched = candidates.find((c) => c.hash === dbRow.hash);
35
+ else matched = byHash.get(dbRow.hash);
36
+ if (matched) toApply.push({
37
+ id: dbRow.id,
38
+ name: matched.name
39
+ });
40
+ else unmatchedIds.push(dbRow.id);
41
+ }
42
+ if (unmatchedIds.length > 0) throw Error(`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${unmatchedIds.join(", ")}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`);
43
+ const statements = [db.dialect.sqlToQuery(__sql_sql_ts.sql`ALTER TABLE ${table} ADD ${__sql_sql_ts.sql.identifier("name")} text`.inlineParams()).sql, db.dialect.sqlToQuery(__sql_sql_ts.sql`ALTER TABLE ${table} ADD ${__sql_sql_ts.sql.identifier("applied_at")} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`.inlineParams()).sql];
44
+ for (const backfillEntry of toApply) statements.push(db.dialect.sqlToQuery(__sql_sql_ts.sql`UPDATE ${table} SET ${__sql_sql_ts.sql.identifier("name")} = ${backfillEntry.name}, ${__sql_sql_ts.sql.identifier("applied_at")} = NULL WHERE ${__sql_sql_ts.sql.identifier("id")} = ${backfillEntry.id}`.inlineParams()).sql);
45
+ await callback(statements);
46
+ } };
47
+ /**
48
+ * Detects the current version of the migrations table schema and upgrades it if needed.
49
+ *
50
+ * Version 0: Original schema (id, hash, created_at)
51
+ * Version 1: Extended schema (id, hash, created_at, name, applied_at)
52
+ */
53
+ async function upgradeIfNeeded(migrationsTable, db, callback, localMigrations) {
54
+ if ((await db.session.all(__sql_sql_ts.sql`SELECT 1 FROM information_schema.tables
55
+ WHERE table_name = ${migrationsTable}
56
+ AND table_schema = DATABASE()`)).length === 0) return { newDb: true };
57
+ const rows = await db.session.all(__sql_sql_ts.sql`SELECT column_name as \`column_name\`
58
+ FROM information_schema.columns
59
+ WHERE table_name = ${migrationsTable}
60
+ AND table_schema = DATABASE()
61
+ ORDER BY ordinal_position`);
62
+ const version = require_up_migrations_utils.GET_VERSION_FOR.mysql(rows.map((r) => r[0]));
63
+ for (let v = version; v < require_up_migrations_utils.MIGRATIONS_TABLE_VERSIONS.mysql; v++) {
64
+ const upgradeFn = upgradeFunctions[v];
65
+ if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
66
+ await upgradeFn(migrationsTable, db, callback, localMigrations);
67
+ }
68
+ return { newDb: false };
69
+ }
70
+
71
+ //#endregion
72
+ exports.upgradeIfNeeded = upgradeIfNeeded;
73
+ //# sourceMappingURL=mysql-proxy.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql-proxy.cjs","names":["sql","GET_VERSION_FOR","MIGRATIONS_TABLE_VERSIONS"],"sources":["../../src/up-migrations/mysql-proxy.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { MySqlRemoteDatabase } from '~/mysql-proxy/index.ts';\nimport type { ProxyMigrator } from '~/mysql-proxy/migrator.ts';\nimport { sql } from '~/sql/sql.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\n\n/**\n * Map of upgrade functions. Each key is the version being upgraded FROM,\n * and the function upgrades the table to the next version.\n */\nconst upgradeFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsTable: string,\n\t\tdb: MySqlRemoteDatabase<Record<string, unknown>>,\n\t\tcallback: ProxyMigrator,\n\t\tlocalMigrations: MigrationMeta[],\n\t) => Promise<void>\n> = {\n\t/**\n\t * Upgrade from version 0 to version 1:\n\t * 1. Read all existing DB migrations\n\t * 2. Sort localMigrations ASC by millis and if the same - sort by name\n\t * 3. Match each DB row to a local migration\n\t * If multiple migrations share the same second, use hash matching as a tiebreaker\n\t * Not implemented for now -> If hash matching fails, fall back to serial id ordering\n\t * 5. Create extra column and backfill names for matched migrations\n\t */\n\t0: async (migrationsTable, db, callback, localMigrations) => {\n\t\tconst table = sql`${sql.identifier(migrationsTable)}`;\n\n\t\t// 1. Read all existing DB migrations\n\t\t// Sort them by ids asc (order how they were applied)\n\t\tconst dbRows = (await db.session.all<[number, string, string]>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t)).map((it) => ({\n\t\t\tid: it[0],\n\t\t\thash: it[1],\n\t\t\tcreated_at: it[2],\n\t\t}));\n\n\t\t// 2. Sort ASC by millis and if the same - sort by name\n\t\tlocalMigrations.sort((a, b) =>\n\t\t\ta.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? '').localeCompare(b.name ?? '')\n\t\t);\n\n\t\tconst byMillis = new Map<number, MigrationMeta[]>();\n\t\tconst byHash = new Map<string, MigrationMeta>();\n\t\tfor (const lm of localMigrations) {\n\t\t\tif (!byMillis.has(lm.folderMillis)) {\n\t\t\t\tbyMillis.set(lm.folderMillis, []);\n\t\t\t}\n\t\t\tbyMillis.get(lm.folderMillis)!.push(lm);\n\t\t\tbyHash.set(lm.hash, lm);\n\t\t}\n\n\t\t// \t3. Match each DB row to a local migration\n\t\t// \tPriority: millis -> hash\n\t\tconst toApply: { id: number; name: string }[] = [];\n\t\tlet unmatchedIds: number[] = [];\n\n\t\tfor (const dbRow of dbRows) {\n\t\t\tconst stringified = String(dbRow.created_at);\n\t\t\tconst millis = Number(stringified.substring(0, stringified.length - 3) + '000');\n\t\t\tconst candidates = byMillis.get(millis);\n\n\t\t\tlet matched: MigrationMeta | undefined;\n\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash === dbRow.hash);\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t}\n\n\t\t\tif (matched) toApply.push({ id: dbRow.id, name: matched.name });\n\t\t\telse unmatchedIds.push(dbRow.id);\n\t\t}\n\n\t\t// 4. Check for unmatched\n\t\t// Our assumption on this migration flow is that all DB entries should be matched to a local migration\n\t\t// (if same seconds - fallback to hash, if hash fails - corner case)\n\t\t// If there are unmatched entries, it means that the local environment is missing migrations that have been applied to the DB,\n\t\t// which can lead to inconsistencies and potential issues when running future migrations\n\t\tif (unmatchedIds.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatchedIds.length} migrations (ids: ${\n\t\t\t\t\tunmatchedIds.join(', ')\n\t\t\t\t}) in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environment`,\n\t\t\t);\n\t\t}\n\n\t\t// 5. Create extra column and backfill names for matched migrations\n\t\tconst statements: string[] = [\n\t\t\tdb.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`.inlineParams()).sql,\n\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`\n\t\t\t\t\t.inlineParams(),\n\t\t\t).sql,\n\t\t];\n\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tstatements.push(\n\t\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`.inlineParams(),\n\t\t\t\t).sql,\n\t\t\t);\n\t\t}\n\n\t\tawait callback(statements);\n\t\treturn;\n\t},\n};\n\n/**\n * Detects the current version of the migrations table schema and upgrades it if needed.\n *\n * Version 0: Original schema (id, hash, created_at)\n * Version 1: Extended schema (id, hash, created_at, name, applied_at)\n */\nexport async function upgradeIfNeeded(\n\tmigrationsTable: string,\n\tdb: MySqlRemoteDatabase<Record<string, unknown>>,\n\tcallback: ProxyMigrator,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await db.session.all<[1]>(\n\t\tsql`SELECT 1 FROM information_schema.tables \n\t\t\tWHERE table_name = ${migrationsTable}\n\t\t\tAND table_schema = DATABASE()`,\n\t);\n\n\tif (result.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await db.session.all<[string]>(\n\t\tsql`SELECT column_name as \\`column_name\\`\n\t\tFROM information_schema.columns\n\t\tWHERE table_name = ${migrationsTable}\n\t\tAND table_schema = DATABASE()\n\t\tORDER BY ordinal_position`,\n\t);\n\n\tconst version = GET_VERSION_FOR.mysql(rows.map((r) => r[0]));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.mysql; v++) {\n\t\tconst upgradeFn = upgradeFunctions[v];\n\t\tif (!upgradeFn) {\n\t\t\tthrow new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);\n\t\t}\n\t\tawait upgradeFn(migrationsTable, db, callback, localMigrations);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAM,mBAQF,EAUH,GAAG,OAAO,iBAAiB,IAAI,UAAU,oBAAoB;CAC5D,MAAM,QAAQ,gBAAG,GAAGA,iBAAI,WAAW,gBAAgB;CAInD,MAAM,UAAU,MAAM,GAAG,QAAQ,IAChC,gBAAG,oCAAoC,MAAM,kBAC7C,EAAE,KAAK,QAAQ;EACf,IAAI,GAAG;EACP,MAAM,GAAG;EACT,YAAY,GAAG;EACf,EAAE;AAGH,iBAAgB,MAAM,GAAG,MACxB,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG,CAChH;CAED,MAAM,2BAAW,IAAI,KAA8B;CACnD,MAAM,yBAAS,IAAI,KAA4B;AAC/C,MAAK,MAAM,MAAM,iBAAiB;AACjC,MAAI,CAAC,SAAS,IAAI,GAAG,aAAa,CACjC,UAAS,IAAI,GAAG,cAAc,EAAE,CAAC;AAElC,WAAS,IAAI,GAAG,aAAa,CAAE,KAAK,GAAG;AACvC,SAAO,IAAI,GAAG,MAAM,GAAG;;CAKxB,MAAM,UAA0C,EAAE;CAClD,IAAI,eAAyB,EAAE;AAE/B,MAAK,MAAM,SAAS,QAAQ;EAC3B,MAAM,cAAc,OAAO,MAAM,WAAW;EAC5C,MAAM,SAAS,OAAO,YAAY,UAAU,GAAG,YAAY,SAAS,EAAE,GAAG,MAAM;EAC/E,MAAM,aAAa,SAAS,IAAI,OAAO;EAEvC,IAAI;AAEJ,MAAI,cAAc,WAAW,WAAW,EACvC,WAAU,WAAW;WACX,cAAc,WAAW,SAAS,EAC5C,WAAU,WAAW,MAAM,MAAM,EAAE,SAAS,MAAM,KAAK;MAEvD,WAAU,OAAO,IAAI,MAAM,KAAK;AAGjC,MAAI,QAAS,SAAQ,KAAK;GAAE,IAAI,MAAM;GAAI,MAAM,QAAQ;GAAM,CAAC;MAC1D,cAAa,KAAK,MAAM,GAAG;;AAQjC,KAAI,aAAa,SAAS,EACzB,OAAM,MACL,2DAA2D,aAAa,OAAO,oBAC9E,aAAa,KAAK,KAAK,CACvB,kKACD;CAIF,MAAM,aAAuB,CAC5B,GAAG,QAAQ,WAAW,gBAAG,eAAe,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,OAAO,cAAc,CAAC,CAAC,KACnG,GAAG,QAAQ,WACV,gBAAG,eAAe,MAAM,OAAOA,iBAAI,WAAW,aAAa,CAAC,sCAC1D,cAAc,CAChB,CAAC,IACF;AAED,MAAK,MAAM,iBAAiB,QAC3B,YAAW,KACV,GAAG,QAAQ,WACV,gBAAG,UAAU,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxEA,iBAAI,WAAW,aAAa,CAC5B,gBAAgBA,iBAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KAAK,cAAc,CAC5E,CAAC,IACF;AAGF,OAAM,SAAS,WAAW;GAG3B;;;;;;;AAQD,eAAsB,gBACrB,iBACA,IACA,UACA,iBACyB;AAQzB,MANe,MAAM,GAAG,QAAQ,IAC/B,gBAAG;wBACmB,gBAAgB;kCAEtC,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,GAAG,QAAQ,IAC7B,gBAAG;;uBAEkB,gBAAgB;;6BAGrC;CAED,MAAM,UAAUC,4CAAgB,MAAM,KAAK,KAAK,MAAM,EAAE,GAAG,CAAC;AAE5D,MAAK,IAAI,IAAI,SAAS,IAAIC,sDAA0B,OAAO,KAAK;EAC/D,MAAM,YAAY,iBAAiB;AACnC,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,gDAAgD,EAAE,MAAM,IAAI,IAAI;AAEjF,QAAM,UAAU,iBAAiB,IAAI,UAAU,gBAAgB;;AAGhE,QAAO,EAAE,OAAO,OAAO"}