typed-binary
Version:
Describe binary structures with full TypeScript support. Encode and decode into pure JavaScript objects.
1 lines • 73.9 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/main-api.ts","../src/error.ts","../src/io/measurer.ts","../src/structure/types.ts","../src/structure/array.ts","../src/structure/baseTypes.ts","../src/structure/chars.ts","../src/structure/object.ts","../src/structure/concat.ts","../src/structure/dynamicArray.ts","../src/structure/keyed.ts","../src/structure/optional.ts","../src/structure/tuple.ts","../src/structure/typedArray.ts","../src/util.ts","../src/io/unwrapBuffer.ts","../src/io/bufferIOBase.ts","../src/io/float16converter.ts","../src/io/bufferReader.ts","../src/io/bufferWriter.ts"],"sourcesContent":["import * as bin from './main-api.ts';\nexport * from './main-api.ts';\nexport { bin };\nexport default bin;\n\nexport { getSystemEndianness } from './util.ts';\nexport { MaxValue, SubTypeKey, Schema } from './structure/types.ts';\nexport {\n BoolSchema,\n Float16Schema,\n Float32Schema,\n Int16Schema,\n Int32Schema,\n Int8Schema,\n StringSchema,\n Uint16Schema,\n Uint32Schema,\n Uint8Schema,\n /** @deprecated Use Uint8Schema instead. */\n Uint8Schema as ByteSchema,\n} from './structure/baseTypes.ts';\nexport { ArraySchema } from './structure/array.ts';\nexport { CharsSchema } from './structure/chars.ts';\nexport { DynamicArraySchema } from './structure/dynamicArray.ts';\nexport { KeyedSchema } from './structure/keyed.ts';\nexport { ObjectSchema, GenericObjectSchema } from './structure/object.ts';\nexport { OptionalSchema } from './structure/optional.ts';\nexport { TupleSchema } from './structure/tuple.ts';\nexport { TypedArraySchema } from './structure/typedArray.ts';\n\nexport type { AnyObjectSchema } from './structure/object.ts';\nexport type {\n Unwrap,\n UnwrapRecord,\n UnwrapArray,\n IKeyedSchema,\n Ref,\n IRefResolver,\n ISchema,\n AnyKeyedSchema,\n AnySchema,\n AnySchemaWithProperties,\n ISchemaWithProperties,\n} from './structure/types.ts';\nexport type { ParseUnwrapped } from './utilityTypes.ts';\n","export { arrayOf } from './structure/array.ts';\nexport {\n bool,\n byte,\n i8,\n u8,\n i16,\n u16,\n i32,\n u32,\n f16,\n f32,\n string,\n} from './structure/baseTypes.ts';\nexport { chars } from './structure/chars.ts';\nexport { concat } from './structure/concat.ts';\nexport { dynamicArrayOf } from './structure/dynamicArray.ts';\nexport { keyed } from './structure/keyed.ts';\nexport { object, generic, genericEnum } from './structure/object.ts';\nexport { optional } from './structure/optional.ts';\nexport { tupleOf } from './structure/tuple.ts';\nexport {\n f32Array,\n f64Array,\n i16Array,\n i32Array,\n i8Array,\n u16Array,\n u32Array,\n u8Array,\n u8ClampedArray,\n} from './structure/typedArray.ts';\nexport { MaxValue } from './structure/types.ts';\n\nexport { BufferReader } from './io/bufferReader.ts';\nexport { BufferWriter } from './io/bufferWriter.ts';\nexport { Measurer } from './io/measurer.ts';\nexport {\n UnresolvedReferenceError,\n ValidationError,\n} from './error.ts';\n\nexport type {\n Endianness,\n IMeasurer,\n ISerialInput,\n ISerialOutput,\n} from './io/types.ts';\nexport type { Parsed } from './utilityTypes.ts';\n","export class UnresolvedReferenceError extends Error {\n constructor(msg: string) {\n super(msg);\n\n // Set the prototype explicitly.\n Object.setPrototypeOf(this, UnresolvedReferenceError.prototype);\n }\n}\n\nexport class ValidationError extends Error {\n constructor(msg: string) {\n super(msg);\n\n // Set the prototype explicitly.\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n}\n","import type { IMeasurer } from './types.ts';\n\nclass UnboundedMeasurer implements IMeasurer {\n size = Number.NaN;\n unbounded: IMeasurer = this;\n isUnbounded = true;\n\n add(): IMeasurer {\n return this;\n }\n\n fork(): IMeasurer {\n return this;\n }\n}\n\nconst unboundedMeasurer = new UnboundedMeasurer();\n\nexport class Measurer implements IMeasurer {\n size = 0;\n unbounded: IMeasurer = unboundedMeasurer;\n isUnbounded = false;\n\n add(bytes: number): IMeasurer {\n this.size += bytes;\n return this;\n }\n\n fork(): IMeasurer {\n const forked = new Measurer();\n forked.size = this.size;\n return forked;\n }\n}\n","import type { IMeasurer, ISerialInput, ISerialOutput } from '../io/types.ts';\nimport type { Parsed } from '../utilityTypes.ts';\n\nexport type MaxValue = typeof MaxValue;\nexport const MaxValue = Symbol(\n 'The biggest (in amount of bytes needed) value a schema can represent',\n);\n\nexport interface IKeyedSchema<TKeyDef extends string, TUnwrapped>\n extends ISchema<TUnwrapped> {\n readonly __keyDefinition: TKeyDef;\n}\n\nexport type AnyKeyedSchema = IKeyedSchema<string, unknown>;\n\n/**\n * Removes one layer of schema wrapping.\n *\n * @example ```\n * Unwrap<ISchema<ISchema<number>>> -> ISchema<number>\n * Unwrap<ISchema<number>> -> number\n * ```\n *\n * Keyed schemas are bypassed.\n *\n * @example ```\n * Unwrap<IKeyedSchema<'abc', ISchema<number>>> -> IKeyedSchema<'abc', number>\n * ```\n */\nexport type Unwrap<T> = T extends IKeyedSchema<infer TKeyDef, infer TInner>\n ? // bypassing keyed schemas, as that information has to be preserved for parsing\n IKeyedSchema<TKeyDef, Unwrap<TInner>>\n : T extends ISchema<infer TInner>\n ? TInner\n : T;\n\n/**\n * Removes one layer of schema wrapping of record properties.\n *\n * @example ```\n * Unwrap<{\n * a: ISchema<number>,\n * b: ISchema<ISchema<string>>\n * }>\n * // <=>\n * {\n * a: number,\n * b: ISchema<string>\n * }\n * ```\n */\nexport type UnwrapRecord<T> = T extends IKeyedSchema<\n infer TKeyDef,\n Record<infer K, unknown>\n>\n ? IKeyedSchema<TKeyDef, { [key in K]: Unwrap<T['__unwrapped'][key]> }>\n : T extends Record<infer K, unknown>\n ? { [key in K]: Unwrap<T[key]> }\n : T;\n\n/* helper type for UnwrapArray */\ntype __UnwrapArray<T> = T extends unknown[]\n ? {\n [key in keyof T]: Unwrap<T[key]>;\n }\n : never;\n\n/**\n * Removes one layer of schema wrapping of array elements.\n *\n * @example ```\n * Unwrap<[a: ISchema<number>, b: ISchema<ISchema<string>>]>\n * // <=>\n * [a: number, b: ISchema<string>]\n * ```\n */\nexport type UnwrapArray<T> = T extends IKeyedSchema<infer TKeyDef, unknown[]>\n ? IKeyedSchema<TKeyDef, __UnwrapArray<T['__unwrapped']>>\n : T extends unknown[]\n ? __UnwrapArray<T>\n : T;\n\nexport interface ISchemaWithProperties<TProps extends Record<string, AnySchema>>\n extends ISchema<UnwrapRecord<TProps>> {\n readonly properties: TProps;\n}\n\nexport type AnySchemaWithProperties = ISchemaWithProperties<\n Record<string, AnySchema>\n>;\n\nexport type PropertiesOf<T extends AnySchemaWithProperties> = T['properties'];\n\nexport type PropertyDescription = {\n bufferOffset: number;\n schema: ISchema<unknown>;\n};\n\n/**\n * @param TUnwrap one level of unwrapping to the inferred type.\n */\nexport interface ISchema<TUnwrapped> {\n readonly __unwrapped: TUnwrapped;\n\n resolveReferences(ctx: IRefResolver): void;\n write(output: ISerialOutput, value: Parsed<TUnwrapped>): void;\n read(input: ISerialInput): Parsed<TUnwrapped>;\n measure(\n value: Parsed<TUnwrapped> | MaxValue,\n measurer?: IMeasurer,\n ): IMeasurer;\n seekProperty(\n reference: Parsed<TUnwrapped> | MaxValue,\n prop: keyof TUnwrapped,\n ): PropertyDescription | null;\n}\n\nexport type AnySchema = ISchema<unknown>;\n\nexport abstract class Schema<TUnwrapped> implements ISchema<TUnwrapped> {\n readonly __unwrapped!: TUnwrapped;\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n resolveReferences(ctx: IRefResolver): void {\n // override this if you need to resolve internal references.\n }\n abstract write(output: ISerialOutput, value: Parsed<TUnwrapped>): void;\n abstract read(input: ISerialInput): Parsed<TUnwrapped>;\n abstract measure(\n value: Parsed<TUnwrapped> | MaxValue,\n measurer?: IMeasurer,\n ): IMeasurer;\n seekProperty(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _reference: Parsed<TUnwrapped> | MaxValue,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _prop: keyof TUnwrapped,\n ): PropertyDescription | null {\n // override this if necessary.\n return null;\n }\n}\n\nexport class Ref<K extends string> {\n constructor(public readonly key: K) {}\n}\n\n////\n// Generic types\n////\n\nexport type SubTypeKey = 'string' | 'enum';\nexport const SubTypeKey = {\n STRING: 'string',\n ENUM: 'enum',\n} as const;\n\nexport interface IRefResolver {\n hasKey(key: string): boolean;\n\n resolve<TSchema extends AnySchema>(schemaOrRef: TSchema): TSchema;\n register<K extends string>(key: K, schema: ISchema<unknown>): void;\n}\n\n////\n// Alias types\n////\n","import { ValidationError } from '../error.ts';\nimport { Measurer } from '../io/measurer.ts';\nimport type { IMeasurer, ISerialInput, ISerialOutput } from '../io/types.ts';\nimport type { ParseUnwrapped } from '../utilityTypes.ts';\nimport {\n type AnySchema,\n type IRefResolver,\n MaxValue,\n Schema,\n type Unwrap,\n} from './types.ts';\n\nexport class ArraySchema<TElement extends AnySchema> extends Schema<\n Unwrap<TElement>[]\n> {\n private elementSchema: TElement;\n\n constructor(\n private readonly _unstableElementSchema: TElement,\n public readonly length: number,\n ) {\n super();\n\n // In case this array isn't part of a keyed chain,\n // let's assume the inner type is stable.\n this.elementSchema = _unstableElementSchema;\n }\n\n override resolveReferences(ctx: IRefResolver): void {\n this.elementSchema = ctx.resolve(this._unstableElementSchema);\n }\n\n override write(\n output: ISerialOutput,\n values: ParseUnwrapped<TElement>[],\n ): void {\n if (values.length !== this.length) {\n throw new ValidationError(\n `Expected array of length ${this.length}, got ${values.length}`,\n );\n }\n\n for (const value of values) {\n this.elementSchema.write(output, value);\n }\n }\n\n override read(input: ISerialInput): ParseUnwrapped<TElement>[] {\n const array: ParseUnwrapped<TElement>[] = [];\n\n for (let i = 0; i < this.length; ++i) {\n array.push(this.elementSchema.read(input) as ParseUnwrapped<TElement>);\n }\n\n return array;\n }\n\n /**\n * Returns the maximum number of bytes this schema can take up.\n *\n * Returns `NaN` if the schema is unbounded. If you would like to know\n * how many bytes a particular value encoding will take up, use `.measure(value)`.\n *\n * Alias for `.measure(MaxValue).size`\n */\n get maxSize(): number {\n return this.elementSchema.measure(MaxValue).size * this.length;\n }\n\n override measure(\n values: ParseUnwrapped<TElement>[] | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n for (let i = 0; i < this.length; ++i) {\n this.elementSchema.measure(\n values === MaxValue ? MaxValue : values[i],\n measurer,\n );\n }\n\n return measurer;\n }\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function arrayOf<TSchema extends AnySchema>(\n elementSchema: TSchema,\n length: number,\n): ArraySchema<TSchema> {\n return new ArraySchema(elementSchema, length);\n}\n","import { Measurer } from '../io/measurer.ts';\nimport type { IMeasurer, ISerialInput, ISerialOutput } from '../io/types.ts';\nimport { MaxValue, Schema } from './types.ts';\n\n////\n// BOOL\n////\n\nexport class BoolSchema extends Schema<boolean> {\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Alias for `.measure(MaxValue).size`\n */\n readonly maxSize = 1;\n\n read(input: ISerialInput): boolean {\n return input.readBool();\n }\n\n write(output: ISerialOutput, value: boolean): void {\n output.writeBool(value);\n }\n\n measure(\n _: boolean | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return measurer.add(1);\n }\n}\n\nexport const bool: BoolSchema = new BoolSchema();\n\n////\n// STRING\n////\n\nexport class StringSchema extends Schema<string> {\n private static _cachedEncoder: TextEncoder | undefined;\n\n private static get _encoder() {\n if (!StringSchema._cachedEncoder) {\n StringSchema._cachedEncoder = new TextEncoder();\n }\n return StringSchema._cachedEncoder;\n }\n\n read(input: ISerialInput): string {\n return input.readString();\n }\n\n write<T extends string>(output: ISerialOutput, value: T): void {\n output.writeString(value);\n }\n\n measure(\n value: string | typeof MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n if (value === MaxValue) {\n // A string cannot be bound\n return measurer.unbounded;\n }\n const encoded = StringSchema._encoder.encode(value);\n return measurer.add(encoded.byteLength + 1);\n }\n}\n\nexport const string: StringSchema = new StringSchema();\n\n////\n// i8\n////\n\nexport class Int8Schema extends Schema<number> {\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Alias for `.measure(MaxValue).size`\n */\n readonly maxSize = 1;\n\n read(input: ISerialInput): number {\n return input.readInt8();\n }\n\n write(output: ISerialOutput, value: number): void {\n output.writeInt8(value);\n }\n\n measure(\n _: number | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return measurer.add(1);\n }\n}\n\nexport const i8: Int8Schema = new Int8Schema();\n\n////\n// u8\n////\n\nexport class Uint8Schema extends Schema<number> {\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Alias for `.measure(MaxValue).size`\n */\n readonly maxSize = 1;\n\n read(input: ISerialInput): number {\n return input.readUint8();\n }\n\n write(output: ISerialOutput, value: number): void {\n output.writeUint8(value);\n }\n\n measure(\n _: number | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return measurer.add(1);\n }\n}\n\nexport const u8: Uint8Schema = new Uint8Schema();\n\n/**\n * Alias for `bin.u8`\n */\nexport const byte: Uint8Schema = u8;\n\n////\n// i16\n////\n\nexport class Int16Schema extends Schema<number> {\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Alias for `.measure(MaxValue).size`\n */\n readonly maxSize = 2;\n\n read(input: ISerialInput): number {\n return input.readInt16();\n }\n\n write(output: ISerialOutput, value: number): void {\n output.writeInt16(value);\n }\n\n measure(\n _: number | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return measurer.add(2);\n }\n}\n\nexport const i16: Int16Schema = new Int16Schema();\n\n////\n// u16\n////\n\nexport class Uint16Schema extends Schema<number> {\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Alias for `.measure(MaxValue).size`\n */\n readonly maxSize = 2;\n\n read(input: ISerialInput): number {\n return input.readUint16();\n }\n\n write(output: ISerialOutput, value: number): void {\n output.writeUint16(value);\n }\n\n measure(\n _: number | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return measurer.add(2);\n }\n}\n\nexport const u16: Uint16Schema = new Uint16Schema();\n\n////\n// i32\n////\n\nexport class Int32Schema extends Schema<number> {\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Alias for `.measure(MaxValue).size`\n */\n readonly maxSize = 4;\n\n read(input: ISerialInput): number {\n return input.readInt32();\n }\n\n write(output: ISerialOutput, value: number): void {\n output.writeInt32(value);\n }\n\n measure(\n _: number | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return measurer.add(4);\n }\n}\n\nexport const i32: Int32Schema = new Int32Schema();\n\n////\n// u32\n////\n\nexport class Uint32Schema extends Schema<number> {\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Alias for `.measure(MaxValue).size`\n */\n readonly maxSize = 4;\n\n read(input: ISerialInput): number {\n return input.readUint32();\n }\n\n write(output: ISerialOutput, value: number): void {\n output.writeUint32(value);\n }\n\n measure(\n _: number | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return measurer.add(4);\n }\n}\n\nexport const u32: Uint32Schema = new Uint32Schema();\n\n////\n// f16\n////\n\nexport class Float16Schema extends Schema<number> {\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Alias for `.measure(MaxValue).size`\n */\n readonly maxSize = 2;\n\n read(input: ISerialInput): number {\n return input.readFloat16();\n }\n\n write(output: ISerialOutput, value: number): void {\n output.writeFloat16(value);\n }\n\n measure(\n _: number | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return measurer.add(2);\n }\n}\n\nexport const f16: Float16Schema = new Float16Schema();\n\n////\n// f32\n////\n\nexport class Float32Schema extends Schema<number> {\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Alias for `.measure(MaxValue).size`\n */\n readonly maxSize = 4;\n\n read(input: ISerialInput): number {\n return input.readFloat32();\n }\n\n write(output: ISerialOutput, value: number): void {\n output.writeFloat32(value);\n }\n\n measure(\n _: number | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return measurer.add(4);\n }\n}\n\nexport const f32: Float32Schema = new Float32Schema();\n","import { ValidationError } from '../error.ts';\nimport { Measurer } from '../io/measurer.ts';\nimport type { IMeasurer, ISerialInput, ISerialOutput } from '../io/types.ts';\nimport { Schema } from './types.ts';\n\nexport class CharsSchema<\n TLength extends number = number,\n> extends Schema<string> {\n constructor(public readonly length: TLength) {\n super();\n }\n\n write(output: ISerialOutput, value: string): void {\n if (value.length !== this.length) {\n throw new ValidationError(\n `Expected char-string of length ${this.length}, got ${value.length}`,\n );\n }\n\n for (let i = 0; i < value.length; ++i) {\n output.writeUint8(value.charCodeAt(i));\n }\n }\n\n read(input: ISerialInput): string {\n let content = '';\n\n for (let i = 0; i < this.length; ++i) {\n content += String.fromCharCode(input.readByte());\n }\n\n return content;\n }\n\n measure(_: string, measurer: IMeasurer = new Measurer()): IMeasurer {\n return measurer.add(this.length);\n }\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function chars<T extends number>(length: T): CharsSchema<T> {\n return new CharsSchema(length);\n}\n","import { Measurer } from '../io/measurer.ts';\nimport type { IMeasurer, ISerialInput, ISerialOutput } from '../io/types.ts';\nimport type { ParseUnwrappedRecord, Parsed } from '../utilityTypes.ts';\nimport {\n type AnySchema,\n type AnySchemaWithProperties,\n type IRefResolver,\n type ISchema,\n type ISchemaWithProperties,\n MaxValue,\n type PropertyDescription,\n Schema,\n SubTypeKey,\n type Unwrap,\n type UnwrapRecord,\n} from './types.ts';\n\n// @__NO_SIDE_EFFECTS__\nexport function exactEntries<T extends Record<keyof T, T[keyof T]>>(\n record: T,\n): [keyof T, T[keyof T]][] {\n return Object.entries(record) as [keyof T, T[keyof T]][];\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function resolveMap<T extends Record<string, AnySchema>>(\n ctx: IRefResolver,\n refs: T,\n): T {\n const props = {} as T;\n\n for (const [key, ref] of exactEntries(refs)) {\n props[key] = ctx.resolve(ref);\n }\n\n return props;\n}\n\nexport type AnyObjectSchema = ObjectSchema<Record<string, AnySchema>>;\n\nexport class ObjectSchema<TProps extends Record<string, AnySchema>>\n extends Schema<UnwrapRecord<TProps>>\n implements ISchemaWithProperties<TProps>\n{\n public properties: TProps;\n\n constructor(private readonly _properties: TProps) {\n super();\n\n // In case this object isn't part of a keyed chain,\n // let's assume properties are stable.\n this.properties = _properties;\n }\n\n override resolveReferences(ctx: IRefResolver): void {\n this.properties = resolveMap(ctx, this._properties);\n }\n\n override write(\n output: ISerialOutput,\n value: ParseUnwrappedRecord<TProps>,\n ): void {\n type Property = keyof ParseUnwrappedRecord<TProps>;\n\n for (const [key, property] of exactEntries(this.properties)) {\n property.write(output, value[key as Property]);\n }\n }\n\n override read(input: ISerialInput): ParseUnwrappedRecord<TProps> {\n type Property = keyof ParseUnwrappedRecord<TProps>;\n\n const result = {} as ParseUnwrappedRecord<TProps>;\n\n for (const [key, property] of exactEntries(this.properties)) {\n result[key as Property] = property.read(input) as Parsed<\n UnwrapRecord<TProps>\n >[Property];\n }\n\n return result;\n }\n\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Is `NaN` if the schema is unbounded. If you would like to know\n * how many bytes a particular value encoding will take up, use `.measure(value)`.\n *\n * Alias for `.measure(MaxValue).size`\n */\n get maxSize(): number {\n const measurer = new Measurer();\n\n for (const property of Object.values(this.properties)) {\n property.measure(MaxValue, measurer);\n }\n\n return measurer.size;\n }\n\n override measure(\n value: ParseUnwrappedRecord<TProps> | typeof MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n type Property = keyof ParseUnwrappedRecord<TProps>;\n\n for (const [key, property] of exactEntries(this.properties)) {\n property.measure(\n value === MaxValue ? MaxValue : value[key as Property],\n measurer,\n );\n }\n\n return measurer;\n }\n\n override seekProperty(\n reference: ParseUnwrappedRecord<TProps> | MaxValue,\n prop: keyof UnwrapRecord<TProps>,\n ): PropertyDescription | null {\n let bufferOffset = 0;\n\n for (const [key, property] of exactEntries(this.properties)) {\n if (key === prop) {\n return {\n bufferOffset,\n schema: property,\n };\n }\n\n bufferOffset += property.measure(reference).size;\n }\n\n return null;\n }\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function object<P extends Record<string, AnySchema>>(\n properties: P,\n): ObjectSchema<P> {\n return new ObjectSchema(properties);\n}\n\ntype UnwrapGeneric<Base extends Record<string, AnySchema>, Ext> = {\n [TKey in keyof Ext]: ISchema<\n UnwrapRecord<Base> & { type: TKey } & UnwrapRecord<Unwrap<Ext[TKey]>>\n >;\n}[keyof Ext];\n\nexport class GenericObjectSchema<\n TUnwrapBase extends Record<string, AnySchema>, // Base properties\n TUnwrapExt extends Record<string, AnySchemaWithProperties>, // Sub type map\n> extends Schema<UnwrapGeneric<TUnwrapBase, TUnwrapExt>> {\n private _baseObject: ObjectSchema<TUnwrapBase>;\n public subTypeMap: TUnwrapExt;\n\n constructor(\n public readonly keyedBy: SubTypeKey,\n properties: TUnwrapBase,\n private readonly _subTypeMap: TUnwrapExt,\n ) {\n super();\n\n this._baseObject = new ObjectSchema(properties);\n\n // In case this object isn't part of a keyed chain,\n // let's assume sub types are stable.\n this.subTypeMap = _subTypeMap;\n }\n\n override resolveReferences(ctx: IRefResolver): void {\n this._baseObject.resolveReferences(ctx);\n this.subTypeMap = resolveMap(ctx, this._subTypeMap);\n }\n\n override write(\n output: ISerialOutput,\n value: Parsed<UnwrapGeneric<TUnwrapBase, TUnwrapExt>>,\n ): void {\n // Figuring out sub-types\n\n const subTypeKey = value.type as keyof TUnwrapExt;\n const subTypeDescription = this.subTypeMap[subTypeKey] || null;\n if (subTypeDescription === null) {\n throw new Error(\n `Unknown sub-type '${subTypeKey.toString()}' in among '${JSON.stringify(\n Object.keys(this.subTypeMap),\n )}'`,\n );\n }\n\n // Writing the sub-type out.\n if (this.keyedBy === SubTypeKey.ENUM) {\n output.writeUint8(value.type as number);\n } else {\n output.writeString(value.type as string);\n }\n\n // Writing the base properties\n this._baseObject.write(output, value as ParseUnwrappedRecord<TUnwrapBase>);\n\n // Extra sub-type fields\n for (const [key, extraProp] of exactEntries(\n subTypeDescription.properties,\n )) {\n extraProp.write(output, value[key]);\n }\n }\n\n override read(\n input: ISerialInput,\n ): Parsed<UnwrapGeneric<TUnwrapBase, TUnwrapExt>> {\n const subTypeKey =\n this.keyedBy === SubTypeKey.ENUM ? input.readByte() : input.readString();\n\n const subTypeDescription =\n this.subTypeMap[subTypeKey as keyof TUnwrapExt] || null;\n if (subTypeDescription === null) {\n throw new Error(\n `Unknown sub-type '${subTypeKey}' in among '${JSON.stringify(\n Object.keys(this.subTypeMap),\n )}'`,\n );\n }\n\n const result = this._baseObject.read(input) as Parsed<\n UnwrapGeneric<TUnwrapBase, TUnwrapExt>\n >;\n\n // Making the sub type key available to the result object.\n (result as { type: keyof TUnwrapExt }).type =\n subTypeKey as keyof TUnwrapExt;\n\n if (subTypeDescription !== null) {\n for (const [key, extraProp] of exactEntries(\n subTypeDescription.properties,\n )) {\n // biome-ignore lint/suspicious/noExplicitAny: <covered by tests>\n (result as any)[key] = extraProp.read(input);\n }\n }\n\n return result;\n }\n\n measure(\n value: Parsed<UnwrapGeneric<TUnwrapBase, TUnwrapExt>> | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n this._baseObject.measure(\n value as Parsed<UnwrapRecord<TUnwrapBase>> | MaxValue,\n measurer,\n );\n\n // We're a generic object trying to encode a concrete value.\n if (this.keyedBy === SubTypeKey.ENUM) {\n measurer.add(1);\n } else if (value !== MaxValue) {\n measurer.add((value.type as string).length + 1);\n } else {\n // 'type' can be a string of any length, so the schema is unbounded.\n return measurer.unbounded;\n }\n\n // Extra sub-type fields\n if (value === MaxValue) {\n const biggestSubType = (\n Object.values(this.subTypeMap) as TUnwrapExt[keyof TUnwrapExt][]\n )\n .map((subType) => {\n const forkedMeasurer = measurer.fork();\n\n // Going through extra properties\n for (const prop of Object.values(subType.properties)) {\n // Measuring them\n prop.measure(MaxValue, forkedMeasurer);\n }\n\n return [subType, forkedMeasurer.size] as const;\n })\n .reduce((a, b) => (a[1] > b[1] ? a : b))[0];\n\n // Going through extra properties\n for (const prop of Object.values(biggestSubType.properties)) {\n // Measuring for real this time\n prop.measure(MaxValue, measurer);\n }\n } else {\n const subTypeKey = (value as { type: keyof TUnwrapExt }).type;\n const subTypeDescription = this.subTypeMap[subTypeKey] || null;\n if (subTypeDescription === null) {\n throw new Error(\n `Unknown sub-type '${subTypeKey.toString()}', expected one of '${JSON.stringify(\n Object.keys(this.subTypeMap),\n )}'`,\n );\n }\n\n // Going through extra properties\n for (const [key, prop] of exactEntries(subTypeDescription.properties)) {\n // Measuring them\n prop.measure(value[key], measurer);\n }\n }\n\n return measurer;\n }\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function generic<\n P extends Record<string, AnySchema>,\n S extends {\n [Key in keyof S]: AnySchemaWithProperties;\n },\n>(properties: P, subTypeMap: S): GenericObjectSchema<P, S> {\n return new GenericObjectSchema(SubTypeKey.STRING, properties, subTypeMap);\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function genericEnum<\n P extends Record<string, AnySchema>,\n S extends {\n [Key in keyof S]: AnySchemaWithProperties;\n },\n>(properties: P, subTypeMap: S): GenericObjectSchema<P, S> {\n return new GenericObjectSchema(SubTypeKey.ENUM, properties, subTypeMap);\n}\n","import type { MergeRecordUnion } from '../utilityTypes.ts';\nimport { type AnyObjectSchema, ObjectSchema } from './object.ts';\nimport type { PropertiesOf } from './types.ts';\n\ntype Concat<Objs extends AnyObjectSchema[]> = ObjectSchema<\n MergeRecordUnion<PropertiesOf<Objs[number]>>\n>;\n\n// @__NO_SIDE_EFFECTS__\nexport function concat<Objs extends AnyObjectSchema[]>(\n objs: Objs,\n): Concat<Objs> {\n return new ObjectSchema(\n Object.fromEntries(\n objs.flatMap(({ properties }) => Object.entries(properties)),\n ) as unknown as Concat<Objs>['properties'],\n );\n}\n","import { Measurer } from '../io/measurer.ts';\nimport type { IMeasurer, ISerialInput, ISerialOutput } from '../io/types.ts';\nimport type { ParseUnwrapped } from '../utilityTypes.ts';\nimport {\n type AnySchema,\n type IRefResolver,\n MaxValue,\n type PropertyDescription,\n Schema,\n type Unwrap,\n} from './types.ts';\n\nexport class DynamicArraySchema<TElement extends AnySchema> extends Schema<\n Unwrap<TElement>[]\n> {\n public elementType: TElement;\n\n constructor(private readonly _unstableElementType: TElement) {\n super();\n\n // In case this array isn't part of a keyed chain,\n // let's assume the inner type is stable.\n this.elementType = _unstableElementType;\n }\n\n override resolveReferences(ctx: IRefResolver): void {\n this.elementType = ctx.resolve(this._unstableElementType);\n }\n\n override write(\n output: ISerialOutput,\n values: ParseUnwrapped<TElement>[],\n ): void {\n output.writeUint32(values.length);\n\n for (const value of values) {\n this.elementType.write(output, value);\n }\n }\n\n override read(input: ISerialInput): ParseUnwrapped<TElement>[] {\n const array: ParseUnwrapped<TElement>[] = [];\n\n const len = input.readUint32();\n\n for (let i = 0; i < len; ++i) {\n array.push(this.elementType.read(input) as ParseUnwrapped<TElement>);\n }\n\n return array;\n }\n\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Is `NaN` if the schema is unbounded. If you would like to know\n * how many bytes a particular value encoding will take up, use `.measure(value)`.\n *\n * Alias for `.measure(MaxValue).size`\n */\n get maxSize(): number {\n return this.measure(MaxValue).size;\n }\n\n override measure(\n values: ParseUnwrapped<TElement>[] | typeof MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n if (values === MaxValue) {\n // arrays cannot be bound\n return measurer.unbounded;\n }\n\n // Length encoding\n measurer.add(4); // u32\n\n // Values encoding\n for (const value of values) {\n this.elementType.measure(value, measurer);\n }\n\n return measurer;\n }\n\n override seekProperty(\n reference: ParseUnwrapped<TElement>[] | MaxValue,\n prop: number,\n ): PropertyDescription | null {\n if (typeof prop === 'symbol') {\n return null;\n }\n\n const indexProp = Number.parseInt(String(prop), 10);\n if (Number.isNaN(indexProp)) {\n return null;\n }\n\n if (reference === MaxValue) {\n return {\n bufferOffset: this.elementType.measure(MaxValue).size * indexProp,\n schema: this.elementType,\n };\n }\n\n if (indexProp >= reference.length) {\n // index out of range\n return null;\n }\n\n const measurer = new Measurer();\n for (let i = 0; i < indexProp; ++i) {\n this.elementType.measure(reference[i], measurer);\n }\n\n return {\n bufferOffset: measurer.size,\n schema: this.elementType,\n };\n }\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function dynamicArrayOf<TSchema extends AnySchema>(\n elementSchema: TSchema,\n): DynamicArraySchema<TSchema> {\n return new DynamicArraySchema(elementSchema);\n}\n","import { UnresolvedReferenceError } from '../error.ts';\nimport { Measurer } from '../io/measurer.ts';\nimport type { IMeasurer, ISerialInput, ISerialOutput } from '../io/types.ts';\nimport type { ParseUnwrapped, Parsed } from '../utilityTypes.ts';\nimport {\n type AnySchema,\n type IKeyedSchema,\n type IRefResolver,\n type ISchema,\n MaxValue,\n type PropertyDescription,\n Ref,\n type Unwrap,\n} from './types.ts';\n\nclass RefSchema<TKeyDef extends string> implements ISchema<Ref<TKeyDef>> {\n public readonly __unwrapped!: Ref<TKeyDef>;\n public readonly ref: Ref<TKeyDef>;\n\n constructor(key: TKeyDef) {\n this.ref = new Ref(key);\n }\n\n resolveReferences(): void {\n throw new UnresolvedReferenceError(\n 'Tried to resolve a reference directly. Do it through a RefResolver instead.',\n );\n }\n\n read(): Parsed<Ref<TKeyDef>> {\n throw new UnresolvedReferenceError(\n 'Tried to read a reference directly. Resolve it instead.',\n );\n }\n\n write(): void {\n throw new UnresolvedReferenceError(\n 'Tried to write a reference directly. Resolve it instead.',\n );\n }\n\n measure(): IMeasurer {\n throw new UnresolvedReferenceError(\n 'Tried to measure size of a reference directly. Resolve it instead.',\n );\n }\n\n seekProperty(): PropertyDescription | null {\n throw new UnresolvedReferenceError(\n 'Tried to seek property of a reference directly. Resolve it instead.',\n );\n }\n}\n\nclass RefResolve implements IRefResolver {\n private registry: { [key: string]: ISchema<unknown> } = {};\n\n hasKey(key: string): boolean {\n return this.registry[key] !== undefined;\n }\n\n register<K extends string>(key: K, schema: ISchema<unknown>): void {\n this.registry[key] = schema;\n }\n\n resolve<TSchema extends AnySchema>(unstableSchema: TSchema): TSchema {\n if (unstableSchema instanceof RefSchema) {\n const ref = unstableSchema.ref;\n const key = ref.key as string;\n if (this.registry[key] !== undefined) {\n return this.registry[key] as TSchema;\n }\n\n throw new UnresolvedReferenceError(\n `Couldn't resolve reference to ${key}. Unknown key.`,\n );\n }\n\n // Since it's not a RefSchema, we assume it can be resolved.\n unstableSchema.resolveReferences(this);\n\n return unstableSchema;\n }\n}\n\nexport class KeyedSchema<\n TInner extends ISchema<unknown>,\n TKeyDef extends string,\n> implements IKeyedSchema<TKeyDef, Unwrap<TInner>>\n{\n public readonly __unwrapped!: Unwrap<TInner>;\n public readonly __keyDefinition!: TKeyDef;\n public innerType: TInner;\n\n constructor(\n public readonly key: TKeyDef,\n innerResolver: (ref: ISchema<Ref<TKeyDef>>) => TInner,\n ) {\n this.innerType = innerResolver(new RefSchema(key));\n\n // Automatically resolving after keyed creation.\n this.resolveReferences(new RefResolve());\n }\n\n resolveReferences(ctx: IRefResolver): void {\n if (!ctx.hasKey(this.key)) {\n ctx.register(this.key, this.innerType);\n\n this.innerType.resolveReferences(ctx);\n }\n }\n\n read(input: ISerialInput): ParseUnwrapped<TInner> {\n return this.innerType.read(input) as ParseUnwrapped<TInner>;\n }\n\n write(output: ISerialOutput, value: ParseUnwrapped<TInner>): void {\n this.innerType.write(output, value);\n }\n\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Is `NaN` if the schema is unbounded. If you would like to know\n * how many bytes a particular value encoding will take up, use `.measure(value)`.\n *\n * Alias for `.measure(MaxValue).size`\n */\n get maxSize(): number {\n return this.measure(MaxValue).size;\n }\n\n measure(\n value: ParseUnwrapped<TInner> | typeof MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return this.innerType.measure(value, measurer);\n }\n\n seekProperty(\n reference: ParseUnwrapped<TInner> | typeof MaxValue,\n prop: keyof Unwrap<TInner>,\n ): PropertyDescription | null {\n return this.innerType.seekProperty(reference, prop as never);\n }\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function keyed<K extends string, P extends ISchema<unknown>>(\n key: K,\n inner: (ref: ISchema<Ref<K>>) => P,\n): KeyedSchema<P, K> {\n return new KeyedSchema(key, inner);\n}\n","import { Measurer } from '../io/measurer.ts';\nimport type { IMeasurer, ISerialInput, ISerialOutput } from '../io/types.ts';\nimport type { ParseUnwrapped } from '../utilityTypes.ts';\nimport {\n type AnySchema,\n type IRefResolver,\n MaxValue,\n Schema,\n type Unwrap,\n} from './types.ts';\n\nexport class OptionalSchema<TInner extends AnySchema> extends Schema<\n Unwrap<TInner> | undefined\n> {\n private innerSchema: TInner;\n\n constructor(private readonly _innerUnstableSchema: TInner) {\n super();\n\n // In case this optional isn't part of a keyed chain,\n // let's assume the inner type is stable.\n this.innerSchema = _innerUnstableSchema;\n }\n\n override resolveReferences(ctx: IRefResolver): void {\n this.innerSchema = ctx.resolve(this._innerUnstableSchema);\n }\n\n override write(\n output: ISerialOutput,\n value: ParseUnwrapped<TInner> | undefined,\n ): void {\n if (value !== undefined && value !== null) {\n output.writeBool(true);\n this.innerSchema.write(output, value);\n } else {\n output.writeBool(false);\n }\n }\n\n override read(input: ISerialInput): ParseUnwrapped<TInner> | undefined {\n const valueExists = input.readBool();\n\n if (valueExists) {\n return this.innerSchema.read(input) as ParseUnwrapped<TInner>;\n }\n\n return undefined;\n }\n\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Is `NaN` if the schema is unbounded. If you would like to know\n * how many bytes a particular value encoding will take up, use `.measure(value)`.\n *\n * Alias for `.measure(MaxValue).size`\n */\n get maxSize(): number {\n return this.measure(MaxValue).size;\n }\n\n override measure(\n value: ParseUnwrapped<TInner> | MaxValue | undefined,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n if (value !== undefined) {\n this.innerSchema.measure(value, measurer);\n }\n\n return measurer.add(1);\n }\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function optional<TSchema extends AnySchema>(\n innerType: TSchema,\n): OptionalSchema<TSchema> {\n return new OptionalSchema(innerType);\n}\n","import { ValidationError } from '../error.ts';\nimport { Measurer } from '../io/measurer.ts';\nimport type { IMeasurer, ISerialInput, ISerialOutput } from '../io/types.ts';\nimport type { Parsed } from '../utilityTypes.ts';\nimport {\n type AnySchema,\n type IRefResolver,\n MaxValue,\n Schema,\n type UnwrapArray,\n} from './types.ts';\n\n// @__NO_SIDE_EFFECTS__\nexport function resolveArray<T extends AnySchema[]>(\n ctx: IRefResolver,\n refs: T,\n): T {\n return refs.map((ref) => ctx.resolve(ref)) as T;\n}\n\nexport class TupleSchema<\n TSequence extends [AnySchema, ...AnySchema[]],\n> extends Schema<UnwrapArray<TSequence>> {\n private schemas: TSequence;\n\n constructor(private readonly _unstableSchemas: TSequence) {\n super();\n\n // In case this tuple isn't part of a keyed chain,\n // let's assume the inner type is stable.\n this.schemas = _unstableSchemas;\n }\n\n override resolveReferences(ctx: IRefResolver): void {\n this.schemas = resolveArray(ctx, this._unstableSchemas);\n }\n\n override write(\n output: ISerialOutput,\n values: Parsed<UnwrapArray<TSequence>>,\n ): void {\n if (values.length !== this.schemas.length) {\n throw new ValidationError(\n `Expected tuple of length ${this.schemas.length}, got ${values.length}`,\n );\n }\n\n for (let i = 0; i < this.schemas.length; ++i) {\n this.schemas[i].write(output, values[i]);\n }\n }\n\n override read(input: ISerialInput): Parsed<UnwrapArray<TSequence>> {\n const array = [] as Parsed<UnwrapArray<TSequence>>;\n\n for (let i = 0; i < this.schemas.length; ++i) {\n array.push(\n this.schemas[i].read(input) as Parsed<UnwrapArray<TSequence>>[number],\n );\n }\n\n return array;\n }\n\n /**\n * The maximum number of bytes this schema can take up.\n *\n * Is `NaN` if the schema is unbounded. If you would like to know\n * how many bytes a particular value encoding will take up, use `.measure(value)`.\n *\n * Alias for `.measure(MaxValue).size`\n */\n get maxSize(): number {\n return this.measure(MaxValue).size;\n }\n\n measure(\n values: Parsed<UnwrapArray<TSequence>> | MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n for (let i = 0; i < this.schemas.length; ++i) {\n this.schemas[i].measure(\n values === MaxValue ? MaxValue : values[i],\n measurer,\n );\n }\n\n return measurer;\n }\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function tupleOf<TSchema extends [AnySchema, ...AnySchema[]]>(\n schemas: TSchema,\n): TupleSchema<TSchema> {\n return new TupleSchema(schemas);\n}\n","import { Measurer } from '../io/measurer.ts';\nimport type { IMeasurer, ISerialInput, ISerialOutput } from '../io/types.ts';\nimport type { Parsed } from '../utilityTypes.ts';\nimport { type MaxValue, Schema } from './types.ts';\n\ntype TypedArrayConstructor<T> = {\n readonly BYTES_PER_ELEMENT: number;\n new (buffer: ArrayBufferLike, offset?: number, length?: number): T;\n};\n\nexport class TypedArraySchema<\n TTypedArray extends ArrayLike<number> & ArrayBufferView,\n> extends Schema<TTypedArray> {\n public readonly byteLength: number;\n\n constructor(\n public readonly length: number,\n private readonly _arrayConstructor: TypedArrayConstructor<TTypedArray>,\n ) {\n super();\n\n this.byteLength = length * _arrayConstructor.BYTES_PER_ELEMENT;\n }\n\n write(output: ISerialOutput, value: Parsed<TTypedArray>): void {\n output.writeSlice(value);\n }\n\n read(input: ISerialInput): Parsed<TTypedArray> {\n const buffer = new ArrayBuffer(this.byteLength);\n const view = new this._arrayConstructor(buffer, 0, this.length);\n input.readSlice(view, 0, this.byteLength);\n return view as Parsed<TTypedArray>;\n }\n\n measure(\n _value: Parsed<TTypedArray> | typeof MaxValue,\n measurer: IMeasurer = new Measurer(),\n ): IMeasurer {\n return measurer.add(this.byteLength);\n }\n}\n\n// @__NO_SIDE_EFFECTS__\nexport const u8Array = (length: number): TypedArraySchema<Uint8Array> =>\n new TypedArraySchema(length, Uint8Array);\n\n// @__NO_SIDE_EFFECTS__\nexport const u8ClampedArray = (\n length: number,\n): TypedArraySchema<Uint8ClampedArray> =>\n new TypedArraySchema(length, Uint8ClampedArray);\n\n// @__NO_SIDE_EFFECTS__\nexport const u16Array = (length: number): TypedArraySchema<Uint16Array> =>\n new TypedArraySchema(length, Uint16Array);\n\n// @__NO_SIDE_EFFECTS__\nexport const u32Array = (length: number): TypedArraySchema<Uint32Array> =>\n new TypedArraySchema(length, Uint32Array);\n\n// @__NO_SIDE_EFFECTS__\nexport const i8Array = (length: number): TypedArraySchema<Int8Array> =>\n new TypedArraySchema(length, Int8Array);\n\n// @__NO_SIDE_EFFECTS__\nexport const i16Array = (length: number): TypedArraySchema<Int16Array> =>\n new TypedArraySchema(length, Int16Array);\n\n// @__NO_SIDE_EFFECTS__\nexport const i32Array = (length: number): TypedArraySchema<Int32Array> =>\n new TypedArraySchema(length, Int32Array);\n\n// @__NO_SIDE_EFFECTS__\nexport const f32Array = (length: number): TypedArraySchema<Float32Array> =>\n new TypedArraySchema(length, Float32Array);\n\n// @__NO_SIDE_EFFECTS__\nexport const f64Array = (length: number): TypedArraySchema<Float64Array> =>\n new TypedArraySchema(length, Float64Array);\n","/**\n * @returns {Boolean} true if system is big endian\n */\n// @__NO_SIDE_EFFECTS__\nfunction isSystemBigEndian(): boolean {\n const array = new Uint8Array(4);\n const view = new Uint32Array(array.buffer);\n\n view[0] = 1; // setting a one spanning 4 bytes\n\n return array[0] === 0; // if zero is the left-most byte, one was encoded as big endian\n}\n\n// @__NO_SIDE_EFFECTS__\nexport function getSystemEndianness(): 'big' | 'little' {\n return isSystemBigEndian() ? 'big' : 'little';\n}\n","interface UnwrapBufferResult {\n buffer: ArrayBufferLike;\n byteOffset: number;\n byteLength: number;\n}\n\n/**\n * Removes up to one layer of view over a buffer.\n */\n// @__NO_SIDE_EFFECTS__\nexport function unwrapBuffer(\n buffer: ArrayBufferLike | ArrayBufferView,\n): UnwrapBufferResult {\n let byteOffset = 0;\n let innerBuffer = buffer;\n\n if (!!innerBuffer && 'buffer' in innerBuffer && 'byteOffset' in innerBuffer) {\n // Getting rid of the outer shell, which allow us to create new views on the buffer instead of creating copies of it.\n byteOffset += innerBuffer.byteOffset;\n innerBuffer = innerBuffer.buffer;\n }\n\n return { buffer: innerBuffer, byteOffset, byteLength: buffer.byteLength };\n}\n","import { getSystemEndianness } from '../util.ts';\nimport type { Endianness } from './types.ts';\nimport { unwrapBuffer } from './unwrapBuffer.ts';\n\nexport type BufferIOOptions = {\n /**\n * @default 0\n */\n byteOffset?: number;\n /**\n * @default 'system'\n */\n endianness?: Endianness | 'system';\n};\n\nexport class BufferIOBase {\n protected readonly dataView: DataView;\n protected readonly littleEndian: boolean;\n protected byteOffset = 0;\n\n public readonly endianness: Endianness;\n\n constructor(buffer: ArrayBufferLike, options?: BufferIOOptions) {\n const { byteOffset = 0, endianness = 'system' } = options ?? {};\n\n this.byteOffset = byteOffset;\n\n const systemEndianness = getSystemEndianness();\n this.endianness = endianness === 'system' ? systemEndianness : endianness;\n this.littleEndian = this.endianness === 'little';\n\n // Getting rid of the outer shell, which causes the Uint8Array line to create a copy, instead of a view.\n const unwrapped = unwrapBuffer(buffer);\n this.byteOffset += unwrapped.byteOffset;\n\n this.dataView = new DataView(unwrapped.buffer);\n }\n\n get currentByteOffset(): number {\n return this.byteOffset;\n }\n\n seekTo(offset: number): void {\n this.byteOffset = offset;\n }\n\n skipBytes(bytes: number): void {\n this.byteOffset += bytes;\n }\n}\n","export function numberToFloat16(value: number): number {\n // conversion according to IEEE 754 binary16 format\n if (value === 0) return 0;\n if (Number.isNaN(value)) return 0x7e00;\n if (!Number.isFinite(value)) return value > 0 ? 0x7c00 : 0xfc00;\n\n const sign = value < 0 ? 1 : 0;\n const absValue = Math.abs(value);\n const exponent = Math.floor(Math.log2(absValue));\n const mantissa = absValue / 2 ** exponent - 1;\n const biasedExponent = exponent + 15;\n const mantissaBits = Math.floor(mantissa * 1024);\n return (sign << 15) | (biasedExponent << 10) | mantissaBits;\n}\n\nexport function float16ToNumber(uint16Encoding: number): number {\n const sign = (uint16Encoding & 0x8000) >> 15;\n const exponent = (uint16Encoding & 0x7c00) >> 10;\n const mantissa = uint16Encoding & 0x3ff;\n if (exponent === 0) {\n return sign === 0 ? mantissa / 1024 : -mantissa / 1024;\n }\n if (exponent === 31) {\n return mantissa === 0\n ? sign === 0\n ? Number.POSITIVE_INFINITY\n : Number.NEGATIVE_INFINITY\n : Number.NaN;\n }\n return (sign === 0 ? 1 : -1) * (1 + mantissa / 1024) * 2 ** (exponent - 15);\n}\n","import { BufferIOBase } from './bufferIOBase.ts';\nimport { float16ToNumber } from './float16converter.ts';\nimport type { ISerialInput } from './types.ts';\nimport { unwrapBuffer } from './unwrapBuffer.ts';\n\nexport class BufferReader extends BufferIOBase implements ISerialInput {\n private _cachedTextDecoder: TextDecoder | undefined;\n\n private get _textDecoder() {\n if (!this._cachedTextDecoder) {\n this._cachedTextDecoder = new TextDecoder(undefined, { fatal: true });\n }\n return this._cachedTextDecoder;\n }\n\n readBool(): boolean {\n return this.dataView.getUint8(this.byteOffset++) !== 0;\n }\n\n readByte(): number {\n return this.dataView.getUint8(this.byteOffset++);\n }\n\n readInt8(): number {\n return this.dataView.getInt8(this.byteOffset++);\n }\n\n readUint8(): number {\n return this.dataView.getUint8(this.byteOffset++);\n }\n\n readInt16(): number {\n const value = this.dataView.getInt16(this.byteOffset, this.littleEndian);\n this.byteOffset += 2;\n return value;\n }\n\n readUint16(): number {\n const value = this.dataView.getUint16(this.byteOffset, this.littleEndian);\n this.byteOffset += 2;\n return value;\n }\n\n readInt32(): number {\n const value = this.dataView.getInt32(this.byteOffset, this.littleEndian);\n this.byteOffset += 4;\n return value;\n }\n\n readUint32(): number {\n const value = this.dataView.getUint32(this.byteOffset, this.littleEndian);\n this.byteOffset += 4;\n return value;\n }\n\n readFloat16(): number {\n const value = this.dataView.getUint16(this.byteOffset, this.littleEndian);\n this.byteOffset += 2;\n return float16ToNumber(value);\n }\n\n readFloat32(): number {\n const value = this.dataView.getFloat32(this.byteOffset, this.littleEndian);\n this.byteOffset += 4;\n return value;\n }\n\n readString(): string {\n // Looking for the 'NULL' byte.\n let strLength = 0;\n while (this.byteOffset + strLength < this.dataView.byteLength) {\n if (this.dataV