test(struct): add more unit tests

This commit is contained in:
Simon Chan 2021-01-15 19:35:42 +08:00
parent 547e9781e7
commit b71f9714b2
26 changed files with 1151 additions and 473 deletions

View file

@ -59,5 +59,7 @@
"yume",
"zstd"
],
"editor.tabSize": 4
"editor.tabSize": 4,
"jest.rootPath": "packages/struct",
"jest.showCoverageOnLoad": true
}

View file

@ -5,4 +5,5 @@ export * from './device-view';
export * from './error-dialog';
export * from './external-link';
export * from './logger';
export * from './number-picker';
export * from './router';

View file

@ -55,11 +55,11 @@ const buffer = MyStruct.serialize({
- [`postDeserialize`](#postdeserialize)
- [Custom field type](#custom-field-type)
- [`Struct#field` method](#structfield-method)
- [`FieldDefinition`](#fielddefinition)
- [`StructFieldDefinition`](#fielddefinition)
- [`getSize`](#getsize)
- [`deserialize`](#deserialize-1)
- [`createValue`](#createvalue)
- [`FieldRuntimeValue`](#fieldruntimevalue)
- [`StructFieldValue`](#StructFieldValue)
## Compatibility
@ -562,7 +562,7 @@ This library has a plugin system to support adding fields with custom types.
```ts
field<
TName extends PropertyKey,
TDefinition extends FieldDefinition<any, any, any>
TDefinition extends StructFieldDefinition<any, any, any>
>(
name: TName,
definition: TDefinition
@ -574,21 +574,21 @@ field<
>;
```
Appends a `FieldDefinition` to the `Struct`.
Appends a `StructFieldDefinition` to the `Struct`.
All above built-in methods are alias of `field`. To add a field of a custom type, let users call `field` with your custom `FieldDefinition` implementation.
All above built-in methods are alias of `field`. To add a field of a custom type, let users call `field` with your custom `StructFieldDefinition` implementation.
### `FieldDefinition`
### `StructFieldDefinition`
```ts
abstract class FieldDefinition<TOptions = void, TValueType = unknown, TOmitInit = never> {
abstract class StructFieldDefinition<TOptions = void, TValueType = unknown, TOmitInit = never> {
readonly options: TOptions;
constructor(options: TOptions);
}
```
A `FieldDefinition` describes type, size and runtime semantics of a field.
A `StructFieldDefinition` describes type, size and runtime semantics of a field.
It's an `abstract` class, means it lacks some method implementations, so it shouldn't be constructed.
@ -600,7 +600,7 @@ abstract getSize(): number;
Returns the size (or minimal size if it's dynamic) of this field.
Actual size should been returned from `FieldRuntimeValue#getSize`
Actual size should been returned from `StructFieldValue#getSize`
#### `deserialize`
@ -609,7 +609,7 @@ abstract deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
): ValueOrPromise<FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>>;
): ValueOrPromise<StructFieldValue<StructFieldDefinition<TOptions, TValueType, TRemoveInitFields>>>;
```
Defines how to deserialize a value from `context`. Can also return a `Promise`.
@ -617,9 +617,9 @@ Defines how to deserialize a value from `context`. Can also return a `Promise`.
Usually implementations should be:
1. Somehow parse the value from `context`
2. Pass the value into `FieldDefinition#createValue`
2. Pass the value into `StructFieldDefinition#createValue`
Sometimes, some 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 `FieldRuntimeValue` instance for later use.
Sometimes, some 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.
#### `createValue`
@ -629,15 +629,15 @@ abstract createValue(
context: StructSerializationContext,
object: any,
value: TValueType,
): FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>;
): StructFieldValue<StructFieldDefinition<TOptions, TValueType, TRemoveInitFields>>;
```
Similar to `deserialize`, creates a `FieldRuntimeValue` for this instance.
Similar to `deserialize`, creates a `StructFieldValue` for this instance.
The difference is `createValue` will be called when a init value was provided to create a Struct value.
### `FieldRuntimeValue`
### `StructFieldValue`
One `FieldDefinition` instance represents one field declaration, and one `FieldRuntimeValue` instance represents one value.
One `StructFieldDefinition` instance represents one field declaration, and one `StructFieldValue` instance represents one value.
It defines how to get, set, and serialize a value.

View file

@ -1,8 +1,8 @@
import { StructDefaultOptions } from './context';
describe('Runtime', () => {
describe('StructDefaultOptions', () => {
it('should have `littleEndian` that equals to `false`', () => {
describe('StructDefaultOptions', () => {
describe('.littleEndian', () => {
it('should be `false`', () => {
expect(StructDefaultOptions).toHaveProperty('littleEndian', false);
});
});

View file

@ -1,3 +1,5 @@
import type { ValueOrPromise } from '../utils';
/**
* Context with enough methods to serialize a struct
*/
@ -23,7 +25,7 @@ export interface StructDeserializationContext extends StructSerializationContext
* Context should return exactly `length` bytes or data. If that's not possible
* (due to end of file or other error condition), it should throw an error.
*/
read(length: number): ArrayBuffer | Promise<ArrayBuffer>;
read(length: number): ValueOrPromise<ArrayBuffer>;
}
export interface StructOptions {

View file

@ -1,21 +1,23 @@
import { StructOptions, StructDeserializationContext, StructSerializationContext } from './context';
import { FieldDefinition } from './definition';
import { FieldRuntimeValue } from './runtime-value';
import { ValueOrPromise } from '../utils';
import { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
import { StructFieldDefinition } from './definition';
import { StructFieldValue } from './field-value';
import { StructValue } from './struct-value';
describe('FieldDefinition', () => {
describe('new', () => {
describe('StructFieldDefinition', () => {
describe('.constructor', () => {
it('should save the `options` parameter', () => {
class MockFieldDefinition extends FieldDefinition<number>{
class MockFieldDefinition extends StructFieldDefinition<number>{
public constructor(options: number) {
super(options);
}
public getSize(): number {
throw new Error('Method not implemented.');
}
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, object: any): FieldRuntimeValue<FieldDefinition<number, unknown, never>> | Promise<FieldRuntimeValue<FieldDefinition<number, unknown, never>>> {
public create(options: Readonly<StructOptions>, context: StructSerializationContext, object: StructValue, struct: unknown): StructFieldValue<this> {
throw new Error('Method not implemented.');
}
public createValue(options: Readonly<StructOptions>, context: StructSerializationContext, object: any, value: unknown): FieldRuntimeValue<FieldDefinition<number, unknown, never>> {
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, struct: StructValue): ValueOrPromise<StructFieldValue<this>> {
throw new Error('Method not implemented.');
}
}

View file

@ -1,41 +1,41 @@
import type { ValueOrPromise } from '../utils';
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
import type { FieldRuntimeValue } from './runtime-value';
type ValueOrPromise<T> = T | Promise<T>;
import type { StructFieldValue } from './field-value';
import type { StructValue } from './struct-value';
/**
* A field definition is a bridge between its type and its runtime value.
*
* `Struct` record fields in a list of `FieldDefinition`s.
* `Struct` record fields in a list of `StructFieldDefinition`s.
*
* When `Struct#create` or `Struct#deserialize` are called, each field's definition
* crates its own type of `FieldRuntimeValue` to manage the field value in that `Struct` instance.
* crates its own type of `StructFieldValue` to manage the field value in that `Struct` instance.
*
* One `FieldDefinition` can represents multiple similar types, just returns the corresponding
* `FieldRuntimeValue` when `createValue` was called.
* One `StructFieldDefinition` can represents multiple similar types, just returns the corresponding
* `StructFieldValue` when `createValue` was called.
*
* @template TOptions TypeScript type of this definition's `options`.
* @template TValueType TypeScript type of this field.
* @template TOmitInit Optionally remove some fields from the init type. Should be a union of string literal types.
* @template TValue TypeScript type of this field.
* @template TOmitInitKey Optionally remove some fields from the init type. Should be a union of string literal types.
*/
export abstract class FieldDefinition<
export abstract class StructFieldDefinition<
TOptions = void,
TValueType = unknown,
TOmitInit = never,
TValue = unknown,
TOmitInitKey = never,
> {
public readonly options: TOptions;
/**
* When `T` is a type initiated `FieldDefinition`,
* use `T['valueType']` to retrieve its `TValueType` type parameter.
* When `T` is a type initiated `StructFieldDefinition`,
* use `T['valueType']` to retrieve its `TValue` type parameter.
*/
public readonly valueType!: TValueType;
public readonly valueType!: TValue;
/**
* When `T` is a type initiated `FieldDefinition`,
* use `T['omitInitType']` to retrieve its `TOmitInit` type parameter.
* When `T` is a type initiated `StructFieldDefinition`,
* use `T['omitInitKeyType']` to retrieve its `TOmitInitKey` type parameter.
*/
public readonly omitInitType!: TOmitInit;
public readonly omitInitKeyType!: TOmitInitKey;
public constructor(options: TOptions) {
this.options = options;
@ -44,26 +44,26 @@ export abstract class FieldDefinition<
/**
* When implemented in derived classes, returns the size (or minimal size if it's dynamic) of this field.
*
* Actual size can be retrieved from `FieldRuntimeValue#getSize`
* Actual size can be retrieved from `StructFieldValue#getSize`
*/
public abstract getSize(): number;
/**
* When implemented in derived classes, creates a `FieldRuntimeValue` by parsing the `context`.
* When implemented in derived classes, creates a `StructFieldValue` from a given `value`.
*/
public abstract create(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: StructValue,
struct: TValue,
): StructFieldValue<this>;
/**
* When implemented in derived classes, creates a `StructFieldValue` by parsing `context`.
*/
public abstract deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
): ValueOrPromise<FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TOmitInit>>>;
/**
* When implemented in derived classes, creates a `FieldRuntimeValue` from a given `value`.
*/
public abstract createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
value: TValueType,
): FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TOmitInit>>;
struct: StructValue,
): ValueOrPromise<StructFieldValue<this>>;
}

View file

@ -1,11 +1,13 @@
import { ValueOrPromise } from '../utils';
import { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
import { FieldDefinition } from './definition';
import { FieldRuntimeValue } from './runtime-value';
import { StructFieldDefinition } from './definition';
import { StructFieldValue } from './field-value';
import { StructValue } from './struct-value';
describe('FieldRuntimeValue', () => {
describe('StructFieldValue', () => {
describe('.constructor', () => {
it('should save parameters', () => {
class MockFieldRuntimeValue extends FieldRuntimeValue {
class MockStructFieldValue extends StructFieldValue {
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
throw new Error('Method not implemented.');
}
@ -14,58 +16,58 @@ describe('FieldRuntimeValue', () => {
const definition = 1 as any;
const options = 2 as any;
const context = 3 as any;
const object = 4 as any;
const struct = 4 as any;
const value = 5 as any;
const fieldRuntimeValue = new MockFieldRuntimeValue(definition, options, context, object, value);
expect(fieldRuntimeValue).toHaveProperty('definition', definition);
expect(fieldRuntimeValue).toHaveProperty('options', options);
expect(fieldRuntimeValue).toHaveProperty('context', context);
expect(fieldRuntimeValue).toHaveProperty('object', object);
expect(fieldRuntimeValue.get()).toBe(value);
const fieldValue = new MockStructFieldValue(definition, options, context, struct, value);
expect(fieldValue).toHaveProperty('definition', definition);
expect(fieldValue).toHaveProperty('options', options);
expect(fieldValue).toHaveProperty('context', context);
expect(fieldValue).toHaveProperty('struct', struct);
expect(fieldValue.get()).toBe(value);
});
});
describe('#getSize', () => {
it('should return same value as definition\'s', () => {
class MockFieldDefinition extends FieldDefinition {
class MockFieldDefinition extends StructFieldDefinition {
public getSize(): number {
return 42;
}
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, object: any): FieldRuntimeValue<FieldDefinition<void, unknown, never>> | Promise<FieldRuntimeValue<FieldDefinition<void, unknown, never>>> {
public create(options: Readonly<StructOptions>, context: StructSerializationContext, object: StructValue, struct: unknown): StructFieldValue<this> {
throw new Error('Method not implemented.');
}
public createValue(options: Readonly<StructOptions>, context: StructSerializationContext, object: any, value: unknown): FieldRuntimeValue<FieldDefinition<void, unknown, never>> {
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, struct: StructValue): ValueOrPromise<StructFieldValue<this>> {
throw new Error('Method not implemented.');
}
}
class MockFieldRuntimeValue extends FieldRuntimeValue {
class MockStructFieldValue extends StructFieldValue {
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
throw new Error('Method not implemented.');
}
}
const fieldDefinition = new MockFieldDefinition();
const fieldRuntimeValue = new MockFieldRuntimeValue(fieldDefinition, undefined as any, undefined as any, undefined as any, undefined as any);
expect(fieldRuntimeValue.getSize()).toBe(42);
const fieldValue = new MockStructFieldValue(fieldDefinition, undefined as any, undefined as any, undefined as any, undefined as any);
expect(fieldValue.getSize()).toBe(42);
});
});
describe('#set', () => {
it('should update interval value', () => {
class MockFieldRuntimeValue extends FieldRuntimeValue {
it('should update its internal value', () => {
class MockStructFieldValue extends StructFieldValue {
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
throw new Error('Method not implemented.');
}
}
const fieldRuntimeValue = new MockFieldRuntimeValue(undefined as any, undefined as any, undefined as any, undefined as any, undefined as any);
fieldRuntimeValue.set(1);
expect(fieldRuntimeValue.get()).toBe(1);
const fieldValue = new MockStructFieldValue(undefined as any, undefined as any, undefined as any, undefined as any, undefined as any);
fieldValue.set(1);
expect(fieldValue.get()).toBe(1);
fieldRuntimeValue.set(2);
expect(fieldRuntimeValue.get()).toBe(2);
fieldValue.set(2);
expect(fieldValue.get()).toBe(2);
});
});
});

View file

@ -1,14 +1,15 @@
import type { StructOptions, StructSerializationContext } from './context';
import type { FieldDefinition } from './definition';
import type { StructFieldDefinition } from './definition';
import type { StructValue } from './struct-value';
/**
* Field runtime value manages one field of one `Struct` instance.
*
* If one `FieldDefinition` needs to change other field's semantics
* It can override other fields' `FieldRuntimeValue` in its own `FieldRuntimeValue`'s constructor
* If one `StructFieldDefinition` needs to change other field's semantics
* It can override other fields' `StructFieldValue` in its own `StructFieldValue`'s constructor
*/
export abstract class FieldRuntimeValue<
TDefinition extends FieldDefinition<any, any, any> = FieldDefinition<any, any, any>
export abstract class StructFieldValue<
TDefinition extends StructFieldDefinition<any, any, any> = StructFieldDefinition<any, any, any>
> {
/** Gets the definition associated with this runtime value */
public readonly definition: TDefinition;
@ -20,7 +21,7 @@ export abstract class FieldRuntimeValue<
public readonly context: StructSerializationContext;
/** Gets the associated `Struct` instance */
public readonly object: any;
public readonly struct: StructValue;
protected value: TDefinition['valueType'];
@ -28,13 +29,13 @@ export abstract class FieldRuntimeValue<
definition: TDefinition,
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
struct: StructValue,
value: TDefinition['valueType'],
) {
this.definition = definition;
this.options = options;
this.context = context;
this.object = object;
this.struct = struct;
this.value = value;
}

View file

@ -1,4 +1,4 @@
export * from './context';
export * from './definition';
export * from './runtime-value';
export * from './runtime-object';
export * from './field-value';
export * from './struct-value';

View file

@ -1,38 +1,68 @@
import { createRuntimeObject, getRuntimeValue, setRuntimeValue } from './runtime-object';
import { StructValue } from './struct-value';
describe('RuntimeObject', () => {
describe('createRuntimeObject', () => {
it('should create a special object', () => {
const object = createRuntimeObject();
expect(Object.getOwnPropertySymbols(object)).toHaveLength(1);
describe('StructValue', () => {
describe('.constructor', () => {
it('should create `fieldValues` and `value`', () => {
const foo = new StructValue();
const bar = new StructValue();
expect(foo).toHaveProperty('fieldValues', {});
expect(bar).toHaveProperty('fieldValues', {});
expect(foo.fieldValues).not.toBe(bar.fieldValues);
});
});
describe('getRuntimeValue', () => {
it('should return previously set value', () => {
const object = createRuntimeObject();
const field = 'foo';
const value = {} as any;
setRuntimeValue(object, field, value);
expect(getRuntimeValue(object, field)).toBe(value);
describe('#set', () => {
it('should save the `StructFieldValue`', () => {
const object = new StructValue();
const foo = 'foo';
const fooValue = {} as any;
object.set(foo, fooValue);
const bar = 'bar';
const barValue = {} as any;
object.set(bar, barValue);
expect(object.fieldValues[foo]).toBe(fooValue);
expect(object.fieldValues[bar]).toBe(barValue);
});
it('should define a property for `key`', () => {
const object = new StructValue();
const foo = 'foo';
const fooGetter = jest.fn(() => 42);
const fooSetter = jest.fn((value: number) => { });
const fooValue = { get: fooGetter, set: fooSetter } as any;
object.set(foo, fooValue);
const bar = 'bar';
const barGetter = jest.fn(() => true);
const barSetter = jest.fn((value: number) => { });
const barValue = { get: barGetter, set: barSetter } as any;
object.set(bar, barValue);
expect(object.value).toHaveProperty(foo, 42);
expect(fooGetter).toBeCalledTimes(1);
expect(barGetter).toBeCalledTimes(0);
object.value[foo] = 100;
expect(fooSetter).toBeCalledTimes(1);
expect(fooSetter).lastCalledWith(100);
expect(barSetter).toBeCalledTimes(0);
});
});
describe('setRuntimeValue', () => {
it('should define a proxy property to underlying `RuntimeValue`', () => {
const object = createRuntimeObject();
const field = 'foo';
const getter = jest.fn(() => 42);
const setter = jest.fn((value: number) => { });
const value = { get: getter, set: setter } as any;
setRuntimeValue(object, field, value);
describe('#get', () => {
it('should return previously set `StructFieldValue`', () => {
const object = new StructValue();
expect((object as any)[field]).toBe(42);
expect(getter).toBeCalledTimes(1);
const foo = 'foo';
const fooValue = {} as any;
object.set(foo, fooValue);
(object as any)[field] = 100;
expect(setter).toBeCalledTimes(1);
expect(setter).lastCalledWith(100);
expect(object.get(foo)).toBe(fooValue);
});
});
});

View file

@ -1,36 +1,45 @@
import { FieldRuntimeValue } from './runtime-value';
const RuntimeValues = Symbol('RuntimeValues');
export interface RuntimeObject {
[RuntimeValues]: Record<PropertyKey, FieldRuntimeValue>;
}
/** Creates a new runtime object that can be used with `getRuntimeValue` and `setRuntimeValue` */
export function createRuntimeObject(): RuntimeObject {
return {
[RuntimeValues]: {},
};
}
/** Gets the previously set `RuntimeValue` for specified `key` on `object` */
export function getRuntimeValue(object: RuntimeObject, key: PropertyKey): FieldRuntimeValue {
return object[RuntimeValues][key as any] as FieldRuntimeValue;
}
import type { StructFieldValue } from './field-value';
/**
* Sets the `RuntimeValue` for specified `key` on `object`,
* also sets up property accessors so reads/writes to `object`'s `key` will be forwarded to
* the underlying `RuntimeValue`
* Manages the initialization process of a struct value
*/
export function setRuntimeValue(object: RuntimeObject, key: PropertyKey, runtimeValue: FieldRuntimeValue): void {
delete (object as any)[key];
export class StructValue {
/** @internal */ readonly fieldValues: Record<PropertyKey, StructFieldValue> = {};
object[RuntimeValues][key as any] = runtimeValue;
Object.defineProperty(object, key, {
/**
* Gets the result struct value object
*/
public readonly value: Record<PropertyKey, unknown> = {};
/**
* Sets a `StructFieldValue` for `key`
*
* @param key The field name
* @param value The associated `StructFieldValue`
*/
public set(key: PropertyKey, value: StructFieldValue): void {
// TODO: TypeScript 4.2 will allow this behavior
// https://github.com/microsoft/TypeScript/pull/26797
// @ts-expect-error Type 'symbol' cannot be used as an index type. ts(2538)
this.fieldValues[key] = value;
Object.defineProperty(this.value, key, {
configurable: true,
enumerable: true,
get() { return runtimeValue.get(); },
set(value) { runtimeValue.set(value); },
get() { return value.get(); },
set(v) { value.set(v); },
});
}
/**
* Gets a previously `StructFieldValue` for `key`
*
* @param key The field name
*/
public get(key: PropertyKey): StructFieldValue {
// TODO: TypeScript 4.2 will allow this behavior
// https://github.com/microsoft/TypeScript/pull/26797
// @ts-expect-error Type 'symbol' cannot be used as an index type. ts(2538)
return this.fieldValues[key];
}
}

View file

@ -2,3 +2,4 @@ export * from './basic';
export * from './struct';
export { Struct as default } from './struct';
export * from './types';
export * from './utils';

View file

@ -1,5 +1,6 @@
import { createRuntimeObject, FieldDefinition, FieldRuntimeValue, getRuntimeValue, setRuntimeValue, StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext } from './basic';
import { ArrayBufferFieldType, ArrayBufferLikeFieldType, Evaluate, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, Identity, KeysOfType, NumberFieldDefinition, NumberFieldType, Overwrite, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types';
import { StructDefaultOptions, StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from './basic';
import { ArrayBufferFieldType, ArrayBufferLikeFieldType, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types';
import { Evaluate, Identity, KeysOfType, Overwrite } from './utils';
export interface StructLike<TValue> {
deserialize(context: StructDeserializationContext): Promise<TValue>;
@ -24,13 +25,13 @@ type AddFieldDescriptor<
TExtra extends object,
TPostDeserialized,
TFieldName extends PropertyKey,
TDefinition extends FieldDefinition<any, any, any>> =
TDefinition extends StructFieldDefinition<any, any, any>> =
Identity<Struct<
// Merge two types
// Evaluate immediately to optimize editor hover tooltip
Evaluate<TFields & Record<TFieldName, TDefinition['valueType']>>,
// Merge two `TOmitInit
TOmitInit | TDefinition['omitInitType'],
TOmitInit | TDefinition['omitInitKeyType'],
TExtra,
TPostDeserialized
>>;
@ -181,7 +182,7 @@ export class Struct<
*/
public get size() { return this._size; }
private _fields: [name: PropertyKey, definition: FieldDefinition<any, any, any>][] = [];
private _fields: [name: PropertyKey, definition: StructFieldDefinition<any, any, any>][] = [];
private _extra: PropertyDescriptorMap = {};
@ -192,11 +193,11 @@ export class Struct<
}
/**
* Appends a `FieldDefinition` to the `Struct
* Appends a `StructFieldDefinition` to the `Struct
*/
public field<
TName extends PropertyKey,
TDefinition extends FieldDefinition<any, any, any>
TDefinition extends StructFieldDefinition<any, any, any>
>(
name: TName,
definition: TDefinition,
@ -516,61 +517,55 @@ export class Struct<
return this as any;
}
private initializeObject() {
const object = createRuntimeObject();
Object.defineProperties(object, this._extra);
return object;
}
public create(init: Evaluate<Omit<TFields, TOmitInit>>, context: StructSerializationContext): Overwrite<TExtra, TFields> {
const object = this.initializeObject();
for (const [name, definition] of this._fields) {
const runtimeValue = definition.createValue(this.options, context, object, (init as any)[name]);
setRuntimeValue(object, name, runtimeValue);
}
return object as any;
private initializeStructValue() {
const value = new StructValue();
Object.defineProperties(value.value, this._extra);
return value;
}
public async deserialize(
context: StructDeserializationContext
): Promise<StructDeserializedType<TFields, TExtra, TPostDeserialized>> {
const object = this.initializeObject();
const value = this.initializeStructValue();
for (const [name, definition] of this._fields) {
const runtimeValue = await definition.deserialize(this.options, context, object);
setRuntimeValue(object, name, runtimeValue);
const fieldValue = await definition.deserialize(this.options, context, value);
value.set(name, fieldValue);
}
if (this._postDeserialized) {
const result = this._postDeserialized.call(object as TFields, object as TFields);
const result = this._postDeserialized.call(value.value as TFields, value as TFields);
if (result) {
return result;
}
}
return object as any;
return value.value as any;
}
public serialize(init: Evaluate<Omit<TFields, TOmitInit>>, context: StructSerializationContext): ArrayBuffer {
const object = this.create(init, context) as any;
const value = this.initializeStructValue();
for (const [name, definition] of this._fields) {
const fieldValue = definition.create(this.options, context, value, (init as any)[name]);
value.set(name, fieldValue);
}
let structSize = 0;
const fieldsInfo: { runtimeValue: FieldRuntimeValue, size: number; }[] = [];
const fieldsInfo: { fieldValue: StructFieldValue, size: number; }[] = [];
for (const [name] of this._fields) {
const runtimeValue = getRuntimeValue(object, name);
const size = runtimeValue.getSize();
fieldsInfo.push({ runtimeValue, size });
const fieldValue = value.get(name);
const size = fieldValue.getSize();
fieldsInfo.push({ fieldValue, size });
structSize += size;
}
const buffer = new ArrayBuffer(structSize);
const dataView = new DataView(buffer);
let offset = 0;
for (const { runtimeValue, size } of fieldsInfo) {
runtimeValue.serialize(dataView, offset, context);
for (const { fieldValue, size } of fieldsInfo) {
fieldValue.serialize(dataView, offset, context);
offset += size;
}

View file

@ -1,23 +1,24 @@
import { StructDeserializationContext, StructSerializationContext } from '../basic';
import { ArrayBufferFieldType, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer';
import { StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext, StructValue } from '../basic';
import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer';
describe('Types', () => {
describe('ArrayBufferLike', () => {
describe('ArrayBufferFieldType', () => {
it('should have a static instance', () => {
expect(ArrayBufferFieldType.instance).toBeInstanceOf(ArrayBufferFieldType);
});
it('`toArrayBuffer` should return the same `ArrayBuffer`', () => {
it('`#toArrayBuffer` should return the same `ArrayBuffer`', () => {
const arrayBuffer = new ArrayBuffer(10);
expect(ArrayBufferFieldType.instance.toArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
});
it('`fromArrayBuffer` should return the same `ArrayBuffer`', () => {
it('`#fromArrayBuffer` should return the same `ArrayBuffer`', () => {
const arrayBuffer = new ArrayBuffer(10);
expect(ArrayBufferFieldType.instance.fromArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
});
it('`getSize` should return the `byteLength` of the `ArrayBuffer`', () => {
it('`#getSize` should return the `byteLength` of the `ArrayBuffer`', () => {
const arrayBuffer = new ArrayBuffer(10);
expect(ArrayBufferFieldType.instance.getSize(arrayBuffer)).toBe(10);
});
@ -28,13 +29,13 @@ describe('Types', () => {
expect(Uint8ClampedArrayFieldType.instance).toBeInstanceOf(Uint8ClampedArrayFieldType);
});
it('`toArrayBuffer` should return its `buffer`', () => {
it('`#toArrayBuffer` should return its `buffer`', () => {
const array = new Uint8ClampedArray(10);
const buffer = array.buffer;
expect(Uint8ClampedArrayFieldType.instance.toArrayBuffer(array)).toBe(buffer);
});
it('`fromArrayBuffer` should return a view of the `ArrayBuffer`', () => {
it('`#fromArrayBuffer` should return a view of the `ArrayBuffer`', () => {
const arrayBuffer = new ArrayBuffer(10);
const array = Uint8ClampedArrayFieldType.instance.fromArrayBuffer(arrayBuffer);
expect(array).toHaveProperty('buffer', arrayBuffer);
@ -42,7 +43,7 @@ describe('Types', () => {
expect(array).toHaveProperty('byteLength', 10);
});
it('`getSize` should return the `byteLength` of the `ArrayBuffer`', () => {
it('`#getSize` should return the `byteLength` of the `Uint8ClampedArray`', () => {
const array = new Uint8ClampedArray(10);
expect(Uint8ClampedArrayFieldType.instance.getSize(array)).toBe(10);
});
@ -53,7 +54,7 @@ describe('Types', () => {
expect(StringFieldType.instance).toBeInstanceOf(StringFieldType);
});
it('`toArrayBuffer` should return the decoded string', () => {
it('`#toArrayBuffer` should return the decoded string', () => {
const text = 'foo';
const arrayBuffer = Buffer.from(text, 'utf-8');
const context: StructSerializationContext = {
@ -64,7 +65,7 @@ describe('Types', () => {
expect(StringFieldType.instance.toArrayBuffer(text, context)).toEqual(arrayBuffer);
});
it('`fromArrayBuffer` should return the encoded ArrayBuffer', () => {
it('`#fromArrayBuffer` should return the encoded ArrayBuffer', () => {
const text = 'foo';
const arrayBuffer = Buffer.from(text, 'utf-8');
const context: StructDeserializationContext = {
@ -81,8 +82,149 @@ describe('Types', () => {
expect(StringFieldType.instance.fromArrayBuffer(arrayBuffer, context)).toBe(text);
});
it('`getSize` should return -1', () => {
it('`#getSize` should return -1', () => {
expect(StringFieldType.instance.getSize()).toBe(-1);
});
});
class MockArrayBufferFieldDefinition<TType extends ArrayBufferLikeFieldType>
extends ArrayBufferLikeFieldDefinition<TType, number> {
public getSize(): number {
return this.options;
}
}
describe('ArrayBufferLikeFieldDefinition', () => {
it('should work with `ArrayBufferFieldType`', async () => {
const buffer = new ArrayBuffer(10);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const size = 10;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(read).toBeCalledTimes(1);
expect(read).toBeCalledWith(size);
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
const value = fieldValue.get();
expect(value).toBe(buffer);
});
it('should work with `Uint8ClampedArrayFieldType`', async () => {
const buffer = new ArrayBuffer(10);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const size = 10;
const definition = new MockArrayBufferFieldDefinition(Uint8ClampedArrayFieldType.instance, size);
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(read).toBeCalledTimes(1);
expect(read).toBeCalledWith(10);
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
const value = fieldValue.get();
expect(value).toBeInstanceOf(Uint8ClampedArray);
expect(value).toHaveProperty('buffer', buffer);
});
it('should work when `#getSize` returns `0`', async () => {
const buffer = new ArrayBuffer(10);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(read).toBeCalledTimes(0);
expect(fieldValue.arrayBuffer).toBeInstanceOf(ArrayBuffer);
expect(fieldValue.arrayBuffer).toHaveProperty('byteLength', 0);
const value = fieldValue.get();
expect(value).toBeInstanceOf(ArrayBuffer);
expect(value).toHaveProperty('byteLength', 0);
});
});
describe('ArrayBufferLikeFieldValue', () => {
describe('#set', () => {
it('should clear `arrayBuffer` field', async () => {
const size = 10;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const buffer = new ArrayBuffer(size);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
fieldValue.set(new ArrayBuffer(20));
expect(fieldValue).toHaveProperty('arrayBuffer', undefined);
});
});
describe('#serialize', () => {
it('should be able to serialize a deserialized value', async () => {
const size = 10;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
const sourceBuffer = sourceArray.buffer;
const read = jest.fn((length: number) => sourceBuffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
const targetArray = new Uint8Array(size);
const targetView = new DataView(targetArray.buffer);
fieldValue.serialize(targetView, 0, context);
expect(targetArray).toEqual(sourceArray);
});
it('should be able to serialize a modified value', async () => {
const size = 10;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
const sourceBuffer = sourceArray.buffer;
const read = jest.fn((length: number) => sourceBuffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
fieldValue.set(sourceArray.buffer);
const targetArray = new Uint8Array(size);
const targetView = new DataView(targetArray.buffer);
fieldValue.serialize(targetView, 0, context);
expect(targetArray).toEqual(sourceArray);
});
});
});
});
});

View file

@ -1,15 +1,15 @@
import { FieldDefinition, FieldRuntimeValue, StructDeserializationContext, StructOptions, StructSerializationContext } from '../basic';
import { StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic';
/**
* Base class for all types that
* can be converted from an ArrayBuffer when deserialized,
* and need to be converted to an ArrayBuffer when serializing
*
* @template TType The actual TypeScript type of this type
* @template TValue The actual TypeScript type of this type
* @template TTypeScriptType Optional another type (should be compatible with `TType`)
* specified by user when creating field definitions.
*/
export abstract class ArrayBufferLikeFieldType<TType = unknown, TTypeScriptType = TType> {
export abstract class ArrayBufferLikeFieldType<TValue = unknown, TTypeScriptType = TValue> {
public readonly valueType!: TTypeScriptType;
/**
@ -18,10 +18,10 @@ export abstract class ArrayBufferLikeFieldType<TType = unknown, TTypeScriptType
* This function should be "pure", i.e.,
* same `value` should always be converted to `ArrayBuffer`s that have same content.
*/
public abstract toArrayBuffer(value: TType, context: StructSerializationContext): ArrayBuffer;
public abstract toArrayBuffer(value: TValue, context: StructSerializationContext): ArrayBuffer;
/** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */
public abstract fromArrayBuffer(arrayBuffer: ArrayBuffer, context: StructDeserializationContext): TType;
public abstract fromArrayBuffer(arrayBuffer: ArrayBuffer, context: StructDeserializationContext): TValue;
/**
* When implemented in derived classes, gets the size in byte of the type-specific `value`.
@ -30,7 +30,7 @@ export abstract class ArrayBufferLikeFieldType<TType = unknown, TTypeScriptType
* implementer should returns `-1` so the caller will get its size by first converting it to
* an `ArrayBuffer` (and cache the result).
*/
public abstract getSize(value: TType): number;
public abstract getSize(value: TValue): number;
}
/** An ArrayBufferLike type that's actually an `ArrayBuffer` */
@ -54,7 +54,7 @@ export class ArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer>
}
}
/** Am ArrayBufferLike type that converts to/from the `ArrayBuffer` from/to a `Uint8ClampedArray` */
/** Am ArrayBufferLike type that converts between `ArrayBuffer` and `Uint8ClampedArray` */
export class Uint8ClampedArrayFieldType
extends ArrayBufferLikeFieldType<Uint8ClampedArray, Uint8ClampedArray> {
public static readonly instance = new Uint8ClampedArrayFieldType();
@ -76,7 +76,7 @@ export class Uint8ClampedArrayFieldType
}
}
/** Am ArrayBufferLike type that converts to/from the `ArrayBuffer` from/to a `string` */
/** Am ArrayBufferLike type that converts between `ArrayBuffer` and `string` */
export class StringFieldType<TTypeScriptType = string>
extends ArrayBufferLikeFieldType<string, TTypeScriptType> {
public static readonly instance = new StringFieldType();
@ -102,11 +102,11 @@ const EmptyArrayBuffer = new ArrayBuffer(0);
export abstract class ArrayBufferLikeFieldDefinition<
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
TOptions = void,
TOmitInit = never,
> extends FieldDefinition<
TOmitInitKey = never,
> extends StructFieldDefinition<
TOptions,
TType['valueType'],
TOmitInit
TOmitInitKey
>{
public readonly type: TType;
@ -115,16 +115,28 @@ export abstract class ArrayBufferLikeFieldDefinition<
this.type = type;
}
protected getDeserializeSize(object: any): number {
protected getDeserializeSize(struct: StructValue): number {
return this.getSize();
}
/**
* When implemented in derived classes, creates a `StructFieldValue` for the current field definition.
*/
public create(
options: Readonly<StructOptions>,
context: StructSerializationContext,
struct: StructValue,
value: TType['valueType'],
): ArrayBufferLikeFieldValue<this> {
return new ArrayBufferLikeFieldValue(this, options, context, struct, value);
}
public async deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
): Promise<ArrayBufferLikeFieldRuntimeValue<ArrayBufferLikeFieldDefinition<TType, TOptions, TOmitInit>>> {
const size = this.getDeserializeSize(object);
struct: StructValue,
): Promise<ArrayBufferLikeFieldValue<this>> {
const size = this.getDeserializeSize(struct);
let arrayBuffer: ArrayBuffer;
if (size === 0) {
@ -134,25 +146,15 @@ export abstract class ArrayBufferLikeFieldDefinition<
}
const value = this.type.fromArrayBuffer(arrayBuffer, context);
const runtimeValue = this.createValue(options, context, object, value);
runtimeValue.arrayBuffer = arrayBuffer;
return runtimeValue;
const fieldValue = this.create(options, context, struct, value);
fieldValue.arrayBuffer = arrayBuffer;
return fieldValue;
}
/**
* When implemented in derived classes, creates a `FieldRuntimeValue` for the current field definition.
*/
public abstract createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
value: TType['valueType'],
): ArrayBufferLikeFieldRuntimeValue<ArrayBufferLikeFieldDefinition<TType, TOptions, TOmitInit>>;
}
export class ArrayBufferLikeFieldRuntimeValue<
TDefinition extends ArrayBufferLikeFieldDefinition<any, any, any>,
> extends FieldRuntimeValue<TDefinition> {
export class ArrayBufferLikeFieldValue<
TDefinition extends ArrayBufferLikeFieldDefinition<ArrayBufferLikeFieldType, any, any>,
> extends StructFieldValue<TDefinition> {
public arrayBuffer: ArrayBuffer | undefined;
public set(value: TDefinition['valueType']): void {
@ -166,6 +168,6 @@ export class ArrayBufferLikeFieldRuntimeValue<
}
new Uint8Array(dataView.buffer)
.set(new Uint8Array(this.arrayBuffer!), offset);
.set(new Uint8Array(this.arrayBuffer), offset);
}
}

View file

@ -0,0 +1,13 @@
import { ArrayBufferFieldType } from './array-buffer';
import { FixedLengthArrayBufferLikeFieldDefinition } from './fixed-length-array-buffer';
describe('Types', () => {
describe('FixedLengthArrayBufferLikeFieldDefinition', () => {
describe('#getSize', () => {
it('should return size in its options', () => {
const definition = new FixedLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { length: 10 });
expect(definition.getSize()).toBe(10);
});
});
});
});

View file

@ -1,5 +1,4 @@
import { StructOptions, StructSerializationContext } from '../basic';
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldRuntimeValue, ArrayBufferLikeFieldType } from './array-buffer';
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType } from './array-buffer';
export interface FixedLengthArrayBufferLikeFieldOptions {
length: number;
@ -15,13 +14,4 @@ export class FixedLengthArrayBufferLikeFieldDefinition<
public getSize(): number {
return this.options.length;
}
public createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
value: TType['valueType']
): ArrayBufferLikeFieldRuntimeValue<FixedLengthArrayBufferLikeFieldDefinition<TType, TOptions>> {
return new ArrayBufferLikeFieldRuntimeValue(this, options, context, object, value);
}
};

View file

@ -1,5 +1,4 @@
export * from './array-buffer';
export * from './fixed-length-array-buffer';
export * from './number';
export * from './utils';
export * from './variable-length-array-buffer';

View file

@ -1,5 +1,5 @@
import { StructDefaultOptions, StructDeserializationContext, StructSerializationContext } from '../basic';
import { NumberFieldDefinition, NumberFieldRuntimeValue, NumberFieldType } from './number';
import { StructDefaultOptions, StructDeserializationContext, StructSerializationContext, StructValue } from '../basic';
import { NumberFieldDefinition, NumberFieldType } from './number';
describe('Types', () => {
describe('Number', () => {
@ -62,41 +62,20 @@ describe('Types', () => {
});
describe('NumberFieldDefinition', () => {
describe('getSize', () => {
it('should return 1 for int8', () => {
describe('#getSize', () => {
it('should return size of its type', () => {
expect(new NumberFieldDefinition(NumberFieldType.Int8).getSize()).toBe(1);
});
it('should return 1 for uint8', () => {
expect(new NumberFieldDefinition(NumberFieldType.Uint8).getSize()).toBe(1);
});
it('should return 2 for int16', () => {
expect(new NumberFieldDefinition(NumberFieldType.Int16).getSize()).toBe(2);
});
it('should return 2 for uint16', () => {
expect(new NumberFieldDefinition(NumberFieldType.Uint16).getSize()).toBe(2);
});
it('should return 4 for int32', () => {
expect(new NumberFieldDefinition(NumberFieldType.Int32).getSize()).toBe(4);
});
it('should return 4 for uint32', () => {
expect(new NumberFieldDefinition(NumberFieldType.Uint32).getSize()).toBe(4);
});
it('should return 8 for int64', () => {
expect(new NumberFieldDefinition(NumberFieldType.Int64).getSize()).toBe(8);
});
it('should return 8 for uint64', () => {
expect(new NumberFieldDefinition(NumberFieldType.Uint64).getSize()).toBe(8);
});
});
describe('deserialize', () => {
describe('#deserialize', () => {
it('should deserialize Uint8', async () => {
const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer);
const context: StructDeserializationContext = {
@ -106,10 +85,11 @@ describe('Types', () => {
};
const definition = new NumberFieldDefinition(NumberFieldType.Uint8);
const struct = new StructValue();
const value = await definition.deserialize(
StructDefaultOptions,
context,
{}
struct,
);
expect(value.get()).toBe(1);
@ -126,10 +106,11 @@ describe('Types', () => {
};
const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
const struct = new StructValue();
const value = await definition.deserialize(
StructDefaultOptions,
context,
{}
struct,
);
expect(value.get()).toBe((1 << 8) | 2);
@ -146,10 +127,11 @@ describe('Types', () => {
};
const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
const struct = new StructValue();
const value = await definition.deserialize(
{ ...StructDefaultOptions, littleEndian: true },
context,
{}
struct,
);
expect(value.get()).toBe((2 << 8) | 1);
@ -159,100 +141,121 @@ describe('Types', () => {
});
});
describe('NumberRuntimeValue', () => {
describe('getSize', () => {
it('should return 1 for int8', () => {
describe('NumberFieldValue', () => {
describe('#getSize', () => {
it('should return size of its definition', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Int8);
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
const struct = new StructValue();
expect(runtimeValue.getSize()).toBe(1);
});
expect(
new NumberFieldDefinition(NumberFieldType.Int8)
.create(
StructDefaultOptions,
context,
struct,
42,
)
.getSize()
).toBe(1);
it('should return 1 for uint8', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Uint8);
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
expect(
new NumberFieldDefinition(NumberFieldType.Uint8)
.create(
StructDefaultOptions,
context,
struct,
42,
)
.getSize()
).toBe(1);
expect(runtimeValue.getSize()).toBe(1);
});
expect(
new NumberFieldDefinition(NumberFieldType.Int16)
.create(
StructDefaultOptions,
context,
struct,
42,
)
.getSize()
).toBe(2);
it('should return 2 for int16', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Int16);
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
expect(
new NumberFieldDefinition(NumberFieldType.Uint16)
.create(
StructDefaultOptions,
context,
struct,
42,
)
.getSize()
).toBe(2);
expect(runtimeValue.getSize()).toBe(2);
});
expect(
new NumberFieldDefinition(NumberFieldType.Int32)
.create(
StructDefaultOptions,
context,
struct,
42,
)
.getSize()
).toBe(4);
it('should return 2 for uint16', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
expect(
new NumberFieldDefinition(NumberFieldType.Uint32)
.create(
StructDefaultOptions,
context,
struct,
42,
)
.getSize()
).toBe(4);
expect(runtimeValue.getSize()).toBe(2);
});
expect(
new NumberFieldDefinition(NumberFieldType.Int64)
.create(
StructDefaultOptions,
context,
struct,
BigInt(100),
)
.getSize()
).toBe(8);
it('should return 4 for int32', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Int32);
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
expect(runtimeValue.getSize()).toBe(4);
});
it('should return 4 for uint32', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Uint32);
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
expect(runtimeValue.getSize()).toBe(4);
});
it('should return 8 for int64', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Int64);
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, BigInt(42));
expect(runtimeValue.getSize()).toBe(8);
});
it('should return 8 for uint64', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Uint64);
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, BigInt(42));
expect(runtimeValue.getSize()).toBe(8);
expect(
new NumberFieldDefinition(NumberFieldType.Uint64)
.create(
StructDefaultOptions,
context,
struct,
BigInt(100),
)
.getSize()
).toBe(8);
});
});
describe('serialize', () => {
describe('#serialize', () => {
it('should serialize uint8', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Int8);
const runtimeValue = definition.createValue(StructDefaultOptions, context, {}, 42);
const struct = new StructValue();
const value = definition.create(
StructDefaultOptions,
context,
struct,
42,
);
const array = new Uint8Array(10);
const dataView = new DataView(array.buffer);
runtimeValue.serialize(dataView, 2);
value.serialize(dataView, 2);
expect(Array.from(array)).toEqual([0, 0, 42, 0, 0, 0, 0, 0, 0, 0]);
});

View file

@ -1,4 +1,4 @@
import { FieldDefinition, FieldRuntimeValue, StructDeserializationContext, StructOptions, StructSerializationContext } from '../basic';
import { StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic';
export type DataViewGetters =
{ [TKey in keyof DataView]: TKey extends `get${string}` ? TKey : never }[keyof DataView];
@ -45,7 +45,7 @@ export class NumberFieldType<TTypeScriptType extends number | bigint = number |
export class NumberFieldDefinition<
TType extends NumberFieldType = NumberFieldType,
TTypeScriptType = TType['valueType'],
> extends FieldDefinition<
> extends StructFieldDefinition<
void,
TTypeScriptType
> {
@ -60,34 +60,33 @@ export class NumberFieldDefinition<
return this.type.size;
}
public create(
options: Readonly<StructOptions>,
context: StructSerializationContext,
struct: StructValue,
value: TTypeScriptType,
): NumberFieldValue<this> {
return new NumberFieldValue(this, options, context, struct, value);
}
public async deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
): Promise<NumberFieldRuntimeValue<TType, TTypeScriptType>> {
struct: StructValue,
): Promise<NumberFieldValue<this>> {
const buffer = await context.read(this.getSize());
const view = new DataView(buffer);
const value = view[this.type.dataViewGetter](
0,
options.littleEndian
) as any;
return this.createValue(options, context, object, value);
}
public createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
value: TTypeScriptType,
): NumberFieldRuntimeValue<TType, TTypeScriptType> {
return new NumberFieldRuntimeValue(this, options, context, object, value);
return this.create(options, context, struct, value);
}
}
export class NumberFieldRuntimeValue<
TType extends NumberFieldType = NumberFieldType,
TTypeScriptType = TType['valueType'],
> extends FieldRuntimeValue<NumberFieldDefinition<TType, TTypeScriptType>> {
export class NumberFieldValue<
TDefinition extends NumberFieldDefinition<NumberFieldType, any>,
> extends StructFieldValue<TDefinition> {
public serialize(dataView: DataView, offset: number): void {
// `setBigInt64` requires a `bigint` while others require `number`
// So `dataView[DataViewSetters]` requires `bigint & number`

View file

@ -0,0 +1,468 @@
import { StructDefaultOptions, StructDeserializationContext, StructValue } from '../basic';
import { ArrayBufferFieldType, ArrayBufferLikeFieldValue, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer';
import { FixedLengthArrayBufferLikeFieldDefinition } from './fixed-length-array-buffer';
import { NumberFieldDefinition, NumberFieldType, NumberFieldValue } from './number';
import { VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeLengthStructFieldValue, VariableLengthArrayBufferLikeStructFieldValue } from './variable-length-array-buffer';
describe('Types', () => {
describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
describe('#getSize', () => {
it('should always return `0`', () => {
const definition = new VariableLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { lengthField: 'foo' });
expect(definition.getSize()).toBe(0);
});
});
describe('#getDeserializeSize', () => {
it('should return value of its `lengthField`', async () => {
const lengthField = 'foo';
const size = 10;
const definition = new VariableLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { lengthField });
const buffer = new ArrayBuffer(size);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
struct.set(
lengthField,
new NumberFieldValue(
new NumberFieldDefinition(
NumberFieldType.Int8
),
StructDefaultOptions,
context,
struct,
size,
),
);
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(read).toBeCalledTimes(1);
expect(read).toBeCalledWith(size);
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
const value = fieldValue.get();
expect(value).toBe(buffer);
});
it('should return value of its `lengthField` as number', async () => {
const lengthField = 'foo';
const size = 10;
const definition = new VariableLengthArrayBufferLikeFieldDefinition(ArrayBufferFieldType.instance, { lengthField });
const buffer = new ArrayBuffer(size);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
struct.set(
lengthField,
new ArrayBufferLikeFieldValue(
new FixedLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
),
);
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(read).toBeCalledTimes(1);
expect(read).toBeCalledWith(size);
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
const value = fieldValue.get();
expect(value).toBe(buffer);
});
});
});
describe('VariableLengthArrayBufferLikeStructFieldValue', () => {
describe('.constructor', () => {
it('should replace `lengthField` on `struct`', () => {
const lengthField = 'foo';
const size = 10;
const buffer = new ArrayBuffer(size);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
new FixedLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance,
{ lengthField },
);
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
definition,
StructDefaultOptions,
context,
struct,
buffer,
);
expect(fieldValue).toHaveProperty('definition', definition);
expect(struct.fieldValues[lengthField]).not.toBe(originalLengthFieldValue);
});
});
describe('#getSize', () => {
it('should return size of `arrayBuffer` if exist', async () => {
const lengthField = 'foo';
const size = 10;
const buffer = new ArrayBuffer(size);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
new FixedLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance,
{ lengthField },
);
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(fieldValue.getSize()).toBe(size);
});
it('should call `getSize` of its `type`', () => {
const lengthField = 'foo';
const size = 10;
const buffer = new ArrayBuffer(size);
const context: StructDeserializationContext = {
read(length) { throw new Error(''); },
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
new FixedLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance,
{ lengthField },
),
StructDefaultOptions,
context,
struct,
buffer,
);
expect(fieldValue.getSize()).toBe(size);
});
it('should call `toArrayBuffer` of its `type` if it does not support `getSize`', () => {
const lengthField = 'foo';
const size = 10;
const context: StructDeserializationContext = {
read(length) { throw new Error(''); },
encodeUtf8(input) {
return Buffer.from(input, 'utf-8');
},
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
new FixedLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
new VariableLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ lengthField },
),
StructDefaultOptions,
context,
struct,
'test',
);
expect(fieldValue.getSize()).toBe(4);
});
});
describe('#set', () => {
it('should store value', () => {
const lengthField = 'foo';
const size = 10;
const array = new Uint8ClampedArray(size);
const buffer = array.buffer;
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
new FixedLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
new VariableLengthArrayBufferLikeFieldDefinition(
Uint8ClampedArrayFieldType.instance,
{ lengthField },
),
StructDefaultOptions,
context,
struct,
new Uint8ClampedArray(buffer),
);
const newArray = new Uint8ClampedArray(size);
fieldValue.set(newArray);
expect(fieldValue.get()).toBe(newArray);
});
it('should clear length', () => {
const lengthField = 'foo';
const size = 10;
const array = new Uint8ClampedArray(size);
const buffer = array.buffer;
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
new FixedLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const fieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
new VariableLengthArrayBufferLikeFieldDefinition(
Uint8ClampedArrayFieldType.instance,
{ lengthField },
),
StructDefaultOptions,
context,
struct,
new Uint8ClampedArray(buffer),
);
const newArray = new Uint8ClampedArray(size);
fieldValue.set(newArray);
expect(fieldValue['length']).toBeUndefined();
});
});
});
describe('VariableLengthArrayBufferLikeLengthStructFieldValue', () => {
describe('#getSize', () => {
it('should return size of its original field value', () => {
const struct = new StructValue();
const originalFieldValue = new NumberFieldValue(
new NumberFieldDefinition(
NumberFieldType.Int8
),
StructDefaultOptions,
{} as any,
struct,
42,
);
const fieldValue = new VariableLengthArrayBufferLikeLengthStructFieldValue(
originalFieldValue,
{} as any,
);
expect(fieldValue.getSize()).toBe(originalFieldValue.getSize());
});
});
describe('#get', () => {
it('should return size of its `arrayBufferField`', async () => {
const lengthField = 'foo';
const size = 10;
const buffer = new ArrayBuffer(size);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const originalLengthFieldValue = new NumberFieldValue(
new NumberFieldDefinition(
NumberFieldType.Int32
),
StructDefaultOptions,
context,
struct,
size
);
struct.set(lengthField, originalLengthFieldValue,);
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance,
{ lengthField },
);
const fieldValue = (await definition.deserialize(StructDefaultOptions, context, struct)) as any as VariableLengthArrayBufferLikeStructFieldValue;
expect(fieldValue['lengthFieldValue'].get()).toBe(size);
});
it('should return size of its `arrayBufferField` as string', async () => {
const lengthField = 'foo';
const size = 10;
const buffer = new ArrayBuffer(size);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) { throw new Error(''); },
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
new FixedLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance,
{ lengthField },
);
const fieldValue = (await definition.deserialize(StructDefaultOptions, context, struct)) as any as VariableLengthArrayBufferLikeStructFieldValue;
expect(fieldValue['lengthFieldValue'].get()).toBe(size.toString());
});
});
describe('#serialize', () => {
it('should call `serialize` of its `originalField`', async () => {
const lengthField = 'foo';
const size = 10;
const buffer = new ArrayBuffer(size);
const read = jest.fn((length: number) => buffer);
const context: StructDeserializationContext = {
read,
encodeUtf8(input) {
return Buffer.from(input, 'utf-8');
},
decodeUtf8(buffer) { throw new Error(''); },
};
const struct = new StructValue();
const originalLengthFieldValue = new ArrayBufferLikeFieldValue(
new FixedLengthArrayBufferLikeFieldDefinition(
StringFieldType.instance,
{ length: 2 },
),
StructDefaultOptions,
context,
struct,
size.toString()
);
struct.set(lengthField, originalLengthFieldValue,);
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
ArrayBufferFieldType.instance,
{ lengthField },
);
const fieldValue = (await definition.deserialize(StructDefaultOptions, context, struct)) as any as VariableLengthArrayBufferLikeStructFieldValue;
const targetArray = new Uint8Array(2);
const targetView = new DataView(targetArray.buffer);
fieldValue['lengthFieldValue'].serialize(targetView, 0, context);
expect(targetArray).toEqual(new Uint8Array('10'.split('').map(c => c.charCodeAt(0))));
});
});
});
});

View file

@ -1,6 +1,6 @@
import { FieldRuntimeValue, getRuntimeValue, setRuntimeValue, StructOptions, StructSerializationContext } from '../basic';
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldRuntimeValue, ArrayBufferLikeFieldType } from './array-buffer';
import { KeysOfType } from './utils';
import { StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic';
import { KeysOfType } from '../utils';
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer';
export interface VariableLengthArrayBufferLikeFieldOptions<
TFields = object,
@ -21,81 +21,49 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
return 0;
}
protected getDeserializeSize(object: any) {
let value = object[this.options.lengthField] as number | string;
protected getDeserializeSize(struct: StructValue) {
let value = struct.value[this.options.lengthField] as number | string;
if (typeof value === 'string') {
value = Number.parseInt(value, 10);
}
return value;
}
public createValue(
public create(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
struct: StructValue,
value: TType['valueType'],
): VariableLengthArrayBufferLikeFieldRuntimeValue<TType, TOptions> {
return new VariableLengthArrayBufferLikeFieldRuntimeValue(this, options, context, object, value);
): VariableLengthArrayBufferLikeStructFieldValue<this> {
return new VariableLengthArrayBufferLikeStructFieldValue(this, options, context, struct, value);
}
}
class VariableLengthArrayBufferLikeLengthFieldRuntimeValue extends FieldRuntimeValue {
protected originalValue: FieldRuntimeValue;
protected arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue;
public constructor(
originalValue: FieldRuntimeValue,
arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue,
) {
super(originalValue.definition, originalValue.options, originalValue.context, originalValue.object, 0);
this.originalValue = originalValue;
this.arrayBufferValue = arrayBufferValue;
}
public getSize() {
return this.originalValue.getSize();
}
get() {
// TODO: originalValue might be a `string` type, now it always returns `number`.
return this.arrayBufferValue.getSize();
}
set() { }
serialize(dataView: DataView, offset: number, context: StructSerializationContext) {
this.originalValue.set(this.get());
this.originalValue.serialize(dataView, offset, context);
}
}
class VariableLengthArrayBufferLikeFieldRuntimeValue<
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
TOptions extends VariableLengthArrayBufferLikeFieldOptions = VariableLengthArrayBufferLikeFieldOptions
> extends ArrayBufferLikeFieldRuntimeValue<VariableLengthArrayBufferLikeFieldDefinition<TType, TOptions>> {
public static getSize() {
return 0;
}
export class VariableLengthArrayBufferLikeStructFieldValue<
TDefinition extends VariableLengthArrayBufferLikeFieldDefinition = VariableLengthArrayBufferLikeFieldDefinition,
> extends ArrayBufferLikeFieldValue<TDefinition> {
protected length: number | undefined;
protected lengthFieldValue: VariableLengthArrayBufferLikeLengthFieldRuntimeValue;
protected lengthFieldValue: VariableLengthArrayBufferLikeLengthStructFieldValue;
public constructor(
definition: VariableLengthArrayBufferLikeFieldDefinition<TType, TOptions>,
definition: TDefinition,
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
value: TType['valueType'],
struct: StructValue,
value: TDefinition['valueType'],
) {
super(definition, options, context, object, value);
super(definition, options, context, struct, value);
// Patch the associated length field.
const lengthField = this.definition.options.lengthField;
const originalValue = getRuntimeValue(object, lengthField);
this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthFieldRuntimeValue(originalValue, this);
setRuntimeValue(object, lengthField, this.lengthFieldValue);
const originalValue = struct.get(lengthField);
this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthStructFieldValue(
originalValue,
this,
);
struct.set(lengthField, this.lengthFieldValue);
}
public getSize() {
@ -119,3 +87,41 @@ class VariableLengthArrayBufferLikeFieldRuntimeValue<
this.length = undefined;
}
}
export class VariableLengthArrayBufferLikeLengthStructFieldValue
extends StructFieldValue {
protected originalField: StructFieldValue;
protected arrayBufferField: VariableLengthArrayBufferLikeStructFieldValue;
public constructor(
originalField: StructFieldValue,
arrayBufferField: VariableLengthArrayBufferLikeStructFieldValue,
) {
super(originalField.definition, originalField.options, originalField.context, originalField.struct, 0);
this.originalField = originalField;
this.arrayBufferField = arrayBufferField;
}
public getSize() {
return this.originalField.getSize();
}
get() {
let value: string | number = this.arrayBufferField.getSize();
const originalValue = this.originalField.get();
if (typeof originalValue === 'string') {
value = value.toString();
}
return value;
}
set() { }
serialize(dataView: DataView, offset: number, context: StructSerializationContext) {
this.originalField.set(this.get());
this.originalField.serialize(dataView, offset, context);
}
}

View file

@ -0,0 +1,7 @@
import { placeholder } from './utils';
describe('placeholder', () => {
it('should return `undefined`', () => {
expect(placeholder()).toBe(undefined);
});
});

View file

@ -39,6 +39,9 @@ export type OmitNever<T> = Pick<T, { [K in keyof T]: [T[K]] extends [never] ? ne
export type KeysOfType<T, TValue> =
{ [TKey in keyof T]: T[TKey] extends TValue ? TKey : never }[keyof T];
export type ValueOrPromise<T> = T | Promise<T>;
/**
* Returns a (fake) value of the given type.
*/

View file

@ -13,6 +13,7 @@
"declaration": true,
"declarationDir": "dts",
"declarationMap": true,
"stripInternal": true,
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "noEmit": true, /* Do not emit outputs. */
"importHelpers": true, // /* Import emit helpers from 'tslib'. */