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
@@ -0,0 +1,16 @@
1
+ import { UpgradeResult } from "./utils.cjs";
2
+ import { MigrationMeta } from "../migrator.cjs";
3
+ import { MySqlRemoteDatabase } from "../mysql-proxy/index.cjs";
4
+ import { ProxyMigrator } from "../mysql-proxy/migrator.cjs";
5
+
6
+ //#region src/up-migrations/mysql-proxy.d.ts
7
+ /**
8
+ * Detects the current version of the migrations table schema and upgrades it if needed.
9
+ *
10
+ * Version 0: Original schema (id, hash, created_at)
11
+ * Version 1: Extended schema (id, hash, created_at, name, applied_at)
12
+ */
13
+ declare function upgradeIfNeeded(migrationsTable: string, db: MySqlRemoteDatabase<Record<string, unknown>>, callback: ProxyMigrator, localMigrations: MigrationMeta[]): Promise<UpgradeResult>;
14
+ //#endregion
15
+ export { upgradeIfNeeded };
16
+ //# sourceMappingURL=mysql-proxy.d.cts.map
@@ -0,0 +1,16 @@
1
+ import { UpgradeResult } from "./utils.js";
2
+ import { MigrationMeta } from "../migrator.js";
3
+ import { MySqlRemoteDatabase } from "../mysql-proxy/index.js";
4
+ import { ProxyMigrator } from "../mysql-proxy/migrator.js";
5
+
6
+ //#region src/up-migrations/mysql-proxy.d.ts
7
+ /**
8
+ * Detects the current version of the migrations table schema and upgrades it if needed.
9
+ *
10
+ * Version 0: Original schema (id, hash, created_at)
11
+ * Version 1: Extended schema (id, hash, created_at, name, applied_at)
12
+ */
13
+ declare function upgradeIfNeeded(migrationsTable: string, db: MySqlRemoteDatabase<Record<string, unknown>>, callback: ProxyMigrator, localMigrations: MigrationMeta[]): Promise<UpgradeResult>;
14
+ //#endregion
15
+ export { upgradeIfNeeded };
16
+ //# sourceMappingURL=mysql-proxy.d.ts.map
@@ -0,0 +1,71 @@
1
+ import { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS } from "./utils.js";
2
+ import { sql } from "../sql/sql.js";
3
+
4
+ //#region src/up-migrations/mysql-proxy.ts
5
+ /**
6
+ * Map of upgrade functions. Each key is the version being upgraded FROM,
7
+ * and the function upgrades the table to the next version.
8
+ */
9
+ const upgradeFunctions = { 0: async (migrationsTable, db, callback, localMigrations) => {
10
+ const table = sql`${sql.identifier(migrationsTable)}`;
11
+ const dbRows = (await db.session.all(sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`)).map((it) => ({
12
+ id: it[0],
13
+ hash: it[1],
14
+ created_at: it[2]
15
+ }));
16
+ localMigrations.sort((a, b) => a.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? "").localeCompare(b.name ?? ""));
17
+ const byMillis = /* @__PURE__ */ new Map();
18
+ const byHash = /* @__PURE__ */ new Map();
19
+ for (const lm of localMigrations) {
20
+ if (!byMillis.has(lm.folderMillis)) byMillis.set(lm.folderMillis, []);
21
+ byMillis.get(lm.folderMillis).push(lm);
22
+ byHash.set(lm.hash, lm);
23
+ }
24
+ const toApply = [];
25
+ let unmatchedIds = [];
26
+ for (const dbRow of dbRows) {
27
+ const stringified = String(dbRow.created_at);
28
+ const millis = Number(stringified.substring(0, stringified.length - 3) + "000");
29
+ const candidates = byMillis.get(millis);
30
+ let matched;
31
+ if (candidates && candidates.length === 1) matched = candidates[0];
32
+ else if (candidates && candidates.length > 1) matched = candidates.find((c) => c.hash === dbRow.hash);
33
+ else matched = byHash.get(dbRow.hash);
34
+ if (matched) toApply.push({
35
+ id: dbRow.id,
36
+ name: matched.name
37
+ });
38
+ else unmatchedIds.push(dbRow.id);
39
+ }
40
+ 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`);
41
+ const statements = [db.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD ${sql.identifier("name")} text`.inlineParams()).sql, db.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD ${sql.identifier("applied_at")} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`.inlineParams()).sql];
42
+ for (const backfillEntry of toApply) statements.push(db.dialect.sqlToQuery(sql`UPDATE ${table} SET ${sql.identifier("name")} = ${backfillEntry.name}, ${sql.identifier("applied_at")} = NULL WHERE ${sql.identifier("id")} = ${backfillEntry.id}`.inlineParams()).sql);
43
+ await callback(statements);
44
+ } };
45
+ /**
46
+ * Detects the current version of the migrations table schema and upgrades it if needed.
47
+ *
48
+ * Version 0: Original schema (id, hash, created_at)
49
+ * Version 1: Extended schema (id, hash, created_at, name, applied_at)
50
+ */
51
+ async function upgradeIfNeeded(migrationsTable, db, callback, localMigrations) {
52
+ if ((await db.session.all(sql`SELECT 1 FROM information_schema.tables
53
+ WHERE table_name = ${migrationsTable}
54
+ AND table_schema = DATABASE()`)).length === 0) return { newDb: true };
55
+ const rows = await db.session.all(sql`SELECT column_name as \`column_name\`
56
+ FROM information_schema.columns
57
+ WHERE table_name = ${migrationsTable}
58
+ AND table_schema = DATABASE()
59
+ ORDER BY ordinal_position`);
60
+ const version = GET_VERSION_FOR.mysql(rows.map((r) => r[0]));
61
+ for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.mysql; v++) {
62
+ const upgradeFn = upgradeFunctions[v];
63
+ if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
64
+ await upgradeFn(migrationsTable, db, callback, localMigrations);
65
+ }
66
+ return { newDb: false };
67
+ }
68
+
69
+ //#endregion
70
+ export { upgradeIfNeeded };
71
+ //# sourceMappingURL=mysql-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql-proxy.js","names":[],"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,GAAG,GAAG,IAAI,WAAW,gBAAgB;CAInD,MAAM,UAAU,MAAM,GAAG,QAAQ,IAChC,GAAG,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,GAAG,eAAe,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,OAAO,cAAc,CAAC,CAAC,KACnG,GAAG,QAAQ,WACV,GAAG,eAAe,MAAM,OAAO,IAAI,WAAW,aAAa,CAAC,sCAC1D,cAAc,CAChB,CAAC,IACF;AAED,MAAK,MAAM,iBAAiB,QAC3B,YAAW,KACV,GAAG,QAAQ,WACV,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxE,IAAI,WAAW,aAAa,CAC5B,gBAAgB,IAAI,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,GAAG;wBACmB,gBAAgB;kCAEtC,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,GAAG,QAAQ,IAC7B,GAAG;;uBAEkB,gBAAgB;;6BAGrC;CAED,MAAM,UAAU,gBAAgB,MAAM,KAAK,KAAK,MAAM,EAAE,GAAG,CAAC;AAE5D,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,iBAAiB,IAAI,UAAU,gBAAgB;;AAGhE,QAAO,EAAE,OAAO,OAAO"}
@@ -1,30 +1,16 @@
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/mysql.ts
6
- const CURRENT_MIGRATION_TABLE_VERSION = 1;
7
- function getVersion(columns) {
8
- if (columns.includes("name")) return 1;
9
- return 0;
10
- }
11
- async function all(session, sqlQuery, resultMapper = () => []) {
12
- const result = await session.all(sqlQuery);
13
- if (result.length === 0) return [];
14
- if (Array.isArray(result[0])) return result.map((row) => resultMapper(row));
15
- return result;
16
- }
17
7
  /**
18
8
  * Map of upgrade functions. Each key is the version being upgraded FROM,
19
9
  * and the function upgrades the table to the next version.
20
10
  */
