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

Cole-Greer pushed a commit to branch simplePDT
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 16316eb11f605d87bee4586807b1c0f00a5b8052
Author: Cole Greer <[email protected]>
AuthorDate: Wed Jun 24 20:30:04 2026 -0700

    Add PrimitivePDT support to gremlin-javascript GLV
    
    Implements PrimitivePDT in the JavaScript GLV, mirroring composite support
    and applying the review lessons from the Python GLV.
    
    - PrimitiveProviderDefinedType (name, value) in structure/graph.ts.
    - PrimitivePDTSerializer replaces the prior StubSerializer for
      DataType.PRIMITIVEPDT (0xf1): writes/reads two fully-qualified Strings.
    - ProviderDefinedTypeRegistry gains an explicit primitive adapter path
      (registerPrimitive / getPrimitiveAdapterByClass / hydratePrimitive),
      mirroring the composite/primitive split used in Java/Python.
    - gremlin-lang text serialization emits PDT("name","value") for a
      PrimitiveProviderDefinedType and auto-dehydrates classes with a registered
      primitive adapter (primitive checked before composite). This is the
      client-side text path that was the Python gap; covered by unit tests here.
    - Client/connection reuse the existing pdtRegistry option.
    
    No GraphSON g:PrimitivePdt read path is added (consistent with the JS 
driver's
    GraphBinary-based V4 response handling; nothing fabricated).
    
    Tests: unit tests (serializer round-trip incl. opaque-value fidelity, 
registry
    hydration, gremlin-lang PDT text emission) — full unit suite passing
    (21082 tests). Integration tests (raw/unregistered, registered de/hydration,
    nested-in-composite) pass against the test server: 4/4.
    
    tinkerpop-2gy.9
    
    Assisted-by: Kiro:claude-opus-4.8
---
 gremlin-js/gremlin-javascript/lib/index.ts         |   1 +
 .../gremlin-javascript/lib/process/gremlin-lang.ts |  12 +-
 .../lib/structure/ProviderDefinedTypeRegistry.ts   |  45 +++++-
 .../gremlin-javascript/lib/structure/graph.ts      |  19 +++
 .../lib/structure/io/binary/GraphBinary.js         |   4 +-
 .../structure/io/binary/internals/AnySerializer.js |   1 +
 .../io/binary/internals/PrimitivePDTSerializer.js  |  84 ++++++++++
 .../test/integration/client-tests.js               |  49 +++++-
 .../test/integration/traversal-test.js             | 123 ++++++++++++++-
 .../graphbinary/PrimitivePDTSerializer-test.js     | 172 +++++++++++++++++++++
 .../test/unit/gremlin-lang-test.js                 |  98 +++++++++++-
 .../test/unit/pdt-registry-test.js                 | 119 +++++++++++++-
 12 files changed, 719 insertions(+), 8 deletions(-)

