@yume-chan/struct
Advanced tools
Comparing version 0.0.15 to 0.0.16
@@ -5,2 +5,23 @@ { | ||
{ | ||
"version": "0.0.16", | ||
"tag": "@yume-chan/struct_v0.0.16", | ||
"date": "Sat, 28 May 2022 03:56:37 GMT", | ||
"comments": { | ||
"none": [ | ||
{ | ||
"comment": "Add support for custom TypeScript type for `uint8Array`" | ||
}, | ||
{ | ||
"comment": "Upgrade TypeScript to 4.7.2 to enable Node.js ESM" | ||
}, | ||
{ | ||
"comment": "Improve performance of `Struct#deserialize()` by up to 200%." | ||
}, | ||
{ | ||
"comment": "Remove `SyncBird`, it's replaced by `SyncPromise`, which is based on native Promise and is 200% faster." | ||
} | ||
] | ||
} | ||
}, | ||
{ | ||
"version": "0.0.15", | ||
@@ -7,0 +28,0 @@ "tag": "@yume-chan/struct_v0.0.15", |
# Change Log - @yume-chan/struct | ||
This log was last generated on Mon, 02 May 2022 04:18:01 GMT and should not be manually modified. | ||
This log was last generated on Sat, 28 May 2022 03:56:37 GMT and should not be manually modified. | ||
## 0.0.16 | ||
Sat, 28 May 2022 03:56:37 GMT | ||
### Updates | ||
- Add support for custom TypeScript type for `uint8Array` | ||
- Upgrade TypeScript to 4.7.2 to enable Node.js ESM | ||
- Improve performance of `Struct#deserialize()` by up to 200%. | ||
- Remove `SyncBird`, it's replaced by `SyncPromise`, which is based on native Promise and is 200% faster. | ||
## 0.0.15 | ||
@@ -6,0 +16,0 @@ Mon, 02 May 2022 04:18:01 GMT |
@@ -34,3 +34,3 @@ import type { StructAsyncDeserializeStream, StructDeserializeStream } from "./stream.js"; | ||
*/ | ||
abstract create(options: Readonly<StructOptions>, struct: StructValue, value: TValue): StructFieldValue<this>; | ||
abstract create(options: Readonly<StructOptions>, structValue: StructValue, value: TValue): StructFieldValue<this>; | ||
/** | ||
@@ -40,7 +40,7 @@ * When implemented in derived classes,It must be synchronous (returns a value) or asynchronous (returns a `Promise`) depending | ||
* | ||
* `Syncbird` can be used to make the implementation easier. | ||
* `SyncPromise` can be used to simplify implementation. | ||
*/ | ||
abstract deserialize(options: Readonly<StructOptions>, stream: StructDeserializeStream, struct: StructValue): StructFieldValue<this>; | ||
abstract deserialize(options: Readonly<StructOptions>, stream: StructDeserializeStream, structValue: StructValue): StructFieldValue<this>; | ||
abstract deserialize(options: Readonly<StructOptions>, stream: StructAsyncDeserializeStream, struct: StructValue): Promise<StructFieldValue<this>>; | ||
} | ||
//# sourceMappingURL=definition.d.ts.map |
@@ -1,2 +0,1 @@ | ||
// cspell: ignore Syncbird | ||
/** | ||
@@ -3,0 +2,0 @@ * A field definition defines how to deserialize a field. |
@@ -17,2 +17,3 @@ import type { StructFieldDefinition } from "./definition.js"; | ||
readonly struct: StructValue; | ||
get hasCustomAccessors(): boolean; | ||
protected value: TDefinition['TValue']; | ||
@@ -19,0 +20,0 @@ constructor(definition: TDefinition, options: Readonly<StructOptions>, struct: StructValue, value: TDefinition['TValue']); |
@@ -14,2 +14,6 @@ /** | ||
struct; | ||
get hasCustomAccessors() { | ||
return this.get !== StructFieldValue.prototype.get || | ||
this.set !== StructFieldValue.prototype.set; | ||
} | ||
value; | ||
@@ -16,0 +20,0 @@ constructor(definition, options, struct, value) { |
@@ -0,1 +1,2 @@ | ||
import type { ValueOrPromise } from "../utils.js"; | ||
export interface StructDeserializeStream { | ||
@@ -17,4 +18,4 @@ /** | ||
*/ | ||
read(length: number): Promise<Uint8Array>; | ||
read(length: number): ValueOrPromise<Uint8Array>; | ||
} | ||
//# sourceMappingURL=stream.d.ts.map |
import type { StructFieldValue } from "./field-value.js"; | ||
export declare const STRUCT_VALUE_SYMBOL: unique symbol; | ||
/** | ||
@@ -10,16 +11,17 @@ * A struct value is a map between keys in a struct and their field values. | ||
readonly value: Record<PropertyKey, unknown>; | ||
constructor(prototype: any); | ||
/** | ||
* Sets a `StructFieldValue` for `key` | ||
* | ||
* @param key The field name | ||
* @param value The associated `StructFieldValue` | ||
* @param name The field name | ||
* @param fieldValue The associated `StructFieldValue` | ||
*/ | ||
set(key: PropertyKey, value: StructFieldValue): void; | ||
set(name: PropertyKey, fieldValue: StructFieldValue): void; | ||
/** | ||
* Gets the `StructFieldValue` for `key` | ||
* | ||
* @param key The field name | ||
* @param name The field name | ||
*/ | ||
get(key: PropertyKey): StructFieldValue; | ||
get(name: PropertyKey): StructFieldValue; | ||
} | ||
//# sourceMappingURL=struct-value.d.ts.map |
@@ -0,1 +1,2 @@ | ||
export const STRUCT_VALUE_SYMBOL = Symbol("struct-value"); | ||
/** | ||
@@ -9,17 +10,32 @@ * A struct value is a map between keys in a struct and their field values. | ||
*/ | ||
value = {}; | ||
value; | ||
constructor(prototype) { | ||
// PERF: `Object.create(extra)` is 50% faster | ||
// than `Object.defineProperties(this.value, extra)` | ||
this.value = Object.create(prototype); | ||
// PERF: `Object.defineProperty` is slow | ||
// but we need it to be non-enumerable | ||
Object.defineProperty(this.value, STRUCT_VALUE_SYMBOL, { enumerable: false, value: this }); | ||
} | ||
/** | ||
* Sets a `StructFieldValue` for `key` | ||
* | ||
* @param key The field name | ||
* @param value The associated `StructFieldValue` | ||
* @param name The field name | ||
* @param fieldValue The associated `StructFieldValue` | ||
*/ | ||
set(key, value) { | ||
this.fieldValues[key] = value; | ||
Object.defineProperty(this.value, key, { | ||
configurable: true, | ||
enumerable: true, | ||
get() { return value.get(); }, | ||
set(v) { value.set(v); }, | ||
}); | ||
set(name, fieldValue) { | ||
this.fieldValues[name] = fieldValue; | ||
// PERF: `Object.defineProperty` is slow | ||
// use normal property when possible | ||
if (fieldValue.hasCustomAccessors) { | ||
Object.defineProperty(this.value, name, { | ||
configurable: true, | ||
enumerable: true, | ||
get() { return fieldValue.get(); }, | ||
set(v) { fieldValue.set(v); }, | ||
}); | ||
} | ||
else { | ||
this.value[name] = fieldValue.get(); | ||
} | ||
} | ||
@@ -29,8 +45,8 @@ /** | ||
* | ||
* @param key The field name | ||
* @param name The field name | ||
*/ | ||
get(key) { | ||
return this.fieldValues[key]; | ||
get(name) { | ||
return this.fieldValues[name]; | ||
} | ||
} | ||
//# sourceMappingURL=struct-value.js.map |
@@ -1,2 +0,2 @@ | ||
import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructOptions } from './basic/index.js'; | ||
import { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructOptions } from './basic/index.js'; | ||
import { BigIntFieldType, BufferFieldSubType, FixedLengthBufferLikeFieldDefinition, NumberFieldType, StringBufferFieldSubType, Uint8ArrayBufferFieldSubType, VariableLengthBufferLikeFieldDefinition, type FixedLengthBufferLikeFieldOptions, type LengthField, type VariableLengthBufferLikeFieldOptions } from './types/index.js'; | ||
@@ -19,4 +19,4 @@ import type { Evaluate, Identity, Overwrite } from "./utils.js"; | ||
interface BoundArrayBufferLikeFieldDefinitionCreator<TFields extends object, TOmitInitKey extends PropertyKey, TExtra extends object, TPostDeserialized, TType extends BufferFieldSubType<any, any>> { | ||
<TName extends PropertyKey, TTypeScriptType = TType['TTypeScriptType']>(name: TName, options: FixedLengthBufferLikeFieldOptions, typescriptType?: TTypeScriptType): AddFieldDescriptor<TFields, TOmitInitKey, TExtra, TPostDeserialized, TName, FixedLengthBufferLikeFieldDefinition<TType, FixedLengthBufferLikeFieldOptions>>; | ||
<TName extends PropertyKey, TLengthField extends LengthField<TFields>, TOptions extends VariableLengthBufferLikeFieldOptions<TFields, TLengthField>, TTypeScriptType = TType['TTypeScriptType']>(name: TName, options: TOptions, typescriptType?: TTypeScriptType): AddFieldDescriptor<TFields, TOmitInitKey, TExtra, TPostDeserialized, TName, VariableLengthBufferLikeFieldDefinition<TType, TOptions>>; | ||
<TName extends PropertyKey, TTypeScriptType = TType['TTypeScriptType']>(name: TName, options: FixedLengthBufferLikeFieldOptions, typeScriptType?: TTypeScriptType): AddFieldDescriptor<TFields, TOmitInitKey, TExtra, TPostDeserialized, TName, FixedLengthBufferLikeFieldDefinition<TType, FixedLengthBufferLikeFieldOptions, TTypeScriptType>>; | ||
<TName extends PropertyKey, TLengthField extends LengthField<TFields>, TOptions extends VariableLengthBufferLikeFieldOptions<TFields, TLengthField>, TTypeScriptType = TType['TTypeScriptType']>(name: TName, options: TOptions, typeScriptType?: TTypeScriptType): AddFieldDescriptor<TFields, TOmitInitKey, TExtra, TPostDeserialized, TName, VariableLengthBufferLikeFieldDefinition<TType, TOptions, TTypeScriptType>>; | ||
} | ||
@@ -53,23 +53,23 @@ export declare type StructPostDeserialized<TFields, TPostDeserialized> = (this: TFields, object: TFields) => TPostDeserialized; | ||
*/ | ||
int8<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Uint8']['TTypeScriptType']>(name: TName, _typescriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
int8<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Uint8']['TTypeScriptType']>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
/** | ||
* Appends an `uint8` field to the `Struct` | ||
*/ | ||
uint8<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Uint8']['TTypeScriptType']>(name: TName, _typescriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
uint8<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Uint8']['TTypeScriptType']>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
/** | ||
* Appends an `int16` field to the `Struct` | ||
*/ | ||
int16<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Uint16']['TTypeScriptType']>(name: TName, _typescriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
int16<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Uint16']['TTypeScriptType']>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
/** | ||
* Appends an `uint16` field to the `Struct` | ||
*/ | ||
uint16<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Uint16']['TTypeScriptType']>(name: TName, _typescriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
uint16<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Uint16']['TTypeScriptType']>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
/** | ||
* Appends an `int32` field to the `Struct` | ||
*/ | ||
int32<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Int32']['TTypeScriptType']>(name: TName, _typescriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
int32<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Int32']['TTypeScriptType']>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
/** | ||
* Appends an `uint32` field to the `Struct` | ||
*/ | ||
uint32<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Uint32']['TTypeScriptType']>(name: TName, typescriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
uint32<TName extends PropertyKey, TTypeScriptType = (typeof NumberFieldType)['Uint32']['TTypeScriptType']>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
private bigint; | ||
@@ -81,3 +81,3 @@ /** | ||
*/ | ||
int64<TName extends PropertyKey, TTypeScriptType = BigIntFieldType['TTypeScriptType']>(name: TName, _typescriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
int64<TName extends PropertyKey, TTypeScriptType = BigIntFieldType['TTypeScriptType']>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
/** | ||
@@ -88,3 +88,3 @@ * Appends an `uint64` field to the `Struct` | ||
*/ | ||
uint64<TName extends PropertyKey, TTypeScriptType = BigIntFieldType['TTypeScriptType']>(name: TName, _typescriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
uint64<TName extends PropertyKey, TTypeScriptType = BigIntFieldType['TTypeScriptType']>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>; | ||
private arrayBufferLike; | ||
@@ -91,0 +91,0 @@ uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<TFields, TOmitInitKey, TExtra, TPostDeserialized, Uint8ArrayBufferFieldSubType>; |
@@ -1,4 +0,4 @@ | ||
// cspell: ignore Syncbird | ||
import { STRUCT_VALUE_SYMBOL } from './basic/index.js'; | ||
import { StructDefaultOptions, StructValue } from './basic/index.js'; | ||
import { Syncbird } from "./syncbird.js"; | ||
import { SyncPromise } from "./sync-promise.js"; | ||
import { BigIntFieldDefinition, BigIntFieldType, FixedLengthBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringBufferFieldSubType, Uint8ArrayBufferFieldSubType, VariableLengthBufferLikeFieldDefinition } from './types/index.js'; | ||
@@ -46,7 +46,7 @@ export class Struct { | ||
this._size += other._size; | ||
Object.assign(this._extra, other._extra); | ||
Object.defineProperties(this._extra, Object.getOwnPropertyDescriptors(other._extra)); | ||
return this; | ||
} | ||
number(name, type, _typescriptType) { | ||
return this.field(name, new NumberFieldDefinition(type, _typescriptType)); | ||
number(name, type, typeScriptType) { | ||
return this.field(name, new NumberFieldDefinition(type, typeScriptType)); | ||
} | ||
@@ -56,4 +56,4 @@ /** | ||
*/ | ||
int8(name, _typescriptType) { | ||
return this.number(name, NumberFieldType.Int8, _typescriptType); | ||
int8(name, typeScriptType) { | ||
return this.number(name, NumberFieldType.Int8, typeScriptType); | ||
} | ||
@@ -63,4 +63,4 @@ /** | ||
*/ | ||
uint8(name, _typescriptType) { | ||
return this.number(name, NumberFieldType.Uint8, _typescriptType); | ||
uint8(name, typeScriptType) { | ||
return this.number(name, NumberFieldType.Uint8, typeScriptType); | ||
} | ||
@@ -70,4 +70,4 @@ /** | ||
*/ | ||
int16(name, _typescriptType) { | ||
return this.number(name, NumberFieldType.Int16, _typescriptType); | ||
int16(name, typeScriptType) { | ||
return this.number(name, NumberFieldType.Int16, typeScriptType); | ||
} | ||
@@ -77,4 +77,4 @@ /** | ||
*/ | ||
uint16(name, _typescriptType) { | ||
return this.number(name, NumberFieldType.Uint16, _typescriptType); | ||
uint16(name, typeScriptType) { | ||
return this.number(name, NumberFieldType.Uint16, typeScriptType); | ||
} | ||
@@ -84,4 +84,4 @@ /** | ||
*/ | ||
int32(name, _typescriptType) { | ||
return this.number(name, NumberFieldType.Int32, _typescriptType); | ||
int32(name, typeScriptType) { | ||
return this.number(name, NumberFieldType.Int32, typeScriptType); | ||
} | ||
@@ -91,7 +91,7 @@ /** | ||
*/ | ||
uint32(name, typescriptType) { | ||
return this.number(name, NumberFieldType.Uint32, typescriptType); | ||
uint32(name, typeScriptType) { | ||
return this.number(name, NumberFieldType.Uint32, typeScriptType); | ||
} | ||
bigint(name, type, _typescriptType) { | ||
return this.field(name, new BigIntFieldDefinition(type, _typescriptType)); | ||
bigint(name, type, typeScriptType) { | ||
return this.field(name, new BigIntFieldDefinition(type, typeScriptType)); | ||
} | ||
@@ -103,4 +103,4 @@ /** | ||
*/ | ||
int64(name, _typescriptType) { | ||
return this.bigint(name, BigIntFieldType.Int64, _typescriptType); | ||
int64(name, typeScriptType) { | ||
return this.bigint(name, BigIntFieldType.Int64, typeScriptType); | ||
} | ||
@@ -112,4 +112,4 @@ /** | ||
*/ | ||
uint64(name, _typescriptType) { | ||
return this.bigint(name, BigIntFieldType.Uint64, _typescriptType); | ||
uint64(name, typeScriptType) { | ||
return this.bigint(name, BigIntFieldType.Uint64, typeScriptType); | ||
} | ||
@@ -124,7 +124,7 @@ arrayBufferLike = (name, type, options) => { | ||
}; | ||
uint8Array = (name, options) => { | ||
return this.arrayBufferLike(name, Uint8ArrayBufferFieldSubType.Instance, options); | ||
uint8Array = (name, options, typeScriptType) => { | ||
return this.arrayBufferLike(name, Uint8ArrayBufferFieldSubType.Instance, options, typeScriptType); | ||
}; | ||
string = (name, options) => { | ||
return this.arrayBufferLike(name, StringBufferFieldSubType.Instance, options); | ||
string = (name, options, typeScriptType) => { | ||
return this.arrayBufferLike(name, StringBufferFieldSubType.Instance, options, typeScriptType); | ||
}; | ||
@@ -142,3 +142,3 @@ /** | ||
extra(value) { | ||
Object.assign(this._extra, Object.getOwnPropertyDescriptors(value)); | ||
Object.defineProperties(this._extra, Object.getOwnPropertyDescriptors(value)); | ||
return this; | ||
@@ -151,12 +151,14 @@ } | ||
deserialize(stream) { | ||
const value = new StructValue(); | ||
Object.defineProperties(value.value, this._extra); | ||
return Syncbird | ||
.each(this._fields, ([name, definition]) => { | ||
return Syncbird.resolve(definition.deserialize(this.options, stream, value)).then(fieldValue => { | ||
value.set(name, fieldValue); | ||
const structValue = new StructValue(this._extra); | ||
let promise = SyncPromise.resolve(); | ||
for (const [name, definition] of this._fields) { | ||
promise = promise | ||
.then(() => definition.deserialize(this.options, stream, structValue)) | ||
.then(fieldValue => { | ||
structValue.set(name, fieldValue); | ||
}); | ||
}) | ||
} | ||
return promise | ||
.then(() => { | ||
const object = value.value; | ||
const object = structValue.value; | ||
// Run `postDeserialized` | ||
@@ -176,11 +178,23 @@ if (this._postDeserialized) { | ||
serialize(init, output) { | ||
const value = new StructValue(); | ||
for (const [name, definition] of this._fields) { | ||
const fieldValue = definition.create(this.options, value, init[name]); | ||
value.set(name, fieldValue); | ||
let structValue; | ||
if (STRUCT_VALUE_SYMBOL in init) { | ||
structValue = init[STRUCT_VALUE_SYMBOL]; | ||
for (const [key, value] of Object.entries(init)) { | ||
const fieldValue = structValue.get(key); | ||
if (fieldValue) { | ||
fieldValue.set(value); | ||
} | ||
} | ||
} | ||
else { | ||
structValue = new StructValue({}); | ||
for (const [name, definition] of this._fields) { | ||
const fieldValue = definition.create(this.options, structValue, init[name]); | ||
structValue.set(name, fieldValue); | ||
} | ||
} | ||
let structSize = 0; | ||
const fieldsInfo = []; | ||
for (const [name] of this._fields) { | ||
const fieldValue = value.get(name); | ||
const fieldValue = structValue.get(name); | ||
const size = fieldValue.getSize(); | ||
@@ -187,0 +201,0 @@ fieldsInfo.push({ fieldValue, size }); |
@@ -1,5 +0,4 @@ | ||
// cspell: ignore syncbird | ||
import { getBigInt64, getBigUint64, setBigInt64, setBigUint64 } from '@yume-chan/dataview-bigint-polyfill/esm/fallback.js'; | ||
import { StructFieldDefinition, StructFieldValue } from "../basic/index.js"; | ||
import { Syncbird } from "../syncbird.js"; | ||
import { SyncPromise } from "../sync-promise.js"; | ||
export class BigIntFieldType { | ||
@@ -31,9 +30,12 @@ TTypeScriptType; | ||
deserialize(options, stream, struct) { | ||
return Syncbird.try(() => { | ||
return SyncPromise | ||
.try(() => { | ||
return stream.read(this.getSize()); | ||
}).then(array => { | ||
}) | ||
.then(array => { | ||
const view = new DataView(array.buffer, array.byteOffset, array.byteLength); | ||
const value = this.type.getter(view, 0, options.littleEndian); | ||
return this.create(options, struct, value); | ||
}).valueOrPromise(); | ||
}) | ||
.valueOrPromise(); | ||
} | ||
@@ -40,0 +42,0 @@ } |
@@ -32,4 +32,4 @@ import { StructFieldDefinition, StructFieldValue, StructValue, type StructAsyncDeserializeStream, type StructDeserializeStream, type StructOptions } from '../../basic/index.js'; | ||
/** An `BufferFieldSubType` that's actually an `Uint8Array` */ | ||
export declare class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array> { | ||
static readonly Instance: Uint8ArrayBufferFieldSubType; | ||
export declare class Uint8ArrayBufferFieldSubType<TTypeScriptType = Uint8Array> extends BufferFieldSubType<Uint8Array, TTypeScriptType> { | ||
static readonly Instance: Uint8ArrayBufferFieldSubType<Uint8Array>; | ||
protected constructor(); | ||
@@ -47,3 +47,4 @@ toBuffer(value: Uint8Array): Uint8Array; | ||
} | ||
export declare abstract class BufferLikeFieldDefinition<TType extends BufferFieldSubType<any, any> = BufferFieldSubType<unknown, unknown>, TOptions = void, TOmitInitKey extends PropertyKey = never> extends StructFieldDefinition<TOptions, TType['TTypeScriptType'], TOmitInitKey> { | ||
export declare const EMPTY_UINT8_ARRAY: Uint8Array; | ||
export declare abstract class BufferLikeFieldDefinition<TType extends BufferFieldSubType<any, any> = BufferFieldSubType<unknown, unknown>, TOptions = void, TOmitInitKey extends PropertyKey = never, TTypeScriptType = TType["TTypeScriptType"]> extends StructFieldDefinition<TOptions, TTypeScriptType, TOmitInitKey> { | ||
readonly type: TType; | ||
@@ -50,0 +51,0 @@ constructor(type: TType, options: TOptions); |
@@ -1,4 +0,3 @@ | ||
// cspell: ignore syncbird | ||
import { StructFieldDefinition, StructFieldValue } from '../../basic/index.js'; | ||
import { Syncbird } from "../../syncbird.js"; | ||
import { SyncPromise } from "../../sync-promise.js"; | ||
import { decodeUtf8, encodeUtf8 } from "../../utils.js"; | ||
@@ -49,3 +48,3 @@ /** | ||
} | ||
const EmptyBuffer = new Uint8Array(0); | ||
export const EMPTY_UINT8_ARRAY = new Uint8Array(0); | ||
export class BufferLikeFieldDefinition extends StructFieldDefinition { | ||
@@ -67,6 +66,7 @@ type; | ||
deserialize(options, stream, struct) { | ||
return Syncbird.try(() => { | ||
return SyncPromise | ||
.try(() => { | ||
const size = this.getDeserializeSize(struct); | ||
if (size === 0) { | ||
return EmptyBuffer; | ||
return EMPTY_UINT8_ARRAY; | ||
} | ||
@@ -76,6 +76,8 @@ else { | ||
} | ||
}).then(array => { | ||
}) | ||
.then(array => { | ||
const value = this.type.toValue(array); | ||
return this.create(options, struct, value, array); | ||
}).valueOrPromise(); | ||
}) | ||
.valueOrPromise(); | ||
} | ||
@@ -82,0 +84,0 @@ } |
@@ -5,5 +5,5 @@ import { BufferLikeFieldDefinition, type BufferFieldSubType } from "./base.js"; | ||
} | ||
export declare class FixedLengthBufferLikeFieldDefinition<TType extends BufferFieldSubType = BufferFieldSubType, TOptions extends FixedLengthBufferLikeFieldOptions = FixedLengthBufferLikeFieldOptions> extends BufferLikeFieldDefinition<TType, TOptions> { | ||
export declare class FixedLengthBufferLikeFieldDefinition<TType extends BufferFieldSubType = BufferFieldSubType, TOptions extends FixedLengthBufferLikeFieldOptions = FixedLengthBufferLikeFieldOptions, TTypeScriptType = TType["TTypeScriptType"]> extends BufferLikeFieldDefinition<TType, TOptions, never, TTypeScriptType> { | ||
getSize(): number; | ||
} | ||
//# sourceMappingURL=fixed-length.d.ts.map |
@@ -20,6 +20,6 @@ import { StructFieldValue, type StructFieldDefinition, type StructOptions, type StructValue } from '../../basic/index.js'; | ||
} | ||
export declare class VariableLengthBufferLikeFieldDefinition<TType extends BufferFieldSubType = BufferFieldSubType, TOptions extends VariableLengthBufferLikeFieldOptions = VariableLengthBufferLikeFieldOptions> extends BufferLikeFieldDefinition<TType, TOptions, TOptions['lengthField']> { | ||
export declare class VariableLengthBufferLikeFieldDefinition<TType extends BufferFieldSubType = BufferFieldSubType, TOptions extends VariableLengthBufferLikeFieldOptions = VariableLengthBufferLikeFieldOptions, TTypeScriptType = TType["TTypeScriptType"]> extends BufferLikeFieldDefinition<TType, TOptions, TOptions['lengthField'], TTypeScriptType> { | ||
getSize(): number; | ||
protected getDeserializeSize(struct: StructValue): number; | ||
create(options: Readonly<StructOptions>, struct: StructValue, value: TType['TTypeScriptType'], array?: Uint8Array): VariableLengthBufferLikeStructFieldValue<this>; | ||
create(options: Readonly<StructOptions>, struct: StructValue, value: TTypeScriptType, array?: Uint8Array): VariableLengthBufferLikeStructFieldValue<this>; | ||
} | ||
@@ -26,0 +26,0 @@ export declare class VariableLengthBufferLikeStructFieldValue<TDefinition extends VariableLengthBufferLikeFieldDefinition = VariableLengthBufferLikeFieldDefinition> extends BufferLikeFieldValue<TDefinition> { |
import { StructFieldDefinition, StructFieldValue, StructValue, type StructAsyncDeserializeStream, type StructDeserializeStream, type StructOptions } from '../basic/index.js'; | ||
export declare type DataViewGetters = { | ||
[TKey in keyof DataView]: TKey extends `get${string}` ? TKey : never; | ||
}[keyof DataView]; | ||
declare type NumberTypeDeserializer = (array: Uint8Array, littleEndian: boolean) => number; | ||
export declare type DataViewSetters = { | ||
@@ -10,6 +8,8 @@ [TKey in keyof DataView]: TKey extends `set${string}` ? TKey : never; | ||
readonly TTypeScriptType: number; | ||
readonly signed: boolean; | ||
readonly size: number; | ||
readonly dataViewGetter: DataViewGetters; | ||
readonly deserializer: NumberTypeDeserializer; | ||
readonly convertSign: (value: number) => number; | ||
readonly dataViewSetter: DataViewSetters; | ||
constructor(size: number, dataViewGetter: DataViewGetters, dataViewSetter: DataViewSetters); | ||
constructor(size: number, signed: boolean, convertSign: (value: number) => number, dataViewSetter: DataViewSetters); | ||
static readonly Int8: NumberFieldType; | ||
@@ -33,2 +33,3 @@ static readonly Uint8: NumberFieldType; | ||
} | ||
export {}; | ||
//# sourceMappingURL=number.d.ts.map |
@@ -1,20 +0,30 @@ | ||
// cspell: ignore syncbird | ||
import { StructFieldDefinition, StructFieldValue } from '../basic/index.js'; | ||
import { Syncbird } from "../syncbird.js"; | ||
import { SyncPromise } from "../sync-promise.js"; | ||
const DESERIALIZERS = { | ||
1: (array, littleEndian) => array[0], | ||
2: (array, littleEndian) => ((array[1] << 8) | array[0]) * littleEndian | | ||
((array[0] << 8) | array[1]) * !littleEndian, | ||
4: (array, littleEndian) => ((array[3] << 24) | (array[2] << 16) | (array[1] << 8) | array[0]) * littleEndian | | ||
((array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3]) * !littleEndian, | ||
}; | ||
export class NumberFieldType { | ||
TTypeScriptType; | ||
signed; | ||
size; | ||
dataViewGetter; | ||
deserializer; | ||
convertSign; | ||
dataViewSetter; | ||
constructor(size, dataViewGetter, dataViewSetter) { | ||
constructor(size, signed, convertSign, dataViewSetter) { | ||
this.size = size; | ||
this.dataViewGetter = dataViewGetter; | ||
this.signed = signed; | ||
this.deserializer = DESERIALIZERS[size]; | ||
this.convertSign = convertSign; | ||
this.dataViewSetter = dataViewSetter; | ||
} | ||
static Int8 = new NumberFieldType(1, 'getInt8', 'setInt8'); | ||
static Uint8 = new NumberFieldType(1, 'getUint8', 'setUint8'); | ||
static Int16 = new NumberFieldType(2, 'getInt16', 'setInt16'); | ||
static Uint16 = new NumberFieldType(2, 'getUint16', 'setUint16'); | ||
static Int32 = new NumberFieldType(4, 'getInt32', 'setInt32'); | ||
static Uint32 = new NumberFieldType(4, 'getUint32', 'setUint32'); | ||
static Int8 = new NumberFieldType(1, true, value => value << 24 >> 24, 'setInt8'); | ||
static Uint8 = new NumberFieldType(1, false, value => value, 'setUint8'); | ||
static Int16 = new NumberFieldType(2, true, value => value << 16 >> 16, 'setInt16'); | ||
static Uint16 = new NumberFieldType(2, false, value => value, 'setUint16'); | ||
static Int32 = new NumberFieldType(4, true, value => value, 'setInt32'); | ||
static Uint32 = new NumberFieldType(4, false, value => value >>> 0, 'setUint32'); | ||
} | ||
@@ -34,3 +44,3 @@ export class NumberFieldDefinition extends StructFieldDefinition { | ||
deserialize(options, stream, struct) { | ||
return Syncbird | ||
return SyncPromise | ||
.try(() => { | ||
@@ -40,4 +50,5 @@ return stream.read(this.getSize()); | ||
.then(array => { | ||
const view = new DataView(array.buffer, array.byteOffset, array.byteLength); | ||
const value = view[this.type.dataViewGetter](0, options.littleEndian); | ||
let value; | ||
value = this.type.deserializer(array, options.littleEndian); | ||
value = this.type.convertSign(value); | ||
return this.create(options, struct, value); | ||
@@ -44,0 +55,0 @@ }) |
{ | ||
"name": "@yume-chan/struct", | ||
"version": "0.0.15", | ||
"version": "0.0.16", | ||
"description": "C-style structure serializer and deserializer.", | ||
@@ -30,19 +30,19 @@ "keywords": [ | ||
"dependencies": { | ||
"@yume-chan/dataview-bigint-polyfill": "^0.0.15", | ||
"bluebird": "^3.7.2", | ||
"@yume-chan/dataview-bigint-polyfill": "^0.0.16", | ||
"tslib": "^2.3.1" | ||
}, | ||
"devDependencies": { | ||
"jest": "^27.5.1", | ||
"typescript": "4.7.0-beta", | ||
"@jest/globals": "^28.1.0", | ||
"@yume-chan/ts-package-builder": "^1.0.0", | ||
"@types/jest": "^27.4.1", | ||
"@types/bluebird": "^3.5.36" | ||
"cross-env": "^7.0.3", | ||
"jest": "^28.1.0", | ||
"ts-jest": "^28.0.2", | ||
"typescript": "4.7.2" | ||
}, | ||
"scripts": { | ||
"build": "build-ts-package", | ||
"build:watch": "build-ts-package --incremental", | ||
"test": "jest --coverage" | ||
"build": "tsc -b tsconfig.build.json", | ||
"build:watch": "tsc -b tsconfig.build.json", | ||
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage" | ||
}, | ||
"readme": "# @yume-chan/struct\r\n\r\n<!--\r\ncspell: ignore Codecov\r\ncspell: ignore uint8arraystring\r\n-->\r\n\r\n![license](https://img.shields.io/npm/l/@yume-chan/struct)\r\n![npm type definitions](https://img.shields.io/npm/types/@yume-chan/struct)\r\n[![npm version](https://img.shields.io/npm/v/@yume-chan/struct)](https://www.npmjs.com/package/@yume-chan/struct)\r\n![npm bundle size](https://img.shields.io/bundlephobia/min/@yume-chan/struct)\r\n![Codecov](https://img.shields.io/codecov/c/github/yume-chan/ya-webadb?flag=struct&token=2fU3Cx2Edq)\r\n\r\nA C-style structure serializer and deserializer. Written in TypeScript and highly takes advantage of its type system.\r\n\r\n**WARNING:** The public API is UNSTABLE. If you have any questions, please open an issue.\r\n\r\n## Installation\r\n\r\n```sh\r\n$ npm i @yume-chan/struct\r\n```\r\n\r\n## Quick Start\r\n\r\n```ts\r\nimport Struct from '@yume-chan/struct';\r\n\r\nconst MyStruct =\r\n new Struct({ littleEndian: true })\r\n .int8('foo')\r\n .int64('bar')\r\n .int32('bazLength')\r\n .string('baz', { lengthField: 'bazLength' });\r\n\r\nconst value = await MyStruct.deserialize(stream);\r\nvalue.foo // number\r\nvalue.bar // bigint\r\nvalue.bazLength // number\r\nvalue.baz // string\r\n\r\nconst buffer = MyStruct.serialize({\r\n foo: 42,\r\n bar: 42n,\r\n // `bazLength` automatically set to `baz`'s byte length\r\n baz: 'Hello, World!',\r\n});\r\n```\r\n\r\n<!-- cspell: disable -->\r\n\r\n- [Installation](#installation)\r\n- [Quick Start](#quick-start)\r\n- [Compatibility](#compatibility)\r\n - [Basic usage](#basic-usage)\r\n - [`int64`/`uint64`](#int64uint64)\r\n - [`string`](#string)\r\n- [API](#api)\r\n - [`placeholder`](#placeholder)\r\n - [`Struct`](#struct)\r\n - [`int8`/`uint8`/`int16`/`uint16`/`int32`/`uint32`](#int8uint8int16uint16int32uint32)\r\n - [`int64`/`uint64`](#int64uint64-1)\r\n - [`uint8Array`/`string`](#uint8arraystring)\r\n - [`fields`](#fields)\r\n - [`extra`](#extra)\r\n - [`postDeserialize`](#postdeserialize)\r\n - [`deserialize`](#deserialize)\r\n - [`serialize`](#serialize)\r\n- [Custom field type](#custom-field-type)\r\n - [`Struct#field`](#structfield)\r\n - [Relationship between types](#relationship-between-types)\r\n - [`StructFieldDefinition`](#structfielddefinition)\r\n - [`TValue`/`TOmitInitKey`](#tvaluetomitinitkey)\r\n - [`getSize`](#getsize)\r\n - [`create`](#create)\r\n - [`deserialize`](#deserialize-1)\r\n - [`StructFieldValue`](#structfieldvalue)\r\n - [`getSize`](#getsize-1)\r\n - [`get`/`set`](#getset)\r\n - [`serialize`](#serialize-1)\r\n\r\n<!-- cspell: enable -->\r\n\r\n## Compatibility\r\n\r\nHere is a list of features, their used APIs, and their compatibilities. If an optional feature is not actually used, its requirements can be ignored.\r\n\r\nSome features can be polyfilled to support older runtimes, but this library doesn't ship with any polyfills.\r\n\r\n### Basic usage\r\n\r\n| API | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |\r\n| -------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------- |\r\n| [`Promise`][MDN_Promise] | 32 | 12 | 29 | No | 8 | 0.12 |\r\n| [`ArrayBuffer`][MDN_ArrayBuffer] | 7 | 12 | 4 | 10 | 5.1 | 0.10 |\r\n| [`Uint8Array`][MDN_Uint8Array] | 7 | 12 | 4 | 10 | 5.1 | 0.10 |\r\n| [`DataView`][MDN_DataView] | 9 | 12 | 15 | 10 | 5.1 | 0.10 |\r\n| *Overall* | 32 | 12 | 29 | No | 8 | 0.12 |\r\n\r\n### [`int64`/`uint64`](#int64uint64-1)\r\n\r\n| API | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |\r\n| ---------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------- |\r\n| [`BigInt`][MDN_BigInt]<sup>1</sup> | 67 | 79 | 68 | No | 14 | 10.4 |\r\n\r\n<sup>1</sup> Can't be polyfilled\r\n\r\n### [`string`](#uint8arraystring)\r\n\r\n| API | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |\r\n| -------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------------------- |\r\n| [`TextEncoder`][MDN_TextEncoder] | 38 | 79 | 19 | No | 10.1 | 8.3<sup>1</sup>, 11 |\r\n\r\n<sup>1</sup> `TextEncoder` and `TextDecoder` are only available in `util` module. Need to be assigned to `globalThis`.\r\n\r\n[MDN_Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise\r\n[MDN_ArrayBuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer\r\n[MDN_Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array\r\n[MDN_DataView]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView\r\n[MDN_BigInt]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt\r\n[MDN_DataView]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView\r\n[MDN_TextEncoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder\r\n\r\n## API\r\n\r\n### `placeholder`\r\n\r\n```ts\r\nfunction placeholder<T>(): T {\r\n return undefined as unknown as T;\r\n}\r\n```\r\n\r\nReturns a (fake) value of the given type. It's only useful in TypeScript, if you are using JavaScript, you shouldn't care about it.\r\n\r\nMany methods in this library have multiple generic parameters, but TypeScript only allows users to specify none (let TypeScript inference all of them from arguments), or all generic arguments. ([Microsoft/TypeScript#26242](https://github.com/microsoft/TypeScript/issues/26242))\r\n\r\n<details>\r\n<summary>Detail explanation (click to expand)</summary>\r\n\r\nWhen you have a generic method, where half generic parameters can be inferred.\r\n\r\n```ts\r\ndeclare function fn<A, B>(a: A): [A, B];\r\nfn(42); // Expected 2 type arguments, but got 1. ts(2558)\r\n```\r\n\r\nRather than force users repeat the type `A`, I declare a parameter for `B`.\r\n\r\n```ts\r\ndeclare function fn2<A, B>(a: A, b: B): [A, B];\r\n```\r\n\r\nI don't really need a value of type `B`, I only require its type information\r\n\r\n```ts\r\nfn2(42, placeholder<boolean>()) // fn2<number, boolean>\r\n```\r\n</details>\r\n\r\nTo workaround this issue, these methods have an extra `_typescriptType` parameter, to let you specify a generic parameter, without passing all other generic arguments manually. The actual value of `_typescriptType` argument is never used, so you can pass any value, as long as it has the correct type, including values produced by this `placeholder` method.\r\n\r\n**With that said, I don't expect you to specify any generic arguments manually when using this library.**\r\n\r\n### `Struct`\r\n\r\n```ts\r\nclass Struct<\r\n TFields extends object = {},\r\n TOmitInitKey extends string | number | symbol = never,\r\n TExtra extends object = {},\r\n TPostDeserialized = undefined\r\n> {\r\n public constructor(options: Partial<StructOptions> = StructDefaultOptions);\r\n}\r\n```\r\n\r\nCreates a new structure definition.\r\n\r\n<details>\r\n<summary>Generic parameters (click to expand)</summary>\r\n\r\nThis information was added to help you understand how does it work. These are considered as \"internal state\" so don't specify them manually.\r\n\r\n1. `TFields`: Type of the Struct value. Modified when new fields are added.\r\n2. `TOmitInitKey`: When serializing a structure containing variable length buffers, the length field can be calculate from the buffer field, so they doesn't need to be provided explicitly.\r\n3. `TExtra`: Type of extra fields. Modified when `extra` is called.\r\n4. `TPostDeserialized`: State of the `postDeserialize` function. Modified when `postDeserialize` is called. Affects return type of `deserialize`\r\n</details>\r\n\r\n**Parameters**\r\n\r\n1. `options`:\r\n * `littleEndian:boolean = false`: Whether all multi-byte fields in this struct are [little-endian encoded][Wikipeida_Endianess].\r\n\r\n[Wikipeida_Endianess]: https://en.wikipedia.org/wiki/Endianness\r\n\r\n#### `int8`/`uint8`/`int16`/`uint16`/`int32`/`uint32`\r\n\r\n```ts\r\nint32<\r\n TName extends string | number | symbol,\r\n TTypeScriptType = number\r\n>(\r\n name: TName,\r\n _typescriptType?: TTypeScriptType\r\n): Struct<\r\n TFields & Record<TName, TTypeScriptType>,\r\n TOmitInitKey,\r\n TExtra,\r\n TPostDeserialized\r\n>;\r\n```\r\n\r\nAppends an `int8`/`uint8`/`int16`/`uint16`/`int32`/`uint32` field to the `Struct`.\r\n\r\n<details>\r\n<summary>Generic parameters (click to expand)</summary>\r\n\r\n1. `TName`: Literal type of the field's name.\r\n2. `TTypeScriptType = number`: Type of the field in the result object. For example you can declare it as a number literal type, or some enum type.\r\n</details>\r\n\r\n**Parameters**\r\n\r\n1. `name`: (Required) Field name. Must be a string literal.\r\n2. `_typescriptType`: Set field's type. See examples below.\r\n\r\n**Note**\r\n\r\nThere is no generic constraints on the `TTypeScriptType`, because TypeScript doesn't allow casting enum types to `number`.\r\n\r\nSo it's technically possible to pass in an incompatible type (e.g. `string`). But obviously, it's a bad idea.\r\n\r\n**Examples**\r\n\r\n1. Append an `int32` field named `foo`\r\n\r\n ```ts\r\n const struct = new Struct()\r\n .int32('foo');\r\n\r\n const value = await struct.deserialize(stream);\r\n value.foo; // number\r\n\r\n struct.serialize({ }) // error: 'foo' is required\r\n struct.serialize({ foo: 'bar' }) // error: 'foo' must be a number\r\n struct.serialize({ foo: 42 }) // ok\r\n ```\r\n\r\n2. Set fields' type (can be used with [`placeholder` method](#placeholder))\r\n\r\n ```ts\r\n enum MyEnum {\r\n a,\r\n b,\r\n }\r\n\r\n const struct = new Struct()\r\n .int32('foo', placeholder<MyEnum>())\r\n .int32('bar', MyEnum.a as const);\r\n\r\n const value = await struct.deserialize(stream);\r\n value.foo; // MyEnum\r\n value.bar; // MyEnum.a\r\n\r\n struct.serialize({ foo: 42, bar: MyEnum.a }); // error: 'foo' must be of type `MyEnum`\r\n struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }); // error: 'bar' must be of type `MyEnum.a`\r\n struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }); // ok\r\n ```\r\n\r\n#### `int64`/`uint64`\r\n\r\n```ts\r\nint64<\r\n TName extends string | number | symbol,\r\n TTypeScriptType = bigint\r\n>(\r\n name: TName,\r\n _typescriptType?: TTypeScriptType\r\n): Struct<\r\n TFields & Record<TName, TTypeScriptType>,\r\n TOmitInitKey,\r\n TExtra,\r\n TPostDeserialized\r\n>;\r\n```\r\n\r\nAppends an `int64`/`uint64` field to the `Struct`. The usage is same as `uint32`/`uint32`.\r\n\r\nRequires native support for `BigInt`. Check [compatibility table](#compatibility) for more information.\r\n\r\n#### `uint8Array`/`string`\r\n\r\n```ts\r\nuint8Array<\r\n TName extends string | number | symbol,\r\n TTypeScriptType = ArrayBuffer\r\n>(\r\n name: TName,\r\n options: FixedLengthBufferLikeFieldOptions,\r\n _typescriptType?: TTypeScriptType,\r\n): Struct<\r\n TFields & Record<TName, TTypeScriptType>,\r\n TOmitInitKey,\r\n TExtra,\r\n TPostDeserialized\r\n>;\r\n\r\nuint8Array<\r\n TName extends string | number | symbol,\r\n TLengthField extends LengthField<TFields>,\r\n TOptions extends VariableLengthBufferLikeFieldOptions<TFields, TLengthField>,\r\n TTypeScriptType = ArrayBuffer,\r\n>(\r\n name: TName,\r\n options: TOptions,\r\n _typescriptType?: TTypeScriptType,\r\n): Struct<\r\n TFields & Record<TName, TTypeScriptType>,\r\n TOmitInitKey | TLengthField,\r\n TExtra,\r\n TPostDeserialized\r\n>;\r\n```\r\n\r\nAppends an `uint8Array`/`string` field to the `Struct`.\r\n\r\nThe `options` parameter defines its length, it can be in two formats:\r\n\r\n* `{ length: number }`: Presence of the `length` option indicates that it's a fixed length array.\r\n* `{ lengthField: string; lengthFieldRadix?: number }`: Presence of the `lengthField` option indicates it's a variable length array. The `lengthField` options must refers to a `number` or `string` (can't be `bigint`) typed field that's already defined in this `Struct`. If the length field is a `string`, the optional `lengthFieldRadix` option (defaults to `10`) defines the radix when converting the string to a number. When deserializing, it will use that field's value as its length. When serializing, it will write its length to that field.\r\n\r\n#### `fields`\r\n\r\n```ts\r\nfields<\r\n TOther extends Struct<any, any, any, any>\r\n>(\r\n other: TOther\r\n): Struct<\r\n TFields & TOther['fieldsType'],\r\n TOmitInitKey | TOther['omitInitType'],\r\n TExtra & TOther['extraType'],\r\n TPostDeserialized\r\n>;\r\n```\r\n\r\nMerges (flats) another `Struct`'s fields and extra fields into the current one.\r\n\r\n**Examples**\r\n\r\n1. Extending another `Struct`\r\n\r\n ```ts\r\n const MyStructV1 =\r\n new Struct()\r\n .int32('field1');\r\n\r\n const MyStructV2 =\r\n new Struct()\r\n .fields(MyStructV1)\r\n .int32('field2');\r\n\r\n const structV2 = await MyStructV2.deserialize(stream);\r\n structV2.field1; // number\r\n structV2.field2; // number\r\n // Fields are flatten\r\n ```\r\n\r\n2. Also possible in any order\r\n\r\n ```ts\r\n const MyStructV1 =\r\n new Struct()\r\n .int32('field1');\r\n\r\n const MyStructV2 =\r\n new Struct()\r\n .int32('field2')\r\n .fields(MyStructV1);\r\n\r\n const structV2 = await MyStructV2.deserialize(stream);\r\n structV2.field1; // number\r\n structV2.field2; // number\r\n // Same result as above, but serialize/deserialize order is reversed\r\n ```\r\n\r\n#### `extra`\r\n\r\n```ts\r\nextra<\r\n T extends Record<\r\n Exclude<\r\n keyof T,\r\n Exclude<\r\n keyof T,\r\n keyof TFields\r\n >\r\n >,\r\n never\r\n >\r\n>(\r\n value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>>\r\n): Struct<\r\n TFields,\r\n TInit,\r\n Overwrite<TExtra, T>,\r\n TPostDeserialized\r\n>;\r\n```\r\n\r\nAdds extra fields into the `Struct`. Extra fields will be defined on prototype of each Struct values, so they don't affect serialize and deserialize process, and deserialized fields will overwrite extra fields.\r\n\r\nMultiple calls merge all extra fields together.\r\n\r\n**Generic Parameters**\r\n\r\n1. `T`: Type of the extra fields. The scary looking generic constraint is used to forbid overwriting any already existed fields.\r\n\r\n**Parameters**\r\n\r\n1. `value`: An object containing anything you want to add to Struct values. Accessors and methods are also allowed.\r\n\r\n**Examples**\r\n\r\n1. Add an extra field\r\n\r\n ```ts\r\n const struct = new Struct()\r\n .int32('foo')\r\n .extra({\r\n bar: 'hello',\r\n });\r\n\r\n const value = await struct.deserialize(stream);\r\n value.foo; // number\r\n value.bar; // 'hello'\r\n\r\n struct.serialize({ foo: 42 }); // ok\r\n struct.serialize({ foo: 42, bar: 'hello' }); // error: 'bar' is redundant\r\n ```\r\n\r\n2. Add getters and methods. `this` in functions refers to the result object.\r\n\r\n ```ts\r\n const struct = new Struct()\r\n .int32('foo')\r\n .extra({\r\n get bar() {\r\n // `this` is the result Struct value\r\n return this.foo + 1;\r\n },\r\n logBar() {\r\n // `this` also contains other extra fields\r\n console.log(this.bar);\r\n },\r\n });\r\n\r\n const value = await struct.deserialize(stream);\r\n value.foo; // number\r\n value.bar; // number\r\n value.logBar();\r\n ```\r\n\r\n#### `postDeserialize`\r\n\r\n```ts\r\npostDeserialize(): Struct<TFields, TOmitInitKey, TExtra, undefined>;\r\n```\r\n\r\nRemove any registered post-deserialization callback.\r\n\r\n```ts\r\npostDeserialize(\r\n callback: (this: TFields, object: TFields) => never\r\n): Struct<TFields, TOmitInitKey, TExtra, never>;\r\npostDeserialize(\r\n callback: (this: TFields, object: TFields) => void\r\n): Struct<TFields, TOmitInitKey, TExtra, undefined>;\r\n```\r\n\r\nRegisters (or replaces) a custom callback to be run after deserialized.\r\n\r\n`this` in `callback`, along with the first parameter `object` will both be the deserialized Struct value.\r\n\r\nA callback returning `never` (always throws errors) will change the return type of `deserialize` to `never`.\r\n\r\nA callback returning `void` means it modify the result object in-place (or doesn't modify it at all), so `deserialize` will still return the result object.\r\n\r\n```ts\r\npostDeserialize<TPostSerialize>(\r\n callback: (this: TFields, object: TFields) => TPostSerialize\r\n): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;\r\n```\r\n\r\nRegisters (or replaces) a custom callback to be run after deserialized.\r\n\r\nA callback returning anything other than `undefined` will cause `deserialize` to return that value instead.\r\n\r\n**Generic Parameters**\r\n\r\n1. `TPostSerialize`: Type of the new result.\r\n\r\n**Parameters**\r\n\r\n1. `callback`: An function contains the custom logic to be run, optionally returns a new result. Or `undefined`, to remove any previously set `postDeserialize` callback.\r\n\r\n**Examples**\r\n\r\n1. Handle an \"error\" packet\r\n\r\n ```ts\r\n // Say your protocol have an error packet,\r\n // You want to throw a JavaScript Error when received such a packet,\r\n // But you don't want to modify all receiving path\r\n\r\n const struct = new Struct()\r\n .int32('messageLength')\r\n .string('message', { lengthField: 'messageLength' })\r\n .postDeserialize(value => {\r\n throw new Error(value.message);\r\n });\r\n ```\r\n\r\n2. Do anything you want\r\n\r\n ```ts\r\n // I think this one doesn't need any code example\r\n ```\r\n\r\n3. Replace result object\r\n\r\n ```ts\r\n const struct1 = new Struct()\r\n .int32('foo')\r\n .postDeserialize(value => {\r\n return {\r\n bar: value.foo,\r\n };\r\n });\r\n\r\n const value = await struct.deserialize(stream);\r\n value.foo // error: not exist\r\n value.bar; // number\r\n ```\r\n\r\n#### `deserialize`\r\n\r\n```ts\r\ninterface StructDeserializeStream {\r\n /**\r\n * Read data from the underlying data source.\r\n *\r\n * The stream must return exactly `length` bytes or data. If that's not possible\r\n * (due to end of file or other error condition), it must throw an error.\r\n */\r\n read(length: number): Uint8Array;\r\n}\r\n\r\ninterface StructAsyncDeserializeStream {\r\n /**\r\n * Read data from the underlying data source.\r\n *\r\n * The stream must return exactly `length` bytes or data. If that's not possible\r\n * (due to end of file or other error condition), it must throw an error.\r\n */\r\n read(length: number): Promise<Uint8Array>;\r\n}\r\n\r\ndeserialize(\r\n stream: StructDeserializeStream,\r\n): TPostDeserialized extends undefined\r\n ? Overwrite<TExtra, TValue>\r\n : TPostDeserialized\r\n>;\r\ndeserialize(\r\n stream: StructAsyncDeserializeStream,\r\n): Promise<\r\n TPostDeserialized extends undefined\r\n ? Overwrite<TExtra, TValue>\r\n : TPostDeserialized\r\n >\r\n>;\r\n```\r\n\r\nDeserialize a struct value from `stream`.\r\n\r\nIt will be synchronous (returns a value) or asynchronous (returns a `Promise`) depending on the type of `stream`.\r\n\r\nAs the signature shows, if the `postDeserialize` callback returns any value, `deserialize` will return that value instead.\r\n\r\nThe `read` method of `stream`, when being called, should returns exactly `length` bytes of data (or throw an `Error` if it can't).\r\n\r\n#### `serialize`\r\n\r\n```ts\r\nserialize(init: Evaluate<Omit<TFields, TOmitInitKey>>): Uint8Array;\r\nserialize(init: Evaluate<Omit<TFields, TOmitInitKey>>, output: Uint8Array): number;\r\n```\r\n\r\nSerialize a struct value into an `Uint8Array`.\r\n\r\nIf an `output` is given, it will serialize the struct into it, and returns the number of bytes written.\r\n\r\n## Custom field type\r\n\r\nIt's also possible to create your own field types.\r\n\r\n### `Struct#field`\r\n\r\n```ts\r\nfield<\r\n TName extends string | number | symbol,\r\n TDefinition extends StructFieldDefinition<any, any, any>\r\n>(\r\n name: TName,\r\n definition: TDefinition\r\n): Struct<\r\n TFields & Record<TName, TDefinition['TValue']>,\r\n TOmitInitKey | TDefinition['TOmitInitKey'],\r\n TExtra,\r\n TPostDeserialized\r\n>;\r\n```\r\n\r\nAppends a `StructFieldDefinition` to the `Struct`.\r\n\r\nAll built-in field type methods are actually aliases to it. For example, calling\r\n\r\n```ts\r\nstruct.int8('foo')\r\n```\r\n\r\nis same as\r\n\r\n```ts\r\nstruct.field(\r\n 'foo',\r\n new NumberFieldDefinition(\r\n NumberFieldType.Int8\r\n )\r\n)\r\n```\r\n\r\n### Relationship between types\r\n\r\nA `Struct` is a map between keys and `StructFieldDefinition`s.\r\n\r\nA `StructValue` is a map between keys and `StructFieldValue`s.\r\n\r\nA `Struct` can create (deserialize) multiple `StructValue`s with same field definitions.\r\n\r\nEach time a `Struct` deserialize, each `StructFieldDefinition` in it creates exactly one `StructFieldValue` to be put into the `StructValue`.\r\n\r\n### `StructFieldDefinition`\r\n\r\n```ts\r\nabstract class StructFieldDefinition<\r\n TOptions = void,\r\n TValue = unknown,\r\n TOmitInitKey extends PropertyKey = never,\r\n> {\r\n public readonly options: TOptions;\r\n\r\n public constructor(options: TOptions);\r\n}\r\n```\r\n\r\nA field definition defines how to deserialize a field.\r\n\r\nIt's an `abstract` class, means it can't be constructed (`new`ed) directly. It's only used as a base class for other field types.\r\n\r\n#### `TValue`/`TOmitInitKey`\r\n\r\nThese two fields provide type information to TypeScript compiler. Their values will always be `undefined`, but having correct types is enough. You don't need to touch them.\r\n\r\n#### `getSize`\r\n\r\n```ts\r\nabstract getSize(): number;\r\n```\r\n\r\nDerived classes must implement this method to return size (or minimal size if it's dynamic) of this field.\r\n\r\nActual size should be returned from `StructFieldValue#getSize`\r\n\r\n#### `create`\r\n\r\n```ts\r\nabstract create(\r\n options: Readonly<StructOptions>,\r\n struct: StructValue,\r\n value: TValue,\r\n): StructFieldValue<this>;\r\n```\r\n\r\nDerived classes must implement this method to create its own field value instance for the current definition.\r\n\r\n`Struct#serialize` will call this method, then call `StructFieldValue#serialize` to serialize one field value.\r\n\r\n#### `deserialize`\r\n\r\n```ts\r\nabstract deserialize(\r\n options: Readonly<StructOptions>,\r\n stream: StructDeserializeStream,\r\n struct: StructValue,\r\n): StructFieldValue<this>;\r\nabstract deserialize(\r\n options: Readonly<StructOptions>,\r\n stream: StructAsyncDeserializeStream,\r\n struct: StructValue,\r\n): Promise<StructFieldValue<this>>;\r\n```\r\n\r\nDerived classes must implement this method to define how to deserialize a value from `stream`.\r\n\r\nIt must be synchronous (returns a value) or asynchronous (returns a `Promise`) depending on the type of `stream`.\r\n\r\nUsually implementations should be:\r\n\r\n1. Read required bytes from `stream`\r\n2. Parse it to your type\r\n3. Pass the value into your own `create` method\r\n\r\nSometimes, extra metadata is present when deserializing, but need to be calculated when serializing, for example a UTF-8 encoded string may have different length between itself (character count) and serialized form (byte length). So `deserialize` can save those metadata on the `StructFieldValue` instance for later use.\r\n\r\n### `StructFieldValue`\r\n\r\n```ts\r\nabstract class StructFieldValue<\r\n TDefinition extends StructFieldDefinition<any, any, any>\r\n>\r\n```\r\n\r\nA field value defines how to serialize a field.\r\n\r\n#### `getSize`\r\n\r\n```ts\r\ngetSize(): number;\r\n```\r\n\r\nGets size of this field. By default, it returns its `definition`'s size.\r\n\r\nIf this field's size can change based on some criteria, one must override `getSize` to return its actual size.\r\n\r\n#### `get`/`set`\r\n\r\n```ts\r\nget(): TDefinition['TValue'];\r\nset(value: TDefinition['TValue']): void;\r\n```\r\n\r\nDefines how to get or set this field's value. By default, it reads/writes its `value` field.\r\n\r\nIf one needs to manipulate other states when getting/setting values, they can override these methods.\r\n\r\n#### `serialize`\r\n\r\n```ts\r\nabstract serialize(\r\n dataView: DataView,\r\n offset: number\r\n): void;\r\n```\r\n\r\nDerived classes must implement this method to serialize current value into `dataView`, from `offset`. It must not write more bytes than what its `getSize` returned.\r\n" | ||
"readme": "# @yume-chan/struct\n\n<!--\ncspell: ignore Codecov\ncspell: ignore uint8arraystring\n-->\n\n![license](https://img.shields.io/npm/l/@yume-chan/struct)\n![npm type definitions](https://img.shields.io/npm/types/@yume-chan/struct)\n[![npm version](https://img.shields.io/npm/v/@yume-chan/struct)](https://www.npmjs.com/package/@yume-chan/struct)\n![npm bundle size](https://img.shields.io/bundlephobia/min/@yume-chan/struct)\n![Codecov](https://img.shields.io/codecov/c/github/yume-chan/ya-webadb?flag=struct&token=2fU3Cx2Edq)\n\nA C-style structure serializer and deserializer. Written in TypeScript and highly takes advantage of its type system.\n\n**WARNING:** The public API is UNSTABLE. If you have any questions, please open an issue.\n\n## Installation\n\n```sh\n$ npm i @yume-chan/struct\n```\n\n## Quick Start\n\n```ts\nimport Struct from '@yume-chan/struct';\n\nconst MyStruct =\n new Struct({ littleEndian: true })\n .int8('foo')\n .int64('bar')\n .int32('bazLength')\n .string('baz', { lengthField: 'bazLength' });\n\nconst value = await MyStruct.deserialize(stream);\nvalue.foo // number\nvalue.bar // bigint\nvalue.bazLength // number\nvalue.baz // string\n\nconst buffer = MyStruct.serialize({\n foo: 42,\n bar: 42n,\n // `bazLength` automatically set to `baz`'s byte length\n baz: 'Hello, World!',\n});\n```\n\n<!-- cspell: disable -->\n\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Compatibility](#compatibility)\n - [Basic usage](#basic-usage)\n - [`int64`/`uint64`](#int64uint64)\n - [`string`](#string)\n- [API](#api)\n - [`placeholder`](#placeholder)\n - [`Struct`](#struct)\n - [`int8`/`uint8`/`int16`/`uint16`/`int32`/`uint32`](#int8uint8int16uint16int32uint32)\n - [`int64`/`uint64`](#int64uint64-1)\n - [`uint8Array`/`string`](#uint8arraystring)\n - [`fields`](#fields)\n - [`extra`](#extra)\n - [`postDeserialize`](#postdeserialize)\n - [`deserialize`](#deserialize)\n - [`serialize`](#serialize)\n- [Custom field type](#custom-field-type)\n - [`Struct#field`](#structfield)\n - [Relationship between types](#relationship-between-types)\n - [`StructFieldDefinition`](#structfielddefinition)\n - [`TValue`/`TOmitInitKey`](#tvaluetomitinitkey)\n - [`getSize`](#getsize)\n - [`create`](#create)\n - [`deserialize`](#deserialize-1)\n - [`StructFieldValue`](#structfieldvalue)\n - [`getSize`](#getsize-1)\n - [`get`/`set`](#getset)\n - [`serialize`](#serialize-1)\n\n<!-- cspell: enable -->\n\n## Compatibility\n\nHere is a list of features, their used APIs, and their compatibilities. If an optional feature is not actually used, its requirements can be ignored.\n\nSome features can be polyfilled to support older runtimes, but this library doesn't ship with any polyfills.\n\n### Basic usage\n\n| API | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |\n| -------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------- |\n| [`Promise`][MDN_Promise] | 32 | 12 | 29 | No | 8 | 0.12 |\n| [`ArrayBuffer`][MDN_ArrayBuffer] | 7 | 12 | 4 | 10 | 5.1 | 0.10 |\n| [`Uint8Array`][MDN_Uint8Array] | 7 | 12 | 4 | 10 | 5.1 | 0.10 |\n| [`DataView`][MDN_DataView] | 9 | 12 | 15 | 10 | 5.1 | 0.10 |\n| *Overall* | 32 | 12 | 29 | No | 8 | 0.12 |\n\n### [`int64`/`uint64`](#int64uint64-1)\n\n| API | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |\n| ---------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------- |\n| [`BigInt`][MDN_BigInt]<sup>1</sup> | 67 | 79 | 68 | No | 14 | 10.4 |\n\n<sup>1</sup> Can't be polyfilled\n\n### [`string`](#uint8arraystring)\n\n| API | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |\n| -------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------------------- |\n| [`TextEncoder`][MDN_TextEncoder] | 38 | 79 | 19 | No | 10.1 | 8.3<sup>1</sup>, 11 |\n\n<sup>1</sup> `TextEncoder` and `TextDecoder` are only available in `util` module. Need to be assigned to `globalThis`.\n\n[MDN_Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise\n[MDN_ArrayBuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer\n[MDN_Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array\n[MDN_DataView]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView\n[MDN_BigInt]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt\n[MDN_DataView]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView\n[MDN_TextEncoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder\n\n## API\n\n### `placeholder`\n\n```ts\nfunction placeholder<T>(): T {\n return undefined as unknown as T;\n}\n```\n\nReturns a (fake) value of the given type. It's only useful in TypeScript, if you are using JavaScript, you shouldn't care about it.\n\nMany methods in this library have multiple generic parameters, but TypeScript only allows users to specify none (let TypeScript inference all of them from arguments), or all generic arguments. ([Microsoft/TypeScript#26242](https://github.com/microsoft/TypeScript/issues/26242))\n\n<details>\n<summary>Detail explanation (click to expand)</summary>\n\nWhen you have a generic method, where half generic parameters can be inferred.\n\n```ts\ndeclare function fn<A, B>(a: A): [A, B];\nfn(42); // Expected 2 type arguments, but got 1. ts(2558)\n```\n\nRather than force users repeat the type `A`, I declare a parameter for `B`.\n\n```ts\ndeclare function fn2<A, B>(a: A, b: B): [A, B];\n```\n\nI don't really need a value of type `B`, I only require its type information\n\n```ts\nfn2(42, placeholder<boolean>()) // fn2<number, boolean>\n```\n</details>\n\nTo workaround this issue, these methods have an extra `_typescriptType` parameter, to let you specify a generic parameter, without passing all other generic arguments manually. The actual value of `_typescriptType` argument is never used, so you can pass any value, as long as it has the correct type, including values produced by this `placeholder` method.\n\n**With that said, I don't expect you to specify any generic arguments manually when using this library.**\n\n### `Struct`\n\n```ts\nclass Struct<\n TFields extends object = {},\n TOmitInitKey extends string | number | symbol = never,\n TExtra extends object = {},\n TPostDeserialized = undefined\n> {\n public constructor(options: Partial<StructOptions> = StructDefaultOptions);\n}\n```\n\nCreates a new structure definition.\n\n<details>\n<summary>Generic parameters (click to expand)</summary>\n\nThis information was added to help you understand how does it work. These are considered as \"internal state\" so don't specify them manually.\n\n1. `TFields`: Type of the Struct value. Modified when new fields are added.\n2. `TOmitInitKey`: When serializing a structure containing variable length buffers, the length field can be calculate from the buffer field, so they doesn't need to be provided explicitly.\n3. `TExtra`: Type of extra fields. Modified when `extra` is called.\n4. `TPostDeserialized`: State of the `postDeserialize` function. Modified when `postDeserialize` is called. Affects return type of `deserialize`\n</details>\n\n**Parameters**\n\n1. `options`:\n * `littleEndian:boolean = false`: Whether all multi-byte fields in this struct are [little-endian encoded][Wikipeida_Endianess].\n\n[Wikipeida_Endianess]: https://en.wikipedia.org/wiki/Endianness\n\n#### `int8`/`uint8`/`int16`/`uint16`/`int32`/`uint32`\n\n```ts\nint32<\n TName extends string | number | symbol,\n TTypeScriptType = number\n>(\n name: TName,\n _typescriptType?: TTypeScriptType\n): Struct<\n TFields & Record<TName, TTypeScriptType>,\n TOmitInitKey,\n TExtra,\n TPostDeserialized\n>;\n```\n\nAppends an `int8`/`uint8`/`int16`/`uint16`/`int32`/`uint32` field to the `Struct`.\n\n<details>\n<summary>Generic parameters (click to expand)</summary>\n\n1. `TName`: Literal type of the field's name.\n2. `TTypeScriptType = number`: Type of the field in the result object. For example you can declare it as a number literal type, or some enum type.\n</details>\n\n**Parameters**\n\n1. `name`: (Required) Field name. Must be a string literal.\n2. `_typescriptType`: Set field's type. See examples below.\n\n**Note**\n\nThere is no generic constraints on the `TTypeScriptType`, because TypeScript doesn't allow casting enum types to `number`.\n\nSo it's technically possible to pass in an incompatible type (e.g. `string`). But obviously, it's a bad idea.\n\n**Examples**\n\n1. Append an `int32` field named `foo`\n\n ```ts\n const struct = new Struct()\n .int32('foo');\n\n const value = await struct.deserialize(stream);\n value.foo; // number\n\n struct.serialize({ }) // error: 'foo' is required\n struct.serialize({ foo: 'bar' }) // error: 'foo' must be a number\n struct.serialize({ foo: 42 }) // ok\n ```\n\n2. Set fields' type (can be used with [`placeholder` method](#placeholder))\n\n ```ts\n enum MyEnum {\n a,\n b,\n }\n\n const struct = new Struct()\n .int32('foo', placeholder<MyEnum>())\n .int32('bar', MyEnum.a as const);\n\n const value = await struct.deserialize(stream);\n value.foo; // MyEnum\n value.bar; // MyEnum.a\n\n struct.serialize({ foo: 42, bar: MyEnum.a }); // error: 'foo' must be of type `MyEnum`\n struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }); // error: 'bar' must be of type `MyEnum.a`\n struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }); // ok\n ```\n\n#### `int64`/`uint64`\n\n```ts\nint64<\n TName extends string | number | symbol,\n TTypeScriptType = bigint\n>(\n name: TName,\n _typescriptType?: TTypeScriptType\n): Struct<\n TFields & Record<TName, TTypeScriptType>,\n TOmitInitKey,\n TExtra,\n TPostDeserialized\n>;\n```\n\nAppends an `int64`/`uint64` field to the `Struct`. The usage is same as `uint32`/`uint32`.\n\nRequires native support for `BigInt`. Check [compatibility table](#compatibility) for more information.\n\n#### `uint8Array`/`string`\n\n```ts\nuint8Array<\n TName extends string | number | symbol,\n TTypeScriptType = ArrayBuffer\n>(\n name: TName,\n options: FixedLengthBufferLikeFieldOptions,\n _typescriptType?: TTypeScriptType,\n): Struct<\n TFields & Record<TName, TTypeScriptType>,\n TOmitInitKey,\n TExtra,\n TPostDeserialized\n>;\n\nuint8Array<\n TName extends string | number | symbol,\n TLengthField extends LengthField<TFields>,\n TOptions extends VariableLengthBufferLikeFieldOptions<TFields, TLengthField>,\n TTypeScriptType = ArrayBuffer,\n>(\n name: TName,\n options: TOptions,\n _typescriptType?: TTypeScriptType,\n): Struct<\n TFields & Record<TName, TTypeScriptType>,\n TOmitInitKey | TLengthField,\n TExtra,\n TPostDeserialized\n>;\n```\n\nAppends an `uint8Array`/`string` field to the `Struct`.\n\nThe `options` parameter defines its length, it can be in two formats:\n\n* `{ length: number }`: Presence of the `length` option indicates that it's a fixed length array.\n* `{ lengthField: string; lengthFieldRadix?: number }`: Presence of the `lengthField` option indicates it's a variable length array. The `lengthField` options must refers to a `number` or `string` (can't be `bigint`) typed field that's already defined in this `Struct`. If the length field is a `string`, the optional `lengthFieldRadix` option (defaults to `10`) defines the radix when converting the string to a number. When deserializing, it will use that field's value as its length. When serializing, it will write its length to that field.\n\n#### `fields`\n\n```ts\nfields<\n TOther extends Struct<any, any, any, any>\n>(\n other: TOther\n): Struct<\n TFields & TOther['fieldsType'],\n TOmitInitKey | TOther['omitInitType'],\n TExtra & TOther['extraType'],\n TPostDeserialized\n>;\n```\n\nMerges (flats) another `Struct`'s fields and extra fields into the current one.\n\n**Examples**\n\n1. Extending another `Struct`\n\n ```ts\n const MyStructV1 =\n new Struct()\n .int32('field1');\n\n const MyStructV2 =\n new Struct()\n .fields(MyStructV1)\n .int32('field2');\n\n const structV2 = await MyStructV2.deserialize(stream);\n structV2.field1; // number\n structV2.field2; // number\n // Fields are flatten\n ```\n\n2. Also possible in any order\n\n ```ts\n const MyStructV1 =\n new Struct()\n .int32('field1');\n\n const MyStructV2 =\n new Struct()\n .int32('field2')\n .fields(MyStructV1);\n\n const structV2 = await MyStructV2.deserialize(stream);\n structV2.field1; // number\n structV2.field2; // number\n // Same result as above, but serialize/deserialize order is reversed\n ```\n\n#### `extra`\n\n```ts\nextra<\n T extends Record<\n Exclude<\n keyof T,\n Exclude<\n keyof T,\n keyof TFields\n >\n >,\n never\n >\n>(\n value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>>\n): Struct<\n TFields,\n TInit,\n Overwrite<TExtra, T>,\n TPostDeserialized\n>;\n```\n\nAdds extra fields into the `Struct`. Extra fields will be defined on prototype of each Struct values, so they don't affect serialize and deserialize process, and deserialized fields will overwrite extra fields.\n\nMultiple calls merge all extra fields together.\n\n**Generic Parameters**\n\n1. `T`: Type of the extra fields. The scary looking generic constraint is used to forbid overwriting any already existed fields.\n\n**Parameters**\n\n1. `value`: An object containing anything you want to add to Struct values. Accessors and methods are also allowed.\n\n**Examples**\n\n1. Add an extra field\n\n ```ts\n const struct = new Struct()\n .int32('foo')\n .extra({\n bar: 'hello',\n });\n\n const value = await struct.deserialize(stream);\n value.foo; // number\n value.bar; // 'hello'\n\n struct.serialize({ foo: 42 }); // ok\n struct.serialize({ foo: 42, bar: 'hello' }); // error: 'bar' is redundant\n ```\n\n2. Add getters and methods. `this` in functions refers to the result object.\n\n ```ts\n const struct = new Struct()\n .int32('foo')\n .extra({\n get bar() {\n // `this` is the result Struct value\n return this.foo + 1;\n },\n logBar() {\n // `this` also contains other extra fields\n console.log(this.bar);\n },\n });\n\n const value = await struct.deserialize(stream);\n value.foo; // number\n value.bar; // number\n value.logBar();\n ```\n\n#### `postDeserialize`\n\n```ts\npostDeserialize(): Struct<TFields, TOmitInitKey, TExtra, undefined>;\n```\n\nRemove any registered post-deserialization callback.\n\n```ts\npostDeserialize(\n callback: (this: TFields, object: TFields) => never\n): Struct<TFields, TOmitInitKey, TExtra, never>;\npostDeserialize(\n callback: (this: TFields, object: TFields) => void\n): Struct<TFields, TOmitInitKey, TExtra, undefined>;\n```\n\nRegisters (or replaces) a custom callback to be run after deserialized.\n\n`this` in `callback`, along with the first parameter `object` will both be the deserialized Struct value.\n\nA callback returning `never` (always throws errors) will change the return type of `deserialize` to `never`.\n\nA callback returning `void` means it modify the result object in-place (or doesn't modify it at all), so `deserialize` will still return the result object.\n\n```ts\npostDeserialize<TPostSerialize>(\n callback: (this: TFields, object: TFields) => TPostSerialize\n): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;\n```\n\nRegisters (or replaces) a custom callback to be run after deserialized.\n\nA callback returning anything other than `undefined` will cause `deserialize` to return that value instead.\n\n**Generic Parameters**\n\n1. `TPostSerialize`: Type of the new result.\n\n**Parameters**\n\n1. `callback`: An function contains the custom logic to be run, optionally returns a new result. Or `undefined`, to remove any previously set `postDeserialize` callback.\n\n**Examples**\n\n1. Handle an \"error\" packet\n\n ```ts\n // Say your protocol have an error packet,\n // You want to throw a JavaScript Error when received such a packet,\n // But you don't want to modify all receiving path\n\n const struct = new Struct()\n .int32('messageLength')\n .string('message', { lengthField: 'messageLength' })\n .postDeserialize(value => {\n throw new Error(value.message);\n });\n ```\n\n2. Do anything you want\n\n ```ts\n // I think this one doesn't need any code example\n ```\n\n3. Replace result object\n\n ```ts\n const struct1 = new Struct()\n .int32('foo')\n .postDeserialize(value => {\n return {\n bar: value.foo,\n };\n });\n\n const value = await struct.deserialize(stream);\n value.foo // error: not exist\n value.bar; // number\n ```\n\n#### `deserialize`\n\n```ts\ninterface StructDeserializeStream {\n /**\n * Read data from the underlying data source.\n *\n * The stream must return exactly `length` bytes or data. If that's not possible\n * (due to end of file or other error condition), it must throw an error.\n */\n read(length: number): Uint8Array;\n}\n\ninterface StructAsyncDeserializeStream {\n /**\n * Read data from the underlying data source.\n *\n * The stream must return exactly `length` bytes or data. If that's not possible\n * (due to end of file or other error condition), it must throw an error.\n */\n read(length: number): Promise<Uint8Array>;\n}\n\ndeserialize(\n stream: StructDeserializeStream,\n): TPostDeserialized extends undefined\n ? Overwrite<TExtra, TValue>\n : TPostDeserialized\n>;\ndeserialize(\n stream: StructAsyncDeserializeStream,\n): Promise<\n TPostDeserialized extends undefined\n ? Overwrite<TExtra, TValue>\n : TPostDeserialized\n >\n>;\n```\n\nDeserialize a struct value from `stream`.\n\nIt will be synchronous (returns a value) or asynchronous (returns a `Promise`) depending on the type of `stream`.\n\nAs the signature shows, if the `postDeserialize` callback returns any value, `deserialize` will return that value instead.\n\nThe `read` method of `stream`, when being called, should returns exactly `length` bytes of data (or throw an `Error` if it can't).\n\n#### `serialize`\n\n```ts\nserialize(init: Evaluate<Omit<TFields, TOmitInitKey>>): Uint8Array;\nserialize(init: Evaluate<Omit<TFields, TOmitInitKey>>, output: Uint8Array): number;\n```\n\nSerialize a struct value into an `Uint8Array`.\n\nIf an `output` is given, it will serialize the struct into it, and returns the number of bytes written.\n\n## Custom field type\n\nIt's also possible to create your own field types.\n\n### `Struct#field`\n\n```ts\nfield<\n TName extends string | number | symbol,\n TDefinition extends StructFieldDefinition<any, any, any>\n>(\n name: TName,\n definition: TDefinition\n): Struct<\n TFields & Record<TName, TDefinition['TValue']>,\n TOmitInitKey | TDefinition['TOmitInitKey'],\n TExtra,\n TPostDeserialized\n>;\n```\n\nAppends a `StructFieldDefinition` to the `Struct`.\n\nAll built-in field type methods are actually aliases to it. For example, calling\n\n```ts\nstruct.int8('foo')\n```\n\nis same as\n\n```ts\nstruct.field(\n 'foo',\n new NumberFieldDefinition(\n NumberFieldType.Int8\n )\n)\n```\n\n### Relationship between types\n\n* `StructFieldValue`: Contains value of a field, with optional metadata and accessor methods.\n* `StructFieldDefinition`: Definition of a field, can deserialize `StructFieldValue`s from a stream or create them from exist values.\n* `StructValue`: A map between field names and `StructFieldValue`s.\n* `Struct`: Definiton of a struct, a map between field names and `StructFieldDefintion`s. May contain extra metadata.\n* Result of `Struct#deserialize()`: A map between field names and results of `StructFieldValue#get()`.\n\n### `StructFieldDefinition`\n\n```ts\nabstract class StructFieldDefinition<\n TOptions = void,\n TValue = unknown,\n TOmitInitKey extends PropertyKey = never,\n> {\n public readonly options: TOptions;\n\n public constructor(options: TOptions);\n}\n```\n\nA field definition defines how to deserialize a field.\n\nIt's an `abstract` class, means it can't be constructed (`new`ed) directly. It's only used as a base class for other field types.\n\n#### `TValue`/`TOmitInitKey`\n\nThese two fields provide type information to TypeScript compiler. Their values will always be `undefined`, but having correct types is enough. You don't need to touch them.\n\n#### `getSize`\n\n```ts\nabstract getSize(): number;\n```\n\nDerived classes must implement this method to return size (or minimal size if it's dynamic) of this field.\n\nActual size should be returned from `StructFieldValue#getSize`\n\n#### `create`\n\n```ts\nabstract create(\n options: Readonly<StructOptions>,\n struct: StructValue,\n value: TValue,\n): StructFieldValue<this>;\n```\n\nDerived classes must implement this method to create its own field value instance for the current definition.\n\n`Struct#serialize` will call this method, then call `StructFieldValue#serialize` to serialize one field value.\n\n#### `deserialize`\n\n```ts\nabstract deserialize(\n options: Readonly<StructOptions>,\n stream: StructDeserializeStream,\n struct: StructValue,\n): StructFieldValue<this>;\nabstract deserialize(\n options: Readonly<StructOptions>,\n stream: StructAsyncDeserializeStream,\n struct: StructValue,\n): Promise<StructFieldValue<this>>;\n```\n\nDerived classes must implement this method to define how to deserialize a value from `stream`.\n\nIt must be synchronous (returns a value) or asynchronous (returns a `Promise`) depending on the type of `stream`.\n\nUsually implementations should be:\n\n1. Read required bytes from `stream`\n2. Parse it to your type\n3. Pass the value into your own `create` method\n\nSometimes, extra metadata is present when deserializing, but need to be calculated when serializing, for example a UTF-8 encoded string may have different length between itself (character count) and serialized form (byte length). So `deserialize` can save those metadata on the `StructFieldValue` instance for later use.\n\n### `StructFieldValue`\n\n```ts\nabstract class StructFieldValue<\n TDefinition extends StructFieldDefinition<any, any, any>\n>\n```\n\nA field value defines how to serialize a field.\n\n#### `getSize`\n\n```ts\ngetSize(): number;\n```\n\nGets size of this field. By default, it returns its `definition`'s size.\n\nIf this field's size can change based on some criteria, one must override `getSize` to return its actual size.\n\n#### `get`/`set`\n\n```ts\nget(): TDefinition['TValue'];\nset(value: TDefinition['TValue']): void;\n```\n\nDefines how to get or set this field's value. By default, it reads/writes its `value` field.\n\nIf one needs to manipulate other states when getting/setting values, they can override these methods.\n\n#### `serialize`\n\n```ts\nabstract serialize(\n dataView: DataView,\n offset: number\n): void;\n```\n\nDerived classes must implement this method to serialize current value into `dataView`, from `offset`. It must not write more bytes than what its `getSize` returned.\n" | ||
} |
@@ -648,10 +648,8 @@ # @yume-chan/struct | ||
A `Struct` is a map between keys and `StructFieldDefinition`s. | ||
* `StructFieldValue`: Contains value of a field, with optional metadata and accessor methods. | ||
* `StructFieldDefinition`: Definition of a field, can deserialize `StructFieldValue`s from a stream or create them from exist values. | ||
* `StructValue`: A map between field names and `StructFieldValue`s. | ||
* `Struct`: Definiton of a struct, a map between field names and `StructFieldDefintion`s. May contain extra metadata. | ||
* Result of `Struct#deserialize()`: A map between field names and results of `StructFieldValue#get()`. | ||
A `StructValue` is a map between keys and `StructFieldValue`s. | ||
A `Struct` can create (deserialize) multiple `StructValue`s with same field definitions. | ||
Each time a `Struct` deserialize, each `StructFieldDefinition` in it creates exactly one `StructFieldValue` to be put into the `StructValue`. | ||
### `StructFieldDefinition` | ||
@@ -658,0 +656,0 @@ |
@@ -1,3 +0,1 @@ | ||
// cspell: ignore Syncbird | ||
import type { StructAsyncDeserializeStream, StructDeserializeStream } from "./stream.js"; | ||
@@ -50,3 +48,3 @@ import type { StructFieldValue } from "./field-value.js"; | ||
options: Readonly<StructOptions>, | ||
struct: StructValue, | ||
structValue: StructValue, | ||
value: TValue, | ||
@@ -59,3 +57,3 @@ ): StructFieldValue<this>; | ||
* | ||
* `Syncbird` can be used to make the implementation easier. | ||
* `SyncPromise` can be used to simplify implementation. | ||
*/ | ||
@@ -65,3 +63,3 @@ public abstract deserialize( | ||
stream: StructDeserializeStream, | ||
struct: StructValue, | ||
structValue: StructValue, | ||
): StructFieldValue<this>; | ||
@@ -68,0 +66,0 @@ public abstract deserialize( |
@@ -23,2 +23,7 @@ import type { StructFieldDefinition } from "./definition.js"; | ||
public get hasCustomAccessors(): boolean { | ||
return this.get !== StructFieldValue.prototype.get || | ||
this.set !== StructFieldValue.prototype.set; | ||
} | ||
protected value: TDefinition['TValue']; | ||
@@ -25,0 +30,0 @@ |
@@ -0,0 +0,0 @@ export * from './definition.js'; |
@@ -0,0 +0,0 @@ export interface StructOptions { |
@@ -0,1 +1,3 @@ | ||
import type { ValueOrPromise } from "../utils.js"; | ||
export interface StructDeserializeStream { | ||
@@ -18,3 +20,3 @@ /** | ||
*/ | ||
read(length: number): Promise<Uint8Array>; | ||
read(length: number): ValueOrPromise<Uint8Array>; | ||
} |
import type { StructFieldValue } from "./field-value.js"; | ||
export const STRUCT_VALUE_SYMBOL = Symbol("struct-value"); | ||
/** | ||
@@ -12,19 +14,39 @@ * A struct value is a map between keys in a struct and their field values. | ||
*/ | ||
public readonly value: Record<PropertyKey, unknown> = {}; | ||
public readonly value: Record<PropertyKey, unknown>; | ||
public constructor(prototype: any) { | ||
// PERF: `Object.create(extra)` is 50% faster | ||
// than `Object.defineProperties(this.value, extra)` | ||
this.value = Object.create(prototype); | ||
// PERF: `Object.defineProperty` is slow | ||
// but we need it to be non-enumerable | ||
Object.defineProperty( | ||
this.value, | ||
STRUCT_VALUE_SYMBOL, | ||
{ enumerable: false, value: this } | ||
); | ||
} | ||
/** | ||
* Sets a `StructFieldValue` for `key` | ||
* | ||
* @param key The field name | ||
* @param value The associated `StructFieldValue` | ||
* @param name The field name | ||
* @param fieldValue The associated `StructFieldValue` | ||
*/ | ||
public set(key: PropertyKey, value: StructFieldValue): void { | ||
this.fieldValues[key] = value; | ||
public set(name: PropertyKey, fieldValue: StructFieldValue): void { | ||
this.fieldValues[name] = fieldValue; | ||
Object.defineProperty(this.value, key, { | ||
configurable: true, | ||
enumerable: true, | ||
get() { return value.get(); }, | ||
set(v) { value.set(v); }, | ||
}); | ||
// PERF: `Object.defineProperty` is slow | ||
// use normal property when possible | ||
if (fieldValue.hasCustomAccessors) { | ||
Object.defineProperty(this.value, name, { | ||
configurable: true, | ||
enumerable: true, | ||
get() { return fieldValue.get(); }, | ||
set(v) { fieldValue.set(v); }, | ||
}); | ||
} else { | ||
this.value[name] = fieldValue.get(); | ||
} | ||
} | ||
@@ -35,7 +57,7 @@ | ||
* | ||
* @param key The field name | ||
* @param name The field name | ||
*/ | ||
public get(key: PropertyKey): StructFieldValue { | ||
return this.fieldValues[key]!; | ||
public get(name: PropertyKey): StructFieldValue { | ||
return this.fieldValues[name]!; | ||
} | ||
} |
@@ -0,0 +0,0 @@ declare global { |
@@ -1,6 +0,4 @@ | ||
// cspell: ignore Syncbird | ||
import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic/index.js'; | ||
import { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, STRUCT_VALUE_SYMBOL } from './basic/index.js'; | ||
import { StructDefaultOptions, StructValue } from './basic/index.js'; | ||
import { Syncbird } from "./syncbird.js"; | ||
import { SyncPromise } from "./sync-promise.js"; | ||
import { BigIntFieldDefinition, BigIntFieldType, BufferFieldSubType, FixedLengthBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringBufferFieldSubType, Uint8ArrayBufferFieldSubType, VariableLengthBufferLikeFieldDefinition, type FixedLengthBufferLikeFieldOptions, type LengthField, type VariableLengthBufferLikeFieldOptions } from './types/index.js'; | ||
@@ -55,3 +53,3 @@ import type { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils.js"; | ||
* @param options Fixed-length array options | ||
* @param typescriptType Type of the field in TypeScript. | ||
* @param typeScriptType Type of the field in TypeScript. | ||
* For example, if this field is a string, you can declare it as a string enum or literal union. | ||
@@ -67,3 +65,3 @@ */ | ||
options: FixedLengthBufferLikeFieldOptions, | ||
typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
): AddFieldDescriptor< | ||
@@ -93,3 +91,3 @@ TFields, | ||
options: TOptions, | ||
typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
): AddFieldDescriptor< | ||
@@ -124,3 +122,3 @@ TFields, | ||
options: FixedLengthBufferLikeFieldOptions, | ||
typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
): AddFieldDescriptor< | ||
@@ -134,3 +132,4 @@ TFields, | ||
TType, | ||
FixedLengthBufferLikeFieldOptions | ||
FixedLengthBufferLikeFieldOptions, | ||
TTypeScriptType | ||
> | ||
@@ -147,3 +146,3 @@ >; | ||
options: TOptions, | ||
typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
): AddFieldDescriptor< | ||
@@ -157,3 +156,4 @@ TFields, | ||
TType, | ||
TOptions | ||
TOptions, | ||
TTypeScriptType | ||
> | ||
@@ -195,3 +195,3 @@ >; | ||
private _extra: PropertyDescriptorMap = {}; | ||
private _extra: Record<PropertyKey, unknown> = {}; | ||
@@ -251,3 +251,3 @@ private _postDeserialized?: StructPostDeserialized<any, any> | undefined; | ||
this._size += other._size; | ||
Object.assign(this._extra, other._extra); | ||
Object.defineProperties(this._extra, Object.getOwnPropertyDescriptors(other._extra)); | ||
return this as any; | ||
@@ -263,7 +263,7 @@ } | ||
type: TType, | ||
_typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
) { | ||
return this.field( | ||
name, | ||
new NumberFieldDefinition(type, _typescriptType), | ||
new NumberFieldDefinition(type, typeScriptType), | ||
); | ||
@@ -280,3 +280,3 @@ } | ||
name: TName, | ||
_typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
) { | ||
@@ -286,3 +286,3 @@ return this.number( | ||
NumberFieldType.Int8, | ||
_typescriptType | ||
typeScriptType | ||
); | ||
@@ -299,3 +299,3 @@ } | ||
name: TName, | ||
_typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
) { | ||
@@ -305,3 +305,3 @@ return this.number( | ||
NumberFieldType.Uint8, | ||
_typescriptType | ||
typeScriptType | ||
); | ||
@@ -318,3 +318,3 @@ } | ||
name: TName, | ||
_typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
) { | ||
@@ -324,3 +324,3 @@ return this.number( | ||
NumberFieldType.Int16, | ||
_typescriptType | ||
typeScriptType | ||
); | ||
@@ -337,3 +337,3 @@ } | ||
name: TName, | ||
_typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
) { | ||
@@ -343,3 +343,3 @@ return this.number( | ||
NumberFieldType.Uint16, | ||
_typescriptType | ||
typeScriptType | ||
); | ||
@@ -356,3 +356,3 @@ } | ||
name: TName, | ||
_typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
) { | ||
@@ -362,3 +362,3 @@ return this.number( | ||
NumberFieldType.Int32, | ||
_typescriptType | ||
typeScriptType | ||
); | ||
@@ -375,3 +375,3 @@ } | ||
name: TName, | ||
typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
) { | ||
@@ -381,3 +381,3 @@ return this.number( | ||
NumberFieldType.Uint32, | ||
typescriptType | ||
typeScriptType | ||
); | ||
@@ -393,7 +393,7 @@ } | ||
type: TType, | ||
_typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
) { | ||
return this.field( | ||
name, | ||
new BigIntFieldDefinition(type, _typescriptType), | ||
new BigIntFieldDefinition(type, typeScriptType), | ||
); | ||
@@ -412,3 +412,3 @@ } | ||
name: TName, | ||
_typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
) { | ||
@@ -418,3 +418,3 @@ return this.bigint( | ||
BigIntFieldType.Int64, | ||
_typescriptType | ||
typeScriptType | ||
); | ||
@@ -433,3 +433,3 @@ } | ||
name: TName, | ||
_typescriptType?: TTypeScriptType, | ||
typeScriptType?: TTypeScriptType, | ||
) { | ||
@@ -439,3 +439,3 @@ return this.bigint( | ||
BigIntFieldType.Uint64, | ||
_typescriptType | ||
typeScriptType | ||
); | ||
@@ -475,5 +475,6 @@ } | ||
name: PropertyKey, | ||
options: any | ||
options: any, | ||
typeScriptType: any, | ||
): any => { | ||
return this.arrayBufferLike(name, Uint8ArrayBufferFieldSubType.Instance, options); | ||
return this.arrayBufferLike(name, Uint8ArrayBufferFieldSubType.Instance, options, typeScriptType); | ||
}; | ||
@@ -489,5 +490,6 @@ | ||
name: PropertyKey, | ||
options: any | ||
options: any, | ||
typeScriptType: any, | ||
): any => { | ||
return this.arrayBufferLike(name, StringBufferFieldSubType.Instance, options); | ||
return this.arrayBufferLike(name, StringBufferFieldSubType.Instance, options, typeScriptType); | ||
}; | ||
@@ -520,3 +522,6 @@ | ||
> { | ||
Object.assign(this._extra, Object.getOwnPropertyDescriptors(value)); | ||
Object.defineProperties( | ||
this._extra, | ||
Object.getOwnPropertyDescriptors(value) | ||
); | ||
return this as any; | ||
@@ -571,15 +576,19 @@ } | ||
): ValueOrPromise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>> { | ||
const value = new StructValue(); | ||
Object.defineProperties(value.value, this._extra); | ||
const structValue = new StructValue(this._extra); | ||
return Syncbird | ||
.each(this._fields, ([name, definition]) => { | ||
return Syncbird.resolve( | ||
definition.deserialize(this.options, stream as any, value) | ||
).then(fieldValue => { | ||
value.set(name, fieldValue); | ||
let promise = SyncPromise.resolve(); | ||
for (const [name, definition] of this._fields) { | ||
promise = promise | ||
.then(() => | ||
definition.deserialize(this.options, stream as any, structValue) | ||
) | ||
.then(fieldValue => { | ||
structValue.set(name, fieldValue); | ||
}); | ||
}) | ||
} | ||
return promise | ||
.then(() => { | ||
const object = value.value; | ||
const object = structValue.value; | ||
@@ -604,7 +613,21 @@ // Run `postDeserialized` | ||
public serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>, output?: Uint8Array): Uint8Array | number { | ||
const value = new StructValue(); | ||
for (const [name, definition] of this._fields) { | ||
const fieldValue = definition.create(this.options, value, (init as any)[name]); | ||
value.set(name, fieldValue); | ||
let structValue: StructValue; | ||
if (STRUCT_VALUE_SYMBOL in init) { | ||
structValue = (init as any)[STRUCT_VALUE_SYMBOL]; | ||
for (const [key, value] of Object.entries(init)) { | ||
const fieldValue = structValue.get(key); | ||
if (fieldValue) { | ||
fieldValue.set(value); | ||
} | ||
} | ||
} else { | ||
structValue = new StructValue({}); | ||
for (const [name, definition] of this._fields) { | ||
const fieldValue = definition.create( | ||
this.options, | ||
structValue, | ||
(init as any)[name] | ||
); | ||
structValue.set(name, fieldValue); | ||
} | ||
} | ||
@@ -616,3 +639,3 @@ | ||
for (const [name] of this._fields) { | ||
const fieldValue = value.get(name); | ||
const fieldValue = structValue.get(name); | ||
const size = fieldValue.getSize(); | ||
@@ -619,0 +642,0 @@ fieldsInfo.push({ fieldValue, size }); |
@@ -1,6 +0,4 @@ | ||
// cspell: ignore syncbird | ||
import { getBigInt64, getBigUint64, setBigInt64, setBigUint64 } from '@yume-chan/dataview-bigint-polyfill/esm/fallback.js'; | ||
import { StructFieldDefinition, StructFieldValue, StructValue, type StructAsyncDeserializeStream, type StructDeserializeStream, type StructOptions } from "../basic/index.js"; | ||
import { Syncbird } from "../syncbird.js"; | ||
import { SyncPromise } from "../sync-promise.js"; | ||
import type { ValueOrPromise } from "../utils.js"; | ||
@@ -77,13 +75,16 @@ | ||
): ValueOrPromise<BigIntFieldValue<this>> { | ||
return Syncbird.try(() => { | ||
return stream.read(this.getSize()); | ||
}).then(array => { | ||
const view = new DataView(array.buffer, array.byteOffset, array.byteLength); | ||
const value = this.type.getter( | ||
view, | ||
0, | ||
options.littleEndian | ||
); | ||
return this.create(options, struct, value as any); | ||
}).valueOrPromise(); | ||
return SyncPromise | ||
.try(() => { | ||
return stream.read(this.getSize()); | ||
}) | ||
.then(array => { | ||
const view = new DataView(array.buffer, array.byteOffset, array.byteLength); | ||
const value = this.type.getter( | ||
view, | ||
0, | ||
options.littleEndian | ||
); | ||
return this.create(options, struct, value as any); | ||
}) | ||
.valueOrPromise(); | ||
} | ||
@@ -90,0 +91,0 @@ } |
@@ -1,5 +0,3 @@ | ||
// cspell: ignore syncbird | ||
import { StructFieldDefinition, StructFieldValue, StructValue, type StructAsyncDeserializeStream, type StructDeserializeStream, type StructOptions } from '../../basic/index.js'; | ||
import { Syncbird } from "../../syncbird.js"; | ||
import { SyncPromise } from "../../sync-promise.js"; | ||
import { decodeUtf8, encodeUtf8, type ValueOrPromise } from "../../utils.js"; | ||
@@ -41,3 +39,4 @@ | ||
/** An `BufferFieldSubType` that's actually an `Uint8Array` */ | ||
export class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array> { | ||
export class Uint8ArrayBufferFieldSubType<TTypeScriptType = Uint8Array> | ||
extends BufferFieldSubType<Uint8Array, TTypeScriptType> { | ||
public static readonly Instance = new Uint8ArrayBufferFieldSubType(); | ||
@@ -83,3 +82,3 @@ | ||
const EmptyBuffer = new Uint8Array(0); | ||
export const EMPTY_UINT8_ARRAY = new Uint8Array(0); | ||
@@ -90,5 +89,6 @@ export abstract class BufferLikeFieldDefinition< | ||
TOmitInitKey extends PropertyKey = never, | ||
TTypeScriptType = TType["TTypeScriptType"], | ||
> extends StructFieldDefinition< | ||
TOptions, | ||
TType['TTypeScriptType'], | ||
TTypeScriptType, | ||
TOmitInitKey | ||
@@ -134,13 +134,16 @@ >{ | ||
): ValueOrPromise<BufferLikeFieldValue<this>> { | ||
return Syncbird.try(() => { | ||
const size = this.getDeserializeSize(struct); | ||
if (size === 0) { | ||
return EmptyBuffer; | ||
} else { | ||
return stream.read(size); | ||
} | ||
}).then(array => { | ||
const value = this.type.toValue(array); | ||
return this.create(options, struct, value, array); | ||
}).valueOrPromise(); | ||
return SyncPromise | ||
.try(() => { | ||
const size = this.getDeserializeSize(struct); | ||
if (size === 0) { | ||
return EMPTY_UINT8_ARRAY; | ||
} else { | ||
return stream.read(size); | ||
} | ||
}) | ||
.then(array => { | ||
const value = this.type.toValue(array); | ||
return this.create(options, struct, value, array); | ||
}) | ||
.valueOrPromise(); | ||
} | ||
@@ -147,0 +150,0 @@ } |
@@ -10,5 +10,8 @@ import { BufferLikeFieldDefinition, type BufferFieldSubType } from "./base.js"; | ||
TOptions extends FixedLengthBufferLikeFieldOptions = FixedLengthBufferLikeFieldOptions, | ||
TTypeScriptType = TType["TTypeScriptType"], | ||
> extends BufferLikeFieldDefinition< | ||
TType, | ||
TOptions | ||
TOptions, | ||
never, | ||
TTypeScriptType | ||
> { | ||
@@ -15,0 +18,0 @@ public getSize(): number { |
export * from "./base.js"; | ||
export * from "./fixed-length.js"; | ||
export * from "./variable-length.js"; |
@@ -29,7 +29,9 @@ import { StructFieldValue, type StructFieldDefinition, type StructOptions, type StructValue } from '../../basic/index.js'; | ||
TType extends BufferFieldSubType = BufferFieldSubType, | ||
TOptions extends VariableLengthBufferLikeFieldOptions = VariableLengthBufferLikeFieldOptions | ||
TOptions extends VariableLengthBufferLikeFieldOptions = VariableLengthBufferLikeFieldOptions, | ||
TTypeScriptType = TType["TTypeScriptType"], | ||
> extends BufferLikeFieldDefinition< | ||
TType, | ||
TOptions, | ||
TOptions['lengthField'] | ||
TOptions['lengthField'], | ||
TTypeScriptType | ||
> { | ||
@@ -51,3 +53,3 @@ public getSize(): number { | ||
struct: StructValue, | ||
value: TType['TTypeScriptType'], | ||
value: TTypeScriptType, | ||
array?: Uint8Array | ||
@@ -54,0 +56,0 @@ ): VariableLengthBufferLikeStructFieldValue<this> { |
export * from "./bigint.js"; | ||
export * from "./buffer/index.js"; | ||
export * from "./number.js"; |
@@ -1,10 +0,18 @@ | ||
// cspell: ignore syncbird | ||
import { StructFieldDefinition, StructFieldValue, StructValue, type StructAsyncDeserializeStream, type StructDeserializeStream, type StructOptions } from '../basic/index.js'; | ||
import { Syncbird } from "../syncbird.js"; | ||
import { SyncPromise } from "../sync-promise.js"; | ||
import type { ValueOrPromise } from "../utils.js"; | ||
export type DataViewGetters = | ||
{ [TKey in keyof DataView]: TKey extends `get${string}` ? TKey : never }[keyof DataView]; | ||
type NumberTypeDeserializer = (array: Uint8Array, littleEndian: boolean) => number; | ||
const DESERIALIZERS: Record<number, NumberTypeDeserializer> = { | ||
1: (array, littleEndian) => | ||
array[0]!, | ||
2: (array, littleEndian) => | ||
((array[1]! << 8) | array[0]!) * (littleEndian as any) | | ||
((array[0]! << 8) | array[1]!) * (!littleEndian as any), | ||
4: (array, littleEndian) => | ||
((array[3]! << 24) | (array[2]! << 16) | (array[1]! << 8) | array[0]!) * (littleEndian as any) | | ||
((array[0]! << 24) | (array[1]! << 16) | (array[2]! << 8) | array[3]!) * (!littleEndian as any), | ||
}; | ||
export type DataViewSetters = | ||
@@ -16,5 +24,8 @@ { [TKey in keyof DataView]: TKey extends `set${string}` ? TKey : never }[keyof DataView]; | ||
public readonly signed: boolean; | ||
public readonly size: number; | ||
public readonly dataViewGetter: DataViewGetters; | ||
public readonly deserializer: NumberTypeDeserializer; | ||
public readonly convertSign: (value: number) => number; | ||
@@ -25,21 +36,24 @@ public readonly dataViewSetter: DataViewSetters; | ||
size: number, | ||
dataViewGetter: DataViewGetters, | ||
signed: boolean, | ||
convertSign: (value: number) => number, | ||
dataViewSetter: DataViewSetters | ||
) { | ||
this.size = size; | ||
this.dataViewGetter = dataViewGetter; | ||
this.signed = signed; | ||
this.deserializer = DESERIALIZERS[size]!; | ||
this.convertSign = convertSign; | ||
this.dataViewSetter = dataViewSetter; | ||
} | ||
public static readonly Int8 = new NumberFieldType(1, 'getInt8', 'setInt8'); | ||
public static readonly Int8 = new NumberFieldType(1, true, value => value << 24 >> 24, 'setInt8'); | ||
public static readonly Uint8 = new NumberFieldType(1, 'getUint8', 'setUint8'); | ||
public static readonly Uint8 = new NumberFieldType(1, false, value => value, 'setUint8'); | ||
public static readonly Int16 = new NumberFieldType(2, 'getInt16', 'setInt16'); | ||
public static readonly Int16 = new NumberFieldType(2, true, value => value << 16 >> 16, 'setInt16'); | ||
public static readonly Uint16 = new NumberFieldType(2, 'getUint16', 'setUint16'); | ||
public static readonly Uint16 = new NumberFieldType(2, false, value => value, 'setUint16'); | ||
public static readonly Int32 = new NumberFieldType(4, 'getInt32', 'setInt32'); | ||
public static readonly Int32 = new NumberFieldType(4, true, value => value, 'setInt32'); | ||
public static readonly Uint32 = new NumberFieldType(4, 'getUint32', 'setUint32'); | ||
public static readonly Uint32 = new NumberFieldType(4, false, value => value >>> 0, 'setUint32'); | ||
} | ||
@@ -88,3 +102,3 @@ | ||
): ValueOrPromise<NumberFieldValue<this>> { | ||
return Syncbird | ||
return SyncPromise | ||
.try(() => { | ||
@@ -94,7 +108,5 @@ return stream.read(this.getSize()); | ||
.then(array => { | ||
const view = new DataView(array.buffer, array.byteOffset, array.byteLength); | ||
const value = view[this.type.dataViewGetter]( | ||
0, | ||
options.littleEndian | ||
); | ||
let value: number; | ||
value = this.type.deserializer(array, options.littleEndian); | ||
value = this.type.convertSign(value); | ||
return this.create(options, struct, value as any); | ||
@@ -101,0 +113,0 @@ }) |
@@ -0,0 +0,0 @@ /** |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
220517
2
93
2835
6
768
+ Added@yume-chan/dataview-bigint-polyfill@0.0.16(transitive)
- Removedbluebird@^3.7.2
- Removed@yume-chan/dataview-bigint-polyfill@0.0.15(transitive)
- Removedbluebird@3.7.2(transitive)