UNPKG

@r-delfino/mux-sync-engine

Version:

Mux Sync Engine to sync Mux data based on webhooks to Postgres

1 lines 18.3 kB
{"version":3,"sources":["../src/database/postgres.ts","../src/schemas/mux_assets.ts","../src/muxSync.ts","../src/database/migrate.ts"],"names":["pg","sql","Mux","DEFAULT_SCHEMA","fileURLToPath","path","fs","migrate","Client"],"mappings":"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","file":"index.cjs","sourcesContent":["import pg, { QueryResult } from 'pg'\nimport { pg as sql } from 'yesql'\nimport { EntitySchema } from '../schemas/types'\n\ntype PostgresConfig = {\n databaseUrl: string\n schema: string\n maxConnections?: number\n}\n\nexport class PostgresClient {\n pool: pg.Pool\n\n constructor(private config: PostgresConfig) {\n this.pool = new pg.Pool({\n connectionString: config.databaseUrl,\n max: config.maxConnections || 10,\n keepAlive: true,\n })\n }\n\n async delete(table: string, id: string): Promise<boolean> {\n const prepared = sql(`\n delete from \"${this.config.schema}\".\"${table}\" \n where id = :id\n returning id;\n `)({ id })\n const { rows } = await this.query(prepared.text, prepared.values)\n return rows.length > 0\n }\n\n async query(text: string, params?: string[]): Promise<QueryResult> {\n return this.pool.query(text, params)\n }\n\n async upsertMany<\n T extends {\n [Key: string]: any // eslint-disable-line @typescript-eslint/no-explicit-any\n },\n >(entries: T[], table: string, tableSchema: EntitySchema, options?: { conflict?: string }): Promise<T[]> {\n if (!entries.length) return []\n\n // Max 5 in parallel to avoid exhausting connection pool\n const chunkSize = 5\n const results: pg.QueryResult<T>[] = []\n\n for (let i = 0; i < entries.length; i += chunkSize) {\n const chunk = entries.slice(i, i + chunkSize)\n\n const queries: Promise<pg.QueryResult<T>>[] = []\n chunk.forEach((entry) => {\n // Inject the values\n const cleansed = this.cleanseArrayField(entry)\n const upsertSql = this.constructUpsertSql(this.config.schema, table, tableSchema, options)\n\n const prepared = sql(upsertSql, {\n useNullForMissing: true,\n })(cleansed)\n\n queries.push(this.pool.query(prepared.text, prepared.values))\n })\n\n results.push(...(await Promise.all(queries)))\n }\n\n return results.flatMap((it) => it.rows)\n }\n\n async findMissingEntries(table: string, ids: string[]): Promise<string[]> {\n if (!ids.length) return []\n\n const prepared = sql(`\n select id from \"${this.config.schema}\".\"${table}\"\n where id=any(:ids::text[]);\n `)({ ids })\n\n const { rows } = await this.query(prepared.text, prepared.values)\n const existingIds = rows.map((it) => it.id)\n\n const missingIds = ids.filter((it) => !existingIds.includes(it))\n\n return missingIds\n }\n\n /**\n * Returns an (yesql formatted) upsert function based on the key/vals of an object.\n * eg,\n * insert into customers (\"id\", \"name\")\n * values (:id, :name)\n * on conflict (id)\n * do update set (\n * \"id\" = :id,\n * \"name\" = :name\n * )\n */\n private constructUpsertSql(\n schema: string,\n table: string,\n tableSchema: EntitySchema,\n options?: {\n conflict?: string\n }\n ): string {\n const { conflict = 'id' } = options || {}\n const properties = tableSchema.properties\n\n return `\n insert into \"${schema}\".\"${table}\" (\n ${properties.map((x) => `\"${x}\"`).join(',')}\n )\n values (\n ${properties.map((x) => `:${x}`).join(',')}\n )\n on conflict (\n ${conflict}\n )\n do update set \n ${properties.map((x) => `\"${x}\" = :${x}`).join(',')}\n ;`\n }\n\n /**\n * For array object field like invoice.custom_fields\n * ex: [{\"name\":\"Project name\",\"value\":\"Test Project\"}]\n *\n * we need to stringify it first cos passing array object directly will end up with\n * {\n * invalid input syntax for type json\n * detail: 'Expected \":\", but found \"}\".',\n * where: 'JSON data, line 1: ...\\\\\":\\\\\"Project name\\\\\",\\\\\"value\\\\\":\\\\\"Test Project\\\\\"}\"}',\n * }\n */\n\n private cleanseArrayField(obj: {\n [Key: string]: any // eslint-disable-line @typescript-eslint/no-explicit-any\n }): {\n [Key: string]: any // eslint-disable-line @typescript-eslint/no-explicit-any\n } {\n const cleansed = { ...obj }\n Object.keys(cleansed).map((k) => {\n const data = cleansed[k]\n if (Array.isArray(data)) {\n cleansed[k] = JSON.stringify(data)\n }\n })\n return cleansed\n }\n}\n","import type { EntitySchema } from './types'\n\nexport const muxAssetsSchema: EntitySchema = {\n properties: [\n 'mux_asset_id',\n 'status',\n 'created_at',\n 'duration',\n 'max_stored_resolution',\n 'max_stored_frame_rate',\n 'aspect_ratio',\n 'playback_ids',\n 'tracks',\n 'errors',\n 'master_access',\n 'mp4_support',\n 'normalize_audio',\n 'static_renditions',\n 'test',\n 'passthrough',\n 'live_stream_id',\n 'encoding_tier',\n 'ingest_type',\n 'source_asset_id',\n 'per_title_encode',\n 'upload_id',\n 'input_info',\n 'video_quality',\n 'resolution_tier',\n 'non_standard_input_reasons',\n 'progress',\n 'meta',\n 'max_resolution_tier'\n ],\n} as const\n","import { Mux } from '@mux/mux-node'\nimport { pg as sql } from 'yesql'\nimport { PostgresClient } from './database/postgres'\nimport { muxAssetsSchema } from './schemas/mux_assets'\nimport { muxLiveStreamsSchema } from './schemas/mux_live_streams'\nimport { MuxSyncConfig, Sync, SyncBackfill, SyncBackfillParams } from './types'\nimport { HeadersLike } from '@mux/mux-node/core'\n\nconst DEFAULT_SCHEMA = 'mux'\n\nexport class MuxSync {\n mux: Mux\n postgresClient: PostgresClient\n\n constructor(private config: MuxSyncConfig) {\n this.mux = new Mux({\n tokenId: config.muxTokenId,\n tokenSecret: config.muxTokenSecret,\n webhookSecret: config.muxWebhookSecret\n })\n\n this.config.logger?.info(\n 'MuxSync initialized'\n )\n\n this.postgresClient = new PostgresClient({\n databaseUrl: config.databaseUrl,\n schema: config.schema || DEFAULT_SCHEMA,\n maxConnections: config.maxPostgresConnections,\n })\n }\n\n async processWebhook(payload: string, headers: HeadersLike) {\n const event = this.mux.webhooks.unwrap(payload, headers)\n this.config.logger?.info(\n `Received webhook ${event.id}: ${event.type}`\n )\n\n switch (event.type) {\n //todo: add manual fetch\n case 'video.asset.created':\n case 'video.asset.ready':\n const asset = await this.fetchOrUseWebhookData(event.data as Mux.Video.Assets.Asset, async (id) => Promise.resolve(event.data as Mux.Video.Assets.Asset))\n this.config.logger?.info(\n `Asset for upsert: ${JSON.stringify(asset)}`\n )\n await this.upsertAssets([asset])\n break\n default:\n console.warn('Unhandled webhook event', event.type)\n break\n }\n }\n\n private async fetchOrUseWebhookData<T extends { id?: string }>(\n entity: T,\n fetchFn: (id: string) => Promise<T>\n ): Promise<T> {\n if (!entity.id) return entity\n\n if (this.config.revalidateEntityViaMuxApi) {\n return fetchFn(entity.id)\n }\n\n return entity\n }\n\n async syncBackfill(params?: SyncBackfillParams): Promise<SyncBackfill> {\n const { object } = params ?? {}\n let muxAssets,\n muxLiveStreams\n\n switch (object) {\n case 'all':\n break\n case 'mux_assets':\n break\n case 'mux_live_streams':\n break\n default:\n break\n }\n\n return {\n muxAssets,\n muxLiveStreams,\n }\n }\n\n private async upsertAssets(\n assets: Mux.Video.Assets.Asset[],\n backfillRelatedEntities?: boolean\n ): Promise<any[]> {\n if (backfillRelatedEntities ?? this.config.backfillRelatedEntities) {\n //todo: fetch related entities\n }\n\n const transformedAssets = assets.map((asset: any) => ({\n ...asset,\n mux_asset_id: asset.id,\n created_at: asset.created_at ? new Date(Number(asset.created_at) * 1000).toISOString() : null,\n }))\n\n return this.postgresClient.upsertMany(transformedAssets, 'assets', muxAssetsSchema, { conflict: 'mux_asset_id' })\n }\n\n}\n","import { Client } from 'pg'\nimport { migrate } from 'pg-node-migrations'\nimport fs from 'node:fs'\nimport pino from 'pino'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst DEFAULT_SCHEMA = 'mux'\n\n// Helper function to get __dirname that works in both ESM and CommonJS\nfunction getDirname(): string {\n try {\n if (typeof import.meta !== 'undefined' && import.meta.url) {\n const __filename = fileURLToPath(import.meta.url)\n return path.dirname(__filename)\n }\n } catch (error) {\n // Fallback for cases where import.meta.url is not available\n }\n \n // Fallback for CommonJS context\n try {\n // @ts-ignore - __dirname is available in CommonJS context\n return __dirname\n } catch (error) {\n // If neither works, use a relative path from the current working directory\n return path.join(process.cwd(), 'packages/sync-engine/src/database')\n }\n}\n\ntype MigrationConfig = {\n databaseUrl: string\n logger?: pino.Logger\n}\n\nasync function connectAndMigrate(\n client: Client,\n migrationsDirectory: string,\n config: MigrationConfig,\n logOnError = false\n) {\n if (!fs.existsSync(migrationsDirectory)) {\n config.logger?.info(`Migrations directory ${migrationsDirectory} not found, skipping`)\n return\n }\n\n const optionalConfig = {\n schemaName: DEFAULT_SCHEMA,\n tableName: 'migrations',\n }\n\n try {\n await migrate({ client }, migrationsDirectory, optionalConfig)\n } catch (error) {\n if (logOnError && error instanceof Error) {\n config.logger?.error(error, 'Migration error:')\n } else {\n throw error\n }\n }\n}\n\nexport async function runMigrations(config: MigrationConfig): Promise<void> {\n // Init DB\n const client = new Client({\n connectionString: config.databaseUrl,\n connectionTimeoutMillis: 10_000,\n })\n\n try {\n // Run migrations\n await client.connect()\n\n // Ensure schema exists, not doing it via migration to not break current migration checksums\n await client.query(`CREATE SCHEMA IF NOT EXISTS ${DEFAULT_SCHEMA};`)\n\n config.logger?.info('Running migrations')\n\n await connectAndMigrate(client, path.resolve(getDirname(), './migrations'), config)\n } catch (err) {\n config.logger?.error(err, 'Error running migrations')\n } finally {\n await client.end()\n config.logger?.info('Finished migrations')\n }\n}\n"]}