Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
4 changes: 2 additions & 2 deletions _examples/node-ts/server-fastify/server.gen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable */
// node-ts v1.0.0 21701cae51b73d035bf2180831cdb38220bbbccc
// --
// Code generated by Webrpc-gen@v0.30.4 with ../../../gen-typescript generator. DO NOT EDIT.
// Code generated by Webrpc-gen@v0.31.1 with ../../../gen-typescript generator. DO NOT EDIT.
//
// webrpc-gen -schema=service.ridl -target=../../../gen-typescript -server -out=./server-fastify/server.gen.ts

Expand Down Expand Up @@ -555,7 +555,7 @@ export const webrpcErrorByCode: { [code: number]: any } = {

export const WebrpcHeader = "Webrpc"

export const WebrpcHeaderValue = "webrpc@v0.30.4;gen-typescript@unknown;node-ts@v1.0.0"
export const WebrpcHeaderValue = "webrpc@v0.31.1;gen-typescript@unknown;node-ts@v1.0.0"

type WebrpcGenVersions = {
WebrpcGenVersion: string;
Expand Down
4 changes: 2 additions & 2 deletions _examples/node-ts/server-hono/server.gen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable */
// node-ts v1.0.0 21701cae51b73d035bf2180831cdb38220bbbccc
// --
// Code generated by Webrpc-gen@v0.30.4 with ../../../gen-typescript generator. DO NOT EDIT.
// Code generated by Webrpc-gen@v0.31.1 with ../../../gen-typescript generator. DO NOT EDIT.
//
// webrpc-gen -schema=service.ridl -target=../../../gen-typescript -server -out=./server-hono/server.gen.ts

Expand Down Expand Up @@ -555,7 +555,7 @@ export const webrpcErrorByCode: { [code: number]: any } = {

export const WebrpcHeader = "Webrpc"

export const WebrpcHeaderValue = "webrpc@v0.30.4;gen-typescript@unknown;node-ts@v1.0.0"
export const WebrpcHeaderValue = "webrpc@v0.31.1;gen-typescript@unknown;node-ts@v1.0.0"

type WebrpcGenVersions = {
WebrpcGenVersion: string;
Expand Down
4 changes: 2 additions & 2 deletions _examples/node-ts/server/server.gen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable */
// node-ts v1.0.0 21701cae51b73d035bf2180831cdb38220bbbccc
// --
// Code generated by Webrpc-gen@v0.30.4 with ../../../gen-typescript generator. DO NOT EDIT.
// Code generated by Webrpc-gen@v0.31.1 with ../../../gen-typescript generator. DO NOT EDIT.
//
// webrpc-gen -schema=service.ridl -target=../../../gen-typescript -server -out=./server/server.gen.ts

Expand Down Expand Up @@ -555,7 +555,7 @@ export const webrpcErrorByCode: { [code: number]: any } = {

export const WebrpcHeader = "Webrpc"

export const WebrpcHeaderValue = "webrpc@v0.30.4;gen-typescript@unknown;node-ts@v1.0.0"
export const WebrpcHeaderValue = "webrpc@v0.31.1;gen-typescript@unknown;node-ts@v1.0.0"

type WebrpcGenVersions = {
WebrpcGenVersion: string;
Expand Down
4 changes: 2 additions & 2 deletions _examples/node-ts/webapp/client.gen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable */
// node-ts v1.0.0 21701cae51b73d035bf2180831cdb38220bbbccc
// --
// Code generated by Webrpc-gen@v0.30.4 with ../../../gen-typescript generator. DO NOT EDIT.
// Code generated by Webrpc-gen@v0.31.1 with ../../../gen-typescript generator. DO NOT EDIT.
//
// webrpc-gen -schema=service.ridl -target=../../../gen-typescript -client -out=./webapp/client.gen.ts

Expand Down Expand Up @@ -496,7 +496,7 @@ export const webrpcErrorByCode: { [code: number]: any } = {

export const WebrpcHeader = "Webrpc"

export const WebrpcHeaderValue = "webrpc@v0.30.4;gen-typescript@unknown;node-ts@v1.0.0"
export const WebrpcHeaderValue = "webrpc@v0.31.1;gen-typescript@unknown;node-ts@v1.0.0"

type WebrpcGenVersions = {
WebrpcGenVersion: string;
Expand Down
4 changes: 2 additions & 2 deletions _examples/sse/webapp/client.gen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable */
// webrpc-sse-chat v1.0.0 a799dc63b082644f5d003c8881424546aee23a2c
// --
// Code generated by Webrpc-gen@v0.30.4 with ../../ generator. DO NOT EDIT.
// Code generated by Webrpc-gen@v0.31.1 with ../../ generator. DO NOT EDIT.
//
// webrpc-gen -schema=service.ridl -target=../../ -client -out=./webapp/client.gen.ts

Expand Down Expand Up @@ -565,7 +565,7 @@ export const webrpcErrorByCode: { [code: number]: any } = {

export const WebrpcHeader = "Webrpc"

export const WebrpcHeaderValue = "webrpc@v0.30.4;@unknown;webrpc-sse-chat@v1.0.0"
export const WebrpcHeaderValue = "webrpc@v0.31.1;@unknown;webrpc-sse-chat@v1.0.0"

type WebrpcGenVersions = {
WebrpcGenVersion: string;
Expand Down
53 changes: 15 additions & 38 deletions bigintHelpers.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -50,40 +50,6 @@ const BIG_INT_FIELDS: { [typ: string]: (string | [string, string])[] } = {
{{- end }}
}

// Encode in-place: mutate provided object graph to serialize bigints to strings.
function encodeType(typ: string, obj: any): any {
if (obj == null || typeof obj !== 'object') return obj
const descs = BIG_INT_FIELDS[typ] || []
if (!descs.length) return obj
for (const d of descs) {
if (Array.isArray(d)) {
const [fieldName, nestedType] = d
if (fieldName.endsWith('[]')) {
const base = fieldName.slice(0, -2)
const arr = obj[base]
if (Array.isArray(arr)) {
for (let i = 0; i < arr.length; i++) arr[i] = encodeType(nestedType, arr[i])
}
} else if (obj[fieldName]) {
obj[fieldName] = encodeType(nestedType, obj[fieldName])
}
continue
}
if (d.endsWith('[]')) {
const base = d.slice(0, -2)
const arr = obj[base]
if (Array.isArray(arr)) {
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] === 'bigint') arr[i] = arr[i].toString()
}
}
continue
}
if (typeof obj[d] === 'bigint') obj[d] = obj[d].toString()
}
return obj
}

// Decode in-place: mutate object graph; throw if expected numeric string is invalid.
function decodeType(typ: string, obj: any): any {
if (obj == null || typeof obj !== 'object') return obj
Expand All @@ -99,7 +65,16 @@ function decodeType(typ: string, obj: any): any {
for (let i = 0; i < arr.length; i++) arr[i] = decodeType(nestedType, arr[i])
}
} else if (obj[fieldName]) {
obj[fieldName] = decodeType(nestedType, obj[fieldName])
// Handle nestedType that might be an array type like 'Message[]'
if (nestedType.endsWith('[]')) {
const baseType = nestedType.slice(0, -2)
const arr = obj[fieldName]
if (Array.isArray(arr)) {
for (let i = 0; i < arr.length; i++) arr[i] = decodeType(baseType, arr[i])
}
} else {
obj[fieldName] = decodeType(nestedType, obj[fieldName])
}
}
continue
}
Expand All @@ -124,9 +99,11 @@ function decodeType(typ: string, obj: any): any {
return obj
}

// Encode object of given root type to JSON with BigInts converted to decimal strings.
export const JsonEncode = <T = any>(obj: T, typ: string = ''): string => {
return JSON.stringify(encodeType(typ, obj))
// Encode object to JSON with BigInts converted to decimal strings.
export const JsonEncode = <T = any>(obj: T): string => {
return JSON.stringify(obj, (key, value) =>
typeof value === 'bigint' ? value.toString() : value
)
}

// Decode data (JSON string or already-parsed object) and convert declared BigInt string fields back to BigInt.
Expand Down
162 changes: 162 additions & 0 deletions tests-unit/bigint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { describe, it, expect } from 'vitest'
import { JsonEncode, JsonDecode } from './client.gen.js'
import type { BigIntArrayTest } from './client.gen.js'


describe('JsonEncode', () => {
it('should encode BigInt arrays and nested objects', () => {
const input: BigIntArrayTest = {
ids: [1n, 2n, 3n],
timestamps: [-100n, 0n, 100n],
messages: [
{ id: 10n, userId: 20n, timestamp: 30n, content: 'test' },
],
}

const encoded = JsonEncode(input)
const parsed = JSON.parse(encoded)

// All BigInt values (arrays and nested objects) encoded as strings
expect(parsed).toMatchInlineSnapshot(`
{
"ids": [
"1",
"2",
"3",
],
"messages": [
{
"content": "test",
"id": "10",
"timestamp": "30",
"userId": "20",
},
],
"timestamps": [
"-100",
"0",
"100",
],
}
`)
})

it('should encode large BigInt values exceeding MAX_SAFE_INTEGER', () => {
const input: BigIntArrayTest = {
ids: [9007199254740992n, 18446744073709551615n],
timestamps: [],
messages: [],
}

const encoded = JsonEncode(input)
const parsed = JSON.parse(encoded)

expect(parsed).toMatchInlineSnapshot(`
{
"ids": [
"9007199254740992",
"18446744073709551615",
],
"messages": [],
"timestamps": [],
}
`)
})
})

describe('JsonDecode', () => {
it('should decode BigInt arrays and nested objects', () => {
const json = JSON.stringify({
ids: ['1', '2', '3'],
timestamps: ['-100', '0', '100'],
messages: [
{ id: '10', userId: '20', timestamp: '30', content: 'test' },
],
})

const decoded = JsonDecode<BigIntArrayTest>(json, 'BigIntArrayTest')

// Strings converted to BigInt (arrays and nested objects)
expect(decoded).toMatchInlineSnapshot(`
{
"ids": [
1n,
2n,
3n,
],
"messages": [
{
"content": "test",
"id": 10n,
"timestamp": 30n,
"userId": 20n,
},
],
"timestamps": [
-100n,
0n,
100n,
],
}
`)
expect(typeof decoded.ids[0]).toBe('bigint')
expect(typeof decoded.messages[0].id).toBe('bigint')
})

it('should decode large BigInt values', () => {
const json = JSON.stringify({
ids: ['9007199254740992', '18446744073709551615'],
timestamps: [],
messages: [],
})

const decoded = JsonDecode<BigIntArrayTest>(json, 'BigIntArrayTest')

expect(decoded.ids).toEqual([9007199254740992n, 18446744073709551615n])
expect(typeof decoded.ids[0]).toBe('bigint')
})
})

describe('Round-trip', () => {
it('should preserve all BigInt values through encode/decode cycle', () => {
const original: BigIntArrayTest = {
ids: [1n, 9007199254740992n],
timestamps: [-100n, 0n, 100n],
messages: [
{ id: 10n, userId: 20n, timestamp: 30n, content: 'test' },
],
}

const encoded = JsonEncode(original)
const decoded = JsonDecode<BigIntArrayTest>(encoded, 'BigIntArrayTest')

expect(decoded).toEqual(original)

})

it('should not mutate the original object during encoding or decoding', () => {
const original: BigIntArrayTest = {
ids: [1n, 2n, 3n],
timestamps: [-100n, 0n, 100n],
messages: [
{ id: 10n, userId: 20n, timestamp: 30n, content: 'test' },
],
}

// Create a deep clone to compare against later
const originalClone = structuredClone(original)

// Encode and decode
const encoded = JsonEncode(original)
const decoded = JsonDecode<BigIntArrayTest>(encoded, 'BigIntArrayTest')

// Original should be completely unchanged
expect(original).toEqual(originalClone)
expect(original.ids).toEqual(originalClone.ids)
expect(original.messages).toEqual(originalClone.messages)

// But decoded should be a different reference
expect(decoded).not.toBe(original)
expect(decoded.ids).not.toBe(original.ids)
})
})
Loading