drizzle-orm 1.0.0-beta.22-19faa3c → 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.
@@ -4,7 +4,7 @@ import { Field, TypeHint } from "@aws-sdk/client-rds-data";
4
4
 
5
5
  //#region src/aws-data-api/common/index.d.ts
6
6
  declare const typeHint: { [K in TypeHint]: K };
7
- declare function getValueFromDataApi(field: Field): string | number | boolean | string[] | number[] | Uint8Array<ArrayBufferLike> | boolean[] | _aws_sdk_client_rds_data0.ArrayValue[] | null;
7
+ declare function getValueFromDataApi(field: Field): string | number | boolean | string[] | Uint8Array<ArrayBufferLike> | number[] | boolean[] | _aws_sdk_client_rds_data0.ArrayValue[] | null;
8
8
  declare function typingsToAwsTypeHint(typings?: QueryTypingsValue): TypeHint | undefined;
9
9
  declare function toValueParam(value: any, typings?: QueryTypingsValue): {
10
10
  value: Field;
@@ -4,7 +4,7 @@ import { Field, TypeHint } from "@aws-sdk/client-rds-data";
4
4
 
5
5
  //#region src/aws-data-api/common/index.d.ts
6
6
  declare const typeHint: { [K in TypeHint]: K };
7
- declare function getValueFromDataApi(field: Field): string | number | boolean | string[] | number[] | Uint8Array<ArrayBufferLike> | boolean[] | _aws_sdk_client_rds_data0.ArrayValue[] | null;
7
+ declare function getValueFromDataApi(field: Field): string | number | boolean | string[] | Uint8Array<ArrayBufferLike> | number[] | boolean[] | _aws_sdk_client_rds_data0.ArrayValue[] | null;
8
8
  declare function typingsToAwsTypeHint(typings?: QueryTypingsValue): TypeHint | undefined;
9
9
  declare function toValueParam(value: any, typings?: QueryTypingsValue): {
10
10
  value: Field;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drizzle-orm",
3
- "version": "1.0.0-beta.22-19faa3c",
3
+ "version": "1.0.0-beta.22-bf0fa46",
4
4
  "description": "Drizzle ORM package for SQL databases",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -11,8 +11,8 @@ let __sql_sql_ts = require("../sql/sql.cjs");
11
11
  * Version 1: Extended schema (id, hash, created_at, name, applied_at)
12
12
  */
13
13
  async function upgradeAsyncIfNeeded(migrationsTable, db, callback, localMigrations) {
14
- if ((await db.session.all(__sql_sql_ts.sql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${migrationsTable}`)).length === 0) return { newDb: true };
15
- const rows = await db.session.all(__sql_sql_ts.sql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`);
14
+ if ((await db.session.values(__sql_sql_ts.sql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${migrationsTable}`)).length === 0) return { newDb: true };
15
+ const rows = await db.session.values(__sql_sql_ts.sql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`);
16
16
  const version = require_up_migrations_utils.GET_VERSION_FOR.sqlite(rows.map((r) => r[0]));
17
17
  for (let v = version; v < require_up_migrations_utils.MIGRATIONS_TABLE_VERSIONS.sqlite; v++) {
18
18
  const upgradeFn = upgradeAsyncFunctions[v];
@@ -23,7 +23,7 @@ async function upgradeAsyncIfNeeded(migrationsTable, db, callback, localMigratio
23
23
  }
24
24
  const upgradeAsyncFunctions = { 0: async (migrationsTable, db, callback, localMigrations) => {
25
25
  const table = __sql_sql_ts.sql`${__sql_sql_ts.sql.identifier(migrationsTable)}`;
26
- const dbRows = (await db.session.all(__sql_sql_ts.sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`)).map((row) => ({
26
+ const dbRows = (await db.session.values(__sql_sql_ts.sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`)).map((row) => ({
27
27
  id: row[0],
28
28
  hash: row[1],
29
29
  created_at: row[2]
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-proxy.cjs","names":["GET_VERSION_FOR","MIGRATIONS_TABLE_VERSIONS","sql"],"sources":["../../src/up-migrations/sqlite-proxy.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport { sql } from '~/sql/sql.ts';\nimport type { BaseSQLiteDatabase } from '~/sqlite-core/index.ts';\nimport type { SqliteRemoteDatabase } from '~/sqlite-proxy/index.ts';\nimport type { ProxyMigrator } from '~/sqlite-proxy/migrator.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\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 upgradeAsyncIfNeeded(\n\tmigrationsTable: string,\n\tdb: SqliteRemoteDatabase<Record<string, unknown>>,\n\tcallback: ProxyMigrator,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst tableExists = await db.session.all<[1]>(\n\t\tsql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND 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 db.session.all<[string]>(\n\t\tsql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`,\n\t);\n\n\tconst version = GET_VERSION_FOR.sqlite(rows.map((r) => r[0]));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.sqlite; v++) {\n\t\tconst upgradeFn = upgradeAsyncFunctions[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\nconst upgradeAsyncFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsTable: string,\n\t\tdb: BaseSQLiteDatabase<'async', unknown, 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\t// this can be null from legacy implementation where id was serial\n\t\tconst dbRows = (await db.session.all<[number | null, string, number]>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t)).map((row) => ({\n\t\t\tid: row[0],\n\t\t\thash: row[1],\n\t\t\tcreated_at: row[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\n\t\t// id can be null from legacy implementation where id was serial\n\t\tconst toApply: {\n\t\t\tid: number | null;\n\t\t\tname: string;\n\t\t\thash: string;\n\t\t\tcreated_at: string;\n\t\t\tmatchedBy: 'id' | 'hash' | 'millis';\n\t\t}[] = [];\n\n\t\t// id can be null from legacy implementation where id was serial\n\t\t// hash can only be '' for bun-sqlite journal entries\n\t\tlet unmatched: { id: number | null; hash: string; created_at: 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\t\t\tlet matchedBy: 'hash' | 'millis' | null = null;\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t\tmatchedBy = 'millis';\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash && dbRow.hash && c.hash === dbRow.hash); // for bun-sqlite cases (journal had empty hash)\n\t\t\t\tif (matched) matchedBy = 'hash';\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t\tif (matched) matchedBy = 'hash';\n\t\t\t}\n\n\t\t\tif (matched) {\n\t\t\t\ttoApply.push({\n\t\t\t\t\tid: dbRow.id,\n\t\t\t\t\tname: matched.name,\n\t\t\t\t\thash: dbRow.hash,\n\t\t\t\t\tcreated_at: stringified,\n\t\t\t\t\tmatchedBy: dbRow.id ? 'id' : matchedBy!,\n\t\t\t\t});\n\t\t\t} else unmatched.push(dbRow);\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 (unmatched.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatched.length} (${\n\t\t\t\t\tunmatched.map((it) => `[id: ${it.id}, created_at: ${it.created_at}]`).join(', ')\n\t\t\t\t}) migrations 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 COLUMN ${sql.identifier('name')} text`.inlineParams()).sql,\n\t\t\tdb.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD COLUMN ${sql.identifier('applied_at')} TEXT`.inlineParams())\n\t\t\t\t.sql,\n\t\t];\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tconst updateQuery = sql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\tsql.identifier('applied_at')\n\t\t\t} = NULL WHERE`;\n\n\t\t\t// id\n\t\t\t// created_at\n\t\t\t// hash\n\t\t\tif (backfillEntry.id) updateQuery.append(sql` ${sql.identifier('id')} = ${backfillEntry.id}`);\n\t\t\telse if (backfillEntry.matchedBy === 'millis') {\n\t\t\t\tupdateQuery.append(sql` ${sql.identifier('created_at')} = ${backfillEntry.created_at}`);\n\t\t\t} else updateQuery.append(sql` ${sql.identifier('hash')} = ${backfillEntry.hash}`);\n\n\t\t\tstatements.push(db.dialect.sqlToQuery(updateQuery.inlineParams()).sql);\n\t\t}\n\n\t\tawait callback(statements);\n\t\treturn;\n\t},\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-proxy.cjs","names":["GET_VERSION_FOR","MIGRATIONS_TABLE_VERSIONS","sql"],"sources":["../../src/up-migrations/sqlite-proxy.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport { sql } from '~/sql/sql.ts';\nimport type { BaseSQLiteDatabase } from '~/sqlite-core/index.ts';\nimport type { SqliteRemoteDatabase } from '~/sqlite-proxy/index.ts';\nimport type { ProxyMigrator } from '~/sqlite-proxy/migrator.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\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 upgradeAsyncIfNeeded(\n\tmigrationsTable: string,\n\tdb: SqliteRemoteDatabase<Record<string, unknown>>,\n\tcallback: ProxyMigrator,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst tableExists = await db.session.values<[1]>(\n\t\tsql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND 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 db.session.values<[string]>(\n\t\tsql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`,\n\t);\n\n\tconst version = GET_VERSION_FOR.sqlite(rows.map((r) => r[0]));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.sqlite; v++) {\n\t\tconst upgradeFn = upgradeAsyncFunctions[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\nconst upgradeAsyncFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsTable: string,\n\t\tdb: BaseSQLiteDatabase<'async', unknown, 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\t// this can be null from legacy implementation where id was serial\n\t\tconst dbRows = (await db.session.values<[number | null, string, number]>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t)).map((row) => ({\n\t\t\tid: row[0],\n\t\t\thash: row[1],\n\t\t\tcreated_at: row[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\n\t\t// id can be null from legacy implementation where id was serial\n\t\tconst toApply: {\n\t\t\tid: number | null;\n\t\t\tname: string;\n\t\t\thash: string;\n\t\t\tcreated_at: string;\n\t\t\tmatchedBy: 'id' | 'hash' | 'millis';\n\t\t}[] = [];\n\n\t\t// id can be null from legacy implementation where id was serial\n\t\t// hash can only be '' for bun-sqlite journal entries\n\t\tlet unmatched: { id: number | null; hash: string; created_at: 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\t\t\tlet matchedBy: 'hash' | 'millis' | null = null;\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t\tmatchedBy = 'millis';\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash && dbRow.hash && c.hash === dbRow.hash); // for bun-sqlite cases (journal had empty hash)\n\t\t\t\tif (matched) matchedBy = 'hash';\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t\tif (matched) matchedBy = 'hash';\n\t\t\t}\n\n\t\t\tif (matched) {\n\t\t\t\ttoApply.push({\n\t\t\t\t\tid: dbRow.id,\n\t\t\t\t\tname: matched.name,\n\t\t\t\t\thash: dbRow.hash,\n\t\t\t\t\tcreated_at: stringified,\n\t\t\t\t\tmatchedBy: dbRow.id ? 'id' : matchedBy!,\n\t\t\t\t});\n\t\t\t} else unmatched.push(dbRow);\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 (unmatched.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatched.length} (${\n\t\t\t\t\tunmatched.map((it) => `[id: ${it.id}, created_at: ${it.created_at}]`).join(', ')\n\t\t\t\t}) migrations 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 COLUMN ${sql.identifier('name')} text`.inlineParams()).sql,\n\t\t\tdb.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD COLUMN ${sql.identifier('applied_at')} TEXT`.inlineParams())\n\t\t\t\t.sql,\n\t\t];\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tconst updateQuery = sql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\tsql.identifier('applied_at')\n\t\t\t} = NULL WHERE`;\n\n\t\t\t// id\n\t\t\t// created_at\n\t\t\t// hash\n\t\t\tif (backfillEntry.id) updateQuery.append(sql` ${sql.identifier('id')} = ${backfillEntry.id}`);\n\t\t\telse if (backfillEntry.matchedBy === 'millis') {\n\t\t\t\tupdateQuery.append(sql` ${sql.identifier('created_at')} = ${backfillEntry.created_at}`);\n\t\t\t} else updateQuery.append(sql` ${sql.identifier('hash')} = ${backfillEntry.hash}`);\n\n\t\t\tstatements.push(db.dialect.sqlToQuery(updateQuery.inlineParams()).sql);\n\t\t}\n\n\t\tawait callback(statements);\n\t\treturn;\n\t},\n};\n"],"mappings":"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"}
@@ -9,8 +9,8 @@ import { sql } from "../sql/sql.js";
9
9
  * Version 1: Extended schema (id, hash, created_at, name, applied_at)
10
10
  */
11
11
  async function upgradeAsyncIfNeeded(migrationsTable, db, callback, localMigrations) {
12
- if ((await db.session.all(sql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${migrationsTable}`)).length === 0) return { newDb: true };
13
- const rows = await db.session.all(sql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`);
12
+ if ((await db.session.values(sql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${migrationsTable}`)).length === 0) return { newDb: true };
13
+ const rows = await db.session.values(sql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`);
14
14
  const version = GET_VERSION_FOR.sqlite(rows.map((r) => r[0]));
15
15
  for (let v = version; v < MIGRATIONS_TABLE_VERSIONS.sqlite; v++) {
16
16
  const upgradeFn = upgradeAsyncFunctions[v];
@@ -21,7 +21,7 @@ async function upgradeAsyncIfNeeded(migrationsTable, db, callback, localMigratio
21
21
  }
22
22
  const upgradeAsyncFunctions = { 0: async (migrationsTable, db, callback, localMigrations) => {
23
23
  const table = sql`${sql.identifier(migrationsTable)}`;
24
- const dbRows = (await db.session.all(sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`)).map((row) => ({
24
+ const dbRows = (await db.session.values(sql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`)).map((row) => ({
25
25
  id: row[0],
26
26
  hash: row[1],
27
27
  created_at: row[2]
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-proxy.js","names":[],"sources":["../../src/up-migrations/sqlite-proxy.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport { sql } from '~/sql/sql.ts';\nimport type { BaseSQLiteDatabase } from '~/sqlite-core/index.ts';\nimport type { SqliteRemoteDatabase } from '~/sqlite-proxy/index.ts';\nimport type { ProxyMigrator } from '~/sqlite-proxy/migrator.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\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 upgradeAsyncIfNeeded(\n\tmigrationsTable: string,\n\tdb: SqliteRemoteDatabase<Record<string, unknown>>,\n\tcallback: ProxyMigrator,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst tableExists = await db.session.all<[1]>(\n\t\tsql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND 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 db.session.all<[string]>(\n\t\tsql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`,\n\t);\n\n\tconst version = GET_VERSION_FOR.sqlite(rows.map((r) => r[0]));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.sqlite; v++) {\n\t\tconst upgradeFn = upgradeAsyncFunctions[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\nconst upgradeAsyncFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsTable: string,\n\t\tdb: BaseSQLiteDatabase<'async', unknown, 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\t// this can be null from legacy implementation where id was serial\n\t\tconst dbRows = (await db.session.all<[number | null, string, number]>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t)).map((row) => ({\n\t\t\tid: row[0],\n\t\t\thash: row[1],\n\t\t\tcreated_at: row[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\n\t\t// id can be null from legacy implementation where id was serial\n\t\tconst toApply: {\n\t\t\tid: number | null;\n\t\t\tname: string;\n\t\t\thash: string;\n\t\t\tcreated_at: string;\n\t\t\tmatchedBy: 'id' | 'hash' | 'millis';\n\t\t}[] = [];\n\n\t\t// id can be null from legacy implementation where id was serial\n\t\t// hash can only be '' for bun-sqlite journal entries\n\t\tlet unmatched: { id: number | null; hash: string; created_at: 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\t\t\tlet matchedBy: 'hash' | 'millis' | null = null;\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t\tmatchedBy = 'millis';\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash && dbRow.hash && c.hash === dbRow.hash); // for bun-sqlite cases (journal had empty hash)\n\t\t\t\tif (matched) matchedBy = 'hash';\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t\tif (matched) matchedBy = 'hash';\n\t\t\t}\n\n\t\t\tif (matched) {\n\t\t\t\ttoApply.push({\n\t\t\t\t\tid: dbRow.id,\n\t\t\t\t\tname: matched.name,\n\t\t\t\t\thash: dbRow.hash,\n\t\t\t\t\tcreated_at: stringified,\n\t\t\t\t\tmatchedBy: dbRow.id ? 'id' : matchedBy!,\n\t\t\t\t});\n\t\t\t} else unmatched.push(dbRow);\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 (unmatched.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatched.length} (${\n\t\t\t\t\tunmatched.map((it) => `[id: ${it.id}, created_at: ${it.created_at}]`).join(', ')\n\t\t\t\t}) migrations 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 COLUMN ${sql.identifier('name')} text`.inlineParams()).sql,\n\t\t\tdb.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD COLUMN ${sql.identifier('applied_at')} TEXT`.inlineParams())\n\t\t\t\t.sql,\n\t\t];\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tconst updateQuery = sql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\tsql.identifier('applied_at')\n\t\t\t} = NULL WHERE`;\n\n\t\t\t// id\n\t\t\t// created_at\n\t\t\t// hash\n\t\t\tif (backfillEntry.id) updateQuery.append(sql` ${sql.identifier('id')} = ${backfillEntry.id}`);\n\t\t\telse if (backfillEntry.matchedBy === 'millis') {\n\t\t\t\tupdateQuery.append(sql` ${sql.identifier('created_at')} = ${backfillEntry.created_at}`);\n\t\t\t} else updateQuery.append(sql` ${sql.identifier('hash')} = ${backfillEntry.hash}`);\n\n\t\t\tstatements.push(db.dialect.sqlToQuery(updateQuery.inlineParams()).sql);\n\t\t}\n\n\t\tawait callback(statements);\n\t\treturn;\n\t},\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-proxy.js","names":[],"sources":["../../src/up-migrations/sqlite-proxy.ts"],"sourcesContent":["import type { MigrationMeta } from '~/migrator.ts';\nimport { sql } from '~/sql/sql.ts';\nimport type { BaseSQLiteDatabase } from '~/sqlite-core/index.ts';\nimport type { SqliteRemoteDatabase } from '~/sqlite-proxy/index.ts';\nimport type { ProxyMigrator } from '~/sqlite-proxy/migrator.ts';\nimport { GET_VERSION_FOR, MIGRATIONS_TABLE_VERSIONS, type UpgradeResult } from './utils.ts';\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 upgradeAsyncIfNeeded(\n\tmigrationsTable: string,\n\tdb: SqliteRemoteDatabase<Record<string, unknown>>,\n\tcallback: ProxyMigrator,\n\tlocalMigrations: MigrationMeta[],\n): Promise<UpgradeResult> {\n\t// Check if the table exists at all\n\tconst tableExists = await db.session.values<[1]>(\n\t\tsql`SELECT 1 FROM sqlite_master WHERE type = 'table' AND 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 db.session.values<[string]>(\n\t\tsql`SELECT name as column_name FROM pragma_table_info(${migrationsTable})`,\n\t);\n\n\tconst version = GET_VERSION_FOR.sqlite(rows.map((r) => r[0]));\n\n\tfor (let v = version; v < MIGRATIONS_TABLE_VERSIONS.sqlite; v++) {\n\t\tconst upgradeFn = upgradeAsyncFunctions[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\nconst upgradeAsyncFunctions: Record<\n\tnumber,\n\t(\n\t\tmigrationsTable: string,\n\t\tdb: BaseSQLiteDatabase<'async', unknown, 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\t// this can be null from legacy implementation where id was serial\n\t\tconst dbRows = (await db.session.values<[number | null, string, number]>(\n\t\t\tsql`SELECT id, hash, created_at FROM ${table} ORDER BY id ASC`,\n\t\t)).map((row) => ({\n\t\t\tid: row[0],\n\t\t\thash: row[1],\n\t\t\tcreated_at: row[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\n\t\t// id can be null from legacy implementation where id was serial\n\t\tconst toApply: {\n\t\t\tid: number | null;\n\t\t\tname: string;\n\t\t\thash: string;\n\t\t\tcreated_at: string;\n\t\t\tmatchedBy: 'id' | 'hash' | 'millis';\n\t\t}[] = [];\n\n\t\t// id can be null from legacy implementation where id was serial\n\t\t// hash can only be '' for bun-sqlite journal entries\n\t\tlet unmatched: { id: number | null; hash: string; created_at: 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\t\t\tlet matchedBy: 'hash' | 'millis' | null = null;\n\t\t\tif (candidates && candidates.length === 1) {\n\t\t\t\tmatched = candidates[0];\n\t\t\t\tmatchedBy = 'millis';\n\t\t\t} else if (candidates && candidates.length > 1) {\n\t\t\t\tmatched = candidates.find((c) => c.hash && dbRow.hash && c.hash === dbRow.hash); // for bun-sqlite cases (journal had empty hash)\n\t\t\t\tif (matched) matchedBy = 'hash';\n\t\t\t} else {\n\t\t\t\tmatched = byHash.get(dbRow.hash);\n\t\t\t\tif (matched) matchedBy = 'hash';\n\t\t\t}\n\n\t\t\tif (matched) {\n\t\t\t\ttoApply.push({\n\t\t\t\t\tid: dbRow.id,\n\t\t\t\t\tname: matched.name,\n\t\t\t\t\thash: dbRow.hash,\n\t\t\t\t\tcreated_at: stringified,\n\t\t\t\t\tmatchedBy: dbRow.id ? 'id' : matchedBy!,\n\t\t\t\t});\n\t\t\t} else unmatched.push(dbRow);\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 (unmatched.length > 0) {\n\t\t\tthrow Error(\n\t\t\t\t`While upgrading your database migrations table we found ${unmatched.length} (${\n\t\t\t\t\tunmatched.map((it) => `[id: ${it.id}, created_at: ${it.created_at}]`).join(', ')\n\t\t\t\t}) migrations 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 COLUMN ${sql.identifier('name')} text`.inlineParams()).sql,\n\t\t\tdb.dialect.sqlToQuery(sql`ALTER TABLE ${table} ADD COLUMN ${sql.identifier('applied_at')} TEXT`.inlineParams())\n\t\t\t\t.sql,\n\t\t];\n\t\tfor (const backfillEntry of toApply) {\n\t\t\tconst updateQuery = sql`UPDATE ${table} SET ${sql.identifier('name')} = ${backfillEntry.name}, ${\n\t\t\t\tsql.identifier('applied_at')\n\t\t\t} = NULL WHERE`;\n\n\t\t\t// id\n\t\t\t// created_at\n\t\t\t// hash\n\t\t\tif (backfillEntry.id) updateQuery.append(sql` ${sql.identifier('id')} = ${backfillEntry.id}`);\n\t\t\telse if (backfillEntry.matchedBy === 'millis') {\n\t\t\t\tupdateQuery.append(sql` ${sql.identifier('created_at')} = ${backfillEntry.created_at}`);\n\t\t\t} else updateQuery.append(sql` ${sql.identifier('hash')} = ${backfillEntry.hash}`);\n\n\t\t\tstatements.push(db.dialect.sqlToQuery(updateQuery.inlineParams()).sql);\n\t\t}\n\n\t\tawait callback(statements);\n\t\treturn;\n\t},\n};\n"],"mappings":"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"}