import { Kysely, CamelCasePlugin, Generated, GeneratedAlways, Migrator, FileMigrationProvider } from "kysely"; import { PostgresJSDialect } from "kysely-postgres-js"; import postgres from "postgres"; import { MessageType, ReactionType, UserDataType, HashScheme, SignatureScheme } from "@farcaster/hub-nodejs"; import * as path from "path"; import { promises as fs } from "fs"; import { fileURLToPath } from "url"; import { Logger } from "./log"; import { err, ok, Result } from "neverthrow"; export interface Database { hubSubscriptions: { host: string; lastEventId: number; }; casts: { id: GeneratedAlways; createdAt: Generated; updatedAt: Generated; deletedAt: Date | null; timestamp: Date; fid: number; text: string; hash: Uint8Array; parentHash: Uint8Array | null; parentFid: number | null; parentUrl: string | null; embeds: Generated; mentions: Generated; mentionsPositions: Generated; }; messages: { id: GeneratedAlways; createdAt: Generated; updatedAt: Generated; deletedAt: Date | null; revokedAt: Date | null; prunedAt: Date | null; fid: number; messageType: MessageType; timestamp: Date; hash: Uint8Array; hashScheme: HashScheme; signature: Uint8Array; signatureScheme: SignatureScheme; signer: Uint8Array; raw: Uint8Array; }; reactions: { id: GeneratedAlways; createdAt: Generated; updatedAt: Generated; deletedAt: Date | null; fid: number; reactionType: ReactionType; timestamp: Date; hash: Uint8Array; targetHash: Uint8Array | null; targetFid: number | null; targetUrl: string | null; }; signers: { id: GeneratedAlways; createdAt: Generated; updatedAt: Generated; deletedAt: Date | null; timestamp: Date; fid: number; custodyAddress: Uint8Array; signer: Uint8Array; name: string | null; hash: Uint8Array; }; verifications: { id: GeneratedAlways; createdAt: Generated; updatedAt: Generated; deletedAt: Date | null; fid: number; timestamp: Date; hash: Uint8Array; claim: { address: string; ethSignature: string; blockHash: string; }; }; userData: { id: GeneratedAlways; createdAt: Generated; updatedAt: Generated; deletedAt: Date | null; timestamp: Date; fid: number; hash: Uint8Array; type: UserDataType; value: string; }; fids: { fid: number; createdAt: Generated; updatedAt: Generated; custodyAddress: Uint8Array; }; fnames: { fname: string; createdAt: Generated; updatedAt: Generated; custodyAddress: Uint8Array; expiresAt: Date; }; links: { hash: Uint8Array; id: GeneratedAlways; fid: number; targetFid: number | null; type: string; timestamp: Date; createdAt: Generated; updatedAt: Generated; displayTimestamp: Date | null; deletedAt: Date | null; }; } export const getDbClient = (connectionString: string) => { return new Kysely({ dialect: new PostgresJSDialect({ connectionString, options: { max: 10, types: { // BigInts will not exceed Number.MAX_SAFE_INTEGER for our use case. // Return as JavaScript's `number` type so it's easier to work with. bigint: { to: 20, from: 20, // rome-ignore lint/suspicious/noExplicitAny: legacy code, avoid using ignore for new code parse: (x: any) => Number(x), // rome-ignore lint/suspicious/noExplicitAny: legacy code, avoid using ignore for new code serialize: (x: any) => x.toString(), }, }, }, postgres, }), plugins: [new CamelCasePlugin()], }); }; // rome-ignore lint/suspicious/noExplicitAny: legacy code, avoid using ignore for new code export const migrateToLatest = async (db: Kysely, log: Logger): Promise> => { const migrator = new Migrator({ db, provider: new FileMigrationProvider({ fs, path, migrationFolder: path.join(path.dirname(fileURLToPath(import.meta.url)), "migrations"), }), }); const { error, results } = await migrator.migrateToLatest(); results?.forEach((it) => { if (it.status === "Success") { log.info(`migration "${it.migrationName}" was executed successfully`); } else if (it.status === "Error") { log.error(`failed to execute migration "${it.migrationName}"`); } }); if (error) { log.error("failed to migrate"); log.error(error); return err(error); } return ok(undefined); };