test(kysely-adapter): reproduce D1 migration race

This commit is contained in:
Taesu
2026-10-10 14:00:53 +09:00
parent e15625ff38
commit 5da57c6b5b
4 changed files with 114 additions and 0 deletions
+2
View File
@@ -3,11 +3,13 @@
"private": true,
"dependencies": {
"@better-auth/core": "workspace:*",
"@better-auth/kysely-adapter": "workspace:*",
"@better-auth/sso": "workspace:*",
"better-auth": "workspace:*",
"hono": "^4.13.7"
},
"devDependencies": {
"kysely": "catalog:",
"vitest": "catalog:vitest",
"wrangler": "4.123.0"
},
+72
View File
@@ -1,3 +1,4 @@
import { env } from "cloudflare:workers";
import type { AuthEndpointContext } from "@better-auth/core/context";
import {
getCurrentAdapter,
@@ -9,8 +10,11 @@ import type {
DBAdapter,
DBTransactionAdapter,
} from "@better-auth/core/db/adapter";
import { createKyselyAdapter } from "@better-auth/kysely-adapter";
import { getMigrations } from "better-auth/db/migration";
import { Hono } from "hono";
import type { Kysely } from "kysely";
import { Migrator } from "kysely";
import { auth } from "./auth";
const app = new Hono();
@@ -30,6 +34,74 @@ app.post("/_test/migrate", async (c) => {
return c.body(null, 204);
});
app.get("/_test/d1-migration-lock", async (c) => {
const { kysely } = await createKyselyAdapter({ database: env.DB });
if (!kysely) throw new Error("D1 test database was not created");
const suffix = crypto.randomUUID().replaceAll("-", "");
const migrationTableName = `migration_state_${suffix}`;
const migrationLockTableName = `migration_lock_${suffix}`;
const targetTableName = `migration_target_${suffix}`;
const migrationOptions = {
db: kysely,
migrationTableName,
migrationLockTableName,
};
try {
const initialization = await new Migrator({
...migrationOptions,
provider: { getMigrations: async () => ({}) },
}).migrateToLatest();
if (initialization.error) throw initialization.error;
let migrationStarts = 0;
let releaseBarrier = () => {};
const barrier = new Promise<void>((resolve) => {
releaseBarrier = resolve;
});
const timeout = setTimeout(releaseBarrier, 1500);
try {
const migrator = new Migrator({
...migrationOptions,
provider: {
getMigrations: async () => ({
"001_create_table": {
async up(database: Kysely<unknown>) {
migrationStarts++;
if (migrationStarts === 2) releaseBarrier();
await barrier;
await database.schema
.createTable(targetTableName)
.addColumn("id", "integer")
.execute();
},
},
}),
},
});
const results = await Promise.all([
migrator.migrateToLatest(),
migrator.migrateToLatest(),
]);
const recorded = await env.DB.prepare(
`select count(*) as count from ${migrationTableName}`,
).first<{ count: number }>();
return c.json({
migrationStarts,
recorded: recorded?.count,
errors: results.map((result) =>
result.error ? String(result.error) : null,
),
});
} finally {
clearTimeout(timeout);
}
} finally {
await kysely.destroy();
}
});
app.get("/_test/async-context/concurrency", async (c) => {
const contexts = Array.from(
{ length: 32 },
@@ -0,0 +1,34 @@
import { afterAll, afterEach, beforeAll, expect, it } from "vitest";
import { createTestHarness } from "wrangler";
const server = createTestHarness({
workers: [{ configPath: "./wrangler.json" }],
});
beforeAll(async () => {
await server.listen();
});
afterEach(({ task }) => {
if (task.result?.state === "fail") server.debug();
});
afterAll(async () => {
await server.close();
});
/**
* @see https://kysely-org.github.io/kysely-apidoc/classes/SqliteAdapter.html#acquiremigrationlock
*/
it("runs a D1 migration only once when two Kysely migrators start together", async () => {
const response = await server.fetch(
"http://localhost:8787/_test/d1-migration-lock",
);
expect(response.status).toBe(200);
const result: unknown = await response.json();
expect(result).toEqual({
migrationStarts: 1,
recorded: 1,
errors: [null, null],
});
}, 15_000);
+6
View File
@@ -887,6 +887,9 @@ importers:
'@better-auth/core':
specifier: workspace:*
version: link:../../../../../packages/core
'@better-auth/kysely-adapter':
specifier: workspace:*
version: link:../../../../../packages/kysely-adapter
'@better-auth/sso':
specifier: workspace:*
version: link:../../../../../packages/sso
@@ -897,6 +900,9 @@ importers:
specifier: ^4.13.7
version: 4.13.7
devDependencies:
kysely:
specifier: 0.28.17
version: 0.28.17
vitest:
specifier: catalog:vitest
version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@25.9.4)(@vitest/coverage-istanbul@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.8.9)(msw@2.14.6(@types/node@25.9.4)(typescript@6.0.3))(vite@8.2.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0))