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

commit 51cefd8c28f5201a70893ab81ff10372e128854a
Merge: fba170065a 390ab7012a
Author: Yang Xia <[email protected]>
AuthorDate: Mon Jul 27 11:55:47 2026 -0700

    Merge branch '3.8-dev'
    
    # Conflicts:
    #       
gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/OffsetDateTimeSerializer.js
    #       
gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
    #       
gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js
    #       
gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/OffsetDateTimeSerializer-test.js

 CHANGELOG.asciidoc                                 |  1 +
 .../io/binary/internals/DateTimeSerializer.js      |  9 +++++++
 .../test/unit/graphbinary/model-test.js            | 29 ++++++++++++++--------
 .../test/unit/graphbinary/model.js                 |  4 ---
 4 files changed, 29 insertions(+), 14 deletions(-)

diff --cc CHANGELOG.asciidoc
index a20724ab35,c4379896bf..27fb924d12
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@@ -225,9 -27,12 +225,10 @@@ image::https://raw.githubusercontent.co
  
  This release also includes changes from prior 3.7.x releases.
  
 -* Enabled building and running with Java 21 and Java 25 (experimental; 
`spark-gremlin` excluded, as Spark 3.3.x only runs on Java 8 through 17).
 -* Bumped to Groovy 4.0.32 which adds support for parsing Java 25 bytecode.
 -* Bumped Hadoop to 3.4.3 (and Kerby to 2.0.3) to enable `hadoop-gremlin` to 
build and run on Java 25.
  * Add missing `Configuring` interface to `GraphStepPlaceholder` and 
`VertexStepPlaceholder`
  * Fixed bug in `group()` value traversal where keys were retained with stale 
barrier state instead of being filtered when steps following a `Barrier` in the 
second `by()` produced no output (e.g. `by(values("age").fold().unfold())` or 
`by(__.out().fold().count(local).is(P.gt(0)))` for vertices with no out-edges).
 -* Fixed bug in `gremlin-javascript` GraphBinary and GraphSON deserialization 
where `OffsetDateTime` values outside the JavaScript `Date` range were silently 
returned as invalid `Date` objects instead of failing deserialization.
 +* Corrected numerous inaccuracies in the reference documentation, including 
wrong default values (connection pool sizes, buffer sizes, ports, timeouts), 
stale serializer class names, removed options documented as available, and 
broken code examples across the JVM, Python, `.NET`, Go, and JavaScript drivers.
++* Fixed bug in `gremlin-javascript` GraphBinary deserialization where 
`DateTime` values outside the JavaScript `Date` range were silently returned as 
invalid `Date` objects instead of failing deserialization.
  * Added a `propertyMap()` helper to view an element's properties as a map 
keyed by property key, on the `Element` structure API in `gremlin-core` 
(inherited by `Vertex`, `Edge`, and `VertexProperty`) and on `Vertex`, `Edge`, 
and `VertexProperty` in `gremlin-javascript`, `gremlin-python`, 
`gremlin-dotnet`, and `gremlin-go`.
  * Fixed a bug in `gremlin-go` where a `VertexProperty` deserialized from 
GraphBinary did not have its `Key` field populated, causing 
`Vertex.PropertyMap()` to group properties under an empty key.
  
