mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 11:24:57 -05:00
test(kysely-adapter): reproduce D1 migration race
This commit is contained in:
@@ -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
@@ -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);
|
||||
Generated
+6
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user