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

kou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-js.git


The following commit(s) were added to refs/heads/main by this push:
     new 582398e  fix: implement Symbol.hasInstance for cross-library 
instanceof checks (#377)
582398e is described below

commit 582398ef7ed93127d6093e8c2656f98b8370b21f
Author: Divyanshu Singh <[email protected]>
AuthorDate: Wed Feb 18 07:41:23 2026 +0530

    fix: implement Symbol.hasInstance for cross-library instanceof checks (#377)
    
    What's Changed
    Fixed the instanceof check issue that occurs when multiple versions or
    instances of the Arrow library are loaded in the same application.
    Previously, checks like [value instanceof Schema] would fail if the
    value came from a different Arrow library instance (e.g., when a library
    like LanceDB uses a different Arrow version than the user's code).
    
    Now instanceof works reliably across different Arrow library instances
    by using global symbols for type identification.
    
    Also added helper functions like [isArrowSchema()],[isArrowTable()],
    etc. for explicit type checking.
    
    Closes #61.
    
    ---------
    
    Co-authored-by: Divyanshu singh <[email protected]>
---
 src/Arrow.dom.ts              |   9 +-
 src/Arrow.ts                  |  13 ++
 src/data.ts                   |  22 +++
 src/recordbatch.ts            |  22 +++
 src/schema.ts                 |  43 +++++
 src/table.ts                  |  24 ++-
 src/type.ts                   |  20 +++
 src/util/typecheck.ts         |  93 +++++++++++
 src/vector.ts                 |  22 +++
 test/unit/instanceof-tests.ts | 374 ++++++++++++++++++++++++++++++++++++++++++
 10 files changed, 640 insertions(+), 2 deletions(-)

diff --git a/src/Arrow.dom.ts b/src/Arrow.dom.ts
index 30feeb8..777b859 100644
--- a/src/Arrow.dom.ts
+++ b/src/Arrow.dom.ts
@@ -77,7 +77,14 @@ export {
     RecordBatch,
     util,
     Builder, makeBuilder, builderThroughIterable, builderThroughAsyncIterable,
-    compressionRegistry, CompressionType
+    compressionRegistry, CompressionType,
+    isArrowSchema,
+    isArrowField,
+    isArrowDataType,
+    isArrowData,
+    isArrowVector,
+    isArrowRecordBatch,
+    isArrowTable
 } from './Arrow.js';
 
 export {
diff --git a/src/Arrow.ts b/src/Arrow.ts
index 2049583..848156d 100644
--- a/src/Arrow.ts
+++ b/src/Arrow.ts
@@ -102,6 +102,16 @@ export { Message } from './ipc/metadata/message.js';
 export { RecordBatch } from './recordbatch.js';
 export type { ArrowJSONLike, FileHandle, Readable, Writable, ReadableWritable, 
ReadableDOMStreamOptions } from './io/interfaces.js';
 
+export {
+    isArrowSchema,
+    isArrowField,
+    isArrowDataType,
+    isArrowData,
+    isArrowVector,
+    isArrowRecordBatch,
+    isArrowTable,
+} from './util/typecheck.js';
+
 import * as util_bn_ from './util/bn.js';
 import * as util_int_ from './util/int.js';
 import * as util_bit_ from './util/bit.js';
@@ -113,6 +123,8 @@ import * as util_pretty_ from './util/pretty.js';
 import * as util_interval_ from './util/interval.js';
 export type * from './util/interval.js';
 
+import * as util_typecheck_ from './util/typecheck.js';
+
 import { compareSchemas, compareFields, compareTypes } from 
'./visitor/typecomparator.js';
 
 /** @ignore */
@@ -125,6 +137,7 @@ export const util = {
     ...util_vector_,
     ...util_pretty_,
     ...util_interval_,
+    ...util_typecheck_,
     compareSchemas,
     compareFields,
     compareTypes,
diff --git a/src/data.ts b/src/data.ts
index b5edff8..46080e6 100644
--- a/src/data.ts
+++ b/src/data.ts
@@ -41,6 +41,9 @@ export interface Buffers<T extends DataType> {
     [BufferType.TYPE]: T['TArray'];
 }
 
+/** @ignore */
+const kDataSymbol = Symbol.for('apache-arrow/Data');
+
 /** @ignore */
 export interface Data<T extends DataType = DataType> {
     readonly TType: T['TType'];
@@ -53,6 +56,17 @@ export interface Data<T extends DataType = DataType> {
  */
 export class Data<T extends DataType = DataType> {
 
+    /**
+     * Check if an object is an instance of Data.
+     * This works across different instances of the Arrow library.
+     */
+    /** @nocollapse */ static isData(x: any): x is Data {
+        return x?.[kDataSymbol] === true;
+    }
+
+    /** @internal */
+    declare public readonly [kDataSymbol]: true;
+
     declare public readonly type: T;
     declare public readonly length: number;
     declare public readonly offset: number;
@@ -291,6 +305,14 @@ export class Data<T extends DataType = DataType> {
 }
 
 (Data.prototype as any).children = Object.freeze([]);
+(Data.prototype as any)[kDataSymbol] = true;
+
+Object.defineProperty(Data, Symbol.hasInstance, {
+    value: function isDataInstance(instance: any): instance is Data {
+        return Function.prototype[Symbol.hasInstance].call(this, instance)
+            || (this === Data && Data.isData(instance));
+    },
+});
 
 import {
     Dictionary,
diff --git a/src/recordbatch.ts b/src/recordbatch.ts
index 33dbe9e..cb4dbf9 100644
--- a/src/recordbatch.ts
+++ b/src/recordbatch.ts
@@ -27,6 +27,9 @@ import { instance as setVisitor } from './visitor/set.js';
 import { instance as indexOfVisitor } from './visitor/indexof.js';
 import { instance as iteratorVisitor } from './visitor/iterator.js';
 
+/** @ignore */
+const kRecordBatchSymbol = Symbol.for('apache-arrow/RecordBatch');
+
 /** @ignore */
 export interface RecordBatch<T extends TypeMap = any> {
     ///
@@ -46,6 +49,17 @@ export interface RecordBatch<T extends TypeMap = any> {
 /** @ignore */
 export class RecordBatch<T extends TypeMap = any> {
 
+    /**
+     * Check if an object is an instance of RecordBatch.
+     * This works across different instances of the Arrow library.
+     */
+    /** @nocollapse */ static isRecordBatch(x: any): x is RecordBatch {
+        return x?.[kRecordBatchSymbol] === true;
+    }
+
+    /** @internal */
+    declare public readonly [kRecordBatchSymbol]: true;
+
     constructor(columns: { [P in keyof T]: Data<T[P]> });
     constructor(schema: Schema<T>, data?: Data<Struct<T>>);
     constructor(...args: any[]) {
@@ -280,10 +294,18 @@ export class RecordBatch<T extends TypeMap = any> {
     protected static [Symbol.toStringTag] = ((proto: RecordBatch) => {
         (proto as any)._nullCount = -1;
         (proto as any)[Symbol.isConcatSpreadable] = true;
+        (proto as any)[kRecordBatchSymbol] = true;
         return 'RecordBatch';
     })(RecordBatch.prototype);
 }
 
+Object.defineProperty(RecordBatch, Symbol.hasInstance, {
+    value: function isRecordBatchInstance(instance: any): instance is 
RecordBatch {
+        return Function.prototype[Symbol.hasInstance].call(this, instance)
+            || (this === RecordBatch && RecordBatch.isRecordBatch(instance));
+    },
+});
+
 
 /** @ignore */
 function ensureSameLengthData<T extends TypeMap = any>(
diff --git a/src/schema.ts b/src/schema.ts
index 2eb33b7..5916793 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -18,8 +18,24 @@
 import { MetadataVersion } from './enum.js';
 import { DataType, TypeMap } from './type.js';
 
+/** @ignore */
+const kSchemaSymbol = Symbol.for('apache-arrow/Schema');
+/** @ignore */
+const kFieldSymbol = Symbol.for('apache-arrow/Field');
+
 export class Schema<T extends TypeMap = any> {
 
+    /**
+     * Check if an object is an instance of Schema.
+     * This works across different instances of the Arrow library.
+     */
+    /** @nocollapse */ static isSchema(x: any): x is Schema {
+        return x?.[kSchemaSymbol] === true;
+    }
+
+    /** @internal */
+    declare public readonly [kSchemaSymbol]: true;
+
     public readonly fields: Field<T[keyof T]>[];
     public readonly metadata: Map<string, string>;
     public readonly dictionaries: Map<number, DataType>;
@@ -102,9 +118,28 @@ export class Schema<T extends TypeMap = any> {
 (Schema.prototype as any).fields = <any>null;
 (Schema.prototype as any).metadata = <any>null;
 (Schema.prototype as any).dictionaries = <any>null;
+(Schema.prototype as any)[kSchemaSymbol] = true;
+
+Object.defineProperty(Schema, Symbol.hasInstance, {
+    value: function isSchemaInstance(instance: any): instance is Schema {
+        return Function.prototype[Symbol.hasInstance].call(this, instance)
+            || (this === Schema && Schema.isSchema(instance));
+    },
+});
 
 export class Field<T extends DataType = any> {
 
+    /**
+     * Check if an object is an instance of Field.
+     * This works across different instances of the Arrow library.
+     */
+    /** @nocollapse */ static isField(x: any): x is Field {
+        return x?.[kFieldSymbol] === true;
+    }
+
+    /** @internal */
+    declare public readonly [kFieldSymbol]: true;
+
     public static new<T extends DataType = any>(props: { name: string | 
number; type: T; nullable?: boolean; metadata?: Map<string, string> | null }): 
Field<T>;
     public static new<T extends DataType = any>(name: string | number | 
Field<T>, type: T, nullable?: boolean, metadata?: Map<string, string> | null): 
Field<T>;
     /** @nocollapse */
@@ -151,6 +186,14 @@ export class Field<T extends DataType = any> {
 (Field.prototype as any).name = null;
 (Field.prototype as any).nullable = null;
 (Field.prototype as any).metadata = null;
+(Field.prototype as any)[kFieldSymbol] = true;
+
+Object.defineProperty(Field, Symbol.hasInstance, {
+    value: function isFieldInstance(instance: any): instance is Field {
+        return Function.prototype[Symbol.hasInstance].call(this, instance)
+            || (this === Field && Field.isField(instance));
+    },
+});
 
 /** @ignore */
 function mergeMaps<TKey, TVal>(m1?: Map<TKey, TVal> | null, m2?: Map<TKey, 
TVal> | null): Map<TKey, TVal> {
diff --git a/src/table.ts b/src/table.ts
index 2aab2b7..2caeb75 100644
--- a/src/table.ts
+++ b/src/table.ts
@@ -44,6 +44,9 @@ import { clampRange, wrapIndex } from './util/vector.js';
 import { ArrayDataType, BigIntArray, TypedArray, TypedArrayDataType } from 
'./interfaces.js';
 import { RecordBatch, _InternalEmptyPlaceholderRecordBatch } from 
'./recordbatch.js';
 
+/** @ignore */
+const kTableSymbol = Symbol.for('apache-arrow/Table');
+
 /** @ignore */
 export interface Table<T extends TypeMap = any> {
     ///
@@ -67,6 +70,17 @@ export interface Table<T extends TypeMap = any> {
  */
 export class Table<T extends TypeMap = any> {
 
+    /**
+     * Check if an object is an instance of Table.
+     * This works across different instances of the Arrow library.
+     */
+    /** @nocollapse */ static isTable(x: any): x is Table {
+        return x?.[kTableSymbol] === true;
+    }
+
+    /** @internal */
+    declare public readonly [kTableSymbol]: true;
+
     constructor();
     constructor(batches: Iterable<RecordBatch<T>>);
     constructor(...batches: readonly RecordBatch<T>[]);
@@ -104,7 +118,7 @@ export class Table<T extends TypeMap = any> {
                     return x.batches;
                 } else if (x instanceof Data) {
                     if (x.type instanceof Struct) {
-                        return [new RecordBatch(new Schema(x.type.children), 
x)];
+                        return [new RecordBatch(new Schema(x.type.children), x 
as Data<Struct<any>>)];
                     }
                 } else if (Array.isArray(x)) {
                     return x.flatMap(v => unwrap(v));
@@ -387,6 +401,7 @@ export class Table<T extends TypeMap = any> {
         (proto as any)._offsets = new Uint32Array([0]);
         (proto as any)._nullCount = -1;
         (proto as any)[Symbol.isConcatSpreadable] = true;
+        (proto as any)[kTableSymbol] = true;
         (proto as any)['isValid'] = wrapChunkedCall1(isChunkedValid);
         (proto as any)['get'] = 
wrapChunkedCall1(getVisitor.getVisitFn(Type.Struct));
         (proto as any)['set'] = 
wrapChunkedCall2(setVisitor.getVisitFn(Type.Struct));
@@ -395,6 +410,13 @@ export class Table<T extends TypeMap = any> {
     })(Table.prototype);
 }
 
+Object.defineProperty(Table, Symbol.hasInstance, {
+    value: function isTableInstance(instance: any): instance is Table {
+        return Function.prototype[Symbol.hasInstance].call(this, instance)
+            || (this === Table && Table.isTable(instance));
+    },
+});
+
 
 type VectorsMap<T extends TypeMap> = { [P in keyof T]: Vector<T[P]> };
 
diff --git a/src/type.ts b/src/type.ts
index f1fc3fc..7bb9a07 100644
--- a/src/type.ts
+++ b/src/type.ts
@@ -35,6 +35,9 @@ export type IntBitWidth = 8 | 16 | 32 | 64;
 /** @ignore */
 export type IsSigned = { 'true': true; 'false': false };
 
+/** @ignore */
+const kDataTypeSymbol = Symbol.for('apache-arrow/DataType');
+
 export interface DataType<TType extends Type = Type, TChildren extends TypeMap 
= any> {
     readonly TType: TType;
     readonly TArray: any;
@@ -52,6 +55,22 @@ export interface DataType<TType extends Type = Type, 
TChildren extends TypeMap =
  */
 export abstract class DataType<TType extends Type = Type, TChildren extends 
TypeMap = any> {
 
+    /**
+     * Check if an object is an instance of DataType.
+     * This works across different instances of the Arrow library.
+     * 
+     * Note: We intentionally do NOT implement Symbol.hasInstance here because
+     * it would break instanceof checks for subclasses like Struct, 
Dictionary, etc.
+     * Use DataType.isDataType() for cross-library type checking instead.
+     * @nocollapse
+     */
+    static isDataType(x: any): x is DataType {
+        return x?.[kDataTypeSymbol] === true;
+    }
+
+    /** @internal */
+    declare public readonly [kDataTypeSymbol]: true;
+
     declare public [Symbol.toStringTag]: string;
 
     /** @nocollapse */ static isNull(x: any): x is Null { return x?.typeId === 
Type.Null; }
@@ -93,6 +112,7 @@ export abstract class DataType<TType extends Type = Type, 
TChildren extends Type
         (<any>proto).children = null;
         (<any>proto).ArrayType = Array;
         (<any>proto).OffsetArrayType = Int32Array;
+        (<any>proto)[kDataTypeSymbol] = true;
         return proto[Symbol.toStringTag] = 'DataType';
     })(DataType.prototype);
 }
diff --git a/src/util/typecheck.ts b/src/util/typecheck.ts
new file mode 100644
index 0000000..7e77c8a
--- /dev/null
+++ b/src/util/typecheck.ts
@@ -0,0 +1,93 @@
+// 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.
+
+/**
+ * Type guards for Apache Arrow types that work across different instances
+ * of the Arrow library. These functions use Symbol.for() based markers
+ * to identify Arrow types, making them reliable when multiple versions
+ * or instances of the library are loaded.
+ * 
+ * @example
+ * ```ts
+ * import { isArrowSchema, isArrowTable } from 'apache-arrow';
+ * 
+ * // Works even with different Arrow library instances
+ * if (isArrowSchema(maybeSchema)) {
+ *     console.log('This is a Schema from any Arrow version');
+ * }
+ * 
+ * if (isArrowTable(maybeTable)) {
+ *     console.log('This is a Table from any Arrow version');
+ * }
+ * ```
+ */
+
+import { Schema, Field } from '../schema.js';
+import { DataType } from '../type.js';
+import { Data } from '../data.js';
+import { Vector } from '../vector.js';
+import { RecordBatch } from '../recordbatch.js';
+import { Table } from '../table.js';
+
+/**
+ * Check if a value is an Arrow Schema from any version of the library.
+ */
+export function isArrowSchema(x: any): x is Schema {
+    return Schema.isSchema(x);
+}
+
+/**
+ * Check if a value is an Arrow Field from any version of the library.
+ */
+export function isArrowField(x: any): x is Field {
+    return Field.isField(x);
+}
+
+/**
+ * Check if a value is an Arrow DataType from any version of the library.
+ */
+export function isArrowDataType(x: any): x is DataType {
+    return DataType.isDataType(x);
+}
+
+/**
+ * Check if a value is an Arrow Data from any version of the library.
+ */
+export function isArrowData(x: any): x is Data {
+    return Data.isData(x);
+}
+
+/**
+ * Check if a value is an Arrow Vector from any version of the library.
+ */
+export function isArrowVector(x: any): x is Vector {
+    return Vector.isVector(x);
+}
+
+/**
+ * Check if a value is an Arrow RecordBatch from any version of the library.
+ */
+export function isArrowRecordBatch(x: any): x is RecordBatch {
+    return RecordBatch.isRecordBatch(x);
+}
+
+/**
+ * Check if a value is an Arrow Table from any version of the library.
+ */
+export function isArrowTable(x: any): x is Table {
+    return Table.isTable(x);
+}
diff --git a/src/vector.ts b/src/vector.ts
index aeaa1c1..197c0cd 100644
--- a/src/vector.ts
+++ b/src/vector.ts
@@ -40,6 +40,9 @@ import { instance as iteratorVisitor } from 
'./visitor/iterator.js';
 // @ts-ignore
 import type { vectorFromArray } from './factories.js';
 
+/** @ignore */
+const kVectorSymbol = Symbol.for('apache-arrow/Vector');
+
 export interface Vector<T extends DataType = any> {
     ///
     // Virtual properties for the TypeScript compiler.
@@ -63,6 +66,17 @@ const vectorPrototypesByTypeId = {} as { [typeId: number]: 
any };
  */
 export class Vector<T extends DataType = any> {
 
+    /**
+     * Check if an object is an instance of Vector.
+     * This works across different instances of the Arrow library.
+     */
+    /** @nocollapse */ static isVector(x: any): x is Vector {
+        return x?.[kVectorSymbol] === true;
+    }
+
+    /** @internal */
+    declare public readonly [kVectorSymbol]: true;
+
     constructor(input: readonly (Data<T> | Vector<T>)[]) {
         const data: Data<T>[] = input[0] instanceof Vector
             ? (input as Vector<T>[]).flatMap(x => x.data)
@@ -356,6 +370,7 @@ export class Vector<T extends DataType = any> {
         (proto as any).numChildren = 0;
         (proto as any)._offsets = new Uint32Array([0]);
         (proto as any)[Symbol.isConcatSpreadable] = true;
+        (proto as any)[kVectorSymbol] = true;
 
         const typeIds: Type[] = Object.keys(Type)
             .map((T: any) => Type[T] as any)
@@ -379,6 +394,13 @@ export class Vector<T extends DataType = any> {
     })(Vector.prototype);
 }
 
+Object.defineProperty(Vector, Symbol.hasInstance, {
+    value: function isVectorInstance(instance: any): instance is Vector {
+        return Function.prototype[Symbol.hasInstance].call(this, instance)
+            || (this === Vector && Vector.isVector(instance));
+    },
+});
+
 class MemoizedVector<T extends DataType = any> extends Vector<T> {
 
     public constructor(vector: Vector<T>) {
diff --git a/test/unit/instanceof-tests.ts b/test/unit/instanceof-tests.ts
new file mode 100644
index 0000000..0ef1fd7
--- /dev/null
+++ b/test/unit/instanceof-tests.ts
@@ -0,0 +1,374 @@
+// 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 {
+    Schema, Field, DataType, Data, Vector, RecordBatch, Table,
+    Int32, Utf8, Float64,
+    makeData, vectorFromArray,
+    isArrowSchema, isArrowField, isArrowDataType, isArrowData,
+    isArrowVector, isArrowRecordBatch, isArrowTable
+} from 'apache-arrow';
+
+/**
+ * Tests for Symbol.hasInstance implementation that enables instanceof
+ * to work across different instances of the Arrow library.
+ * 
+ * @see https://github.com/apache/arrow/issues/61
+ */
+describe('Cross-library instanceof support', () => {
+
+    describe('Schema', () => {
+        const schema = new Schema([
+            new Field('id', new Int32()),
+            new Field('name', new Utf8())
+        ]);
+
+        test('instanceof works with Schema', () => {
+            expect(schema instanceof Schema).toBe(true);
+        });
+
+        test('Schema.isSchema() returns true for Schema instances', () => {
+            expect(Schema.isSchema(schema)).toBe(true);
+        });
+
+        test('isArrowSchema() returns true for Schema instances', () => {
+            expect(isArrowSchema(schema)).toBe(true);
+        });
+
+        test('Schema.isSchema() returns false for non-Schema values', () => {
+            expect(Schema.isSchema(null)).toBe(false);
+            expect(Schema.isSchema()).toBe(false);
+            expect(Schema.isSchema({})).toBe(false);
+            expect(Schema.isSchema({ fields: [] })).toBe(false);
+            expect(Schema.isSchema('Schema')).toBe(false);
+        });
+
+        test('isArrowSchema() returns false for non-Schema values', () => {
+            expect(isArrowSchema(null)).toBe(false);
+            expect(isArrowSchema()).toBe(false);
+            expect(isArrowSchema({})).toBe(false);
+        });
+    });
+
+    describe('Field', () => {
+        const field = new Field('test', new Int32());
+
+        test('instanceof works with Field', () => {
+            expect(field instanceof Field).toBe(true);
+        });
+
+        test('Field.isField() returns true for Field instances', () => {
+            expect(Field.isField(field)).toBe(true);
+        });
+
+        test('isArrowField() returns true for Field instances', () => {
+            expect(isArrowField(field)).toBe(true);
+        });
+
+        test('Field.isField() returns false for non-Field values', () => {
+            expect(Field.isField(null)).toBe(false);
+            expect(Field.isField()).toBe(false);
+            expect(Field.isField({})).toBe(false);
+            expect(Field.isField({ name: 'test', type: new Int32() 
})).toBe(false);
+        });
+
+        test('isArrowField() returns false for non-Field values', () => {
+            expect(isArrowField(null)).toBe(false);
+            expect(isArrowField()).toBe(false);
+            expect(isArrowField({})).toBe(false);
+        });
+    });
+
+    describe('DataType', () => {
+        const dataType = new Int32();
+
+        test('instanceof works with DataType', () => {
+            expect(dataType instanceof DataType).toBe(true);
+        });
+
+        test('DataType.isDataType() returns true for DataType instances', () 
=> {
+            expect(DataType.isDataType(dataType)).toBe(true);
+            expect(DataType.isDataType(new Utf8())).toBe(true);
+            expect(DataType.isDataType(new Float64())).toBe(true);
+        });
+
+        test('isArrowDataType() returns true for DataType instances', () => {
+            expect(isArrowDataType(dataType)).toBe(true);
+        });
+
+        test('DataType.isDataType() returns false for non-DataType values', () 
=> {
+            expect(DataType.isDataType(null)).toBe(false);
+            expect(DataType.isDataType()).toBe(false);
+            expect(DataType.isDataType({})).toBe(false);
+            expect(DataType.isDataType({ typeId: 0 })).toBe(false);
+        });
+
+        test('isArrowDataType() returns false for non-DataType values', () => {
+            expect(isArrowDataType(null)).toBe(false);
+            expect(isArrowDataType()).toBe(false);
+            expect(isArrowDataType({})).toBe(false);
+        });
+    });
+
+    describe('Data', () => {
+        const data = makeData({ type: new Int32(), length: 5 });
+
+        test('instanceof works with Data', () => {
+            expect(data instanceof Data).toBe(true);
+        });
+
+        test('Data.isData() returns true for Data instances', () => {
+            expect(Data.isData(data)).toBe(true);
+        });
+
+        test('isArrowData() returns true for Data instances', () => {
+            expect(isArrowData(data)).toBe(true);
+        });
+
+        test('Data.isData() returns false for non-Data values', () => {
+            expect(Data.isData(null)).toBe(false);
+            expect(Data.isData()).toBe(false);
+            expect(Data.isData({})).toBe(false);
+            expect(Data.isData({ type: new Int32(), length: 5 })).toBe(false);
+        });
+
+        test('isArrowData() returns false for non-Data values', () => {
+            expect(isArrowData(null)).toBe(false);
+            expect(isArrowData()).toBe(false);
+            expect(isArrowData({})).toBe(false);
+        });
+    });
+
+    describe('Vector', () => {
+        const vector = vectorFromArray([1, 2, 3, 4, 5]);
+
+        test('instanceof works with Vector', () => {
+            expect(vector instanceof Vector).toBe(true);
+        });
+
+        test('Vector.isVector() returns true for Vector instances', () => {
+            expect(Vector.isVector(vector)).toBe(true);
+        });
+
+        test('isArrowVector() returns true for Vector instances', () => {
+            expect(isArrowVector(vector)).toBe(true);
+        });
+
+        test('Vector.isVector() returns false for non-Vector values', () => {
+            expect(Vector.isVector(null)).toBe(false);
+            expect(Vector.isVector()).toBe(false);
+            expect(Vector.isVector({})).toBe(false);
+            expect(Vector.isVector([1, 2, 3])).toBe(false);
+        });
+
+        test('isArrowVector() returns false for non-Vector values', () => {
+            expect(isArrowVector(null)).toBe(false);
+            expect(isArrowVector()).toBe(false);
+            expect(isArrowVector({})).toBe(false);
+        });
+    });
+
+    describe('RecordBatch', () => {
+        const schema = new Schema([
+            new Field('id', new Int32()),
+            new Field('value', new Float64())
+        ]);
+        const batch = new RecordBatch({
+            id: vectorFromArray([1, 2, 3]).data[0],
+            value: vectorFromArray([1.1, 2.2, 3.3]).data[0]
+        });
+
+        test('instanceof works with RecordBatch', () => {
+            expect(batch instanceof RecordBatch).toBe(true);
+        });
+
+        test('RecordBatch.isRecordBatch() returns true for RecordBatch 
instances', () => {
+            expect(RecordBatch.isRecordBatch(batch)).toBe(true);
+        });
+
+        test('isArrowRecordBatch() returns true for RecordBatch instances', () 
=> {
+            expect(isArrowRecordBatch(batch)).toBe(true);
+        });
+
+        test('RecordBatch.isRecordBatch() returns false for non-RecordBatch 
values', () => {
+            expect(RecordBatch.isRecordBatch(null)).toBe(false);
+            expect(RecordBatch.isRecordBatch()).toBe(false);
+            expect(RecordBatch.isRecordBatch({})).toBe(false);
+            expect(RecordBatch.isRecordBatch({ schema, numRows: 3 
})).toBe(false);
+        });
+
+        test('isArrowRecordBatch() returns false for non-RecordBatch values', 
() => {
+            expect(isArrowRecordBatch(null)).toBe(false);
+            expect(isArrowRecordBatch()).toBe(false);
+            expect(isArrowRecordBatch({})).toBe(false);
+        });
+    });
+
+    describe('Table', () => {
+        const table = new Table({
+            id: vectorFromArray([1, 2, 3]),
+            name: vectorFromArray(['a', 'b', 'c'])
+        });
+
+        test('instanceof works with Table', () => {
+            expect(table instanceof Table).toBe(true);
+        });
+
+        test('Table.isTable() returns true for Table instances', () => {
+            expect(Table.isTable(table)).toBe(true);
+        });
+
+        test('isArrowTable() returns true for Table instances', () => {
+            expect(isArrowTable(table)).toBe(true);
+        });
+
+        test('Table.isTable() returns false for non-Table values', () => {
+            expect(Table.isTable(null)).toBe(false);
+            expect(Table.isTable()).toBe(false);
+            expect(Table.isTable({})).toBe(false);
+            expect(Table.isTable({ schema: new Schema([]), batches: [] 
})).toBe(false);
+        });
+
+        test('isArrowTable() returns false for non-Table values', () => {
+            expect(isArrowTable(null)).toBe(false);
+            expect(isArrowTable()).toBe(false);
+            expect(isArrowTable({})).toBe(false);
+        });
+    });
+
+    describe('Symbol.for markers', () => {
+        test('Schema has the correct symbol marker', () => {
+            const schema = new Schema([]);
+            const marker = Symbol.for('apache-arrow/Schema');
+            expect((schema as any)[marker]).toBe(true);
+        });
+
+        test('Field has the correct symbol marker', () => {
+            const field = new Field('test', new Int32());
+            const marker = Symbol.for('apache-arrow/Field');
+            expect((field as any)[marker]).toBe(true);
+        });
+
+        test('DataType has the correct symbol marker', () => {
+            const dataType = new Int32();
+            const marker = Symbol.for('apache-arrow/DataType');
+            expect((dataType as any)[marker]).toBe(true);
+        });
+
+        test('Data has the correct symbol marker', () => {
+            const data = makeData({ type: new Int32(), length: 5 });
+            const marker = Symbol.for('apache-arrow/Data');
+            expect((data as any)[marker]).toBe(true);
+        });
+
+        test('Vector has the correct symbol marker', () => {
+            const vector = vectorFromArray([1, 2, 3]);
+            const marker = Symbol.for('apache-arrow/Vector');
+            expect((vector as any)[marker]).toBe(true);
+        });
+
+        test('RecordBatch has the correct symbol marker', () => {
+            const batch = new RecordBatch({
+                id: vectorFromArray([1, 2, 3]).data[0]
+            });
+            const marker = Symbol.for('apache-arrow/RecordBatch');
+            expect((batch as any)[marker]).toBe(true);
+        });
+
+        test('Table has the correct symbol marker', () => {
+            const table = new Table({ id: vectorFromArray([1, 2, 3]) });
+            const marker = Symbol.for('apache-arrow/Table');
+            expect((table as any)[marker]).toBe(true);
+        });
+    });
+
+    describe('Cross-instance detection simulation', () => {
+        /**
+         * Simulates what happens when an object comes from a different
+         * Arrow library instance. We create a plain object with the
+         * Symbol.for marker to simulate this scenario.
+         */
+        test('Schema marker is detected on foreign objects', () => {
+            const foreignSchema = {
+                [Symbol.for('apache-arrow/Schema')]: true,
+                fields: [],
+                metadata: new Map()
+            };
+            expect(Schema.isSchema(foreignSchema)).toBe(true);
+            expect(isArrowSchema(foreignSchema)).toBe(true);
+        });
+
+        test('Field marker is detected on foreign objects', () => {
+            const foreignField = {
+                [Symbol.for('apache-arrow/Field')]: true,
+                name: 'test',
+                type: new Int32()
+            };
+            expect(Field.isField(foreignField)).toBe(true);
+            expect(isArrowField(foreignField)).toBe(true);
+        });
+
+        test('DataType marker is detected on foreign objects', () => {
+            const foreignDataType = {
+                [Symbol.for('apache-arrow/DataType')]: true,
+                typeId: 8 // Int32
+            };
+            expect(DataType.isDataType(foreignDataType)).toBe(true);
+            expect(isArrowDataType(foreignDataType)).toBe(true);
+        });
+
+        test('Data marker is detected on foreign objects', () => {
+            const foreignData = {
+                [Symbol.for('apache-arrow/Data')]: true,
+                type: new Int32(),
+                length: 5
+            };
+            expect(Data.isData(foreignData)).toBe(true);
+            expect(isArrowData(foreignData)).toBe(true);
+        });
+
+        test('Vector marker is detected on foreign objects', () => {
+            const foreignVector = {
+                [Symbol.for('apache-arrow/Vector')]: true,
+                data: [],
+                type: new Int32()
+            };
+            expect(Vector.isVector(foreignVector)).toBe(true);
+            expect(isArrowVector(foreignVector)).toBe(true);
+        });
+
+        test('RecordBatch marker is detected on foreign objects', () => {
+            const foreignBatch = {
+                [Symbol.for('apache-arrow/RecordBatch')]: true,
+                schema: new Schema([]),
+                numRows: 0
+            };
+            expect(RecordBatch.isRecordBatch(foreignBatch)).toBe(true);
+            expect(isArrowRecordBatch(foreignBatch)).toBe(true);
+        });
+
+        test('Table marker is detected on foreign objects', () => {
+            const foreignTable = {
+                [Symbol.for('apache-arrow/Table')]: true,
+                schema: new Schema([]),
+                batches: []
+            };
+            expect(Table.isTable(foreignTable)).toBe(true);
+            expect(isArrowTable(foreignTable)).toBe(true);
+        });
+    });
+});

Reply via email to