diff --cc 
gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/DateTimeSerializer.js
index bc2555d77e,0000000000..db1cfc697d
mode 100644,000000..100644
--- 
a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/DateTimeSerializer.js
+++ 
b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/DateTimeSerializer.js
@@@ -1,137 -1,0 +1,146 @@@
 +/*
 + *  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 { Buffer } from 'buffer';
 +
 +export default class DateTimeSerializer {
 +  constructor(ioc) {
 +    this.ioc = ioc;
 +    this.ID = ioc.DataType.DATETIME;
 +    this.ioc.serializers[this.ID] = this;
 +  }
 +
 +  canBeUsedFor(value) {
 +    return value instanceof Date;
 +  }
 +
 +  serialize(item, fullyQualifiedFormat = true) {
 +    if (item === undefined || item === null) {
 +      if (fullyQualifiedFormat) {
 +        return Buffer.from([this.ID, 0x01]);
 +      }
 +      return Buffer.alloc(18);
 +    }
 +
 +    const bufs = [];
 +    if (fullyQualifiedFormat) {
 +      bufs.push(Buffer.from([this.ID, 0x00]));
 +    }
 +
 +    const v = Buffer.alloc(18);
 +    let offset = 0;
 +
 +    // Year (Int32BE)
 +    v.writeInt32BE(item.getUTCFullYear(), offset);
 +    offset += 4;
 +
 +    // Month (UInt8, 1-based)
 +    v.writeUInt8(item.getUTCMonth() + 1, offset);
 +    offset += 1;
 +
 +    // Day (UInt8, 1-based)
 +    v.writeUInt8(item.getUTCDate(), offset);
 +    offset += 1;
 +
 +    // Nanoseconds since midnight (BigInt64BE)
 +    const hours = item.getUTCHours();
 +    const minutes = item.getUTCMinutes();
 +    const seconds = item.getUTCSeconds();
 +    const millis = item.getUTCMilliseconds();
 +    const nanos = BigInt(hours * 3600 + minutes * 60 + seconds) * 
1_000_000_000n + BigInt(millis) * 1_000_000n;
 +    v.writeBigInt64BE(nanos, offset);
 +    offset += 8;
 +
 +    // UTC offset in seconds (Int32BE) - always 0 for JS Date
 +    v.writeInt32BE(0, offset);
 +
 +    bufs.push(v);
 +    return Buffer.concat(bufs);
 +  }
 +
 +  /**
 +   * @param {StreamReader} reader
 +   * @param {number} valueFlag
 +   * @param {number} typeCode
 +   * @returns {Promise<Date>}
 +   */
 +  async deserializeValue(reader, valueFlag, typeCode) {
 +    // 18 bytes: year(4) + month(1) + day(1) + nanos(8) + utcOffset(4)
 +    const buf = await reader.readBytes(18);
 +
 +    let offset = 0;
 +    const year = buf.readInt32BE(offset);
 +    offset += 4;
 +    const month = buf.readUInt8(offset);
 +    offset += 1;
 +    const day = buf.readUInt8(offset);
 +    offset += 1;
 +    const nanos = buf.readBigInt64BE(offset);
 +    offset += 8;
 +    const utcOffset = buf.readInt32BE(offset);
 +
 +    // Convert nanos to time components
 +    const hours = Number(nanos / 3_600_000_000_000n);
 +    const remainingNanos = nanos % 3_600_000_000_000n;
 +    const minutes = Number(remainingNanos / 60_000_000_000n);
 +    const remainingNanos2 = remainingNanos % 60_000_000_000n;
 +    const seconds = Number(remainingNanos2 / 1_000_000_000n);
 +    const millis = Number((remainingNanos2 % 1_000_000_000n) / 1_000_000n);
 +
 +    const v = new Date(Date.UTC(year, month - 1, day, hours, minutes, 
seconds, millis));
 +    // Date.UTC treats years 0-99 as 1900-1999, correct it
 +    if (year >= 0 && year <= 99) {
 +      v.setUTCFullYear(year);
 +    }
 +    // Adjust for non-zero UTC offset (JS Date is always UTC internally)
 +    if (utcOffset !== 0) {
 +      v.setTime(v.getTime() - utcOffset * 1000);
 +    }
 +
++    // The DateTime wire format can carry values (e.g. extreme years near 
+/-999999999) that fall
++    // outside the range representable by a JavaScript Date. In those cases 
Date.UTC(...) returns NaN
++    // and new Date(NaN) yields an invalid Date without throwing. Reject such 
values here so
++    // unsupported boundary date-times fail deserialization instead of 
silently producing an unusable
++    // Date instance.
++    if (Number.isNaN(v.getTime())) {
++      throw new Error('DateTimeSerializer: {value} is outside the range 
supported by JavaScript Date');
++    }
++
 +    return v;
 +  }
 +
 +  /**
 +   * @param {StreamReader} reader
 +   * @returns {Promise<Date|null>}
 +   */
 +  async deserialize(reader) {
 +    const type_code = await reader.readUInt8();
 +    if (type_code !== this.ID) {
 +      throw new Error(`DateTimeSerializer: unexpected 
{type_code}=0x${type_code.toString(16)}`);
 +    }
 +    const value_flag = await reader.readUInt8();
 +    if (value_flag === 0x01) {
 +      return null;
 +    }
 +    if (value_flag !== 0x00) {
 +      throw new Error(`DateTimeSerializer: unexpected 
{value_flag}=0x${value_flag.toString(16)}`);
 +    }
 +    return this.deserializeValue(reader, value_flag, type_code);
 +  }
 +}
diff --cc gremlin-js/gremlin-javascript/test/unit/graphbinary/model-test.js
index a61bfb566e,0000000000..e115ff0a2c
mode 100644,000000..100644
--- a/gremlin-js/gremlin-javascript/test/unit/graphbinary/model-test.js
+++ b/gremlin-js/gremlin-javascript/test/unit/graphbinary/model-test.js
@@@ -1,360 -1,0 +1,369 @@@
 +/*
 + *  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.
 + */
 +
 +/*
 + * GraphBinaryV4 .gbin reference file validation against expected JS model 
values.
 + *
 + * Set the IO_TEST_DIRECTORY environment variable to the directory where
 + * the .gbin files that represent the serialized "model" are located.
 + */
 +
 +import { assert } from 'chai';
 +import { readFileSync } from 'fs';
 +import { fileURLToPath } from 'url';
 +import { model } from './model.js';
 +import ioc from '../../../lib/structure/io/binary/GraphBinary.js';
 +import StreamReader from 
'../../../lib/structure/io/binary/internals/StreamReader.js';
 +
 +const { anySerializer } = ioc;
 +
 +const gbinDir = 
