New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@yume-chan/struct

Package Overview
Dependencies
Maintainers
0
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@yume-chan/struct - npm Package Compare versions

Comparing version 0.0.0-next-20240917062356 to 0.0.0-next-20241129144018

esm/bipedal.d.ts

44

CHANGELOG.md
# Change Log - @yume-chan/struct
## 0.0.0-next-20240917062356
## 0.0.0-next-20241129144018
### Major Changes
- 53688d3: Use PNPM workspace and Changesets to manage the monorepo.
- 53688d3: Use PNPM workspace and Changesets to manage the monorepo.

@@ -13,4 +13,6 @@ Because Changesets doesn't support alpha versions (`0.x.x`), this version is `1.0.0`. Future versions will follow SemVer rules, for example, breaking API changes will introduce a new major version.

- Updated dependencies [53688d3]
- @yume-chan/no-data-view@0.0.0-next-20240917062356
- db8466f: Rewrite the struct API completely
- db8466f: Improve tree-shaking by removing TypeScript enum and namespace
- Updated dependencies [53688d3]
- @yume-chan/no-data-view@0.0.0-next-20241129144018

@@ -25,3 +27,3 @@ This log was last generated on Tue, 18 Jun 2024 02:49:43 GMT and should not be manually modified.

- Rename some internal types to make them more distinguishable
- Rename some internal types to make them more distinguishable

@@ -52,6 +54,6 @@ ## 0.0.23

- Rename `StructDeserializeStream` and `StructAsyncDeserializeStream` to `ExactReadable` and `AsyncExactReadable`. Rename its `read` method to `readExactly`. Add a `position` field so the caller can check how many bytes have been read.
- Improve performance for decoding integers.
- Rename `Struct#fields` to `Struct#concat`. Now `Struct#fields` returns an array of `[name: PropertyKey, definition: StructFieldDefinition<any, any, any>]` tuples.
- Use ECMAScript private class fields syntax (supported by Chrome 74, Firefox 90, Safari 14.1 and Node.js 12.0.0).
- Rename `StructDeserializeStream` and `StructAsyncDeserializeStream` to `ExactReadable` and `AsyncExactReadable`. Rename its `read` method to `readExactly`. Add a `position` field so the caller can check how many bytes have been read.
- Improve performance for decoding integers.
- Rename `Struct#fields` to `Struct#concat`. Now `Struct#fields` returns an array of `[name: PropertyKey, definition: StructFieldDefinition<any, any, any>]` tuples.
- Use ECMAScript private class fields syntax (supported by Chrome 74, Firefox 90, Safari 14.1 and Node.js 12.0.0).

@@ -70,3 +72,3 @@ ## 0.0.19

- Refactor number types for easier extending (see `ScrcpyFloatToInt16FieldDefinition` for an example)
- Refactor number types for easier extending (see `ScrcpyFloatToInt16FieldDefinition` for an example)

@@ -85,6 +87,6 @@ ## 0.0.17

- 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.
- 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.

@@ -109,4 +111,4 @@ ## 0.0.15

- Fix an issue that `uint64` still deserialize to negative numbers
- Fix an issue where `Syncbird` can't synchronously invoke `then` on some Bluebird internal methods (for example `reduce`)
- Fix an issue that `uint64` still deserialize to negative numbers
- Fix an issue where `Syncbird` can't synchronously invoke `then` on some Bluebird internal methods (for example `reduce`)

@@ -125,6 +127,6 @@ ## 0.0.12

- Update to use Web Streams API
- Improve compatibility with Node.js 12 ESM format
- Update compatibility matrix
- Update license year
- Update to use Web Streams API
- Improve compatibility with Node.js 12 ESM format
- Update compatibility matrix
- Update license year

@@ -137,3 +139,3 @@ ## 0.0.10

- Add synchronized deserialize support
- Add synchronized deserialize support

@@ -140,0 +142,0 @@ ## 0.0.9

@@ -9,8 +9,10 @@ declare global {

}
export * from "./basic/index.js";
export * from "./bipedal.js";
export * from "./buffer.js";
export * from "./field.js";
export * from "./number.js";
export * from "./readable.js";
export * from "./string.js";
export * from "./struct.js";
export { Struct as default } from "./struct.js";
export * from "./sync-promise.js";
export * from "./types/index.js";
export * from "./utils.js";
//# sourceMappingURL=index.d.ts.map

@@ -1,7 +0,9 @@

export * from "./basic/index.js";
export * from "./bipedal.js";
export * from "./buffer.js";
export * from "./field.js";
export * from "./number.js";
export * from "./readable.js";
export * from "./string.js";
export * from "./struct.js";
export { Struct as default } from "./struct.js";
export * from "./sync-promise.js";
export * from "./types/index.js";
export * from "./utils.js";
//# sourceMappingURL=index.js.map

@@ -1,25 +0,11 @@

