diff --git a/packages/bundle-size/README.md b/packages/bundle-size/README.md index ce1b64853..80f5654eb 100644 --- a/packages/bundle-size/README.md +++ b/packages/bundle-size/README.md @@ -16,11 +16,11 @@ usually do. We repeat this for an increasing number of files. | code generator | files | bundle size | minified | compressed | |-----------------|----------|------------------------:|-----------------------:|-------------------:| -| protobuf-es | 1 | 123,019 b | 63,933 b | 14,930 b | -| protobuf-es | 4 | 125,214 b | 65,443 b | 15,592 b | -| protobuf-es | 8 | 127,992 b | 67,214 b | 16,104 b | -| protobuf-es | 16 | 138,500 b | 75,195 b | 18,432 b | -| protobuf-es | 32 | 166,395 b | 97,210 b | 23,848 b | +| protobuf-es | 1 | 123,350 b | 64,136 b | 14,970 b | +| protobuf-es | 4 | 125,545 b | 65,646 b | 15,662 b | +| protobuf-es | 8 | 128,323 b | 67,417 b | 16,196 b | +| protobuf-es | 16 | 138,831 b | 75,398 b | 18,504 b | +| protobuf-es | 32 | 166,726 b | 97,413 b | 23,953 b | | protobuf-javascript | 1 | 339,613 b | 255,820 b | 42,481 b | | protobuf-javascript | 4 | 366,281 b | 271,092 b | 43,912 b | | protobuf-javascript | 8 | 388,324 b | 283,409 b | 45,038 b | diff --git a/packages/bundle-size/chart.svg b/packages/bundle-size/chart.svg index 43934a6d3..183492679 100644 --- a/packages/bundle-size/chart.svg +++ b/packages/bundle-size/chart.svg @@ -43,14 +43,14 @@ 0 KiB - + protobuf-es -protobuf-es 14.58 KiB for 1 files -protobuf-es 15.23 KiB for 4 files -protobuf-es 15.73 KiB for 8 files -protobuf-es 18 KiB for 16 files -protobuf-es 23.29 KiB for 32 files +protobuf-es 14.62 KiB for 1 files +protobuf-es 15.29 KiB for 4 files +protobuf-es 15.82 KiB for 8 files +protobuf-es 18.07 KiB for 16 files +protobuf-es 23.39 KiB for 32 files diff --git a/packages/protobuf-test/src/wire/binary-encoding.test.ts b/packages/protobuf-test/src/wire/binary-encoding.test.ts index c6aaa04ae..4e430c2d1 100644 --- a/packages/protobuf-test/src/wire/binary-encoding.test.ts +++ b/packages/protobuf-test/src/wire/binary-encoding.test.ts @@ -31,7 +31,7 @@ describe("BinaryWriter", () => { expect(user.firstName).toBe("Homer"); expect(user.active).toBe(true); }); - describe("float32", () => { + describe("float32()", () => { it.each([ 1024, 3.142, @@ -41,13 +41,20 @@ describe("BinaryWriter", () => { Number.NEGATIVE_INFINITY, Number.NaN, ])("should encode %s", (val) => { - expect(new BinaryWriter().float(val).finish().length).toBeGreaterThan(0); + const bytes = new BinaryWriter().float(val).finish(); + expect(bytes.length).toBeGreaterThan(0); + // @ts-expect-error test string + const bytesStr = new BinaryWriter().float(val.toString()).finish(); + expect(bytesStr.length).toBeGreaterThan(0); + expect(bytesStr).toStrictEqual(bytes); }); it.each([ - { val: "123", err: /^invalid float32: string/ }, - { val: true, err: /^invalid float32: bool/ }, + { val: null, err: "invalid float32: object" }, + { val: new Date(), err: "invalid float32: object" }, + { val: undefined, err: "invalid float32: undefined" }, + { val: true, err: "invalid float32: bool" }, ])("should error for wrong type $val", ({ val, err }) => { - // @ts-expect-error TS2345 + // @ts-expect-error test wrong type expect(() => new BinaryWriter().float(val)).toThrow(err); }); it.each([Number.MAX_VALUE, -Number.MAX_VALUE])( @@ -56,40 +63,76 @@ describe("BinaryWriter", () => { expect(() => new BinaryWriter().float(val)).toThrow( /^invalid float32: .*/, ); + // @ts-expect-error test string + expect(() => new BinaryWriter().float(val.toString())).toThrow( + /^invalid float32: .*/, + ); }, ); }); - describe("int32", () => { + // sfixed32, sint32, and int32 are signed 32-bit integers, just with different encoding + describe.each(["sfixed32", "sint32", "int32"] as const)("%s()", (type) => { it.each([-0x80000000, 1024, 0x7fffffff])("should encode %s", (val) => { - expect(new BinaryWriter().int32(val).finish().length).toBeGreaterThan(0); + const bytes = new BinaryWriter()[type](val).finish(); + expect(bytes.length).toBeGreaterThan(0); + // @ts-expect-error test string + const bytesStr = new BinaryWriter()[type](val.toString()).finish(); + expect(bytesStr.length).toBeGreaterThan(0); + expect(bytesStr).toStrictEqual(bytes); }); it.each([ - { val: "123", err: /^invalid int32: string/ }, - { val: true, err: /^invalid int32: bool/ }, + { val: null, err: "invalid int32: object" }, + { val: new Date(), err: "invalid int32: object" }, + { val: undefined, err: "invalid int32: undefined" }, + { val: true, err: "invalid int32: bool" }, ])("should error for wrong type $val", ({ val, err }) => { // @ts-expect-error TS2345 - expect(() => new BinaryWriter().int32(val)).toThrow(err); + expect(() => new BinaryWriter()[type](val)).toThrow(err); }); - it.each([0x7fffffff + 1, -0x80000000 - 1])( + it.each([0x7fffffff + 1, -0x80000000 - 1, 3.142])( "should error for value out of range %s", (val) => { - expect(() => new BinaryWriter().int32(val)).toThrow( + expect(() => new BinaryWriter()[type](val)).toThrow( + /^invalid int32: .*/, + ); + // @ts-expect-error test string + expect(() => new BinaryWriter()[type](val.toString())).toThrow( /^invalid int32: .*/, ); }, ); }); - describe("uint32", () => { + // fixed32 and uint32 are unsigned 32-bit integers, just with different encoding + describe.each(["fixed32", "uint32"] as const)("%s()", (type) => { it.each([0, 1024, 0xffffffff])("should encode %s", (val) => { - expect(new BinaryWriter().uint32(val).finish().length).toBeGreaterThan(0); + const bytes = new BinaryWriter()[type](val).finish(); + expect(bytes.length).toBeGreaterThan(0); + // @ts-expect-error test string + const bytesStr = new BinaryWriter()[type](val.toString()).finish(); + expect(bytesStr.length).toBeGreaterThan(0); + expect(bytesStr).toStrictEqual(bytes); }); it.each([ - { val: "123", err: /^invalid uint32: string/ }, - { val: true, err: /^invalid uint32: bool/ }, - ])("should error for wrong type$val", ({ val, err }) => { + { val: null, err: `invalid uint32: object` }, + { val: new Date(), err: `invalid uint32: object` }, + { val: undefined, err: `invalid uint32: undefined` }, + { val: true, err: `invalid uint32: bool` }, + ])("should error for wrong type $val", ({ val, err }) => { // @ts-expect-error TS2345 - expect(() => new BinaryWriter().uint32(val)).toThrow(err); + expect(() => new BinaryWriter()[type](val)).toThrow(err); }); + it.each([0xffffffff + 1, -1, 3.142])( + "should error for value out of range %s", + (val) => { + expect(() => new BinaryWriter()[type](val)).toThrow( + /^invalid uint32: .*/, + ); + // @ts-expect-error test string + expect(() => new BinaryWriter()[type](val.toString())).toThrow( + /^invalid uint32: .*/, + ); + }, + ); }); }); diff --git a/packages/protobuf/src/reflect/guard.ts b/packages/protobuf/src/reflect/guard.ts index a3e7180d3..2115a2d58 100644 --- a/packages/protobuf/src/reflect/guard.ts +++ b/packages/protobuf/src/reflect/guard.ts @@ -21,7 +21,6 @@ import type { } from "./reflect-types.js"; import { unsafeLocal } from "./unsafe.js"; import type { DescField, DescMessage } from "../descriptors.js"; -import { isMessage } from "../is-message.js"; export function isObject(arg: unknown): arg is Record { return arg !== null && typeof arg == "object" && !Array.isArray(arg); @@ -102,8 +101,9 @@ export function isReflectMessage( return ( isObject(arg) && unsafeLocal in arg && - "message" in arg && - isMessage(arg.message) && - (messageDesc === undefined || arg.message.$typeName == messageDesc.typeName) + "desc" in arg && + isObject(arg.desc) && + arg.desc.kind === "message" && + (messageDesc === undefined || arg.desc.typeName == messageDesc.typeName) ); } diff --git a/packages/protobuf/src/wire/binary-encoding.ts b/packages/protobuf/src/wire/binary-encoding.ts index d9fb30a35..29d57d11d 100644 --- a/packages/protobuf/src/wire/binary-encoding.ts +++ b/packages/protobuf/src/wire/binary-encoding.ts @@ -568,29 +568,55 @@ export class BinaryReader { } /** - * Assert a valid signed protobuf 32-bit integer. + * Assert a valid signed protobuf 32-bit integer as a number or string. */ function assertInt32(arg: unknown): asserts arg is number { - if (typeof arg !== "number") throw new Error("invalid int32: " + typeof arg); - if (!Number.isInteger(arg) || arg > INT32_MAX || arg < INT32_MIN) + if (typeof arg == "string") { + arg = Number(arg); + } else if (typeof arg != "number") { + throw new Error("invalid int32: " + typeof arg); + } + if ( + !Number.isInteger(arg) || + (arg as number) > INT32_MAX || + (arg as number) < INT32_MIN + ) throw new Error("invalid int32: " + arg); } /** - * Assert a valid unsigned protobuf 32-bit integer. + * Assert a valid unsigned protobuf 32-bit integer as a number or string. */ function assertUInt32(arg: unknown): asserts arg is number { - if (typeof arg !== "number") throw new Error("invalid uint32: " + typeof arg); - if (!Number.isInteger(arg) || arg > UINT32_MAX || arg < 0) + if (typeof arg == "string") { + arg = Number(arg); + } else if (typeof arg != "number") { + throw new Error("invalid uint32: " + typeof arg); + } + if ( + !Number.isInteger(arg) || + (arg as number) > UINT32_MAX || + (arg as number) < 0 + ) throw new Error("invalid uint32: " + arg); } /** - * Assert a valid protobuf float value. + * Assert a valid protobuf float value as a number or string. */ function assertFloat32(arg: unknown): asserts arg is number { - if (typeof arg !== "number") + if (typeof arg == "string") { + const o = arg; + arg = Number(arg); + if (isNaN(arg as number) && o !== "NaN") { + throw new Error("invalid float32: " + o); + } + } else if (typeof arg != "number") { throw new Error("invalid float32: " + typeof arg); - if (Number.isFinite(arg) && (arg > FLOAT32_MAX || arg < FLOAT32_MIN)) + } + if ( + Number.isFinite(arg) && + ((arg as number) > FLOAT32_MAX || (arg as number) < FLOAT32_MIN) + ) throw new Error("invalid float32: " + arg); }