diff --git a/gremlin-js/gremlin-javascript/lib/index.ts 
b/gremlin-js/gremlin-javascript/lib/index.ts
index ef0e7ce133..ae04f1ffb2 100644
--- a/gremlin-js/gremlin-javascript/lib/index.ts
+++ b/gremlin-js/gremlin-javascript/lib/index.ts
@@ -85,6 +85,7 @@ export const structure = {
   Graph: graph.Graph,
   Path: graph.Path,
   Property: graph.Property,
+  PrimitiveProviderDefinedType: graph.PrimitiveProviderDefinedType,
   ProviderDefinedType: graph.ProviderDefinedType,
   ProviderDefinedTypeRegistry,
   Vertex: graph.Vertex,
diff --git a/gremlin-js/gremlin-javascript/lib/process/gremlin-lang.ts 
b/gremlin-js/gremlin-javascript/lib/process/gremlin-lang.ts
index 070a88fceb..fdc8bbfa9a 100644
--- a/gremlin-js/gremlin-javascript/lib/process/gremlin-lang.ts
+++ b/gremlin-js/gremlin-javascript/lib/process/gremlin-lang.ts
@@ -20,7 +20,7 @@
 import { P, TextP, EnumValue } from './traversal.js';
 import { OptionsStrategy, TraversalStrategy } from './traversal-strategy.js';
 import { Long, Int, Float, Double, Short, Byte, INT32_MIN, INT32_MAX } from 
'../utils.js';
-import { Vertex, ProviderDefinedType } from '../structure/graph.js';
+import { Vertex, ProviderDefinedType, PrimitiveProviderDefinedType } from 
'../structure/graph.js';
 import { ProviderDefinedTypeRegistry } from 
'../structure/ProviderDefinedTypeRegistry.js';
 import { Buffer } from 'buffer';
 
@@ -131,6 +131,11 @@ export default class GremlinLang {
     if (typeof arg === 'function' && arg.prototype instanceof 
TraversalStrategy) {
       return arg.name;
     }
+    if (arg instanceof PrimitiveProviderDefinedType) {
+      const escapedName = JSON.stringify(arg.name).slice(1, -1);
+      const escapedValue = JSON.stringify(arg.value).slice(1, -1);
+      return `PDT("${escapedName}","${escapedValue}")`;
+    }
     if (arg instanceof ProviderDefinedType) {
       const fields = arg.fields;
       const keys = Object.keys(fields);
@@ -180,6 +185,11 @@ export default class GremlinLang {
     }
     // Registry-based dehydration
     if (this.pdtRegistry && typeof arg === 'object' && arg.constructor) {
+      const primitiveEntry = 
this.pdtRegistry.getPrimitiveAdapterByClass(arg.constructor);
+      if (primitiveEntry) {
+        const value = primitiveEntry.toValue(arg);
+        return this._argAsString(new 
PrimitiveProviderDefinedType(primitiveEntry.typeName, value));
+      }
       const entry = this.pdtRegistry.getAdapterByClass(arg.constructor);
       if (entry) {
         const fields = entry.serialize(arg);
diff --git 
a/gremlin-js/gremlin-javascript/lib/structure/ProviderDefinedTypeRegistry.ts 
b/gremlin-js/gremlin-javascript/lib/structure/ProviderDefinedTypeRegistry.ts
index 7c560147a6..fa57c6532a 100644
--- a/gremlin-js/gremlin-javascript/lib/structure/ProviderDefinedTypeRegistry.ts
+++ b/gremlin-js/gremlin-javascript/lib/structure/ProviderDefinedTypeRegistry.ts
@@ -17,20 +17,28 @@
  *  under the License.
  */
 
-import { ProviderDefinedType } from './graph.js';
+import { ProviderDefinedType, PrimitiveProviderDefinedType } from './graph.js';
 
 export interface PdtAdapter {
   serialize: (obj: any) => Record<string, any>;
   deserialize: (fields: Record<string, any>) => any;
 }
 
+export interface PrimitivePdtAdapter {
+  toValue: (obj: any) => string;
+  fromValue: (value: string) => any;
+}
+
 /**
  * A standalone registry that allows users to register adapters for hydrating
- * raw {@link ProviderDefinedType} instances into domain-specific objects.
+ * raw {@link ProviderDefinedType} and {@link PrimitiveProviderDefinedType} 
instances
+ * into domain-specific objects.
  */
 export class ProviderDefinedTypeRegistry {
   private readonly _adapters: Map<string, PdtAdapter> = new Map();
   private readonly _adaptersByClass: Map<Function, { typeName: string; 
adapter: PdtAdapter }> = new Map();
+  private readonly _primitiveAdapters: Map<string, PrimitivePdtAdapter> = new 
Map();
+  private readonly _primitiveAdaptersByClass: Map<Function, { typeName: 
string; adapter: PrimitivePdtAdapter }> = new Map();
 
   register(typeName: string, adapter: PdtAdapter, targetClass?: Function): 
void {
     this._adapters.set(typeName, adapter);
@@ -39,6 +47,13 @@ export class ProviderDefinedTypeRegistry {
     }
   }
 
+  registerPrimitive(typeName: string, adapter: PrimitivePdtAdapter, 
targetClass?: Function): void {
+    this._primitiveAdapters.set(typeName, adapter);
+    if (targetClass) {
+      this._primitiveAdaptersByClass.set(targetClass, { typeName, adapter });
+    }
+  }
+
   hydrate(pdt: any): any {
     if (!(pdt instanceof ProviderDefinedType)) return pdt;
     const adapter = this._adapters.get(pdt.name);
@@ -49,6 +64,10 @@ export class ProviderDefinedTypeRegistry {
         const h = this.hydrate(v);
         hydratedFields[k] = h;
         if (h !== v) changed = true;
+      } else if (v instanceof PrimitiveProviderDefinedType) {
+        const h = this.hydratePrimitive(v);
+        hydratedFields[k] = h;
+        if (h !== v) changed = true;
       } else {
         hydratedFields[k] = v;
       }
@@ -64,10 +83,26 @@ export class ProviderDefinedTypeRegistry {
     }
   }
 
+  hydratePrimitive(pdt: any): any {
+    if (!(pdt instanceof PrimitiveProviderDefinedType)) return pdt;
+    const adapter = this._primitiveAdapters.get(pdt.name);
+    if (!adapter) return pdt;
+    try {
+      return adapter.fromValue(pdt.value);
+    } catch (e: any) {
+      console.warn(`Primitive PDT hydration failed for '${pdt.name}': 
${e.message}`);
+      return pdt;
+    }
+  }
+
   hasAdapter(typeName: string): boolean {
     return this._adapters.has(typeName);
   }
 
+  hasPrimitiveAdapter(typeName: string): boolean {
+    return this._primitiveAdapters.has(typeName);
+  }
+
   getSerializer(typeName: string): ((obj: any) => Record<string, any>) | null {
     const adapter = this._adapters.get(typeName);
     return adapter ? adapter.serialize : null;
@@ -78,4 +113,10 @@ export class ProviderDefinedTypeRegistry {
     if (!entry) return null;
     return { typeName: entry.typeName, serialize: entry.adapter.serialize };
   }
+
+  getPrimitiveAdapterByClass(cls: Function): { typeName: string; toValue: 
(obj: any) => string } | null {
+    const entry = this._primitiveAdaptersByClass.get(cls);
+    if (!entry) return null;
+    return { typeName: entry.typeName, toValue: entry.adapter.toValue };
+  }
 }
diff --git a/gremlin-js/gremlin-javascript/lib/structure/graph.ts 
b/gremlin-js/gremlin-javascript/lib/structure/graph.ts
index 75baa70014..17b447b3cc 100644
--- a/gremlin-js/gremlin-javascript/lib/structure/graph.ts
+++ b/gremlin-js/gremlin-javascript/lib/structure/graph.ts
@@ -214,6 +214,25 @@ export class ProviderDefinedType {
   }
 }
 
+/**
+ * Represents a primitive Provider Defined Type (PDT).
+ */
+export class PrimitiveProviderDefinedType {
+  readonly name: string;
+  readonly value: string;
+
+  constructor(name: string, value: string) {
+    if (!name) throw new Error('PrimitiveProviderDefinedType name cannot be 
null or empty');
+    if (value === null || value === undefined) throw new 
Error('PrimitiveProviderDefinedType value cannot be null');
+    this.name = name;
+    this.value = value;
+  }
+
+  toString() {
+    return `pdt[${this.name}:${this.value}]`;
+  }
+}
+
 function summarize(value: any) {
   if (value === null || value === undefined) {
     return value;
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 ab3cd58b0b..188c44be7c 100644
--- a/gremlin-js/gremlin-javascript/lib/structure/io/binary/GraphBinary.js
+++ b/gremlin-js/gremlin-javascript/lib/structure/io/binary/GraphBinary.js
@@ -67,6 +67,7 @@ import UnspecifiedNullSerializer from 
'./internals/UnspecifiedNullSerializer.js'
 import EnumSerializer from './internals/EnumSerializer.js';
 import StubSerializer from './internals/StubSerializer.js';
 import CompositePDTSerializer from './internals/CompositePDTSerializer.js';
+import PrimitivePDTSerializer from './internals/PrimitivePDTSerializer.js';
 import NumberSerializationStrategy from 
'./internals/NumberSerializationStrategy.js';
 import AnySerializer from './internals/AnySerializer.js';
 import GraphBinaryReader from './internals/GraphBinaryReader.js';
@@ -107,10 +108,10 @@ function createIoc(anySerializerOptions) {
   ioc.unspecifiedNullSerializer = new UnspecifiedNullSerializer(ioc);
   ioc.enumSerializer = new EnumSerializer(ioc);
   ioc.compositePDTSerializer = new CompositePDTSerializer(ioc);
+  ioc.primitivePDTSerializer = new PrimitivePDTSerializer(ioc);
 
   // Register stub serializers for unimplemented v4 types
   new StubSerializer(ioc, ioc.DataType.TREE, 'Tree');
-  new StubSerializer(ioc, ioc.DataType.PRIMITIVEPDT, 'PrimitivePDT');
 
   ioc.pdtRegistry = null;
 
@@ -177,6 +178,7 @@ export const {
   unspecifiedNullSerializer,
   enumSerializer,
   compositePDTSerializer,
+  primitivePDTSerializer,
   numberSerializationStrategy,
   anySerializer,
   graphBinaryReader,
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 94841ad355..e4010789bc 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
@@ -44,6 +44,7 @@ export default class AnySerializer {
       ioc.enumSerializer,
       ioc.stringSerializer,
       ioc.binarySerializer,
+      ioc.primitivePDTSerializer,
       ioc.compositePDTSerializer,
       ioc.mapSerializer,
     ];
diff --git 
a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/PrimitivePDTSerializer.js
 
b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/PrimitivePDTSerializer.js
new file mode 100644
index 0000000000..311007e5a7
--- /dev/null
+++ 
b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/PrimitivePDTSerializer.js
@@ -0,0 +1,84 @@
+/*
+ *  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';
+import { PrimitiveProviderDefinedType } from '../../../graph.js';
+
+export default class PrimitivePDTSerializer {
+  constructor(ioc) {
+    this.ioc = ioc;
+    this.ioc.serializers[ioc.DataType.PRIMITIVEPDT] = this;
+  }
+
+  canBeUsedFor(value) {
+    return value instanceof PrimitiveProviderDefinedType;
+  }
+
+  serialize(item, fullyQualifiedFormat = true) {
+    if (item === undefined || item === null) {
+      if (fullyQualifiedFormat) {
+        return Buffer.from([this.ioc.DataType.PRIMITIVEPDT, 0x01]);
+      }
+      const bufs = [];
+      bufs.push(this.ioc.stringSerializer.serialize('', false));
+      bufs.push(this.ioc.stringSerializer.serialize('', false));
+      return Buffer.concat(bufs);
+    }
+
+    const bufs = [];
+    if (fullyQualifiedFormat) {
+      bufs.push(Buffer.from([this.ioc.DataType.PRIMITIVEPDT, 0x00]));
+    }
+    bufs.push(this.ioc.stringSerializer.serialize(item.name, true));
+    bufs.push(this.ioc.stringSerializer.serialize(item.value, true));
+    return Buffer.concat(bufs);
+  }
+
+  async deserializeValue(reader, valueFlag, typeCode) {
+    const name = await this.ioc.anySerializer.deserialize(reader);
+    if (!name) {
+      throw new Error('PrimitivePDTSerializer: name cannot be null or empty');
+    }
+    const value = await this.ioc.anySerializer.deserialize(reader);
+    const pdt = new PrimitiveProviderDefinedType(name, value != null ? 
String(value) : '');
+    const pdtRegistry = reader.pdtRegistry;
+    if (pdtRegistry) {
+      const hydrated = pdtRegistry.hydratePrimitive(pdt);
+      if (!(hydrated instanceof PrimitiveProviderDefinedType)) {
+        return hydrated;
+      }
+    }
+    return pdt;
+  }
+
+  async deserialize(reader) {
+    const type_code = await reader.readUInt8();
+    if (type_code !== this.ioc.DataType.PRIMITIVEPDT) {
+      throw new Error(`PrimitivePDTSerializer: 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(`PrimitivePDTSerializer: unexpected 
{value_flag}=0x${value_flag.toString(16)}`);
+    }
+    return this.deserializeValue(reader, value_flag, type_code);
+  }
+}
diff --git a/gremlin-js/gremlin-javascript/test/integration/client-tests.js 
b/gremlin-js/gremlin-javascript/test/integration/client-tests.js
index cf439aaa56..0e8f490e95 100644
--- a/gremlin-js/gremlin-javascript/test/integration/client-tests.js
+++ b/gremlin-js/gremlin-javascript/test/integration/client-tests.js
@@ -18,7 +18,7 @@
  */
 
 import assert from 'assert';
-import { Vertex, Edge, VertexProperty, ProviderDefinedType } from 
'../../lib/structure/graph.js';
+import { Vertex, Edge, VertexProperty, ProviderDefinedType, 
PrimitiveProviderDefinedType } from '../../lib/structure/graph.js';
 import { getClient, serverUrl } from '../helper.js';
 import { cardinality } from '../../lib/process/traversal.js';
 import Client from '../../lib/driver/client.js';
@@ -273,4 +273,51 @@ describe('ProviderDefinedType - Client', function () {
         assert.strictEqual(list[1].fields.y, 4);
       });
   });
+});
+
+describe('PrimitiveProviderDefinedType - Client', function () {
+  let pdtClient;
+  before(function () {
+    pdtClient = getClient('gmodern');
+    return pdtClient.open();
+  });
+  after(function () {
+    return pdtClient.close();
+  });
+
+  it('should round-trip a simple primitive PDT', function () {
+    return pdtClient.submit('g.inject(PDT("Uint32","42"))')
+      .then(function (result) {
+        assert.strictEqual(result.length, 1);
+        const pdt = result.first();
+        assert.ok(pdt instanceof PrimitiveProviderDefinedType);
+        assert.strictEqual(pdt.name, 'Uint32');
+        assert.strictEqual(pdt.value, '42');
+      });
+  });
+
+  it('should round-trip a primitive PDT with leading zeros', function () {
+    return pdtClient.submit('g.inject(PDT("TinkerId","007"))')
+      .then(function (result) {
+        assert.strictEqual(result.length, 1);
+        const pdt = result.first();
+        assert.ok(pdt instanceof PrimitiveProviderDefinedType);
+        assert.strictEqual(pdt.name, 'TinkerId');
+        assert.strictEqual(pdt.value, '007');
+      });
+  });
+
+  it('should handle primitive PDTs in a collection', function () {
+    return pdtClient.submit('g.inject([PDT("Uint32","1"), PDT("Uint32","2")])')
+      .then(function (result) {
+        assert.strictEqual(result.length, 1);
+        const list = result.first();
+        assert.ok(Array.isArray(list));
+        assert.strictEqual(list.length, 2);
+        assert.ok(list[0] instanceof PrimitiveProviderDefinedType);
+        assert.strictEqual(list[0].value, '1');
+        assert.ok(list[1] instanceof PrimitiveProviderDefinedType);
+        assert.strictEqual(list[1].value, '2');
+      });
+  });
 });
\ No newline at end of file
diff --git a/gremlin-js/gremlin-javascript/test/integration/traversal-test.js 
b/gremlin-js/gremlin-javascript/test/integration/traversal-test.js
index 867080b040..98e2fedfed 100644
--- a/gremlin-js/gremlin-javascript/test/integration/traversal-test.js
+++ b/gremlin-js/gremlin-javascript/test/integration/traversal-test.js
@@ -23,7 +23,7 @@
 
 import assert from 'assert';
 import { AssertionError } from 'assert';
-import {Edge, Vertex, VertexProperty, ProviderDefinedType} from 
'../../lib/structure/graph.js';
+import {Edge, Vertex, VertexProperty, ProviderDefinedType, 
PrimitiveProviderDefinedType} from '../../lib/structure/graph.js';
 import { ProviderDefinedTypeRegistry } from 
'../../lib/structure/ProviderDefinedTypeRegistry.js';
 import anon from '../../lib/process/anonymous-traversal.js';
 import { GraphTraversalSource, GraphTraversal, statics } from 
'../../lib/process/graph-traversal.js';
@@ -404,3 +404,124 @@ describe('ProviderDefinedType - Traversal API', function 
() {
     });
   });
 });
+
+describe('PrimitiveProviderDefinedType - Traversal API', function () {
+  describe('raw primitive PDT round-trip via Traversal API', function () {
+    let pdtConnection;
+
+    before(function () {
+      pdtConnection = getConnection('gmodern');
+      return pdtConnection.open();
+    });
+    after(function () {
+      return pdtConnection.close();
+    });
+
+    it('should round-trip a primitive PDT via g.inject()', async function () {
+      const g = anon.traversal().with_(pdtConnection);
+      const pdt = new PrimitiveProviderDefinedType('Uint32', '42');
+
+      const results = await g.inject(pdt).toList();
+
+      assert.strictEqual(results.length, 1);
+      const result = results[0];
+      assert.ok(result instanceof PrimitiveProviderDefinedType);
+      assert.strictEqual(result.name, 'Uint32');
+      assert.strictEqual(result.value, '42');
+    });
+
+    it('should round-trip an unregistered primitive PDT (raw)', async function 
() {
+      const g = anon.traversal().with_(pdtConnection);
+      const pdt = new PrimitiveProviderDefinedType('UnregisteredType', 
'opaque-value');
+
+      const results = await g.inject(pdt).toList();
+
+      assert.strictEqual(results.length, 1);
+      const result = results[0];
+      assert.ok(result instanceof PrimitiveProviderDefinedType);
+      assert.strictEqual(result.name, 'UnregisteredType');
+      assert.strictEqual(result.value, 'opaque-value');
+    });
+  });
+
+  describe('registry-based primitive round-trip via typed object', function () 
{
+    let pdtConnection;
+
+    class Uint32 {
+      constructor(v) {
+        this.v = v;
+      }
+    }
+
+    before(function () {
+      const registry = new ProviderDefinedTypeRegistry();
+      registry.registerPrimitive('Uint32', {
+        toValue: (obj) => String(obj.v),
+        fromValue: (value) => new Uint32(parseInt(value, 10)),
+      }, Uint32);
+      pdtConnection = new DriverRemoteConnection(serverUrl, {
+        traversalSource: 'gmodern',
+        pdtRegistry: registry,
+      });
+      return pdtConnection.open();
+    });
+    after(function () {
+      return pdtConnection.close();
+    });
+
+    it('should auto-dehydrate primitive on send and auto-hydrate on receive', 
async function () {
+      const g = anon.traversal().with_(pdtConnection);
+      const val = new Uint32(99);
+
+      const results = await g.inject(val).toList();
+
+      assert.strictEqual(results.length, 1);
+      const result = results[0];
+      assert.ok(result instanceof Uint32);
+      assert.strictEqual(result.v, 99);
+    });
+  });
+
+  describe('nested composite containing primitive PDT', function () {
+    let pdtConnection;
+
+    class Uint32 {
+      constructor(v) {
+        this.v = v;
+      }
+    }
+
+    before(function () {
+      const registry = new ProviderDefinedTypeRegistry();
+      registry.registerPrimitive('Uint32', {
+        toValue: (obj) => String(obj.v),
+        fromValue: (value) => new Uint32(parseInt(value, 10)),
+      }, Uint32);
+      pdtConnection = new DriverRemoteConnection(serverUrl, {
+        traversalSource: 'gmodern',
+        pdtRegistry: registry,
+      });
+      return pdtConnection.open();
+    });
+    after(function () {
+      return pdtConnection.close();
+    });
+
+    it('should hydrate nested primitive inside composite', async function () {
+      const g = anon.traversal().with_(pdtConnection);
+      const inner = new PrimitiveProviderDefinedType('Uint32', '55');
+      const outer = new ProviderDefinedType('Measurement', { unit: 'kg', 
amount: inner });
+
+      const results = await g.inject(outer).toList();
+
+      assert.strictEqual(results.length, 1);
+      const result = results[0];
+      assert.ok(result instanceof ProviderDefinedType);
+      assert.strictEqual(result.name, 'Measurement');
+      assert.strictEqual(result.fields.unit, 'kg');
+      // The nested primitive PDT should be hydrated to Uint32
+      assert.ok(result.fields.amount instanceof Uint32);
+      assert.strictEqual(result.fields.amount.v, 55);
+    });
+  });
+});
diff --git 
a/gremlin-js/gremlin-javascript/test/unit/graphbinary/PrimitivePDTSerializer-test.js
 
b/gremlin-js/gremlin-javascript/test/unit/graphbinary/PrimitivePDTSerializer-test.js
new file mode 100644
index 0000000000..b3a08e038a
--- /dev/null
+++ 
b/gremlin-js/gremlin-javascript/test/unit/graphbinary/PrimitivePDTSerializer-test.js
@@ -0,0 +1,172 @@
+/*
+ *  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 'chai';
+import { PrimitiveProviderDefinedType, ProviderDefinedType } from 
'../../../lib/structure/graph.js';
+import { ProviderDefinedTypeRegistry } from 
'../../../lib/structure/ProviderDefinedTypeRegistry.js';
+import ioc, { DataType } from 
'../../../lib/structure/io/binary/GraphBinary.js';
+import StreamReader from 
'../../../lib/structure/io/binary/internals/StreamReader.js';
+
+const { anySerializer, primitivePDTSerializer } = ioc;
+
+async function roundTrip(value) {
+  const bytes = anySerializer.serialize(value);
+  return anySerializer.deserialize(StreamReader.fromBuffer(bytes));
+}
+
+describe('PrimitivePDTSerializer', () => {
+  describe('round-trip: simple primitive PDT', () => {
+    it('serializes and deserializes a simple PrimitiveProviderDefinedType', 
async () => {
+      const pdt = new PrimitiveProviderDefinedType('Uint32', '42');
+      const result = await roundTrip(pdt);
+      assert.instanceOf(result, PrimitiveProviderDefinedType);
+      assert.strictEqual(result.name, 'Uint32');
+      assert.strictEqual(result.value, '42');
+    });
+
+    it('uses PRIMITIVEPDT type code', () => {
+      const pdt = new PrimitiveProviderDefinedType('Uint32', '123');
+      const bytes = anySerializer.serialize(pdt);
+      assert.strictEqual(bytes[0], DataType.PRIMITIVEPDT);
+    });
+  });
+
+  describe('round-trip: opaque string values', () => {
+    it('handles leading zeros (preserved as string)', async () => {
+      const pdt = new PrimitiveProviderDefinedType('TinkerId', '007');
+      const result = await roundTrip(pdt);
+      assert.instanceOf(result, PrimitiveProviderDefinedType);
+      assert.strictEqual(result.value, '007');
+    });
+
+    it('handles large numbers', async () => {
+      const pdt = new PrimitiveProviderDefinedType('BigNum', 
'99999999999999999999999999');
+      const result = await roundTrip(pdt);
+      assert.strictEqual(result.value, '99999999999999999999999999');
+    });
+
+    it('handles non-numeric values', async () => {
+      const pdt = new PrimitiveProviderDefinedType('CustomId', 'abc-def-123');
+      const result = await roundTrip(pdt);
+      assert.strictEqual(result.value, 'abc-def-123');
+    });
+
+    it('handles empty string value', async () => {
+      const pdt = new PrimitiveProviderDefinedType('Empty', '');
+      const result = await roundTrip(pdt);
+      assert.instanceOf(result, PrimitiveProviderDefinedType);
+      assert.strictEqual(result.name, 'Empty');
+      assert.strictEqual(result.value, '');
+    });
+  });
+
+  describe('empty name rejected', () => {
+    it('constructor rejects empty string name', () => {
+      assert.throws(() => new PrimitiveProviderDefinedType('', '42'), /name 
cannot be null or empty/);
+    });
+
+    it('constructor rejects null name', () => {
+      assert.throws(() => new PrimitiveProviderDefinedType(null, '42'), /name 
cannot be null or empty/);
+    });
+
+    it('constructor rejects undefined name', () => {
+      assert.throws(() => new PrimitiveProviderDefinedType(undefined, '42'), 
/name cannot be null or empty/);
+    });
+
+    it('constructor rejects null value', () => {
+      assert.throws(() => new PrimitiveProviderDefinedType('Uint32', null), 
/value cannot be null/);
+    });
+
+    it('deserializer rejects null name from wire', async () => {
+      const nullString = Buffer.from([DataType.STRING, 0x01]);
+      const valueString = Buffer.from([DataType.STRING, 0x00, 0x00, 0x00, 
0x00, 0x02, 0x34, 0x32]); // "42"
+      const bytes = Buffer.concat([
+        Buffer.from([DataType.PRIMITIVEPDT, 0x00]),
+        nullString,
+        valueString,
+      ]);
+      try {
+        await anySerializer.deserialize(StreamReader.fromBuffer(bytes));
+        assert.fail('should have thrown');
+      } catch (e) {
+        assert.match(e.message, /name cannot be null or empty/);
+      }
+    });
+  });
+
+  describe('canBeUsedFor', () => {
+    it('returns true for PrimitiveProviderDefinedType instances', () => {
+      assert.isTrue(primitivePDTSerializer.canBeUsedFor(new 
PrimitiveProviderDefinedType('t', '1')));
+    });
+
+    it('returns false for plain objects', () => {
+      assert.isFalse(primitivePDTSerializer.canBeUsedFor({ name: 'test', 
value: '1' }));
+    });
+
+    it('returns false for strings', () => {
+      assert.isFalse(primitivePDTSerializer.canBeUsedFor('test'));
+    });
+
+    it('returns false for composite ProviderDefinedType', () => {
+      assert.isFalse(primitivePDTSerializer.canBeUsedFor(new 
ProviderDefinedType('t', { a: 1 })));
+    });
+  });
+
+  describe('auto-hydration via pdtRegistry', () => {
+    it('auto-hydrates when pdtRegistry is set on the reader', async () => {
+      const registry = new ProviderDefinedTypeRegistry();
+      registry.registerPrimitive('Uint32', {
+        toValue: (obj) => String(obj),
+        fromValue: (value) => parseInt(value, 10),
+      });
+
+      const pdt = new PrimitiveProviderDefinedType('Uint32', '42');
+      const bytes = anySerializer.serialize(pdt);
+      const reader = StreamReader.fromBuffer(bytes);
+      reader.pdtRegistry = registry;
+      const result = await anySerializer.deserialize(reader);
+
+      assert.notInstanceOf(result, PrimitiveProviderDefinedType);
+      assert.strictEqual(result, 42);
+    });
+
+    it('returns raw primitive PDT when no pdtRegistry is set', async () => {
+      const pdt = new PrimitiveProviderDefinedType('Uint32', '42');
+      const bytes = anySerializer.serialize(pdt);
+      const result = await 
anySerializer.deserialize(StreamReader.fromBuffer(bytes));
+
+      assert.instanceOf(result, PrimitiveProviderDefinedType);
+      assert.strictEqual(result.name, 'Uint32');
+      assert.strictEqual(result.value, '42');
+    });
+
+    it('returns raw primitive PDT when no adapter registered for that type', 
async () => {
+      const registry = new ProviderDefinedTypeRegistry();
+      const pdt = new PrimitiveProviderDefinedType('Unknown', 'xyz');
+      const bytes = anySerializer.serialize(pdt);
+      const reader = StreamReader.fromBuffer(bytes);
+      reader.pdtRegistry = registry;
+      const result = await anySerializer.deserialize(reader);
+
+      assert.instanceOf(result, PrimitiveProviderDefinedType);
+      assert.strictEqual(result.name, 'Unknown');
+      assert.strictEqual(result.value, 'xyz');
+    });
+  });
+});
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 73ccf69e36..4009a3f72c 100644
--- a/gremlin-js/gremlin-javascript/test/unit/gremlin-lang-test.js
+++ b/gremlin-js/gremlin-javascript/test/unit/gremlin-lang-test.js
@@ -25,10 +25,11 @@ import { P, TextP, t as T, order as Order, scope as Scope, 
column as Column,
          withOptions as WithOptions, direction } from 
'../../lib/process/traversal.js';
 import { ReadOnlyStrategy, SubgraphStrategy, OptionsStrategy,
          PartitionStrategy, SeedStrategy } from 
'../../lib/process/traversal-strategy.js';
-import { Graph, Vertex, ProviderDefinedType } from 
'../../lib/structure/graph.js';
+import { Graph, Vertex, ProviderDefinedType, PrimitiveProviderDefinedType } 
from '../../lib/structure/graph.js';
 import { TraversalStrategies } from '../../lib/process/traversal-strategy.js';
 import { Long, toFloat, toDouble, toShort, toByte, toInt, toLong } from 
'../../lib/utils.js';
 import GremlinLang from '../../lib/process/gremlin-lang.js';
+import { ProviderDefinedTypeRegistry } from 
'../../lib/structure/ProviderDefinedTypeRegistry.js';
 
 const g = new GraphTraversalSource(new Graph(), new TraversalStrategies());
 
@@ -661,4 +662,99 @@ describe('GremlinLang', function () {
       );
     });
   });
+
+  describe('Primitive PDT gremlin-lang tests', function () {
+    it('should handle basic primitive PDT', function () {
+      const pdt = new PrimitiveProviderDefinedType('Uint32', '42');
+      assert.strictEqual(
+        g.inject(pdt).getGremlinLang().getGremlin(),
+        'g.inject(PDT("Uint32","42"))'
+      );
+    });
+
+    it('should handle primitive PDT with leading zeros', function () {
+      const pdt = new PrimitiveProviderDefinedType('TinkerId', '007');
+      assert.strictEqual(
+        g.inject(pdt).getGremlinLang().getGremlin(),
+        'g.inject(PDT("TinkerId","007"))'
+      );
+    });
+
+    it('should handle primitive PDT with large number', function () {
+      const pdt = new PrimitiveProviderDefinedType('BigNum', 
'99999999999999999999');
+      assert.strictEqual(
+        g.inject(pdt).getGremlinLang().getGremlin(),
+        'g.inject(PDT("BigNum","99999999999999999999"))'
+      );
+    });
+
+    it('should handle primitive PDT with non-numeric value', function () {
+      const pdt = new PrimitiveProviderDefinedType('CustomId', 'abc-def-123');
+      assert.strictEqual(
+        g.inject(pdt).getGremlinLang().getGremlin(),
+        'g.inject(PDT("CustomId","abc-def-123"))'
+      );
+    });
+
+    it('should handle primitive PDT with empty value', function () {
+      const pdt = new PrimitiveProviderDefinedType('Empty', '');
+      assert.strictEqual(
+        g.inject(pdt).getGremlinLang().getGremlin(),
+        'g.inject(PDT("Empty",""))'
+      );
+    });
+
+    it('should handle primitive PDT with special chars in name', function () {
+      const pdt = new PrimitiveProviderDefinedType('my"type', '1');
+      assert.strictEqual(
+        g.inject(pdt).getGremlinLang().getGremlin(),
+        'g.inject(PDT("my\\"type","1"))'
+      );
+    });
+
+    it('should handle primitive PDT with special chars in value', function () {
+      const pdt = new PrimitiveProviderDefinedType('Str', 'hello"world');
+      assert.strictEqual(
+        g.inject(pdt).getGremlinLang().getGremlin(),
+        'g.inject(PDT("Str","hello\\"world"))'
+      );
+    });
+
+    it('should auto-dehydrate registered primitive types', function () {
+      class Uint32 {
+        constructor(v) { this.v = v; }
+      }
+      const registry = new ProviderDefinedTypeRegistry();
+      registry.registerPrimitive('Uint32', {
+        toValue: (obj) => String(obj.v),
+        fromValue: (value) => new Uint32(parseInt(value, 10)),
+      }, Uint32);
+
+      const gl = new GremlinLang();
+      gl.pdtRegistry = registry;
+      gl.addStep('inject', [new Uint32(99)]);
+      assert.strictEqual(gl.getGremlin(), 'g.inject(PDT("Uint32","99"))');
+    });
+
+    it('should prefer primitive adapter over composite when both are 
registered', function () {
+      class DualType {
+        constructor(v) { this.v = v; }
+      }
+      const registry = new ProviderDefinedTypeRegistry();
+      registry.registerPrimitive('DualType', {
+        toValue: (obj) => String(obj.v),
+        fromValue: (value) => new DualType(value),
+      }, DualType);
+      registry.register('DualType', {
+        serialize: (obj) => ({ v: obj.v }),
+        deserialize: (fields) => new DualType(fields.v),
+      }, DualType);
+
+      const gl = new GremlinLang();
+      gl.pdtRegistry = registry;
+      gl.addStep('inject', [new DualType('hello')]);
+      // primitive should win
+      assert.strictEqual(gl.getGremlin(), 'g.inject(PDT("DualType","hello"))');
+    });
+  });
 });
diff --git a/gremlin-js/gremlin-javascript/test/unit/pdt-registry-test.js 
b/gremlin-js/gremlin-javascript/test/unit/pdt-registry-test.js
index ded764b7c6..285717e882 100644
--- a/gremlin-js/gremlin-javascript/test/unit/pdt-registry-test.js
+++ b/gremlin-js/gremlin-javascript/test/unit/pdt-registry-test.js
@@ -18,7 +18,7 @@
  */
 
 import { assert } from 'chai';
-import { ProviderDefinedType } from '../../lib/structure/graph.js';
+import { ProviderDefinedType, PrimitiveProviderDefinedType } from 
'../../lib/structure/graph.js';
 import { ProviderDefinedTypeRegistry } from 
'../../lib/structure/ProviderDefinedTypeRegistry.js';
 import Client from '../../lib/driver/client.js';
 import Connection from '../../lib/driver/connection.js';
@@ -172,3 +172,120 @@ describe('pdtRegistry wiring through Client/Connection', 
() => {
     assert.strictEqual(conn1._reader.pdtRegistry, registry);
   });
 });