'../../gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary/';
 +const searchPattern = 'gremlin-javascript/src/main';
 +const thisFile = fileURLToPath(import.meta.url);
 +const defaultDir = thisFile.substring(0, thisFile.indexOf(searchPattern));
 +const testResourceDirectory = process.env.IO_TEST_DIRECTORY || (defaultDir + 
gbinDir);
 +
 +function readGbinFile(name) {
 +  return readFileSync(testResourceDirectory + name + '-v4.gbin');
 +}
 +
 +// byte-exact: deserialize, serialize, round-trip, length tracking
 +function run(name, comparator = assertEqual) {
 +  describe(name, () => {
 +    const fileBytes = readGbinFile(name);
 +    const modelValue = model[name];
 +
 +    it('deserialize .gbin matches model', async () => {
 +      const result = await 
anySerializer.deserialize(StreamReader.fromBuffer(fileBytes));
 +      comparator(result, modelValue);
 +    });
 +
 +    it('serialize model matches .gbin bytes', () => {
 +      const serialized = anySerializer.serialize(modelValue);
 +      assert.deepStrictEqual(serialized, fileBytes);
 +    });
 +
 +    it('round-trip serialize(deserialize(fileBytes)) matches .gbin', async () 
=> {
 +      const deserialized = await 
anySerializer.deserialize(StreamReader.fromBuffer(fileBytes));
 +      const reserialized = anySerializer.serialize(deserialized);
 +      assert.deepStrictEqual(reserialized, fileBytes);
 +    });
 +
 +    it('length tracking', async () => {
 +      const garbageBytes = Buffer.concat([fileBytes, Buffer.from([0xFF])]);
 +      const reader = StreamReader.fromBuffer(garbageBytes);
 +      await anySerializer.deserialize(reader);
 +      assert.strictEqual(reader.position, fileBytes.length);
 +    });
 +  });
 +}
 +
 +// object-level: deserialize, double round-trip idempotency, length tracking
 +function runWriteRead(name, comparator = assertEqual) {
 +  describe(name, () => {
 +    const fileBytes = readGbinFile(name);
 +    const modelValue = model[name];
 +
 +    it('deserialize .gbin matches model', async () => {
 +      const result = await 
anySerializer.deserialize(StreamReader.fromBuffer(fileBytes));
 +      comparator(result, modelValue);
 +    });
 +
 +    it('double round-trip idempotency', async () => {
 +      const firstRoundTrip = await anySerializer.deserialize(
 +        StreamReader.fromBuffer(anySerializer.serialize(modelValue)),
 +      );
 +      const secondRoundTrip = await anySerializer.deserialize(
 +        StreamReader.fromBuffer(anySerializer.serialize(firstRoundTrip)),
 +      );
 +      comparator(firstRoundTrip, secondRoundTrip);
 +    });
 +
 +    it('length tracking', async () => {
 +      const garbageBytes = Buffer.concat([fileBytes, Buffer.from([0xFF])]);
 +      const reader = StreamReader.fromBuffer(garbageBytes);
 +      await anySerializer.deserialize(reader);
 +      assert.strictEqual(reader.position, fileBytes.length);
 +    });
 +  });
 +}
 +
 +// deserialize-only: deserialize, length tracking
 +function runRead(name, comparator = assertEqual) {
 +  describe(name, () => {
 +    const fileBytes = readGbinFile(name);
 +    const modelValue = model[name];
 +
 +    it('deserialize .gbin matches model', async () => {
 +      const result = await 
anySerializer.deserialize(StreamReader.fromBuffer(fileBytes));
 +      comparator(result, modelValue);
 +    });
 +
 +    it('length tracking', async () => {
 +      const garbageBytes = Buffer.concat([fileBytes, Buffer.from([0xFF])]);
 +      const reader = StreamReader.fromBuffer(garbageBytes);
 +      await anySerializer.deserialize(reader);
 +      assert.strictEqual(reader.position, fileBytes.length);
 +    });
 +  });
 +}
 +
