import * as ts from '@terascope/utils' ; import { FieldType } from '@terascope/types' ; import crypto from 'crypto' ; import PhoneValidator from 'awesome-phonenumber' ; import { format as dateFormat , parse } from 'date-fns' ; import { ReplaceLiteralConfig , ReplaceRegexConfig , ExtractFieldConfig } from './interfaces' ; import { isString , isValidDate , isNumber , isArray , isNumberTuple } from '../validations/field-validator' ; import { Repository , InputType } from '../interfaces' ; export const repository = { toString : { fn : toString , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , toBoolean : { fn : toBoolean , config : { } , output_type : FieldType . Boolean , primary_input_type : InputType . Any } , toUpperCase : { fn : toUpperCase , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , toLowerCase : { fn : toLowerCase , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , trim : { fn : trim , config : { char : { type : FieldType . String } } , output_type : FieldType . String , primary_input_type : InputType . String } , truncate : { fn : truncate , config : { size : { type : FieldType . Number } } , output_type : FieldType . String , primary_input_type : InputType . String } , toISDN : { fn : toISDN , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , toNumber : { fn : toNumber , config : { booleanLike : { type : FieldType . Boolean } } , output_type : FieldType . Number , primary_input_type : InputType . String } , decodeBase64 : { fn : decodeBase64 , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , encodeBase64 : { fn : encodeBase64 , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , decodeURL : { fn : decodeURL , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , encodeURL : { fn : encodeURL , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , decodeHex : { fn : decodeHex , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , encodeHex : { fn : encodeHex , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , encodeMD5 : { fn : encodeMD5 , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , encodeSHA : { fn : encodeSHA , config : { hash : { type : FieldType . String } , digest : { type : FieldType . String } } , output_type : FieldType . String , primary_input_type : InputType . String } , encodeSHA1 : { fn : encodeSHA1 , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , parseJSON : { fn : parseJSON , config : { } , output_type : FieldType . Any , primary_input_type : InputType . String } , toJSON : { fn : toJSON , config : { pretty : { type : FieldType . Boolean } } , output_type : FieldType . String , primary_input_type : InputType . Any } , toGeoPoint : { fn : toGeoPoint , config : { } , output_type : FieldType . GeoPoint , primary_input_type : InputType . String } , extract : { fn : extract , config : { regex : { type : FieldType . String } , isMultiValue : { type : FieldType . Boolean } , jexlExp : { type : FieldType . String } , start : { type : FieldType . String } , end : { type : FieldType . String } } , output_type : FieldType . Any , primary_input_type : InputType . String } , replaceRegex : { fn : replaceRegex , config : { regex : { type : FieldType . String } , replace : { type : FieldType . String } , global : { type : FieldType . String } , ignore_case : { type : FieldType . Boolean } } , output_type : FieldType . String , primary_input_type : InputType . String } , replaceLiteral : { fn : replaceLiteral , config : { search : { type : FieldType . String } , replace : { type : FieldType . String } } , output_type : FieldType . String , primary_input_type : InputType . String } , toUnixTime : { fn : toUnixTime , config : { } , output_type : FieldType . Number , primary_input_type : InputType . String } , toISO8601 : { fn : toISO8601 , config : { resolution : { type : FieldType . String , description : 'may be set to seconds | milliseconds' } } , output_type : FieldType . String , primary_input_type : InputType . String } , formatDate : { fn : formatDate , config : { format : { type : FieldType . String } , resolution : { type : FieldType . String , description : 'may be set to seconds | milliseconds' } , } , output_type : FieldType . String , primary_input_type : InputType . String } , parseDate : { fn : parseDate , config : { format : { type : FieldType . String } , } , output_type : FieldType . Date , primary_input_type : InputType . String } , trimStart : { fn : trimStart , config : { char : { type : FieldType . String } } , output_type : FieldType . String , primary_input_type : InputType . String } , trimEnd : { fn : trimEnd , config : { char : { type : FieldType . String } } , output_type : FieldType . String , primary_input_type : InputType . String } , toCamelCase : { fn : toCamelCase , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , toKebabCase : { fn : toKebabCase , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , toPascalCase : { fn : toPascalCase , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , toSnakeCase : { fn : toSnakeCase , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , toTitleCase : { fn : toTitleCase , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } , setField : { fn : setField , config : { value : { type : FieldType . Any } } , output_type : FieldType . Any , primary_input_type : InputType . String } , setDefault : { fn : setDefault , config : { value : { type : FieldType . Any } } , output_type : FieldType . Any , primary_input_type : InputType . String } , map : { fn : map , config : { fn : { type : FieldType . String } , options : { type : FieldType . Object } } , output_type : FieldType . Any , primary_input_type : InputType . Array } , splitString : { fn : splitString , config : { } , output_type : FieldType . String , primary_input_type : InputType . String } } ; type StringInput = string | string [ ] | null | undefined ; example param { * } input param { { value : any } } args returns { * } export function setDefault ( input , _parentContext , args ) { if ( ts . isNil ( input ) ) { if ( ts . isNil ( args . value ) ) throw new Error ( 'Parameter value cannot be set to undefined or null' ) ; return args . value ; } return input ; } example param { any [ ] } input param { { fn : string ; options ? : any } } args returns { any [ ] | null } export function map ( input , parentContext , args ) { if ( ts . isNil ( input ) ) return null ; if ( ! isArray ( input ) ) throw new Error ( `Input must be an array, received ${ ts . getTypeOf ( input ) }` ) ; const { fn , options } = args ; const repoConfig = repository [ fn ] ; if ( ! repoConfig ) throw new Error ( `No function ${ fn } was found in the field transform repository` ) ; return input . map ( ( data ) => repoConfig . fn ( data , parentContext , options ) ) ; } param { * } _input param { { value : any } } args returns export function setField ( _input , _parentContext , args ) { const { value } = args ; return value ; } example param { * } input returns { String | null } export function toString ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( ts . toString ) ; return ts . toString ( input ) ; } example param { * } input returns { Boolean | null } export function toBoolean ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( ts . toBoolean ) ; return ts . toBoolean ( input ) ; } example param { StringInput } input returns { String | String [ ] | null } export function toUpperCase ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( ( str ) => str . toUpperCase ( ) ) ; if ( ! isString ( input ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( input ) }` ) ; return input . toUpperCase ( ) ; } example param { StringInput } input returns { String | String [ ] | null } export function toLowerCase ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( ( str ) => str . toLowerCase ( ) ) ; if ( ! isString ( input ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( input ) }` ) ; return input . toLowerCase ( ) ; } example param { StringInput } input param { { char : string } } args returns { String | String [ ] | null } export function trim ( input , parentContext ? , args ? ) { if ( ts . isNil ( input ) ) return null ; const char = ( args ?. char && isString ( args . char ) ) ? args . char : ' ' ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( str ) => ts . trim ( str , char ) ) ; } return ts . trim ( input , char ) ; } example param { StringInput } input param { { char : string } } args returns { String | String [ ] | null } export function trimStart ( input , _parentContext ? , args ? ) { if ( ts . isNil ( input ) ) return null ; if ( args ?. char && ! isString ( args . char ) ) throw new Error ( `Parameter char must be a string, received ${ ts . getTypeOf ( input ) }` ) ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( str ) => ts . trimStart ( str , args ?. char ) ) ; } if ( ! isString ( input ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( input ) }` ) ; return ts . trimStart ( input , args ?. char ) ; } example param { StringInput } input param { { char : string } } args returns { String | String [ ] | null } export function trimEnd ( input , _parentContext ? , args ? ) { if ( ts . isNil ( input ) ) return null ; if ( args ?. char && ! isString ( args . char ) ) throw new Error ( `Parameter char must be a string, received ${ ts . getTypeOf ( input ) }` ) ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( str ) => ts . trimEnd ( str , args ?. char ) ) ; } if ( ! isString ( input ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( input ) }` ) ; return ts . trimEnd ( input , args ?. char ) ; } example param { StringInput } input param { { size : number } } args returns { String | String [ ] | null } export function truncate ( input , _parentContext , args ) { const { size } = args ; if ( ts . isNil ( input ) ) return null ; if ( ! size || ! ts . isNumber ( size ) || size <= 0 ) throw new Error ( 'Invalid size paramter for truncate' ) ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( str ) => str . slice ( 0 , size ) ) ; } if ( ! isString ( input ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( input ) }` ) ; return input . slice ( 0 , size ) ; } function parsePhoneNumber ( str ) { let testNumber = ts . toString ( str ) . trim ( ) ; if ( testNumber . charAt ( 0 ) === '0' ) testNumber = testNumber . slice ( 1 ) ; if ( testNumber . charAt ( 0 ) !== '+' ) testNumber = `+${ testNumber }` ; const fullNumber = new PhoneValidator ( testNumber ) . getNumber ( ) ; if ( fullNumber ) return String ( fullNumber ) . slice ( 1 ) ; throw Error ( 'Could not determine the incoming phone number' ) ; } example param { * } input returns { String | String [ ] | null } export function toISDN ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( parsePhoneNumber ) ; return parsePhoneNumber ( input ) ; } function convertToNumber ( input , args ? ) { let result = input ; if ( args ?. booleanLike === true && ts . isBooleanLike ( input ) ) { result = ts . toNumber ( toBoolean ( result ) ) ; } result = ts . toNumber ( result ) ; if ( Number . isNaN ( result ) ) throw new Error ( `Could not convert input of type ${ ts . getTypeOf ( input ) } to a number` ) ; return result ; } example param { * } input param { { booleanLike ? : boolean } } args returns { number | null } export function toNumber ( input , _parentContext ? , args ? ) { if ( ts . isNil ( input ) && args ?. booleanLike !== true ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => convertToNumber ( data , args ) ) ; } return convertToNumber ( input , args ) ; } example param { * } input returns { string | null } export function decodeBase64 ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => Buffer . from ( data , 'base64' ) . toString ( 'utf8' ) ) ; } return Buffer . from ( input as string , 'base64' ) . toString ( 'utf8' ) ; } example param { * } input returns { string | null } export function encodeBase64 ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => Buffer . from ( data ) . toString ( 'base64' ) ) ; } return Buffer . from ( input as any ) . toString ( 'base64' ) ; } example param { StringInput } input returns { string | null } export function decodeURL ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( decodeURIComponent ) ; if ( ! isString ( input ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( input ) }` ) ; return decodeURIComponent ( input ) ; } example param { StringInput } input returns { string | null } export function encodeURL ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( encodeURIComponent ) ; if ( ! isString ( input ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( input ) }` ) ; return encodeURIComponent ( input ) ; } example param { * } input returns { string | null } export function decodeHex ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => Buffer . from ( data , 'hex' ) . toString ( 'utf8' ) ) ; } return Buffer . from ( input as string , 'hex' ) . toString ( 'utf8' ) ; } example param { * } input returns { string | null } export function encodeHex ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => Buffer . from ( data ) . toString ( 'hex' ) ) ; } return Buffer . from ( input as string ) . toString ( 'hex' ) ; } example param { * } input returns { string | null } export function encodeMD5 ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => crypto . createHash ( 'md5' ) . update ( data ) . digest ( 'hex' ) ) ; } return crypto . createHash ( 'md5' ) . update ( input as string ) . digest ( 'hex' ) ; } example param { * } input param { * } returns { string | null } export function encodeSHA ( input , _parentContext ? , { hash = 'sha256' , digest = 'hex' } = { } ) { if ( ts . isNil ( input ) ) return null ; if ( ! [ 'ascii' , 'utf8' , 'utf16le' , 'ucs2' , 'base64' , 'latin1' , 'hex' , 'binary' ] . includes ( digest ) ) { throw new Error ( 'Parameter digest is misconfigured' ) ; } if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => crypto . createHash ( hash ) . update ( data ) . digest ( digest as any ) ) ; } return crypto . createHash ( hash ) . update ( input as string ) . digest ( digest as any ) ; } example param { * } input returns { string | null } export function encodeSHA1 ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => crypto . createHash ( 'sha1' ) . update ( data ) . digest ( 'hex' ) ) ; } return crypto . createHash ( 'sha1' ) . update ( input as string ) . digest ( 'hex' ) ; } example param { * } input returns { any | null } export function parseJSON ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => JSON . parse ( data ) ) ; } return JSON . parse ( input as string ) ; } example param { * } input param { * } returns { string | string [ ] | null } export function toJSON ( input , _parentContext ? , { pretty = false } = { } ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => { if ( pretty ) return JSON . stringify ( data , null , 2 ) ; return JSON . stringify ( data ) ; } ) ; } if ( pretty ) return JSON . stringify ( input , null , 2 ) ; return JSON . stringify ( input ) ; } example param { * } input returns { { lat : number , lon : number } | { lat : number , lon : number } [ ] | null } export function toGeoPoint ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) && ! isNumberTuple ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => ts . parseGeoPoint ( data , true ) ) ; } return ts . parseGeoPoint ( input as any , true ) ; } param { * } input param { ExtractFieldConfig } returns { string | string [ ] | null } example export function extract ( _input , _parentContext , _args ) { } example param { StringInput } input param { ReplaceRegexConfig } returns { string | string [ ] | null } export function replaceRegex ( input , _parentContext , { regex , replace , ignoreCase , global } ) { if ( ts . isNil ( input ) ) return null ; let options = '' ; if ( ignoreCase ) options += 'i' ; if ( global ) options += 'g' ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => { if ( ! isString ( data ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( input ) }` ) ; const re = new RegExp ( regex , options ) ; return data . replace ( re , replace ) ; } ) ; } if ( ! isString ( input ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( input ) }` ) ; const re = new RegExp ( regex , options ) ; return input . replace ( re , replace ) ; } example param { StringInput } input param { ReplaceLiteralConfig } returns { string | string [ ] | null } export function replaceLiteral ( input , _parentContext , { search , replace } ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => { if ( ! isString ( data ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( data ) }` ) ; return data . replace ( search , replace ) ; } ) ; } if ( ! isString ( input ) ) throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( input ) }` ) ; try { return input . replace ( search , replace ) ; } catch ( e ) { throw new Error ( `Could not replace ${ search } with ${ replace }` ) ; } } example param { * } input param { { delimiter : string } } args returns { ( string [ ] | null ) } export function splitString ( input , _parentContext ? , args ? ) { if ( ts . isNil ( input ) ) return null ; const delimiter = args ? args . delimiter : '' ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => { if ( ! ts . isString ( data ) ) { throw new Error ( `Input must be a string, or an array of string, received ${ ts . getTypeOf ( data ) }` ) ; } return data . split ( delimiter ) ; } ) ; } if ( ts . isString ( input ) ) { return input . split ( delimiter ) ; } throw new Error ( `Input must be a string or an array, got ${ ts . getTypeOf ( input ) }` ) ; } function _makeUnitTime ( input , { ms = false } = { } ) { let time ; if ( ms ) { time = ts . getTime ( input ) ; } else { time = ts . getUnixTime ( input ) ; } return time as number ; } example param { * } input param { * } returns { number | number [ ] | null } export function toUnixTime ( input , _parentContext ? , { ms = false } = { } ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => { if ( ! isValidDate ( data ) ) { throw new Error ( `Not a valid date, cannot transform ${ data } to unix time` ) ; } return _makeUnitTime ( data , { ms } ) ; } ) ; } if ( ! isValidDate ( input ) ) throw new Error ( `Not a valid date, cannot transform ${ input } to unix time` ) ; return _makeUnitTime ( input , { ms } ) ; } function _makeIso ( input , args ? ) { let value = input ; if ( isNumber ( input ) && args && args . resolution ) value *= 1000 ; return new Date ( value ) . toISOString ( ) ; } example param { * } input param { ( { resolution ? : 'seconds' | 'milliseconds' } ) } args returns { string | string [ ] | null } export function toISO8601 ( input , _parentContext ? , args ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => { if ( ! isValidDate ( data ) ) { throw new Error ( `Input is not valid date, received ${ data }` ) ; } return _makeIso ( data , args ) ; } ) ; } if ( ! isValidDate ( input ) ) { throw new Error ( `Input is not valid date, received ${ input }` ) ; } return _makeIso ( input , args ) ; } interface FormatDateConfig { format : string ; resolution ? : 'seconds' | 'milliseconds' ; } function _formatDate ( input , args ) { if ( ! isValidDate ( input ) ) { throw new Error ( 'Input is not valid date' ) ; } let value = input ; const { format , resolution } = args ; if ( ! isString ( format ) ) throw new Error ( `Invalid parameter format, must be a string, received ${ ts . getTypeOf ( input ) }` ) ; if ( isString ( value ) ) value = new Date ( value ) ; if ( isNumber ( value ) && resolution === 'seconds' ) value *= 1000 ; return dateFormat ( value , format ) ; } example param { * } input param { { format : string , resolution ? : 'seconds' | 'milliseconds' } } args returns { string | string [ ] | null } export function formatDate ( input , _parentContext , args ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => _formatDate ( data , args ) ) ; } return _formatDate ( input , args ) ; } interface ParseDateConfig { format : string ; } function _parseDate ( input , args ) { if ( ts . isNil ( input ) ) return null ; const { format } = args ; if ( ! isString ( format ) ) { throw new Error ( `Invalid parameter format, must be a string, received ${ ts . getTypeOf ( input ) }` ) ; } const parsed = parse ( input , format , new Date ( ) ) ; if ( ! ts . isValidDateInstance ( parsed ) ) { throw new Error ( 'Cannot parse date' ) ; } return parsed ; } example param { * } input param { format returns { Date | Date [ ] | null } export function parseDate ( input , _parentContext , args ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) { return input . filter ( ts . isNotNil ) . map ( ( data ) => _parseDate ( data , args ) ) ; } return _parseDate ( input , args ) ; } example param { string | string [ ] } input returns { string | string [ ] | null } export function toCamelCase ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( ts . toCamelCase ) ; return ts . toCamelCase ( input ) ; } example param { string | string [ ] } input returns { string | string [ ] | null } export function toKebabCase ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( ts . toKebabCase ) ; return ts . toKebabCase ( input ) ; } example param { string | string [ ] } input returns { string | string [ ] | null } export function toPascalCase ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( ts . toPascalCase ) ; return ts . toPascalCase ( input ) ; } example param { string | string [ ] } input returns { string | string [ ] | null } export function toSnakeCase ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( ts . toSnakeCase ) ; return ts . toSnakeCase ( input ) ; } example param { string | string [ ] } input returns { string | string [ ] | null } export function toTitleCase ( input , _parentContext ? ) { if ( ts . isNil ( input ) ) return null ; if ( isArray ( input ) ) return input . filter ( ts . isNotNil ) . map ( ts . toTitleCase ) ; return ts . toTitleCase ( input ) ; }