Skip to main content

JS Codecs & Int64

JavaScript lacks native types for Decimal, UUID, date-only, and time-only values. The TTOON codec system lets you customize how these types are represented in JS.

Default Behavior (No Codecs)

Typed TypeJS ResultWhy
intnumber (throws if overflow)Safe by default
floatnumberNative
decimalstring (stripped m)No native Decimal
datestringNo native date-only
timestringNo native time-only
datetimestringAvoids Date object quirks
uuidstringNo native UUID
binaryUint8ArrayNative
null, bool, stringnativeNative

Int64 Strategies

JS number safely represents integers only within -(2^53 - 1) to 2^53 - 1. TTOON provides three optional modes (plus the default safe-error behavior):

Default: Safe Error

import { parse } from '@ttoon/shared';

parse('9223372036854775807'); // throws: outside safe integer range

No silent precision loss. This is the safest default.

BigInt Mode

import { use, intBigInt } from '@ttoon/shared';

await use({ int: intBigInt() });

const value = parse('9223372036854775807');
console.log(value); // 9223372036854775807n (BigInt)

All integers become BigInt, including small ones.

NaN Mode

import { use, intNumber } from '@ttoon/shared';

await use({ int: intNumber({ overflow: 'nan' }) });

const value = parse('9223372036854775807');
console.log(value); // NaN

When overflow happens, intNumber() returns NaN instead of throwing.

Lossy Mode

import { use, intNumber } from '@ttoon/shared';

await use({ int: intNumber({ overflow: 'lossy' }) });

const value = parse('9223372036854775807');
console.log(value); // 9223372036854776000 (precision lost)

Explicitly accepts precision loss. Use only when you know your data fits or precision doesn't matter.

Custom Codecs

Decimal Codec Example

import Decimal from 'decimal.js';
import { use, type Codec } from '@ttoon/shared';

const decimalCodec: Codec<Decimal> = {
type: 'decimal',
fromPayload(payload) {
if (typeof payload !== 'string') throw new Error('expected decimal payload');
return new Decimal(payload.slice(0, -1)); // strip 'm' suffix
},
toPayload(value) {
return `${value.toString()}m`;
},
is(value): value is Decimal {
return value instanceof Decimal;
},
};

await use({ decimal: decimalCodec });

After registration:

const data = parse('price: 123.45m');
// data.price is now a Decimal instance, not a string

Codec Interface

interface Codec<T> {
type?: CodecType; // 'int' | 'decimal' | 'date' | 'time' | 'datetime' | 'uuid' | 'binary'
fromPayload(payload: CodecPayload): T; // payload → JS value
toPayload(value: T): CodecPayload; // JS value → payload
is(value: unknown): value is T; // Type guard
}

Registering Multiple Codecs

await use({
int: intBigInt(),
decimal: decimalCodec,
date: dateCodec,
uuid: uuidCodec,
});

Per-Call Override

const data = parse(text, {
codecs: { int: intBigInt() }, // override global for this call only
});

Codec Scope

Codecs affect JS object-path value mapping in parse(), stringify(), toTjson(), and object-path streaming readers/writers. They do not affect:

  • T-TOON / T-JSON syntax (the text format is always the same)
  • Rust or Python type behavior
  • Arrow schema inference (readArrow() / stringifyArrow())
  • Transcode operations (tjsonToTtoon() / ttoonToTjson())

This is by design — codecs are a JS-specific adaptation layer, not a format-level feature.

Streaming with Codecs

Object-path streaming readers and writers also respect codecs:

import { streamRead, StreamSchema, types, use, intBigInt } from '@ttoon/shared';

await use({ int: intBigInt() });

const schema = new StreamSchema({ id: types.int, name: types.string });

for await (const row of streamRead(source, { schema })) {
console.log(typeof row.id); // 'bigint'
}

Arrow streaming is not affected by codecs — Arrow uses native types.

Recommendations

ScenarioStrategy
Data with guaranteed small integersDefault (no codec)
Data with potential int64 valuesintBigInt()
Financial/accounting dataCustom decimal codec with decimal.js or big.js
Date-heavy applicationsCustom date/time/datetime codecs
Performance-critical Arrow pipelinesSkip codecs, use Arrow path