21
11
  const upgradeFunctions = { 0: async (migrationsTable, session, localMigrations) => {
22
12
  const table = __sql_sql_ts.sql`${__sql_sql_ts.sql.identifier(migrationsTable)}`;
23
- const dbRows = await all(session, __sql_sql_ts.sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`, (row) => ({
24
- id: row[0],
25
- hash: row[1],
26
- created_at: row[2]
27
- }));
13
+ const dbRows = await session.all(__sql_sql_ts.sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`);
28
14
  localMigrations.sort((a, b) => a.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? "").localeCompare(b.name ?? ""));
29
15
  const byMillis = /* @__PURE__ */ new Map();
30
16
  const byHash = /* @__PURE__ */ new Map();
@@ -50,9 +36,9 @@ const upgradeFunctions = { 0: async (migrationsTable, session, localMigrations)
50
36
  else unmatchedIds.push(dbRow.id);
51
37
  }
52
38
  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`);
53
- await session.execute(__sql_sql_ts.sql`ALTER TABLE ${table} ADD ${__sql_sql_ts.sql.identifier("name")} text`);
54
- await session.execute(__sql_sql_ts.sql`ALTER TABLE ${table} ADD ${__sql_sql_ts.sql.identifier("applied_at")} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`);
55
- for (const backfillEntry of toApply) await session.execute(__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}`);
39
+ await session.all(__sql_sql_ts.sql`ALTER TABLE ${table} ADD ${__sql_sql_ts.sql.identifier("name")} text`);
40
+ await session.all(__sql_sql_ts.sql`ALTER TABLE ${table} ADD ${__sql_sql_ts.sql.identifier("applied_at")} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`);
41
+ for (const backfillEntry of toApply) await session.all(__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}`);
56
42
  } };
57
43
  /**
58
44
  * Detects the current version of the migrations table schema and upgrades it if needed.
@@ -61,23 +47,21 @@ const upgradeFunctions = { 0: async (migrationsTable, session, localMigrations)
61
47
  * Version 1: Extended schema (id, hash, created_at, name, applied_at)
62
48
  */
63
49
  async function upgradeIfNeeded(migrationsTable, session, localMigrations) {
64
- if ((await all(session, __sql_sql_ts.sql`SELECT 1 FROM information_schema.tables
50
+ if ((await session.all(__sql_sql_ts.sql`SELECT 1 FROM information_schema.tables
65
51
  WHERE table_name = ${migrationsTable}
66
- AND table_schema = DATABASE()`, (row) => ({ "1": row[0] }))).length === 0) return { newDb: true };
67
- const version = getVersion((await all(session, __sql_sql_ts.sql`SELECT column_name as \`column_name\`
52
+ AND table_schema = DATABASE()`)).length === 0) return { newDb: true };
53
+ const rows = await session.all(__sql_sql_ts.sql`SELECT column_name as \`column_name\`
68
54
  FROM information_schema.columns
69
55
  WHERE table_name = ${migrationsTable}
70
56
  AND table_schema = DATABASE()
71
- ORDER BY ordinal_position`, (row) => ({ column_name: row[0] }))).map((r) => r.column_name));
72
- for (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {
57
+ ORDER BY ordinal_position`);
58
+ const version = require_up_migrations_utils.GET_VERSION_FOR.mysql(rows.map((r) => r.column_name));
59
+ for (let v = version; v < require_up_migrations_utils.MIGRATIONS_TABLE_VERSIONS.mysql; v++) {
73
60
  const upgradeFn = upgradeFunctions[v];
74
61
  if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
75
62
  await upgradeFn(migrationsTable, session, localMigrations);
76
63
  }
77
- return {
78
- prevVersion: version,
79
- currentVersion: CURRENT_MIGRATION_TABLE_VERSION
80
- };
64
+ return { newDb: false };
81
65
  }
82
66
 
83
67
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"mysql.cjs","names":["sql"],"sources":["../../src/up-migrations/mysql.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { MySqlSession } from '~/mysql-core/session.ts';\nimport { type SQL, 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// mysql returns array of objects for .all, but mysql-proxy -> array of arrays\nasync function all<T>(\n\tsession: MySqlSession,\n\tsqlQuery: SQL,\n\tresultMapper: (row: any[]) => T = () => [] as T,\n): Promise<T[]> {\n\tconst result = await session.all(sqlQuery) as any[] | any[][];\n\n\tif (result.length === 0) return [];\n\n\tif (Array.isArray(result[0])) {\n\t\treturn (result as any[][]).map((row) => resultMapper(row));\n\t}\n\n\treturn result as T[];\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\tmigrationsTable: string,\n\t\tsession: MySqlSession,\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, session, 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 all<{ id: number; hash: string; created_at: string }>(\n\t\t\tsession,\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t\t(row) => ({\n\t\t\t\tid: row[0],\n\t\t\t\thash: row[1],\n\t\t\t\tcreated_at: row[2],\n\t\t\t}),\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.execute(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`);\n\t\tawait session.execute(\n\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`,\n\t\t);\n\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tawait session.execute(\n\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\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\tmigrationsTable: string,\n\tsession: MySqlSession,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await all<{ '1': 1 }>(\n\t\tsession,\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\t(row) => ({ '1': row[0] }),\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 all<{ column_name: string }>(\n\t\tsession,\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\t(row) => ({ column_name: row[0] }),\n\t);\n\n\tconst 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(migrationsTable, session, localMigrations);\n\t}\n\n\treturn { prevVersion: version, currentVersion: CURRENT_MIGRATION_TABLE_VERSION };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mysql.cjs","names":["sql","GET_VERSION_FOR","MIGRATIONS_TABLE_VERSIONS"],"sources":["../../src/up-migrations/mysql.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { MySqlSession } from '~/mysql-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\tmigrationsTable: string,\n\t\tsession: MySqlSession,\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, session, 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 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.all(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`);\n\t\tawait session.all(\n\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`,\n\t\t);\n\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tawait session.all(\n\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\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\tmigrationsTable: string,\n\tsession: MySqlSession,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await session.all<{ '1': 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 session.all<{ column_name: 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.column_name));\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, session, localMigrations);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":";;;;;;;;;;AASA,MAAM,mBAOF,EAUH,GAAG,OAAO,iBAAiB,SAAS,oBAAoB;CACvD,MAAM,QAAQ,gBAAG,GAAGA,iBAAI,WAAW,gBAAgB;CAInD,MAAM,SAAS,MAAM,QAAQ,IAC5B,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,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,IAAI,gBAAG,eAAe,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,OAAO;AAC/E,OAAM,QAAQ,IACb,gBAAG,eAAe,MAAM,OAAOA,iBAAI,WAAW,aAAa,CAAC,sCAC5D;AAED,MAAK,MAAM,iBAAiB,QAC3B,OAAM,QAAQ,IACb,gBAAG,UAAU,MAAM,OAAOA,iBAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxEA,iBAAI,WAAW,aAAa,CAC5B,gBAAgBA,iBAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;GAGH;;;;;;;AAQD,eAAsB,gBACrB,iBACA,SACA,iBACyB;AAQzB,MANe,MAAM,QAAQ,IAC5B,gBAAG;wBACmB,gBAAgB;kCAEtC,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,QAAQ,IAC1B,gBAAG;;uBAEkB,gBAAgB;;6BAGrC;CAED,MAAM,UAAUC,4CAAgB,MAAM,KAAK,KAAK,MAAM,EAAE,YAAY,CAAC;AAErE,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,SAAS,gBAAgB;;AAG3D,QAAO,EAAE,OAAO,OAAO"}
@@ -1,12 +1,8 @@
1
+ import { UpgradeResult } from "./utils.cjs";
1
2
  import { MigrationMeta } from "../migrator.cjs";
2
3
  import { MySqlSession } from "../mysql-core/session.cjs";
3
4
 
4
5
  //#region src/up-migrations/mysql.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 { MySqlSession } from "../mysql-core/session.js";
3
4
 
4
5
  //#region src/up-migrations/mysql.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,28 +1,14 @@
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/mysql.ts
4
- const CURRENT_MIGRATION_TABLE_VERSION = 1;
5
- function getVersion(columns) {
6
- if (columns.includes("name")) return 1;
7
- return 0;
8
- }
9
- async function all(session, sqlQuery, resultMapper = () => []) {
10
- const result = await session.all(sqlQuery);
11
- if (result.length === 0) return [];
12
- if (Array.isArray(result[0])) return result.map((row) => resultMapper(row));
13
- return result;
14
- }
15
5
  /**
16
6
  * Map of upgrade functions. Each key is the version being upgraded FROM,
17
7
  * and the function upgrades the table to the next version.
18
8
  */
19
9
  const upgradeFunctions = { 0: async (migrationsTable, session, localMigrations) => {
20
10
  const table = sql`${sql.identifier(migrationsTable)}`;
21
- const dbRows = await all(session, sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`, (row) => ({
22
- id: row[0],
23
- hash: row[1],
24
- created_at: row[2]
25
- }));
11
+ const dbRows = await session.all(sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`);
26
12
  localMigrations.sort((a, b) => a.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? "").localeCompare(b.name ?? ""));
27
13
  const byMillis = /* @__PURE__ */ new Map();
28
14
  const byHash = /* @__PURE__ */ new Map();
@@ -48,9 +34,9 @@ const upgradeFunctions = { 0: async (migrationsTable, session, localMigrations)
48
34
  else unmatchedIds.push(dbRow.id);
49
35
  }
50
36
  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`);
51
- await session.execute(sql`ALTER TABLE ${table} ADD ${sql.identifier("name")} text`);
52
- await session.execute(sql`ALTER TABLE ${table} ADD ${sql.identifier("applied_at")} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`);
53
- for (const backfillEntry of toApply) await session.execute(sql`UPDATE ${table} SET ${sql.identifier("name")} = ${backfillEntry.name}, ${sql.identifier("applied_at")} = NULL WHERE ${sql.identifier("id")} = ${backfillEntry.id}`);
37
+ await session.all(sql`ALTER TABLE ${table} ADD ${sql.identifier("name")} text`);
38
+ await session.all(sql`ALTER TABLE ${table} ADD ${sql.identifier("applied_at")} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`);
39
+ for (const backfillEntry of toApply) await session.all(sql`UPDATE ${table} SET ${sql.identifier("name")} = ${backfillEntry.name}, ${sql.identifier("applied_at")} = NULL WHERE ${sql.identifier("id")} = ${backfillEntry.id}`);
54
40
  } };
