import { RichValue, RichValue_NullValue } from '@sentio/protos' import type { String, Int, Float, ID, Bytes, Timestamp, Boolean } from './types.js' import { BigDecimal } from '@sentio/bigdecimal' import { toBigInteger, toBigDecimal } from '../core/numberish.js' export interface ValueConverter { from: (value: T) => RichValue to: (value: RichValue) => T required?: boolean isArray?: boolean isRelation?: boolean relationName?: string } export const ValueRequiredError = new Error('Value is required but received null or undefined') export function required_(converter: ValueConverter): ValueConverter { const { from, to, ...rest } = converter return { from: (value: T | undefined) => { if (value == null) { throw ValueRequiredError } return from(value) }, to: (value: RichValue) => { if (value == null || value.nullValue) { throw ValueRequiredError } return to(value)! }, ...rest, required: true } } export function array_(converter: ValueConverter): ValueConverter { return { from: (value: T[]) => { return { listValue: { values: value.map(converter.from) } } }, to: (value: RichValue) => { return value.listValue?.values.map(converter.to) || [] }, isArray: true, isRelation: converter.isRelation, relationName: converter.relationName } } export function enumerate_(values: Record): ValueConverter { return { from: (value?: T) => { if (value == null) { return { nullValue: RichValue_NullValue.NULL_VALUE } } return { stringValue: values[value] } }, to(v: RichValue): T { return v.stringValue as T } } } export function objectId_(entityName: string): ValueConverter { return { from: (value: T | ID) => { if (typeof value == 'string') { return { stringValue: value } } if (value instanceof Uint8Array) { return { stringValue: `0x${Buffer.from(value).toString('hex')}` } } if (typeof value == 'object') { const entity = value as any return { stringValue: entity.id.toString() } } return { nullValue: RichValue_NullValue.NULL_VALUE } }, to(v) { return v.stringValue as T | ID }, isRelation: true, relationName: entityName } } export const StringConverter: ValueConverter = { from: (value?: String) => { if (value == null) { return { nullValue: RichValue_NullValue.NULL_VALUE } } return { stringValue: value } }, to(v) { return v.stringValue } } export const IntConverter: ValueConverter = { from: (value?: Int) => { if (value == null) { return { nullValue: RichValue_NullValue.NULL_VALUE } } return { intValue: Math.floor(value) } }, to(v) { return v.intValue as Int } } export const Int8Converter: ValueConverter = { from: (value?: bigint) => { if (value == null) { return { nullValue: RichValue_NullValue.NULL_VALUE } } return { int64Value: BigInt(value) } }, to(v) { return v.int64Value } } export const FloatConverter: ValueConverter = { from: (value?: Float) => { if (value == null) { return { nullValue: RichValue_NullValue.NULL_VALUE } } return { floatValue: value } }, to(v) { return v.floatValue } } export const BooleanConverter: ValueConverter = { from: (value?: Boolean) => { if (value == null) { return { nullValue: RichValue_NullValue.NULL_VALUE } } return { boolValue: value } }, to(v) { return v.boolValue } } export const TimestampConverter: ValueConverter = { from: (value: Timestamp | undefined) => { if (value == null) { return { nullValue: RichValue_NullValue.NULL_VALUE } } return { timestampValue: value } }, to(v) { return v.timestampValue } } export const BytesConverter: ValueConverter = { from: (value?: Bytes) => { if (value == null) { return { nullValue: RichValue_NullValue.NULL_VALUE } } return { bytesValue: value } }, to(v) { return v.bytesValue } } export const IDConverter: ValueConverter = { from(value: ID | undefined): RichValue { if (typeof value == 'string') { return { stringValue: value } } if (value instanceof Uint8Array) { return { stringValue: `0x${Buffer.from(value).toString('hex')}` } } return { nullValue: RichValue_NullValue.NULL_VALUE } }, to(value: RichValue): ID | undefined { if (value.stringValue) { return value.stringValue as ID } if (value.bytesValue) { const v = `0x${Buffer.from(value.bytesValue).toString('hex')}` return v as ID } return undefined } } export const BigDecimalConverter: ValueConverter = { from: (value?: BigDecimal): RichValue => { if (value == null) { return { nullValue: RichValue_NullValue.NULL_VALUE } } return { bigdecimalValue: toBigDecimal(value) } }, to(v) { const d = v.bigdecimalValue if (d) { const i = bytesToBigInt(d.value!.data) let ret = new BigDecimal(i.toString()) if (d.exp < 0) { ret = ret.dividedBy(new BigDecimal(10).pow(-d.exp)) } else { ret = ret.multipliedBy(new BigDecimal(10).pow(d.exp)) } return ret.multipliedBy(d.value?.negative ? -1 : 1) } return undefined } } export const BigIntConverter: ValueConverter = { from: (value?: bigint) => { if (value == null) { return { nullValue: RichValue_NullValue.NULL_VALUE } } return { bigintValue: toBigInteger(value) } }, to(v) { if (v.bigintValue) { let res = bytesToBigInt(v.bigintValue?.data) if (v.bigintValue.negative) { res = -res } return res } return undefined } } export function bytesToBigInt(bytes: Uint8Array) { let intValue = BigInt(0) for (let i = 0; i < bytes.length; i++) { intValue = intValue * BigInt(256) + BigInt(bytes[i]) } return intValue } export const TypeConverters: Record> = { BigDecimal: BigDecimalConverter, BigInt: BigIntConverter, String: StringConverter, Boolean: BooleanConverter, Uint8Array: BytesConverter, ID: IDConverter, Bytes: BytesConverter, Int: IntConverter, Int8: Int8Converter, Float: FloatConverter, Timestamp: TimestampConverter }