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 c93e6ee43 feat(JavaScript): Align xlang protocol (#3326)
c93e6ee43 is described below
commit c93e6ee43e65226210137e92f4c6913207c88351
Author: weipeng <[email protected]>
AuthorDate: Wed Feb 11 23:03:02 2026 +0800
feat(JavaScript): Align xlang protocol (#3326)
## Why?
1. Align xlang protocol. current progress is 38/39
## What does this PR do?
## Related issues
#3133
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
javascript/packages/fory/lib/gen/any.ts | 2 +-
javascript/packages/fory/lib/gen/binary.ts | 58 -----
javascript/packages/fory/lib/gen/builder.ts | 8 +-
javascript/packages/fory/lib/gen/datetime.ts | 65 +++---
javascript/packages/fory/lib/gen/enum.ts | 6 +
javascript/packages/fory/lib/gen/index.ts | 3 +-
javascript/packages/fory/lib/gen/typedArray.ts | 124 ++++++++---
javascript/packages/fory/lib/meta/TypeMeta.ts | 11 +-
javascript/packages/fory/lib/typeInfo.ts | 19 +-
javascript/packages/fory/lib/typeMetaResolver.ts | 16 +-
javascript/packages/fory/lib/typeResolver.ts | 33 ++-
javascript/packages/fory/lib/writer/index.ts | 6 +
javascript/test/array.test.ts | 22 +-
javascript/test/crossLanguage.test.ts | 267 +++++++++++++++++++----
javascript/test/datetime.test.ts | 7 +-
javascript/test/fory.test.ts | 2 +-
javascript/test/object.test.ts | 2 +-
17 files changed, 461 insertions(+), 190 deletions(-)
diff --git a/javascript/packages/fory/lib/gen/any.ts
b/javascript/packages/fory/lib/gen/any.ts
index b3dd6aa88..cf941ca60 100644
--- a/javascript/packages/fory/lib/gen/any.ts
+++ b/javascript/packages/fory/lib/gen/any.ts
@@ -37,7 +37,7 @@ export class AnyHelper {
function tryUpdateSerializer(serializer: Serializer | undefined | null,
typeMeta: TypeMeta) {
if (!serializer) {
- throw new Error(`can't find implements of typeId: ${typeId}`);
+ return fory.typeMetaResolver.genSerializerByTypeMetaRuntime(typeMeta);
}
const hash = serializer.getHash();
if (hash !== typeMeta.getHash()) {
diff --git a/javascript/packages/fory/lib/gen/binary.ts
b/javascript/packages/fory/lib/gen/binary.ts
deleted file mode 100644
index f44f63302..000000000
--- a/javascript/packages/fory/lib/gen/binary.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { TypeInfo } from "../typeInfo";
-import { CodecBuilder } from "./builder";
-import { BaseSerializerGenerator } from "./serializer";
-import { CodegenRegistry } from "./router";
-import { TypeId } from "../type";
-import { Scope } from "./scope";
-
-class BinarySerializerGenerator extends BaseSerializerGenerator {
- typeInfo: TypeInfo;
-
- constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
- super(typeInfo, builder, scope);
- this.typeInfo = typeInfo;
- }
-
- write(accessor: string): string {
- return `
- ${this.builder.writer.uint8(1)}
- ${this.builder.writer.uint32(`${accessor}.byteLength`)}
- ${this.builder.writer.buffer(accessor)}
- `;
- }
-
- read(accessor: (expr: string) => string, refState: string): string {
- const result = this.scope.uniqueName("result");
- return `
- ${this.builder.reader.uint8()}
- ${result} = ${this.builder.reader.buffer(this.builder.reader.int32())};
- ${this.maybeReference(result, refState)};
- ${accessor(result)}
- `;
- }
-
- getFixedSize(): number {
- return 8;
- }
-}
-
-CodegenRegistry.register(TypeId.BINARY, BinarySerializerGenerator);
diff --git a/javascript/packages/fory/lib/gen/builder.ts
b/javascript/packages/fory/lib/gen/builder.ts
index 8138220f4..7627340d1 100644
--- a/javascript/packages/fory/lib/gen/builder.ts
+++ b/javascript/packages/fory/lib/gen/builder.ts
@@ -79,8 +79,8 @@ export class BinaryReaderBuilder {
return `${this.holder}.buffer(${len})`;
}
- bufferRef() {
- return `${this.holder}.bufferRef()`;
+ bufferRef(len: string | number) {
+ return `${this.holder}.bufferRef(${len})`;
}
uint8() {
@@ -177,6 +177,10 @@ class BinaryWriterBuilder {
return `${this.holder}.reserve(${v})`;
}
+ arrayBuffer(buffer: number | string, byteOffset: number | string,
byteLength: number | string) {
+ return `${this.holder}.arrayBuffer(${buffer}, ${byteOffset},
${byteLength})`;
+ }
+
uint16(v: number | string) {
return `${this.holder}.uint16(${v})`;
}
diff --git a/javascript/packages/fory/lib/gen/datetime.ts
b/javascript/packages/fory/lib/gen/datetime.ts
index 6a32de666..071e7c3d2 100644
--- a/javascript/packages/fory/lib/gen/datetime.ts
+++ b/javascript/packages/fory/lib/gen/datetime.ts
@@ -33,32 +33,16 @@ class TimestampSerializerGenerator extends
BaseSerializerGenerator {
}
write(accessor: string): string {
- if (/^-?[0-9]+$/.test(accessor)) {
- const msVar = this.scope.uniqueName("ts_ms");
- const secondsVar = this.scope.uniqueName("ts_sec");
- const nanosVar = this.scope.uniqueName("ts_nanos");
- return `
- {
- const ${msVar} = ${accessor};
- const ${secondsVar} = Math.floor(${msVar} / 1000);
- const ${nanosVar} = (${msVar} - ${secondsVar} * 1000) * 1000000;
- ${this.builder.writer.int64(`BigInt(${secondsVar})`)}
- ${this.builder.writer.uint32(`${nanosVar}`)}
- }
- `;
- }
const msVar = this.scope.uniqueName("ts_ms");
const secondsVar = this.scope.uniqueName("ts_sec");
const nanosVar = this.scope.uniqueName("ts_nanos");
return `
- {
- const ${msVar} = ${accessor}.getTime();
- const ${secondsVar} = Math.floor(${msVar} / 1000);
- const ${nanosVar} = (${msVar} - ${secondsVar} * 1000) * 1000000;
- ${this.builder.writer.int64(`BigInt(${secondsVar})`)}
- ${this.builder.writer.uint32(`${nanosVar}`)}
- }
- `;
+ const ${msVar} = (${accessor} instanceof Date) ? ${accessor}.getTime() :
${accessor};
+ const ${secondsVar} = Math.floor(${msVar} / 1000);
+ const ${nanosVar} = (${msVar} - ${secondsVar} * 1000) * 1000000;
+ ${this.builder.writer.int64(`${secondsVar}`)}
+ ${this.builder.writer.uint32(`${nanosVar}`)}
+ `;
}
read(accessor: (expr: string) => string): string {
@@ -80,6 +64,38 @@ class DurationSerializerGenerator extends
BaseSerializerGenerator {
this.typeInfo = typeInfo;
}
+ write(accessor: string): string {
+ const msVar = this.scope.uniqueName("ts_ms");
+ const secondsVar = this.scope.uniqueName("ts_sec");
+ const nanosVar = this.scope.uniqueName("ts_nanos");
+ return `
+ const ${msVar} = ${accessor};
+ const ${secondsVar} = Math.floor(${msVar} / 1000);
+ const ${nanosVar} = (${msVar} - ${secondsVar} * 1000) * 1000000;
+ ${this.builder.writer.int64(`${secondsVar}`)}
+ ${this.builder.writer.uint32(`${nanosVar}`)}
+ `;
+ }
+
+ read(accessor: (expr: string) => string): string {
+ const seconds = this.builder.reader.int64();
+ const nanos = this.builder.reader.uint32();
+ return accessor(`Number(${seconds}) * 1000 + Math.floor(${nanos} /
1000000)`);
+ }
+
+ getFixedSize(): number {
+ return 7;
+ }
+}
+
+class DateSerializerGenerator extends BaseSerializerGenerator {
+ typeInfo: TypeInfo;
+
+ constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
+ super(typeInfo, builder, scope);
+ this.typeInfo = typeInfo;
+ }
+
write(accessor: string): string {
const epoch = this.scope.declareByName("epoch", `new Date("1970/01/01
00:00").getTime()`);
return `
@@ -93,9 +109,7 @@ class DurationSerializerGenerator extends
BaseSerializerGenerator {
read(accessor: (expr: string) => string): string {
const epoch = this.scope.declareByName("epoch", `new Date("1970/01/01
00:00").getTime()`);
- return accessor(`
- new Date(${epoch} + (${this.builder.reader.int32()} * (24 * 60 *
60) * 1000))
- `);
+ return accessor(`new Date(${epoch} + (${this.builder.reader.int32()} * (24
* 60 * 60) * 1000))`);
}
getFixedSize(): number {
@@ -105,3 +119,4 @@ class DurationSerializerGenerator extends
BaseSerializerGenerator {
CodegenRegistry.register(TypeId.DURATION, DurationSerializerGenerator);
CodegenRegistry.register(TypeId.TIMESTAMP, TimestampSerializerGenerator);
+CodegenRegistry.register(TypeId.DATE, DateSerializerGenerator);
diff --git a/javascript/packages/fory/lib/gen/enum.ts
b/javascript/packages/fory/lib/gen/enum.ts
index 65e289f21..6a7218780 100644
--- a/javascript/packages/fory/lib/gen/enum.ts
+++ b/javascript/packages/fory/lib/gen/enum.ts
@@ -33,6 +33,9 @@ class EnumSerializerGenerator extends BaseSerializerGenerator
{
}
write(accessor: string): string {
+ if (!this.typeInfo.options?.inner) {
+ return this.builder.writer.varUInt32(accessor);
+ }
if (Object.values(this.typeInfo.options.inner).length < 1) {
throw new Error("An enum must contain at least one field");
}
@@ -121,6 +124,9 @@ class EnumSerializerGenerator extends
BaseSerializerGenerator {
}
read(accessor: (expr: string) => string): string {
+ if (!this.typeInfo.options?.inner) {
+ return accessor(this.builder.reader.varUInt32());
+ }
const enumValue = this.scope.uniqueName("enum_v");
return `
const ${enumValue} = ${this.builder.reader.varUInt32()};
diff --git a/javascript/packages/fory/lib/gen/index.ts
b/javascript/packages/fory/lib/gen/index.ts
index 64529f060..6df62034e 100644
--- a/javascript/packages/fory/lib/gen/index.ts
+++ b/javascript/packages/fory/lib/gen/index.ts
@@ -25,7 +25,6 @@ import { Scope } from "./scope";
import "./array";
import "./struct";
import "./string";
-import "./binary";
import "./bool";
import "./datetime";
import "./map";
@@ -77,7 +76,7 @@ export class Gen {
return;
}
const options = (<StructTypeInfo>typeInfo).options;
- if (options.props) {
+ if (options?.props) {
this.register(<StructTypeInfo>typeInfo);
Object.values(options.props).forEach((x) => {
this.traversalContainer(x);
diff --git a/javascript/packages/fory/lib/gen/typedArray.ts
b/javascript/packages/fory/lib/gen/typedArray.ts
index 6b2f86868..38659a4d7 100644
--- a/javascript/packages/fory/lib/gen/typedArray.ts
+++ b/javascript/packages/fory/lib/gen/typedArray.ts
@@ -19,45 +19,37 @@
import { Type, TypeInfo } from "../typeInfo";
import { CodecBuilder } from "./builder";
-import { BaseSerializerGenerator, SerializerGenerator } from "./serializer";
+import { BaseSerializerGenerator } from "./serializer";
import { CodegenRegistry } from "./router";
import { Scope } from "./scope";
import { TypeId } from "../type";
-function build(inner: TypeInfo) {
+function build(inner: TypeInfo, creator: string, size: number) {
return class TypedArraySerializerGenerator extends BaseSerializerGenerator {
typeInfo: TypeInfo;
- innerGenerator: SerializerGenerator;
constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
super(typeInfo, builder, scope);
this.typeInfo = <TypeInfo>typeInfo;
- this.innerGenerator = CodegenRegistry.newGeneratorByTypeInfo(inner,
builder, scope);
}
write(accessor: string): string {
- const item = this.scope.uniqueName("item");
return `
- ${this.builder.writer.varUInt32(`${accessor}.length`)}
-
${this.builder.writer.reserve(`${this.innerGenerator.getFixedSize()} *
${accessor}.length`)};
- for (const ${item} of ${accessor}) {
- ${this.innerGenerator.writeEmbed().write(item)}
- }
+ ${this.builder.writer.varUInt32(`${accessor}.byteLength`)}
+ ${this.builder.writer.arrayBuffer(`${accessor}.buffer`,
`${accessor}.byteOffset`, `${accessor}.byteLength`)}
`;
}
read(accessor: (expr: string) => string, refState: string): string {
const result = this.scope.uniqueName("result");
const len = this.scope.uniqueName("len");
- const idx = this.scope.uniqueName("idx");
+ const copied = this.scope.uniqueName("copied");
return `
const ${len} = ${this.builder.reader.varUInt32()};
- const ${result} = new Array(${len});
+ const ${copied} = ${this.builder.reader.buffer(len)}
+ const ${result} = new ${creator}(${copied}.buffer,
${copied}.byteOffset, ${copied}.byteLength / ${size});
${this.maybeReference(result, refState)}
- for (let ${idx} = 0; ${idx} < ${len}; ${idx}++) {
- ${this.innerGenerator.read(x => `${result}[${idx}] =
${x};`, "false")}
- }
${accessor(result)}
`;
}
@@ -68,15 +60,93 @@ function build(inner: TypeInfo) {
};
}
-CodegenRegistry.register(TypeId.BOOL_ARRAY, build(Type.bool()));
-CodegenRegistry.register(TypeId.INT8_ARRAY, build(Type.int8()));
-CodegenRegistry.register(TypeId.INT16_ARRAY, build(Type.int16()));
-CodegenRegistry.register(TypeId.INT32_ARRAY, build(Type.int32()));
-CodegenRegistry.register(TypeId.INT64_ARRAY, build(Type.int64()));
-CodegenRegistry.register(TypeId.UINT8_ARRAY, build(Type.uint8()));
-CodegenRegistry.register(TypeId.UINT16_ARRAY, build(Type.uint16()));
-CodegenRegistry.register(TypeId.UINT32_ARRAY, build(Type.uint32()));
-CodegenRegistry.register(TypeId.UINT64_ARRAY, build(Type.uint64()));
-CodegenRegistry.register(TypeId.FLOAT16_ARRAY, build(Type.float16()));
-CodegenRegistry.register(TypeId.FLOAT32_ARRAY, build(Type.float32()));
-CodegenRegistry.register(TypeId.FLOAT64_ARRAY, build(Type.float64()));
+class BoolArraySerializerGenerator extends BaseSerializerGenerator {
+ typeInfo: TypeInfo;
+
+ constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
+ super(typeInfo, builder, scope);
+ this.typeInfo = <TypeInfo>typeInfo;
+ }
+
+ write(accessor: string): string {
+ const item = this.scope.uniqueName("item");
+ return `
+ ${this.builder.writer.varUInt32(`${accessor}.length`)}
+ ${this.builder.writer.reserve(`${accessor}.length`)};
+ for (const ${item} of ${accessor}) {
+ ${this.builder.writer.uint8(`${item} ? 1 : 0`)}
+ }
+ `;
+ }
+
+ read(accessor: (expr: string) => string, refState: string): string {
+ const result = this.scope.uniqueName("result");
+ const len = this.scope.uniqueName("len");
+ const idx = this.scope.uniqueName("idx");
+ return `
+ const ${len} = ${this.builder.reader.varUInt32()};
+ const ${result} = new Array(${len});
+ ${this.maybeReference(result, refState)}
+ for (let ${idx} = 0; ${idx} < ${len}; ${idx}++) {
+ ${result}[${idx}] = ${this.builder.reader.uint8()} === 1;
+ }
+ ${accessor(result)}
+ `;
+ }
+
+ getFixedSize(): number {
+ return 7;
+ }
+}
+
+class Float16ArraySerializerGenerator extends BaseSerializerGenerator {
+ typeInfo: TypeInfo;
+
+ constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
+ super(typeInfo, builder, scope);
+ this.typeInfo = <TypeInfo>typeInfo;
+ }
+
+ write(accessor: string): string {
+ const item = this.scope.uniqueName("item");
+ return `
+ ${this.builder.writer.varUInt32(`${accessor}.length * 2`)}
+ ${this.builder.writer.reserve(`${accessor}.length * 2`)};
+ for (const ${item} of ${accessor}) {
+ ${this.builder.writer.float16(item)}
+ }
+ `;
+ }
+
+ read(accessor: (expr: string) => string, refState: string): string {
+ const result = this.scope.uniqueName("result");
+ const len = this.scope.uniqueName("len");
+ const idx = this.scope.uniqueName("idx");
+ return `
+ const ${len} = ${this.builder.reader.varUInt32()} / 2;
+ const ${result} = new Array(${len});
+ ${this.maybeReference(result, refState)}
+ for (let ${idx} = 0; ${idx} < ${len}; ${idx}++) {
+ ${result}[${idx}] = ${this.builder.reader.float16()};
+ }
+ ${accessor(result)}
+ `;
+ }
+
+ getFixedSize(): number {
+ return 7;
+ }
+}
+CodegenRegistry.register(TypeId.BOOL_ARRAY, BoolArraySerializerGenerator);
+CodegenRegistry.register(TypeId.BINARY, build(Type.uint8(), `Uint8Array`, 1));
+CodegenRegistry.register(TypeId.INT8_ARRAY, build(Type.int8(), `Int8Array`,
1));
+CodegenRegistry.register(TypeId.INT16_ARRAY, build(Type.int16(), `Int16Array`,
2));
+CodegenRegistry.register(TypeId.INT32_ARRAY, build(Type.int32(), `Int32Array`,
4));
+CodegenRegistry.register(TypeId.INT64_ARRAY, build(Type.int64(),
`BigInt64Array`, 8));
+CodegenRegistry.register(TypeId.UINT8_ARRAY, build(Type.uint8(), `Uint8Array`,
1));
+CodegenRegistry.register(TypeId.UINT16_ARRAY, build(Type.uint16(),
`Uint16Array`, 2));
+CodegenRegistry.register(TypeId.UINT32_ARRAY, build(Type.uint32(),
`Uint32Array`, 4));
+CodegenRegistry.register(TypeId.UINT64_ARRAY, build(Type.uint64(),
`BigUint64Array`, 8));
+CodegenRegistry.register(TypeId.FLOAT16_ARRAY,
Float16ArraySerializerGenerator);
+CodegenRegistry.register(TypeId.FLOAT32_ARRAY, build(Type.float32(),
`Float32Array`, 4));
+CodegenRegistry.register(TypeId.FLOAT64_ARRAY, build(Type.float64(),
`Float64Array`, 6));
diff --git a/javascript/packages/fory/lib/meta/TypeMeta.ts
b/javascript/packages/fory/lib/meta/TypeMeta.ts
index 6ade956fc..f52114baa 100644
--- a/javascript/packages/fory/lib/meta/TypeMeta.ts
+++ b/javascript/packages/fory/lib/meta/TypeMeta.ts
@@ -131,6 +131,10 @@ class FieldInfo {
return this.typeId;
}
+ getUserTypeId() {
+ return this.userTypeId;
+ }
+
hasFieldId() {
return typeof this.fieldId === "number";
}
@@ -332,13 +336,14 @@ export class TypeMeta {
const encodingFlags = (header >>> 6) & 0b11;
let size = (header >>> 2) & 0b1111;
const bigSize = size === FIELD_NAME_SIZE_THRESHOLD;
-
+ const nullable = (header & 0b10) > 0;
+ const trackingRef = (header & 0b1) > 0;
if (bigSize) {
size += reader.readVarUint32Small7();
}
// Read type ID
- const { typeId, userTypeId, trackingRef, nullable, options, fieldId } =
this.readTypeId(reader);
+ const { typeId, userTypeId, options, fieldId } = this.readTypeId(reader);
let fieldName: string;
if (encodingFlags === 3) {
@@ -751,7 +756,7 @@ export class TypeMeta {
listFields.sort(typeIdThenNameSorter);
setFields.sort(typeIdThenNameSorter);
mapFields.sort(typeIdThenNameSorter);
- otherFields.sort(typeIdThenNameSorter);
+ otherFields.sort(nameSorter);
return [
primitiveFields,
diff --git a/javascript/packages/fory/lib/typeInfo.ts
b/javascript/packages/fory/lib/typeInfo.ts
index c5c3ac368..9359eb7ae 100644
--- a/javascript/packages/fory/lib/typeInfo.ts
+++ b/javascript/packages/fory/lib/typeInfo.ts
@@ -115,7 +115,7 @@ export class TypeInfo<T = unknown> extends
ExtensibleFunction {
TypeInfo.fory = null;
}
- private constructor(private _typeId: number, userTypeId = -1) {
+ public constructor(private _typeId: number, userTypeId = -1) {
super(function (target: any, key?: string | { name?: string }) {
if (key === undefined) {
initMeta(target, that as unknown as StructTypeInfo);
@@ -582,7 +582,11 @@ export type HintResult<T> = T extends never ? any : T
extends {
: T extends {
type: typeof TypeId.DURATION;
}
- ? Date
+ ? number
+ : T extends {
+ type: typeof TypeId.DATE;
+ }
+ ? (Date | number)
: T extends {
type: typeof TypeId.TIMESTAMP;
}
@@ -788,6 +792,11 @@ export const Type = {
(TypeId.DURATION),
);
},
+ date() {
+ return TypeInfo.fromNonParam<typeof TypeId.DATE>(
+ (TypeId.DATE),
+ );
+ },
timestamp() {
return TypeInfo.fromNonParam<typeof TypeId.TIMESTAMP>(
(TypeId.TIMESTAMP),
@@ -825,13 +834,13 @@ export const Type = {
},
uint8Array() {
return TypeInfo.fromNonParam<typeof TypeId.UINT8_ARRAY>(
- (TypeId.INT8_ARRAY),
+ (TypeId.UINT8_ARRAY),
);
},
uint16Array() {
return TypeInfo.fromNonParam<typeof TypeId.UINT16_ARRAY>(
- (TypeId.INT16_ARRAY),
+ (TypeId.UINT16_ARRAY),
);
},
@@ -843,7 +852,7 @@ export const Type = {
},
uint64Array() {
return TypeInfo.fromNonParam<typeof TypeId.UINT64_ARRAY>(
- (TypeId.INT64_ARRAY),
+ (TypeId.UINT64_ARRAY),
);
},
diff --git a/javascript/packages/fory/lib/typeMetaResolver.ts
b/javascript/packages/fory/lib/typeMetaResolver.ts
index 00ede32f4..f7de94461 100644
--- a/javascript/packages/fory/lib/typeMetaResolver.ts
+++ b/javascript/packages/fory/lib/typeMetaResolver.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { StructTypeInfo, TypeInfo } from "./typeInfo";
+import { StructTypeInfo, Type, TypeInfo } from "./typeInfo";
import fory from "./fory";
import { TypeMeta } from "./meta/TypeMeta";
import { BinaryReader } from "./reader";
@@ -38,9 +38,9 @@ export class TypeMetaResolver {
const typeId = x.getTypeId();
const fieldName = x.getFieldName();
const declared = typeInfo.options.props?.[fieldName];
- const fieldTypeInfo = declared ??
this.fory.typeResolver.getTypeInfo(typeId);
+ let fieldTypeInfo = declared ??
this.fory.typeResolver.getTypeInfo(typeId);
if (!fieldTypeInfo) {
- throw new Error(`typeid: ${typeId} in prop ${fieldName} not
registered`);
+ fieldTypeInfo = Type.any();
}
if (!typeInfo.options.fieldInfo) {
typeInfo.options.fieldInfo = {};
@@ -62,12 +62,16 @@ export class TypeMetaResolver {
let typeInfo;
if (!TypeId.isNamedType(typeId)) {
typeInfo = this.fory.typeResolver.getTypeInfo(typeId, userTypeId);
+ if (!typeInfo) {
+ typeInfo = Type.struct({ typeId });
+ }
} else {
typeInfo = this.fory.typeResolver.getTypeInfo(`${ns}$${typeName}`);
+ if (!typeInfo) {
+ typeInfo = Type.struct({ typeName, namespace: ns });
+ }
}
- if (!typeInfo) {
- throw new Error(`${typeId} not registered`); // todo
- }
+
this.updateTypeInfo(typeMeta, typeInfo);
return this.fory.replaceSerializerReader(typeInfo);
}
diff --git a/javascript/packages/fory/lib/typeResolver.ts
b/javascript/packages/fory/lib/typeResolver.ts
index 9dbab56da..e11de059a 100644
--- a/javascript/packages/fory/lib/typeResolver.ts
+++ b/javascript/packages/fory/lib/typeResolver.ts
@@ -86,6 +86,8 @@ export default class TypeResolver {
return this.registerSerializer(typeInfo, new
Gen(this.fory).generateSerializer(typeInfo));
};
registerSerializer(Type.string());
+ registerSerializer(new TypeInfo(TypeId.ENUM));
+ registerSerializer(new TypeInfo(TypeId.NAMED_ENUM));
registerSerializer(Type.any());
registerSerializer(Type.array(Type.any()));
registerSerializer(Type.map(Type.any(), Type.any()));
@@ -112,12 +114,17 @@ export default class TypeResolver {
registerSerializer(Type.float64());
registerSerializer(Type.timestamp());
registerSerializer(Type.duration());
+ registerSerializer(Type.date());
registerSerializer(Type.set(Type.any()));
registerSerializer(Type.binary());
registerSerializer(Type.boolArray());
+ registerSerializer(Type.uint8Array());
registerSerializer(Type.int8Array());
+ registerSerializer(Type.uint16Array());
registerSerializer(Type.int16Array());
+ registerSerializer(Type.uint32Array());
registerSerializer(Type.int32Array());
+ registerSerializer(Type.uint64Array());
registerSerializer(Type.int64Array());
registerSerializer(Type.float16Array());
registerSerializer(Type.float32Array());
@@ -126,10 +133,10 @@ export default class TypeResolver {
this.float64Serializer = this.getSerializerById(TypeId.FLOAT64);
this.float32Serializer = this.getSerializerById(TypeId.FLOAT32);
this.varint32Serializer = this.getSerializerById(TypeId.VARINT32);
- this.taggedint64Serializer = this.getSerializerById(TypeId.TAGGED_INT64);
+ this.varInt64Serializer = this.getSerializerById(TypeId.VARINT64);
this.int64Serializer = this.getSerializerById((TypeId.INT64));
this.boolSerializer = this.getSerializerById((TypeId.BOOL));
- this.dateSerializer = this.getSerializerById((TypeId.TIMESTAMP));
+ this.datetimeSerializer = this.getSerializerById((TypeId.TIMESTAMP));
this.stringSerializer = this.getSerializerById((TypeId.STRING));
this.setSerializer = this.getSerializerById((TypeId.SET));
this.arraySerializer = this.getSerializerById((TypeId.LIST));
@@ -142,15 +149,17 @@ export default class TypeResolver {
this.int16ArraySerializer = this.getSerializerById(TypeId.INT16_ARRAY);
this.int32ArraySerializer = this.getSerializerById(TypeId.INT32_ARRAY);
this.int64ArraySerializer = this.getSerializerById(TypeId.INT64_ARRAY);
+ this.float32ArraySerializer = this.getSerializerById(TypeId.FLOAT32_ARRAY);
+ this.float64ArraySerializer = this.getSerializerById(TypeId.FLOAT64_ARRAY);
}
private float64Serializer: null | Serializer = null;
private float32Serializer: null | Serializer = null;
private varint32Serializer: null | Serializer = null;
- private taggedint64Serializer: null | Serializer = null;
+ private varInt64Serializer: null | Serializer = null;
private int64Serializer: null | Serializer = null;
private boolSerializer: null | Serializer = null;
- private dateSerializer: null | Serializer = null;
+ private datetimeSerializer: null | Serializer = null;
private stringSerializer: null | Serializer = null;
private setSerializer: null | Serializer = null;
private arraySerializer: null | Serializer = null;
@@ -163,6 +172,8 @@ export default class TypeResolver {
private int16ArraySerializer: null | Serializer = null;
private int32ArraySerializer: null | Serializer = null;
private int64ArraySerializer: null | Serializer = null;
+ private float32ArraySerializer: null | Serializer = null;
+ private float64ArraySerializer: null | Serializer = null;
constructor(private fory: Fory) {
}
@@ -265,7 +276,7 @@ export default class TypeResolver {
if (typeof v === "number") {
if (Number.isInteger(v)) {
if (v > MaxInt32 || v < MinInt32) {
- return this.taggedint64Serializer;
+ return this.varInt64Serializer;
}
return this.varint32Serializer;
}
@@ -276,7 +287,7 @@ export default class TypeResolver {
}
if (typeof v === "bigint") {
- return this.taggedint64Serializer;
+ return this.varInt64Serializer;
}
if (typeof v === "string") {
@@ -315,6 +326,14 @@ export default class TypeResolver {
return this.int64ArraySerializer;
}
+ if (v instanceof Float32Array) {
+ return this.float32ArraySerializer;
+ }
+
+ if (v instanceof Float64Array) {
+ return this.float64ArraySerializer;
+ }
+
if (Array.isArray(v)) {
return this.arraySerializer;
}
@@ -328,7 +347,7 @@ export default class TypeResolver {
}
if (v instanceof Date) {
- return this.dateSerializer;
+ return this.datetimeSerializer;
}
if (v instanceof Map) {
diff --git a/javascript/packages/fory/lib/writer/index.ts
b/javascript/packages/fory/lib/writer/index.ts
index 5c4036602..bbb28a419 100644
--- a/javascript/packages/fory/lib/writer/index.ts
+++ b/javascript/packages/fory/lib/writer/index.ts
@@ -218,6 +218,12 @@ export class BinaryWriter {
this.cursor += 8;
}
+ arrayBuffer(v: ArrayBuffer, byteOffset: number, byteLength: number) {
+ this.reserve(byteLength);
+ this.platformBuffer.set(new Uint8Array(v, byteOffset, byteLength),
this.cursor);
+ this.cursor += byteLength;
+ }
+
buffer(v: ArrayLike<number>) {
this.reserve(v.length);
this.platformBuffer.set(v, this.cursor);
diff --git a/javascript/test/array.test.ts b/javascript/test/array.test.ts
index 2a53633f0..c8c094858 100644
--- a/javascript/test/array.test.ts
+++ b/javascript/test/array.test.ts
@@ -54,24 +54,24 @@ describe('array', () => {
a6: Type.float64Array()
});
- const fory = new Fory({ refTracking: true }); const serializer =
fory.registerSerializer(typeinfo).serializer;
+ const fory = new Fory({ refTracking: true });
+ const serializer = fory.registerSerializer(typeinfo).serializer;
const input = fory.serialize({
a: [true, false],
- a2: [1, 2, 3],
- a3: [3, 5, 76],
- a4: [634, 564, 76],
- a6: [234243.555, 55654.6786],
+ a2: new Int16Array([1, 2, 3]),
+ a3: new Int32Array([3, 5, 76]),
+ a4: new BigInt64Array([634n, 564n, 76n]),
+ a6: new Float64Array([234243.555, 55654.679]),
}, serializer);
const result = fory.deserialize(
input
);
- result.a4 = result.a4.map(x => Number(x));
expect(result).toEqual({
a: [true, false],
- a2: [1, 2, 3],
- a3: [3, 5, 76],
- a4: [634, 564, 76],
- a6: [234243.555, 55654.6786],
+ a2: new Int16Array([1, 2, 3]),
+ a3: new Int32Array([3, 5, 76]),
+ a4: new BigInt64Array([634n, 564n, 76n]),
+ a6: new Float64Array([234243.555, 55654.679]),
})
});
@@ -85,7 +85,7 @@ describe('array', () => {
const fory = new Fory({ refTracking: true }); const serialize =
fory.registerSerializer(typeinfo).serializer;
const input = fory.serialize({
- a5: [2.43, 654.4, 55],
+ a5: new Float32Array([2.43, 654.4, 55]),
}, serialize);
const result = fory.deserialize(
input
diff --git a/javascript/test/crossLanguage.test.ts
b/javascript/test/crossLanguage.test.ts
index a21925f71..af5129efb 100644
--- a/javascript/test/crossLanguage.test.ts
+++ b/javascript/test/crossLanguage.test.ts
@@ -27,6 +27,7 @@ import Fory, {
import { describe, expect, test } from "@jest/globals";
import * as fs from "node:fs";
import * as beautify from 'js-beautify';
+import { TypeId } from "../packages/fory/lib/type";
const Byte = {
MAX_VALUE: 127,
@@ -223,11 +224,11 @@ describe("bool", () => {
Blue: 2,
White: 3,
};
- fory.registerSerializer(Type.enum(101, Color));
+ const { serialize: colorSerialize } =
fory.registerSerializer(Type.enum(101, Color));
// Deserialize various data types from Java
const deserializedData = [];
let cursor = 0;
- for (let i = 0; i < 28; i++) { // 28 serialized items from Java
+ for (let i = 0; i < 27; i++) { // 28 serialized items from Java
const deserializedItem = fory.deserialize(content.subarray(cursor));
cursor += fory.binaryReader.getCursor();
deserializedData.push(deserializedItem);
@@ -236,8 +237,26 @@ describe("bool", () => {
const bfs = []
// Serialize each deserialized item back
- for (const item of deserializedData) {
- const serializedData = fory.serialize(item);
+ for (let index = 0; index < deserializedData.length; index++) {
+ const item = deserializedData[index];
+ let serializedData;
+ if (index === 11) {
+ serializedData = fory.serialize(item,
fory.typeResolver.getSerializerById(TypeId.FLOAT32));
+ } else if (index === 12) {
+ serializedData = fory.serialize(item,
fory.typeResolver.getSerializerById(TypeId.FLOAT64));
+ } else if (index === 14) {
+ serializedData = fory.serialize(item,
fory.typeResolver.getSerializerById(TypeId.DATE));
+ } else if (index === 15) {
+ serializedData = fory.serialize(item,
fory.typeResolver.getSerializerById(TypeId.TIMESTAMP));
+ } else if (index === 16) {
+ serializedData = fory.serialize(item,
fory.typeResolver.getSerializerById(TypeId.BOOL_ARRAY));
+ } else if (index === 17) {
+ serializedData = fory.serialize(item,
fory.typeResolver.getSerializerById(TypeId.BINARY));
+ } else if (index === 26) {
+ serializedData = colorSerialize(item);
+ } else {
+ serializedData = fory.serialize(item);
+ }
bfs.push(serializedData);
}
@@ -621,50 +640,172 @@ describe("bool", () => {
});
test("test_skip_id_custom", () => {
- if (Boolean("1")) { return; }
- const fory = new Fory({
+ const fory1 = new Fory({
compatible: true
});
+ @Type.ext(103)
+ class MyExt {
+ id: number = 0;
+ }
+ fory1.registerSerializer(MyExt, {
+ write: (value: MyExt, writer: BinaryWriter, fory: Fory) => {
+ writer.varInt32(value.id);
+ },
+ read: (result: MyExt, reader: BinaryReader, fory: Fory) => {
+ result.id = reader.varInt32();
+ }
+ });
+
// Define empty wrapper for deserialization
@Type.struct(104)
- class EmptyWrapper { }
- fory.registerSerializer(EmptyWrapper);
+ class Empty { }
+ fory1.registerSerializer(Empty);
- const reader = new BinaryReader({});
- reader.reset(content);
+ const fory2 = new Fory({
+ compatible: true
+ });
- // Deserialize empty wrapper from Java
- let cursor = 0;
- const deserializedWrapper = fory.deserialize(content.subarray(cursor));
- cursor += fory.binaryReader.getCursor();
+ // Define Color enum
+ const Color = {
+ Green: 0,
+ Red: 1,
+ Blue: 2,
+ White: 3,
+ };
+ fory2.registerSerializer(Type.enum(101, Color));
- // Serialize the deserialized wrapper back
- const serializedData = fory.serialize(deserializedWrapper);
+ @Type.struct(102, {
+ id: Type.varInt32()
+ })
+ class MyStruct {
+ id: number = 0;
+ }
+ fory2.registerSerializer(MyStruct);
+
+ fory2.registerSerializer(MyExt, {
+ write: (value: MyExt, writer: BinaryWriter, fory: Fory) => {
+ writer.varInt32(value.id);
+ },
+ read: (result: MyExt, reader: BinaryReader, fory: Fory) => {
+ result.id = reader.varInt32();
+ }
+ });
+
+ @Type.struct(104, {
+ color: Type.enum(101, Color),
+ myStruct: Type.struct(102),
+ myExt: Type.ext(103)
+ })
+ class MyWrapper {
+ color: number = 0;
+ myStruct: MyStruct = new MyStruct();
+ myExt: MyExt = new MyExt();
+ }
+ fory2.registerSerializer(MyWrapper);
+
+
+ // Deserialize empty from Java
+ let cursor = 0;
+ const deserializedEmpty = fory1.deserialize(content.subarray(cursor));
+ cursor += fory1.binaryReader.getCursor();
+ expect(deserializedEmpty instanceof Empty).toEqual(true);
+
+ // Create wrapper object
+ const wrapper = new MyWrapper();
+ wrapper.color = Color.White;
+ wrapper.myStruct = new MyStruct();
+ wrapper.myStruct.id = 42;
+ wrapper.myExt = new MyExt();
+ wrapper.myExt.id = 43;
+
+ // Serialize wrapper
+ const serializedData = fory2.serialize(wrapper);
writeToFile(serializedData as Buffer);
});
test("test_skip_name_custom", () => {
- if (Boolean("1")) { return; }
- const fory = new Fory({
+ const fory1 = new Fory({
compatible: true
});
+ @Type.ext("my_ext")
+ class MyExt {
+ id: number = 0;
+ }
+ fory1.registerSerializer(MyExt, {
+ write: (value: MyExt, writer: BinaryWriter, fory: Fory) => {
+ writer.varInt32(value.id);
+ },
+ read: (result: MyExt, reader: BinaryReader, fory: Fory) => {
+ result.id = reader.varInt32();
+ }
+ });
+
// Define empty wrapper for deserialization
- @Type.struct({ namespace: "", typeName: "my_wrapper" })
- class EmptyWrapper { }
- fory.registerSerializer(EmptyWrapper);
+ @Type.struct("my_wrapper")
+ class Empty { }
+ fory1.registerSerializer(Empty);
- const reader = new BinaryReader({});
- reader.reset(content);
+ const fory2 = new Fory({
+ compatible: true
+ });
- // Deserialize empty wrapper from Java
- let cursor = 0;
- const deserializedWrapper = fory.deserialize(content.subarray(cursor));
- cursor += fory.binaryReader.getCursor();
+ // Define Color enum
+ const Color = {
+ Green: 0,
+ Red: 1,
+ Blue: 2,
+ White: 3,
+ };
+ fory2.registerSerializer(Type.enum("color", Color));
- // Serialize the deserialized wrapper back
- const serializedData = fory.serialize(deserializedWrapper);
+ @Type.struct("my_struct", {
+ id: Type.varInt32()
+ })
+ class MyStruct {
+ id: number = 0;
+ }
+ fory2.registerSerializer(MyStruct);
+
+ fory2.registerSerializer(MyExt, {
+ write: (value: MyExt, writer: BinaryWriter, fory: Fory) => {
+ writer.varInt32(value.id);
+ },
+ read: (result: MyExt, reader: BinaryReader, fory: Fory) => {
+ result.id = reader.varInt32();
+ }
+ });
+
+ @Type.struct("my_wrapper", {
+ color: Type.enum("color", Color),
+ myStruct: Type.struct("my_struct"),
+ myExt: Type.ext("my_ext")
+ })
+ class MyWrapper {
+ color: number = 0;
+ myStruct: MyStruct = new MyStruct();
+ myExt: MyExt = new MyExt();
+ }
+ fory2.registerSerializer(MyWrapper);
+
+
+ // Deserialize empty from Java
+ let cursor = 0;
+ const deserializedEmpty = fory1.deserialize(content.subarray(cursor));
+ cursor += fory1.binaryReader.getCursor();
+ expect(deserializedEmpty instanceof Empty).toEqual(true);
+
+ // Create wrapper object
+ const wrapper = new MyWrapper();
+ wrapper.color = Color.White;
+ wrapper.myStruct = new MyStruct();
+ wrapper.myStruct.id = 42;
+ wrapper.myExt = new MyExt();
+ wrapper.myExt.id = 43;
+
+ // Serialize wrapper
+ const serializedData = fory2.serialize(wrapper);
writeToFile(serializedData as Buffer);
});
@@ -832,7 +973,7 @@ describe("bool", () => {
})
class Dog {
age: number = 0;
- @ForyField({ nullable: true})
+ @ForyField({ nullable: true })
name: string | null = null;
}
fory.registerSerializer(Dog);
@@ -953,8 +1094,31 @@ describe("bool", () => {
writeToFile(serializedData as Buffer);
});
+ test("test_schema_evolution_compatible_reverse", () => {
+ const fory = new Fory({
+ compatible: true
+ });
+
+ @Type.struct(200)
+ class TwoStringFieldStruct {
+ @Type.string()
+ f1: string = "";
+ @Type.string()
+ f2: string = "";
+ }
+ fory.registerSerializer(TwoStringFieldStruct);
+
+ // Deserialize empty struct from Java
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
+
+ // Serialize the deserialized struct back
+ const serializedData = fory.serialize(deserializedStruct);
+ writeToFile(serializedData as Buffer);
+ });
+
test("test_schema_evolution_compatible", () => {
- if (Boolean("1")) { return; }
const fory = new Fory({
compatible: true
});
@@ -963,9 +1127,6 @@ describe("bool", () => {
class EmptyStruct { }
fory.registerSerializer(EmptyStruct);
- const reader = new BinaryReader({});
- reader.reset(content);
-
// Deserialize empty struct from Java
let cursor = 0;
const deserializedStruct = fory.deserialize(content.subarray(cursor));
@@ -1073,8 +1234,41 @@ describe("bool", () => {
writeToFile(serializedData as Buffer);
});
+ test("test_enum_schema_evolution_compatible_reverse", () => {
+ const fory = new Fory({
+ compatible: true
+ });
+
+ // Define and register TestEnum
+ const TestEnum = {
+ VALUE_A: 0,
+ VALUE_B: 1,
+ VALUE_C: 2,
+ };
+ fory.registerSerializer(Type.enum(210, TestEnum));
+
+ @Type.struct(211, {
+ f1: Type.enum(210, TestEnum),
+ f2: Type.enum(210, TestEnum)
+ })
+ class TwoEnumFieldStruct {
+ f1: number = 0; // enum value
+ f2: number = 0; // enum value
+ }
+ fory.registerSerializer(TwoEnumFieldStruct);
+
+ // Deserialize struct from Java
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
+
+ // Serialize the deserialized struct back
+ const serializedData = fory.serialize(deserializedStruct);
+ writeToFile(serializedData as Buffer);
+ });
+
+
test("test_enum_schema_evolution_compatible", () => {
- if (Boolean("1")) { return; }
const fory = new Fory({
compatible: true
});
@@ -1091,9 +1285,6 @@ describe("bool", () => {
class EmptyStruct { }
fory.registerSerializer(EmptyStruct);
- const reader = new BinaryReader({});
- reader.reset(content);
-
// Deserialize empty struct from Java
let cursor = 0;
const deserializedStruct = fory.deserialize(content.subarray(cursor));
diff --git a/javascript/test/datetime.test.ts b/javascript/test/datetime.test.ts
index b0b169230..73c77346e 100644
--- a/javascript/test/datetime.test.ts
+++ b/javascript/test/datetime.test.ts
@@ -26,10 +26,11 @@ describe('datetime', () => {
const fory = new Fory({ refTracking: true });
const now = new Date();
const input = fory.serialize(now);
- const result = fory.deserialize(
+ const result: Date | null = fory.deserialize(
input
);
- expect(result).toEqual(now)
+ expect(result?.getFullYear()).toEqual(now.getFullYear())
+ expect(result?.getDate()).toEqual(now.getDate())
});
test('should datetime work', () => {
const typeinfo = Type.struct("example.foo", {
@@ -43,7 +44,7 @@ describe('datetime', () => {
const result = fory.deserialize(
input
);
- expect(result).toEqual({ a: d, b: new Date('2021/10/20 00:00') })
+ expect(result).toEqual({ a: d, b: d.getTime() })
});
});
diff --git a/javascript/test/fory.test.ts b/javascript/test/fory.test.ts
index a84758717..2f5c4d7ad 100644
--- a/javascript/test/fory.test.ts
+++ b/javascript/test/fory.test.ts
@@ -73,7 +73,7 @@ describe('fory', () => {
testTypeInfo(typeinfo6, 123.456789)
const typeinfo7 = Type.binary()
- testTypeInfo(typeinfo7, new Uint8Array([1, 2, 3]), fromUint8Array(new
Uint8Array([1, 2, 3])));
+ testTypeInfo(typeinfo7, new Uint8Array([1, 2, 3]), new Uint8Array([1,
2, 3]));
const typeinfo8 = Type.string()
testTypeInfo(typeinfo8, '123')
diff --git a/javascript/test/object.test.ts b/javascript/test/object.test.ts
index 257da2ed1..7270270b0 100644
--- a/javascript/test/object.test.ts
+++ b/javascript/test/object.test.ts
@@ -113,7 +113,7 @@ describe('object', () => {
input
);
result.a.forEach(x => x.e = Number(x.e))
- expect(result).toEqual({ a: [{ b: "hel", c: true, d: 123, e: 123, f:
Buffer.from([1, 2, 3]) }] })
+ expect(result).toEqual({ a: [{ b: "hel", c: true, d: 123, e: 123, f: new
Uint8Array([1, 2, 3]) }] })
});
test('should write tag and read tag work', () => {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]