++// deserialize-only: assert that reading the .gbin reference rejects with the 
expected error
++function runReadThrows(name, errorPattern) {
++  describe(name, () => {
++    const fileBytes = readGbinFile(name);
++
++    it('deserialize .gbin rejects', async () => {
++      try {
++        await anySerializer.deserialize(StreamReader.fromBuffer(fileBytes));
++        assert.fail('Expected an error to be thrown');
++      } catch (e) {
++        if (errorPattern) assert.match(e.message, errorPattern);
++      }
++    });
++  });
++}
++
 +function assertEqual(actual, expected) {
 +  if (Number.isNaN(expected) && Number.isNaN(actual)) return;
 +  if (Object.is(expected, -0) && Object.is(actual, -0)) return;
 +  if (Buffer.isBuffer(expected) && Buffer.isBuffer(actual)) {
 +    assert.isTrue(expected.equals(actual));
 +    return;
 +  }
 +  if (expected instanceof Set && actual instanceof Set) {
 +    assert.deepStrictEqual([...expected].sort(), [...actual].sort());
 +    return;
 +  }
 +  if (typeof expected === 'bigint' && typeof actual === 'bigint') {
 +    assert.strictEqual(actual, expected);
 +    return;
 +  }
 +  assert.deepStrictEqual(actual, expected);
 +}
 +
 +function nanComparator(actual, expected) {
 +  assert.isTrue(Number.isNaN(actual));
 +  assert.isTrue(Number.isNaN(expected));
 +}
 +
 +function negZeroComparator(actual, expected) {
 +  assert.isTrue(Object.is(actual, -0));
 +  assert.isTrue(Object.is(expected, -0));
 +}
 +
 +function setComparator(actual, expected) {
 +  assert.instanceOf(actual, Set);
 +  assert.instanceOf(expected, Set);
 +  assert.deepStrictEqual([...actual].sort(), [...expected].sort());
 +}
 +
 +function orderedMapComparator(actual, expected) {
 +  assert.instanceOf(actual, Map);
 +  assert.instanceOf(expected, Map);
 +  assert.deepStrictEqual([...actual.entries()], [...expected.entries()]);
 +}
 +
 +function treeComparator(actual, expected) {
 +  assert.isTrue(actual.equals(expected));
 +}
 +
 +function pathComparator(actual, expected) {
 +  assert.deepStrictEqual(actual.objects, expected.objects);
 +  assert.strictEqual(actual.labels.length, expected.labels.length);
 +  for (let ix = 0; ix < actual.labels.length; ix++) {
 +    setComparator(actual.labels[ix], expected.labels[ix]);
 +  }
 +}
 +
 +function vertexLabelComparator(actual, expected) {
 +  assert.strictEqual(actual.id, expected.id);
 +  setComparator(actual.labels, expected.labels);
 +}
 +
 +function propertyComparator(actual, expected) {
 +  assert.strictEqual(actual.key, expected.key);
 +  assert.deepStrictEqual(actual.value, expected.value);
 +}
 +
 +function vertexPropertyComparator(actual, expected) {
 +  assert.strictEqual(actual.id, expected.id);
 +  assert.strictEqual(actual.label, expected.label);
 +  assert.deepStrictEqual(actual.value, expected.value);
 +  assert.strictEqual(actual.properties.length, expected.properties.length);
 +  for (let ix = 0; ix < actual.properties.length; ix++) {
 +    propertyComparator(actual.properties[ix], expected.properties[ix]);
 +  }
 +}
 +
 +function vertexComparator(actual, expected) {
 +  assert.strictEqual(actual.id, expected.id);
 +  assert.strictEqual(actual.label, expected.label);
 +  setComparator(actual.labels, expected.labels);
 +  assert.strictEqual(actual.properties.length, expected.properties.length);
 +  for (let ix = 0; ix < actual.properties.length; ix++) {
 +    vertexPropertyComparator(actual.properties[ix], expected.properties[ix]);
 +  }
 +}
 +
 +function edgeComparator(actual, expected) {
 +  assert.strictEqual(actual.id, expected.id);
 +  assert.strictEqual(actual.label, expected.label);
 +  setComparator(actual.labels, expected.labels);
 +  assert.strictEqual(actual.outV.id, expected.outV.id);
 +  assert.strictEqual(actual.inV.id, expected.inV.id);
 +  assert.strictEqual(actual.properties.length, expected.properties.length);
 +  for (let ix = 0; ix < actual.properties.length; ix++) {
 +    propertyComparator(actual.properties[ix], expected.properties[ix]);
 +  }
 +}
 +
 +function graphComparator(actual, expected) {
 +  assert.strictEqual(actual.vertices.size, expected.vertices.size);
 +  assert.strictEqual(actual.edges.size, expected.edges.size);
 +  assert.deepStrictEqual([...actual.vertices.keys()], 
[...expected.vertices.keys()]);
 +  assert.deepStrictEqual([...actual.edges.keys()], 
[...expected.edges.keys()]);
 +  for (const [id, expectedVertex] of expected.vertices) {
 +    vertexComparator(actual.vertices.get(id), expectedVertex);
 +  }
 +  for (const [id, expectedEdge] of expected.edges) {
 +    edgeComparator(actual.edges.get(id), expectedEdge);
 +  }
 +}
 +
 +function primitivePdtComparator(actual, expected) {
 +  assert.strictEqual(actual.name, expected.name);
 +  assert.strictEqual(actual.value, expected.value);
 +}
 +
 +function compositePdtComparator(actual, expected) {
 +  assert.strictEqual(actual.name, expected.name);
 +  assert.deepStrictEqual(actual.fields, expected.fields);
 +}
 +
 +function setCardinalityComparator(actual, expected) {
 +  assert.strictEqual(actual.id, expected.id);
 +  assert.strictEqual(actual.label, expected.label);
 +  assert.instanceOf(actual.value, Set);
 +  assert.instanceOf(expected.value, Set);
 +  assert.deepStrictEqual([...actual.value].sort(), 
[...expected.value].sort());
 +  assert.deepStrictEqual(actual.properties, expected.properties);
 +}
 +
