| |
| |
| |
|
|
| |
| |
|
|
| export enum PostgresTypes { |
| abstime = 'abstime', |
| bool = 'bool', |
| date = 'date', |
| daterange = 'daterange', |
| float4 = 'float4', |
| float8 = 'float8', |
| int2 = 'int2', |
| int4 = 'int4', |
| int4range = 'int4range', |
| int8 = 'int8', |
| int8range = 'int8range', |
| json = 'json', |
| jsonb = 'jsonb', |
| money = 'money', |
| numeric = 'numeric', |
| oid = 'oid', |
| reltime = 'reltime', |
| text = 'text', |
| time = 'time', |
| timestamp = 'timestamp', |
| timestamptz = 'timestamptz', |
| timetz = 'timetz', |
| tsrange = 'tsrange', |
| tstzrange = 'tstzrange', |
| } |
|
|
| type Columns = { |
| name: string |
| type: string |
| flags?: string[] |
| type_modifier?: number |
| }[] |
|
|
| type BaseValue = null | string | number | boolean |
| type RecordValue = BaseValue | BaseValue[] |
|
|
| type Record = { |
| [key: string]: RecordValue |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export const convertChangeData = ( |
| columns: Columns, |
| record: Record | null, |
| options: { skipTypes?: string[] } = {} |
| ): Record => { |
| const skipTypes = options.skipTypes ?? [] |
|
|
| if (!record) { |
| return {} |
| } |
|
|
| return Object.keys(record).reduce((acc, rec_key) => { |
| acc[rec_key] = convertColumn(rec_key, columns, record, skipTypes) |
| return acc |
| }, {} as Record) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export const convertColumn = ( |
| columnName: string, |
| columns: Columns, |
| record: Record, |
| skipTypes: string[] |
| ): RecordValue => { |
| const column = columns.find((x) => x.name === columnName) |
| const colType = column?.type |
| const value = record[columnName] |
|
|
| if (colType && !skipTypes.includes(colType)) { |
| return convertCell(colType, value) |
| } |
|
|
| return noop(value) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export const convertCell = (type: string, value: RecordValue): RecordValue => { |
| |
| if (type.charAt(0) === '_') { |
| const dataType = type.slice(1, type.length) |
| return toArray(value, dataType) |
| } |
|
|
| |
| switch (type) { |
| case PostgresTypes.bool: |
| return toBoolean(value) |
| case PostgresTypes.float4: |
| case PostgresTypes.float8: |
| case PostgresTypes.int2: |
| case PostgresTypes.int4: |
| case PostgresTypes.int8: |
| case PostgresTypes.numeric: |
| case PostgresTypes.oid: |
| return toNumber(value) |
| case PostgresTypes.json: |
| case PostgresTypes.jsonb: |
| return toJson(value) |
| case PostgresTypes.timestamp: |
| return toTimestampString(value) |
| case PostgresTypes.abstime: |
| case PostgresTypes.date: |
| case PostgresTypes.daterange: |
| case PostgresTypes.int4range: |
| case PostgresTypes.int8range: |
| case PostgresTypes.money: |
| case PostgresTypes.reltime: |
| case PostgresTypes.text: |
| case PostgresTypes.time: |
| case PostgresTypes.timestamptz: |
| case PostgresTypes.timetz: |
| case PostgresTypes.tsrange: |
| case PostgresTypes.tstzrange: |
| return noop(value) |
| default: |
| |
| return noop(value) |
| } |
| } |
|
|
| const noop = (value: RecordValue): RecordValue => { |
| return value |
| } |
| export const toBoolean = (value: RecordValue): RecordValue => { |
| switch (value) { |
| case 't': |
| return true |
| case 'f': |
| return false |
| default: |
| return value |
| } |
| } |
| export const toNumber = (value: RecordValue): RecordValue => { |
| if (typeof value === 'string') { |
| const parsedValue = parseFloat(value) |
| if (!Number.isNaN(parsedValue)) { |
| return parsedValue |
| } |
| } |
| return value |
| } |
| export const toJson = (value: RecordValue): RecordValue => { |
| if (typeof value === 'string') { |
| try { |
| return JSON.parse(value) |
| } catch { |
| return value |
| } |
| } |
| return value |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export const toArray = (value: RecordValue, type: string): RecordValue => { |
| if (typeof value !== 'string') { |
| return value |
| } |
|
|
| const lastIdx = value.length - 1 |
| const closeBrace = value[lastIdx] |
| const openBrace = value[0] |
|
|
| |
| if (openBrace === '{' && closeBrace === '}') { |
| let arr |
| const valTrim = value.slice(1, lastIdx) |
|
|
| |
| try { |
| arr = JSON.parse('[' + valTrim + ']') |
| } catch (_) { |
| |
| arr = valTrim ? valTrim.split(',') : [] |
| } |
|
|
| return arr.map((val: BaseValue) => convertCell(type, val)) |
| } |
|
|
| return value |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export const toTimestampString = (value: RecordValue): RecordValue => { |
| if (typeof value === 'string') { |
| return value.replace(' ', 'T') |
| } |
|
|
| return value |
| } |
|
|
| export const httpEndpointURL = (socketUrl: string): string => { |
| const wsUrl = new URL(socketUrl) |
|
|
| wsUrl.protocol = wsUrl.protocol.replace(/^ws/i, 'http') |
|
|
| wsUrl.pathname = wsUrl.pathname |
| .replace(/\/+$/, '') |
| .replace(/\/socket\/websocket$/i, '') |
| .replace(/\/socket$/i, '') |
| .replace(/\/websocket$/i, '') |
|
|
| if (wsUrl.pathname === '' || wsUrl.pathname === '/') { |
| wsUrl.pathname = '/api/broadcast' |
| } else { |
| wsUrl.pathname = wsUrl.pathname + '/api/broadcast' |
| } |
|
|
| return wsUrl.href |
| } |
|
|