Apache Arrow Type System in the Node.js SDK#
The Node.js SDK's Arrow type handling lives primarily in two files:
nodejs/lancedb/arrow.tsβ schema inference, table construction, and IPC serialization.nodejs/lancedb/sanitize.tsβ type normalization across multiple Arrow library instances.
Everything in arrow.ts is re-exported from apache-arrow, so consumers can use LanceDB's Arrow instance directly via import { ... } from "@lancedb/lancedb/arrow" .
Schema Inference from JavaScript Records#
makeArrowTable is the primary entry point for converting Record<string, unknown>[] (row-major JS objects) into a columnar Arrow Table. Schema inference is handled by inferSchema.
Inference Rules#
When no schema is provided, JS values are mapped to Arrow types by inferType:
| JS type / value | Arrow type |
|---|---|
boolean | Bool |
number | Float64 (even integers β use bigint for Int64) |
bigint | Int64 |
string | Utf8 (or Dictionary(Utf8, Int32) if dictionaryEncodeStrings: true) |
Buffer | Binary |
Typed arrays (Float32Array, etc.) | FixedSizeList of the matching element type |
Array (generic) | List (variable-size) |
Plain object ({}) | Struct |
Vector Column Heuristics#
Arrays are a special case. Before falling back to List, inferType checks two conditions :
- Explicit
vectorColumnsoption β if the column name appears inMakeArrowTableOptions.vectorColumns, the array is encoded as aFixedSizeListwith the configured float type (defaultFloat32). - Name heuristic β if the field name contains
"vector"or"embedding"(case-insensitive), the array is automatically promoted to aFixedSizeList<Float32>(orFixedSizeList<Uint8>for integer data). SeenameSuggestsVectorColumn.
By default, a column named "vector" is treated as Float32 vector without any explicit configuration .
Typed Array β FixedSizeList#
TypedArray column values (e.g. Float32Array, Uint8Array) bypass the name heuristic and are always inferred as FixedSizeList via typedArrayToArrowType. This handles Float32Array, Float64Array, Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, and Int32Array.
Nested Structs#
inferSchema uses a PathTree to traverse nested object fields. Plain objects (not Date, Buffer, Array, Set, Map, or typed arrays) are recursed into and emitted as Struct columns . Nested paths are represented as dotted strings and unified under the Struct Arrow type.
Schema Subset Behavior#
If a schema is provided but some schema fields are absent from the data, makeArrowTable returns a subset of the schema matching only the fields present in the records . This is intentional to support partial-write patterns.
Known Bug: Type Equality in inferSchema#
inferSchema is supposed to detect type mismatches across records (e.g., a column that is Float64 in row 0 but Utf8 in row 1). However, the comparison uses object identity (!==) rather than semantic equality . Because each inferType call creates a new DataType instance, logically identical types are always considered different, and the mismatch code path is never reached. Compounding this, the error is created but never thrown, so inconsistent types are silently ignored.
This is tracked in issue #3781. The proposed fix is to compare semantic type representations (e.g. via dataTypeToJson) and actually throw on mismatch.
Semantic Type Comparison: dataTypeToJson#
LanceDB's approach to type equality is serialization-based. dataTypeToJson converts any DataType to a plain JsonDataType object using a switch on typeId. The JSON representation matches the format used by the Rust lance crate . Comparing the JSON output of two types is the intended way to check semantic equivalence β and this is the approach proposed in issue #3781 to fix the inferred-type mismatch bug.
Key mappings in dataTypeToJson :
Floatβ"halffloat"/"float"/"double"based on precisionTimestampβ"timestamp:{unit}:{timezone}"Decimalβ"decimal:{bitWidth}:{precision}:{scale}"FixedSizeListβ{ type: "fixed_size_list", fields: [...], length: N }Dictionaryβ"dict:{valueType}:{indexType}:false"
Multi-Version Sanitization#
Node.js allows multiple Arrow library versions to coexist. Because apache-arrow uses instanceof checks that are version-specific, types from a different Arrow instance fail those checks. sanitize.ts solves this by reconstructing types from their structural properties rather than comparing identity.
The core function is sanitizeType: it reads typeId from any object (function or number form) and dispatches to per-type constructors from LanceDB's own Arrow instance. Cascading sanitizers handle nested types:
sanitizeFieldβ callssanitizeTypeon the field's typesanitizeSchemaβ mapssanitizeFieldover all fieldssanitizeTableβ sanitizes schema and all record batches
All public entry points (makeArrowTable, fromTableToBuffer, etc.) call sanitizeSchema before processing user-supplied schemas, ensuring cross-version safety .
Related Bug: Schema Metadata Validation#
sanitizeMetadata validates that schema metadata is a Map<string, string> , but sanitizeSchema short-circuits on instanceof Schema and skips metadata validation when the schema already passes the instance check . This means malformed metadata (e.g. Map<string, number>) is silently accepted when the schema is already the correct Arrow instance. Tracked in issue #3729.
Key Entry Points#
| Function | File | Purpose |
|---|---|---|
makeArrowTable | arrow.ts:412 | JS records β Arrow Table |
convertToTable | arrow.ts:1142 | Records β Table + embeddings |
inferSchema | arrow.ts:458 | Schema inference from data |
inferType | arrow.ts:610 | JS value β Arrow DataType |
dataTypeToJson | arrow.ts:1520 | DataType β JSON (semantic repr) |
sanitizeType | sanitize.ts:327 | Normalize type across Arrow versions |
sanitizeSchema | sanitize.ts:503 | Normalize schema across Arrow versions |
newVectorType | arrow.ts:1166 | Build FixedSizeList<Float> for vectors |