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 b734daa81 feat(JavaScript): support ForyField (#3314)
b734daa81 is described below

commit b734daa81398f34c105ae75ab98522401b832e04
Author: weipeng <[email protected]>
AuthorDate: Mon Feb 9 17:01:48 2026 +0800

    feat(JavaScript): support ForyField (#3314)
    
    ## Why?
    
    
    
    ## What does this PR do?
    1. Support `ForyField` decoration for config `nullable` and
    `trackingRef`
    2. More testcase were passed, the current progress is 23/39.
    
    ## Related issues
    
    
    
    ## 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/index.ts             |  2 +
 javascript/packages/fory/lib/fory.ts          |  1 -
 javascript/packages/fory/lib/gen/struct.ts    |  4 +-
 javascript/packages/fory/lib/meta/TypeMeta.ts | 20 +++++---
 javascript/packages/fory/lib/type.ts          |  1 -
 javascript/packages/fory/lib/typeInfo.ts      | 29 +++++++++++-
 javascript/test/crossLanguage.test.ts         | 68 +++++----------------------
 7 files changed, 57 insertions(+), 68 deletions(-)

diff --git a/javascript/packages/fory/index.ts 
b/javascript/packages/fory/index.ts
index a383a5548..00ea9b978 100644
--- a/javascript/packages/fory/index.ts
+++ b/javascript/packages/fory/index.ts
@@ -22,6 +22,7 @@ import {
   TypeInfo,
   ArrayTypeInfo,
   Type,
+  ForyField,
 } from "./lib/typeInfo";
 import { Serializer, Mode } from "./lib/type";
 import Fory from "./lib/fory";
@@ -36,6 +37,7 @@ export {
   Type,
   Mode,
   BinaryWriter,
+  ForyField,
   BinaryReader,
 };
 
diff --git a/javascript/packages/fory/lib/fory.ts 
b/javascript/packages/fory/lib/fory.ts
index fbef0e3ff..18ea876bb 100644
--- a/javascript/packages/fory/lib/fory.ts
+++ b/javascript/packages/fory/lib/fory.ts
@@ -59,7 +59,6 @@ export default class {
       useSliceString: Boolean(config?.useSliceString),
       hooks: config?.hooks || {},
       mode: config?.mode || Mode.SchemaConsistent,
-      classVersionHash: config?.classVersionHash || false,
     };
   }
 
diff --git a/javascript/packages/fory/lib/gen/struct.ts 
b/javascript/packages/fory/lib/gen/struct.ts
index f86ab2180..6f8627442 100644
--- a/javascript/packages/fory/lib/gen/struct.ts
+++ b/javascript/packages/fory/lib/gen/struct.ts
@@ -164,7 +164,7 @@ class StructSerializerGenerator extends 
BaseSerializerGenerator {
   write(accessor: string): string {
     const hash = this.typeMeta.computeStructHash();
     return `
-      ${this.builder.fory.config.classVersionHash ? 
this.builder.writer.int32(hash) : ""}
+      ${!this.builder.fory.isCompatible() ? this.builder.writer.int32(hash) : 
""}
       ${this.sortedProps.map(({ key, typeInfo }) => {
       const InnerGeneratorClass = CodegenRegistry.get(typeInfo.typeId);
       if (!InnerGeneratorClass) {
@@ -182,7 +182,7 @@ class StructSerializerGenerator extends 
BaseSerializerGenerator {
     const result = this.scope.uniqueName("result");
     const hash = this.typeMeta.computeStructHash();
     return `
-      ${this.builder.fory.config.classVersionHash
+      ${!this.builder.fory.isCompatible()
 ? `
         if(${this.builder.reader.int32()} !== ${hash}) {
           throw new Error("Read class version is not consistent with ${hash} ")
diff --git a/javascript/packages/fory/lib/meta/TypeMeta.ts 
b/javascript/packages/fory/lib/meta/TypeMeta.ts
index 8b5dce8d3..d132b5afe 100644
--- a/javascript/packages/fory/lib/meta/TypeMeta.ts
+++ b/javascript/packages/fory/lib/meta/TypeMeta.ts
@@ -132,6 +132,7 @@ interface InnerFieldInfo {
   trackingRef: boolean;
   nullable: boolean;
   options?: InnerFieldInfoOptions;
+  fieldId?: number;
 }
 class FieldInfo {
   constructor(
@@ -141,6 +142,7 @@ class FieldInfo {
     public trackingRef = false,
     public nullable = false,
     public options: InnerFieldInfoOptions = {},
+    public fieldId?: number
   ) {
   }
 
@@ -153,11 +155,11 @@ class FieldInfo {
   }
 
   hasFieldId() {
-    return false; // todo not impl yet.
+    return typeof this.fieldId === "number";
   }
 
   getFieldId() {
-    return 0;
+    return this.fieldId;
   }
 
   static writeTypeId(writer: BinaryWriter, typeInfo: InnerFieldInfo, 
writeFlags = false) {
@@ -231,7 +233,10 @@ export class TypeMeta {
   computeStructFingerprint(fields: FieldInfo[]) {
     let fieldInfos = [];
     for (const field of fields) {
-      const typeId = field.getTypeId();
+      let typeId = field.getTypeId();
+      if (TypeId.userDefinedType(typeId)) {
+        typeId = TypeId.UNKNOWN;
+      }
       let fieldIdentifier = "";
       if (field.getFieldId()) {
         fieldIdentifier = `${field.getFieldId()}`;
@@ -270,7 +275,7 @@ export class TypeMeta {
         } else if (fieldTypeId === TypeId.NAMED_UNION || fieldTypeId === 
TypeId.TYPED_UNION) {
           fieldTypeId = TypeId.UNION;
         }
-        const { trackingRef, nullable } = 
structTypeInfo.options.fieldInfo?.[fieldName] || {};
+        const { trackingRef, nullable, id } = 
structTypeInfo.options.fieldInfo?.[fieldName] || {};
         return new FieldInfo(
           fieldName,
           fieldTypeId,
@@ -278,6 +283,7 @@ export class TypeMeta {
           trackingRef,
           nullable,
           typeInfo.options,
+          id
         );
       });
     }
@@ -355,7 +361,7 @@ export class TypeMeta {
     }
 
     // Read type ID
-    const { typeId, userTypeId, trackingRef, nullable, options } = 
this.readTypeId(reader);
+    const { typeId, userTypeId, trackingRef, nullable, options, fieldId } = 
this.readTypeId(reader);
 
     let fieldName: string;
     if (encodingFlags === 3) {
@@ -367,7 +373,7 @@ export class TypeMeta {
       fieldName = fieldDecoder.decode(reader, size + 1, encoding || 
Encoding.UTF_8);
     }
 
-    return new FieldInfo(fieldName, typeId, userTypeId, trackingRef, nullable, 
options);
+    return new FieldInfo(fieldName, typeId, userTypeId, trackingRef, nullable, 
options, fieldId);
   }
 
   private static readTypeId(reader: BinaryReader, readFlag = false): 
InnerFieldInfo {
@@ -536,7 +542,7 @@ export class TypeMeta {
       let encoded: Uint8Array | null = null;
 
       if (fieldInfo.hasFieldId()) {
-        size = fieldInfo.getFieldId();
+        size = fieldInfo.getFieldId()!;
         encodingFlags = 3; // TAG_ID encoding
       } else {
         // Convert camelCase to snake_case for xlang compatibility
diff --git a/javascript/packages/fory/lib/type.ts 
b/javascript/packages/fory/lib/type.ts
index 278157732..10d54d943 100644
--- a/javascript/packages/fory/lib/type.ts
+++ b/javascript/packages/fory/lib/type.ts
@@ -253,7 +253,6 @@ export interface Config {
     afterCodeGenerated?: (code: string) => string;
   };
   mode: Mode;
-  classVersionHash?: boolean;
 }
 
 export interface WithForyClsInfo {
diff --git a/javascript/packages/fory/lib/typeInfo.ts 
b/javascript/packages/fory/lib/typeInfo.ts
index f3b846f9a..88696b15f 100644
--- a/javascript/packages/fory/lib/typeInfo.ts
+++ b/javascript/packages/fory/lib/typeInfo.ts
@@ -20,6 +20,26 @@
 import Fory from "./fory";
 import { ForyTypeInfoSymbol, TypeId, Mode } from "./type";
 
+const targetFieldInfo = new WeakMap<new () => any, { [key: string]: 
StructFieldInfo }>();
+
+export const ForyField = (fieldInfo: {
+  nullable?: boolean,
+  trackingRef?: boolean,
+  id?: number,
+}) => {
+  return (target: any, key: string | {name?: string}) => {
+    const creator = target.constructor;
+    if (!targetFieldInfo.has(creator)) {
+      targetFieldInfo.set(creator, {});
+    }
+    const keyString = typeof key === "string" ? key : key?.name;
+    if (!keyString) {
+      throw new Error("Decorators can only be placed on classes and fields");
+    }
+    targetFieldInfo.get(creator)![keyString] = fieldInfo;
+  };
+}
+
 const initMeta = (target: new () => any, typeInfo: TypeInfo) => {
   if (!target.prototype) {
     target.prototype = {};
@@ -32,6 +52,13 @@ const initMeta = (target: new () => any, typeInfo: TypeInfo) 
=> {
   if (!typeInfo.options.props) {
     typeInfo.options.props = {}
   }
+  if(targetFieldInfo.has(target)) {
+    const structTypeInfo = (typeInfo as StructTypeInfo);
+    if (!structTypeInfo.options.fieldInfo) {
+      structTypeInfo.options.fieldInfo = {};
+    }
+    Object.assign(structTypeInfo.options.fieldInfo, 
targetFieldInfo.get(target));
+  }
   Object.assign(typeInfo.options.props, targetFields.get(target) || {})
   Object.defineProperties(target.prototype, {
     [ForyTypeInfoSymbol]: {
@@ -340,7 +367,7 @@ export class TypeInfo<T = unknown> extends 
ExtensibleFunction {
   }
 }
 
-type StructFieldInfo = {nullable?: boolean, trackingRef?: boolean}
+type StructFieldInfo = {nullable?: boolean, trackingRef?: boolean, id?: number}
 export interface StructTypeInfo extends TypeInfo {
   options: {
     props?: { [key: string]: TypeInfo };
diff --git a/javascript/test/crossLanguage.test.ts 
b/javascript/test/crossLanguage.test.ts
index f5650b91e..5c16b7782 100644
--- a/javascript/test/crossLanguage.test.ts
+++ b/javascript/test/crossLanguage.test.ts
@@ -21,6 +21,7 @@ import Fory, {
   BinaryReader,
   BinaryWriter,
   Mode,
+  ForyField,
   Type,
 } from "../packages/fory/index";
 import { describe, expect, test } from "@jest/globals";
@@ -182,40 +183,12 @@ describe("bool", () => {
     writeToFile(writer.dump() as Buffer);
   });
   test("test_murmurhash3", () => {
-    if (Boolean("1")) { return; }
+  const { x64hash128 } = require("../packages/fory/lib/murmurHash3");
     const reader = new BinaryReader({});
     reader.reset(content);
-
-    // Read the two hash values written by Java
-    const hash1Bytes = new Uint8Array(16);
-    for (let i = 0; i < 16; i++) {
-      hash1Bytes[i] = reader.uint8();
-    }
-
-    const hash2Bytes = new Uint8Array(16);
-    for (let i = 0; i < 16; i++) {
-      hash2Bytes[i] = reader.uint8();
-    }
-
-    // Import murmurHash3 function
-    const { x64hash128 } = require("../packages/fory/lib/murmurHash3");
-
-    // Test hash1: hash of [1, 2, 8] with seed 47
-    const testData1 = new Uint8Array([1, 2, 8]);
-    const result1 = x64hash128(testData1, 47);
-    const result1Bytes = new Uint8Array(result1.buffer);
-
-    // Test hash2: hash of "01234567890123456789" with seed 47
-    const testData2 = new TextEncoder().encode("01234567890123456789");
-    const result2 = x64hash128(testData2, 47);
-    const result2Bytes = new Uint8Array(result2.buffer);
-
-    // Write our computed hashes back
-    const writer = new BinaryWriter();
-    writer.reserve(32);
-    writer.buffer(result1Bytes);
-    writer.buffer(result2Bytes);
-    writeToFile(writer.dump() as Buffer);
+    let dataview = x64hash128(new Uint8Array([1, 2, 8]), 47);
+    expect(reader.int64()).toEqual(dataview.getBigInt64(0));
+    expect(reader.int64()).toEqual(dataview.getBigInt64(8));
   });
   test("test_string_serializer", () => {
     const fory = new Fory({
@@ -698,7 +671,6 @@ describe("bool", () => {
   test("test_consistent_named", () => {
     const fory = new Fory({
       mode: Mode.SchemaConsistent,
-      classVersionHash: true,
     });
 
     // Define and register Color enum
@@ -758,18 +730,18 @@ describe("bool", () => {
   });
 
   test("test_struct_version_check", () => {
-    if (Boolean("1")) { return; }
     const fory = new Fory({
-      mode: Mode.Compatible
+      mode: Mode.SchemaConsistent,
     });
 
     @Type.struct(201, {
-      f1: Type.int32(),
+      f1: Type.varInt32(),
       f2: Type.string(),
       f3: Type.float64()
     })
     class VersionCheckStruct {
       f1: number = 0;
+      @ForyField({ nullable: true })
       f2: string | null = null;
       f3: number = 0;
     }
@@ -918,22 +890,19 @@ describe("bool", () => {
     writeToFile(writer.dump() as Buffer);
   });
   test("test_one_string_field_schema", () => {
-    if (Boolean("1")) { return; }
     const fory = new Fory({
-      mode: Mode.Compatible
+      mode: Mode.SchemaConsistent
     });
 
     @Type.struct(200, {
       f1: Type.string()
     })
     class OneStringFieldStruct {
+      @ForyField({nullable: true})
       f1: string | null = null;
     }
     fory.registerSerializer(OneStringFieldStruct);
 
-    const reader = new BinaryReader({});
-    reader.reset(content);
-
     // Deserialize struct from Java
     let cursor = 0;
     const deserializedStruct = fory.deserialize(content.subarray(cursor));
@@ -944,7 +913,6 @@ describe("bool", () => {
     writeToFile(serializedData as Buffer);
   });
   test("test_one_string_field_compatible", () => {
-    if (Boolean("1")) { return; }
     const fory = new Fory({
       mode: Mode.Compatible
     });
@@ -953,6 +921,7 @@ describe("bool", () => {
       f1: Type.string()
     })
     class OneStringFieldStruct {
+      @ForyField({nullable: true})
       f1: string | null = null;
     }
     fory.registerSerializer(OneStringFieldStruct);
@@ -971,7 +940,6 @@ describe("bool", () => {
   });
 
   test("test_two_string_field_compatible", () => {
-    if (Boolean("1")) { return; }
     const fory = new Fory({
       mode: Mode.Compatible
     });
@@ -986,9 +954,6 @@ describe("bool", () => {
     }
     fory.registerSerializer(TwoStringFieldStruct);
 
-    const reader = new BinaryReader({});
-    reader.reset(content);
-
     // Deserialize struct from Java
     let cursor = 0;
     const deserializedStruct = fory.deserialize(content.subarray(cursor));
@@ -1022,9 +987,8 @@ describe("bool", () => {
     writeToFile(serializedData as Buffer);
   });
   test("test_one_enum_field_schema", () => {
-    if (Boolean("1")) { return; }
     const fory = new Fory({
-      mode: Mode.Compatible
+      mode: Mode.SchemaConsistent
     });
 
     // Define and register TestEnum
@@ -1057,7 +1021,6 @@ describe("bool", () => {
   });
 
   test("test_one_enum_field_compatible", () => {
-    if (Boolean("1")) { return; }
     const fory = new Fory({
       mode: Mode.Compatible
     });
@@ -1078,9 +1041,6 @@ describe("bool", () => {
     }
     fory.registerSerializer(OneEnumFieldStruct);
 
-    const reader = new BinaryReader({});
-    reader.reset(content);
-
     // Deserialize struct from Java
     let cursor = 0;
     const deserializedStruct = fory.deserialize(content.subarray(cursor));
@@ -1092,7 +1052,6 @@ describe("bool", () => {
   });
 
   test("test_two_enum_field_compatible", () => {
-    if (Boolean("1")) { return; }
     const fory = new Fory({
       mode: Mode.Compatible
     });
@@ -1115,9 +1074,6 @@ describe("bool", () => {
     }
     fory.registerSerializer(TwoEnumFieldStruct);
 
-    const reader = new BinaryReader({});
-    reader.reset(content);
-
     // Deserialize struct from Java
     let cursor = 0;
     const deserializedStruct = fory.deserialize(content.subarray(cursor));


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to