UNPKG

@flatfile/plugin-record-hook

Version:

A plugin for running custom logic on individual data records in Flatfile.

1 lines 22.3 kB
{"version":3,"sources":["../src/record.translator.ts","../src/record.utils.ts","../src/RecordHook.ts","../src/record.hook.plugin.ts"],"names":["RecordTranslator","records","FlatfileRecords","XRecords","record","rawData","k","v","links","link","linkedRawData","lk","lv","metadata","FlatfileRecord","recordWithInfo","values","messages","info","config","deepEqual","obj1","obj2","options","isObject","keys1","keys2","key","handleValuesComparison","values1","values2","valueKeys","hasChanges","unchangedKeys","value","cleanRecord","deleteKeys","obj","keys","message","completeCommit","event","debug","commitId","trackChanges","logInfo","logError","prepareFlatfileRecords","prepareXRecords","startTimer","label","endTimer","RecordHook","handler","concurrency","BulkRecordHook","promises","promise","data","batch","asyncBatch","modifiedRecords","originalRecord","original","e","recordHookPlugin","sheetSlug","callback","listener","bulkRecordHookPlugin"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Flatfile } from '@flatfile/api'\nimport type {\n IRawRecord,\n TPrimitive,\n TRecordData,\n TRecordDataWithLinks,\n TRecordValue,\n} from '@flatfile/hooks'\nimport { FlatfileRecord, FlatfileRecords } from '@flatfile/hooks'\n\nexport class RecordTranslator<\n T extends FlatfileRecord | Flatfile.RecordWithLinks,\n> {\n constructor(private readonly records: T[]) {\n this.records = records\n }\n\n toFlatfileRecords = (): FlatfileRecords<any> => {\n if (this.records instanceof FlatfileRecords) {\n return this.records\n } else {\n const XRecords = this.records as Flatfile.RecordWithLinks[]\n\n const FFRecords = new FlatfileRecords(\n XRecords.map((record: Flatfile.RecordWithLinks) => {\n let rawData: TRecordDataWithLinks = {}\n for (let [k, v] of Object.entries(record.values)) {\n if (!!v.links?.length && v.value) {\n const links = v.links.map((link) => {\n let linkedRawData: TRecordData = {}\n for (let [lk, lv] of Object.entries(link.values)) {\n linkedRawData[lk] = lv.value as TPrimitive\n }\n return linkedRawData\n })\n rawData[k] = {\n value: v.value as TRecordValue,\n links,\n }\n } else {\n rawData[k] = v.value as TPrimitive\n }\n }\n const metadata = record.metadata\n const rawRecord = {\n rowId: record.id,\n rawData,\n metadata,\n } as IRawRecord\n\n return rawRecord\n })\n )\n return FFRecords\n }\n }\n\n toXRecords = (): Flatfile.RecordsWithLinks => {\n if (this.records[0] instanceof FlatfileRecord) {\n const FFRecords = this.records as FlatfileRecord[]\n return FFRecords.map((record: FlatfileRecord) => {\n const recordWithInfo = record.toJSON()\n let values: any = {}\n for (let [k, v] of Object.entries(recordWithInfo.row.rawData)) {\n const messages = recordWithInfo.info\n .filter((info) => info.field === k)\n .map((info) => ({\n message: info.message,\n type: info.level,\n source: 'custom-logic',\n path: info.path,\n }))\n\n values[k] = {\n value:\n v !== null && typeof v === 'object' && !Array.isArray(v)\n ? v.value\n : v,\n messages: messages,\n }\n }\n const metadata = recordWithInfo.row.metadata\n const config = recordWithInfo.config\n return {\n id: String(record.rowId),\n values,\n metadata,\n config,\n }\n }) as Flatfile.RecordsWithLinks\n } else {\n return this.records as Flatfile.RecordsWithLinks\n }\n }\n}\n","import type { Flatfile } from '@flatfile/api'\nimport { FlatfileRecord, FlatfileRecords } from '@flatfile/hooks'\nimport type { FlatfileEvent } from '@flatfile/listener'\nimport { logError, logInfo } from '@flatfile/util-common'\nimport { RecordTranslator } from './record.translator'\n\n// deepEqual is a generic object comparison function that is ambivalent to the key order\nexport function deepEqual(\n obj1: any,\n obj2: any,\n options?: { removeUnchanged: boolean }\n): boolean {\n // Handle primitive comparisons\n if (obj1 === obj2) return true\n\n if (!isObject(obj1) || !isObject(obj2)) return false\n\n const keys1 = Object.keys(obj1)\n const keys2 = Object.keys(obj2)\n if (keys1.length !== keys2.length) return false\n\n return keys1.every((key) => {\n if (key === 'values') return handleValuesComparison(obj1, obj2)\n return keys2.includes(key) && deepEqual(obj1[key], obj2[key], options)\n })\n}\n\nfunction handleValuesComparison(obj1: any, obj2: any): boolean {\n const values1 = obj1.values\n const values2 = obj2.values\n const valueKeys = Object.keys(values1)\n\n let hasChanges = false\n const unchangedKeys = []\n\n // Compare each value and track unchanged ones\n valueKeys.forEach((key) => {\n if (deepEqual(values1[key], values2[key])) {\n unchangedKeys.push(key)\n } else {\n hasChanges = true\n }\n })\n\n // Remove unchanged values\n unchangedKeys.forEach((key) => {\n delete values1[key]\n delete values2[key]\n })\n\n // Clean up empty values object\n if (Object.keys(values1).length === 0) {\n delete obj1.values\n delete obj2.values\n }\n\n return !hasChanges\n}\n\nfunction isObject(value: any): boolean {\n return typeof value === 'object' && value !== null\n}\n\n/**\n * This function is used to clean a the original record before it compared to the modified/updated record.\n *\n * @remarks\n * Prior to the recordHook's callback being called, the RecordTranslator's toFlatfileRecords() method is called\n * which converts the records into a FlatfileRecords object. This removes fields that the recordHook's callback is\n * expected to check and re-add (i.e. custom-logic messages) and also removes system fields like 'updatedAt'. After all\n * callbacks have run, the toXRecords() method is called to convert the records back into the original format, adds\n * 'source: custom-logic' to all messages and sets all fields to 'valid: true'.\n *\n * This function is intended to remove fields that will never be set on a modified record, specifically 'valid' at the\n * the root level, updatedAt at all levels, and any message that does not have a source of 'custom-logic'.\n */\nexport function cleanRecord(record: Flatfile.RecordWithLinks | undefined) {\n if (!record) {\n return\n }\n // Remove `valid` at the root level of the Record, but not on the Record's fields\n if (Object.keys(record).includes('valid')) {\n delete record.valid\n }\n // Remove `updatedAt` and `valid` at all levels of the Record\n deleteKeys(record.values, ['updatedAt', 'valid'])\n}\n\nfunction deleteKeys(obj, keys) {\n if (typeof obj !== 'object' || obj === null) {\n return\n }\n\n Object.keys(obj).forEach((key) => {\n if (keys.includes(key)) {\n delete obj[key]\n } else if (key === 'messages') {\n // Filters out messages not from 'custom-logic'\n if (Array.isArray(obj['messages'])) {\n obj['messages'] = obj['messages'].filter(\n (message) => message.source === 'custom-logic'\n )\n }\n } else if (typeof obj[key] === 'object' && obj[key] !== null) {\n // Apply cleaning recursively for nested objects\n deleteKeys(obj[key], keys)\n }\n })\n}\n\nexport async function completeCommit(\n event: FlatfileEvent,\n debug: boolean\n): Promise<void> {\n const { commitId } = event.context\n const { trackChanges } = event.payload\n\n if (trackChanges) {\n try {\n await event.fetch(`v1/commits/${commitId}/complete`, {\n method: 'POST',\n })\n if (debug) {\n logInfo('@flatfile/plugin-record-hook', 'Commit completed successfully')\n }\n } catch (e) {\n logError(\n '@flatfile/plugin-record-hook',\n `Error completing commit ${commitId}`\n )\n }\n }\n return\n}\n\nexport async function prepareFlatfileRecords(\n records: any\n): Promise<Flatfile.RecordsWithLinks> {\n const fromFlatfile = new RecordTranslator<FlatfileRecord<any>>(records)\n return fromFlatfile.toXRecords()\n}\n\nexport async function prepareXRecords(\n records: any\n): Promise<FlatfileRecords<any>> {\n const fromX = new RecordTranslator<Flatfile.RecordWithLinks>(records)\n return fromX.toFlatfileRecords()\n}\n\nexport function startTimer(label: string, debug: boolean) {\n debug && console.time(`⏱️ ${label}`)\n}\n\nexport function endTimer(label: string, debug: boolean) {\n debug && console.timeEnd(`⏱️ ${label}`)\n}\n","import type { Flatfile } from '@flatfile/api'\nimport type { FlatfileRecord, FlatfileRecords } from '@flatfile/hooks'\nimport type { FlatfileEvent } from '@flatfile/listener'\nimport { asyncBatch, logError, logInfo } from '@flatfile/util-common'\nimport {\n cleanRecord,\n completeCommit,\n deepEqual,\n endTimer,\n prepareFlatfileRecords,\n prepareXRecords,\n startTimer,\n} from './record.utils'\n\nexport interface RecordHookOptions {\n concurrency?: number\n debug?: boolean\n}\n\nexport const RecordHook = async (\n event: FlatfileEvent,\n handler: (record: FlatfileRecord, event: FlatfileEvent) => any | Promise<any>,\n options: RecordHookOptions = {}\n) => {\n const { concurrency = 10 } = options\n return BulkRecordHook(\n event,\n async (records, event) => {\n const promises = new Set<Promise<any>>()\n\n for (const record of records) {\n const promise = Promise.resolve(handler(record, event)).finally(() =>\n promises.delete(promise)\n )\n promises.add(promise)\n\n if (promises.size >= concurrency) {\n await Promise.race(promises)\n }\n }\n\n return await Promise.all(promises)\n },\n options\n )\n}\n\nexport interface BulkRecordHookOptions {\n chunkSize?: number\n parallel?: number\n debug?: boolean\n}\n\nexport const BulkRecordHook = async (\n event: FlatfileEvent,\n handler: (\n records: FlatfileRecord[],\n event: FlatfileEvent\n ) => any | Promise<any>,\n options: BulkRecordHookOptions = {}\n) => {\n const { debug = false } = options\n\n try {\n startTimer('fetch data', debug)\n const data = await event.cache.init<Flatfile.RecordsWithLinks>(\n 'data',\n async (): Promise<Flatfile.RecordsWithLinks> => {\n try {\n const data = await event.data\n return data.records && data.records.length ? data.records : undefined\n } catch (e) {\n throw new Error('Error fetching records')\n }\n }\n )\n\n if (!data || data.length === 0) {\n logInfo('@flatfile/plugin-record-hook', 'No records to process')\n await completeCommit(event, debug)\n return\n }\n\n const batch = await event.cache.init<FlatfileRecords<any>>(\n 'records',\n async () => await prepareXRecords(data)\n )\n endTimer('fetch data', debug)\n\n // Execute client-defined data hooks\n startTimer('run handler', debug)\n await asyncBatch(batch.records, handler, options, event)\n endTimer('run handler', debug)\n\n event.afterAll(async () => {\n startTimer('filter modified records', debug)\n const { records: batch } =\n event.cache.get<FlatfileRecords<any>>('records')\n const records: Flatfile.RecordsWithLinks =\n await prepareFlatfileRecords(batch)\n\n const data = await event.cache.get<Flatfile.RecordsWithLinks>('data')\n const modifiedRecords: Flatfile.RecordsWithLinks = records.filter(\n (record: Flatfile.RecordWithLinks) => {\n const originalRecord: Flatfile.RecordWithLinks | undefined =\n data.find(\n (original: Flatfile.RecordWithLinks) => original.id === record.id\n )\n cleanRecord(originalRecord) // Remove fields that should not be compared\n const hasChanges = !deepEqual(record, originalRecord, {\n removeUnchanged: true,\n })\n return hasChanges\n }\n )\n\n if (!modifiedRecords || modifiedRecords.length === 0) {\n logInfo('@flatfile/plugin-record-hook', 'No records modified')\n await completeCommit(event, debug)\n return\n }\n endTimer('filter modified records', debug)\n\n logInfo(\n '@flatfile/plugin-record-hook',\n `${modifiedRecords.length} modified records`\n )\n\n try {\n startTimer('update modified records', debug)\n await event.update(modifiedRecords)\n endTimer('update modified records', debug)\n return\n } catch (e) {\n throw new Error('Error updating records')\n }\n })\n } catch (e) {\n logError('@flatfile/plugin-record-hook', (e as Error).message)\n await completeCommit(event, debug)\n return\n }\n}\n","import type { FlatfileRecord } from '@flatfile/hooks'\nimport type { FlatfileEvent, FlatfileListener } from '@flatfile/listener'\nimport type { BulkRecordHookOptions, RecordHookOptions } from './RecordHook'\nimport { BulkRecordHook, RecordHook } from './RecordHook'\n\nexport const recordHookPlugin = (\n sheetSlug: string,\n callback: (\n record: FlatfileRecord,\n event: FlatfileEvent\n ) => any | Promise<any>,\n options: RecordHookOptions = {}\n) => {\n return (listener: FlatfileListener) => {\n listener.on('commit:created', { sheetSlug }, (event: FlatfileEvent) =>\n RecordHook(event, callback, options)\n )\n }\n}\n\nexport const bulkRecordHookPlugin = (\n sheetSlug: string,\n callback: (\n records: FlatfileRecord[],\n event: FlatfileEvent\n ) => any | Promise<any>,\n options: BulkRecordHookOptions = {}\n) => {\n return (listener: FlatfileListener) => {\n listener.on('commit:created', { sheetSlug }, (event: FlatfileEvent) =>\n BulkRecordHook(event, callback, options)\n )\n }\n}\n\nexport {\n bulkRecordHookPlugin as bulkRecordHook,\n recordHookPlugin as recordHook,\n}\n"]}