@genrojs/tytx 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,128 @@
1
+ // Copyright 2025 Softwell S.r.l. - Licensed under Apache License 2.0
2
+ /**
3
+ * TYTX Decoding - TYTX format to JavaScript objects.
4
+ *
5
+ * Supports multiple transports: json, xml, msgpack.
6
+ */
7
+
8
+ import { fromXml } from './xml.js';
9
+ import { fromMsgpack } from './msgpack.js';
10
+ import { walk, rawDecode } from './utils.js';
11
+
12
+ const TYTX_MARKER = '::JS';
13
+
14
+ /**
15
+ * Filter for string values.
16
+ * @param {any} v
17
+ * @returns {boolean}
18
+ */
19
+ function isString(v) {
20
+ return typeof v === 'string';
21
+ }
22
+
23
+ /**
24
+ * Decode a TYTX JSON string to JavaScript objects (internal).
25
+ *
26
+ * @param {string} data - JSON string with ::JS suffix (struct) or ::T suffix (scalar)
27
+ * @returns {any} JavaScript object with typed values hydrated
28
+ */
29
+ function _fromJson(data) {
30
+ // Try rawDecode first (scalar with type suffix)
31
+ const [decoded, value] = rawDecode(data);
32
+ if (decoded) {
33
+ return value;
34
+ }
35
+
36
+ let jsonData = data;
37
+ if (jsonData.endsWith('::JS')) {
38
+ jsonData = jsonData.slice(0, -4);
39
+ }
40
+
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(jsonData);
44
+ } catch {
45
+ return data;
46
+ }
47
+
48
+ return walk(parsed, _decodeItem, isString);
49
+ }
50
+
51
+ /**
52
+ * Decode a single item with TYTX suffix.
53
+ * @param {string} s
54
+ * @returns {any}
55
+ */
56
+ function _decodeItem(s) {
57
+ if (!s.includes('::')) {
58
+ return s;
59
+ }
60
+ return rawDecode(s)[1];
61
+ }
62
+
63
+ /**
64
+ * Decode a TYTX XML string to JavaScript objects (internal).
65
+ * @param {string} data
66
+ * @returns {any}
67
+ */
68
+ function _fromXml(data) {
69
+ const result = fromXml(data);
70
+ // If result is a string with TYTX suffix, hydrate it via JSON decoder
71
+ if (typeof result === 'string') {
72
+ return fromTytx(result);
73
+ }
74
+ return result;
75
+ }
76
+
77
+ /**
78
+ * Decode TYTX MessagePack bytes to JavaScript objects (internal).
79
+ * @param {Uint8Array} data
80
+ * @returns {any}
81
+ */
82
+ function _fromMsgpack(data) {
83
+ return fromMsgpack(data);
84
+ }
85
+
86
+ /**
87
+ * Decode TYTX format to JavaScript objects.
88
+ *
89
+ * @param {string|Uint8Array|null} data - Encoded data (string for json/xml, Uint8Array for msgpack), or null
90
+ * @param {string|null} transport - Input format: "json", "xml", "msgpack", or null
91
+ * @returns {any} JavaScript object with typed values hydrated, or null if data is null
92
+ *
93
+ * @example
94
+ * fromTytx('{"price": "100.50::N"}::JS')
95
+ * // {"price": Decimal("100.50")}
96
+ *
97
+ * fromTytx('<root>100::N</root>', "xml")
98
+ * // {"root": {"attrs": {}, "value": Decimal("100")}}
99
+ */
100
+ function fromTytx(data, transport = null) {
101
+ if (data === null) {
102
+ return null;
103
+ }
104
+
105
+ if (transport === null || transport === 'json') {
106
+ let jsonData = data;
107
+ if (transport === 'json' && data.startsWith('"') && data.endsWith('"')) {
108
+ jsonData = data.slice(1, -1); // Remove surrounding quotes (TYTX-wrapped)
109
+ }
110
+ return _fromJson(jsonData);
111
+ } else if (transport === 'xml') {
112
+ return _fromXml(data);
113
+ } else if (transport === 'msgpack') {
114
+ return _fromMsgpack(data);
115
+ } else {
116
+ throw new Error(`Unknown transport: ${transport}`);
117
+ }
118
+ }
119
+
120
+ export {
121
+ fromTytx,
122
+ TYTX_MARKER,
123
+ isString,
124
+ _fromJson,
125
+ _fromXml,
126
+ _fromMsgpack,
127
+ _decodeItem,
128
+ };
@@ -0,0 +1,162 @@
1
+ // Copyright 2025 Softwell S.r.l. - Licensed under Apache License 2.0
2
+ /**
3
+ * TYTX Encoding - JavaScript objects to TYTX format.
4
+ *
5
+ * Supports multiple transports: json, xml, msgpack.
6
+ */
7
+
8
+ import { toQs } from './qs.js';
9
+ import { toXml } from './xml.js';
10
+ import { toMsgpack } from './msgpack.js';
11
+ import { rawEncode } from './utils.js';
12
+ import { getTypeEntry } from './registry.js';
13
+ import { msgpack } from './platform/dependencies.js';
14
+
15
+ /**
16
+ * Pre-process a value recursively, converting typed values to TYTX strings.
17
+ * Returns [processedValue, hasSpecial].
18
+ *
19
+ * @param {any} value
20
+ * @returns {[any, boolean]}
21
+ */
22
+ function _preprocessValue(value) {
23
+ // Check if value is a typed value that needs encoding
24
+ const entry = getTypeEntry(value);
25
+ if (entry !== null) {
26
+ const [suffix, serializer, jsonNative] = entry;
27
+ if (!jsonNative) {
28
+ return [`${serializer(value)}::${suffix}`, true];
29
+ }
30
+ // jsonNative types (bool, int, float) are kept as-is
31
+ return [value, false];
32
+ }
33
+
34
+ // Handle arrays
35
+ if (Array.isArray(value)) {
36
+ let hasSpecial = false;
37
+ const result = value.map(item => {
38
+ const [processed, special] = _preprocessValue(item);
39
+ if (special) hasSpecial = true;
40
+ return processed;
41
+ });
42
+ return [result, hasSpecial];
43
+ }
44
+
45
+ // Handle objects (but not null)
46
+ if (value !== null && typeof value === 'object') {
47
+ let hasSpecial = false;
48
+ const result = {};
49
+ for (const [k, v] of Object.entries(value)) {
50
+ const [processed, special] = _preprocessValue(v);
51
+ if (special) hasSpecial = true;
52
+ result[k] = processed;
53
+ }
54
+ return [result, hasSpecial];
55
+ }
56
+
57
+ // Primitive values (string, number, boolean, null)
58
+ return [value, false];
59
+ }
60
+
61
+ /**
62
+ * Encode a JavaScript value to TYTX JSON string (internal).
63
+ *
64
+ * @param {any} value - JavaScript object to encode
65
+ * @param {boolean} forceSuffix - If true, add suffix for all types (int/bool/float)
66
+ * @returns {string} JSON string. For dict/list with typed values: adds ::JS suffix.
67
+ */
68
+ function _toJson(value, forceSuffix = false) {
69
+ const [encoded, result] = rawEncode(value, forceSuffix);
70
+ if (encoded) {
71
+ return result;
72
+ }
73
+
74
+ const [processed, hasSpecial] = _preprocessValue(value);
75
+ const jsonResult = JSON.stringify(processed);
76
+
77
+ if (hasSpecial) {
78
+ return `${jsonResult}::JS`;
79
+ }
80
+ return jsonResult;
81
+ }
82
+
83
+ /**
84
+ * Encode a JavaScript value to raw JSON string (no TYTX suffix).
85
+ * @param {any} value
86
+ * @returns {string}
87
+ */
88
+ function _toRawJson(value) {
89
+ return JSON.stringify(value);
90
+ }
91
+
92
+ /**
93
+ * Encode a JavaScript value to raw MessagePack bytes (no TYTX processing).
94
+ * @param {any} value
95
+ * @returns {Uint8Array}
96
+ */
97
+ function _toRawMsgpack(value) {
98
+ if (!msgpack) throw new Error('@msgpack/msgpack is required for MessagePack support');
99
+ return msgpack.encode(value);
100
+ }
101
+
102
+ /**
103
+ * Encode a JavaScript value to TYTX format.
104
+ *
105
+ * @param {any} value - JavaScript object to encode
106
+ * @param {string|null} transport - Output format: "json", "xml", "msgpack", or null
107
+ * @param {Object} options - Options object
108
+ * @param {boolean} options.raw - If true, output raw format without TYTX type suffixes
109
+ * @param {boolean} options.qs - If true, output as query string format (flat object or array only)
110
+ * @param {boolean} options._forceSuffix - Internal: force suffix for all types
111
+ * @returns {string|Uint8Array} Encoded data (string for json/xml, Uint8Array for msgpack)
112
+ *
113
+ * @example
114
+ * toTytx({"price": createDecimal("100.50")})
115
+ * // '{"price": "100.50::N"}::JS'
116
+ *
117
+ * toTytx({"price": 100.50}, null, { raw: true })
118
+ * // '{"price": 100.5}'
119
+ *
120
+ * toTytx({"alfa": 33, "date": new Date(Date.UTC(2025, 11, 14))}, null, { qs: true })
121
+ * // 'alfa=33::L&date=2025-12-14::D::QS'
122
+ *
123
+ * toTytx({"root": {"value": createDecimal("100")}}, "xml")
124
+ * // '<?xml version="1.0" ?><root>100::N</root>'
125
+ */
126
+ function toTytx(value, transport = null, { raw = false, qs = false, _forceSuffix = false } = {}) {
127
+ if (qs) {
128
+ return `${toQs(value)}::QS`;
129
+ }
130
+
131
+ if (raw) {
132
+ if (transport === null || transport === 'json') {
133
+ return _toRawJson(value);
134
+ } else if (transport === 'msgpack') {
135
+ return _toRawMsgpack(value);
136
+ } else if (transport === 'xml') {
137
+ throw new Error('raw=true is not supported for XML transport');
138
+ } else {
139
+ throw new Error(`Unknown transport: ${transport}`);
140
+ }
141
+ }
142
+
143
+ if (transport === null || transport === 'json') {
144
+ const result = _toJson(value, _forceSuffix);
145
+ if (transport === 'json') {
146
+ return `"${result}"`;
147
+ }
148
+ return result;
149
+ } else if (transport === 'xml') {
150
+ const result = toXml(value);
151
+ return `<?xml version="1.0" ?><tytx_root>${result}</tytx_root>`;
152
+ } else if (transport === 'msgpack') {
153
+ return toMsgpack(value);
154
+ } else {
155
+ throw new Error(`Unknown transport: ${transport}`);
156
+ }
157
+ }
158
+
159
+ export {
160
+ toTytx,
161
+ _toJson,
162
+ };
package/js/src/http.js ADDED
@@ -0,0 +1,101 @@
1
+ // Copyright 2025 Softwell S.r.l. - Licensed under Apache License 2.0
2
+ /**
3
+ * TYTX HTTP utilities.
4
+ *
5
+ * Client-side functions to make HTTP requests with TYTX serialization.
6
+ */
7
+
8
+ import { toTytx } from './encode.js';
9
+ import { fromTytx } from './decode.js';
10
+
11
+ const CONTENT_TYPES = {
12
+ json: 'application/json',
13
+ xml: 'application/xml',
14
+ msgpack: 'application/msgpack',
15
+ };
16
+
17
+ /**
18
+ * Get transport from Content-Type header.
19
+ * @param {string} contentType
20
+ * @returns {'json'|'xml'|'msgpack'|null}
21
+ */
22
+ function getTransport(contentType) {
23
+ if (!contentType) return null;
24
+ const ct = contentType.toLowerCase();
25
+ if (ct.includes('json')) return 'json';
26
+ if (ct.includes('xml')) return 'xml';
27
+ if (ct.includes('msgpack')) return 'msgpack';
28
+ return null;
29
+ }
30
+
31
+ /**
32
+ * Fetch with TYTX serialization/deserialization.
33
+ *
34
+ * @param {string} url - URL to fetch
35
+ * @param {Object} options - Fetch options
36
+ * @param {any} [options.body] - Data to send (will be serialized with toTytx)
37
+ * @param {'json'|'xml'|'msgpack'} [options.transport='json'] - Transport format
38
+ * @param {string} [options.method='GET'] - HTTP method
39
+ * @param {Object} [options.headers={}] - Additional headers
40
+ * @returns {Promise<any>} Deserialized response data
41
+ */
42
+ async function fetchTytx(url, options = {}) {
43
+ const {
44
+ body,
45
+ transport = 'json',
46
+ method = body !== undefined ? 'POST' : 'GET',
47
+ headers = {},
48
+ ...fetchOptions
49
+ } = options;
50
+
51
+ const requestHeaders = {
52
+ 'X-TYTX-Transport': transport,
53
+ ...headers,
54
+ };
55
+
56
+ // Serialize body if provided
57
+ let requestBody;
58
+ if (body !== undefined) {
59
+ requestHeaders['Content-Type'] = CONTENT_TYPES[transport];
60
+ const encoded = toTytx(body, transport);
61
+ if (transport === 'msgpack') {
62
+ requestBody = encoded;
63
+ } else {
64
+ requestBody = encoded;
65
+ }
66
+ }
67
+
68
+ // Make request
69
+ const response = await fetch(url, {
70
+ method,
71
+ headers: requestHeaders,
72
+ body: requestBody,
73
+ ...fetchOptions,
74
+ });
75
+
76
+ if (!response.ok) {
77
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
78
+ }
79
+
80
+ // Determine response transport from Content-Type
81
+ const responseContentType = response.headers.get('Content-Type') || '';
82
+ const responseTransport = getTransport(responseContentType) || transport;
83
+
84
+ // Read and deserialize response
85
+ let responseData;
86
+ if (responseTransport === 'msgpack') {
87
+ const buffer = await response.arrayBuffer();
88
+ responseData = fromTytx(Buffer.from(buffer), responseTransport);
89
+ } else {
90
+ const text = await response.text();
91
+ responseData = fromTytx(text, responseTransport);
92
+ }
93
+
94
+ return responseData;
95
+ }
96
+
97
+ export {
98
+ fetchTytx,
99
+ getTransport,
100
+ CONTENT_TYPES,
101
+ };
@@ -0,0 +1,38 @@
1
+ // Copyright 2026 Softwell S.r.l. - SPDX-License-Identifier: Apache-2.0
2
+ /** Supported wire transports; null selects suffix-encoded typed text. */
3
+ export type Transport = 'json' | 'xml' | 'msgpack' | null;
4
+ export type DecimalLibrary = 'decimal.js' | 'big.js' | 'number';
5
+ export interface EncodeOptions { raw?: boolean; qs?: boolean; _forceSuffix?: boolean; }
6
+ /** Serialize a value. MessagePack returns bytes; other transports return text. */
7
+ export function toTytx(value: unknown, transport?: Transport, options?: EncodeOptions): string | Uint8Array;
8
+ /** Decode typed text, XML or MessagePack. Registered decoders determine the result. */
9
+ export function fromTytx(data: string | Uint8Array | null, transport?: Transport): unknown;
10
+ export interface FetchOptions extends Omit<RequestInit, 'body' | 'headers'> {
11
+ body?: unknown;
12
+ transport?: Exclude<Transport, null>;
13
+ headers?: Record<string, string>;
14
+ }
15
+ export function fetchTytx(url: string, options?: FetchOptions): Promise<unknown>;
16
+ export function getTransport(contentType: string | null | undefined): Transport;
17
+ export const CONTENT_TYPES: { json: string; xml: string; msgpack: string };
18
+ export function isDecimal(value: unknown): boolean;
19
+ /** Returns an instance of the selected decimal library, or boxed Number. */
20
+ export function createDecimal(value: string | number): { toString(): string; valueOf(): string | number };
21
+ export function setDecimalLibrary(name: DecimalLibrary): void;
22
+ export function getDecimalLibrary(): DecimalLibrary;
23
+ export function registerType<T>(cls: new (...args: any[]) => T, suffix: string,
24
+ serializer: (value: T) => string, deserializer: (value: string) => T,
25
+ jsonNative?: boolean): void;
26
+ export interface RegisteredClass<T extends { toTytx(): string }> {
27
+ new (...args: any[]): T;
28
+ tytxSuffix: string;
29
+ tytxJsonNative?: boolean;
30
+ fromTytx(value: string): T;
31
+ }
32
+ export function registerClass<T extends { toTytx(): string }, C extends RegisteredClass<T>>(cls: C): C;
33
+ export function getRegisteredType(suffix: string): Function | null;
34
+ /** Stores the subtype dictionary of a suffix, replacing the previous one. */
35
+ export function setSubtypeDict(suffix: string, subtypes: Record<string, unknown>): void;
36
+ /** Returns the subtype dictionary of a suffix, or {} if none was set. */
37
+ export function getSubtypeDict(suffix: string): Record<string, unknown>;
38
+ export const __version__: string;
@@ -0,0 +1,63 @@
1
+ /* @ts-self-types="./index.d.ts" */
2
+ // Copyright 2025 Softwell S.r.l. - Licensed under Apache License 2.0
3
+ /**
4
+ * TYTX Base - Typed Text Protocol for Scalar Types
5
+ *
6
+ * Minimal implementation supporting:
7
+ * - Scalar types: Decimal, date, datetime, time, bool, int
8
+ * - Encoders/Decoders: JSON, XML, MessagePack
9
+ *
10
+ * Usage:
11
+ * import { toTytx, fromTytx, fetchTytx } from '@genrojs/tytx';
12
+ * import Big from 'big.js';
13
+ *
14
+ * // Encode
15
+ * const data = {"price": new Big("100.50"), "date": new Date(Date.UTC(2025, 0, 15))};
16
+ * const jsonStr = toTytx(data);
17
+ * // '{"price": "100.50::N", "date": "2025-01-15::D"}::JS'
18
+ *
19
+ * // Decode
20
+ * const result = fromTytx(jsonStr);
21
+ * // {"price": Big("100.50"), "date": Date}
22
+ */
23
+
24
+ import {
25
+ isDecimal,
26
+ createDecimal,
27
+ setDecimalLibrary,
28
+ getDecimalLibrary,
29
+ registerType,
30
+ registerClass,
31
+ getRegisteredType,
32
+ setSubtypeDict,
33
+ getSubtypeDict,
34
+ } from './registry.js';
35
+
36
+ import { toTytx } from './encode.js';
37
+ import { fromTytx } from './decode.js';
38
+ import { fetchTytx, getTransport, CONTENT_TYPES } from './http.js';
39
+
40
+ const __version__ = '0.16.1';
41
+
42
+ export {
43
+ // Core API
44
+ toTytx,
45
+ fromTytx,
46
+ // HTTP utilities
47
+ fetchTytx,
48
+ getTransport,
49
+ CONTENT_TYPES,
50
+ // Decimal utilities
51
+ isDecimal,
52
+ createDecimal,
53
+ setDecimalLibrary,
54
+ getDecimalLibrary,
55
+ // Custom type registration
56
+ registerType,
57
+ registerClass,
58
+ getRegisteredType,
59
+ setSubtypeDict,
60
+ getSubtypeDict,
61
+ // Version
62
+ __version__,
63
+ };
@@ -0,0 +1,185 @@
1
+ // Copyright 2025 Softwell S.r.l. - Licensed under Apache License 2.0
2
+ /**
3
+ * TYTX MessagePack Encoding/Decoding.
4
+ *
5
+ * Uses MessagePack extension types to carry TYTX type information directly in the protocol:
6
+ * - Ext -1: datetime (native Timestamp, handled by @msgpack/msgpack)
7
+ * - Ext 1: Decimal (payload: UTF-8 string, e.g. "100.50")
8
+ * - Ext 2: date (payload: ISO "YYYY-MM-DD")
9
+ * - Ext 3: time (payload: ISO "HH:MM:SS.mmm")
10
+ * - Ext 4: registered custom type (payload: UTF-8 "SUFFIX:serialized",
11
+ * split at the first ":"; unknown suffixes on the receiving side
12
+ * degrade to the string "serialized::SUFFIX", like the json path)
13
+ */
14
+
15
+ import {
16
+ getDateType,
17
+ createDecimal,
18
+ isDecimal,
19
+ getCustomTypeEntry,
20
+ SUFFIX_TO_TYPE,
21
+ } from './registry.js';
22
+ import { msgpack } from './platform/dependencies.js';
23
+
24
+ // Check for @msgpack/msgpack availability
25
+ const HAS_MSGPACK = msgpack !== null;
26
+
27
+ function _checkMsgpack() {
28
+ if (!HAS_MSGPACK) {
29
+ throw new Error(
30
+ '@msgpack/msgpack is required for MessagePack support. ' +
31
+ 'Install with: npm install @msgpack/msgpack'
32
+ );
33
+ }
34
+ }
35
+
36
+ // Build the ExtensionCodec once at module load (if msgpack is available).
37
+ // The encode/decode functions close over isDecimal/createDecimal/getDateType
38
+ // which read the current decimal library state at call time.
39
+ const _extensionCodec = _buildCodec();
40
+
41
+ function _buildCodec() {
42
+ if (!HAS_MSGPACK) {
43
+ return null;
44
+ }
45
+
46
+ const enc = new TextEncoder();
47
+ const dec = new TextDecoder();
48
+ const codec = new msgpack.ExtensionCodec();
49
+
50
+ // Override built-in Timestamp encoder (-1) to skip date-only and time-only
51
+ // Date objects so they can be handled by ext 2 and ext 3 below.
52
+ // DHZ datetimes fall through to the native Timestamp encoding.
53
+ codec.register({
54
+ type: -1,
55
+ encode: (v) => {
56
+ if (v instanceof Date) {
57
+ const dt = getDateType(v);
58
+ if (dt === 'D' || dt === 'H') {
59
+ return null; // let custom encoders (2, 3) handle these
60
+ }
61
+ }
62
+ return msgpack.encodeTimestampExtension(v);
63
+ },
64
+ decode: (data) => msgpack.decodeTimestampExtension(data),
65
+ });
66
+
67
+ // Ext 1: Decimal
68
+ codec.register({
69
+ type: 1,
70
+ encode: (v) => {
71
+ if (isDecimal(v)) {
72
+ return enc.encode(v.toString());
73
+ }
74
+ return null;
75
+ },
76
+ decode: (data) => createDecimal(dec.decode(data)),
77
+ });
78
+
79
+ // Ext 2: date-only (midnight UTC)
80
+ codec.register({
81
+ type: 2,
82
+ encode: (v) => {
83
+ if (v instanceof Date && getDateType(v) === 'D') {
84
+ const y = v.getUTCFullYear();
85
+ const m = String(v.getUTCMonth() + 1).padStart(2, '0');
86
+ const d = String(v.getUTCDate()).padStart(2, '0');
87
+ return enc.encode(`${y}-${m}-${d}`);
88
+ }
89
+ return null;
90
+ },
91
+ decode: (data) => new Date(dec.decode(data) + 'T00:00:00.000Z'),
92
+ });
93
+
94
+ // Ext 3: time-only (epoch-based Date)
95
+ codec.register({
96
+ type: 3,
97
+ encode: (v) => {
98
+ if (v instanceof Date && getDateType(v) === 'H') {
99
+ const h = String(v.getUTCHours()).padStart(2, '0');
100
+ const m = String(v.getUTCMinutes()).padStart(2, '0');
101
+ const s = String(v.getUTCSeconds()).padStart(2, '0');
102
+ const ms = String(v.getUTCMilliseconds()).padStart(3, '0');
103
+ return enc.encode(`${h}:${m}:${s}.${ms}`);
104
+ }
105
+ return null;
106
+ },
107
+ decode: (data) => {
108
+ const str = dec.decode(data);
109
+ // Accept "HH:MM:SS", "HH:MM:SS.mmm", or "HH:MM:SS.ffffff"
110
+ const dotIdx = str.indexOf('.');
111
+ const timePart = dotIdx >= 0 ? str.substring(0, dotIdx) : str;
112
+ const fracStr = dotIdx >= 0 ? str.substring(dotIdx + 1) : '0';
113
+ const [h, m, s] = timePart.split(':');
114
+ // Truncate fractional part to 3 digits (milliseconds)
115
+ const ms = Math.round(+fracStr.substring(0, 3));
116
+ return new Date(Date.UTC(1970, 0, 1, +h, +m, +s, ms));
117
+ },
118
+ });
119
+
120
+ // Ext 4: registered custom type ("SUFFIX:serialized", first-colon split)
121
+ codec.register({
122
+ type: 4,
123
+ encode: (v) => {
124
+ const entry = getCustomTypeEntry(v);
125
+ if (entry !== null) {
126
+ const [suffix, serializer] = entry;
127
+ return enc.encode(`${suffix}:${serializer(v)}`);
128
+ }
129
+ return null;
130
+ },
131
+ decode: (data) => {
132
+ const str = dec.decode(data);
133
+ const idx = str.indexOf(':');
134
+ const suffix = str.slice(0, idx);
135
+ const payload = str.slice(idx + 1);
136
+ const entry = SUFFIX_TO_TYPE[suffix];
137
+ if (entry !== undefined) {
138
+ const [, deserializer] = entry;
139
+ return deserializer(payload);
140
+ }
141
+ // Unknown suffix on the receiving side: degrade to the same
142
+ // pass-through string rawDecode gives on the json path.
143
+ return `${payload}::${suffix}`;
144
+ },
145
+ });
146
+
147
+ return codec;
148
+ }
149
+
150
+ /**
151
+ * Encode a JavaScript value to TYTX MessagePack bytes.
152
+ *
153
+ * @param {any} value - JavaScript object to encode
154
+ * @returns {Uint8Array} MessagePack bytes with TYTX types as extension types
155
+ *
156
+ * @example
157
+ * toMsgpack({"price": createDecimal("100.50")})
158
+ * // Uint8Array[...] // MessagePack bytes
159
+ */
160
+ function toMsgpack(value) {
161
+ _checkMsgpack();
162
+ return msgpack.encode(value, { extensionCodec: _extensionCodec });
163
+ }
164
+
165
+ /**
166
+ * Decode TYTX MessagePack bytes to JavaScript objects.
167
+ *
168
+ * @param {Uint8Array} data - MessagePack bytes
169
+ * @returns {any} JavaScript object with TYTX extension types hydrated
170
+ *
171
+ * @example
172
+ * fromMsgpack(packedBytes)
173
+ * // {"price": Decimal("100.50")}
174
+ */
175
+ function fromMsgpack(data) {
176
+ _checkMsgpack();
177
+ return msgpack.decode(data, { extensionCodec: _extensionCodec });
178
+ }
179
+
180
+ export {
181
+ toMsgpack,
182
+ fromMsgpack,
183
+ HAS_MSGPACK,
184
+ _checkMsgpack,
185
+ };
@@ -0,0 +1,12 @@
1
+ // Copyright 2026 Softwell S.r.l. - SPDX-License-Identifier: Apache-2.0
2
+ // One statically analyzable codec graph for Node, Bun and browser bundlers.
3
+ import Decimal from 'decimal.js';
4
+ import Big from 'big.js';
5
+ import * as messagePack from '@msgpack/msgpack';
6
+ import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
7
+
8
+ export const DecimalJS = Decimal;
9
+ export const BigJS = Big;
10
+ export const msgpack = messagePack;
11
+ export const NodeDOMParser = DOMParser;
12
+ export const NodeXMLSerializer = XMLSerializer;