+
+describe('ProviderDefinedTypeRegistry - Primitive', () => {
+  describe('#hydratePrimitive()', () => {
+    it('should return a typed value when a primitive adapter is registered', 
() => {
+      const registry = new ProviderDefinedTypeRegistry();
+      registry.registerPrimitive('Uint32', {
+        toValue: (obj) => String(obj),
+        fromValue: (value) => parseInt(value, 10),
+      });
+
+      const pdt = new PrimitiveProviderDefinedType('Uint32', '42');
+      const result = registry.hydratePrimitive(pdt);
+
+      assert.strictEqual(result, 42);
+    });
+
+    it('should return the raw primitive PDT when no adapter is registered', () 
=> {
+      const registry = new ProviderDefinedTypeRegistry();
+      const pdt = new PrimitiveProviderDefinedType('Unknown', 'xyz');
+      const result = registry.hydratePrimitive(pdt);
+
+      assert.strictEqual(result, pdt);
+      assert.instanceOf(result, PrimitiveProviderDefinedType);
+    });
+
+    it('should fall back gracefully when adapter throws', () => {
+      const registry = new ProviderDefinedTypeRegistry();
+      registry.registerPrimitive('Broken', {
+        toValue: () => '',
+        fromValue: () => { throw new Error('adapter error'); },
+      });
+
+      const pdt = new PrimitiveProviderDefinedType('Broken', '1');
+      const warnings = [];
+      const origWarn = console.warn;
+      console.warn = (msg) => warnings.push(msg);
+      try {
+        const result = registry.hydratePrimitive(pdt);
+        assert.strictEqual(result, pdt);
+        assert.lengthOf(warnings, 1);
+        assert.include(warnings[0], 'adapter error');
+        assert.include(warnings[0], 'Broken');
+      } finally {
+        console.warn = origWarn;
+      }
+    });
+
+    it('should return non-PrimitiveProviderDefinedType values unchanged', () 
=> {
+      const registry = new ProviderDefinedTypeRegistry();
+      assert.strictEqual(registry.hydratePrimitive('hello'), 'hello');
+      assert.strictEqual(registry.hydratePrimitive(42), 42);
+      assert.strictEqual(registry.hydratePrimitive(null), null);
+    });
+
+    it('should preserve leading zeros in opaque string value', () => {
+      const registry = new ProviderDefinedTypeRegistry();
+      registry.registerPrimitive('PaddedId', {
+        toValue: (obj) => obj.id,
+        fromValue: (value) => ({ id: value }),
+      });
+
+      const pdt = new PrimitiveProviderDefinedType('PaddedId', '007');
+      const result = registry.hydratePrimitive(pdt);
+      assert.deepStrictEqual(result, { id: '007' });
+    });
+  });
+
+  describe('#hasPrimitiveAdapter()', () => {
+    it('should return true for registered primitive types', () => {
+      const registry = new ProviderDefinedTypeRegistry();
+      registry.registerPrimitive('Uint32', { toValue: () => '', fromValue: (v) 
=> v });
+      assert.isTrue(registry.hasPrimitiveAdapter('Uint32'));
+      assert.isFalse(registry.hasPrimitiveAdapter('Missing'));
+    });
+  });
+
+  describe('#getPrimitiveAdapterByClass()', () => {
+    it('should return the adapter entry for registered class', () => {
+      const registry = new ProviderDefinedTypeRegistry();
+      class Uint32 { constructor(v) { this.v = v; } }
+      registry.registerPrimitive('Uint32', {
+        toValue: (obj) => String(obj.v),
+        fromValue: (value) => new Uint32(parseInt(value, 10)),
+      }, Uint32);
+      const entry = registry.getPrimitiveAdapterByClass(Uint32);
+      assert.isNotNull(entry);
+      assert.strictEqual(entry.typeName, 'Uint32');
+      assert.strictEqual(entry.toValue(new Uint32(5)), '5');
+    });
+
+    it('should return null for unregistered class', () => {
+      const registry = new ProviderDefinedTypeRegistry();
+      class Unknown {}
+      assert.isNull(registry.getPrimitiveAdapterByClass(Unknown));
+    });
+  });
+
+  describe('composite hydrate with nested primitive PDT', () => {
+    it('should hydrate nested primitive PDT inside composite fields', () => {
+      const registry = new ProviderDefinedTypeRegistry();
+      registry.registerPrimitive('Uint32', {
+        toValue: (obj) => String(obj),
+        fromValue: (value) => parseInt(value, 10),
+      });
+      registry.register('Measurement', {
+        serialize: (obj) => obj,
+        deserialize: (fields) => ({ type: 'Measurement', unit: fields.unit, 
value: fields.value }),
+      });
+
+      const primPdt = new PrimitiveProviderDefinedType('Uint32', '99');
+      const compPdt = new ProviderDefinedType('Measurement', { unit: 'kg', 
value: primPdt });
+      const result = registry.hydrate(compPdt);
+
+      assert.deepStrictEqual(result, { type: 'Measurement', unit: 'kg', value: 
99 });
+    });
+  });
+});


Reply via email to