- function invalidDateComparator(actual, expected) {
-   assert.isTrue(actual instanceof Date);
-   assert.isTrue(expected instanceof Date);
-   assert.isTrue(Number.isNaN(actual.getTime()));
-   assert.isTrue(Number.isNaN(expected.getTime()));
- }
- 
 +describe('GraphBinary v4 Model Tests', () => {
 +  // run mode
 +  run('pos-biginteger');
 +  run('neg-biginteger');
 +  run('zero-biginteger');
 +  run('sign-boundary-pos-biginteger');
 +  run('sign-boundary-neg-biginteger');
 +  run('uint8-primitive-pdt', primitivePdtComparator);
 +  run('point-composite-pdt', compositePdtComparator);
 +  run('empty-binary');
 +  run('str-binary');
 +  run('max-double');
 +  run('min-double');
 +  run('neg-max-double');
 +  run('neg-min-double');
 +  run('nan-double', nanComparator);
 +  run('pos-inf-double');
 +  run('neg-inf-double');
 +  run('unspecified-null');
 +  run('true-boolean');
 +  run('false-boolean');
 +  run('single-byte-string');
 +  run('mixed-string');
 +  run('empty-string');
 +  run('var-type-list');
 +  run('empty-list');
 +  run('no-prop-edge');
 +  run('max-int');
 +  run('min-int');
 +  run('empty-map');
 +  run('traversal-path');
 +  run('empty-path');
 +  run('path-zero-labels', pathComparator);
 +  run('empty-tree', treeComparator);
 +  run('tree-null-key', treeComparator);
 +  run('tree-mixed-key-types', treeComparator);
 +  run('tree-deep-nesting', treeComparator);
 +  run('edge-property');
 +  run('null-property');
 +  run('empty-set');
 +  run('no-prop-vertex');
 +  run('multi-label-vertex', vertexLabelComparator);
 +  run('empty-label-vertex', vertexLabelComparator);
 +  run('id-t');
 +  run('out-direction');
 +  run('merge-on-create');
 +  run('merge-on-match');
 +  run('merge-out-v');
 +  run('merge-in-v');
 +  run('neg-zero-double', negZeroComparator);
 +
 +  // runWriteRead mode
 +  runWriteRead('min-byte');
 +  runWriteRead('max-byte');
 +  runWriteRead('max-float');
 +  runWriteRead('min-float');
 +  runWriteRead('neg-max-float');
 +  runWriteRead('neg-min-float');
 +  runWriteRead('nan-float', nanComparator);
 +  runWriteRead('pos-inf-float');
 +  runWriteRead('neg-inf-float');
 +  runWriteRead('var-bulklist');
 +  runWriteRead('empty-bulklist');
 +  runWriteRead('traversal-edge');
 +  runWriteRead('min-long');
 +  runWriteRead('max-long');
 +  runWriteRead('var-type-set', setComparator);
 +  runWriteRead('max-short');
 +  runWriteRead('min-short');
 +  runWriteRead('specified-uuid');
 +  runWriteRead('nil-uuid');
 +  runWriteRead('traversal-vertexproperty');
 +  runWriteRead('meta-vertexproperty');
 +  runWriteRead('set-cardinality-vertexproperty', setCardinalityComparator);
 +  runWriteRead('traversal-vertex');
 +  // JS deserializes safe Long values to Number, so vertex-property ids can't 
be re-emitted byte-exactly.
 +  runWriteRead('tinker-graph', graphComparator);
 +  // label set ordering isn't guaranteed after a JS object round trip.
 +  runWriteRead('path-multiple-labels', pathComparator);
 +  // JS preserves Map entry order, but its writer doesn't emit the 
ordered-map value flag.
 +  runWriteRead('ordered-string-int-map', orderedMapComparator);
 +  runWriteRead('traversal-tree', treeComparator); // vertex properties aren't 
serialized
 +
 +  // runRead mode
 +  // JS Number can't re-emit this as a Float.
 +  runRead('neg-zero-float', negZeroComparator);
-   // invalid Date models can't be serialized back.
-   runRead('max-offsetdatetime', invalidDateComparator);
-   runRead('min-offsetdatetime', invalidDateComparator);
++  // DateTime values outside the JS Date range are rejected rather than 
deserialized to invalid Dates.
++  runReadThrows('max-offsetdatetime', /outside the range supported by 
JavaScript Date/);
++  runReadThrows('min-offsetdatetime', /outside the range supported by 
JavaScript Date/);
 +  // properties aren't serialized in JS for this path fixture.
 +  runRead('prop-path');
 +  // this fixture has complex keys that JS can't write back byte-exactly.
 +  runRead('var-type-map');
 +  // typed nulls deserialize to plain null, so the original type can't be 
re-emitted.
 +  runRead('null-int');
 +  runRead('null-long');
 +  runRead('null-string');
 +  runRead('null-list');
 +  runRead('null-map');
 +  runRead('null-set');
 +});
