This is an automated email from the ASF dual-hosted git repository.

xiazcy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


The following commit(s) were added to refs/heads/master by this push:
     new cf0118a45c Add typed numeric wrappers and precise number mode to 
gremlin-javascript (#3427)
cf0118a45c is described below

commit cf0118a45c44947cc6f2613d7247ec3246aebb74
Author: kirill-stepanishin <[email protected]>
AuthorDate: Tue May 26 13:04:55 2026 -0700

    Add typed numeric wrappers and precise number mode to gremlin-javascript 
(#3427)
    
    Assisted-by: Claude Code:claude-opus-4-6
---
 CHANGELOG.asciidoc                                 |   1 +
 docs/src/reference/gremlin-variants.asciidoc       |  51 +-
 docs/src/upgrade/release-4.x.x.asciidoc            |  47 ++
 .../gremlin-javascript/lib/driver/connection.ts    |   5 +-
 gremlin-js/gremlin-javascript/lib/index.ts         |  12 +
 .../gremlin-javascript/lib/process/gremlin-lang.ts |  26 +-
 .../lib/structure/io/binary/GraphBinary.js         | 115 +++--
 .../structure/io/binary/internals/AnySerializer.js |  11 +-
 .../internals/NumberSerializationStrategy.js       |  34 +-
 gremlin-js/gremlin-javascript/lib/utils.ts         | 156 +++++-
 gremlin-js/gremlin-javascript/test/helper.js       |   2 +
 .../test/integration/client-tests.js               |  33 +-
 .../gremlin-javascript/test/unit/exports-test.js   |  13 +
 .../test/unit/graphbinary/precise-mode-test.js     | 521 +++++++++++++++++++++
 .../test/unit/graphbinary/typed-number-test.js     | 212 +++++++++
 .../test/unit/gremlin-lang-test.js                 | 134 +++++-
 16 files changed, 1315 insertions(+), 58 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 284bbcca26..e312807286 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,6 +25,7 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 [[release-4-0-0]]
 === TinkerPop 4.0.0 (Release Date: NOT OFFICIALLY RELEASED YET)
 
+* Added typed numeric wrappers and `preciseNumbers` connection option to 
`gremlin-javascript` for explicit control over numeric type serialization and 
deserialization.
 * Added `NextN(n)` to `Traversal` in `gremlin-go` for batched result 
iteration, providing API parity with `next(n)` in the Java, Python, and .NET 
GLVs, and updated the Go translators in `gremlin-core` and `gremlin-javascript` 
to emit `NextN(n)` for the batched form.
 * Added Gremlator, a single page web application, that translates Gremlin into 
various programming languages like Javascript and Python.
 * Removed `uuid` dependency from `gremlin-javascript` in favor of the built-in 
`globalThis.crypto.randomUUID()`.
diff --git a/docs/src/reference/gremlin-variants.asciidoc 
b/docs/src/reference/gremlin-variants.asciidoc
index 2ad34f1d9e..521a7017bf 100644
--- a/docs/src/reference/gremlin-variants.asciidoc
+++ b/docs/src/reference/gremlin-variants.asciidoc
@@ -1677,6 +1677,7 @@ can be passed in the constructor of a new `Client` or 
`DriverRemoteConnection` :
 |options.traversalSource |String |The traversal source. |'g'
 |options.headers |Object |Additional HTTP header key/values included with each 
request. |undefined
 |options.interceptors |RequestInterceptor/RequestInterceptor[] |One or more 
functions that can modify the HTTP request before it is sent. |undefined
+|options.preciseNumbers |Boolean |When `true`, wraps deserialized numbers in 
typed wrappers that preserve the server's original type. |undefined
 |options.reader |GraphBinaryReader |The reader to use for deserializing 
responses. |GraphBinaryReader
 |options.writer |GraphBinaryWriter |The writer to use for serializing 
requests. |GraphBinaryWriter
 |options.enableUserAgentOnConnect |Boolean |Determines if a user agent header 
will be sent with requests. |true
@@ -1975,15 +1976,61 @@ g.V().hasLabel('person').groupCount().by('age')
 Either of the above two options accomplishes the desired goal as both prevent 
`groupCount()` from having to process
 the possibility of `null`.
 
+[[gremlin-javascript-numeric-types]]
+=== Numeric Types
+
+JavaScript has a single `Number` type (IEEE 754 double) which cannot 
distinguish between Gremlin numeric types. The driver
+provides typed wrapper classes and factory functions that give explicit 
control over serialization and deserialization.
+
+Wrapping a value selects the GremlinLang type suffix and GraphBinary type code 
sent to the server. Without wrappers the
+driver infers types automatically, so existing code is unaffected.
+
+[source,javascript]
+----
+const { toLong, toInt, toFloat, toDouble, toShort, toByte } = 
gremlin.structure;
+
+g.V().has('age', toInt(29)).next();
+g.V().has('score', toFloat(3.14)).next();
+g.V().has('id', toLong('9007199254740993')).next();
+----
+
+`toLong()` accepts `number`, `string`, or `bigint`. String and bigint inputs 
support the full signed 64-bit range;
+number inputs must be within the safe integer range (`RangeError` is thrown 
otherwise).
+
+By default, the driver deserializes all numeric values as plain `Number` (or 
`BigInt` for large longs). To preserve the
+server's original type, set `preciseNumbers` to `true` on the connection:
+
+[source,javascript]
+----
+const g = traversal().with_(new 
DriverRemoteConnection('http://localhost:8182/gremlin', {
+  preciseNumbers: true
+}));
+
+const v = await g.V(1).elementMap().next();
+const age = v.value.get('age');  // Int { value: 29, type: 'int' }
+age + 1;                         // 30 — wrappers support arithmetic via 
valueOf()
+----
+
+The `unwrap()` helper extracts the raw value from any wrapper, passing 
non-wrapper values through unchanged:
+
+[source,javascript]
+----
+const { unwrap } = gremlin.structure;
+unwrap(toInt(29));  // 29
+unwrap('hello');    // 'hello'
+----
+
 [[gremlin-javascript-limitations]]
 === Limitations
 
 * JavaScript's `Number` type is an IEEE 754 double-precision float. `Float`, 
`Byte`, and `Short` values from the server
-are deserialized as `Number` and lose their original type information.
+are deserialized as `Number` and lose their original type information. Use 
`preciseNumbers: true` to preserve the
+original types — see <<gremlin-javascript-numeric-types>>.
 * `Long` values outside the safe integer range (|n| > 2^53 - 1) are 
deserialized as `BigInt` to preserve precision.
 Values within the safe range are deserialized as `Number`. The same server 
type may produce different JavaScript types.
 * `Number.isInteger(1.0)` is `true` in JavaScript, so the driver cannot 
distinguish integer values from whole-number
-doubles. `BigDecimal` is not implemented.
+doubles. `BigDecimal` is not implemented. Typed wrappers (e.g. `toInt()`, 
`toDouble()`) can be used to control the
+exact type sent to the server — see <<gremlin-javascript-numeric-types>>.
 * The driver applies GremlinLang type suffixes automatically based on value 
characteristics: integers within the 32-bit
 signed range are unsuffixed (Int), integers beyond that up to 
`Number.MAX_SAFE_INTEGER` use the `L` suffix (Long),
 non-integer numbers and integers beyond the safe range use the `D` suffix 
(Double), and `BigInt` values use the `N`
diff --git a/docs/src/upgrade/release-4.x.x.asciidoc 
b/docs/src/upgrade/release-4.x.x.asciidoc
index 960c090f5d..aa14264ca0 100644
--- a/docs/src/upgrade/release-4.x.x.asciidoc
+++ b/docs/src/upgrade/release-4.x.x.asciidoc
@@ -442,6 +442,53 @@ beyond this limit will be rejected with an error.
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-3247[TINKERPOP-3247]
 
+==== JavaScript Typed Numeric Wrappers
+
+JavaScript has a single `Number` type (IEEE 754 double) which loses the 
distinction between Gremlin numeric types like
+`int`, `float`, `long`, and `double`. The `gremlin-javascript` driver now 
provides typed wrapper classes and factory
+functions that give explicit control over how numbers are serialized and 
deserialized.
+
+On the *serialization* side, wrapping a value controls the GremlinLang type 
suffix and GraphBinary type code sent to
+the server:
+
+[source,javascript]
+----
+const { toInt, toLong, toFloat, toDouble } = gremlin.structure;
+
+g.V().has('age', toInt(29)).next();        // age sent as Int
+g.V().has('score', toFloat(3.14)).next();  // score sent as Float
+g.V().has('id', toLong('9007199254740993')).next(); // id sent as Long
+----
+
+`toLong()` accepts `number`, `string`, or `bigint`. Number inputs must be 
within the safe integer range (throws
+`RangeError` otherwise); string and bigint inputs support the full signed 
64-bit range.
+
+Without wrappers, the driver continues to infer types automatically from the 
JavaScript value, so existing code is
+unaffected.
+
+On the *deserialization* side, a new `preciseNumbers: true` connection option 
wraps incoming numeric values in the
+same typed wrappers, preserving the server's original type information:
+
+[source,javascript]
+----
+const g = traversal().with_(new 
DriverRemoteConnection('http://localhost:8182/gremlin', {
+  preciseNumbers: true
+}));
+
+const v = await g.V(1).elementMap().next();
+const age = v.value.get('age');  // Int { value: 29, type: 'int' }
+age + 1;                         // 30 — wrappers support arithmetic via 
valueOf()
+----
+
+The `unwrap()` helper extracts the raw value from any wrapper, passing 
non-wrapper values through unchanged:
+
+[source,javascript]
+----
+const { unwrap } = gremlin.structure;
+unwrap(toInt(29));  // 29
+unwrap('hello');    // 'hello'
+----
+
 === Upgrading for Providers
 
 ==== Graph System Providers
diff --git a/gremlin-js/gremlin-javascript/lib/driver/connection.ts 
b/gremlin-js/gremlin-javascript/lib/driver/connection.ts
index ea1b231d59..12f7da0467 100644
--- a/gremlin-js/gremlin-javascript/lib/driver/connection.ts
+++ b/gremlin-js/gremlin-javascript/lib/driver/connection.ts
@@ -24,7 +24,7 @@
 import { Buffer } from 'buffer';
 import { EventEmitter } from 'eventemitter3';
 import type { Agent } from 'node:http';
-import ioc from '../structure/io/binary/GraphBinary.js';
+import ioc, { createPreciseReader } from 
'../structure/io/binary/GraphBinary.js';
 import StreamReader from '../structure/io/binary/internals/StreamReader.js';
 import * as utils from '../utils.js';
 import ResultSet from './result-set.js';
@@ -53,6 +53,7 @@ export type ConnectionOptions = {
   ca?: string[];
   cert?: string | string[] | Buffer;
   pfx?: string | Buffer;
+  preciseNumbers?: boolean;
   reader?: any;
   rejectUnauthorized?: boolean;
   traversalSource?: string;
@@ -87,7 +88,7 @@ export default class Connection extends EventEmitter {
   ) {
     super();
 
-    this._reader = options.reader || graphBinaryReader;
+    this._reader = options.reader || (options.preciseNumbers === true ? 
createPreciseReader() : graphBinaryReader);
     this._writer = 'writer' in options ? options.writer : graphBinaryWriter;
     this.traversalSource = options.traversalSource || 'g';
     this._enableUserAgentOnConnect = options.enableUserAgentOnConnect !== 
false;
diff --git a/gremlin-js/gremlin-javascript/lib/index.ts 
b/gremlin-js/gremlin-javascript/lib/index.ts
index fd40186eb6..f79c31e948 100644
--- a/gremlin-js/gremlin-javascript/lib/index.ts
+++ b/gremlin-js/gremlin-javascript/lib/index.ts
@@ -87,6 +87,18 @@ export const structure = {
   Vertex: graph.Vertex,
   VertexProperty: graph.VertexProperty,
   toLong: utils.toLong,
+  toInt: utils.toInt,
+  toFloat: utils.toFloat,
+  toDouble: utils.toDouble,
+  toShort: utils.toShort,
+  toByte: utils.toByte,
+  Int: utils.Int,
+  Float: utils.Float,
+  Double: utils.Double,
+  Short: utils.Short,
+  Byte: utils.Byte,
+  Long: utils.Long,
+  unwrap: utils.unwrap,
 };
 
 export default { driver, process, structure };
diff --git a/gremlin-js/gremlin-javascript/lib/process/gremlin-lang.ts 
b/gremlin-js/gremlin-javascript/lib/process/gremlin-lang.ts
index 6d8c09287b..2a07ad8dec 100644
--- a/gremlin-js/gremlin-javascript/lib/process/gremlin-lang.ts
+++ b/gremlin-js/gremlin-javascript/lib/process/gremlin-lang.ts
@@ -19,7 +19,7 @@
 
 import { P, TextP, EnumValue } from './traversal.js';
 import { OptionsStrategy, TraversalStrategy } from './traversal-strategy.js';
-import { Long } from '../utils.js';
+import { Long, Int, Float, Double, Short, Byte, INT32_MIN, INT32_MAX } from 
'../utils.js';
 import { Vertex } from '../structure/graph.js';
 import { Buffer } from 'buffer';
 
@@ -72,6 +72,21 @@ export default class GremlinLang {
     if (arg instanceof Long) {
       return String(arg.value) + 'L';
     }
+    if (arg instanceof Float) {
+      return GremlinLang._fpAsString(arg.value, 'F');
+    }
+    if (arg instanceof Double) {
+      return GremlinLang._fpAsString(arg.value, 'D');
+    }
+    if (arg instanceof Short) {
+      return String(arg.value) + 'S';
+    }
+    if (arg instanceof Byte) {
+      return String(arg.value) + 'B';
+    }
+    if (arg instanceof Int) {
+      return String(arg.value);
+    }
     if (typeof arg === 'bigint') {
       return String(arg) + 'N';
     }
@@ -80,7 +95,7 @@ export default class GremlinLang {
       if (arg === Infinity) return '+Infinity';
       if (arg === -Infinity) return '-Infinity';
       if (!Number.isInteger(arg)) return String(arg) + 'D';
-      if (arg >= -2147483648 && arg <= 2147483647) return String(arg);
+      if (arg >= INT32_MIN && arg <= INT32_MAX) return String(arg);
       // Outside safe integer range, values have lost precision and may exceed 
Java Long — emit as Double.
       if (arg > Number.MAX_SAFE_INTEGER || arg < -Number.MAX_SAFE_INTEGER) 
return String(arg) + 'D';
       return String(arg) + 'L';
@@ -187,6 +202,13 @@ export default class GremlinLang {
     return this;
   }
   
+  private static _fpAsString(v: number, suffix: 'F' | 'D'): string {
+    if (v === Infinity) return '+Infinity';
+    if (v === -Infinity) return '-Infinity';
+    if (Number.isNaN(v)) return 'NaN';
+    return Number.isInteger(v) ? `${v}.0${suffix}` : `${v}${suffix}`;
+  }
+
   getGremlin(prefix: string = 'g'): string {
     if (this.gremlin.length > 0 && this.gremlin[0] !== '.') {
       return this.gremlin;
diff --git 
a/gremlin-js/gremlin-javascript/lib/structure/io/binary/GraphBinary.js 
b/gremlin-js/gremlin-javascript/lib/structure/io/binary/GraphBinary.js
index b8930f91c4..4bd402b596 100644
--- a/gremlin-js/gremlin-javascript/lib/structure/io/binary/GraphBinary.js
+++ b/gremlin-js/gremlin-javascript/lib/structure/io/binary/GraphBinary.js
@@ -70,48 +70,79 @@ import AnySerializer from './internals/AnySerializer.js';
 import GraphBinaryReader from './internals/GraphBinaryReader.js';
 import GraphBinaryWriter from './internals/GraphBinaryWriter.js';
 
-const ioc = {};
-
-ioc.DataType = DataType;
-ioc.utils = utils;
-
-ioc.serializers = {};
-
-ioc.intSerializer = new IntSerializer(ioc);
-ioc.longSerializer = new LongSerializer(ioc);
-ioc.stringSerializer = new StringSerializer(ioc, ioc.DataType.STRING);
-ioc.dateTimeSerializer = new DateTimeSerializer(ioc);
-ioc.doubleSerializer = new DoubleSerializer(ioc);
-ioc.floatSerializer = new FloatSerializer(ioc);
-ioc.listSerializer = new ArraySerializer(ioc, ioc.DataType.LIST);
-ioc.mapSerializer = new MapSerializer(ioc);
-ioc.setSerializer = new SetSerializer(ioc, ioc.DataType.SET);
-ioc.uuidSerializer = new UuidSerializer(ioc);
-ioc.edgeSerializer = new EdgeSerializer(ioc);
-ioc.pathSerializer = new PathSerializer(ioc);
-ioc.propertySerializer = new PropertySerializer(ioc);
-ioc.vertexSerializer = new VertexSerializer(ioc);
-ioc.vertexPropertySerializer = new VertexPropertySerializer(ioc);
-ioc.bigIntegerSerializer = new BigIntegerSerializer(ioc);
-ioc.byteSerializer = new ByteSerializer(ioc);
-ioc.binarySerializer = new BinarySerializer(ioc);
-ioc.shortSerializer = new ShortSerializer(ioc);
-ioc.booleanSerializer = new BooleanSerializer(ioc);
-ioc.markerSerializer = new MarkerSerializer(ioc);
-ioc.unspecifiedNullSerializer = new UnspecifiedNullSerializer(ioc);
-ioc.enumSerializer = new EnumSerializer(ioc);
-
-// Register stub serializers for unimplemented v4 types
-new StubSerializer(ioc, ioc.DataType.TREE, 'Tree');
-new StubSerializer(ioc, ioc.DataType.GRAPH, 'Graph');
-new StubSerializer(ioc, ioc.DataType.COMPOSITEPDT, 'CompositePDT');
-new StubSerializer(ioc, ioc.DataType.PRIMITIVEPDT, 'PrimitivePDT');
-
-ioc.numberSerializationStrategy = new NumberSerializationStrategy(ioc);
-ioc.anySerializer = new AnySerializer(ioc);
-
-ioc.graphBinaryReader = new GraphBinaryReader(ioc);
-ioc.graphBinaryWriter = new GraphBinaryWriter(ioc);
+import { Float, Double, Int, Long, Short, Byte } from '../../../utils.js';
+
+function createIoc(anySerializerOptions) {
+  const ioc = {};
+
+  ioc.DataType = DataType;
+  ioc.utils = utils;
+
+  ioc.serializers = {};
+
+  ioc.intSerializer = new IntSerializer(ioc);
+  ioc.longSerializer = new LongSerializer(ioc);
+  ioc.stringSerializer = new StringSerializer(ioc, ioc.DataType.STRING);
+  ioc.dateTimeSerializer = new DateTimeSerializer(ioc);
+  ioc.doubleSerializer = new DoubleSerializer(ioc);
+  ioc.floatSerializer = new FloatSerializer(ioc);
+  ioc.listSerializer = new ArraySerializer(ioc, ioc.DataType.LIST);
+  ioc.mapSerializer = new MapSerializer(ioc);
+  ioc.setSerializer = new SetSerializer(ioc, ioc.DataType.SET);
+  ioc.uuidSerializer = new UuidSerializer(ioc);
+  ioc.edgeSerializer = new EdgeSerializer(ioc);
+  ioc.pathSerializer = new PathSerializer(ioc);
+  ioc.propertySerializer = new PropertySerializer(ioc);
+  ioc.vertexSerializer = new VertexSerializer(ioc);
+  ioc.vertexPropertySerializer = new VertexPropertySerializer(ioc);
+  ioc.bigIntegerSerializer = new BigIntegerSerializer(ioc);
+  ioc.byteSerializer = new ByteSerializer(ioc);
+  ioc.binarySerializer = new BinarySerializer(ioc);
+  ioc.shortSerializer = new ShortSerializer(ioc);
+  ioc.booleanSerializer = new BooleanSerializer(ioc);
+  ioc.markerSerializer = new MarkerSerializer(ioc);
+  ioc.unspecifiedNullSerializer = new UnspecifiedNullSerializer(ioc);
+  ioc.enumSerializer = new EnumSerializer(ioc);
+
+  // Register stub serializers for unimplemented v4 types
+  new StubSerializer(ioc, ioc.DataType.TREE, 'Tree');
+  new StubSerializer(ioc, ioc.DataType.GRAPH, 'Graph');
+  new StubSerializer(ioc, ioc.DataType.COMPOSITEPDT, 'CompositePDT');
+  new StubSerializer(ioc, ioc.DataType.PRIMITIVEPDT, 'PrimitivePDT');
+
+  ioc.numberSerializationStrategy = new NumberSerializationStrategy(ioc);
+  ioc.anySerializer = new AnySerializer(ioc, anySerializerOptions);
+
+  ioc.graphBinaryReader = new GraphBinaryReader(ioc);
+  ioc.graphBinaryWriter = new GraphBinaryWriter(ioc);
+
+  return ioc;
+}
+
+export function createPreciseReader() {
+  const wrapperMap = new Map([
+    [DataType.INT, Int],
+    [DataType.LONG, Long],
+    [DataType.FLOAT, Float],
+    [DataType.DOUBLE, Double],
+    [DataType.SHORT, Short],
+    [DataType.BYTE, Byte],
+  ]);
+
+  const preciseIoc = createIoc({
+    postDeserialize(result, typeCode) {
+      const Wrapper = wrapperMap.get(typeCode);
+      if (Wrapper && result !== null && result !== undefined) {
+        return new Wrapper(result);
+      }
+      return result;
+    },
+  });
+
+  return preciseIoc.graphBinaryReader;
+}
+
+const ioc = createIoc();
 
 export { default as DataType } from './internals/DataType.js';
 
diff --git 
a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/AnySerializer.js
 
b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/AnySerializer.js
index c3bd93bf1d..bfc20c0016 100644
--- 
a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/AnySerializer.js
+++ 
b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/AnySerializer.js
@@ -22,8 +22,9 @@
  */
 
 export default class AnySerializer {
-  constructor(ioc) {
+  constructor(ioc, { postDeserialize } = {}) {
     this.ioc = ioc;
+    this._postDeserialize = postDeserialize || null;
 
     // specifically ordered, the first canBeUsedFor=true wins
     this.serializers = [
@@ -84,11 +85,17 @@ export default class AnySerializer {
       throw new Error(`AnySerializer: unexpected 
{value_flag}=0x${value_flag.toString(16)} at position ${pos}`);
     }
 
+    let result;
     try {
-      return await serializer.deserializeValue(reader, value_flag, type_code);
+      result = await serializer.deserializeValue(reader, value_flag, 
type_code);
     } catch (err) {
       err.message = `${serializer.constructor.name}.deserializeValue() at 
position ${pos}: ${err.message}`;
       throw err;
     }
+
+    if (this._postDeserialize) {
+      return this._postDeserialize(result, type_code);
+    }
+    return result;
   }
 }
diff --git 
a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/NumberSerializationStrategy.js
 
b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/NumberSerializationStrategy.js
index c457935e45..61c74fd551 100644
--- 
a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/NumberSerializationStrategy.js
+++ 
b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/NumberSerializationStrategy.js
@@ -21,6 +21,8 @@
  * @author Igor Ostapenko
  */
 
+import { Long, Int, Float, Double, Short, Byte, INT32_MIN, INT32_MAX } from 
'../../../../utils.js';
+
 // Based on GraphSON NumberSerializer.serialize().
 // It's tested by AnySerializer.serialize() tests.
 export default class NumberSerializationStrategy {
@@ -29,6 +31,16 @@ export default class NumberSerializationStrategy {
   }
 
   canBeUsedFor(value) {
+    if (
+      value instanceof Long ||
+      value instanceof Int ||
+      value instanceof Float ||
+      value instanceof Double ||
+      value instanceof Short ||
+      value instanceof Byte
+    ) {
+      return true;
+    }
     if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY || value === 
Number.NEGATIVE_INFINITY) {
       return true;
     }
@@ -43,6 +55,25 @@ export default class NumberSerializationStrategy {
   }
 
   serialize(item, fullyQualifiedFormat = true) {
+    if (item instanceof Float) {
+      return this.ioc.floatSerializer.serialize(item.value, 
fullyQualifiedFormat);
+    }
+    if (item instanceof Double) {
+      return this.ioc.doubleSerializer.serialize(item.value, 
fullyQualifiedFormat);
+    }
+    if (item instanceof Int) {
+      return this.ioc.intSerializer.serialize(item.value, 
fullyQualifiedFormat);
+    }
+    if (item instanceof Long) {
+      return this.ioc.longSerializer.serialize(item.value, 
fullyQualifiedFormat);
+    }
+    if (item instanceof Short) {
+      return this.ioc.shortSerializer.serialize(item.value, 
fullyQualifiedFormat);
+    }
+    if (item instanceof Byte) {
+      return this.ioc.byteSerializer.serialize(item.value, 
fullyQualifiedFormat);
+    }
+
     if (typeof item === 'number') {
       if (
         Number.isNaN(item) ||
@@ -54,8 +85,7 @@ export default class NumberSerializationStrategy {
         return this.ioc.doubleSerializer.serialize(item, fullyQualifiedFormat);
       }
 
-      if (item >= -2147483648 && item <= 2147483647) {
-        // INT32_MIN/MAX
+      if (item >= INT32_MIN && item <= INT32_MAX) {
         return this.ioc.intSerializer.serialize(item, fullyQualifiedFormat);
       }
       if (item >= Number.MIN_SAFE_INTEGER && item <= Number.MAX_SAFE_INTEGER) {
diff --git a/gremlin-js/gremlin-javascript/lib/utils.ts 
b/gremlin-js/gremlin-javascript/lib/utils.ts
index 35ba5a45ae..be81755872 100644
--- a/gremlin-js/gremlin-javascript/lib/utils.ts
+++ b/gremlin-js/gremlin-javascript/lib/utils.ts
@@ -24,16 +24,164 @@
 
 const gremlinVersion = '4.0.0-SNAPSHOT'; // DO NOT MODIFY - Configured 
automatically by Maven Replacer Plugin
 
-export function toLong(value: number | string) {
+const INT64_MIN = -9223372036854775808n;
+const INT64_MAX = 9223372036854775807n;
+
+export const INT32_MIN = -2147483648;
+export const INT32_MAX = 2147483647;
+
+export function toLong(value: number | string | bigint) {
   return new Long(value);
 }
 
 export class Long {
-  constructor(public value: number | string) {
-    if (typeof value !== 'string' && typeof value !== 'number') {
-      throw new TypeError('The value must be a string or a number');
+  readonly type = 'long';
+
+  constructor(public readonly value: number | string | bigint) {
+    if (typeof value !== 'string' && typeof value !== 'number' && typeof value 
!== 'bigint') {
+      throw new TypeError('The value must be a string, a number, or a bigint');
+    }
+    if (typeof value === 'string') {
+      if (!/^(?:0|-?[1-9]\d*)$/.test(value)) {
+        throw new TypeError('Long value must be a valid integer');
+      }
+      const n = BigInt(value);
+      if (n < INT64_MIN || n > INT64_MAX) {
+        throw new RangeError('Long value is outside int64 range');
+      }
+    }
+    if (typeof value === 'number') {
+      if (!Number.isInteger(value)) {
+        throw new TypeError('Long value must be an integer');
+      }
+      if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) {
+        throw new RangeError('Long number values outside safe integer range 
lose precision; use string or bigint');
+      }
+    }
+    if (typeof value === 'bigint') {
+      if (value < INT64_MIN || value > INT64_MAX) {
+        throw new RangeError('Long value is outside int64 range');
+      }
+    }
+  }
+
+  valueOf(): number {
+    if (typeof this.value === 'number') return this.value;
+    const big = typeof this.value === 'bigint' ? this.value : 
BigInt(this.value);
+    if (big > BigInt(Number.MAX_SAFE_INTEGER) || big < 
BigInt(Number.MIN_SAFE_INTEGER)) {
+      throw new RangeError('Long value is outside safe integer range');
+    }
+    return Number(big);
+  }
+
+  [Symbol.toPrimitive](hint: string) {
+    if (hint === 'string') return String(this.value);
+    return this.valueOf();
+  }
+
+  toJSON() {
+    if (typeof this.value === 'bigint') return this.value.toString();
+    return this.value;
+  }
+}
+
+export class Int {
+  readonly type = 'int';
+  constructor(public readonly value: number) {
+    if (typeof value !== 'number') {
+      throw new TypeError('Int value must be a number');
     }
+    if (!Number.isFinite(value) || !Number.isInteger(value)) {
+      throw new TypeError('Int value must be a finite integer');
+    }
+    if (value < INT32_MIN || value > INT32_MAX) {
+      throw new RangeError('Int value must be within int32 range');
+    }
+  }
+  valueOf() { return this.value; }
+  [Symbol.toPrimitive](hint: string) { return hint === 'string' ? 
String(this.value) : this.value; }
+  toJSON() { return this.value; }
+}
+
+export class Float {
+  readonly type = 'float';
+  constructor(public readonly value: number) {
+    if (typeof value !== 'number') {
+      throw new TypeError('Float value must be a number');
+    }
+  }
+  valueOf() { return this.value; }
+  [Symbol.toPrimitive](hint: string) { return hint === 'string' ? 
String(this.value) : this.value; }
+  toJSON() { return this.value; }
+}
+
+export class Double {
+  readonly type = 'double';
+  constructor(public readonly value: number) {
+    if (typeof value !== 'number') {
+      throw new TypeError('Double value must be a number');
+    }
+  }
+  valueOf() { return this.value; }
+  [Symbol.toPrimitive](hint: string) { return hint === 'string' ? 
String(this.value) : this.value; }
+  toJSON() { return this.value; }
+}
+
+export class Short {
+  readonly type = 'short';
+  constructor(public readonly value: number) {
+    if (typeof value !== 'number') {
+      throw new TypeError('Short value must be a number');
+    }
+    if (!Number.isFinite(value) || !Number.isInteger(value)) {
+      throw new TypeError('Short value must be a finite integer');
+    }
+    if (value < -32768 || value > 32767) {
+      throw new RangeError('Short value must be within int16 range');
+    }
+  }
+  valueOf() { return this.value; }
+  [Symbol.toPrimitive](hint: string) { return hint === 'string' ? 
String(this.value) : this.value; }
+  toJSON() { return this.value; }
+}
+
+export class Byte {
+  readonly type = 'byte';
+  constructor(public readonly value: number) {
+    if (typeof value !== 'number') {
+      throw new TypeError('Byte value must be a number');
+    }
+    if (!Number.isFinite(value) || !Number.isInteger(value)) {
+      throw new TypeError('Byte value must be a finite integer');
+    }
+    if (value < -128 || value > 127) {
+      throw new RangeError('Byte value must be within int8 range');
+    }
+  }
+  valueOf() { return this.value; }
+  [Symbol.toPrimitive](hint: string) { return hint === 'string' ? 
String(this.value) : this.value; }
+  toJSON() { return this.value; }
+}
+
+export function toInt(value: number) { return new Int(value); }
+export function toFloat(value: number) { return new Float(value); }
+export function toDouble(value: number) { return new Double(value); }
+export function toShort(value: number) { return new Short(value); }
+export function toByte(value: number) { return new Byte(value); }
+
+export function unwrap(value: Float): number;
+export function unwrap(value: Double): number;
+export function unwrap(value: Int): number;
+export function unwrap(value: Long): number | string | bigint;
+export function unwrap(value: Short): number;
+export function unwrap(value: Byte): number;
+export function unwrap<T>(value: T): T;
+export function unwrap(value: any): any {
+  if (value instanceof Long || value instanceof Int || value instanceof Float 
||
+      value instanceof Double || value instanceof Short || value instanceof 
Byte) {
+    return value.value;
   }
+  return value;
 }
 
 export function getUuid() {
diff --git a/gremlin-js/gremlin-javascript/test/helper.js 
b/gremlin-js/gremlin-javascript/test/helper.js
index 42926b323d..fa7f0d65b2 100644
--- a/gremlin-js/gremlin-javascript/test/helper.js
+++ b/gremlin-js/gremlin-javascript/test/helper.js
@@ -34,6 +34,8 @@ if (process.env.DOCKER_ENVIRONMENT === 'true') {
   serverAuthUrl = 'https://localhost:45941/gremlin';
 }
 
+export { serverUrl };
+
 /** @returns {DriverRemoteConnection} */
 export function getConnection(traversalSource) {
   return new DriverRemoteConnection(serverUrl, { traversalSource });
diff --git a/gremlin-js/gremlin-javascript/test/integration/client-tests.js 
b/gremlin-js/gremlin-javascript/test/integration/client-tests.js
index 09a4cd8230..aca3548d40 100644
--- a/gremlin-js/gremlin-javascript/test/integration/client-tests.js
+++ b/gremlin-js/gremlin-javascript/test/integration/client-tests.js
@@ -19,8 +19,10 @@
 
 import assert from 'assert';
 import { Vertex, Edge, VertexProperty } from '../../lib/structure/graph.js';
-import { getClient } from '../helper.js';
+import { getClient, serverUrl } from '../helper.js';
 import { cardinality } from '../../lib/process/traversal.js';
+import Client from '../../lib/driver/client.js';
+import { Int, Double } from '../../lib/utils.js';
 
 let client, clientCrew;
 
@@ -157,6 +159,35 @@ describe('Client', function () {
     //   assert.ok(!closingClient.isOpen());
     // });
   });
+
+  describe('#submit() with preciseNumbers', function () {
+    let preciseClient;
+
+    before(async function () {
+      preciseClient = new Client(serverUrl, { traversalSource: 'gmodern', 
preciseNumbers: true });
+      await preciseClient.open();
+    });
+
+    after(async function () {
+      await preciseClient.close();
+    });
+
+    it('should return Int wrapper for integer vertex property', async function 
() {
+      const result = await preciseClient.submit('g.V().has("name", 
"marko").values("age")');
+      assert.ok(result);
+      assert.ok(result.first() instanceof Int);
+      assert.strictEqual(result.first().value, 29);
+    });
+
+    it('should return Double wrapper for edge weight property', async function 
() {
+      const result = await preciseClient.submit('g.E().has("weight", 
0.5).limit(1)');
+      assert.ok(result);
+      const edge = result.first();
+      assert.ok(edge instanceof Edge);
+      assert.ok(edge.properties[0].value instanceof Double);
+      assert.strictEqual(edge.properties[0].value.value, 0.5);
+    });
+  });
 });
 
 function assertVertexProperties(vertex) {
diff --git a/gremlin-js/gremlin-javascript/test/unit/exports-test.js 
b/gremlin-js/gremlin-javascript/test/unit/exports-test.js
index 3f5adb88bf..f009c7074d 100644
--- a/gremlin-js/gremlin-javascript/test/unit/exports-test.js
+++ b/gremlin-js/gremlin-javascript/test/unit/exports-test.js
@@ -61,6 +61,19 @@ describe('API', function () {
     assert.strictEqual(typeof glvModule.structure.Property, 'function');
     assert.strictEqual(typeof glvModule.structure.Vertex, 'function');
     assert.strictEqual(typeof glvModule.structure.VertexProperty, 'function');
+    assert.strictEqual(typeof glvModule.structure.toLong, 'function');
+    assert.strictEqual(typeof glvModule.structure.toInt, 'function');
+    assert.strictEqual(typeof glvModule.structure.toFloat, 'function');
+    assert.strictEqual(typeof glvModule.structure.toDouble, 'function');
+    assert.strictEqual(typeof glvModule.structure.toShort, 'function');
+    assert.strictEqual(typeof glvModule.structure.toByte, 'function');
+    assert.strictEqual(typeof glvModule.structure.Int, 'function');
+    assert.strictEqual(typeof glvModule.structure.Float, 'function');
+    assert.strictEqual(typeof glvModule.structure.Double, 'function');
+    assert.strictEqual(typeof glvModule.structure.Short, 'function');
+    assert.strictEqual(typeof glvModule.structure.Byte, 'function');
+    assert.strictEqual(typeof glvModule.structure.Long, 'function');
+    assert.strictEqual(typeof glvModule.structure.unwrap, 'function');
   });
   it('should expose fields under driver', function () {
     assert.ok(glvModule.driver);
diff --git 
a/gremlin-js/gremlin-javascript/test/unit/graphbinary/precise-mode-test.js 
b/gremlin-js/gremlin-javascript/test/unit/graphbinary/precise-mode-test.js
new file mode 100644
index 0000000000..c2b9246713
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/test/unit/graphbinary/precise-mode-test.js
@@ -0,0 +1,521 @@
+/*
+ *  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 assert from 'assert';
+import ioc, { createPreciseReader, DataType } from 
'../../../lib/structure/io/binary/GraphBinary.js';
+import StreamReader from 
'../../../lib/structure/io/binary/internals/StreamReader.js';
+import { Float, Double, Int, Long, Short, Byte, toFloat, toDouble, toInt, 
toLong, toShort, toByte, unwrap } from '../../../lib/utils.js';
+import Connection from '../../../lib/driver/connection.js';
+import { Path } from '../../../lib/structure/graph.js';
+
+const { anySerializer, graphBinaryReader } = ioc;
+
+describe('Precise Mode Tests', () => {
+  let preciseReader;
+
+  before(() => {
+    preciseReader = createPreciseReader();
+  });
+
+  async function deserializeWithPrecise(buf) {
+    return 
preciseReader.ioc.anySerializer.deserialize(StreamReader.fromBuffer(buf));
+  }
+
+  async function deserializeWithDefault(buf) {
+    return anySerializer.deserialize(StreamReader.fromBuffer(buf));
+  }
+
+  describe('Basic deserialization', () => {
+    it('FLOAT bytes → Float instance', async () => {
+      const buf = anySerializer.serialize(toFloat(1.5));
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result instanceof Float);
+      assert.strictEqual(result.value, 1.5);
+      assert.strictEqual(result.type, 'float');
+    });
+
+    it('DOUBLE bytes → Double instance', async () => {
+      const buf = anySerializer.serialize(toDouble(3.14));
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result instanceof Double);
+      assert.strictEqual(result.value, 3.14);
+      assert.strictEqual(result.type, 'double');
+    });
+
+    it('INT bytes → Int instance', async () => {
+      const buf = anySerializer.serialize(toInt(42));
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result instanceof Int);
+      assert.strictEqual(result.value, 42);
+      assert.strictEqual(result.type, 'int');
+    });
+
+    it('LONG bytes (safe range) → Long instance with number value', async () 
=> {
+      const buf = anySerializer.serialize(toLong(42));
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result instanceof Long);
+      assert.strictEqual(result.value, 42);
+      assert.strictEqual(result.type, 'long');
+    });
+
+    it('LONG bytes (unsafe range) → Long instance with bigint value', async () 
=> {
+      const buf = anySerializer.serialize(toLong(9007199254740993n));
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result instanceof Long);
+      assert.strictEqual(result.value, 9007199254740993n);
+    });
+
+    it('SHORT bytes → Short instance', async () => {
+      const buf = anySerializer.serialize(toShort(5));
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result instanceof Short);
+      assert.strictEqual(result.value, 5);
+      assert.strictEqual(result.type, 'short');
+    });
+
+    it('BYTE bytes → Byte instance', async () => {
+      const buf = anySerializer.serialize(toByte(127));
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result instanceof Byte);
+      assert.strictEqual(result.value, 127);
+      assert.strictEqual(result.type, 'byte');
+    });
+  });
+
+  describe('Nested structures', () => {
+    it('MAP containing Float values → Float wrappers inside Map', async () => {
+      const map = new Map([['x', toFloat(1.5)]]);
+      const buf = anySerializer.serialize(map);
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result.get('x') instanceof Float);
+      assert.strictEqual(result.get('x').value, 1.5);
+    });
+
+    it('LIST with mixed numeric types → each wrapped correctly', async () => {
+      const list = [toFloat(1.5), toInt(2), toDouble(3.14)];
+      const buf = anySerializer.serialize(list);
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result[0] instanceof Float);
+      assert.ok(result[1] instanceof Int);
+      assert.ok(result[2] instanceof Double);
+    });
+
+    it('PATH with numeric vertex IDs → wrapped correctly', async () => {
+      const path = new Path([['a'], ['b']], [toInt(1), toInt(2)]);
+      const buf = anySerializer.serialize(path);
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result instanceof Path);
+      assert.ok(result.objects[0] instanceof Int);
+      assert.strictEqual(result.objects[0].value, 1);
+      assert.ok(result.objects[1] instanceof Int);
+      assert.strictEqual(result.objects[1].value, 2);
+    });
+
+    it('LIST with Short, Byte, Long → each wrapped correctly', async () => {
+      const list = [toShort(5), toByte(127), toLong(99)];
+      const buf = anySerializer.serialize(list);
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result[0] instanceof Short);
+      assert.strictEqual(result[0].value, 5);
+      assert.ok(result[1] instanceof Byte);
+      assert.strictEqual(result[1].value, 127);
+      assert.ok(result[2] instanceof Long);
+      assert.strictEqual(result[2].value, 99);
+    });
+
+    it('Non-numeric types still deserialize correctly', async () => {
+      const list = ['hello', true, 'world'];
+      const buf = anySerializer.serialize(list);
+      const result = await deserializeWithPrecise(buf);
+      assert.deepStrictEqual(result, ['hello', true, 'world']);
+    });
+  });
+
+  describe('Precise reader error paths', () => {
+    it('returns null for a null-flagged numeric value', async () => {
+      const buf = Buffer.from([DataType.INT, 0x01]);
+      const result = await deserializeWithPrecise(buf);
+      assert.strictEqual(result, null);
+    });
+
+    it('returns null for a null-flagged LONG value', async () => {
+      const buf = Buffer.from([DataType.LONG, 0x01]);
+      assert.strictEqual(await deserializeWithPrecise(buf), null);
+    });
+
+    it('returns null for a null-flagged FLOAT value', async () => {
+      const buf = Buffer.from([DataType.FLOAT, 0x01]);
+      assert.strictEqual(await deserializeWithPrecise(buf), null);
+    });
+
+    it('returns null for a null-flagged DOUBLE value', async () => {
+      const buf = Buffer.from([DataType.DOUBLE, 0x01]);
+      assert.strictEqual(await deserializeWithPrecise(buf), null);
+    });
+
+    it('returns null for a null-flagged SHORT value', async () => {
+      const buf = Buffer.from([DataType.SHORT, 0x01]);
+      assert.strictEqual(await deserializeWithPrecise(buf), null);
+    });
+
+    it('returns null for a null-flagged BYTE value', async () => {
+      const buf = Buffer.from([DataType.BYTE, 0x01]);
+      assert.strictEqual(await deserializeWithPrecise(buf), null);
+    });
+
+    it('throws on invalid value_flag', async () => {
+      const buf = Buffer.from([DataType.INT, 0xFF, 0, 0, 0, 0]);
+      await assert.rejects(() => deserializeWithPrecise(buf), /AnySerializer: 
unexpected \{value_flag}=0x/);
+    });
+
+    it('throws on unknown type_code', async () => {
+      const buf = Buffer.from([0xEE, 0x00]);
+      await assert.rejects(() => deserializeWithPrecise(buf), /AnySerializer: 
unknown \{type_code}=0xee/);
+    });
+
+    it('deserializes value_flag 0x02 as non-null', async () => {
+      const buf = Buffer.from([DataType.INT, 0x02, 0x00, 0x00, 0x00, 0x2A]);
+      const result = await deserializeWithPrecise(buf);
+      assert.ok(result instanceof Int);
+      assert.strictEqual(result.value, 42);
+    });
+
+    it('wraps deserializeValue errors with position info', async () => {
+      const buf = Buffer.from([DataType.INT, 0x00]);
+      await assert.rejects(() => deserializeWithPrecise(buf), 
/IntSerializer\.deserializeValue\(\) at position \d+/);
+    });
+  });
+
+  describe('Wrapper behavior', () => {
+    it('Float valueOf works in arithmetic', () => {
+      assert.strictEqual(new Float(1.5) + 1, 2.5);
+    });
+
+    it('Int valueOf works in arithmetic', () => {
+      assert.strictEqual(new Int(42) + 0, 42);
+    });
+
+    it('Double valueOf works in arithmetic', () => {
+      assert.strictEqual(new Double(3.14) + 0, 3.14);
+    });
+
+    it('Short valueOf works in arithmetic', () => {
+      assert.strictEqual(new Short(5) + 1, 6);
+    });
+
+    it('Byte valueOf works in arithmetic', () => {
+      assert.strictEqual(new Byte(127) + 0, 127);
+    });
+
+    it('Long with unsafe bigint throws RangeError on arithmetic', () => {
+      assert.throws(() => new Long(9007199254740993n) + 1, RangeError);
+    });
+
+    it('Long with unsafe string throws RangeError on arithmetic', () => {
+      assert.throws(() => new Long('9007199254740993') + 1, RangeError);
+    });
+
+    it('Long with safe bigint works in arithmetic', () => {
+      assert.strictEqual(new Long(42n) + 1, 43);
+    });
+
+    it('Long toPrimitive string hint works for unsafe values', () => {
+      assert.strictEqual(`${new Long(9007199254740993n)}`, '9007199254740993');
+    });
+
+    it('Float toPrimitive string hint', () => {
+      assert.strictEqual(`${new Float(1.5)}`, '1.5');
+    });
+
+    it('Double toPrimitive string hint', () => {
+      assert.strictEqual(`${new Double(3.14)}`, '3.14');
+    });
+
+    it('Int toPrimitive string hint', () => {
+      assert.strictEqual(`${new Int(67)}`, '67');
+    });
+
+    it('Short toPrimitive string hint', () => {
+      assert.strictEqual(`${new Short(5)}`, '5');
+    });
+
+    it('Byte toPrimitive string hint', () => {
+      assert.strictEqual(`${new Byte(127)}`, '127');
+    });
+
+    it('JSON.stringify Float', () => {
+      assert.strictEqual(JSON.stringify(new Float(1.5)), '1.5');
+    });
+
+    it('JSON.stringify Long with bigint', () => {
+      assert.strictEqual(JSON.stringify(new Long(9007199254740993n)), 
'"9007199254740993"');
+    });
+
+    it('Long valueOf with safe string returns number', () => {
+      assert.strictEqual(new Long('42').valueOf(), 42);
+    });
+
+    it('Long valueOf with unsafe string throws RangeError', () => {
+      assert.throws(() => new Long('9007199254740993').valueOf(), RangeError);
+    });
+
+    it('JSON.stringify Long with string value preserves string', () => {
+      assert.strictEqual(JSON.stringify(new Long('9007199254740993')), 
'"9007199254740993"');
+    });
+
+    it('JSON.stringify Long with number value is a number', () => {
+      assert.strictEqual(JSON.stringify(new Long(42)), '42');
+    });
+
+    it('JSON.stringify Double', () => {
+      assert.strictEqual(JSON.stringify(new Double(3.14)), '3.14');
+    });
+
+    it('JSON.stringify Int', () => {
+      assert.strictEqual(JSON.stringify(new Int(67)), '67');
+    });
+
+    it('JSON.stringify Short', () => {
+      assert.strictEqual(JSON.stringify(new Short(5)), '5');
+    });
+
+    it('JSON.stringify Byte', () => {
+      assert.strictEqual(JSON.stringify(new Byte(127)), '127');
+    });
+
+    it('unwrap Float', () => {
+      assert.strictEqual(unwrap(new Float(1.5)), 1.5);
+    });
+
+    it('unwrap Long with bigint', () => {
+      assert.strictEqual(unwrap(new Long(42n)), 42n);
+    });
+
+    it('unwrap Long with string', () => {
+      assert.strictEqual(unwrap(new Long('123')), '123');
+    });
+
+    it('unwrap Long with number', () => {
+      assert.strictEqual(unwrap(new Long(42)), 42);
+    });
+
+    it('unwrap Int', () => {
+      assert.strictEqual(unwrap(new Int(42)), 42);
+    });
+
+    it('unwrap Double', () => {
+      assert.strictEqual(unwrap(new Double(3.14)), 3.14);
+    });
+
+    it('unwrap Short', () => {
+      assert.strictEqual(unwrap(new Short(5)), 5);
+    });
+
+    it('unwrap Byte', () => {
+      assert.strictEqual(unwrap(new Byte(127)), 127);
+    });
+
+    it('unwrap plain number passthrough', () => {
+      assert.strictEqual(unwrap(42), 42);
+    });
+
+    it('unwrap null passthrough', () => {
+      assert.strictEqual(unwrap(null), null);
+    });
+
+    it('unwrap undefined passthrough', () => {
+      assert.strictEqual(unwrap(undefined), undefined);
+    });
+  });
+
+  describe('Long constructor validation', () => {
+    it('rejects non-numeric string', () => {
+      assert.throws(() => new Long('abc'), TypeError);
+    });
+
+    it('rejects injection attempt', () => {
+      assert.throws(() => new Long('1L).drop()'), TypeError);
+    });
+
+    it('rejects empty string', () => {
+      assert.throws(() => new Long(''), TypeError);
+    });
+
+    it('rejects non-integer number', () => {
+      assert.throws(() => new Long(1.5), TypeError);
+    });
+
+    it('accepts exact int64 max (bigint)', () => {
+      const l = new Long(9223372036854775807n);
+      assert.strictEqual(l.value, 9223372036854775807n);
+    });
+
+    it('accepts exact int64 min (bigint)', () => {
+      const l = new Long(-9223372036854775808n);
+      assert.strictEqual(l.value, -9223372036854775808n);
+    });
+
+    it('rejects one above int64 max (bigint)', () => {
+      assert.throws(() => new Long(9223372036854775808n), RangeError);
+    });
+
+    it('rejects one below int64 min (bigint)', () => {
+      assert.throws(() => new Long(-9223372036854775809n), RangeError);
+    });
+
+    it('accepts exact int64 max (string)', () => {
+      const l = new Long('9223372036854775807');
+      assert.strictEqual(l.value, '9223372036854775807');
+    });
+
+    it('rejects one above int64 max (string)', () => {
+      assert.throws(() => new Long('9223372036854775808'), RangeError);
+    });
+
+    it('rejects negative zero string', () => {
+      assert.throws(() => new Long('-0'), TypeError);
+    });
+
+    it('accepts negative zero number', () => {
+      const l = new Long(-0);
+      assert.strictEqual(l.valueOf(), -0);
+    });
+
+    it('rejects leading zeros in string', () => {
+      assert.throws(() => new Long('0042'), TypeError);
+    });
+  });
+
+  describe('Float constructor validation', () => {
+    it('rejects non-number argument', () => {
+      assert.throws(() => new Float('1.5'), TypeError);
+    });
+  });
+
+  describe('Double constructor validation', () => {
+    it('rejects non-number argument', () => {
+      assert.throws(() => new Double('3.14'), TypeError);
+    });
+  });
+
+  describe('Int constructor validation', () => {
+    it('accepts exact int32 max', () => {
+      assert.strictEqual(new Int(2147483647).value, 2147483647);
+    });
+
+    it('accepts exact int32 min', () => {
+      assert.strictEqual(new Int(-2147483648).value, -2147483648);
+    });
+
+    it('rejects above int32 max', () => {
+      assert.throws(() => new Int(2147483648), RangeError);
+    });
+
+    it('rejects below int32 min', () => {
+      assert.throws(() => new Int(-2147483649), RangeError);
+    });
+
+    it('rejects non-integer', () => {
+      assert.throws(() => new Int(1.5), TypeError);
+    });
+
+    it('rejects non-number argument', () => {
+      assert.throws(() => new Int('5'), TypeError);
+    });
+  });
+
+  describe('Short constructor validation', () => {
+    it('accepts exact int16 max', () => {
+      assert.strictEqual(new Short(32767).value, 32767);
+    });
+
+    it('accepts exact int16 min', () => {
+      assert.strictEqual(new Short(-32768).value, -32768);
+    });
+
+    it('rejects above int16 max', () => {
+      assert.throws(() => new Short(32768), RangeError);
+    });
+
+    it('rejects below int16 min', () => {
+      assert.throws(() => new Short(-32769), RangeError);
+    });
+
+    it('rejects non-integer', () => {
+      assert.throws(() => new Short(1.5), TypeError);
+    });
+
+    it('rejects non-number argument', () => {
+      assert.throws(() => new Short('5'), TypeError);
+    });
+  });
+
+  describe('Byte constructor validation', () => {
+    it('accepts exact int8 max', () => {
+      assert.strictEqual(new Byte(127).value, 127);
+    });
+
+    it('accepts exact int8 min', () => {
+      assert.strictEqual(new Byte(-128).value, -128);
+    });
+
+    it('rejects above int8 max', () => {
+      assert.throws(() => new Byte(128), RangeError);
+    });
+
+    it('rejects below int8 min', () => {
+      assert.throws(() => new Byte(-129), RangeError);
+    });
+
+    it('rejects non-integer', () => {
+      assert.throws(() => new Byte(1.5), TypeError);
+    });
+
+    it('rejects non-number argument', () => {
+      assert.throws(() => new Byte('127'), TypeError);
+    });
+  });
+
+  describe('Backward compatibility', () => {
+    it('default graphBinaryReader still returns plain numbers after 
createPreciseReader()', async () => {
+      const buf = anySerializer.serialize(toFloat(1.5));
+      const result = await deserializeWithDefault(buf);
+      assert.strictEqual(result, 1.5);
+      assert.ok(!(result instanceof Float));
+    });
+  });
+
+  describe('Connection option wiring', () => {
+    it('preciseNumbers: true uses a precise reader', () => {
+      const conn = new Connection('http://localhost:8182', { preciseNumbers: 
true });
+      assert.ok(conn._reader !== graphBinaryReader);
+    });
+
+    it('explicit reader takes precedence over preciseNumbers', () => {
+      const customReader = { custom: true };
+      const conn = new Connection('http://localhost:8182', { reader: 
customReader, preciseNumbers: true });
+      assert.strictEqual(conn._reader, customReader);
+    });
+
+    it('default uses the default reader', () => {
+      const conn = new Connection('http://localhost:8182', {});
+      assert.strictEqual(conn._reader, graphBinaryReader);
+    });
+  });
+});
diff --git 
a/gremlin-js/gremlin-javascript/test/unit/graphbinary/typed-number-test.js 
b/gremlin-js/gremlin-javascript/test/unit/graphbinary/typed-number-test.js
new file mode 100644
index 0000000000..9e611e419f
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/test/unit/graphbinary/typed-number-test.js
@@ -0,0 +1,212 @@
+/*
+ *  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 assert from 'assert';
+import { toFloat, toDouble, toInt, toLong, toShort, toByte } from 
'../../../lib/utils.js';
+import ioc, { DataType } from 
'../../../lib/structure/io/binary/GraphBinary.js';
+import StreamReader from 
'../../../lib/structure/io/binary/internals/StreamReader.js';
+import { P } from '../../../lib/process/traversal.js';
+
+const { anySerializer, numberSerializationStrategy } = ioc;
+
+describe('Typed Number Tests', () => {
+  describe('Type-code routing via anySerializer', () => {
+    it('toFloat → FLOAT', () => {
+      assert.strictEqual(anySerializer.serialize(toFloat(1.0))[0], 
DataType.FLOAT);
+    });
+
+    it('toDouble → DOUBLE', () => {
+      assert.strictEqual(anySerializer.serialize(toDouble(1.0))[0], 
DataType.DOUBLE);
+    });
+
+    it('toInt → INT', () => {
+      assert.strictEqual(anySerializer.serialize(toInt(67))[0], DataType.INT);
+    });
+
+    it('toLong → LONG', () => {
+      assert.strictEqual(anySerializer.serialize(toLong(67))[0], 
DataType.LONG);
+    });
+
+    it('toShort → SHORT', () => {
+      assert.strictEqual(anySerializer.serialize(toShort(5))[0], 
DataType.SHORT);
+    });
+
+    it('toByte → BYTE', () => {
+      assert.strictEqual(anySerializer.serialize(toByte(127))[0], 
DataType.BYTE);
+    });
+  });
+
+  describe('canBeUsedFor routing', () => {
+    it('accepts typed wrappers', () => {
+      assert.ok(numberSerializationStrategy.canBeUsedFor(toFloat(1.0)));
+      assert.ok(numberSerializationStrategy.canBeUsedFor(toInt(67)));
+      assert.ok(numberSerializationStrategy.canBeUsedFor(toLong(67n)));
+      assert.ok(numberSerializationStrategy.canBeUsedFor(toDouble(1.0)));
+      assert.ok(numberSerializationStrategy.canBeUsedFor(toShort(5)));
+      assert.ok(numberSerializationStrategy.canBeUsedFor(toByte(127)));
+    });
+
+    it('rejects plain objects', () => {
+      assert.ok(!numberSerializationStrategy.canBeUsedFor({ value: 1 }));
+    });
+  });
+
+  describe('Byte-level verification', () => {
+    it('toFloat(1.0) produces different bytes than toInt(1)', () => {
+      const floatBytes = anySerializer.serialize(toFloat(1.0));
+      const intBytes = anySerializer.serialize(toInt(1));
+      assert.ok(!Buffer.from(floatBytes).equals(Buffer.from(intBytes)));
+    });
+
+    it('toFloat(1.0) produces different bytes than toDouble(1.0) (4 vs 8 value 
bytes)', () => {
+      const floatBytes = anySerializer.serialize(toFloat(1.0));
+      const doubleBytes = anySerializer.serialize(toDouble(1.0));
+      assert.ok(!Buffer.from(floatBytes).equals(Buffer.from(doubleBytes)));
+    });
+
+    it('toShort(1) produces different bytes than toInt(1)', () => {
+      const shortBytes = anySerializer.serialize(toShort(1));
+      const intBytes = anySerializer.serialize(toInt(1));
+      assert.ok(!Buffer.from(shortBytes).equals(Buffer.from(intBytes)));
+    });
+
+    it('toByte(1) produces different bytes than toShort(1)', () => {
+      const byteBytes = anySerializer.serialize(toByte(1));
+      const shortBytes = anySerializer.serialize(toShort(1));
+      assert.ok(!Buffer.from(byteBytes).equals(Buffer.from(shortBytes)));
+    });
+
+    it('toLong(1) produces different bytes than toInt(1)', () => {
+      const longBytes = anySerializer.serialize(toLong(1));
+      const intBytes = anySerializer.serialize(toInt(1));
+      assert.ok(!Buffer.from(longBytes).equals(Buffer.from(intBytes)));
+    });
+  });
+
+  describe('Backward compatibility', () => {
+    it('plain 67 still routes to INT', () => {
+      assert.strictEqual(anySerializer.serialize(67)[0], DataType.INT);
+    });
+
+    it('plain 3.14 still routes to DOUBLE', () => {
+      assert.strictEqual(anySerializer.serialize(3.14)[0], DataType.DOUBLE);
+    });
+
+    it('plain 2147483648 still routes to LONG', () => {
+      assert.strictEqual(anySerializer.serialize(2147483648)[0], 
DataType.LONG);
+    });
+  });
+
+  describe('Edge cases', () => {
+    it('toLong(67n) serializes correctly (bigint input)', () => {
+      const bytes = anySerializer.serialize(toLong(67n));
+      assert.strictEqual(bytes[0], DataType.LONG);
+    });
+
+    it('toLong string input serializes without precision loss', () => {
+      const bytes = anySerializer.serialize(toLong('9007199254740993'));
+      assert.strictEqual(bytes[0], DataType.LONG);
+    });
+
+    it('toShort(-1) — negative value', () => {
+      const bytes = anySerializer.serialize(toShort(-1));
+      assert.strictEqual(bytes[0], DataType.SHORT);
+    });
+
+    it('toByte(-128) — negative value', () => {
+      const bytes = anySerializer.serialize(toByte(-128));
+      assert.strictEqual(bytes[0], DataType.BYTE);
+    });
+
+    it('toFloat(NaN)', () => {
+      const bytes = anySerializer.serialize(toFloat(NaN));
+      assert.strictEqual(bytes[0], DataType.FLOAT);
+    });
+
+    it('toFloat(Infinity)', () => {
+      const bytes = anySerializer.serialize(toFloat(Infinity));
+      assert.strictEqual(bytes[0], DataType.FLOAT);
+    });
+
+    it('toFloat(-0)', () => {
+      const bytes = anySerializer.serialize(toFloat(-0));
+      assert.strictEqual(bytes[0], DataType.FLOAT);
+    });
+
+    it('toDouble(NaN)', () => {
+      const bytes = anySerializer.serialize(toDouble(NaN));
+      assert.strictEqual(bytes[0], DataType.DOUBLE);
+    });
+
+    it('toDouble(Infinity)', () => {
+      const bytes = anySerializer.serialize(toDouble(Infinity));
+      assert.strictEqual(bytes[0], DataType.DOUBLE);
+    });
+
+    it('toDouble(-Infinity)', () => {
+      const bytes = anySerializer.serialize(toDouble(-Infinity));
+      assert.strictEqual(bytes[0], DataType.DOUBLE);
+    });
+
+    it('toDouble(-0)', () => {
+      const bytes = anySerializer.serialize(toDouble(-0));
+      assert.strictEqual(bytes[0], DataType.DOUBLE);
+    });
+  });
+
+  describe('Round-trip through default reader', () => {
+    it('toFloat(1.5) round-trips to plain number', async () => {
+      const bytes = anySerializer.serialize(toFloat(1.5));
+      const result = await 
anySerializer.deserialize(StreamReader.fromBuffer(bytes));
+      assert.strictEqual(result, 1.5);
+    });
+
+    it('toInt(67) round-trips to plain number', async () => {
+      const bytes = anySerializer.serialize(toInt(67));
+      const result = await 
anySerializer.deserialize(StreamReader.fromBuffer(bytes));
+      assert.strictEqual(result, 67);
+    });
+
+    it('toLong string value round-trips without precision loss', async () => {
+      const bytes = anySerializer.serialize(toLong('9007199254740993'));
+      const result = await 
anySerializer.deserialize(StreamReader.fromBuffer(bytes));
+      assert.strictEqual(result, 9007199254740993n);
+    });
+  });
+
+  describe('Integration with traversal structures', () => {
+    it('wrappers work inside P predicates', () => {
+      const predicate = P.gt(toFloat(1.0));
+      const bytes = anySerializer.serialize(predicate);
+      // P serializes successfully — the inner Float gets correct type code
+      assert.ok(bytes.length > 0);
+    });
+
+    it('wrappers inside collections get correct type codes', async () => {
+      const bytes = anySerializer.serialize([toFloat(1.0), toInt(2)]);
+      const reader = StreamReader.fromBuffer(bytes);
+      // Skip list type code (1 byte) and length (4 bytes)
+      const result = await anySerializer.deserialize(reader);
+      // The list deserializes — each element was serialized with its own type 
code
+      assert.strictEqual(result.length, 2);
+      assert.strictEqual(result[0], 1.0);
+      assert.strictEqual(result[1], 2);
+    });
+  });
+});
diff --git a/gremlin-js/gremlin-javascript/test/unit/gremlin-lang-test.js 
b/gremlin-js/gremlin-javascript/test/unit/gremlin-lang-test.js
index 40472c99f5..a8c4e9add0 100644
--- a/gremlin-js/gremlin-javascript/test/unit/gremlin-lang-test.js
+++ b/gremlin-js/gremlin-javascript/test/unit/gremlin-lang-test.js
@@ -27,7 +27,7 @@ import { ReadOnlyStrategy, SubgraphStrategy, OptionsStrategy,
          PartitionStrategy, SeedStrategy } from 
'../../lib/process/traversal-strategy.js';
 import { Graph, Vertex } from '../../lib/structure/graph.js';
 import { TraversalStrategies } from '../../lib/process/traversal-strategy.js';
-import { Long } from '../../lib/utils.js';
+import { Long, toFloat, toDouble, toShort, toByte, toInt, toLong } from 
'../../lib/utils.js';
 import GremlinLang from '../../lib/process/gremlin-lang.js';
 
 const g = new GraphTraversalSource(new Graph(), new TraversalStrategies());
@@ -434,6 +434,138 @@ describe('GremlinLang', function () {
       const view = new Uint8Array(ab, 1, 3); // bytes [1, 2, 3]
       assert.strictEqual(g.inject(view).getGremlinLang().getGremlin(), 
'g.inject(Binary("AQID"))');
     });
+
+    it('should handle toFloat with round number', function () {
+      assert.strictEqual(g.V(toFloat(1.0)).getGremlinLang().getGremlin(), 
'g.V(1.0F)');
+    });
+
+    it('should handle toFloat with fractional number', function () {
+      assert.strictEqual(g.V(toFloat(1.5)).getGremlinLang().getGremlin(), 
'g.V(1.5F)');
+    });
+
+    it('should handle toDouble with round number', function () {
+      assert.strictEqual(g.V(toDouble(1.0)).getGremlinLang().getGremlin(), 
'g.V(1.0D)');
+    });
+
+    it('should handle toDouble with fractional number', function () {
+      assert.strictEqual(g.V(toDouble(1.5)).getGremlinLang().getGremlin(), 
'g.V(1.5D)');
+    });
+
+    it('should handle toShort', function () {
+      assert.strictEqual(g.V(toShort(5)).getGremlinLang().getGremlin(), 
'g.V(5S)');
+    });
+
+    it('should handle toShort with negative value', function () {
+      assert.strictEqual(g.V(toShort(-32768)).getGremlinLang().getGremlin(), 
'g.V(-32768S)');
+    });
+
+    it('should handle toByte', function () {
+      assert.strictEqual(g.V(toByte(127)).getGremlinLang().getGremlin(), 
'g.V(127B)');
+    });
+
+    it('should handle toByte with negative value', function () {
+      assert.strictEqual(g.V(toByte(-128)).getGremlinLang().getGremlin(), 
'g.V(-128B)');
+    });
+
+    it('should handle toInt', function () {
+      assert.strictEqual(g.V(toInt(42)).getGremlinLang().getGremlin(), 
'g.V(42)');
+    });
+
+    it('should handle toLong with bigint input', function () {
+      assert.strictEqual(g.V(toLong(42n)).getGremlinLang().getGremlin(), 
'g.V(42L)');
+    });
+
+    it('should handle toLong with number input', function () {
+      assert.strictEqual(g.V(toLong(42)).getGremlinLang().getGremlin(), 
'g.V(42L)');
+    });
+
+    it('should handle toLong with string input', function () {
+      
assert.strictEqual(g.V(toLong('9007199254740993')).getGremlinLang().getGremlin(),
 'g.V(9007199254740993L)');
+    });
+
+    it('should handle toFloat with zero', function () {
+      assert.strictEqual(g.V(toFloat(0)).getGremlinLang().getGremlin(), 
'g.V(0.0F)');
+    });
+
+    it('should handle toDouble with zero', function () {
+      assert.strictEqual(g.V(toDouble(0)).getGremlinLang().getGremlin(), 
'g.V(0.0D)');
+    });
+
+    it('should handle toFloat with NaN', function () {
+      assert.strictEqual(g.V(toFloat(NaN)).getGremlinLang().getGremlin(), 
'g.V(NaN)');
+    });
+
+    it('should handle toFloat with Infinity', function () {
+      assert.strictEqual(g.V(toFloat(Infinity)).getGremlinLang().getGremlin(), 
'g.V(+Infinity)');
+    });
+
+    it('should handle toFloat with -Infinity', function () {
+      
assert.strictEqual(g.V(toFloat(-Infinity)).getGremlinLang().getGremlin(), 
'g.V(-Infinity)');
+    });
+
+    it('should handle toDouble with NaN', function () {
+      assert.strictEqual(g.V(toDouble(NaN)).getGremlinLang().getGremlin(), 
'g.V(NaN)');
+    });
+
+    it('should handle toDouble with Infinity', function () {
+      
assert.strictEqual(g.V(toDouble(Infinity)).getGremlinLang().getGremlin(), 
'g.V(+Infinity)');
+    });
+
+    it('should handle toDouble with -Infinity', function () {
+      
assert.strictEqual(g.V(toDouble(-Infinity)).getGremlinLang().getGremlin(), 
'g.V(-Infinity)');
+    });
+
+    it('should handle toLong with negative number input', function () {
+      assert.strictEqual(g.V(toLong(-1)).getGremlinLang().getGremlin(), 
'g.V(-1L)');
+    });
+
+    it('should handle toLong with int64 min bigint', function () {
+      
assert.strictEqual(g.V(toLong(-9223372036854775808n)).getGremlinLang().getGremlin(),
 'g.V(-9223372036854775808L)');
+    });
+
+    it('should handle toInt with negative value', function () {
+      assert.strictEqual(g.V(toInt(-1)).getGremlinLang().getGremlin(), 
'g.V(-1)');
+    });
+
+    it('should handle toInt at int32 max', function () {
+      assert.strictEqual(g.V(toInt(2147483647)).getGremlinLang().getGremlin(), 
'g.V(2147483647)');
+    });
+
+    it('should handle toInt at int32 min', function () {
+      
assert.strictEqual(g.V(toInt(-2147483648)).getGremlinLang().getGremlin(), 
'g.V(-2147483648)');
+    });
+
+    it('should handle toShort at boundary values', function () {
+      assert.strictEqual(g.V(toShort(32767)).getGremlinLang().getGremlin(), 
'g.V(32767S)');
+    });
+
+    it('should handle toByte at zero', function () {
+      assert.strictEqual(g.V(toByte(0)).getGremlinLang().getGremlin(), 
'g.V(0B)');
+    });
+
+    it('should handle toFloat inside P.gt', function () {
+      assert.strictEqual(g.V().has('x', 
P.gt(toFloat(1.5))).getGremlinLang().getGremlin(), "g.V().has('x',gt(1.5F))");
+    });
+
+    it('should handle toShort inside P.between', function () {
+      assert.strictEqual(g.V().has('x', P.between(toShort(1), 
toShort(10))).getGremlinLang().getGremlin(), "g.V().has('x',between(1S,10S))");
+    });
+
+    it('should handle toInt inside P.within', function () {
+      assert.strictEqual(g.V().has('x', P.within([toInt(1), 
toInt(2)])).getGremlinLang().getGremlin(), "g.V().has('x',within([1,2]))");
+    });
+
+    it('should handle toDouble inside P.gt', function () {
+      assert.strictEqual(g.V().has('x', 
P.gt(toDouble(3.14))).getGremlinLang().getGremlin(), 
"g.V().has('x',gt(3.14D))");
+    });
+
+    it('should handle typed wrappers in inject array', function () {
+      assert.strictEqual(g.inject([toFloat(1.5), 
toFloat(2.5)]).getGremlinLang().getGremlin(), 'g.inject([1.5F,2.5F])');
+    });
+
+    it('should handle typed wrapper in inject Map', function () {
+      assert.strictEqual(g.inject(new Map([['x', 
toDouble(3.14)]])).getGremlinLang().getGremlin(), "g.inject(['x':3.14D])");
+    });
   });
 
   describe('Unsupported type tests', function () {

Reply via email to