55
41
  /**
56
42
  * Detects the current version of the migrations table schema and upgrades it if needed.
@@ -59,23 +45,21 @@ const upgradeFunctions = { 0: async (migrationsTable, session, localMigrations)
59
45
  * Version 1: Extended schema (id, hash, created_at, name, applied_at)
60
46
  */
61
47
  async function upgradeIfNeeded(migrationsTable, session, localMigrations) {
62
- if ((await all(session, sql`SELECT 1 FROM information_schema.tables
48
+ if ((await session.all(sql`SELECT 1 FROM information_schema.tables
63
49
  WHERE table_name = ${migrationsTable}
64
- AND table_schema = DATABASE()`, (row) => ({ "1": row[0] }))).length === 0) return { newDb: true };
65
- const version = getVersion((await all(session, sql`SELECT column_name as \`column_name\`
50
+ AND table_schema = DATABASE()`)).length === 0) return { newDb: true };
51
+ const rows = await session.all(sql`SELECT column_name as \`column_name\`
66
52
  FROM information_schema.columns
67
53
  WHERE table_name = ${migrationsTable}
68
54
  AND table_schema = DATABASE()
69
- ORDER BY ordinal_position`, (row) => ({ column_name: row[0] }))).map((r) => r.column_name));
70
- for (let v = version; v < CURRENT_MIGRATION_TABLE_VERSION; v++) {
55
+ ORDER BY ordinal_position`);
56
+ const version = GET_VERSION_FOR.mysql(rows.map((r) => r.column_name));
57
+ for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.mysql; v++) {
71
58
  const upgradeFn = upgradeFunctions[v];
72
59
  if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
73
60
  await upgradeFn(migrationsTable, session, localMigrations);
74
61
  }
75
- return {
76
- prevVersion: version,
77
- currentVersion: CURRENT_MIGRATION_TABLE_VERSION
78
- };
62
+ return { newDb: false };
79
63
  }
80
64
 
81
65
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"mysql.js","names":[],"sources":["../../src/up-migrations/mysql.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { MySqlSession } from '~/mysql-core/session.ts';\nimport { type SQL, 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// mysql returns array of objects for .all, but mysql-proxy -> array of arrays\nasync function all<T>(\n\tsession: MySqlSession,\n\tsqlQuery: SQL,\n\tresultMapper: (row: any[]) => T = () => [] as T,\n): Promise<T[]> {\n\tconst result = await session.all(sqlQuery) as any[] | any[][];\n\n\tif (result.length === 0) return [];\n\n\tif (Array.isArray(result[0])) {\n\t\treturn (result as any[][]).map((row) => resultMapper(row));\n\t}\n\n\treturn result as T[];\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\tmigrationsTable: string,\n\t\tsession: MySqlSession,\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, session, 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 all<{ id: number; hash: string; created_at: string }>(\n\t\t\tsession,\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t\t(row) => ({\n\t\t\t\tid: row[0],\n\t\t\t\thash: row[1],\n\t\t\t\tcreated_at: row[2],\n\t\t\t}),\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.execute(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`);\n\t\tawait session.execute(\n\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`,\n\t\t);\n\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tawait session.execute(\n\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\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\tmigrationsTable: string,\n\tsession: MySqlSession,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await all<{ '1': 1 }>(\n\t\tsession,\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\t(row) => ({ '1': row[0] }),\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 all<{ column_name: string }>(\n\t\tsession,\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\t(row) => ({ column_name: row[0] }),\n\t);\n\n\tconst 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(migrationsTable, session, localMigrations);\n\t}\n\n\treturn { prevVersion: version, currentVersion: CURRENT_MIGRATION_TABLE_VERSION };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mysql.js","names":[],"sources":["../../src/up-migrations/mysql.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { MySqlSession } from '~/mysql-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\tmigrationsTable: string,\n\t\tsession: MySqlSession,\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, session, 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 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.all(sql`ALTER TABLE ${table} ADD ${sql.identifier('name')} text`);\n\t\tawait session.all(\n\t\t\tsql`ALTER TABLE ${table} ADD ${sql.identifier('applied_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP`,\n\t\t);\n\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tawait session.all(\n\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\t\tsql.identifier('applied_at')\n\t\t\t\t} = NULL WHERE ${sql.identifier('id')} = ${backfillEntry.id}`,\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\tmigrationsTable: string,\n\tsession: MySqlSession,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst result = await session.all<{ '1': 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 session.all<{ column_name: 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.column_name));\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, session, localMigrations);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":";;;;;;;;AASA,MAAM,mBAOF,EAUH,GAAG,OAAO,iBAAiB,SAAS,oBAAoB;CACvD,MAAM,QAAQ,GAAG,GAAG,IAAI,WAAW,gBAAgB;CAInD,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,IAAI,GAAG,eAAe,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,OAAO;AAC/E,OAAM,QAAQ,IACb,GAAG,eAAe,MAAM,OAAO,IAAI,WAAW,aAAa,CAAC,sCAC5D;AAED,MAAK,MAAM,iBAAiB,QAC3B,OAAM,QAAQ,IACb,GAAG,UAAU,MAAM,OAAO,IAAI,WAAW,OAAO,CAAC,KAAK,cAAc,KAAK,IACxE,IAAI,WAAW,aAAa,CAC5B,gBAAgB,IAAI,WAAW,KAAK,CAAC,KAAK,cAAc,KACzD;GAGH;;;;;;;AAQD,eAAsB,gBACrB,iBACA,SACA,iBACyB;AAQzB,MANe,MAAM,QAAQ,IAC5B,GAAG;wBACmB,gBAAgB;kCAEtC,EAEU,WAAW,EACrB,QAAO,EAAE,OAAO,MAAM;CAIvB,MAAM,OAAO,MAAM,QAAQ,IAC1B,GAAG;;uBAEkB,gBAAgB;;6BAGrC;CAED,MAAM,UAAU,gBAAgB,MAAM,KAAK,KAAK,MAAM,EAAE,YAAY,CAAC;AAErE,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,iBAAiB,SAAS,gBAAgB;;AAG3D,QAAO,EAAE,OAAO,OAAO"}
@@ -0,0 +1,79 @@
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/pg-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 (migrationsSchema, migrationsTable, db, callback, localMigrations) => {
12
+ const table = __sql_sql_ts.sql`${__sql_sql_ts.sql.identifier(migrationsSchema)}.${__sql_sql_ts.sql.identifier(migrationsTable)}`;
13
+ const dbRows = await db.session.execute(__sql_sql_ts.sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`);
14
+ localMigrations.sort((a, b) => a.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? "").localeCompare(b.name ?? ""));
15
+ const byMillis = /* @__PURE__ */ new Map();
16
+ const byHash = /* @__PURE__ */ new Map();
17
+ for (const lm of localMigrations) {
18
+ if (!byMillis.has(lm.folderMillis)) byMillis.set(lm.folderMillis, []);
19
+ byMillis.get(lm.folderMillis).push(lm);
20
+ byHash.set(lm.hash, lm);
21
+ }
22
+ const toApply = [];
23
+ let unmatchedIds = [];
24
+ for (const dbRow of dbRows) {
25
+ const stringified = String(dbRow.created_at);
26
+ const millis = Number(stringified.substring(0, stringified.length - 3) + "000");
27
+ const candidates = byMillis.get(millis);
28
+ let matched;
29
+ if (candidates && candidates.length === 1) matched = candidates[0];
30
+ else if (candidates && candidates.length > 1) matched = candidates.find((c) => c.hash === dbRow.hash);
31
+ else matched = byHash.get(dbRow.hash);
32
+ if (matched) toApply.push({
33
+ id: dbRow.id,
34
+ name: matched.name
35
+ });
36
+ else unmatchedIds.push(dbRow.id);
37
+ }
38
+ 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`);
39
+ const sqls = [db.dialect.sqlToQuery(__sql_sql_ts.sql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${__sql_sql_ts.sql.identifier("name")} text`.inlineParams()).sql, db.dialect.sqlToQuery(__sql_sql_ts.sql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${__sql_sql_ts.sql.identifier("applied_at")} timestamp with time zone DEFAULT now()`.inlineParams()).sql];
40
+ for (const { id, name } of toApply) sqls.push(db.dialect.sqlToQuery(__sql_sql_ts.sql`UPDATE ${table} SET ${__sql_sql_ts.sql.identifier("name")} = ${name}, ${__sql_sql_ts.sql.identifier("applied_at")} = NULL WHERE ${__sql_sql_ts.sql.identifier("id")} = ${id}`.inlineParams()).sql);
41
+ await callback(sqls);
42
+ } };
43
+ /**
44
+ * Detects the current version of the migrations table schema and upgrades it if needed.
45
+ *
46
+ * Version 0: Original schema (id, hash, created_at)
47
+ * Version 1: Extended schema (id, hash, created_at, name, applied_at)
48
+ */
49
+ async function upgradeIfNeeded(migrationsSchema, migrationsTable, db, callback, localMigrations) {
50
+ if ((await db.session.execute(__sql_sql_ts.sql`SELECT 1 FROM information_schema.tables
51
+ WHERE table_schema = ${migrationsSchema}
52
+ AND table_name = ${migrationsTable}`)).length === 0) return { newDb: true };
53
+ const rows = await db.session.execute(__sql_sql_ts.sql`SELECT
54
+ n.nspname AS "schema",
55
+ c.relname AS "table_name",
56
+ a.attname AS "column_name",
57
+ pg_catalog.format_type(a.atttypid, a.atttypmod) AS "type"
58
+ FROM
59
+ pg_catalog.pg_attribute a
60
+ JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
61
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
62
+ WHERE
63
+ a.attnum > 0
64
+ AND NOT a.attisdropped
65
+ AND n.nspname = ${migrationsSchema}
66
+ AND c.relname = ${migrationsTable}
67
+ ORDER BY a.attnum;`);
68
+ let version = require_up_migrations_utils.GET_VERSION_FOR.pg(rows.map((r) => r.column_name));
69
+ for (let v = version; v < require_up_migrations_utils.MIGRATIONS_TABLE_VERSIONS.pg; v++) {
70
+ const upgradeFn = upgradeFunctions[v];
71
+ if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
72
+ await upgradeFn(migrationsSchema, migrationsTable, db, callback, localMigrations);
73
+ }
74
+ return { newDb: false };
75
+ }
76
+
77
+ //#endregion
78
+ exports.upgradeIfNeeded = upgradeIfNeeded;
79
+ //# sourceMappingURL=pg-proxy.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-proxy.cjs","names":["sql","GET_VERSION_FOR","MIGRATIONS_TABLE_VERSIONS"],"sources":["../../src/up-migrations/pg-proxy.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport type { PgRemoteDatabase } from '~/pg-proxy/index.ts';\nimport type { ProxyMigrator } from '~/pg-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\tmigrationsSchema: string,\n\t\tmigrationsTable: string,\n\t\tdb: PgRemoteDatabase<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 (migrationsSchema, migrationsTable, db, callback, 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 db.session.execute<{ 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\tconst sqls: string[] = [\n\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\tsql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier('name')} text`.inlineParams(),\n\t\t\t).sql,\n\t\t\tdb.dialect.sqlToQuery(\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()`.inlineParams(),\n\t\t\t).sql,\n\t\t];\n\t\tfor (const { id, name } of toApply) {\n\t\t\tsqls.push(\n\t\t\t\tdb.dialect.sqlToQuery(\n\t\t\t\t\tsql`UPDATE ${table} SET ${sql.identifier('name')} = ${name}, ${sql.identifier('applied_at')} = NULL WHERE ${\n\t\t\t\t\t\tsql.identifier('id')\n\t\t\t\t\t} = ${id}`.inlineParams(),\n\t\t\t\t).sql,\n\t\t\t);\n\t\t}\n\n\t\tawait callback(sqls);\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\tmigrationsSchema: string,\n\tmigrationsTable: string,\n\tdb: PgRemoteDatabase<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.execute<{ '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 (result.length === 0) {\n\t\treturn { newDb: true };\n\t}\n\n\t// Table exists, check table shape\n\tconst rows = await db.session.execute<\n\t\t{ schema: string; table_name: string; column_name: string; type: string }[]\n\t>(\n\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);\n\n\tlet version = GET_VERSION_FOR.pg(rows.map((r) => r.column_name));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.pg; 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, db, callback, localMigrations);\n\t}\n\n\treturn { newDb: false };\n}\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { UpgradeResult } from "./utils.cjs";
2
+ import { MigrationMeta } from "../migrator.cjs";
3
+ import { PgRemoteDatabase } from "../pg-proxy/index.cjs";
4
+ import { ProxyMigrator } from "../pg-proxy/migrator.cjs";
5
+
6
+ //#region src/up-migrations/pg-proxy.d.ts
7
+ /**
8
+ * Detects the current version of the migrations table schema and upgrades it if needed.
9
+ *
10
+ * Version 0: Original schema (id, hash, created_at)
11
+ * Version 1: Extended schema (id, hash, created_at, name, applied_at)
12
+ */
13
+ declare function upgradeIfNeeded(migrationsSchema: string, migrationsTable: string, db: PgRemoteDatabase<Record<string, unknown>>, callback: ProxyMigrator, localMigrations: MigrationMeta[]): Promise<UpgradeResult>;
14
+ //#endregion
15
+ export { upgradeIfNeeded };
16
+ //# sourceMappingURL=pg-proxy.d.cts.map
@@ -0,0 +1,16 @@
1
+ import { UpgradeResult } from "./utils.js";
2
+ import { MigrationMeta } from "../migrator.js";
3
+ import { PgRemoteDatabase } from "../pg-proxy/index.js";
4
+ import { ProxyMigrator } from "../pg-proxy/migrator.js";
5
+
6
+ //#region src/up-migrations/pg-proxy.d.ts
7
+ /**
8
+ * Detects the current version of the migrations table schema and upgrades it if needed.
9
+ *
10
+ * Version 0: Original schema (id, hash, created_at)
11
+ * Version 1: Extended schema (id, hash, created_at, name, applied_at)
12
+ */
13
+ declare function upgradeIfNeeded(migrationsSchema: string, migrationsTable: string, db: PgRemoteDatabase<Record<string, unknown>>, callback: ProxyMigrator, localMigrations: MigrationMeta[]): Promise<UpgradeResult>;
14
+ //#endregion
15
+ export { upgradeIfNeeded };
16
+ //# sourceMappingURL=pg-proxy.d.ts.map
@@ -0,0 +1,77 @@
1
+ import { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS } from "./utils.js";
2
+ import { sql } from "../sql/sql.js";
3
+
4
+ //#region src/up-migrations/pg-proxy.ts
5
+ /**
6
+ * Map of upgrade functions. Each key is the version being upgraded FROM,
7
+ * and the function upgrades the table to the next version.
8
+ */
9
+ const upgradeFunctions = { 0: async (migrationsSchema, migrationsTable, db, callback, localMigrations) => {
10
+ const table = sql`${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)}`;
11
+ const dbRows = await db.session.execute(sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`);
12
+ localMigrations.sort((a, b) => a.folderMillis !== b.folderMillis ? a.folderMillis - b.folderMillis : (a.name ?? "").localeCompare(b.name ?? ""));
13
+ const byMillis = /* @__PURE__ */ new Map();
14
+ const byHash = /* @__PURE__ */ new Map();
15
+ for (const lm of localMigrations) {
16
+ if (!byMillis.has(lm.folderMillis)) byMillis.set(lm.folderMillis, []);
17
+ byMillis.get(lm.folderMillis).push(lm);
18
+ byHash.set(lm.hash, lm);
19
+ }
20
+ const toApply = [];
21
+ let unmatchedIds = [];
22
+ for (const dbRow of dbRows) {
23
+ const stringified = String(dbRow.created_at);
24
+ const millis = Number(stringified.substring(0, stringified.length - 3) + "000");
25
+ const candidates = byMillis.get(millis);
26
+ let matched;
27
+ if (candidates && candidates.length === 1) matched = candidates[0];
28
+ else if (candidates && candidates.length > 1) matched = candidates.find((c) => c.hash === dbRow.hash);
29
+ else matched = byHash.get(dbRow.hash);
30
+ if (matched) toApply.push({
31
+ id: dbRow.id,
32
+ name: matched.name
33
+ });
34
+ else unmatchedIds.push(dbRow.id);
35
+ }
36
+ 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`);
37
+ const sqls = [db.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier("name")} text`.inlineParams()).sql, db.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${sql.identifier("applied_at")} timestamp with time zone DEFAULT now()`.inlineParams()).sql];
38
+ for (const { id, name } of toApply) sqls.push(db.dialect.sqlToQuery(sql`UPDATE ${table} SET ${sql.identifier("name")} = ${name}, ${sql.identifier("applied_at")} = NULL WHERE ${sql.identifier("id")} = ${id}`.inlineParams()).sql);
39
+ await callback(sqls);
40
+ } };
41
+ /**
42
+ * Detects the current version of the migrations table schema and upgrades it if needed.
43
+ *
44
+ * Version 0: Original schema (id, hash, created_at)
45
+ * Version 1: Extended schema (id, hash, created_at, name, applied_at)
46
+ */
47
+ async function upgradeIfNeeded(migrationsSchema, migrationsTable, db, callback, localMigrations) {
48
+ if ((await db.session.execute(sql`SELECT 1 FROM information_schema.tables
49
+ WHERE table_schema = ${migrationsSchema}
50
+ AND table_name = ${migrationsTable}`)).length === 0) return { newDb: true };
51
+ const rows = await db.session.execute(sql`SELECT
52
+ n.nspname AS "schema",
53
+ c.relname AS "table_name",
54
+ a.attname AS "column_name",
55
+ pg_catalog.format_type(a.atttypid, a.atttypmod) AS "type"
56
+ FROM
57
+ pg_catalog.pg_attribute a
58
+ JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
59
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
60
+ WHERE
61
+ a.attnum > 0
62
+ AND NOT a.attisdropped
63
+ AND n.nspname = ${migrationsSchema}
64
+ AND c.relname = ${migrationsTable}
65
+ ORDER BY a.attnum;`);
66
+ let version = GET_VERSION_FOR.pg(rows.map((r) => r.column_name));
67
+ for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.pg; v++) {
68
+ const upgradeFn = upgradeFunctions[v];
69
+ if (!upgradeFn) throw new Error(`No upgrade path from migration table version ${v} to ${v + 1}`);
70
+ await upgradeFn(migrationsSchema, migrationsTable, db, callback, localMigrations);
71
+ }
72
+ return { newDb: false };
73
+ }
74
+
75
+ //#endregion
76
+ export { upgradeIfNeeded };
77
+ //# sourceMappingURL=pg-proxy.js.map