Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions apps/server/src/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,38 @@ import { getMigrations } from 'better-auth/db/migration';
import { config } from './config';
import { createAuth } from './auth';
import { SyncStore } from './store';
import { retryReady } from './startup';

const settings = config();
const store = new SyncStore(settings.databaseUrl);
const identity = createAuth(settings);
try {
await store.migrate();
await store.transaction(async (tx) => {
await tx`SELECT pg_advisory_xact_lock(192837466)`;
const migration = await getMigrations(identity.auth.options);
await migration.runMigrations();
// Pre-deploy migrations run before the container is healthchecked, and the
// managed database may still be waking. Retry instead of failing the deploy.
await retryReady(() => store.migrate(), {
onRetry: (error, attempt, delayMs) =>
console.warn(
`Database not ready for migrations (attempt ${attempt}): ${
error instanceof Error ? error.message : 'unknown error'
}; retrying in ${delayMs}ms`,
),
});
await retryReady(
() =>
store.transaction(async (tx) => {
await tx`SELECT pg_advisory_xact_lock(192837466)`;
const migration = await getMigrations(identity.auth.options);
await migration.runMigrations();
}),
{
onRetry: (error, attempt, delayMs) =>
console.warn(
`Auth migrations not ready (attempt ${attempt}): ${
error instanceof Error ? error.message : 'unknown error'
}; retrying in ${delayMs}ms`,
),
},
);
} finally {
await identity.close();
await store.close();
Expand Down
12 changes: 6 additions & 6 deletions apps/server/src/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
*/

export interface RetryReadyOptions {
/** Total probe attempts before giving up. Defaults to 60. */
/** Total probe attempts before giving up. Defaults to 30. */
attempts?: number;
/** Delay before the second attempt, doubled each retry. Defaults to 1000ms. */
/** Delay before the second attempt, doubled each retry. Defaults to 500ms. */
baseDelayMs?: number;
/** Upper bound for a single delay. Defaults to 5000ms. */
/** Upper bound for a single delay. Defaults to 3000ms. */
maxDelayMs?: number;
/** Injectable sleep for tests. Defaults to `setTimeout`. */
sleep?: (ms: number) => Promise<void>;
Expand All @@ -32,9 +32,9 @@ export async function retryReady(
probe: () => Promise<void>,
options: RetryReadyOptions = {},
): Promise<void> {
const attempts = Math.max(1, options.attempts ?? 60);
const baseDelayMs = Math.max(0, options.baseDelayMs ?? 1000);
const maxDelayMs = Math.max(0, options.maxDelayMs ?? 5000);
const attempts = Math.max(1, options.attempts ?? 30);
const baseDelayMs = Math.max(0, options.baseDelayMs ?? 500);
const maxDelayMs = Math.max(0, options.maxDelayMs ?? 3000);
const sleep = options.sleep ?? defaultSleep;
let lastError: unknown;
for (let attempt = 1; attempt <= attempts; attempt += 1) {
Expand Down
Loading