diff --cc gremlin-js/gremlin-javascript/test/unit/graphbinary/model.js
index c06f4c4965,0000000000..0c18009940
mode 100644,000000..100644
--- a/gremlin-js/gremlin-javascript/test/unit/graphbinary/model.js
+++ b/gremlin-js/gremlin-javascript/test/unit/graphbinary/model.js
@@@ -1,336 -1,0 +1,332 @@@
 +/*
 + *  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 {
 +  Graph,
 +  Vertex,
 +  Edge,
 +  Property,
 +  VertexProperty,
 +  Path,
 +  Tree,
 +  CompositePDT,
 +  PrimitivePDT
 +} from '../../../lib/structure/graph.js';
 +import { direction, merge, t } from '../../../lib/process/traversal.js';
 +
 +/*
 + * Unsupported types (no map entries, .gbin files exist but types not 
implemented):
 + * - single-byte-char, two-byte-char, three-byte-char, four-byte-char (Char 
0x80 not implemented)
 + * - pos-bigdecimal, neg-bigdecimal, zero-bigdecimal, scale-zero-bigdecimal,
 + *   negative-scale-bigdecimal, small-decimal-bigdecimal (BigDecimal type not 
implemented)
 + * - forever-duration, zero-duration, positive-duration, negative-duration,
 + *   nanos-duration (Duration type not implemented)
 + */
 +
 +const model = {};
 +
 +// BigInteger values
 +model['pos-biginteger'] = BigInt('123456789987654321123456789987654321');
 +model['neg-biginteger'] = BigInt('-123456789987654321123456789987654321');
 +model['zero-biginteger'] = 0n;
 +model['sign-boundary-pos-biginteger'] = 128n;
 +model['sign-boundary-neg-biginteger'] = -129n;
 +
 +// Provider-defined type values
 +model['uint8-primitive-pdt'] = new PrimitivePDT('Uint8', '10');
 +model['point-composite-pdt'] = new CompositePDT('Point', { x: 1, y: 2 });
 +
 +// Byte values (JS has no byte type, deserializes to Number)
 +model['min-byte'] = -128;
 +model['max-byte'] = 127;
 +
 +// Binary values
 +model['empty-binary'] = Buffer.from('', 'utf-8');
 +model['str-binary'] = Buffer.from('some bytes for you', 'utf-8');
 +
 +// Double values
 +model['max-double'] = 1.7976931348623157e+308;
 +model['min-double'] = 5e-324;
 +model['neg-max-double'] = -1.7976931348623157e+308;
 +model['neg-min-double'] = -5e-324;
 +model['nan-double'] = NaN;
 +model['pos-inf-double'] = Infinity;
 +model['neg-inf-double'] = -Infinity;
 +model['neg-zero-double'] = -0;
 +
 +// Float values (JS has no float type, deserializes to Number with IEEE 754 
double representation)
 +model['max-float'] = 3.4028234663852886e+38;
 +model['min-float'] = 1.401298464324817e-45;
 +model['neg-max-float'] = -3.4028234663852886e+38;
 +model['neg-min-float'] = -1.401298464324817e-45;
 +model['nan-float'] = NaN;
 +model['pos-inf-float'] = Infinity;
 +model['neg-inf-float'] = -Infinity;
 +model['neg-zero-float'] = -0;
 +
 +// Null
 +model['unspecified-null'] = null;
 +model['null-int'] = null;
 +model['null-long'] = null;
 +model['null-string'] = null;
 +model['null-list'] = null;
 +model['null-map'] = null;
 +model['null-set'] = null;
 +
 +// Boolean values
 +model['true-boolean'] = true;
 +model['false-boolean'] = false;
 +
 +// String values
 +model['single-byte-string'] = 'abc';
 +model['mixed-string'] = 'abc\u0391\u0392\u0393';
 +model['empty-string'] = '';
 +
 +// List values
 +model['var-bulklist'] = ['marko', 'josh', 'josh'];
 +model['empty-bulklist'] = [];
 +model['var-type-list'] = [1, 'person', true, null];
 +model['empty-list'] = [];
 +
 +// Edge values
 +model['traversal-edge'] = new Edge(
 +  13,
 +  new Vertex(1, 'person'),    // outV (first in JS constructor)
 +  'develops',
 +  new Vertex(10, 'software'), // inV
 +  [new Property('since', 2009)]
 +);
 +model['no-prop-edge'] = new Edge(
 +  13,
 +  new Vertex(1, 'person'),
 +  'develops',
 +  new Vertex(10, 'software')
 +);
 +
 +// Integer values
 +model['max-int'] = 2147483647;
 +model['min-int'] = -2147483648;
 +
 +// Long values
 +model['max-long'] = 9223372036854775807n;
 +model['min-long'] = -9223372036854775808n;
 +
 +// Map values
 +const dateKey = new Date(Date.UTC(1970, 0, 1, 0, 24, 41, 295)); // 1481295 ms
 +model['var-type-map'] = new Map([
 +  [null, null],
 +  [[1, 2, 3], dateKey],
 +  [dateKey, 'red'],
 +  ['test', 123]
 +]);
 +model['empty-map'] = new Map();
 +model['ordered-string-int-map'] = new Map([
 +  ['delta', 4],
 +  ['alpha', 1],
 +  ['charlie', 3],
 +  ['bravo', 2],
 +  ['echo', 5],
 +  ['foxtrot', 6]
 +]);
 +
 +// Path values
 +model['traversal-path'] = new Path(
 +  [new Set(), new Set(), new Set()],
 +  [new Vertex(1, 'person'), new Vertex(10, 'software'), new Vertex(11, 
'software')]
 +);
 +model['empty-path'] = new Path([], []);
 +model['path-zero-labels'] = new Path([new Set()], ['marko']);
 +model['path-multiple-labels'] = new Path([new Set(['a', 'b'])], ['marko']);
 +
 +// Tree values
 +// tree for g.V(10).out().tree(): v[10] -> v[11]
 +const traversalTree = new Tree();
 +traversalTree.getOrCreateChild(new Vertex(10, 
'software')).getOrCreateChild(new Vertex(11, 'software'));
 +model['traversal-tree'] = traversalTree;
 +model['empty-tree'] = new Tree();
 +const treeNullKey = new Tree();
 +treeNullKey.getOrCreateChild(null);
 +model['tree-null-key'] = treeNullKey;
 +const treeMixedKeyTypes = new Tree();
 +treeMixedKeyTypes.getOrCreateChild('name');
 +treeMixedKeyTypes.getOrCreateChild(123);
 +model['tree-mixed-key-types'] = treeMixedKeyTypes;
 +const treeDeepNesting = new Tree();
 