import type { AsyncExactReadable, ExactReadable, StructFieldDefinition, StructOptions } from "./basic/index.js";
import type { BufferFieldConverter, FixedLengthBufferLikeFieldOptions, LengthField, VariableLengthBufferLikeFieldOptions } from "./types/index.js";
import { BigIntFieldVariant, FixedLengthBufferLikeFieldDefinition, StringBufferFieldConverter, Uint8ArrayBufferFieldConverter, VariableLengthBufferLikeFieldDefinition } from "./types/index.js";
import type { Evaluate, Identity, Overwrite } from "./utils.js";
export interface StructLike<TValue> {
deserialize(stream: ExactReadable | AsyncExactReadable): Promise<TValue>;
}
/**
* Extract the value type of the specified `Struct`
*/
export type StructValueType<T extends StructLike<unknown>> = Awaited<ReturnType<T["deserialize"]>>;
/**
* Create a new `Struct` type with `TDefinition` appended
*/
type AddFieldDescriptor<TFields extends object, TOmitInitKey extends PropertyKey, TExtra extends object, TPostDeserialized, TFieldName extends PropertyKey, TDefinition extends StructFieldDefinition<unknown, unknown, PropertyKey>> = Identity<Struct<Evaluate<TFields & Record<TFieldName, TDefinition["TValue"]>>, TOmitInitKey | TDefinition["TOmitInitKey"], TExtra, TPostDeserialized>>;
/**
* Similar to `ArrayBufferLikeFieldCreator`, but bind to `TType`
*/
interface BoundArrayBufferLikeFieldDefinitionCreator<TFields extends object, TOmitInitKey extends PropertyKey, TExtra extends object, TPostDeserialized, TType extends BufferFieldConverter<unknown, unknown>> {
<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, TOptions extends VariableLengthBufferLikeFieldOptions<TFields, LengthField<TFields>>, TTypeScriptType = TType["TTypeScriptType"]>(name: TName, options: TOptions, typeScriptType?: TTypeScriptType): AddFieldDescriptor<TFields, TOmitInitKey, TExtra, TPostDeserialized, TName, VariableLengthBufferLikeFieldDefinition<TType, TOptions, TTypeScriptType>>;
}
export type StructPostDeserialized<TFields, TPostDeserialized> = (this: TFields, object: TFields) => TPostDeserialized;
export type StructDeserializedResult<TFields extends object, TExtra extends object, TPostDeserialized> = TPostDeserialized extends undefined ? Overwrite<TExtra, TFields> : TPostDeserialized;
import type { MaybePromiseLike } from "@yume-chan/async";
import type { Field } from "./field.js";
import type { AsyncExactReadable, ExactReadable } from "./readable.js";
export type FieldsType<T extends Record<string, Field<unknown, string, unknown>>> = {
[K in keyof T]: T[K] extends Field<infer TK, string, unknown> ? TK : never;
};
export type StructInit<T extends Struct<any, any, any>> = Omit<FieldsType<T["fields"]>, {
[K in keyof T["fields"]]: T["fields"][K] extends Field<unknown, infer U, unknown> ? U : never;
}[keyof T["fields"]]>;
export type StructValue<T extends Struct<any, any, any>> = T extends Struct<any, any, infer P> ? P : never;
export declare class StructDeserializeError extends Error {

@@ -34,119 +20,17 @@ constructor(message: string);

}
interface StructDefinition<TFields extends object, TOmitInitKey extends PropertyKey, TExtra extends object> {
readonly TFields: TFields;
readonly TOmitInitKey: TOmitInitKey;
readonly TExtra: TExtra;
readonly TInit: Evaluate<Omit<TFields, TOmitInitKey>>;
export type StructLike<T> = Struct<any, any, T>;
export interface Struct<T extends Record<string, Field<unknown, string, Partial<FieldsType<T>>>>, Extra extends Record<PropertyKey, unknown> | undefined = undefined, PostDeserialize = FieldsType<T> & Extra> {
fields: T;
size: number;
extra: Extra;
serialize(runtimeStruct: StructInit<this>): Uint8Array;
serialize(runtimeStruct: StructInit<this>, buffer: Uint8Array): number;
deserialize(reader: ExactReadable): PostDeserialize;
deserialize(reader: AsyncExactReadable): MaybePromiseLike<PostDeserialize>;
}
export declare class Struct<TFields extends object = Record<never, never>, TOmitInitKey extends PropertyKey = never, TExtra extends object = Record<never, never>, TPostDeserialized = undefined> implements StructLike<StructDeserializedResult<TFields, TExtra, TPostDeserialized>> {
#private;
readonly TFields: TFields;
readonly TOmitInitKey: TOmitInitKey;
readonly TExtra: TExtra;
readonly TInit: Evaluate<Omit<TFields, TOmitInitKey>>;
readonly TDeserializeResult: StructDeserializedResult<TFields, TExtra, TPostDeserialized>;
readonly options: Readonly<StructOptions>;
/**
* Gets the static size (exclude fields that can change size at runtime)
*/
get size(): number;
get fields(): readonly [
name: PropertyKey,
definition: StructFieldDefinition<unknown, unknown, PropertyKey>
][];
constructor(options?: Partial<Readonly<StructOptions>>);
/**
* Appends a `StructFieldDefinition` to the `Struct
*/
field<TName extends PropertyKey, TDefinition extends StructFieldDefinition<unknown, unknown, PropertyKey>>(name: TName, definition: TDefinition): AddFieldDescriptor<TFields, TOmitInitKey, TExtra, TPostDeserialized, TName, TDefinition>;
/**
* Merges (flats) another `Struct`'s fields and extra fields into this one.
*
* `other`'s `postDeserialize` will be ignored.
*/
concat<TOther extends StructDefinition<object, PropertyKey, object>>(other: TOther): Struct<TFields & TOther["TFields"], TOmitInitKey | TOther["TOmitInitKey"], TExtra & TOther["TExtra"], TPostDeserialized>;
/**
* Appends an `int8` field to the `Struct`
*/
int8<TName extends PropertyKey, TTypeScriptType = number>(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 = number>(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 = number>(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 = number>(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 = number>(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 = number>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>;
/**
* Appends an `int64` field to the `Struct`
*
* Requires native `BigInt` support
*/
int64<TName extends PropertyKey, TTypeScriptType = BigIntFieldVariant["TTypeScriptType"]>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>;
/**
* Appends an `uint64` field to the `Struct`
*
* Requires native `BigInt` support
*/
uint64<TName extends PropertyKey, TTypeScriptType = BigIntFieldVariant["TTypeScriptType"]>(name: TName, typeScriptType?: TTypeScriptType): Struct<Evaluate<TFields & Record<TName, TTypeScriptType>>, TOmitInitKey, TExtra, TPostDeserialized>;
uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<TFields, TOmitInitKey, TExtra, TPostDeserialized, Uint8ArrayBufferFieldConverter>;
string: BoundArrayBufferLikeFieldDefinitionCreator<TFields, TOmitInitKey, TExtra, TPostDeserialized, StringBufferFieldConverter>;
/**
* Adds some extra properties into every `Struct` value.
*
* Extra properties will not affect serialize or deserialize process.
*
* Multiple calls to `extra` will merge all properties together.
*
* @param value
* An object containing properties to be added to the result value. Accessors and methods are also allowed.
*/
extra<T extends Record<Exclude<keyof T, Exclude<keyof T, keyof TFields>>, never>>(value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>>): Struct<TFields, TOmitInitKey, Overwrite<TExtra, T>, TPostDeserialized>;
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A callback returning `never` (always throw an error)
* will also change the return type of `deserialize` to `never`.
*/
postDeserialize(callback: StructPostDeserialized<TFields, never>): Struct<TFields, TOmitInitKey, TExtra, never>;
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A 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.
*/
postDeserialize(callback?: StructPostDeserialized<TFields, void>): Struct<TFields, TOmitInitKey, TExtra, undefined>;
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A callback returning anything other than `undefined`
* will `deserialize` to return that object instead.
*/
postDeserialize<TPostSerialize>(callback?: StructPostDeserialized<TFields, TPostSerialize>): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;
/**
* Deserialize a struct value from `stream`.
*/
deserialize(stream: ExactReadable): StructDeserializedResult<TFields, TExtra, TPostDeserialized>;
deserialize(stream: AsyncExactReadable): Promise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>>;
/**
* Serialize a struct value to a buffer.
* @param init Fields of the struct
* @param output The buffer to serialize the struct to. It must be large enough to hold the entire struct. If not provided, a new buffer will be created.
* @returns A view of `output` that contains the serialized struct, or a new buffer if `output` is not provided.
*/
serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>, output?: Uint8Array): Uint8Array;
}
export {};
export declare function struct<T extends Record<string, Field<unknown, string, Partial<FieldsType<T>>>>, Extra extends Record<PropertyKey, unknown> = {}, PostDeserialize = FieldsType<T> & Extra>(fields: T, options: {
littleEndian?: boolean;
extra?: Extra & ThisType<FieldsType<T>>;
postDeserialize?: (this: FieldsType<T> & Extra, fields: FieldsType<T> & Extra) => PostDeserialize;
}): Struct<T, Extra, PostDeserialize>;
//# sourceMappingURL=struct.d.ts.map

@@ -1,9 +0,6 @@

/* eslint-disable @typescript-eslint/no-explicit-any */
import { ExactReadableEndedError, STRUCT_VALUE_SYMBOL, StructDefaultOptions, StructValue, isStructValueInit, } from "./basic/index.js";
import { SyncPromise } from "./sync-promise.js";
import { BigIntFieldDefinition, BigIntFieldVariant, FixedLengthBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldVariant, StringBufferFieldConverter, Uint8ArrayBufferFieldConverter, VariableLengthBufferLikeFieldDefinition, } from "./types/index.js";
import { bipedal } from "./bipedal.js";
import { ExactReadableEndedError } from "./readable.js";
export class StructDeserializeError extends Error {
constructor(message) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}

@@ -21,162 +18,67 @@ }

}
export class Struct {
TFields;
TOmitInitKey;
TExtra;
TInit;
TDeserializeResult;
options;
#size = 0;
/**
* Gets the static size (exclude fields that can change size at runtime)
*/
get size() {
return this.#size;
}
#fields = [];
get fields() {
return this.#fields;
}
#extra = {};
#postDeserialized;
constructor(options) {
this.options = { ...StructDefaultOptions, ...options };
}
/**
* Appends a `StructFieldDefinition` to the `Struct
*/
field(name, definition) {
for (const field of this.#fields) {
if (field[0] === name) {
// Convert Symbol to string
const nameString = String(name);
throw new Error(`This struct already have a field with name '${nameString}'`);
/* #__NO_SIDE_EFFECTS__ */
export function struct(fields, options) {
const fieldList = Object.entries(fields);
const size = fieldList.reduce((sum, [, field]) => sum + field.size, 0);
const littleEndian = !!options.littleEndian;
const extra = options.extra
? Object.getOwnPropertyDescriptors(options.extra)
: undefined;
return {
fields,
size,
extra: options.extra,
serialize(runtimeStruct, buffer) {
for (const [key, field] of fieldList) {
if (key in runtimeStruct) {
field.preSerialize?.(runtimeStruct[key], runtimeStruct);
}
}
}
this.#fields.push([name, definition]);
const size = definition.getSize();
this.#size += size;
// Force cast `this` to another type
return this;
}
/**
* Merges (flats) another `Struct`'s fields and extra fields into this one.
*
* `other`'s `postDeserialize` will be ignored.
*/
concat(other) {
if (!(other instanceof Struct)) {
throw new TypeError("The other value must be a `Struct` instance");
}
for (const field of other.#fields) {
this.#fields.push(field);
}
this.#size += other.#size;
Object.defineProperties(this.#extra, Object.getOwnPropertyDescriptors(other.#extra));
return this;
}
#number(name, type, typeScriptType) {
return this.field(name, new NumberFieldDefinition(type, typeScriptType));
}
/**
* Appends an `int8` field to the `Struct`
*/
int8(name, typeScriptType) {
return this.#number(name, NumberFieldVariant.Int8, typeScriptType);
}
/**
* Appends an `uint8` field to the `Struct`
*/
uint8(name, typeScriptType) {
return this.#number(name, NumberFieldVariant.Uint8, typeScriptType);
}
/**
* Appends an `int16` field to the `Struct`
*/
int16(name, typeScriptType) {
return this.#number(name, NumberFieldVariant.Int16, typeScriptType);
}
/**
* Appends an `uint16` field to the `Struct`
*/
uint16(name, typeScriptType) {
return this.#number(name, NumberFieldVariant.Uint16, typeScriptType);
}
/**
* Appends an `int32` field to the `Struct`
*/
int32(name, typeScriptType) {
return this.#number(name, NumberFieldVariant.Int32, typeScriptType);
}
/**
* Appends an `uint32` field to the `Struct`
*/
uint32(name, typeScriptType) {
return this.#number(name, NumberFieldVariant.Uint32, typeScriptType);
}
#bigint(name, type, typeScriptType) {
return this.field(name, new BigIntFieldDefinition(type, typeScriptType));
}
/**
* Appends an `int64` field to the `Struct`
*
* Requires native `BigInt` support
*/
int64(name, typeScriptType) {
return this.#bigint(name, BigIntFieldVariant.Int64, typeScriptType);
}
/**
* Appends an `uint64` field to the `Struct`
*
* Requires native `BigInt` support
*/
uint64(name, typeScriptType) {
return this.#bigint(name, BigIntFieldVariant.Uint64, typeScriptType);
}
#arrayBufferLike = (name, type, options) => {
if ("length" in options) {
return this.field(name, new FixedLengthBufferLikeFieldDefinition(type, options));
}
else {
return this.field(name, new VariableLengthBufferLikeFieldDefinition(type, options));
}
};
uint8Array = (name, options, typeScriptType) => {
return this.#arrayBufferLike(name, Uint8ArrayBufferFieldConverter.Instance, options, typeScriptType);
};
string = (name, options, typeScriptType) => {
return this.#arrayBufferLike(name, StringBufferFieldConverter.Instance, options, typeScriptType);
};
/**
* Adds some extra properties into every `Struct` value.
*
* Extra properties will not affect serialize or deserialize process.
*
* Multiple calls to `extra` will merge all properties together.
*
* @param value
* An object containing properties to be added to the result value. Accessors and methods are also allowed.
*/
extra(value) {
Object.defineProperties(this.#extra, Object.getOwnPropertyDescriptors(value));
return this;
}
postDeserialize(callback) {
this.#postDeserialized = callback;
return this;
}
deserialize(stream) {
const structValue = new StructValue(this.#extra);
let promise = SyncPromise.resolve();
const startPosition = stream.position;
for (const [name, definition] of this.#fields) {
promise = promise
.then(() => definition.deserialize(this.options, stream, structValue))
.then((fieldValue) => {
structValue.set(name, fieldValue);
}, (e) => {
const sizes = fieldList.map(([key, field]) => field.dynamicSize?.(runtimeStruct[key]) ??
field.size);
const size = sizes.reduce((sum, size) => sum + size, 0);
let externalBuffer = false;
if (buffer) {
if (buffer.length < size) {
throw new Error("Buffer too small");
}
externalBuffer = true;
}
else {
buffer = new Uint8Array(size);
}
const context = {
buffer,
index: 0,
littleEndian,
};
for (const [index, [key, field]] of fieldList.entries()) {
field.serialize(runtimeStruct[key], context);
context.index += sizes[index];
}
if (externalBuffer) {
return size;
}
else {
return buffer;
}
},
deserialize: bipedal(function* (then, reader) {
const startPosition = reader.position;
const runtimeStruct = {};
const context = {
reader,
runtimeStruct: runtimeStruct,
littleEndian: littleEndian,
};
try {
for (const [key, field] of fieldList) {
runtimeStruct[key] = yield* then(field.deserialize(context));
}
}
catch (e) {
if (!(e instanceof ExactReadableEndedError)) {
throw e;
}
if (stream.position === startPosition) {
if (reader.position === startPosition) {
throw new StructEmptyError();

@@ -187,71 +89,15 @@ }

}
});
}
return promise
.then(() => {
const value = structValue.value;
// Run `postDeserialized`
if (this.#postDeserialized) {
const override = this.#postDeserialized.call(value, value);
// If it returns a new value, use that as result
// Otherwise it only inspects/mutates the object in place.
if (override !== undefined) {
return override;
}
}
return value;
})
.valueOrPromise();
}
/**
* Serialize a struct value to a buffer.
* @param init Fields of the struct
* @param output The buffer to serialize the struct to. It must be large enough to hold the entire struct. If not provided, a new buffer will be created.
* @returns A view of `output` that contains the serialized struct, or a new buffer if `output` is not provided.
*/
serialize(init, output) {
let structValue;
if (isStructValueInit(init)) {
structValue = init[STRUCT_VALUE_SYMBOL];
for (const [key, value] of Object.entries(init)) {
const fieldValue = structValue.get(key);
if (fieldValue) {
fieldValue.set(value);
}
if (extra) {
Object.defineProperties(runtimeStruct, extra);
}
}
else {
structValue = new StructValue({});
for (const [name, definition] of this.#fields) {
const fieldValue = definition.create(this.options, structValue, init[name]);
structValue.set(name, fieldValue);
if (options.postDeserialize) {
return options.postDeserialize.call(runtimeStruct, runtimeStruct);
}
}
let structSize = 0;
const fieldsInfo = [];
for (const [name] of this.#fields) {
const fieldValue = structValue.get(name);
const size = fieldValue.getSize();
fieldsInfo.push({ fieldValue, size });
structSize += size;
}
if (!output) {
output = new Uint8Array(structSize);
}
else if (output.length < structSize) {
throw new TypeError("Output buffer is too small");
}
let offset = 0;
for (const { fieldValue, size } of fieldsInfo) {
fieldValue.serialize(output, offset);
offset += size;
}
if (output.length !== structSize) {
return output.subarray(0, structSize);
}
else {
return output;
}
}
else {
return runtimeStruct;
}
}),
};
}
//# sourceMappingURL=struct.js.map

@@ -1,48 +0,13 @@

/**
* When evaluating a very complex generic type alias,
* tell TypeScript to use `T`, instead of current type alias' name, as the result type name
*
* Example:
*
* ```ts
* type WithIdentity<T> = Identity<SomeType<T>>;
* type WithoutIdentity<T> = SomeType<T>;
*
* type WithIdentityResult = WithIdentity<number>;
* // Hover on this one shows `SomeType<number>`
*
* type WithoutIdentityResult = WithoutIdentity<number>;
* // Hover on this one shows `WithoutIdentity<number>`
* ```
*/
export type Identity<T> = T;
/**
* Collapse an intersection type (`{ foo: string } & { bar: number }`) to a simple type (`{ foo: string, bar: number }`)
*/
export type Evaluate<T> = T extends infer U ? {
[K in keyof U]: U[K];
} : never;
/**
* Overwrite fields in `TBase` with fields in `TNew`
*/
export type Overwrite<TBase extends object, TNew extends object> = Evaluate<Omit<TBase, keyof TNew> & TNew>;
/**
* Remove fields with `never` type
*/
export type OmitNever<T> = Pick<T, {
[K in keyof T]: [T[K]] extends [never] ? never : K;
}[keyof T]>;
/**
* Extract keys of fields in `T` that has type `TValue`
*/
export type KeysOfType<T, TValue> = {
[TKey in keyof T]: T[TKey] extends TValue ? TKey : never;
}[keyof T];
export type ValueOrPromise<T> = T | PromiseLike<T>;
/**
* Returns a (fake) value of the given type.
*/
export declare function placeholder<T>(): T;
interface TextEncoder {
encode(input: string): Uint8Array;
}
interface TextDecoder {
decode(buffer?: ArrayBufferView | ArrayBuffer, options?: {
stream?: boolean;
}): string;
}
export declare const TextEncoder: new () => TextEncoder, TextDecoder: new () => TextDecoder;
export declare function encodeUtf8(input: string): Uint8Array;
export declare function decodeUtf8(buffer: ArrayBufferView | ArrayBuffer): string;
export {};
//# sourceMappingURL=utils.d.ts.map

@@ -1,8 +0,5 @@

/**
* Returns a (fake) value of the given type.
*/
export function placeholder() {
return undefined;
}
const { TextEncoder, TextDecoder } = globalThis;
// This library can't use `@types/node` or `lib: dom`
// because they will pollute the global scope
// So `TextEncoder` and `TextDecoder` types are not available
export const { TextEncoder, TextDecoder } = globalThis;
const SharedEncoder = /* #__PURE__ */ new TextEncoder();

@@ -14,2 +11,3 @@ const SharedDecoder = /* #__PURE__ */ new TextDecoder();

}
/* #__NO_SIDE_EFFECTS__ */
export function decodeUtf8(buffer) {

@@ -16,0 +14,0 @@ // `TextDecoder` has internal states in stream mode,

{
"name": "@yume-chan/struct",
"version": "0.0.0-next-20240917062356",
"version": "0.0.0-next-20241129144018",
"description": "C-style structure serializer and deserializer.",

@@ -31,11 +31,12 @@ "keywords": [

"dependencies": {
"@yume-chan/no-data-view": "^0.0.0-next-20240917062356"
"@yume-chan/async": "^4.0.2",
"@yume-chan/no-data-view": "^0.0.0-next-20241129144018"
},
"devDependencies": {
"@types/node": "^22.5.5",
"prettier": "^3.3.3",
"typescript": "^5.6.2",
"@yume-chan/test-runner": "^1.0.0",
"@types/node": "^22.10.0",
"prettier": "^3.4.1",
"typescript": "^5.7.2",
"@yume-chan/eslint-config": "^1.0.0",
"@yume-chan/tsconfig": "^1.0.0"
"@yume-chan/tsconfig": "^1.0.0",
"@yume-chan/test-runner": "^1.0.0"
},

@@ -42,0 +43,0 @@ "scripts": {

@@ -5,3 +5,2 @@ # @yume-chan/struct

cspell: ignore Codecov
cspell: ignore uint8arraystring
-->

@@ -17,2 +16,4 @@

The new API is inspired by [TypeGPU](https://docs.swmansion.com/TypeGPU/) which improves DX and tree-shaking.
**WARNING:** The public API is UNSTABLE. Open a GitHub discussion if you have any questions.

@@ -29,722 +30,95 @@

```ts
import Struct from "@yume-chan/struct";
import { struct, u8, u16, s32, buffer, string } from "@yume-chan/struct";
const MyStruct = new Struct({ littleEndian: true })
.int8("foo")
.int64("bar")
.int32("bazLength")
.string("baz", { lengthField: "bazLength" });
const Message = struct(
{
a: u8,
b: u16,
c: s32,
d: buffer(4), // Fixed length Uint8Array
e: buffer("b"), // Use value of `b` as length
f: buffer(u32), // `u32` length prefix
g: buffer(4, {
// Custom conversion between `Uint8Array` and other types
convert(value: Uint8Array) {
return value[0];
},
back(value: number) {
return new Uint8Array([value, 0, 0, 0]);
},
}),
h: string(64), // `string` is an alias to `buffer` with UTF-8 string conversion
},
{ littleEndian: true },
);
const value = await MyStruct.deserialize(stream);
value.foo; // number
value.bar; // bigint
value.bazLength; // number
value.baz; // string
// Custom reader
const reader = {
position: 0,
readExactly(length) {
const slice = new Uint8Array(100).slice(
this.position,
this.position + length,
);
this.position += length;
return slice;
},
};
const buffer = MyStruct.serialize({
foo: 42,
bar: 42n,
// `bazLength` automatically set to `baz`'s byte length
baz: "Hello, World!",
});
```
const message1 = Message.deserialize(reader); // If `reader.readExactly` is synchronous, `deserialize` is also synchronous
const message2 = await Message.deserialize(reader); // If `reader.readExactly` is asynchronous, so do `deserialize`
<!-- cspell: disable -->
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Compatibility](#compatibility)
- [Basic usage](#basic-usage)
- [`int64`/`uint64`](#int64uint64)
- [`string`](#string)
- [API](#api)
- [`placeholder`](#placeholder)
- [`Struct`](#struct)
- [`int8`/`uint8`/`int16`/`uint16`/`int32`/`uint32`](#int8uint8int16uint16int32uint32)
- [`int64`/`uint64`](#int64uint64-1)
- [`uint8Array`/`string`](#uint8arraystring)
- [`concat`](#concat)
- [`extra`](#extra)
- [`postDeserialize`](#postdeserialize)
- [`deserialize`](#deserialize)
- [`serialize`](#serialize)
- [Custom field type](#custom-field-type)
- [`Struct#field`](#structfield)
- [Relationship between types](#relationship-between-types)
- [`StructFieldDefinition`](#structfielddefinition)
- [`TValue`/`TOmitInitKey`](#tvaluetomitinitkey)
- [`getSize`](#getsize)
- [`create`](#create)
- [`deserialize`](#deserialize-1)
- [`StructFieldValue`](#structfieldvalue)
- [`getSize`](#getsize-1)
- [`get`/`set`](#getset)
- [`serialize`](#serialize-1)
<!-- cspell: enable -->
## Compatibility
Here is a list of features, their used APIs, and their compatibilities. If an optional feature is not actually used, its requirements can be ignored.
Some features can be polyfilled to support older runtimes, but this library doesn't ship with any polyfills.
### Basic usage
| API | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |
| -------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------- |
| [`Promise`][mdn_promise] | 32 | 12 | 29 | No | 8 | 0.12 |
| [`ArrayBuffer`][mdn_arraybuffer] | 7 | 12 | 4 | 10 | 5.1 | 0.10 |
| [`Uint8Array`][mdn_uint8array] | 7 | 12 | 4 | 10 | 5.1 | 0.10 |
| _Overall_ | 32 | 12 | 29 | No | 8 | 0.12 |
### [`int64`/`uint64`](#int64uint64-1)
| API | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |
| ---------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------- |
| [`BigInt`][mdn_bigint]<sup>1</sup> | 67 | 79 | 68 | No | 14 | 10.4 |
<sup>1</sup> Can't be polyfilled
### [`string`](#uint8arraystring)
| API | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |
| -------------------------------- | ------ | ---- | ------- | ----------------- | ------ | ------------------- |
| [`TextEncoder`][mdn_textencoder] | 38 | 79 | 19 | No | 10.1 | 8.3<sup>1</sup>, 11 |
<sup>1</sup> `TextEncoder` and `TextDecoder` are only available in `util` module. Need to be assigned to `globalThis`.
[mdn_promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
[mdn_arraybuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
[mdn_uint8array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
[mdn_bigint]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
[mdn_textencoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
## API
### `placeholder`
```ts
function placeholder<T>(): T {
return undefined as unknown as T;
}
const buffer: Uint8Array = Message.serialize(message1);
```
Returns a (fake) value of the given type. It's only useful in TypeScript, if you are using JavaScript, you shouldn't care about it.
## Custom field types
Many 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))
<details>
<summary>Detail explanation (click to expand)</summary>
When you have a generic method, where half generic parameters can be inferred.
```ts
declare function fn<A, B>(a: A): [A, B];
fn(42); // Expected 2 type arguments, but got 1. ts(2558)
```
import { Field, AsyncExactReadable, Struct, u8 } from "@yume-chan/struct";
Rather than force users repeat the type `A`, I declare a parameter for `B`.
const MyField: Field<number, never, never> = {
size: 4, // `0` if dynamically sized,
dynamicSize(value: number) {
// Optional, return dynamic size for value
return 0;
},
serialize(
value: number,
context: { buffer: Uint8Array; index: number; littleEndian: boolean },
) {
// Serialize value to `context.buffer` at `context.index`
},
deserialize(context: {
reader: AsyncExactReadable;
littleEndian: boolean;
}) {
// Deserialize value from `context.reader`
return 0;
},
};
```ts
declare function fn2<A, B>(a: A, b: B): [A, B];
const Message2 = struct({
a: u8,
b: MyField,
});
```
I don't really need a value of type `B`, I only require its type information
## Bipedal
```ts
fn2(42, placeholder<boolean>()); // fn2<number, boolean>
```
`bipedal` is a custom async helper that allows the same code to behave synchronously or asynchronously depends on the parameters.
</details><br/>
It's inspired by [gensync](https://github.com/loganfsmyth/gensync).
To 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.
The word `bipedal` refers to animals who walk using two legs.
**With that said, I don't expect you to specify any generic arguments manually when using this library.**
### `Struct`
```ts
class Struct<
TFields extends object = {},
TOmitInitKey extends string | number | symbol = never,
TExtra extends object = {},
TPostDeserialized = undefined,
> {
public constructor(options: Partial<StructOptions> = StructDefaultOptions);
}
```
import { bipedal } from "@yume-chan/struct";
Creates a new structure definition.
const fn = bipedal(function* (then, name: string | Promise<string>) {
name = yield* then(name);
return "Hello, " + name;
});
<details>
<summary>Generic parameters (click to expand)</summary>
This information was added to help you understand how does it work. These are considered as "internal state" so don't specify them manually.
1. `TFields`: Type of the Struct value. Modified when new fields are added.
2. `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.
3. `TExtra`: Type of extra fields. Modified when `extra` is called.
4. `TPostDeserialized`: State of the `postDeserialize` function. Modified when `postDeserialize` is called. Affects return type of `deserialize`
</details><br/>
**Parameters**
1. `options`:
- `littleEndian:boolean = false`: Whether all multi-byte fields in this struct are [little-endian encoded][wikipeida_endianess].
[wikipeida_endianess]: https://en.wikipedia.org/wiki/Endianness
#### `int8`/`uint8`/`int16`/`uint16`/`int32`/`uint32`
```ts
int32<
TName extends string | number | symbol,
TTypeScriptType = number
>(
name: TName,
_typescriptType?: TTypeScriptType
): Struct<
TFields & Record<TName, TTypeScriptType>,
TOmitInitKey,
TExtra,
TPostDeserialized
>;
fn("Simon"); // "Hello, Simon"
await fn(Promise.resolve("Simon")); // "Hello, Simon"
```
Appends an `int8`/`uint8`/`int16`/`uint16`/`int32`/`uint32` field to the `Struct`.
<details>
<summary>Generic parameters (click to expand)</summary>
1. `TName`: Literal type of the field's name.
2. `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.
</details><br/>
**Parameters**
1. `name`: (Required) Field name. Must be a string literal.
2. `_typescriptType`: Set field's type. See examples below.
**Note**
There is no generic constraints on the `TTypeScriptType`, because TypeScript doesn't allow casting enum types to `number`.
So it's technically possible to pass in an incompatible type (e.g. `string`). But obviously, it's a bad idea.
**Examples**
1. Append an `int32` field named `foo`
```ts
const struct = new Struct().int32("foo");
const value = await struct.deserialize(stream);
value.foo; // number
struct.serialize({}); // error: 'foo' is required
struct.serialize({ foo: "bar" }); // error: 'foo' must be a number
struct.serialize({ foo: 42 }); // ok
```
2. Set fields' type (can use [`placeholder` method](#placeholder))
```ts
enum MyEnum {
a,
b,
}
const struct = new Struct()
.int32("foo", placeholder<MyEnum>())
.int32("bar", MyEnum.a as const);
const value = await struct.deserialize(stream);
value.foo; // MyEnum
value.bar; // MyEnum.a
struct.serialize({ foo: 42, bar: MyEnum.a }); // error: 'foo' must be of type `MyEnum`
struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }); // error: 'bar' must be of type `MyEnum.a`
struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }); // ok
```
#### `int64`/`uint64`
```ts
int64<
TName extends string | number | symbol,
TTypeScriptType = bigint
>(
name: TName,
_typescriptType?: TTypeScriptType
): Struct<
TFields & Record<TName, TTypeScriptType>,
TOmitInitKey,
TExtra,
TPostDeserialized
>;
```
Appends an `int64`/`uint64` field to the `Struct`. The usage is same as `uint32`/`uint32`.
Requires native support for `BigInt`. Check [compatibility table](#compatibility) for more information.
#### `uint8Array`/`string`
```ts
uint8Array<
TName extends string | number | symbol,
TTypeScriptType = ArrayBuffer
>(
name: TName,
options: FixedLengthBufferLikeFieldOptions,
_typescriptType?: TTypeScriptType,
): Struct<
TFields & Record<TName, TTypeScriptType>,
TOmitInitKey,
TExtra,
TPostDeserialized
>;
uint8Array<
TName extends string | number | symbol,
TLengthField extends LengthField<TFields>,
TOptions extends VariableLengthBufferLikeFieldOptions<TFields, TLengthField>,
TTypeScriptType = ArrayBuffer,
>(
name: TName,
options: TOptions,
_typescriptType?: TTypeScriptType,
): Struct<
TFields & Record<TName, TTypeScriptType>,
TOmitInitKey | TLengthField,
TExtra,
TPostDeserialized
>;
```
Appends an `uint8Array`/`string` field to the `Struct`.
The `options` parameter defines its length, it supports two formats:
- `{ length: number }`: Presence of the `length` option indicates that it's a fixed length array.
- `{ 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.
#### `concat`
```ts
concat<
TOther extends Struct<any, any, any, any>
>(
other: TOther
): Struct<
TFields & TOther['fieldsType'],
TOmitInitKey | TOther['omitInitType'],
TExtra & TOther['extraType'],
TPostDeserialized
>;
```
Merges (flats) another `Struct`'s fields and extra fields into the current one.
**Examples**
1. Extending another `Struct`
```ts
const MyStructV1 = new Struct().int32("field1");
const MyStructV2 = new Struct().concat(MyStructV1).int32("field2");
const structV2 = await MyStructV2.deserialize(stream);
structV2.field1; // number
structV2.field2; // number
// Fields are flatten
```
2. Also possible in any order
```ts
const MyStructV1 = new Struct().int32("field1");
const MyStructV2 = new Struct().int32("field2").concat(MyStructV1);
const structV2 = await MyStructV2.deserialize(stream);
structV2.field1; // number
structV2.field2; // number
// Same result as above, but serialize/deserialize order is reversed
```
#### `extra`
```ts
extra<
T extends Record<
Exclude<
keyof T,
Exclude<
keyof T,
keyof TFields
>
>,
never
>
>(
value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>>
): Struct<
TFields,
TInit,
Overwrite<TExtra, T>,
TPostDeserialized
>;
```
Adds 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.
Multiple calls merge all extra fields together.
**Generic Parameters**
1. `T`: Type of the extra fields. The scary looking generic constraint is used to forbid overwriting any already existed fields.
**Parameters**
1. `value`: An object containing anything you want to add to Struct values. Accessors and methods are also allowed.
**Examples**
1. Add an extra field
```ts
const struct = new Struct().int32("foo").extra({
bar: "hello",
});
const value = await struct.deserialize(stream);
value.foo; // number
value.bar; // 'hello'
struct.serialize({ foo: 42 }); // ok
struct.serialize({ foo: 42, bar: "hello" }); // error: 'bar' is redundant
```
2. Add getters and methods. `this` in functions refers to the result object.
```ts
const struct = new Struct().int32("foo").extra({
get bar() {
// `this` is the result Struct value
return this.foo + 1;
},
logBar() {
// `this` also contains other extra fields
console.log(this.bar);
},
});
const value = await struct.deserialize(stream);
value.foo; // number
value.bar; // number
value.logBar();
```
#### `postDeserialize`
```ts
postDeserialize(): Struct<TFields, TOmitInitKey, TExtra, undefined>;
```
Remove any registered post-deserialization callback.
```ts
postDeserialize(
callback: (this: TFields, object: TFields) => never
): Struct<TFields, TOmitInitKey, TExtra, never>;
postDeserialize(
callback: (this: TFields, object: TFields) => void
): Struct<TFields, TOmitInitKey, TExtra, undefined>;
```
Registers (or replaces) a custom callback to be run after deserialized.
`this` in `callback`, along with the first parameter `object` will both be the deserialized Struct value.
A callback returning `never` (always throws errors) will change the return type of `deserialize` to `never`.
A 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.
```ts
postDeserialize<TPostSerialize>(
callback: (this: TFields, object: TFields) => TPostSerialize
): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;
```
Registers (or replaces) a custom callback to be run after deserialized.
A callback returning anything other than `undefined` will cause `deserialize` to return that value instead.
**Generic Parameters**
1. `TPostSerialize`: Type of the new result.
**Parameters**
1. `callback`: An function contains the custom logic to be run, optionally returns a new result. Or `undefined`, to remove any previously set `postDeserialize` callback.
**Examples**
1. Handle an "error" packet
```ts
// Say your protocol have an error packet,
// You want to throw a JavaScript Error when received such a packet,
// But you don't want to modify all receiving path
const struct = new Struct()
.int32("messageLength")
.string("message", { lengthField: "messageLength" })
.postDeserialize((value) => {
throw new Error(value.message);
});
```
2. Do anything you want
```ts
// I think this one doesn't need any code example
```
3. Replace result object
```ts
const struct1 = new Struct().int32("foo").postDeserialize((value) => {
return {
bar: value.foo,
};
});
const value = await struct.deserialize(stream);
value.foo; // error: not exist
value.bar; // number
```
#### `deserialize`
```ts
interface ExactReadable {
readonly position: number;
/**
* Read data from the underlying data source.
*
* The stream must return exactly `length` bytes or data. If that's not possible
* (due to end of file or other error condition), it must throw an {@link ExactReadableEndedError}.
*/
readExactly(length: number): Uint8Array;
}
interface AsyncExactReadable {
readonly position: number;
/**
* Read data from the underlying data source.
*
* The stream must return exactly `length` bytes or data. If that's not possible
* (due to end of file or other error condition), it must throw an {@link ExactReadableEndedError}.
*/
readExactly(length: number): ValueOrPromise<Uint8Array>;
}
deserialize(
stream: ExactReadable,
): TPostDeserialized extends undefined
? Overwrite<TExtra, TValue>
: TPostDeserialized
>;
deserialize(
stream: AsyncExactReadable,
): Promise<
TPostDeserialized extends undefined
? Overwrite<TExtra, TValue>
: TPostDeserialized
>
>;
```
Deserialize a struct value from `stream`.
It will be synchronous (returns a value) or asynchronous (returns a `Promise`) depending on the type of `stream`.
As the signature shows, if the `postDeserialize` callback returns any value, `deserialize` will return that value instead.
#### `serialize`
```ts
serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>): Uint8Array;
serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>, output: Uint8Array): number;
```
Serialize a struct value into an `Uint8Array`.
If an `output` is given, it will serialize the struct into it, and returns the number of bytes written.
## Custom field type
It's also possible to create your own field types.
### `Struct#field`
```ts
field<
TName extends string | number | symbol,
TDefinition extends StructFieldDefinition<any, any, any>
>(
name: TName,
definition: TDefinition
): Struct<
TFields & Record<TName, TDefinition['TValue']>,
TOmitInitKey | TDefinition['TOmitInitKey'],
TExtra,
TPostDeserialized
>;
```
Appends a `StructFieldDefinition` to the `Struct`.
All built-in field type methods are actually aliases to it. For example, calling
```ts
struct.int8("foo");
```
is same as
```ts
struct.field("foo", new NumberFieldDefinition(NumberFieldType.Int8));
```
### Relationship between types
- `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`: Definition 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()`.
### `StructFieldDefinition`
```ts
abstract class StructFieldDefinition<
TOptions = void,
TValue = unknown,
TOmitInitKey extends PropertyKey = never,
> {
public readonly options: TOptions;
public constructor(options: TOptions);
}
```
A field definition defines how to deserialize a field.
It'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.
#### `TValue`/`TOmitInitKey`
These 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.
#### `getSize`
```ts
abstract getSize(): number;
```
Derived classes must implement this method to return size (or minimal size if it's dynamic) of this field.
Actual size should be returned from `StructFieldValue#getSize`
#### `create`
```ts
abstract create(
options: Readonly<StructOptions>,
struct: StructValue,
value: TValue,
): StructFieldValue<this>;
```
Derived classes must implement this method to create its own field value instance for the current definition.
`Struct#serialize` will call this method, then call `StructFieldValue#serialize` to serialize one field value.
#### `deserialize`
```ts
abstract deserialize(
options: Readonly<StructOptions>,
stream: ExactReadable,
struct: StructValue,
): StructFieldValue<this>;
abstract deserialize(
options: Readonly<StructOptions>,
stream: AsyncExactReadable,
struct: StructValue,
): Promise<StructFieldValue<this>>;
```
Derived classes must implement this method to define how to deserialize a value from `stream`.
It must be synchronous (returns a value) or asynchronous (returns a `Promise`) depending on the type of `stream`.
Usually implementations should be:
1. Read required bytes from `stream`
2. Parse it to your type
3. Pass the value into your own `create` method
Sometimes, 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.
### `StructFieldValue`
```ts
abstract class StructFieldValue<
TDefinition extends StructFieldDefinition<any, any, any>
>
```
A field value defines how to serialize a field.
#### `getSize`
```ts
getSize(): number;
```
Gets size of this field. By default, it returns its `definition`'s size.
If this field's size can change based on some criteria, one must override `getSize` to return its actual size.
#### `get`/`set`
```ts
get(): TDefinition['TValue'];
set(value: TDefinition['TValue']): void;
```
Defines how to get or set this field's value. By default, it reads/writes its `value` field.
If one needs to manipulate other states when getting/setting values, they can override these methods.
#### `serialize`
```ts
abstract serialize(
array: Uint8Array,
offset: number
): void;
```
Derived classes must implement this method to serialize current value into `array`, from `offset`. It must not write more bytes than what its `getSize` returned.

@@ -13,7 +13,9 @@ declare global {

export * from "./basic/index.js";
export * from "./bipedal.js";
export * from "./buffer.js";
export * from "./field.js";
export * from "./number.js";
export * from "./readable.js";
export * from "./string.js";
export * from "./struct.js";
export { Struct as default } from "./struct.js";
export * from "./sync-promise.js";
export * from "./types/index.js";
export * from "./utils.js";

@@ -1,200 +0,39 @@

/* eslint-disable @typescript-eslint/no-explicit-any */
import type { MaybePromiseLike } from "@yume-chan/async";
import type {
AsyncExactReadable,
ExactReadable,
StructFieldDefinition,
StructFieldValue,
StructOptions,
} from "./basic/index.js";
import {
ExactReadableEndedError,
STRUCT_VALUE_SYMBOL,
StructDefaultOptions,
StructValue,
isStructValueInit,
} from "./basic/index.js";
import { SyncPromise } from "./sync-promise.js";
import type {
BufferFieldConverter,
FixedLengthBufferLikeFieldOptions,
LengthField,
VariableLengthBufferLikeFieldOptions,
} from "./types/index.js";
import {
BigIntFieldDefinition,
BigIntFieldVariant,
FixedLengthBufferLikeFieldDefinition,
NumberFieldDefinition,
NumberFieldVariant,
StringBufferFieldConverter,
Uint8ArrayBufferFieldConverter,
VariableLengthBufferLikeFieldDefinition,
} from "./types/index.js";
import type { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils.js";
import { bipedal } from "./bipedal.js";
import type { DeserializeContext, Field, SerializeContext } from "./field.js";
import type { AsyncExactReadable, ExactReadable } from "./readable.js";
import { ExactReadableEndedError } from "./readable.js";
export interface StructLike<TValue> {
deserialize(stream: ExactReadable | AsyncExactReadable): Promise<TValue>;
}
export type FieldsType<
T extends Record<string, Field<unknown, string, unknown>>,
> = {
[K in keyof T]: T[K] extends Field<infer TK, string, unknown> ? TK : never;
};
/**
* Extract the value type of the specified `Struct`
*/
export type StructValueType<T extends StructLike<unknown>> = Awaited<
ReturnType<T["deserialize"]>
export type StructInit<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends Struct<any, any, any>,
> = Omit<
FieldsType<T["fields"]>,
{
[K in keyof T["fields"]]: T["fields"][K] extends Field<
unknown,
infer U,
unknown
>
? U
: never;
}[keyof T["fields"]]
>;
/**
* Create a new `Struct` type with `TDefinition` appended
*/
type AddFieldDescriptor<
TFields extends object,
TOmitInitKey extends PropertyKey,
TExtra extends object,
TPostDeserialized,
TFieldName extends PropertyKey,
TDefinition extends StructFieldDefinition<unknown, unknown, PropertyKey>,
> = Identity<
Struct<
// Merge two types
// Evaluate immediately to optimize editor hover tooltip
Evaluate<TFields & Record<TFieldName, TDefinition["TValue"]>>,
// Merge two `TOmitInitKey`s
TOmitInitKey | TDefinition["TOmitInitKey"],
TExtra,
TPostDeserialized
>
>;
export type StructValue<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends Struct<any, any, any>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
> = T extends Struct<any, any, infer P> ? P : never;
/**
* Overload methods to add an array buffer like field
*/
interface ArrayBufferLikeFieldCreator<
TFields extends object,
TOmitInitKey extends PropertyKey,
TExtra extends object,
TPostDeserialized,
> {
/**
* Append a fixed-length array buffer like field to the `Struct`
*
* @param name Name of the field
* @param type `Array.SubType.ArrayBuffer` or `Array.SubType.String`
* @param options Fixed-length array options
* @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.
*/
<
TName extends PropertyKey,
TType extends BufferFieldConverter<unknown, unknown>,
TTypeScriptType = TType["TTypeScriptType"],
>(
name: TName,
type: TType,
options: FixedLengthBufferLikeFieldOptions,
typeScriptType?: TTypeScriptType,
): AddFieldDescriptor<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized,
TName,
FixedLengthBufferLikeFieldDefinition<
TType,
FixedLengthBufferLikeFieldOptions
>
>;
/**
* Append a variable-length array buffer like field to the `Struct`
*/
<
TName extends PropertyKey,
TType extends BufferFieldConverter<unknown, unknown>,
TOptions extends VariableLengthBufferLikeFieldOptions<TFields>,
TTypeScriptType = TType["TTypeScriptType"],
>(
name: TName,
type: TType,
options: TOptions,
typeScriptType?: TTypeScriptType,
): AddFieldDescriptor<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized,
TName,
VariableLengthBufferLikeFieldDefinition<TType, TOptions>
>;
}
/**
* Similar to `ArrayBufferLikeFieldCreator`, but bind to `TType`
*/
interface BoundArrayBufferLikeFieldDefinitionCreator<
TFields extends object,
TOmitInitKey extends PropertyKey,
TExtra extends object,
TPostDeserialized,
TType extends BufferFieldConverter<unknown, unknown>,
> {
<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,
TOptions extends VariableLengthBufferLikeFieldOptions<
TFields,
LengthField<TFields>
>,
TTypeScriptType = TType["TTypeScriptType"],
>(
name: TName,
options: TOptions,
typeScriptType?: TTypeScriptType,
): AddFieldDescriptor<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized,
TName,
VariableLengthBufferLikeFieldDefinition<
TType,
TOptions,
TTypeScriptType
>
>;
}
export type StructPostDeserialized<TFields, TPostDeserialized> = (
this: TFields,
object: TFields,
) => TPostDeserialized;
export type StructDeserializedResult<
TFields extends object,
TExtra extends object,
TPostDeserialized,
> = TPostDeserialized extends undefined
? Overwrite<TExtra, TFields>
: TPostDeserialized;
export class StructDeserializeError extends Error {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}

@@ -217,490 +56,143 @@ }

interface StructDefinition<
TFields extends object,
TOmitInitKey extends PropertyKey,
TExtra extends object,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type StructLike<T> = Struct<any, any, T>;
export interface Struct<
T extends Record<string, Field<unknown, string, Partial<FieldsType<T>>>>,
Extra extends Record<PropertyKey, unknown> | undefined = undefined,
PostDeserialize = FieldsType<T> & Extra,
> {
readonly TFields: TFields;
fields: T;
size: number;
extra: Extra;
readonly TOmitInitKey: TOmitInitKey;
serialize(runtimeStruct: StructInit<this>): Uint8Array;
serialize(runtimeStruct: StructInit<this>, buffer: Uint8Array): number;
readonly TExtra: TExtra;
readonly TInit: Evaluate<Omit<TFields, TOmitInitKey>>;
deserialize(reader: ExactReadable): PostDeserialize;
deserialize(reader: AsyncExactReadable): MaybePromiseLike<PostDeserialize>;
}
export class Struct<
TFields extends object = Record<never, never>,
TOmitInitKey extends PropertyKey = never,
TExtra extends object = Record<never, never>,
TPostDeserialized = undefined,
> implements
StructLike<
StructDeserializedResult<TFields, TExtra, TPostDeserialized>
>
{
readonly TFields!: TFields;
/* #__NO_SIDE_EFFECTS__ */
export function struct<
T extends Record<string, Field<unknown, string, Partial<FieldsType<T>>>>,
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
Extra extends Record<PropertyKey, unknown> = {},
PostDeserialize = FieldsType<T> & Extra,
>(
fields: T,
options: {
littleEndian?: boolean;
extra?: Extra & ThisType<FieldsType<T>>;
postDeserialize?: (
this: FieldsType<T> & Extra,
fields: FieldsType<T> & Extra,
) => PostDeserialize;
},
): Struct<T, Extra, PostDeserialize> {
const fieldList = Object.entries(fields);
const size = fieldList.reduce((sum, [, field]) => sum + field.size, 0);
readonly TOmitInitKey!: TOmitInitKey;
const littleEndian = !!options.littleEndian;
const extra = options.extra
? Object.getOwnPropertyDescriptors(options.extra)
: undefined;
readonly TExtra!: TExtra;
return {
fields,
size,
extra: options.extra,
serialize(
runtimeStruct: StructInit<Struct<T, Extra, PostDeserialize>>,
buffer?: Uint8Array,
): Uint8Array | number {
for (const [key, field] of fieldList) {
if (key in runtimeStruct) {
field.preSerialize?.(
runtimeStruct[key as never],
runtimeStruct as never,
);
}
}
readonly TInit!: Evaluate<Omit<TFields, TOmitInitKey>>;
const sizes = fieldList.map(
([key, field]) =>
field.dynamicSize?.(runtimeStruct[key as never]) ??
field.size,
);
const size = sizes.reduce((sum, size) => sum + size, 0);
readonly TDeserializeResult!: StructDeserializedResult<
TFields,
TExtra,
TPostDeserialized
>;
let externalBuffer = false;
if (buffer) {
if (buffer.length < size) {
throw new Error("Buffer too small");
}
readonly options: Readonly<StructOptions>;
externalBuffer = true;
} else {
buffer = new Uint8Array(size);
}
#size = 0;
/**
* Gets the static size (exclude fields that can change size at runtime)
*/
get size() {
return this.#size;
}
const context: SerializeContext = {
buffer,
index: 0,
littleEndian,
};
for (const [index, [key, field]] of fieldList.entries()) {
field.serialize(runtimeStruct[key as never], context);
context.index += sizes[index]!;
}
#fields: [
name: PropertyKey,
definition: StructFieldDefinition<unknown, unknown, PropertyKey>,
][] = [];
get fields(): readonly [
name: PropertyKey,
definition: StructFieldDefinition<unknown, unknown, PropertyKey>,
][] {
return this.#fields;
}
#extra: Record<PropertyKey, unknown> = {};
#postDeserialized?: StructPostDeserialized<never, unknown> | undefined;
constructor(options?: Partial<Readonly<StructOptions>>) {
this.options = { ...StructDefaultOptions, ...options };
}
/**
* Appends a `StructFieldDefinition` to the `Struct
*/
field<
TName extends PropertyKey,
TDefinition extends StructFieldDefinition<
unknown,
unknown,
PropertyKey
>,
>(
name: TName,
definition: TDefinition,
): AddFieldDescriptor<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized,
TName,
TDefinition
> {
for (const field of this.#fields) {
if (field[0] === name) {
// Convert Symbol to string
const nameString = String(name);
throw new Error(
`This struct already have a field with name '${nameString}'`,
);
if (externalBuffer) {
return size;
} else {
return buffer;
}
}
},
deserialize: bipedal(function* (
this: Struct<T, Extra, PostDeserialize>,
then,
reader: AsyncExactReadable,
) {
const startPosition = reader.position;
this.#fields.push([name, definition]);
const runtimeStruct = {} as Record<string, unknown>;
const context: DeserializeContext<Partial<FieldsType<T>>> = {
reader,
runtimeStruct: runtimeStruct as never,
littleEndian: littleEndian,
};
const size = definition.getSize();
this.#size += size;
// Force cast `this` to another type
return this as never;
}
/**
* Merges (flats) another `Struct`'s fields and extra fields into this one.
*
* `other`'s `postDeserialize` will be ignored.
*/
concat<TOther extends StructDefinition<object, PropertyKey, object>>(
other: TOther,
): Struct<
TFields & TOther["TFields"],
TOmitInitKey | TOther["TOmitInitKey"],
TExtra & TOther["TExtra"],
TPostDeserialized
> {
if (!(other instanceof Struct)) {
throw new TypeError("The other value must be a `Struct` instance");
}
for (const field of other.#fields) {
this.#fields.push(field);
}
this.#size += other.#size;
Object.defineProperties(
this.#extra,
Object.getOwnPropertyDescriptors(other.#extra),
);
return this as never;
}
#number<
TName extends PropertyKey,
TType extends NumberFieldVariant = NumberFieldVariant,
TTypeScriptType = number,
>(name: TName, type: TType, typeScriptType?: TTypeScriptType) {
return this.field(
name,
new NumberFieldDefinition(type, typeScriptType),
);
}
/**
* Appends an `int8` field to the `Struct`
*/
int8<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.#number(name, NumberFieldVariant.Int8, typeScriptType);
}
/**
* Appends an `uint8` field to the `Struct`
*/
uint8<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.#number(name, NumberFieldVariant.Uint8, typeScriptType);
}
/**
* Appends an `int16` field to the `Struct`
*/
int16<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.#number(name, NumberFieldVariant.Int16, typeScriptType);
}
/**
* Appends an `uint16` field to the `Struct`
*/
uint16<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.#number(name, NumberFieldVariant.Uint16, typeScriptType);
}
/**
* Appends an `int32` field to the `Struct`
*/
int32<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.#number(name, NumberFieldVariant.Int32, typeScriptType);
}
/**
* Appends an `uint32` field to the `Struct`
*/
uint32<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.#number(name, NumberFieldVariant.Uint32, typeScriptType);
}
#bigint<
TName extends PropertyKey,
TType extends BigIntFieldVariant = BigIntFieldVariant,
TTypeScriptType = TType["TTypeScriptType"],
>(name: TName, type: TType, typeScriptType?: TTypeScriptType) {
return this.field(
name,
new BigIntFieldDefinition(type, typeScriptType),
);
}
/**
* Appends an `int64` field to the `Struct`
*
* Requires native `BigInt` support
*/
int64<
TName extends PropertyKey,
TTypeScriptType = BigIntFieldVariant["TTypeScriptType"],
>(name: TName, typeScriptType?: TTypeScriptType) {
return this.#bigint(name, BigIntFieldVariant.Int64, typeScriptType);
}
/**
* Appends an `uint64` field to the `Struct`
*
* Requires native `BigInt` support
*/
uint64<
TName extends PropertyKey,
TTypeScriptType = BigIntFieldVariant["TTypeScriptType"],
>(name: TName, typeScriptType?: TTypeScriptType) {
return this.#bigint(name, BigIntFieldVariant.Uint64, typeScriptType);
}
#arrayBufferLike: ArrayBufferLikeFieldCreator<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized
> = (
name: PropertyKey,
type: BufferFieldConverter,
options:
| FixedLengthBufferLikeFieldOptions
| VariableLengthBufferLikeFieldOptions,
): never => {
if ("length" in options) {
return this.field(
name,
new FixedLengthBufferLikeFieldDefinition(type, options),
) as never;
} else {
return this.field(
name,
new VariableLengthBufferLikeFieldDefinition(type, options),
) as never;
}
};
uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized,
Uint8ArrayBufferFieldConverter
> = (
name: PropertyKey,
options: unknown,
typeScriptType: unknown,
): never => {
return this.#arrayBufferLike(
name,
Uint8ArrayBufferFieldConverter.Instance,
options as never,
typeScriptType,
) as never;
};
string: BoundArrayBufferLikeFieldDefinitionCreator<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized,
StringBufferFieldConverter
> = (
name: PropertyKey,
options: unknown,
typeScriptType: unknown,
): never => {
return this.#arrayBufferLike(
name,
StringBufferFieldConverter.Instance,
options as never,
typeScriptType,
) as never;
};
/**
* Adds some extra properties into every `Struct` value.
*
* Extra properties will not affect serialize or deserialize process.
*
* Multiple calls to `extra` will merge all properties together.
*
* @param value
* An object containing properties to be added to the result value. Accessors and methods are also allowed.
*/
extra<
T extends Record<
// This trick disallows any keys that are already in `TValue`
Exclude<keyof T, Exclude<keyof T, keyof TFields>>,
never
>,
>(
value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>>,
): Struct<TFields, TOmitInitKey, Overwrite<TExtra, T>, TPostDeserialized> {
Object.defineProperties(
this.#extra,
Object.getOwnPropertyDescriptors(value),
);
return this as never;
}
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A callback returning `never` (always throw an error)
* will also change the return type of `deserialize` to `never`.
*/
postDeserialize(
callback: StructPostDeserialized<TFields, never>,
): Struct<TFields, TOmitInitKey, TExtra, never>;
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A 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.
*/
postDeserialize(
callback?: StructPostDeserialized<TFields, void>,
): Struct<TFields, TOmitInitKey, TExtra, undefined>;
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A callback returning anything other than `undefined`
* will `deserialize` to return that object instead.
*/
postDeserialize<TPostSerialize>(
callback?: StructPostDeserialized<TFields, TPostSerialize>,
): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;
postDeserialize(callback?: StructPostDeserialized<TFields, unknown>) {
this.#postDeserialized = callback;
return this as never;
}
/**
* Deserialize a struct value from `stream`.
*/
deserialize(
stream: ExactReadable,
): StructDeserializedResult<TFields, TExtra, TPostDeserialized>;
deserialize(
stream: AsyncExactReadable,
): Promise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>>;
deserialize(
stream: ExactReadable | AsyncExactReadable,
): ValueOrPromise<
StructDeserializedResult<TFields, TExtra, TPostDeserialized>
> {
const structValue = new StructValue(this.#extra);
let promise = SyncPromise.resolve();
const startPosition = stream.position;
for (const [name, definition] of this.#fields) {
promise = promise
.then(() =>
definition.deserialize(this.options, stream, structValue),
)
.then(
(fieldValue) => {
structValue.set(name, fieldValue);
},
(e) => {
if (!(e instanceof ExactReadableEndedError)) {
throw e;
}
if (stream.position === startPosition) {
throw new StructEmptyError();
} else {
throw new StructNotEnoughDataError();
}
},
);
}
return promise
.then(() => {
const value = structValue.value;
// Run `postDeserialized`
if (this.#postDeserialized) {
const override = this.#postDeserialized.call(
value as never,
value as never,
try {
for (const [key, field] of fieldList) {
runtimeStruct[key] = yield* then(
field.deserialize(context),
);
// If it returns a new value, use that as result
// Otherwise it only inspects/mutates the object in place.
if (override !== undefined) {
return override as never;
}
}
} catch (e) {
if (!(e instanceof ExactReadableEndedError)) {
throw e;
}
return value as never;
})
.valueOrPromise();
}
/**
* Serialize a struct value to a buffer.
* @param init Fields of the struct
* @param output The buffer to serialize the struct to. It must be large enough to hold the entire struct. If not provided, a new buffer will be created.
* @returns A view of `output` that contains the serialized struct, or a new buffer if `output` is not provided.
*/
serialize(
init: Evaluate<Omit<TFields, TOmitInitKey>>,
output?: Uint8Array,
): Uint8Array {
let structValue: StructValue;
if (isStructValueInit(init)) {
structValue = init[STRUCT_VALUE_SYMBOL];
for (const [key, value] of Object.entries(init)) {
const fieldValue = structValue.get(key);
if (fieldValue) {
fieldValue.set(value);
if (reader.position === startPosition) {
throw new StructEmptyError();
} else {
throw new StructNotEnoughDataError();
}
}
} else {
structValue = new StructValue({});
for (const [name, definition] of this.#fields) {
const fieldValue = definition.create(
this.options,
structValue,
(init as Record<PropertyKey, unknown>)[name],
if (extra) {
Object.defineProperties(runtimeStruct, extra);
}
if (options.postDeserialize) {
return options.postDeserialize.call(
runtimeStruct as never,
runtimeStruct as never,
);
structValue.set(name, fieldValue);
} else {
return runtimeStruct;
}
}
let structSize = 0;
const fieldsInfo: {
fieldValue: StructFieldValue<any>;
size: number;
}[] = [];
for (const [name] of this.#fields) {
const fieldValue = structValue.get(name);
const size = fieldValue.getSize();
fieldsInfo.push({ fieldValue, size });
structSize += size;
}
if (!output) {
output = new Uint8Array(structSize);
} else if (output.length < structSize) {
throw new TypeError("Output buffer is too small");
}
let offset = 0;
for (const { fieldValue, size } of fieldsInfo) {
fieldValue.serialize(output, offset);
offset += size;
}
if (output.length !== structSize) {
return output.subarray(0, structSize);
} else {
return output;
}
}
}),
} as never;
}

@@ -1,56 +0,1 @@

/**
* When evaluating a very complex generic type alias,
* tell TypeScript to use `T`, instead of current type alias' name, as the result type name
*
* Example:
*
* ```ts
* type WithIdentity<T> = Identity<SomeType<T>>;
* type WithoutIdentity<T> = SomeType<T>;
*
* type WithIdentityResult = WithIdentity<number>;
* // Hover on this one shows `SomeType<number>`
*
* type WithoutIdentityResult = WithoutIdentity<number>;
* // Hover on this one shows `WithoutIdentity<number>`
* ```
*/
export type Identity<T> = T;
/**
* Collapse an intersection type (`{ foo: string } & { bar: number }`) to a simple type (`{ foo: string, bar: number }`)
*/
export type Evaluate<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
/**
* Overwrite fields in `TBase` with fields in `TNew`
*/
export type Overwrite<TBase extends object, TNew extends object> = Evaluate<
Omit<TBase, keyof TNew> & TNew
>;
/**
* Remove fields with `never` type
*/
export type OmitNever<T> = Pick<
T,
{ [K in keyof T]: [T[K]] extends [never] ? never : K }[keyof T]
>;
/**
* Extract keys of fields in `T` that has type `TValue`
*/
export type KeysOfType<T, TValue> = {
[TKey in keyof T]: T[TKey] extends TValue ? TKey : never;
}[keyof T];
export type ValueOrPromise<T> = T | PromiseLike<T>;
/**
* Returns a (fake) value of the given type.
*/
export function placeholder<T>(): T {
return undefined as unknown as T;
}
// This library can't use `@types/node` or `lib: dom`

@@ -64,22 +9,20 @@ // because they will pollute the global scope

// eslint-disable-next-line @typescript-eslint/no-unused-vars
declare class TextEncoderType {
constructor();
interface TextEncoder {
encode(input: string): Uint8Array;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
declare class TextDecoderType {
constructor();
decode(buffer: ArrayBufferView | ArrayBuffer): string;
interface TextDecoder {
decode(
buffer?: ArrayBufferView | ArrayBuffer,
options?: { stream?: boolean },
): string;
}
interface GlobalExtension {
TextEncoder: typeof TextEncoderType;
TextDecoder: typeof TextDecoderType;
TextEncoder: new () => TextEncoder;
TextDecoder: new () => TextDecoder;
}
const { TextEncoder, TextDecoder } = globalThis as unknown as GlobalExtension;
export const { TextEncoder, TextDecoder } =
globalThis as unknown as GlobalExtension;

@@ -94,2 +37,3 @@ const SharedEncoder = /* #__PURE__ */ new TextEncoder();

/* #__NO_SIDE_EFFECTS__ */
export function decodeUtf8(buffer: ArrayBufferView | ArrayBuffer): string {

@@ -96,0 +40,0 @@ // `TextDecoder` has internal states in stream mode,

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc