const protobuf = require('protobufjs') const fs = require('fs') import { Utils } from '../BaseUtils' import { DataUtils } from '../data' import { ErrorUtils } from '../error' const $data = new DataUtils() const $error = new ErrorUtils() const isLive = !fs.existsSync('./lib') const protoPath = isLive ? './node_modules/@usssa/core/lib/proto/data.proto' : './lib/proto/data.proto' export class TransformUtils extends Utils { constructor() { super() } parsePubSubMessage = (message: TMessage) => { try { const raw_message = message.data.message.data if (!raw_message) throw new Error('Missing message') const str_message = Buffer.from(raw_message, 'base64').toString() if (!JSON.parse(str_message)) throw new Error('Malformed message') const event = JSON.parse(str_message) return event } catch (error) { throw $error.errorHandler({ error }) } } transformDecodedFirestoreEvent = (event: TEvent) => { function transform(currentObj) { // Initialize an empty object to hold the transformed data const transformed = {} // Iterate over each key in the current object for (const key in currentObj) { if (currentObj.hasOwnProperty(key)) { const value = currentObj[key] if (typeof value === 'object' && value !== null) { // Check if the object contains any of the specific type keys if (value.hasOwnProperty('stringValue')) { transformed[key] = value.stringValue } else if (value.hasOwnProperty('integerValue')) { transformed[key] = Number(value.integerValue) } else if (value.hasOwnProperty('booleanValue')) { transformed[key] = value.booleanValue } else if ( value.hasOwnProperty('mapValue') && value.mapValue.hasOwnProperty('fields') ) { // Recursively transform nested mapValue objects that have fields transformed[key] = transform(value.mapValue.fields) } else if ( value.hasOwnProperty('arrayValue') && value.arrayValue.hasOwnProperty('values') ) { // Transform each element in the array transformed[key] = value.arrayValue.values.map((el) => { // Each element in the arrayValue is an object with a type key if (el.hasOwnProperty('stringValue')) { return el.stringValue } else if (el.hasOwnProperty('integerValue')) { return Number(el.integerValue) } else if (el.hasOwnProperty('booleanValue')) { return el.booleanValue } else if ( el.hasOwnProperty('mapValue') && el.mapValue.hasOwnProperty('fields') ) { return transform(el.mapValue.fields) } else { return transform(el) } }) } else { // If none of the above, recursively transform the current level transformed[key] = transform(value) } } else { transformed[key] = value // If it's a primitive value, keep it as is } } } return transformed } return transform(event) } decodeFirestoreEvent = async (event: TEvent) => { try { const docParts = event.document.split('/') const collection = docParts[0] const docId = docParts[1] const root = await protobuf.load(protoPath) const DocumentEventData = root.lookupType( 'google.events.cloud.firestore.v1.DocumentEventData' ) const data = DocumentEventData.decode(event.data) const result: Record = { collection, docId, data, } if (data.value?.fields) { result.transformedValue = this.transformDecodedFirestoreEvent( data.value.fields ) } if (data.oldValue?.fields) { result.transformedOldValue = this.transformDecodedFirestoreEvent( data.oldValue.fields ) } return result } catch (error) { throw $error.errorHandler({ error }) } } encodeFirestoreEvent = async (event: TEvent) => { try { const root = await protobuf.load(protoPath) const DocumentEventData = root.lookupType( 'google.events.cloud.firestore.v1.DocumentEventData' ) const data = DocumentEventData.encode(event.data).finish() return { ...event, data, } } catch (error) { throw $error.errorHandler({ error }) } } parseUpdateDiff = (before: TEvent, after: TEvent, exclude: string[] = []) => { try { return $data.compareObjects(after, before, exclude) } catch (error) { throw $error.errorHandler({ error }) } } }