+treeDeepNesting.getOrCreateChild('root').getOrCreateChild('branch').getOrCreateChild('leaf');
 +model['tree-deep-nesting'] = treeDeepNesting;
 +
 +// Complex path with nested properties
 +const propPathVertex = new Vertex(1, 'person', [
 +  new VertexProperty(0, 'name', 'marko'),
 +  new VertexProperty(6, 'location', 'san diego', [
 +    new Property('startTime', 1997),
 +    new Property('endTime', 2001)
 +  ]),
 +  new VertexProperty(7, 'location', 'santa cruz', [
 +    new Property('startTime', 2001),
 +    new Property('endTime', 2004)
 +  ]),
 +  new VertexProperty(8, 'location', 'brussels', [
 +    new Property('startTime', 2004),
 +    new Property('endTime', 2005)
 +  ]),
 +  new VertexProperty(9, 'location', 'santa fe', [
 +    new Property('startTime', 2005)
 +  ])
 +]);
 +const propPathVertex2 = new Vertex(10, 'software', [
 +  new VertexProperty(4, 'name', 'gremlin')
 +]);
 +const propPathVertex3 = new Vertex(11, 'software', [
 +  new VertexProperty(5, 'name', 'tinkergraph')
 +]);
 +model['prop-path'] = new Path(
 +  [new Set(), new Set(), new Set()],
 +  [propPathVertex, propPathVertex2, propPathVertex3]
 +);
 +
 +// Property values (no parent param in JS)
 +model['edge-property'] = new Property('since', 2009);
 +model['null-property'] = new Property('', null);
 +
 +// Set values
 +model['var-type-set'] = new Set([2, 'person', true, null]);
 +model['empty-set'] = new Set();
 +
 +// Short values (JS has no short type, deserializes to Number)
 +model['max-short'] = 32767;
 +model['min-short'] = -32768;
 +
 +// UUID values (plain strings in JS)
 +model['specified-uuid'] = '41d2e28a-20a4-4ab0-b379-d810dede3786';
 +model['nil-uuid'] = '00000000-0000-0000-0000-000000000000';
 +
 +// Vertex values
 +model['no-prop-vertex'] = new Vertex(1, 'person');
 +
 +// VertexProperty values (no parent param, key equals label)
 +model['traversal-vertexproperty'] = new VertexProperty(0, 'name', 'marko');
 +model['meta-vertexproperty'] = new VertexProperty(1, 'person', 'stephen', 
[new Property('a', 'b')]);
 +model['set-cardinality-vertexproperty'] = new VertexProperty(1, 'person', new 
Set(['stephen', 'marko']), [new Property('a', 'b')]);
 +
 +// Enum values
 +model['id-t'] = t.id;
 +model['out-direction'] = direction.out;
 +model['merge-on-create'] = merge.onCreate;
 +model['merge-on-match'] = merge.onMatch;
 +model['merge-out-v'] = merge.outV;
 +model['merge-in-v'] = merge.inV;
 +
 +// Complex vertex with properties (from .gbin deserialization structure)
 +const name = new VertexProperty(0, 'name', 'marko');
 +const sanDiego = new VertexProperty(6, 'location', 'san diego', [
 +  new Property('startTime', 1997),
 +  new Property('endTime', 2001)
 +]);
 +const santaCruz = new VertexProperty(7, 'location', 'santa cruz', [
 +  new Property('startTime', 2001),
 +  new Property('endTime', 2004)
 +]);
 +const brussels = new VertexProperty(8, 'location', 'brussels', [
 +  new Property('startTime', 2004),
 +  new Property('endTime', 2005)
 +]);
 +const santaFe = new VertexProperty(9, 'location', 'santa fe', [
 +  new Property('startTime', 2005)
 +]);
 +model['traversal-vertex'] = new Vertex(1, 'person', [name, sanDiego, 
santaCruz, brussels, santaFe]);
 +model['multi-label-vertex'] = new Vertex(1, ['person', 'employee']);
 +model['empty-label-vertex'] = new Vertex(1, 'vertex', [], []);
 +
