Skip to content

Commit 625d0ce

Browse files
authored
add bigint support to msgpack codec (#341)
## Why we are seeing `failed to send message: Unrecognized object: [object BigInt]` in production from msgpack docs: ``` This library does not handle BigInt by default, but you have two options to handle it: 1. Set useBigInt64: true to map bigint to MessagePack's int64/uint64 2. Define a custom ExtensionCodec to map bigint to a MessagePack's extension type ``` ## What changed - do option 2 of the above for binary codec - add custom extension for json codec ## Versioning - [ ] Breaking protocol change - [ ] Breaking ts/js API change <!-- Kind reminder to add tests and updated documentation if needed -->
1 parent b399110 commit 625d0ce

File tree

5 files changed

+47
-7
lines changed

5 files changed

+47
-7
lines changed

codec/binary.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,44 @@
1-
import { decode, encode } from '@msgpack/msgpack';
1+
import { DecodeError, ExtensionCodec, decode, encode } from '@msgpack/msgpack';
22
import { Codec } from './types';
33

4+
const BIGINT_EXT_TYPE = 0;
5+
const extensionCodec = new ExtensionCodec();
6+
extensionCodec.register({
7+
type: BIGINT_EXT_TYPE,
8+
encode(input: unknown): Uint8Array | null {
9+
if (typeof input === 'bigint') {
10+
if (
11+
input <= Number.MAX_SAFE_INTEGER &&
12+
input >= Number.MIN_SAFE_INTEGER
13+
) {
14+
return encode(Number(input));
15+
} else {
16+
return encode(String(input));
17+
}
18+
} else {
19+
return null;
20+
}
21+
},
22+
decode(data: Uint8Array): bigint {
23+
const val = decode(data);
24+
if (!(typeof val === 'string' || typeof val === 'number')) {
25+
throw new DecodeError(`unexpected BigInt source: ${typeof val}`);
26+
}
27+
28+
return BigInt(val);
29+
},
30+
});
31+
432
/**
533
* Binary codec, uses [msgpack](https://www.npmjs.com/package/@msgpack/msgpack) under the hood
634
* @type {Codec}
735
*/
836
export const BinaryCodec: Codec = {
937
toBuffer(obj) {
10-
return encode(obj, { ignoreUndefined: true });
38+
return encode(obj, { ignoreUndefined: true, extensionCodec });
1139
},
1240
fromBuffer: (buff: Uint8Array) => {
13-
const res = decode(buff);
41+
const res = decode(buff, { extensionCodec });
1442
if (typeof res !== 'object' || res === null) {
1543
throw new Error('unpacked msg is not an object');
1644
}

codec/codec.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ describe.each(codecs)('codec -- $name', ({ codec }) => {
2727
expect(codec.fromBuffer(codec.toBuffer(msg))).toStrictEqual({});
2828
});
2929

30+
test('encodes bigint properly', () => {
31+
const msg = { big: BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1) };
32+
expect(codec.fromBuffer(codec.toBuffer(msg))).toStrictEqual(msg);
33+
});
34+
3035
test('deeply nested test', () => {
3136
const msg = {
3237
array: [{ object: true }],

codec/json.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ interface Base64EncodedValue {
2828
$t: string;
2929
}
3030

31+
interface BigIntEncodedValue {
32+
$b: string;
33+
}
34+
3135
/**
3236
* Naive JSON codec implementation using JSON.stringify and JSON.parse.
3337
* @type {Codec}
@@ -41,6 +45,8 @@ export const NaiveJsonCodec: Codec = {
4145
const val = this[key];
4246
if (val instanceof Uint8Array) {
4347
return { $t: uint8ArrayToBase64(val) } satisfies Base64EncodedValue;
48+
} else if (typeof val === 'bigint') {
49+
return { $b: val.toString() } satisfies BigIntEncodedValue;
4450
} else {
4551
return val;
4652
}
@@ -53,12 +59,13 @@ export const NaiveJsonCodec: Codec = {
5359
function reviver(_key, val: unknown) {
5460
if ((val as Base64EncodedValue | undefined)?.$t !== undefined) {
5561
return base64ToUint8Array((val as Base64EncodedValue).$t);
62+
} else if ((val as BigIntEncodedValue | undefined)?.$b !== undefined) {
63+
return BigInt((val as BigIntEncodedValue).$b);
5664
} else {
5765
return val;
5866
}
5967
},
6068
) as unknown;
61-
6269
if (typeof parsed !== 'object' || parsed === null) {
6370
throw new Error('unpacked msg is not an object');
6471
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@replit/river",
33
"description": "It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!",
4-
"version": "0.209.5",
4+
"version": "0.209.6",
55
"type": "module",
66
"exports": {
77
".": {

0 commit comments

Comments
 (0)