refactor(struct): improve tree-shaking

This commit is contained in:
Simon Chan 2024-11-01 22:06:18 +08:00
parent e8d59b232e
commit c91baa9116
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
32 changed files with 266 additions and 266 deletions

View file

@ -1,12 +1,12 @@
import { BufferedReadableStream } from "@yume-chan/stream-extra";
import type { StructValue } from "@yume-chan/struct";
import { buffer, Struct, StructEmptyError, u32 } from "@yume-chan/struct";
import { buffer, struct, StructEmptyError, u32 } from "@yume-chan/struct";
import type { Adb } from "../adb.js";
const Version = new Struct({ version: u32 }, { littleEndian: true });
const Version = struct({ version: u32 }, { littleEndian: true });
export const AdbFrameBufferV1 = new Struct(
export const AdbFrameBufferV1 = struct(
{
bpp: u32,
size: u32,
@ -27,7 +27,7 @@ export const AdbFrameBufferV1 = new Struct(
export type AdbFrameBufferV1 = StructValue<typeof AdbFrameBufferV1>;
export const AdbFrameBufferV2 = new Struct(
export const AdbFrameBufferV2 = struct(
{
bpp: u32,
colorSpace: u32,

View file

@ -2,10 +2,10 @@
import { BufferedReadableStream } from "@yume-chan/stream-extra";
import {
ExactReadableEndedError,
Struct,
encodeUtf8,
ExactReadableEndedError,
string,
struct,
} from "@yume-chan/struct";
import type { Adb, AdbIncomingSocketHandler } from "../adb.js";
@ -19,7 +19,7 @@ export interface AdbForwardListener {
remoteName: string;
}
const AdbReverseStringResponse = new Struct(
const AdbReverseStringResponse = struct(
{
length: string(4),
content: string({
@ -38,7 +38,6 @@ const AdbReverseStringResponse = new Struct(
export class AdbReverseError extends Error {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}
}
@ -50,7 +49,9 @@ export class AdbReverseNotSupportedError extends AdbReverseError {
}
}
const AdbReverseErrorResponse = new Struct(AdbReverseStringResponse.fields, {
const AdbReverseErrorResponse = struct(
/* #__PURE__ */ (() => AdbReverseStringResponse.fields)(),
{
littleEndian: true,
postDeserialize: (value) => {
// https://issuetracker.google.com/issues/37066218
@ -62,7 +63,8 @@ const AdbReverseErrorResponse = new Struct(AdbReverseStringResponse.fields, {
throw new AdbReverseError(value.content);
}
},
});
},
);
// Like `hexToNumber`, it's much faster than first converting `buffer` to a string
function decimalToNumber(buffer: Uint8Array) {

View file

@ -11,7 +11,7 @@ import {
WritableStream,
} from "@yume-chan/stream-extra";
import type { StructValue } from "@yume-chan/struct";
import { Struct, buffer, u32, u8 } from "@yume-chan/struct";
import { buffer, struct, u32, u8 } from "@yume-chan/struct";
import type { Adb, AdbSocket } from "../../../adb.js";
import { AdbFeature } from "../../../features.js";
@ -32,7 +32,7 @@ export type AdbShellProtocolId =
(typeof AdbShellProtocolId)[keyof typeof AdbShellProtocolId];
// This packet format is used in both directions.
export const AdbShellProtocolPacket = new Struct(
export const AdbShellProtocolPacket = struct(
{
id: u8.as<AdbShellProtocolId>(),
data: buffer(u32),

View file

@ -1,5 +1,5 @@
import type { StructValue } from "@yume-chan/struct";
import { Struct, string, u32 } from "@yume-chan/struct";
import { string, struct, u32 } from "@yume-chan/struct";
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js";
@ -15,21 +15,21 @@ export interface AdbSyncEntry extends AdbSyncStat {
name: string;
}
export const AdbSyncEntryResponse = new Struct(
{
export const AdbSyncEntryResponse = struct(
/* #__PURE__ */ (() => ({
...AdbSyncLstatResponse.fields,
name: string(u32),
},
}))(),
{ littleEndian: true, extra: AdbSyncLstatResponse.extra },
);
export type AdbSyncEntryResponse = StructValue<typeof AdbSyncEntryResponse>;
export const AdbSyncEntry2Response = new Struct(
{
export const AdbSyncEntry2Response = struct(
/* #__PURE__ */ (() => ({
...AdbSyncStatResponse.fields,
name: string(u32),
},
}))(),
{ littleEndian: true, extra: AdbSyncStatResponse.extra },
);

View file

@ -1,13 +1,13 @@
import type { ReadableStream } from "@yume-chan/stream-extra";
import { PushReadableStream } from "@yume-chan/stream-extra";
import type { StructValue } from "@yume-chan/struct";
import { buffer, Struct, u32 } from "@yume-chan/struct";
import { buffer, struct, u32 } from "@yume-chan/struct";
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
import { adbSyncReadResponses, AdbSyncResponseId } from "./response.js";
import type { AdbSyncSocket } from "./socket.js";
export const AdbSyncDataResponse = new Struct(
export const AdbSyncDataResponse = struct(
{ data: buffer(u32) },
{ littleEndian: true },
);

View file

@ -4,7 +4,7 @@ import {
DistributionStream,
MaybeConsumable,
} from "@yume-chan/stream-extra";
import { Struct, u32 } from "@yume-chan/struct";
import { struct, u32 } from "@yume-chan/struct";
import { NOOP } from "../../utils/index.js";
@ -25,7 +25,7 @@ export interface AdbSyncPushV1Options {
packetSize?: number;
}
export const AdbSyncOkResponse = new Struct(
export const AdbSyncOkResponse = struct(
{ unused: u32 },
{ littleEndian: true },
);
@ -114,7 +114,7 @@ export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
dryRun?: boolean;
}
export const AdbSyncSendV2Request = new Struct(
export const AdbSyncSendV2Request = struct(
{ id: u32, mode: u32, flags: u32.as<AdbSyncSendV2Flags>() },
{ littleEndian: true },
);

View file

@ -1,4 +1,4 @@
import { encodeUtf8, Struct, u32 } from "@yume-chan/struct";
import { encodeUtf8, struct, u32 } from "@yume-chan/struct";
import { adbSyncEncodeId } from "./response.js";
@ -15,7 +15,7 @@ export const AdbSyncRequestId = {
Receive: adbSyncEncodeId("RECV"),
} as const;
export const AdbSyncNumberRequest = new Struct(
export const AdbSyncNumberRequest = struct(
{ id: u32, arg: u32 },
{ littleEndian: true },
);

View file

@ -1,6 +1,6 @@
import { getUint32LittleEndian } from "@yume-chan/no-data-view";
import type { AsyncExactReadable, StructLike } from "@yume-chan/struct";
import { Struct, decodeUtf8, string, u32 } from "@yume-chan/struct";
import { decodeUtf8, string, struct, u32 } from "@yume-chan/struct";
function encodeAsciiUnchecked(value: string): Uint8Array {
const result = new Uint8Array(value.length);
@ -36,7 +36,7 @@ export const AdbSyncResponseId = {
export class AdbSyncError extends Error {}
export const AdbSyncFailResponse = new Struct(
export const AdbSyncFailResponse = struct(
{ message: string(u32) },
{
littleEndian: true,

View file

@ -1,5 +1,5 @@
import type { StructValue } from "@yume-chan/struct";
import { Struct, u32, u64 } from "@yume-chan/struct";
import { struct, u32, u64 } from "@yume-chan/struct";
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js";
@ -27,7 +27,7 @@ export interface AdbSyncStat {
ctime?: bigint;
}
export const AdbSyncLstatResponse = new Struct(
export const AdbSyncLstatResponse = struct(
{ mode: u32, size: u32, mtime: u32 },
{
littleEndian: true,
@ -86,7 +86,7 @@ const AdbSyncStatErrorName =
]),
);
export const AdbSyncStatResponse = new Struct(
export const AdbSyncStatResponse = struct(
{
error: u32.as<AdbSyncStatErrorCode>(),
dev: u64,

View file

@ -1,6 +1,6 @@
import { Consumable, TransformStream } from "@yume-chan/stream-extra";
import type { StructInit, StructValue } from "@yume-chan/struct";
import { buffer, s32, Struct, u32 } from "@yume-chan/struct";
import { buffer, s32, struct, u32 } from "@yume-chan/struct";
export const AdbCommand = {
Auth: 0x48545541, // 'AUTH'
@ -13,7 +13,7 @@ export const AdbCommand = {
export type AdbCommand = (typeof AdbCommand)[keyof typeof AdbCommand];
export const AdbPacketHeader = new Struct(
export const AdbPacketHeader = struct(
{
command: u32,
arg0: u32,
@ -29,8 +29,11 @@ export type AdbPacketHeader = StructValue<typeof AdbPacketHeader>;
type AdbPacketHeaderInit = StructInit<typeof AdbPacketHeader>;
export const AdbPacket = new Struct(
{ ...AdbPacketHeader.fields, payload: buffer("payloadLength") },
export const AdbPacket = struct(
/* #__PURE__ */ (() => ({
...AdbPacketHeader.fields,
payload: buffer("payloadLength"),
}))(),
{ littleEndian: true },
);

View file

@ -11,7 +11,7 @@ import {
WritableStream,
} from "@yume-chan/stream-extra";
import type { AsyncExactReadable, StructValue } from "@yume-chan/struct";
import { Struct, decodeUtf8, u16, u32 } from "@yume-chan/struct";
import { decodeUtf8, struct, u16, u32 } from "@yume-chan/struct";
// `adb logcat` is an alias to `adb shell logcat`
// so instead of adding to core library, it's implemented here
@ -99,7 +99,7 @@ export interface LogcatOptions {
const NANOSECONDS_PER_SECOND = /* #__PURE__ */ BigInt(1e9);
// https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/include/log/log_read.h;l=39;drc=82b5738732161dbaafb2e2f25cce19cd26b9157d
export const LoggerEntry = new Struct(
export const LoggerEntry = struct(
{
payloadSize: u16,
headerSize: u16,

View file

@ -1,7 +1,7 @@
import type { StructInit } from "@yume-chan/struct";
import { Struct, u8 } from "@yume-chan/struct";
import { struct, u8 } from "@yume-chan/struct";
export const EmptyControlMessage = new Struct(
export const EmptyControlMessage = struct(
{ type: u8 },
{ littleEndian: false },
);

View file

@ -1,5 +1,5 @@
import type { StructInit } from "@yume-chan/struct";
import { Struct, u32, u8 } from "@yume-chan/struct";
import { struct, u32, u8 } from "@yume-chan/struct";
export enum AndroidKeyEventAction {
Down = 0,
@ -206,7 +206,7 @@ export enum AndroidKeyCode {
AndroidPaste,
}
export const ScrcpyInjectKeyCodeControlMessage = new Struct(
export const ScrcpyInjectKeyCodeControlMessage = struct(
{
type: u8,
action: u8.as<AndroidKeyEventAction>(),

View file

@ -1,7 +1,7 @@
import type { StructInit } from "@yume-chan/struct";
import { string, Struct, u32, u8 } from "@yume-chan/struct";
import { string, struct, u32, u8 } from "@yume-chan/struct";
export const ScrcpyInjectTextControlMessage = new Struct(
export const ScrcpyInjectTextControlMessage = struct(
{ type: u8, text: string(u32) },
{ littleEndian: false },
);

View file

@ -1,5 +1,5 @@
import type { StructInit } from "@yume-chan/struct";
import { Struct, u8 } from "@yume-chan/struct";
import { struct, u8 } from "@yume-chan/struct";
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/SurfaceControl.java;l=659;drc=20303e05bf73796124ab70a279cf849b61b97905
export const AndroidScreenPowerMode = {
@ -10,7 +10,7 @@ export const AndroidScreenPowerMode = {
export type AndroidScreenPowerMode =
(typeof AndroidScreenPowerMode)[keyof typeof AndroidScreenPowerMode];
export const ScrcpySetScreenPowerModeControlMessage = new Struct(
export const ScrcpySetScreenPowerModeControlMessage = struct(
{ type: u8, mode: u8.as<AndroidScreenPowerMode>() },
{ littleEndian: false },
);

View file

@ -1,11 +1,6 @@
import type { CodecOptions } from "./codec-options.js";
export enum ScrcpyLogLevel1_16 {
Debug = "debug",
Info = "info",
Warn = "warn",
Error = "error",
}
export type ScrcpyLogLevel1_16 = "debug" | "info" | "warn" | "error";
export enum ScrcpyVideoOrientation1_16 {
Unlocked = -1,

View file

@ -1,5 +1,5 @@
import type { StructInit } from "@yume-chan/struct";
import { Struct, buffer, string, u16, u32, u64, u8 } from "@yume-chan/struct";
import { buffer, string, struct, u16, u32, u64, u8 } from "@yume-chan/struct";
import type { AndroidMotionEventAction } from "../../control/index.js";
import {
@ -24,14 +24,14 @@ export const SCRCPY_CONTROL_MESSAGE_TYPES_1_16: readonly ScrcpyControlMessageTyp
/* 10 */ ScrcpyControlMessageType.RotateDevice,
];
export const ScrcpyMediaStreamRawPacket = new Struct(
export const ScrcpyMediaStreamRawPacket = struct(
{ pts: u64, data: buffer(u32) },
{ littleEndian: false },
);
export const SCRCPY_MEDIA_PACKET_FLAG_CONFIG = 1n << 63n;
export const ScrcpyInjectTouchControlMessage1_16 = new Struct(
export const ScrcpyInjectTouchControlMessage1_16 = struct(
{
type: u8,
action: u8.as<AndroidMotionEventAction>(),
@ -52,7 +52,7 @@ export type ScrcpyInjectTouchControlMessage1_16 = StructInit<
export const ScrcpyBackOrScreenOnControlMessage1_16 = EmptyControlMessage;
export const ScrcpySetClipboardControlMessage1_15 = new Struct(
export const ScrcpySetClipboardControlMessage1_15 = struct(
{ type: u8, content: string(u32) },
{ littleEndian: false },
);
@ -61,7 +61,7 @@ export type ScrcpySetClipboardControlMessage1_15 = StructInit<
typeof ScrcpySetClipboardControlMessage1_15
>;
export const ScrcpyClipboardDeviceMessage = new Struct(
export const ScrcpyClipboardDeviceMessage = struct(
{ content: string(u32) },
{ littleEndian: false },
);

View file

@ -37,7 +37,7 @@ import {
import { CodecOptions } from "./codec-options.js";
import type { ScrcpyOptionsInit1_16 } from "./init.js";
import { ScrcpyLogLevel1_16, ScrcpyVideoOrientation1_16 } from "./init.js";
import { ScrcpyVideoOrientation1_16 } from "./init.js";
import {
SCRCPY_CONTROL_MESSAGE_TYPES_1_16,
SCRCPY_MEDIA_PACKET_FLAG_CONFIG,
@ -52,7 +52,7 @@ import { ScrcpyScrollController1_16 } from "./scroll.js";
export class ScrcpyOptions1_16 extends ScrcpyOptions<ScrcpyOptionsInit1_16> {
static readonly DEFAULTS = {
logLevel: ScrcpyLogLevel1_16.Debug,
logLevel: "debug",
maxSize: 0,
bitRate: 8_000_000,
maxFps: 0,

View file

@ -1,4 +1,4 @@
import { s32, Struct, u16, u32, u8 } from "@yume-chan/struct";
import { s32, struct, u16, u32, u8 } from "@yume-chan/struct";
import type { ScrcpyInjectScrollControlMessage } from "../../control/index.js";
import { ScrcpyControlMessageType } from "../../control/index.js";
@ -9,7 +9,7 @@ export interface ScrcpyScrollController {
): Uint8Array | undefined;
}
export const ScrcpyInjectScrollControlMessage1_16 = new Struct(
export const ScrcpyInjectScrollControlMessage1_16 = struct(
{
type: u8.as(ScrcpyControlMessageType.InjectScroll as const),
pointerX: u32,

View file

@ -1,5 +1,5 @@
import type { StructInit } from "@yume-chan/struct";
import { Struct, u8 } from "@yume-chan/struct";
import { struct, u8 } from "@yume-chan/struct";
import type {
AndroidKeyEventAction,
@ -17,22 +17,24 @@ import { ScrcpyOptions1_17 } from "./1_17.js";
import type { ScrcpyEncoder } from "./types.js";
import { ScrcpyOptions } from "./types.js";
export enum ScrcpyLogLevel1_18 {
Verbose = "verbose",
Debug = "debug",
Info = "info",
Warn = "warn",
Error = "error",
}
export type ScrcpyLogLevel1_18 =
| "verbose"
| "debug"
| "info"
| "warn"
| "error";
export enum ScrcpyVideoOrientation1_18 {
Initial = -2,
Unlocked = -1,
Portrait = 0,
Landscape = 1,
PortraitFlipped = 2,
LandscapeFlipped = 3,
}
export const ScrcpyVideoOrientation1_18 = {
Initial: -2,
Unlocked: -1,
Portrait: 0,
Landscape: 1,
PortraitFlipped: 2,
LandscapeFlipped: 3,
};
export type ScrcpyVideoOrientation1_18 =
(typeof ScrcpyVideoOrientation1_18)[keyof typeof ScrcpyVideoOrientation1_18];
export interface ScrcpyOptionsInit1_18
extends Omit<ScrcpyOptionsInit1_17, "logLevel" | "lockVideoOrientation"> {
@ -43,11 +45,11 @@ export interface ScrcpyOptionsInit1_18
powerOffOnClose?: boolean;
}
export const ScrcpyBackOrScreenOnControlMessage1_18 = new Struct(
{
export const ScrcpyBackOrScreenOnControlMessage1_18 = struct(
/* #__PURE__ */ (() => ({
...ScrcpyBackOrScreenOnControlMessage1_16.fields,
action: u8.as<AndroidKeyEventAction>(),
},
}))(),
{ littleEndian: false },
);
@ -55,18 +57,16 @@ export type ScrcpyBackOrScreenOnControlMessage1_18 = StructInit<
typeof ScrcpyBackOrScreenOnControlMessage1_18
>;
export const SCRCPY_CONTROL_MESSAGE_TYPES_1_18 =
SCRCPY_CONTROL_MESSAGE_TYPES_1_16.slice();
SCRCPY_CONTROL_MESSAGE_TYPES_1_18.splice(
6,
0,
ScrcpyControlMessageType.ExpandSettingPanel,
);
export const SCRCPY_CONTROL_MESSAGE_TYPES_1_18 = /* #__PURE__ */ (() => {
const result = SCRCPY_CONTROL_MESSAGE_TYPES_1_16.slice();
result.splice(6, 0, ScrcpyControlMessageType.ExpandSettingPanel);
return result;
})();
export class ScrcpyOptions1_18 extends ScrcpyOptions<ScrcpyOptionsInit1_18> {
static readonly DEFAULTS = {
...ScrcpyOptions1_17.DEFAULTS,
logLevel: ScrcpyLogLevel1_18.Debug,
logLevel: "debug",
lockVideoOrientation: ScrcpyVideoOrientation1_18.Unlocked,
powerOffOnClose: false,
} as const satisfies Required<ScrcpyOptionsInit1_18>;

View file

@ -2,7 +2,7 @@
import { PromiseResolver } from "@yume-chan/async";
import type { AsyncExactReadable, StructInit } from "@yume-chan/struct";
import { Struct, string, u32, u64, u8 } from "@yume-chan/struct";
import { string, struct, u32, u64, u8 } from "@yume-chan/struct";
import type { ScrcpySetClipboardControlMessage } from "../control/index.js";
@ -11,7 +11,7 @@ import type { ScrcpyOptionsInit1_18 } from "./1_18.js";
import { ScrcpyOptions1_18 } from "./1_18.js";
import { ScrcpyOptions, toScrcpyOptionValue } from "./types.js";
export const ScrcpyAckClipboardDeviceMessage = new Struct(
export const ScrcpyAckClipboardDeviceMessage = struct(
{ sequence: u64 },
{ littleEndian: false },
);
@ -24,7 +24,7 @@ function toSnakeCase(input: string): string {
return input.replace(/([A-Z])/g, "_$1").toLowerCase();
}
export const ScrcpySetClipboardControlMessage1_21 = new Struct(
export const ScrcpySetClipboardControlMessage1_21 = struct(
{
type: u8,
sequence: u64,

View file

@ -1,13 +1,16 @@
import type { StructInit } from "@yume-chan/struct";
import { s32, Struct } from "@yume-chan/struct";
import { s32, struct } from "@yume-chan/struct";
import {
ScrcpyInjectScrollControlMessage1_16,
ScrcpyScrollController1_16,
} from "../1_16/index.js";
export const ScrcpyInjectScrollControlMessage1_22 = new Struct(
{ ...ScrcpyInjectScrollControlMessage1_16.fields, buttons: s32 },
export const ScrcpyInjectScrollControlMessage1_22 = struct(
/* #__PURE__ */ (() => ({
...ScrcpyInjectScrollControlMessage1_16.fields,
buttons: s32,
}))(),
{ littleEndian: false },
);

View file

@ -1,6 +1,6 @@
import { getInt16, setInt16 } from "@yume-chan/no-data-view";
import type { Field, StructInit } from "@yume-chan/struct";
import { bipedal, Struct, u16, u32, u8 } from "@yume-chan/struct";
import { bipedal, struct, u16, u32, u8 } from "@yume-chan/struct";
import type { ScrcpyInjectScrollControlMessage } from "../../control/index.js";
import { ScrcpyControlMessageType } from "../../control/index.js";
@ -23,7 +23,7 @@ export const ScrcpySignedFloat: Field<number, never, never> = {
}),
};
export const ScrcpyInjectScrollControlMessage1_25 = new Struct(
export const ScrcpyInjectScrollControlMessage1_25 = struct(
{
type: u8.as(ScrcpyControlMessageType.InjectScroll as const),
pointerX: u32,

View file

@ -5,7 +5,7 @@ import {
PushReadableStream,
} from "@yume-chan/stream-extra";
import type { MaybePromiseLike, StructInit } from "@yume-chan/struct";
import { Struct, u16, u32, u64, u8 } from "@yume-chan/struct";
import { struct, u16, u32, u64, u8 } from "@yume-chan/struct";
import type {
AndroidMotionEventAction,
@ -30,7 +30,7 @@ import type {
} from "./types.js";
import { ScrcpyOptions } from "./types.js";
export const ScrcpyInjectTouchControlMessage2_0 = new Struct(
export const ScrcpyInjectTouchControlMessage2_0 = struct(
{
type: u8,
action: u8.as<AndroidMotionEventAction>(),

View file

@ -1,8 +1,8 @@
import { ScrcpyLogLevel1_18, ScrcpyVideoOrientation1_18 } from "./1_18.js";
import type { ScrcpyLogLevel1_18 } from "./1_18.js";
import { ScrcpyVideoOrientation1_18 } from "./1_18.js";
import type { ScrcpyOptionsInit2_3 } from "./2_3.js";
import { ScrcpyOptions2_3 } from "./2_3.js";
export const ScrcpyLogLevel = ScrcpyLogLevel1_18;
export type ScrcpyLogLevel = ScrcpyLogLevel1_18;
export const ScrcpyVideoOrientation = ScrcpyVideoOrientation1_18;

View file

@ -25,9 +25,9 @@ $ npm i @yume-chan/struct
## Quick Start
```ts
import { Struct, u8, u16, s32, buffer, string } from "@yume-chan/struct";
import { struct, u8, u16, s32, buffer, string } from "@yume-chan/struct";
const Message = new Struct(
const Message = struct(
{
a: u8,
b: u16,
@ -94,7 +94,7 @@ const MyField: Field<number, never, never> = {
},
};
const Message2 = new Struct({
const Message2 = struct({
a: u8,
b: MyField,
});

View file

@ -4,15 +4,12 @@ import { describe, it } from "node:test";
import { buffer } from "./buffer.js";
import type { ExactReadable } from "./readable.js";
import { ExactReadableEndedError } from "./readable.js";
import { Struct } from "./struct.js";
import { struct } from "./struct.js";
describe("buffer", () => {
describe("fixed size", () => {
it("should deserialize", () => {
const A = new Struct(
{ value: buffer(10) },
{ littleEndian: false },
);
const A = struct({ value: buffer(10) }, { littleEndian: false });
const reader: ExactReadable = {
position: 0,
readExactly() {
@ -25,10 +22,7 @@ describe("buffer", () => {
});
it("should throw for not enough data", () => {
const A = new Struct(
{ value: buffer(10) },
{ littleEndian: false },
);
const A = struct({ value: buffer(10) }, { littleEndian: false });
const reader: ExactReadable = {
position: 0,
readExactly() {
@ -43,10 +37,7 @@ describe("buffer", () => {
});
it("should throw for no data", () => {
const A = new Struct(
{ value: buffer(10) },
{ littleEndian: false },
);
const A = struct({ value: buffer(10) }, { littleEndian: false });
const reader: ExactReadable = {
position: 0,
readExactly() {

View file

@ -42,14 +42,16 @@ export interface BufferLike {
export const EmptyUint8Array = new Uint8Array(0);
export const buffer: BufferLike = ((
// This is required for Rollup tree-shaking to work.
/* #__NO_SIDE_EFFECTS__ */
function _buffer(
lengthOrField:
| string
| number
| Field<number, never, unknown>
| BufferLengthConverter<string, unknown>,
converter?: Converter<Uint8Array, unknown>,
): Field<unknown, string, Record<string, unknown>> => {
): Field<unknown, string, Record<string, unknown>> {
if (typeof lengthOrField === "number") {
if (converter) {
return {
@ -226,4 +228,6 @@ export const buffer: BufferLike = ((
return reader.readExactly(length);
},
};
}) as never;
}
export const buffer: BufferLike = _buffer as never;

View file

@ -1,12 +1,12 @@
import * as assert from "node:assert";
import { describe, it } from "node:test";
import { Struct } from "./index.js";
import { struct } from "./index.js";
describe("Struct", () => {
describe("Index", () => {
it("should export default Struct", () => {
assert.ok(Struct);
assert.ok(struct);
});
});
});

View file

@ -25,15 +25,19 @@ export interface String {
): Field<string, KOmitInit, KS>;
}
export const string: String = ((
// This is required for Rollup tree-shaking to work.
/* #__NO_SIDE_EFFECTS__ */
function _string(
lengthOrField: string | number | BufferLengthConverter<string, unknown>,
): Field<string, string, Record<string, unknown>> & {
as: <T>(infer: T) => Field<T, string, Record<string, unknown>>;
} => {
} {
const field = buffer(lengthOrField as never, {
convert: decodeUtf8,
back: encodeUtf8,
});
(field as never as { as: unknown }).as = () => field;
return field as never;
}) as never;
}
export const string: String = _string as never;

View file

@ -2,11 +2,11 @@ import * as assert from "node:assert";
import { describe, it } from "node:test";
import { u8 } from "./number.js";
import { Struct } from "./struct.js";
import { struct } from "./struct.js";
describe("Struct", () => {
it("serialize", () => {
const A = new Struct({ id: u8 }, { littleEndian: true });
const A = struct({ id: u8 }, { littleEndian: true });
assert.deepStrictEqual(A.serialize({ id: 10 }), new Uint8Array([10]));
});
});

View file

@ -29,7 +29,7 @@ export type StructInit<
export type StructValue<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends Struct<any, any, any>,
> = ReturnType<Exclude<T["postDeserialize"], undefined>>;
> = T extends Struct<any, any, infer P> ? P : never;
export class StructDeserializeError extends Error {
constructor(message: string) {
@ -55,26 +55,29 @@ export class StructEmptyError extends StructDeserializeError {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type StructLike<T> = Struct<any, any, T>;
export class Struct<
export interface 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> = {},
Extra extends Record<PropertyKey, unknown> | undefined = undefined,
PostDeserialize = FieldsType<T> & Extra,
> {
fields: T;
size: number;
#fieldList: [string, Field<unknown, string, unknown>][] = [];
littleEndian: boolean;
extra: Extra;
postDeserialize?:
| ((fields: FieldsType<T> & Extra) => PostDeserialize)
| undefined;
serialize(runtimeStruct: StructInit<this>): Uint8Array;
serialize(runtimeStruct: StructInit<this>, buffer: Uint8Array): number;
constructor(
deserialize(reader: ExactReadable): PostDeserialize;
deserialize(reader: AsyncExactReadable): MaybePromiseLike<PostDeserialize>;
}
/* #__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;
@ -84,37 +87,36 @@ export class Struct<
fields: FieldsType<T> & Extra,
) => PostDeserialize;
},
) {
this.#fieldList = Object.entries(fields);
this.fields = fields;
this.size = this.#fieldList.reduce(
(sum, [, field]) => sum + field.size,
0,
);
): Struct<T, Extra, PostDeserialize> {
const fieldList = Object.entries(fields);
const size = fieldList.reduce((sum, [, field]) => sum + field.size, 0);
this.littleEndian = !!options.littleEndian;
this.extra = options.extra!;
this.postDeserialize = options.postDeserialize;
}
const littleEndian = !!options.littleEndian;
const extra = options.extra
? Object.getOwnPropertyDescriptors(options.extra)
: undefined;
serialize(runtimeStruct: StructInit<this>): Uint8Array;
serialize(runtimeStruct: StructInit<this>, buffer: Uint8Array): number;
return {
fields,
size,
extra: options.extra,
serialize(
runtimeStruct: StructInit<this>,
runtimeStruct: StructInit<Struct<T, Extra, PostDeserialize>>,
buffer?: Uint8Array,
): Uint8Array | number {
for (const [key, field] of this.#fieldList) {
for (const [key, field] of fieldList) {
if (key in runtimeStruct) {
field.preSerialize?.(
runtimeStruct[key as never],
runtimeStruct,
runtimeStruct as never,
);
}
}
const sizes = this.#fieldList.map(
const sizes = fieldList.map(
([key, field]) =>
field.dynamicSize?.(runtimeStruct[key as never]) ?? field.size,
field.dynamicSize?.(runtimeStruct[key as never]) ??
field.size,
);
const size = sizes.reduce((sum, size) => sum + size, 0);
@ -132,9 +134,9 @@ export class Struct<
const context: SerializeContext = {
buffer,
index: 0,
littleEndian: this.littleEndian,
littleEndian,
};
for (const [index, [key, field]] of this.#fieldList.entries()) {
for (const [index, [key, field]] of fieldList.entries()) {
field.serialize(runtimeStruct[key as never], context);
context.index += sizes[index]!;
}
@ -144,12 +146,8 @@ export class Struct<
} else {
return buffer;
}
}
deserialize: {
(reader: ExactReadable): PostDeserialize;
(reader: AsyncExactReadable): MaybePromiseLike<PostDeserialize>;
} = bipedal(function* (
},
deserialize: bipedal(function* (
this: Struct<T, Extra, PostDeserialize>,
then,
reader: AsyncExactReadable,
@ -157,15 +155,17 @@ export class Struct<
const startPosition = reader.position;
const runtimeStruct = {} as Record<string, unknown>;
const context: DeserializeContext<unknown> = {
const context: DeserializeContext<Partial<FieldsType<T>>> = {
reader,
runtimeStruct,
littleEndian: this.littleEndian,
runtimeStruct: runtimeStruct as never,
littleEndian: littleEndian,
};
try {
for (const [key, field] of this.#fieldList) {
runtimeStruct[key] = yield* then(field.deserialize(context));
for (const [key, field] of fieldList) {
runtimeStruct[key] = yield* then(
field.deserialize(context),
);
}
} catch (e) {
if (!(e instanceof ExactReadableEndedError)) {
@ -179,20 +179,18 @@ export class Struct<
}
}
if (this.extra) {
Object.defineProperties(
runtimeStruct,
Object.getOwnPropertyDescriptors(this.extra),
);
if (extra) {
Object.defineProperties(runtimeStruct, extra);
}
if (this.postDeserialize) {
return this.postDeserialize.call(
runtimeStruct,
if (options.postDeserialize) {
return options.postDeserialize.call(
runtimeStruct as never,
runtimeStruct as never,
);
} else {
return runtimeStruct as never;
return runtimeStruct;
}
}) as never;
}),
} as never;
}