- // DateTime values (invalid dates for overflow cases)
- model['max-offsetdatetime'] = new Date(NaN);  // Year 999999999 overflows JS 
Date
- model['min-offsetdatetime'] = new Date(NaN);  // Year -999999999 overflows JS 
Date
- 
 +class CrewGraphFactory {
 +  static vertexProperty(id, label, value, metaProperties = []) {
 +    return new VertexProperty(
 +      id,
 +      label,
 +      value,
 +      metaProperties.map(([key, metaValue]) => new Property(key, metaValue))
 +    );
 +  }
 +
 +  static addVertex(graph, id, label, propertySpecs) {
 +    const vertex = new Vertex(id, label);
 +    for (const spec of propertySpecs) {
 +      vertex.properties.push(CrewGraphFactory.vertexProperty(spec[0], 
spec[1], spec[2], spec[3] || []));
 +    }
 +    graph.vertices.set(id, vertex);
 +    return vertex;
 +  }
 +
 +  static addEdge(graph, id, outV, label, inV, properties = []) {
 +    const edge = new Edge(id, outV, label, inV, properties.map(([key, value]) 
=> new Property(key, value)));
 +    graph.edges.set(id, edge);
 +    return edge;
 +  }
 +
 +  static create() {
 +    const graph = new Graph();
 +    const v1 = CrewGraphFactory.addVertex(graph, 1, 'person', [
 +      [0, 'name', 'marko'],
 +      [6, 'location', 'san diego', [['startTime', 1997], ['endTime', 2001]]],
 +      [7, 'location', 'santa cruz', [['startTime', 2001], ['endTime', 2004]]],
 +      [8, 'location', 'brussels', [['startTime', 2004], ['endTime', 2005]]],
 +      [9, 'location', 'santa fe', [['startTime', 2005]]]
 +    ]);
 +    const v7 = CrewGraphFactory.addVertex(graph, 7, 'person', [
 +      [1, 'name', 'stephen'],
 +      [10, 'location', 'centreville', [['startTime', 1990], ['endTime', 
2000]]],
 +      [11, 'location', 'dulles', [['startTime', 2000], ['endTime', 2006]]],
 +      [12, 'location', 'purcellville', [['startTime', 2006]]]
 +    ]);
 +    const v8 = CrewGraphFactory.addVertex(graph, 8, 'person', [
 +      [2, 'name', 'matthias'],
 +      [13, 'location', 'bremen', [['startTime', 2004], ['endTime', 2007]]],
 +      [14, 'location', 'baltimore', [['startTime', 2007], ['endTime', 2011]]],
 +      [15, 'location', 'oakland', [['startTime', 2011], ['endTime', 2014]]],
 +      [16, 'location', 'seattle', [['startTime', 2014]]]
 +    ]);
 +    const v9 = CrewGraphFactory.addVertex(graph, 9, 'person', [
 +      [3, 'name', 'daniel'],
 +      [17, 'location', 'spremberg', [['startTime', 1982], ['endTime', 2005]]],
 +      [18, 'location', 'kaiserslautern', [['startTime', 2005], ['endTime', 
2009]]],
 +      [19, 'location', 'aachen', [['startTime', 2009]]]
 +    ]);
 +    const v10 = CrewGraphFactory.addVertex(graph, 10, 'software', [[4, 
'name', 'gremlin']]);
 +    const v11 = CrewGraphFactory.addVertex(graph, 11, 'software', [[5, 
'name', 'tinkergraph']]);
 +    CrewGraphFactory.addEdge(graph, 13, v1, 'develops', v10, [['since', 
2009]]);
 +    CrewGraphFactory.addEdge(graph, 14, v1, 'develops', v11, [['since', 
2010]]);
 +    CrewGraphFactory.addEdge(graph, 15, v1, 'uses', v10, [['skill', 4]]);
 +    CrewGraphFactory.addEdge(graph, 16, v1, 'uses', v11, [['skill', 5]]);
 +    CrewGraphFactory.addEdge(graph, 17, v7, 'develops', v10, [['since', 
2010]]);
 +    CrewGraphFactory.addEdge(graph, 18, v7, 'develops', v11, [['since', 
2011]]);
 +    CrewGraphFactory.addEdge(graph, 19, v7, 'uses', v10, [['skill', 5]]);
 +    CrewGraphFactory.addEdge(graph, 20, v7, 'uses', v11, [['skill', 4]]);
 +    CrewGraphFactory.addEdge(graph, 21, v8, 'develops', v10, [['since', 
2012]]);
 +    CrewGraphFactory.addEdge(graph, 22, v8, 'uses', v10, [['skill', 3]]);
 +    CrewGraphFactory.addEdge(graph, 23, v8, 'uses', v11, [['skill', 3]]);
 +    CrewGraphFactory.addEdge(graph, 24, v9, 'uses', v10, [['skill', 5]]);
 +    CrewGraphFactory.addEdge(graph, 25, v9, 'uses', v11, [['skill', 3]]);
 +    CrewGraphFactory.addEdge(graph, 26, v10, 'traverses', v11);
 +    return graph;
 +  }
 +}
 +
 +model['tinker-graph'] = CrewGraphFactory.create();
 +
 +export { model };


Reply via email to