// SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright The LanceDB Authors // The utilities in this file help sanitize data from the user's arrow // library into the types expected by vectordb's arrow library. Node // generally allows for multiple versions of the same library (and sometimes // even multiple copies of the same version) to be installed at the same // time. However, arrow-js uses instanceof which expected that the input // comes from the exact same library instance. This is not always the case // and so we must sanitize the input to ensure that it is compatible. import * as arrowModule from "apache-arrow"; import { BufferType, Data, Vector } from "apache-arrow"; import type { IntBitWidth, TKeys, TimeBitWidth } from "apache-arrow/type"; import { Binary, Bool, DataLike, DataType, DateDay, DateMillisecond, type DateUnit, Date_, Decimal, DenseUnion, Dictionary, Duration, DurationMicrosecond, DurationMillisecond, DurationNanosecond, DurationSecond, Field, FixedSizeBinary, FixedSizeList, Float, Float16, Float32, Float64, Int, Int8, Int16, Int32, Int64, Interval, IntervalDayTime, IntervalYearMonth, LargeBinary, LargeUtf8, List, Map_, Null, type Precision, RecordBatch, RecordBatchLike, Schema, SchemaLike, SparseUnion, Struct, Table, TableLike, Time, TimeMicrosecond, TimeMillisecond, TimeNanosecond, TimeSecond, Timestamp, TimestampMicrosecond, TimestampMillisecond, TimestampNanosecond, TimestampSecond, Type, Uint8, Uint16, Uint32, Uint64, Union, Utf8, } from "./arrow"; /** * Whether `value` was created by *this* copy of apache-arrow's `ctor`. * * Arrow 21 overrides `Symbol.hasInstance` on `Schema`, `Field`, `Table`, * `RecordBatch`, `Vector` and `Data` so that `instanceof` also matches objects * built by a different copy of the library. Those are exactly the objects * this module has to rebuild: the `DataType` instances nested inside them are * deliberately *not* given that treatment upstream, so Arrow's own builders and * writers reject them. Walk the real prototype chain instead of trusting * `instanceof`. */ function isOwnInstance( value: unknown, // biome-ignore lint/suspicious/noExplicitAny: matches any class constructor ctor: abstract new (...args: any[]) => T, ): value is T { return ( typeof value === "object" && value !== null && Object.prototype.isPrototypeOf.call(ctor.prototype, value) ); } /** * Types added to Arrow JS after release 18, keyed by the `Type` enum value * they use. The library compiles and runs against every Arrow release in the * peer range, so these are looked up by name at runtime instead of imported: * an older Arrow simply does not have the class. */ type NewerTypeName = | "IntervalMonthDayNano" | "LargeList" | "BinaryView" | "Utf8View"; const NEWER_TYPES: ReadonlyArray<{ name: NewerTypeName; typeId: number; since: number; }> = [ { name: "IntervalMonthDayNano", typeId: -31, since: 20 }, { name: "LargeList", typeId: 21, since: 21 }, { name: "BinaryView", typeId: 23, since: 21 }, { name: "Utf8View", typeId: 24, since: 21 }, ]; // biome-ignore lint/suspicious/noExplicitAny: constructor arity varies by type type DataTypeConstructor = new (...args: any[]) => DataType; function newerTypeConstructor(name: NewerTypeName): DataTypeConstructor { const ctor = (arrowModule as unknown as Record)[name]; if (typeof ctor !== "function") { const since = NEWER_TYPES.find((t) => t.name === name)?.since; throw new Error( `The Arrow type ${name} requires apache-arrow ${since} or newer, ` + "but the installed apache-arrow does not provide it", ); } return ctor as DataTypeConstructor; } type SanitizationContext = { types: WeakMap; vectors: WeakMap; data: WeakMap>; }; function createSanitizationContext(): SanitizationContext { return { types: new WeakMap(), vectors: new WeakMap(), data: new WeakMap(), }; } export function sanitizeMetadata( metadataLike?: unknown, ): Map | undefined { if (metadataLike === undefined || metadataLike === null) { return undefined; } let entries: IterableIterator<[unknown, unknown]>; try { entries = Map.prototype.entries.call(metadataLike); } catch { throw Error("Expected metadata, if present, to be a Map"); } const metadata = new Map(); for (const [key, value] of entries) { if (typeof key !== "string" || typeof value !== "string") { throw Error( "Expected metadata, if present, to be a Map but it had non-string keys or values", ); } metadata.set(key, value); } return metadata; } export function sanitizeInt(typeLike: object) { if ( !("bitWidth" in typeLike) || typeof typeLike.bitWidth !== "number" || !("isSigned" in typeLike) || typeof typeLike.isSigned !== "boolean" ) { throw Error( "Expected an Int Type to have a `bitWidth` and `isSigned` property", ); } return new Int(typeLike.isSigned, typeLike.bitWidth as IntBitWidth); } export function sanitizeFloat(typeLike: object) { if (!("precision" in typeLike) || typeof typeLike.precision !== "number") { throw Error("Expected a Float Type to have a `precision` property"); } return new Float(typeLike.precision as Precision); } export function sanitizeDecimal(typeLike: object) { if ( !("scale" in typeLike) || typeof typeLike.scale !== "number" || !("precision" in typeLike) || typeof typeLike.precision !== "number" || !("bitWidth" in typeLike) || typeof typeLike.bitWidth !== "number" ) { throw Error( "Expected a Decimal Type to have `scale`, `precision`, and `bitWidth` properties", ); } return new Decimal(typeLike.scale, typeLike.precision, typeLike.bitWidth); } export function sanitizeDate(typeLike: object) { if (!("unit" in typeLike) || typeof typeLike.unit !== "number") { throw Error("Expected a Date type to have a `unit` property"); } return new Date_(typeLike.unit as DateUnit); } export function sanitizeTime(typeLike: object) { if ( !("unit" in typeLike) || typeof typeLike.unit !== "number" || !("bitWidth" in typeLike) || typeof typeLike.bitWidth !== "number" ) { throw Error( "Expected a Time type to have `unit` and `bitWidth` properties", ); } return new Time(typeLike.unit, typeLike.bitWidth as TimeBitWidth); } export function sanitizeTimestamp(typeLike: object) { if (!("unit" in typeLike) || typeof typeLike.unit !== "number") { throw Error("Expected a Timestamp type to have a `unit` property"); } let timezone = null; if ("timezone" in typeLike && typeof typeLike.timezone === "string") { timezone = typeLike.timezone; } return new Timestamp(typeLike.unit, timezone); } export function sanitizeTypedTimestamp( typeLike: object, // eslint-disable-next-line @typescript-eslint/naming-convention Datatype: | typeof TimestampNanosecond | typeof TimestampMicrosecond | typeof TimestampMillisecond | typeof TimestampSecond, ) { let timezone = null; if ("timezone" in typeLike && typeof typeLike.timezone === "string") { timezone = typeLike.timezone; } return new Datatype(timezone); } export function sanitizeInterval(typeLike: object) { if (!("unit" in typeLike) || typeof typeLike.unit !== "number") { throw Error("Expected an Interval type to have a `unit` property"); } return new Interval(typeLike.unit); } export function sanitizeList(typeLike: object) { return sanitizeListWithContext(typeLike, createSanitizationContext()); } function sanitizeListChildWithContext( typeLike: object, context: SanitizationContext, typeName: string, ): Field { if (!("children" in typeLike) || !Array.isArray(typeLike.children)) { throw Error( `Expected a ${typeName} type to have an array-like \`children\` property`, ); } if (typeLike.children.length !== 1) { throw Error(`Expected a ${typeName} type to have exactly one child`); } return sanitizeFieldWithContext(typeLike.children[0], context); } function sanitizeListWithContext( typeLike: object, context: SanitizationContext, ) { return new List(sanitizeListChildWithContext(typeLike, context, "List")); } function sanitizeLargeListWithContext( typeLike: object, context: SanitizationContext, ) { const LargeList = newerTypeConstructor("LargeList"); return new LargeList( sanitizeListChildWithContext(typeLike, context, "LargeList"), ); } export function sanitizeStruct(typeLike: object) { return sanitizeStructWithContext(typeLike, createSanitizationContext()); } function sanitizeStructWithContext( typeLike: object, context: SanitizationContext, ) { if (!("children" in typeLike) || !Array.isArray(typeLike.children)) { throw Error( "Expected a Struct type to have an array-like `children` property", ); } return new Struct( typeLike.children.map((child) => sanitizeFieldWithContext(child, context)), ); } export function sanitizeUnion(typeLike: object) { return sanitizeUnionWithContext(typeLike, createSanitizationContext()); } function sanitizeUnionWithContext( typeLike: object, context: SanitizationContext, ) { if ( !("typeIds" in typeLike) || !("mode" in typeLike) || typeof typeLike.mode !== "number" ) { throw Error( "Expected a Union type to have `typeIds` and `mode` properties", ); } if (!("children" in typeLike) || !Array.isArray(typeLike.children)) { throw Error( "Expected a Union type to have an array-like `children` property", ); } return new Union( typeLike.mode, // biome-ignore lint/suspicious/noExplicitAny: skip typeLike.typeIds as any, typeLike.children.map((child) => sanitizeFieldWithContext(child, context)), ); } export function sanitizeTypedUnion( typeLike: object, // eslint-disable-next-line @typescript-eslint/naming-convention UnionType: typeof DenseUnion | typeof SparseUnion, ) { return sanitizeTypedUnionWithContext( typeLike, UnionType, createSanitizationContext(), ); } function sanitizeTypedUnionWithContext( typeLike: object, // eslint-disable-next-line @typescript-eslint/naming-convention UnionType: typeof DenseUnion | typeof SparseUnion, context: SanitizationContext, ) { if (!("typeIds" in typeLike)) { throw Error( "Expected a DenseUnion/SparseUnion type to have a `typeIds` property", ); } if (!("children" in typeLike) || !Array.isArray(typeLike.children)) { throw Error( "Expected a DenseUnion/SparseUnion type to have an array-like `children` property", ); } return new UnionType( typeLike.typeIds as Int32Array | number[], typeLike.children.map((child) => sanitizeFieldWithContext(child, context)), ); } export function sanitizeFixedSizeBinary(typeLike: object) { if (!("byteWidth" in typeLike) || typeof typeLike.byteWidth !== "number") { throw Error( "Expected a FixedSizeBinary type to have a `byteWidth` property", ); } return new FixedSizeBinary(typeLike.byteWidth); } export function sanitizeFixedSizeList(typeLike: object) { return sanitizeFixedSizeListWithContext( typeLike, createSanitizationContext(), ); } function sanitizeFixedSizeListWithContext( typeLike: object, context: SanitizationContext, ) { if (!("listSize" in typeLike) || typeof typeLike.listSize !== "number") { throw Error("Expected a FixedSizeList type to have a `listSize` property"); } if (!("children" in typeLike) || !Array.isArray(typeLike.children)) { throw Error( "Expected a FixedSizeList type to have an array-like `children` property", ); } if (typeLike.children.length !== 1) { throw Error("Expected a FixedSizeList type to have exactly one child"); } return new FixedSizeList( typeLike.listSize, sanitizeFieldWithContext(typeLike.children[0], context), ); } export function sanitizeMap(typeLike: object) { return sanitizeMapWithContext(typeLike, createSanitizationContext()); } function sanitizeMapWithContext( typeLike: object, context: SanitizationContext, ) { if (!("children" in typeLike) || !Array.isArray(typeLike.children)) { throw Error( "Expected a Map type to have an array-like `children` property", ); } if (!("keysSorted" in typeLike) || typeof typeLike.keysSorted !== "boolean") { throw Error("Expected a Map type to have a `keysSorted` property"); } if (typeLike.children.length !== 1) { throw Error("Expected a Map type to have exactly one child"); } return new Map_( sanitizeFieldWithContext(typeLike.children[0], context), typeLike.keysSorted, ); } export function sanitizeDuration(typeLike: object) { if (!("unit" in typeLike) || typeof typeLike.unit !== "number") { throw Error("Expected a Duration type to have a `unit` property"); } return new Duration(typeLike.unit); } export function sanitizeDictionary(typeLike: object) { return sanitizeDictionaryWithContext(typeLike, createSanitizationContext()); } function sanitizeDictionaryWithContext( typeLike: object, context: SanitizationContext, ) { if (!("id" in typeLike) || typeof typeLike.id !== "number") { throw Error("Expected a Dictionary type to have an `id` property"); } if (!("indices" in typeLike) || typeof typeLike.indices !== "object") { throw Error("Expected a Dictionary type to have an `indices` property"); } if (!("dictionary" in typeLike) || typeof typeLike.dictionary !== "object") { throw Error("Expected a Dictionary type to have an `dictionary` property"); } if (!("isOrdered" in typeLike) || typeof typeLike.isOrdered !== "boolean") { throw Error("Expected a Dictionary type to have an `isOrdered` property"); } return new Dictionary( sanitizeTypeWithContext(typeLike.dictionary, context), sanitizeTypeWithContext(typeLike.indices, context) as TKeys, typeLike.id, typeLike.isOrdered, ); } // biome-ignore lint/suspicious/noExplicitAny: skip export function sanitizeType(typeLike: unknown): DataType { return sanitizeTypeWithContext(typeLike, createSanitizationContext()); } function sanitizeTypeWithContext( typeLike: unknown, context: SanitizationContext, ): DataType { if (typeof typeLike === "string") { return dataTypeFromName(typeLike); } if (typeof typeLike !== "object" || typeLike === null) { throw Error("Expected a Type but object was null/undefined"); } const cached = context.types.get(typeLike); if (cached !== undefined) { return cached; } if ( !("typeId" in typeLike) || !( typeof typeLike.typeId !== "function" || typeof typeLike.typeId !== "number" ) ) { throw Error("Expected a Type to have a typeId property"); } let typeId: Type; if (typeof typeLike.typeId === "function") { typeId = (typeLike.typeId as () => unknown)() as Type; } else if (typeof typeLike.typeId === "number") { typeId = typeLike.typeId as Type; } else { throw Error("Type's typeId property was not a function or number"); } const type = sanitizeTypeById(typeLike, typeId, context); context.types.set(typeLike, type); return type; } function sanitizeNewerTypeById( typeLike: object, typeId: number, context: SanitizationContext, ): DataType | undefined { const entry = NEWER_TYPES.find((t) => t.typeId === typeId); if (entry === undefined) { return undefined; } if (entry.name === "LargeList") { return sanitizeLargeListWithContext(typeLike, context); } return new (newerTypeConstructor(entry.name))(); } function sanitizeTypeById( typeLike: object, typeId: Type, context: SanitizationContext, ): DataType { const newer = sanitizeNewerTypeById(typeLike, typeId, context); if (newer !== undefined) { return newer; } switch (typeId) { case Type.NONE: throw Error("Received a Type with a typeId of NONE"); case Type.Null: return new Null(); case Type.Int: return sanitizeInt(typeLike); case Type.Float: return sanitizeFloat(typeLike); case Type.Binary: return new Binary(); case Type.LargeBinary: return new LargeBinary(); case Type.Utf8: return new Utf8(); case Type.LargeUtf8: return new LargeUtf8(); case Type.Bool: return new Bool(); case Type.Decimal: return sanitizeDecimal(typeLike); case Type.Date: return sanitizeDate(typeLike); case Type.Time: return sanitizeTime(typeLike); case Type.Timestamp: return sanitizeTimestamp(typeLike); case Type.Interval: return sanitizeInterval(typeLike); case Type.List: return sanitizeListWithContext(typeLike, context); case Type.Struct: return sanitizeStructWithContext(typeLike, context); case Type.Union: return sanitizeUnionWithContext(typeLike, context); case Type.FixedSizeBinary: return sanitizeFixedSizeBinary(typeLike); case Type.FixedSizeList: return sanitizeFixedSizeListWithContext(typeLike, context); case Type.Map: return sanitizeMapWithContext(typeLike, context); case Type.Duration: return sanitizeDuration(typeLike); case Type.Dictionary: return sanitizeDictionaryWithContext(typeLike, context); case Type.Int8: return new Int8(); case Type.Int16: return new Int16(); case Type.Int32: return new Int32(); case Type.Int64: return new Int64(); case Type.Uint8: return new Uint8(); case Type.Uint16: return new Uint16(); case Type.Uint32: return new Uint32(); case Type.Uint64: return new Uint64(); case Type.Float16: return new Float16(); case Type.Float32: return new Float32(); case Type.Float64: return new Float64(); case Type.DateMillisecond: return new DateMillisecond(); case Type.DateDay: return new DateDay(); case Type.TimeNanosecond: return new TimeNanosecond(); case Type.TimeMicrosecond: return new TimeMicrosecond(); case Type.TimeMillisecond: return new TimeMillisecond(); case Type.TimeSecond: return new TimeSecond(); case Type.TimestampNanosecond: return sanitizeTypedTimestamp(typeLike, TimestampNanosecond); case Type.TimestampMicrosecond: return sanitizeTypedTimestamp(typeLike, TimestampMicrosecond); case Type.TimestampMillisecond: return sanitizeTypedTimestamp(typeLike, TimestampMillisecond); case Type.TimestampSecond: return sanitizeTypedTimestamp(typeLike, TimestampSecond); case Type.DenseUnion: return sanitizeTypedUnionWithContext(typeLike, DenseUnion, context); case Type.SparseUnion: return sanitizeTypedUnionWithContext(typeLike, SparseUnion, context); case Type.IntervalDayTime: return new IntervalDayTime(); case Type.IntervalYearMonth: return new IntervalYearMonth(); case Type.DurationNanosecond: return new DurationNanosecond(); case Type.DurationMicrosecond: return new DurationMicrosecond(); case Type.DurationMillisecond: return new DurationMillisecond(); case Type.DurationSecond: return new DurationSecond(); default: throw new Error("Unrecognized type id in schema: " + typeId); } } export function sanitizeField(fieldLike: unknown): Field { return sanitizeFieldWithContext(fieldLike, createSanitizationContext()); } function sanitizeFieldWithContext( fieldLike: unknown, context: SanitizationContext, ): Field { if (isOwnInstance(fieldLike, Field)) { return fieldLike; } if (typeof fieldLike !== "object" || fieldLike === null) { throw Error("Expected a Field but object was null/undefined"); } if ( !("type" in fieldLike) || !("name" in fieldLike) || !("nullable" in fieldLike) ) { throw Error( "The field passed in is missing a `type`/`name`/`nullable` property", ); } let type: DataType; try { type = sanitizeTypeWithContext(fieldLike.type, context); } catch (error: unknown) { throw Error( `Unable to sanitize type for field: ${fieldLike.name} due to error: ${error}`, { cause: error }, ); } const name = fieldLike.name; if (!(typeof name === "string")) { throw Error("The field passed in had a non-string `name` property"); } const nullable = fieldLike.nullable; if (!(typeof nullable === "boolean")) { throw Error("The field passed in had a non-boolean `nullable` property"); } let metadata; if ("metadata" in fieldLike) { metadata = sanitizeMetadata(fieldLike.metadata); } return new Field(name, type, nullable, metadata); } /** * Convert something schemaLike into a Schema instance * * This method is often needed even when the caller is using a Schema * instance because they might be using a different instance of apache-arrow * than lancedb is using. */ export function sanitizeSchema(schemaLike: SchemaLike): Schema { return sanitizeSchemaWithContext(schemaLike, createSanitizationContext()); } function sanitizeSchemaWithContext( schemaLike: SchemaLike, context: SanitizationContext, ): Schema { if (isOwnInstance(schemaLike, Schema)) { return schemaLike; } if (typeof schemaLike !== "object" || schemaLike === null) { throw Error("Expected a Schema but object was null/undefined"); } if (!("fields" in schemaLike)) { throw Error( "The schema passed in does not appear to be a schema (no 'fields' property)", ); } let metadata; if ("metadata" in schemaLike) { metadata = sanitizeMetadata(schemaLike.metadata); } if (!Array.isArray(schemaLike.fields)) { throw Error( "The schema passed in had a 'fields' property but it was not an array", ); } const sanitizedFields = schemaLike.fields.map((field) => sanitizeFieldWithContext(field, context), ); return new Schema(sanitizedFields, metadata); } export function sanitizeTable(tableLike: TableLike): Table { if (isOwnInstance(tableLike, Table)) { return tableLike; } if (typeof tableLike !== "object" || tableLike === null) { throw Error("Expected a Table but object was null/undefined"); } if (!("schema" in tableLike)) { throw Error( "The table passed in does not appear to be a table (no 'schema' property)", ); } if (!("batches" in tableLike)) { throw Error( "The table passed in does not appear to be a table (no 'columns' property)", ); } const context = createSanitizationContext(); const schema = sanitizeSchemaWithContext(tableLike.schema, context); const batches = tableLike.batches.map((batch) => sanitizeRecordBatch(batch, context), ); return new Table(schema, batches); } function sanitizeRecordBatch( batchLike: RecordBatchLike, context: SanitizationContext, ): RecordBatch { if (isOwnInstance(batchLike, RecordBatch)) { return batchLike; } if (typeof batchLike !== "object" || batchLike === null) { throw Error("Expected a RecordBatch but object was null/undefined"); } if (!("schema" in batchLike)) { throw Error( "The record batch passed in does not appear to be a record batch (no 'schema' property)", ); } if (!("data" in batchLike)) { throw Error( "The record batch passed in does not appear to be a record batch (no 'data' property)", ); } const schema = sanitizeSchemaWithContext(batchLike.schema, context); const data = sanitizeData(batchLike.data, context) as Data; return new RecordBatch(schema, data); } type DictionaryVectorLike = { data: readonly DataLike[]; }; type DictionaryDataLike = DataLike & { dictionary?: DictionaryVectorLike; }; // Arrow 21 added `variadicBuffers` to `Data` for the Utf8View / BinaryView // types. Older Arrow versions do not have the property, so it is optional. type VariadicDataLike = DataLike & { variadicBuffers?: ReadonlyArray; }; function sanitizeData( dataLike: DataLike, context: SanitizationContext, ): Data { if (isOwnInstance(dataLike, Data)) { return dataLike; } const cachedData = context.data.get(dataLike); if (cachedData !== undefined) { return cachedData; } const dictionaryLike = (dataLike as DictionaryDataLike).dictionary; let dictionary: Vector | undefined; if (dictionaryLike !== undefined) { dictionary = context.vectors.get(dictionaryLike); if (dictionary === undefined) { dictionary = new Vector( dictionaryLike.data.map((data) => sanitizeData(data, context)), ); context.vectors.set(dictionaryLike, dictionary); } } // Arrow 21 added an eighth constructor parameter (`variadicBuffers`); older // releases ignore the extra argument but their typings do not declare it. const DataCtor = Data as unknown as new ( ...args: unknown[] ) => Data; const data = new DataCtor( sanitizeTypeWithContext(dataLike.type, context), dataLike.offset, dataLike.length, dataLike.nullCount, { [BufferType.OFFSET]: dataLike.valueOffsets, [BufferType.DATA]: dataLike.values, [BufferType.VALIDITY]: dataLike.nullBitmap, [BufferType.TYPE]: dataLike.typeIds, }, dataLike.children.map((child) => sanitizeData(child, context)), dictionary, (dataLike as VariadicDataLike).variadicBuffers, ); context.data.set(dataLike, data); return data; } const constructorsByTypeName = { null: () => new Null(), binary: () => new Binary(), largebinary: () => new LargeBinary(), binaryview: () => new (newerTypeConstructor("BinaryView"))(), utf8: () => new Utf8(), largeutf8: () => new LargeUtf8(), utf8view: () => new (newerTypeConstructor("Utf8View"))(), bool: () => new Bool(), int8: () => new Int8(), int16: () => new Int16(), int32: () => new Int32(), int64: () => new Int64(), uint8: () => new Uint8(), uint16: () => new Uint16(), uint32: () => new Uint32(), uint64: () => new Uint64(), float16: () => new Float16(), float32: () => new Float32(), float64: () => new Float64(), datemillisecond: () => new DateMillisecond(), dateday: () => new DateDay(), timenanosecond: () => new TimeNanosecond(), timemicrosecond: () => new TimeMicrosecond(), timemillisecond: () => new TimeMillisecond(), timesecond: () => new TimeSecond(), intervaldaytime: () => new IntervalDayTime(), intervalyearmonth: () => new IntervalYearMonth(), intervalmonthdaynano: () => new (newerTypeConstructor("IntervalMonthDayNano"))(), durationnanosecond: () => new DurationNanosecond(), durationmicrosecond: () => new DurationMicrosecond(), durationmillisecond: () => new DurationMillisecond(), durationsecond: () => new DurationSecond(), } as const; type MappableTypeName = keyof typeof constructorsByTypeName; export function dataTypeFromName(typeName: string): DataType { const normalizedTypeName = typeName.toLowerCase() as MappableTypeName; const _constructor = constructorsByTypeName[normalizedTypeName]; if (!_constructor) { throw new Error("Unrecognized type name in schema: " + typeName); } return _constructor(); }