chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { runMigrations } from "graphile-worker";
|
||||
import type { PrismaClient } from "~/db.server";
|
||||
import { prisma } from "~/db.server";
|
||||
import { env } from "~/env.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { PgNotifyService } from "./pgNotify.server";
|
||||
import { z } from "zod";
|
||||
|
||||
export class GraphileMigrationHelperService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call() {
|
||||
this.#logDebug("GraphileMigrationHelperService.call");
|
||||
|
||||
await this.#detectAndPrepareForMigrations();
|
||||
|
||||
await runMigrations({
|
||||
connectionString: env.DATABASE_URL,
|
||||
schema: env.WORKER_SCHEMA,
|
||||
});
|
||||
}
|
||||
|
||||
#logDebug(message: string, args?: any) {
|
||||
logger.debug(`[migrationHelper] ${message}`, args);
|
||||
}
|
||||
|
||||
async #getLatestMigration() {
|
||||
const migrationQueryResult = await this.#prismaClient.$queryRawUnsafe(`
|
||||
SELECT id FROM ${env.WORKER_SCHEMA}.migrations
|
||||
ORDER BY id DESC LIMIT 1
|
||||
`);
|
||||
|
||||
const MigrationQueryResultSchema = z.array(z.object({ id: z.number() }));
|
||||
|
||||
const migrationResults = MigrationQueryResultSchema.parse(migrationQueryResult);
|
||||
|
||||
if (!migrationResults.length) {
|
||||
// no migrations applied yet
|
||||
return -1;
|
||||
}
|
||||
|
||||
return migrationResults[0].id;
|
||||
}
|
||||
|
||||
async #graphileSchemaExists() {
|
||||
const schemaCount = await this.#prismaClient.$executeRaw`
|
||||
SELECT schema_name FROM information_schema.schemata
|
||||
WHERE schema_name = ${env.WORKER_SCHEMA}
|
||||
`;
|
||||
|
||||
return schemaCount === 1;
|
||||
}
|
||||
|
||||
/** Helper for graphile-worker v0.14.0 migration. No-op if already migrated. */
|
||||
async #detectAndPrepareForMigrations() {
|
||||
if (!(await this.#graphileSchemaExists())) {
|
||||
// no schema yet, likely first start
|
||||
return;
|
||||
}
|
||||
|
||||
const latestMigration = await this.#getLatestMigration();
|
||||
|
||||
if (latestMigration < 0) {
|
||||
// no migrations found
|
||||
return;
|
||||
}
|
||||
|
||||
// the first v0.14.0 migration has ID 11
|
||||
if (latestMigration > 10) {
|
||||
// already migrated
|
||||
return;
|
||||
}
|
||||
|
||||
// add 15s to graceful shutdown timeout, just to be safe
|
||||
const migrationDelayInMs = env.GRACEFUL_SHUTDOWN_TIMEOUT + 15000;
|
||||
|
||||
this.#logDebug("Delaying worker startup due to pending migration", {
|
||||
latestMigration,
|
||||
migrationDelayInMs,
|
||||
});
|
||||
|
||||
console.log(`⚠️ detected pending graphile migration`);
|
||||
console.log(`⚠️ notifying running workers`);
|
||||
|
||||
const pgNotify = new PgNotifyService();
|
||||
await pgNotify.call("trigger:graphile:migrate", { latestMigration });
|
||||
|
||||
console.log(`⚠️ delaying worker startup by ${migrationDelayInMs}ms`);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, migrationDelayInMs));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { z } from "zod";
|
||||
import type { PrismaClient } from "~/db.server";
|
||||
import { prisma } from "~/db.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import type { NotificationCatalog, NotificationChannel } from "./types";
|
||||
|
||||
export class PgNotifyService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call<TChannel extends NotificationChannel>(
|
||||
channelName: TChannel,
|
||||
payload: z.infer<NotificationCatalog[TChannel]>
|
||||
) {
|
||||
this.#logDebug("Sending notification", { channelName, notifyPayload: payload });
|
||||
|
||||
await this.#prismaClient.$executeRaw`
|
||||
SELECT pg_notify(${channelName}, ${JSON.stringify(payload)})
|
||||
`;
|
||||
}
|
||||
|
||||
#logDebug(message: string, args?: any) {
|
||||
logger.debug(`[pgNotify] ${message}`, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const notificationCatalog = {
|
||||
"trigger:graphile:migrate": z.object({
|
||||
latestMigration: z.number(),
|
||||
}),
|
||||
};
|
||||
|
||||
export type NotificationCatalog = typeof notificationCatalog;
|
||||
|
||||
export type NotificationChannel = keyof NotificationCatalog;
|
||||
Reference in New Issue
Block a user