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: { 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, parse: (x: any) => Number(x), serialize: (x: any) => x.toString(), }, }, }, postgres, }), plugins: [new CamelCasePlugin()], }); }; 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); };