This is an automated email from the ASF dual-hosted git repository.
wangweipeng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 27f2b64b8 feat(xlang): add unsigned integer type support for
JavaScript (#3139)
27f2b64b8 is described below
commit 27f2b64b845129f30a115d01788c7bd4369b8680
Author: Ayush Kumar <[email protected]>
AuthorDate: Thu Jan 15 08:23:50 2026 +0530
feat(xlang): add unsigned integer type support for JavaScript (#3139)
## Why?
As requested in Issue #3130, JavaScript implementation was missing
support for unsigned integer types that are already available in other
Fory language implementations (Rust, Go, C++, Python). This PR adds
complete unsigned integer support for JavaScript.
## What does this PR do?
This PR implements schema-based support for unsigned integer types in
the Fory JavaScript/TypeScript library:
### Added Types
- `uint8`
- `uint16`
- `uint32`
- `uint64`
- `var_uint32`
- `var_uint64`
- `tagged_uint64`
### Changes Made
#### 1. `lib/type.ts`
Added 7 unsigned integer types to the `InternalSerializerType` enum:
```typescript
export enum InternalSerializerType {
UINT8,
UINT16,
UINT32,
VAR_UINT32,
UINT64,
VAR_UINT64,
TAGGED_UINT64,
// ...
}
```
#### 2. `lib/typeInfo.ts`
**Added Type helper functions** for user-facing API:
```typescript
export const Type = {
uint8() {
return TypeInfo.fromNonParam(InternalSerializerType.UINT8 as const,
TypeId.UINT8);
},
uint16() {
return TypeInfo.fromNonParam(InternalSerializerType.UINT16 as const,
TypeId.UINT16);
},
// ...
}
```
**Updated TypeScript type hints** for proper type inference:
- Added unsigned types to HintInput number type union (returns `number`
for 8/16/32-bit, `bigint` for 64-bit)
- Added unsigned types to HintResult type union for proper
deserialization type inference
#### 3. `lib/gen/number.ts`
Registered serializers for all 7 unsigned integer types using the
existing buildNumberSerializer pattern:
```typescript
CodegenRegistry.register(InternalSerializerType.UINT8,
buildNumberSerializer(
(builder, accessor) => builder.writer.uint8(accessor),
builder => builder.reader.uint8()
)
);
CodegenRegistry.register(InternalSerializerType.UINT16,
buildNumberSerializer(
(builder, accessor) => builder.writer.uint16(accessor),
builder => builder.reader.uint16()
)
);
//....
```
#### 4. `test/number.test.ts`
Added comprehensive tests for all unsigned types:
```typescript
test('should uint8 work', () => {
const fory = new Fory({ refTracking: true });
const serializer = fory.registerSerializer(Type.struct({
typeName: "example.foo"
}, {
a: Type.uint8()
})).serializer;
const input = fory.serialize({ a: 255 }, serializer);
const result = fory.deserialize(input);
expect(result).toEqual({ a: 255 });
});
test('should uint16 work', () => {
const fory = new Fory({ refTracking: true });
const serializer = fory.registerSerializer(Type.struct({
typeName: "example.foo"
}, {
a: Type.uint16()
})).serializer;
const input = fory.serialize({ a: 65535 }, serializer);
const result = fory.deserialize(input);
expect(result).toEqual({ a: 65535 });
});
\\...
```
### Usage Example
```typescript
import { Fory, Type } from '@apache-fory/fory';
const fory = new Fory();
const serializer = fory.registerSerializer(Type.struct({
typeName: 'Foo'
}, {
f1: Type.uint32(),
f2: Type.varUInt32(),
f3: Type.uint64()
})).serializer;
const data = { f1: 4294967295, f2: 1000, f3: 18446744073709551615n };
const bytes = fory.serialize(data, serializer);
const result = fory.deserialize(bytes);
```
## Related issues
- Closes #3130
## Does this PR introduce any user-facing change?
- [x] **Does this PR introduce any public API change?**
- **Yes**: Adds 7 new Type helper functions (`Type.uint8()`,
`Type.uint16()`, `Type.uint32()`, `Type.varUInt32()`, `Type.uint64()`,
`Type.varUInt64()`, `Type.taggedUInt64()`)
- These are **additive changes only** - no breaking changes to existing
APIs
- [x] **Does this PR introduce any binary protocol compatibility
change?**
- **Yes**: Adds support for 7 new TypeId values (9-15) for unsigned
integers
- **Cross-language compatible**: Uses the same TypeId values as other
Fory implementations
- **Backward compatible**: Existing serialized data remains compatible
## Benchmark
N/A - this PR adds new functionality without modifying existing
serialization paths. The underlying writer/reader methods `uint8()`,
`uint16()` were already implemented and optimized; this PR only exposes
them through the type system.
---
---
javascript/.eslintrc.cjs | 2 +-
javascript/packages/fory/lib/gen/number.ts | 49 +++++
javascript/packages/fory/lib/type.ts | 7 +
javascript/packages/fory/lib/typeInfo.ts | 308 +++++++++++++++++------------
javascript/test/number.test.ts | 84 ++++++++
5 files changed, 324 insertions(+), 126 deletions(-)
diff --git a/javascript/.eslintrc.cjs b/javascript/.eslintrc.cjs
index eebadd856..16291aee4 100644
--- a/javascript/.eslintrc.cjs
+++ b/javascript/.eslintrc.cjs
@@ -38,5 +38,5 @@ module.exports = {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
},
- ignorePatterns: ["test/*", "dist/*", "*.js", "murmurHash3.ts",
"packages/**/dist/*", "packages/**/build/*"],
+ ignorePatterns: ["test/*", "dist/*", "*.js", "murmurHash3.ts",
"packages/**/dist/*", "packages/**/build/*", "**/typeInfo.ts"],
};
diff --git a/javascript/packages/fory/lib/gen/number.ts
b/javascript/packages/fory/lib/gen/number.ts
index 23297b141..a5a122c5b 100644
--- a/javascript/packages/fory/lib/gen/number.ts
+++ b/javascript/packages/fory/lib/gen/number.ts
@@ -110,3 +110,52 @@ CodegenRegistry.register(InternalSerializerType.FLOAT64,
builder => builder.reader.float64()
)
);
+
+CodegenRegistry.register(InternalSerializerType.UINT8,
+ buildNumberSerializer(
+ (builder, accessor) => builder.writer.uint8(accessor),
+ builder => builder.reader.uint8()
+ )
+);
+
+CodegenRegistry.register(InternalSerializerType.UINT16,
+ buildNumberSerializer(
+ (builder, accessor) => builder.writer.uint16(accessor),
+ builder => builder.reader.uint16()
+ )
+);
+
+CodegenRegistry.register(InternalSerializerType.UINT32,
+ buildNumberSerializer(
+ (builder, accessor) => builder.writer.uint32(accessor),
+ builder => builder.reader.uint32()
+ )
+);
+
+CodegenRegistry.register(InternalSerializerType.VAR_UINT32,
+ buildNumberSerializer(
+ (builder, accessor) => builder.writer.varUInt32(accessor),
+ builder => builder.reader.varUInt32()
+ )
+);
+
+CodegenRegistry.register(InternalSerializerType.UINT64,
+ buildNumberSerializer(
+ (builder, accessor) => builder.writer.uint64(accessor),
+ builder => builder.reader.uint64()
+ )
+);
+
+CodegenRegistry.register(InternalSerializerType.VAR_UINT64,
+ buildNumberSerializer(
+ (builder, accessor) => builder.writer.varUInt64(accessor),
+ builder => builder.reader.varUInt64()
+ )
+);
+
+CodegenRegistry.register(InternalSerializerType.TAGGED_UINT64,
+ buildNumberSerializer(
+ (builder, accessor) => builder.writer.varUInt64(accessor),
+ builder => builder.reader.varUInt64()
+ )
+);
diff --git a/javascript/packages/fory/lib/type.ts
b/javascript/packages/fory/lib/type.ts
index aafb9c47c..185b83d98 100644
--- a/javascript/packages/fory/lib/type.ts
+++ b/javascript/packages/fory/lib/type.ts
@@ -142,6 +142,13 @@ export enum InternalSerializerType {
INT64,
VARINT64,
TAGGED_INT64,
+ UINT8,
+ UINT16,
+ UINT32,
+ VAR_UINT32,
+ UINT64,
+ VAR_UINT64,
+ TAGGED_UINT64,
FLOAT16,
FLOAT32,
FLOAT64,
diff --git a/javascript/packages/fory/lib/typeInfo.ts
b/javascript/packages/fory/lib/typeInfo.ts
index e98c7da02..6a73d88e0 100644
--- a/javascript/packages/fory/lib/typeInfo.ts
+++ b/javascript/packages/fory/lib/typeInfo.ts
@@ -29,12 +29,12 @@ const initMeta = (target: new () => any, typeInfo:
TypeInfo) => {
...(
TypeId.IS_NAMED_TYPE(typeInfo.typeId)
? {
- namespace: typeInfo.namespace,
- typeName: typeInfo.typeName,
- }
+ namespace: typeInfo.namespace,
+ typeName: typeInfo.typeName,
+ }
: {
- typeId: typeInfo.typeId,
- }
+ typeId: typeInfo.typeId,
+ }
),
}, targetFields.get(target) || {}, {
withConstructor: true,
@@ -253,8 +253,8 @@ type Props<T> = T extends {
};
}
? {
- [P in keyof T2]?: (InputType<T2[P]> | null);
- }
+ [P in keyof T2]?: (InputType<T2[P]> | null);
+ }
: unknown;
type InnerProps<T> = T extends {
@@ -298,8 +298,8 @@ type OneofProps<T> = T extends {
};
}
? {
- [P in keyof T2]?: (InputType<T2[P]> | null);
- }
+ [P in keyof T2]?: (InputType<T2[P]> | null);
+ }
: unknown;
type OneofResult<T> = T extends {
@@ -327,66 +327,74 @@ export type HintInput<T> = T extends unknown ? any : T
extends {
: T extends {
type: InternalSerializerType.STRING;
}
- ? string
- : T extends {
- type: InternalSerializerType.TUPLE;
- }
- ? TupleProps<T>
- : T extends {
- type:
- | InternalSerializerType.INT8
- | InternalSerializerType.INT16
- | InternalSerializerType.INT32
- | InternalSerializerType.VARINT32
- | InternalSerializerType.FLOAT16
- | InternalSerializerType.FLOAT32
- | InternalSerializerType.FLOAT64;
- }
- ? number
+ ? string
+ : T extends {
+ type: InternalSerializerType.TUPLE;
+ }
+ ? TupleProps<T>
+ : T extends {
+ type:
+ | InternalSerializerType.INT8
+ | InternalSerializerType.INT16
+ | InternalSerializerType.INT32
+ | InternalSerializerType.VARINT32
+ | InternalSerializerType.UINT8
+ | InternalSerializerType.UINT16
+ | InternalSerializerType.UINT32
+ | InternalSerializerType.VAR_UINT32
+ | InternalSerializerType.FLOAT16
+ | InternalSerializerType.FLOAT32
+ | InternalSerializerType.FLOAT64;
+ }
+ ? number
- : T extends {
- type: InternalSerializerType.VARINT64
- | InternalSerializerType.TAGGED_INT64
- | InternalSerializerType.INT64;
- }
- ? bigint
- : T extends {
- type: InternalSerializerType.MAP;
- }
- ? MapProps<T>
- : T extends {
- type: InternalSerializerType.SET;
- }
- ? SetProps<T>
- : T extends {
- type: InternalSerializerType.ARRAY;
- }
- ? InnerProps<T>
- : T extends {
- type: InternalSerializerType.BOOL;
- }
- ? boolean
- : T extends {
- type: InternalSerializerType.DURATION;
- }
- ? Date
- : T extends {
- type: InternalSerializerType.TIMESTAMP;
- }
- ? number
- : T extends {
- type: InternalSerializerType.ANY;
- }
- ? any
- : T extends { type: InternalSerializerType.BINARY;
- }
- ? Uint8Array
- : T extends {
- type: InternalSerializerType.ENUM;
- }
- ? EnumProps<T> : T extends {
- type: InternalSerializerType.ONEOF;
- } ? OneofProps<T> : unknown;
+ : T extends {
+ type: InternalSerializerType.VARINT64
+ | InternalSerializerType.TAGGED_INT64
+ | InternalSerializerType.INT64
+ | InternalSerializerType.UINT64
+ | InternalSerializerType.VAR_UINT64
+ | InternalSerializerType.TAGGED_UINT64;
+ }
+ ? bigint
+ : T extends {
+ type: InternalSerializerType.MAP;
+ }
+ ? MapProps<T>
+ : T extends {
+ type: InternalSerializerType.SET;
+ }
+ ? SetProps<T>
+ : T extends {
+ type: InternalSerializerType.ARRAY;
+ }
+ ? InnerProps<T>
+ : T extends {
+ type: InternalSerializerType.BOOL;
+ }
+ ? boolean
+ : T extends {
+ type: InternalSerializerType.DURATION;
+ }
+ ? Date
+ : T extends {
+ type: InternalSerializerType.TIMESTAMP;
+ }
+ ? number
+ : T extends {
+ type: InternalSerializerType.ANY;
+ }
+ ? any
+ : T extends {
+ type: InternalSerializerType.BINARY;
+ }
+ ? Uint8Array
+ : T extends {
+ type: InternalSerializerType.ENUM;
+ }
+ ? EnumProps<T> : T extends {
+ type: InternalSerializerType.ONEOF;
+ } ? OneofProps<T> : unknown;
export type ResultType<T> = T extends TypeInfo<infer M> ? HintResult<M> :
HintResult<T>;
@@ -397,64 +405,72 @@ export type HintResult<T> = T extends never ? any : T
extends {
: T extends {
type: InternalSerializerType.STRING;
}
- ? string
- : T extends {
- type: InternalSerializerType.TUPLE;
- }
- ? TupleProps<T>
- : T extends {
- type:
- | InternalSerializerType.INT8
- | InternalSerializerType.INT16
- | InternalSerializerType.INT32
- | InternalSerializerType.VARINT32
- | InternalSerializerType.FLOAT16
- | InternalSerializerType.FLOAT32
- | InternalSerializerType.FLOAT64;
- }
- ? number
+ ? string
+ : T extends {
+ type: InternalSerializerType.TUPLE;
+ }
+ ? TupleProps<T>
+ : T extends {
+ type:
+ | InternalSerializerType.INT8
+ | InternalSerializerType.INT16
+ | InternalSerializerType.INT32
+ | InternalSerializerType.VARINT32
+ | InternalSerializerType.UINT8
+ | InternalSerializerType.UINT16
+ | InternalSerializerType.UINT32
+ | InternalSerializerType.VAR_UINT32
+ | InternalSerializerType.FLOAT16
+ | InternalSerializerType.FLOAT32
+ | InternalSerializerType.FLOAT64;
+ }
+ ? number
- : T extends {
- type: InternalSerializerType.TAGGED_INT64
- | InternalSerializerType.INT64;
- }
- ? bigint
- : T extends {
- type: InternalSerializerType.MAP;
- }
- ? MapProps<T>
- : T extends {
- type: InternalSerializerType.SET;
- }
- ? SetProps<T>
- : T extends {
- type: InternalSerializerType.ARRAY;
- }
- ? InnerProps<T>
- : T extends {
- type: InternalSerializerType.BOOL;
- }
- ? boolean
- : T extends {
- type: InternalSerializerType.DURATION;
- }
- ? Date
- : T extends {
- type: InternalSerializerType.TIMESTAMP;
- }
- ? number
- : T extends { type: InternalSerializerType.BINARY;
- }
- ? Uint8Array : T extends {
- type: InternalSerializerType.ANY;
- }
- ? any
- : T extends {
- type: InternalSerializerType.ENUM;
- }
- ? EnumProps<T> : T extends {
- type: InternalSerializerType.ONEOF;
- } ? OneofResult<T> : unknown;
+ : T extends {
+ type: InternalSerializerType.TAGGED_INT64
+ | InternalSerializerType.INT64
+ | InternalSerializerType.UINT64
+ | InternalSerializerType.VAR_UINT64
+ | InternalSerializerType.TAGGED_UINT64;
+ }
+ ? bigint
+ : T extends {
+ type: InternalSerializerType.MAP;
+ }
+ ? MapProps<T>
+ : T extends {
+ type: InternalSerializerType.SET;
+ }
+ ? SetProps<T>
+ : T extends {
+ type: InternalSerializerType.ARRAY;
+ }
+ ? InnerProps<T>
+ : T extends {
+ type: InternalSerializerType.BOOL;
+ }
+ ? boolean
+ : T extends {
+ type: InternalSerializerType.DURATION;
+ }
+ ? Date
+ : T extends {
+ type: InternalSerializerType.TIMESTAMP;
+ }
+ ? number
+ : T extends {
+ type: InternalSerializerType.BINARY;
+ }
+ ? Uint8Array : T extends {
+ type: InternalSerializerType.ANY;
+ }
+ ? any
+ : T extends {
+ type: InternalSerializerType.ENUM;
+ }
+ ? EnumProps<T> : T extends {
+ type: InternalSerializerType.ONEOF;
+ } ? OneofResult<T> : unknown;
export const Type = {
any() {
@@ -593,6 +609,48 @@ export const Type = {
);
},
+ uint8() {
+ return TypeInfo.fromNonParam(
+ InternalSerializerType.UINT8 as const,
+ (TypeId.UINT8),
+ );
+ },
+ uint16() {
+ return TypeInfo.fromNonParam(
+ InternalSerializerType.UINT16 as const,
+ (TypeId.UINT16),
+ );
+ },
+ uint32() {
+ return TypeInfo.fromNonParam(
+ InternalSerializerType.UINT32 as const,
+ (TypeId.UINT32),
+ );
+ },
+ varUInt32() {
+ return TypeInfo.fromNonParam(
+ InternalSerializerType.VAR_UINT32 as const,
+ (TypeId.VAR_UINT32),
+ );
+ },
+ uint64() {
+ return TypeInfo.fromNonParam(
+ InternalSerializerType.UINT64 as const,
+ (TypeId.UINT64),
+ );
+ },
+ varUInt64() {
+ return TypeInfo.fromNonParam(
+ InternalSerializerType.VAR_UINT64 as const,
+ (TypeId.VAR_UINT64),
+ );
+ },
+ taggedUInt64() {
+ return TypeInfo.fromNonParam(
+ InternalSerializerType.TAGGED_UINT64 as const,
+ (TypeId.TAGGED_UINT64),
+ );
+ },
binary() {
return TypeInfo.fromNonParam(
InternalSerializerType.BINARY as const,
diff --git a/javascript/test/number.test.ts b/javascript/test/number.test.ts
index 2835478e5..dd5d54080 100644
--- a/javascript/test/number.test.ts
+++ b/javascript/test/number.test.ts
@@ -107,6 +107,90 @@ describe('number', () => {
);
expect(result.a).toBeCloseTo(1.2)
});
+
+ test('should uint8 work', () => {
+ const fory = new Fory({ refTracking: true });
+ const serializer = fory.registerSerializer(Type.struct({
+ typeName: "example.foo"
+ }, {
+ a: Type.uint8()
+ })).serializer;
+ const input = fory.serialize({ a: 255 }, serializer);
+ const result = fory.deserialize(input);
+ expect(result).toEqual({ a: 255 });
+ });
+
+ test('should uint16 work', () => {
+ const fory = new Fory({ refTracking: true });
+ const serializer = fory.registerSerializer(Type.struct({
+ typeName: "example.foo"
+ }, {
+ a: Type.uint16()
+ })).serializer;
+ const input = fory.serialize({ a: 65535 }, serializer);
+ const result = fory.deserialize(input);
+ expect(result).toEqual({ a: 65535 });
+ });
+
+ test('should uint32 work', () => {
+ const fory = new Fory({ refTracking: true });
+ const serializer = fory.registerSerializer(Type.struct({
+ typeName: "example.foo"
+ }, {
+ a: Type.uint32()
+ })).serializer;
+ const input = fory.serialize({ a: 4294967295 }, serializer);
+ const result = fory.deserialize(input);
+ expect(result).toEqual({ a: 4294967295 });
+ });
+
+ test('should varUInt32 work', () => {
+ const fory = new Fory({ refTracking: true });
+ const serializer = fory.registerSerializer(Type.struct({
+ typeName: "example.foo"
+ }, {
+ a: Type.varUInt32()
+ })).serializer;
+ const input = fory.serialize({ a: 1000000 }, serializer);
+ const result = fory.deserialize(input);
+ expect(result).toEqual({ a: 1000000 });
+ });
+
+ test('should uint64 work', () => {
+ const fory = new Fory({ refTracking: true });
+ const serializer = fory.registerSerializer(Type.struct({
+ typeName: "example.foo"
+ }, {
+ a: Type.uint64()
+ })).serializer;
+ const input = fory.serialize({ a: 18446744073709551615n }, serializer);
+ const result = fory.deserialize(input);
+ expect(result).toEqual({ a: 18446744073709551615n });
+ });
+
+ test('should varUInt64 work', () => {
+ const fory = new Fory({ refTracking: true });
+ const serializer = fory.registerSerializer(Type.struct({
+ typeName: "example.foo"
+ }, {
+ a: Type.varUInt64()
+ })).serializer;
+ const input = fory.serialize({ a: 1n }, serializer);
+ const result = fory.deserialize(input);
+ expect(result).toEqual({ a: 1n });
+ });
+
+ test('should taggedUInt64 work', () => {
+ const fory = new Fory({ refTracking: true });
+ const serializer = fory.registerSerializer(Type.struct({
+ typeName: "example.foo"
+ }, {
+ a: Type.taggedUInt64()
+ })).serializer;
+ const input = fory.serialize({ a: 1n }, serializer);
+ const result = fory.deserialize(input);
+ expect(result).toEqual({ a: 1n });
+ });
});
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]