From 2c8912e9ac714af6ab09150171892bb703734171 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Fri, 30 Aug 2024 17:27:01 +0800 Subject: [PATCH] refactor: remove TypeScript enum and namespace to improve tree-shaking --- libraries/adb/src/banner.ts | 14 +- libraries/adb/src/commands/framebuffer.ts | 66 ++--- libraries/adb/src/commands/reverse.ts | 14 +- .../commands/subprocess/protocols/shell.ts | 29 ++- libraries/adb/src/commands/sync/list.ts | 20 +- libraries/adb/src/commands/sync/pull.ts | 8 +- libraries/adb/src/commands/sync/push.ts | 33 +-- libraries/adb/src/commands/sync/request.ts | 30 +-- libraries/adb/src/commands/sync/response.ts | 38 +-- libraries/adb/src/commands/sync/stat.ts | 158 ++++++------ libraries/adb/src/daemon/packet.ts | 42 ++-- libraries/adb/src/features.ts | 24 +- libraries/adb/src/utils/base64.ts | 40 ++-- libraries/adb/src/utils/no-op.ts | 1 + libraries/android-bin/src/dumpsys.ts | 46 ++-- libraries/android-bin/src/logcat.ts | 113 +++++---- .../src/video/decoder.ts | 7 +- libraries/scrcpy/src/control/empty.ts | 2 +- .../scrcpy/src/control/inject-keycode.ts | 14 +- libraries/scrcpy/src/control/inject-text.ts | 10 +- .../src/control/set-screen-power-mode.ts | 8 +- libraries/scrcpy/src/options/1_16/message.ts | 48 ++-- libraries/scrcpy/src/options/1_16/scroll.ts | 18 +- libraries/scrcpy/src/options/1_18.ts | 8 +- libraries/scrcpy/src/options/1_21.ts | 18 +- libraries/scrcpy/src/options/1_22/scroll.ts | 6 +- libraries/scrcpy/src/options/1_25/scroll.ts | 20 +- libraries/scrcpy/src/options/2_0.ts | 24 +- libraries/stream-extra/src/consumable.ts | 226 +++++++++--------- .../stream-extra/src/maybe-consumable-ns.ts | 90 +++++++ .../stream-extra/src/maybe-consumable.ts | 100 +------- libraries/stream-extra/src/stream.ts | 16 +- libraries/struct/src/struct.spec.ts | 67 +++--- libraries/struct/src/utils.ts | 5 +- 34 files changed, 744 insertions(+), 619 deletions(-) create mode 100644 libraries/stream-extra/src/maybe-consumable-ns.ts diff --git a/libraries/adb/src/banner.ts b/libraries/adb/src/banner.ts index cb0a19a3..5415b073 100644 --- a/libraries/adb/src/banner.ts +++ b/libraries/adb/src/banner.ts @@ -1,11 +1,13 @@ import type { AdbFeature } from "./features.js"; -export enum AdbBannerKey { - Product = "ro.product.name", - Model = "ro.product.model", - Device = "ro.product.device", - Features = "features", -} +export const AdbBannerKey = { + Product: "ro.product.name", + Model: "ro.product.model", + Device: "ro.product.device", + Features: "features", +} as const; + +export type AdbBannerKey = (typeof AdbBannerKey)[keyof typeof AdbBannerKey]; export class AdbBanner { static parse(banner: string) { diff --git a/libraries/adb/src/commands/framebuffer.ts b/libraries/adb/src/commands/framebuffer.ts index 49c24efb..72a76c9b 100644 --- a/libraries/adb/src/commands/framebuffer.ts +++ b/libraries/adb/src/commands/framebuffer.ts @@ -3,40 +3,46 @@ import Struct, { StructEmptyError } from "@yume-chan/struct"; import type { Adb } from "../adb.js"; -const Version = new Struct({ littleEndian: true }).uint32("version"); +const Version = + /* #__PURE__ */ + new Struct({ littleEndian: true }).uint32("version"); -export const AdbFrameBufferV1 = new Struct({ littleEndian: true }) - .uint32("bpp") - .uint32("size") - .uint32("width") - .uint32("height") - .uint32("red_offset") - .uint32("red_length") - .uint32("blue_offset") - .uint32("blue_length") - .uint32("green_offset") - .uint32("green_length") - .uint32("alpha_offset") - .uint32("alpha_length") - .uint8Array("data", { lengthField: "size" }); +export const AdbFrameBufferV1 = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .uint32("bpp") + .uint32("size") + .uint32("width") + .uint32("height") + .uint32("red_offset") + .uint32("red_length") + .uint32("blue_offset") + .uint32("blue_length") + .uint32("green_offset") + .uint32("green_length") + .uint32("alpha_offset") + .uint32("alpha_length") + .uint8Array("data", { lengthField: "size" }); export type AdbFrameBufferV1 = (typeof AdbFrameBufferV1)["TDeserializeResult"]; -export const AdbFrameBufferV2 = new Struct({ littleEndian: true }) - .uint32("bpp") - .uint32("colorSpace") - .uint32("size") - .uint32("width") - .uint32("height") - .uint32("red_offset") - .uint32("red_length") - .uint32("blue_offset") - .uint32("blue_length") - .uint32("green_offset") - .uint32("green_length") - .uint32("alpha_offset") - .uint32("alpha_length") - .uint8Array("data", { lengthField: "size" }); +export const AdbFrameBufferV2 = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .uint32("bpp") + .uint32("colorSpace") + .uint32("size") + .uint32("width") + .uint32("height") + .uint32("red_offset") + .uint32("red_length") + .uint32("blue_offset") + .uint32("blue_length") + .uint32("green_offset") + .uint32("green_length") + .uint32("alpha_offset") + .uint32("alpha_length") + .uint8Array("data", { lengthField: "size" }); export type AdbFrameBufferV2 = (typeof AdbFrameBufferV2)["TDeserializeResult"]; diff --git a/libraries/adb/src/commands/reverse.ts b/libraries/adb/src/commands/reverse.ts index d3bc4bc6..66f2bfe2 100644 --- a/libraries/adb/src/commands/reverse.ts +++ b/libraries/adb/src/commands/reverse.ts @@ -14,9 +14,11 @@ export interface AdbForwardListener { remoteName: string; } -const AdbReverseStringResponse = new Struct() - .string("length", { length: 4 }) - .string("content", { lengthField: "length", lengthFieldRadix: 16 }); +const AdbReverseStringResponse = + /* #__PURE__ */ + new Struct() + .string("length", { length: 4 }) + .string("content", { lengthField: "length", lengthFieldRadix: 16 }); export class AdbReverseError extends Error { constructor(message: string) { @@ -33,9 +35,9 @@ export class AdbReverseNotSupportedError extends AdbReverseError { } } -const AdbReverseErrorResponse = new Struct() - .concat(AdbReverseStringResponse) - .postDeserialize((value) => { +const AdbReverseErrorResponse = + /* #__PURE__ */ + new Struct().concat(AdbReverseStringResponse).postDeserialize((value) => { // https://issuetracker.google.com/issues/37066218 // ADB on Android <9 can't create reverse tunnels when connected wirelessly (ADB over Wi-Fi), // and returns this confusing "more than one device/emulator" error. diff --git a/libraries/adb/src/commands/subprocess/protocols/shell.ts b/libraries/adb/src/commands/subprocess/protocols/shell.ts index 47d5c56d..f6d7bff4 100644 --- a/libraries/adb/src/commands/subprocess/protocols/shell.ts +++ b/libraries/adb/src/commands/subprocess/protocols/shell.ts @@ -19,20 +19,25 @@ import { encodeUtf8 } from "../../../utils/index.js"; import type { AdbSubprocessProtocol } from "./types.js"; -export enum AdbShellProtocolId { - Stdin, - Stdout, - Stderr, - Exit, - CloseStdin, - WindowSizeChange, -} +export const AdbShellProtocolId = { + Stdin: 0, + Stdout: 1, + Stderr: 2, + Exit: 3, + CloseStdin: 4, + WindowSizeChange: 5, +} as const; + +export type AdbShellProtocolId = + (typeof AdbShellProtocolId)[keyof typeof AdbShellProtocolId]; // This packet format is used in both directions. -export const AdbShellProtocolPacket = new Struct({ littleEndian: true }) - .uint8("id", placeholder()) - .uint32("length") - .uint8Array("data", { lengthField: "length" }); +export const AdbShellProtocolPacket = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .uint8("id", placeholder()) + .uint32("length") + .uint8Array("data", { lengthField: "length" }); type AdbShellProtocolPacket = StructValueType; diff --git a/libraries/adb/src/commands/sync/list.ts b/libraries/adb/src/commands/sync/list.ts index cd028dab..321f3763 100644 --- a/libraries/adb/src/commands/sync/list.ts +++ b/libraries/adb/src/commands/sync/list.ts @@ -14,18 +14,22 @@ export interface AdbSyncEntry extends AdbSyncStat { name: string; } -export const AdbSyncEntryResponse = new Struct({ littleEndian: true }) - .concat(AdbSyncLstatResponse) - .uint32("nameLength") - .string("name", { lengthField: "nameLength" }); +export const AdbSyncEntryResponse = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .concat(AdbSyncLstatResponse) + .uint32("nameLength") + .string("name", { lengthField: "nameLength" }); export type AdbSyncEntryResponse = (typeof AdbSyncEntryResponse)["TDeserializeResult"]; -export const AdbSyncEntry2Response = new Struct({ littleEndian: true }) - .concat(AdbSyncStatResponse) - .uint32("nameLength") - .string("name", { lengthField: "nameLength" }); +export const AdbSyncEntry2Response = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .concat(AdbSyncStatResponse) + .uint32("nameLength") + .string("name", { lengthField: "nameLength" }); export type AdbSyncEntry2Response = (typeof AdbSyncEntry2Response)["TDeserializeResult"]; diff --git a/libraries/adb/src/commands/sync/pull.ts b/libraries/adb/src/commands/sync/pull.ts index 60e977ec..b9001526 100644 --- a/libraries/adb/src/commands/sync/pull.ts +++ b/libraries/adb/src/commands/sync/pull.ts @@ -6,9 +6,11 @@ import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js"; import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js"; import type { AdbSyncSocket } from "./socket.js"; -export const AdbSyncDataResponse = new Struct({ littleEndian: true }) - .uint32("dataLength") - .uint8Array("data", { lengthField: "dataLength" }); +export const AdbSyncDataResponse = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .uint32("dataLength") + .uint8Array("data", { lengthField: "dataLength" }); export type AdbSyncDataResponse = (typeof AdbSyncDataResponse)["TDeserializeResult"]; diff --git a/libraries/adb/src/commands/sync/push.ts b/libraries/adb/src/commands/sync/push.ts index 9adff78a..7dd32807 100644 --- a/libraries/adb/src/commands/sync/push.ts +++ b/libraries/adb/src/commands/sync/push.ts @@ -25,9 +25,9 @@ export interface AdbSyncPushV1Options { packetSize?: number; } -export const AdbSyncOkResponse = new Struct({ littleEndian: true }).uint32( - "unused", -); +export const AdbSyncOkResponse = + /* #__PURE__ */ + new Struct({ littleEndian: true }).uint32("unused"); async function pipeFileData( locked: AdbSyncSocketLocked, @@ -86,19 +86,22 @@ export async function adbSyncPushV1({ } } -export enum AdbSyncSendV2Flags { - None = 0, - Brotli = 1, +export const AdbSyncSendV2Flags = { + None: 0, + Brotli: 1, /** * 2 */ - Lz4 = 1 << 1, + Lz4: 1 << 1, /** * 4 */ - Zstd = 1 << 2, - DryRun = 0x80000000, -} + Zstd: 1 << 2, + DryRun: 0x80000000, +} as const; + +export type AdbSyncSendV2Flags = + (typeof AdbSyncSendV2Flags)[keyof typeof AdbSyncSendV2Flags]; export interface AdbSyncPushV2Options extends AdbSyncPushV1Options { /** @@ -110,10 +113,12 @@ export interface AdbSyncPushV2Options extends AdbSyncPushV1Options { dryRun?: boolean; } -export const AdbSyncSendV2Request = new Struct({ littleEndian: true }) - .uint32("id") - .uint32("mode") - .uint32("flags", placeholder()); +export const AdbSyncSendV2Request = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .uint32("id") + .uint32("mode") + .uint32("flags", placeholder()); export async function adbSyncPushV2({ socket, diff --git a/libraries/adb/src/commands/sync/request.ts b/libraries/adb/src/commands/sync/request.ts index 788a18b8..eecdec29 100644 --- a/libraries/adb/src/commands/sync/request.ts +++ b/libraries/adb/src/commands/sync/request.ts @@ -4,22 +4,22 @@ import { encodeUtf8 } from "../../utils/index.js"; import { adbSyncEncodeId } from "./response.js"; -export namespace AdbSyncRequestId { - export const List = adbSyncEncodeId("LIST"); - export const ListV2 = adbSyncEncodeId("LIS2"); - export const Send = adbSyncEncodeId("SEND"); - export const SendV2 = adbSyncEncodeId("SND2"); - export const Lstat = adbSyncEncodeId("STAT"); - export const Stat = adbSyncEncodeId("STA2"); - export const LstatV2 = adbSyncEncodeId("LST2"); - export const Data = adbSyncEncodeId("DATA"); - export const Done = adbSyncEncodeId("DONE"); - export const Receive = adbSyncEncodeId("RECV"); -} +export const AdbSyncRequestId = { + List: adbSyncEncodeId("LIST"), + ListV2: adbSyncEncodeId("LIS2"), + Send: adbSyncEncodeId("SEND"), + SendV2: adbSyncEncodeId("SND2"), + Lstat: adbSyncEncodeId("STAT"), + Stat: adbSyncEncodeId("STA2"), + LstatV2: adbSyncEncodeId("LST2"), + Data: adbSyncEncodeId("DATA"), + Done: adbSyncEncodeId("DONE"), + Receive: adbSyncEncodeId("RECV"), +} as const; -export const AdbSyncNumberRequest = new Struct({ littleEndian: true }) - .uint32("id") - .uint32("arg"); +export const AdbSyncNumberRequest = + /* #__PURE__ */ + new Struct({ littleEndian: true }).uint32("id").uint32("arg"); export interface AdbSyncWritable { write(buffer: Uint8Array): Promise; diff --git a/libraries/adb/src/commands/sync/response.ts b/libraries/adb/src/commands/sync/response.ts index 4309041f..caa7c5a3 100644 --- a/libraries/adb/src/commands/sync/response.ts +++ b/libraries/adb/src/commands/sync/response.ts @@ -18,32 +18,36 @@ function encodeAsciiUnchecked(value: string): Uint8Array { * Encode ID to numbers for faster comparison * @param value A 4-character string * @returns A 32-bit integer by encoding the string as little-endian + * + * #__NO_SIDE_EFFECTS__ */ export function adbSyncEncodeId(value: string): number { const buffer = encodeAsciiUnchecked(value); return getUint32LittleEndian(buffer, 0); } -export namespace AdbSyncResponseId { - export const Entry = adbSyncEncodeId("DENT"); - export const Entry2 = adbSyncEncodeId("DNT2"); - export const Lstat = adbSyncEncodeId("STAT"); - export const Stat = adbSyncEncodeId("STA2"); - export const Lstat2 = adbSyncEncodeId("LST2"); - export const Done = adbSyncEncodeId("DONE"); - export const Data = adbSyncEncodeId("DATA"); - export const Ok = adbSyncEncodeId("OKAY"); - export const Fail = adbSyncEncodeId("FAIL"); -} +export const AdbSyncResponseId = { + Entry: adbSyncEncodeId("DENT"), + Entry2: adbSyncEncodeId("DNT2"), + Lstat: adbSyncEncodeId("STAT"), + Stat: adbSyncEncodeId("STA2"), + Lstat2: adbSyncEncodeId("LST2"), + Done: adbSyncEncodeId("DONE"), + Data: adbSyncEncodeId("DATA"), + Ok: adbSyncEncodeId("OKAY"), + Fail: adbSyncEncodeId("FAIL"), +}; export class AdbSyncError extends Error {} -export const AdbSyncFailResponse = new Struct({ littleEndian: true }) - .uint32("messageLength") - .string("message", { lengthField: "messageLength" }) - .postDeserialize((object) => { - throw new AdbSyncError(object.message); - }); +export const AdbSyncFailResponse = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .uint32("messageLength") + .string("message", { lengthField: "messageLength" }) + .postDeserialize((object) => { + throw new AdbSyncError(object.message); + }); export async function adbSyncReadResponse( stream: AsyncExactReadable, diff --git a/libraries/adb/src/commands/sync/stat.ts b/libraries/adb/src/commands/sync/stat.ts index 13037656..26339b68 100644 --- a/libraries/adb/src/commands/sync/stat.ts +++ b/libraries/adb/src/commands/sync/stat.ts @@ -5,11 +5,13 @@ import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js"; import type { AdbSyncSocket } from "./socket.js"; // https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36 -export enum LinuxFileType { - Directory = 0o04, - File = 0o10, - Link = 0o12, -} +export const LinuxFileType = { + Directory: 0o04, + File: 0o10, + Link: 0o12, +} as const; + +export type LinuxFileType = (typeof LinuxFileType)[keyof typeof LinuxFileType]; export interface AdbSyncStat { mode: number; @@ -24,76 +26,92 @@ export interface AdbSyncStat { ctime?: bigint; } -export const AdbSyncLstatResponse = new Struct({ littleEndian: true }) - .int32("mode") - .int32("size") - .int32("mtime") - .extra({ - get type() { - return (this.mode >> 12) as LinuxFileType; - }, - get permission() { - return this.mode & 0b00001111_11111111; - }, - }) - .postDeserialize((object) => { - if (object.mode === 0 && object.size === 0 && object.mtime === 0) { - throw new Error("lstat error"); - } - }); +export const AdbSyncLstatResponse = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .int32("mode") + .int32("size") + .int32("mtime") + .extra({ + get type() { + return (this.mode >> 12) as LinuxFileType; + }, + get permission() { + return this.mode & 0b00001111_11111111; + }, + }) + .postDeserialize((object) => { + if (object.mode === 0 && object.size === 0 && object.mtime === 0) { + throw new Error("lstat error"); + } + }); export type AdbSyncLstatResponse = (typeof AdbSyncLstatResponse)["TDeserializeResult"]; -export enum AdbSyncStatErrorCode { - SUCCESS = 0, - EACCES = 13, - EEXIST = 17, - EFAULT = 14, - EFBIG = 27, - EINTR = 4, - EINVAL = 22, - EIO = 5, - EISDIR = 21, - ELOOP = 40, - EMFILE = 24, - ENAMETOOLONG = 36, - ENFILE = 23, - ENOENT = 2, - ENOMEM = 12, - ENOSPC = 28, - ENOTDIR = 20, - EOVERFLOW = 75, - EPERM = 1, - EROFS = 30, - ETXTBSY = 26, -} +export const AdbSyncStatErrorCode = { + SUCCESS: 0, + EACCES: 13, + EEXIST: 17, + EFAULT: 14, + EFBIG: 27, + EINTR: 4, + EINVAL: 22, + EIO: 5, + EISDIR: 21, + ELOOP: 40, + EMFILE: 24, + ENAMETOOLONG: 36, + ENFILE: 23, + ENOENT: 2, + ENOMEM: 12, + ENOSPC: 28, + ENOTDIR: 20, + EOVERFLOW: 75, + EPERM: 1, + EROFS: 30, + ETXTBSY: 26, +} as const; -export const AdbSyncStatResponse = new Struct({ littleEndian: true }) - .uint32("error", placeholder()) - .uint64("dev") - .uint64("ino") - .uint32("mode") - .uint32("nlink") - .uint32("uid") - .uint32("gid") - .uint64("size") - .uint64("atime") - .uint64("mtime") - .uint64("ctime") - .extra({ - get type() { - return (this.mode >> 12) as LinuxFileType; - }, - get permission() { - return this.mode & 0b00001111_11111111; - }, - }) - .postDeserialize((object) => { - if (object.error) { - throw new Error(AdbSyncStatErrorCode[object.error]); - } - }); +export type AdbSyncStatErrorCode = + (typeof AdbSyncStatErrorCode)[keyof typeof AdbSyncStatErrorCode]; + +const AdbSyncStatErrorName = + /* #__PURE__ */ + Object.fromEntries( + Object.entries(AdbSyncStatErrorCode).map(([key, value]) => [ + value, + key, + ]), + ); + +export const AdbSyncStatResponse = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .uint32("error", placeholder()) + .uint64("dev") + .uint64("ino") + .uint32("mode") + .uint32("nlink") + .uint32("uid") + .uint32("gid") + .uint64("size") + .uint64("atime") + .uint64("mtime") + .uint64("ctime") + .extra({ + get type() { + return (this.mode >> 12) as LinuxFileType; + }, + get permission() { + return this.mode & 0b00001111_11111111; + }, + }) + .postDeserialize((object) => { + if (object.error) { + throw new Error(AdbSyncStatErrorName[object.error]); + } + }); export type AdbSyncStatResponse = (typeof AdbSyncStatResponse)["TDeserializeResult"]; diff --git a/libraries/adb/src/daemon/packet.ts b/libraries/adb/src/daemon/packet.ts index 995641cc..f7e266c7 100644 --- a/libraries/adb/src/daemon/packet.ts +++ b/libraries/adb/src/daemon/packet.ts @@ -1,30 +1,36 @@ import { Consumable, TransformStream } from "@yume-chan/stream-extra"; import Struct from "@yume-chan/struct"; -export enum AdbCommand { - Auth = 0x48545541, // 'AUTH' - Close = 0x45534c43, // 'CLSE' - Connect = 0x4e584e43, // 'CNXN' - Okay = 0x59414b4f, // 'OKAY' - Open = 0x4e45504f, // 'OPEN' - Write = 0x45545257, // 'WRTE' -} +export const AdbCommand = { + Auth: 0x48545541, // 'AUTH' + Close: 0x45534c43, // 'CLSE' + Connect: 0x4e584e43, // 'CNXN' + Okay: 0x59414b4f, // 'OKAY' + Open: 0x4e45504f, // 'OPEN' + Write: 0x45545257, // 'WRTE' +} as const; -export const AdbPacketHeader = new Struct({ littleEndian: true }) - .uint32("command") - .uint32("arg0") - .uint32("arg1") - .uint32("payloadLength") - .uint32("checksum") - .int32("magic"); +export type AdbCommand = (typeof AdbCommand)[keyof typeof AdbCommand]; + +export const AdbPacketHeader = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .uint32("command") + .uint32("arg0") + .uint32("arg1") + .uint32("payloadLength") + .uint32("checksum") + .int32("magic"); export type AdbPacketHeader = (typeof AdbPacketHeader)["TDeserializeResult"]; type AdbPacketHeaderInit = (typeof AdbPacketHeader)["TInit"]; -export const AdbPacket = new Struct({ littleEndian: true }) - .concat(AdbPacketHeader) - .uint8Array("payload", { lengthField: "payloadLength" }); +export const AdbPacket = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .concat(AdbPacketHeader) + .uint8Array("payload", { lengthField: "payloadLength" }); export type AdbPacket = (typeof AdbPacket)["TDeserializeResult"]; diff --git a/libraries/adb/src/features.ts b/libraries/adb/src/features.ts index 707c212c..7b7553c0 100644 --- a/libraries/adb/src/features.ts +++ b/libraries/adb/src/features.ts @@ -1,13 +1,15 @@ // The order follows // https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/transport.cpp;l=77;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd -export enum AdbFeature { - ShellV2 = "shell_v2", - Cmd = "cmd", - StatV2 = "stat_v2", - ListV2 = "ls_v2", - FixedPushMkdir = "fixed_push_mkdir", - Abb = "abb", - AbbExec = "abb_exec", - SendReceiveV2 = "sendrecv_v2", - DelayedAck = "delayed_ack", -} +export const AdbFeature = { + ShellV2: "shell_v2", + Cmd: "cmd", + StatV2: "stat_v2", + ListV2: "ls_v2", + FixedPushMkdir: "fixed_push_mkdir", + Abb: "abb", + AbbExec: "abb_exec", + SendReceiveV2: "sendrecv_v2", + DelayedAck: "delayed_ack", +} as const; + +export type AdbFeature = (typeof AdbFeature)[keyof typeof AdbFeature]; diff --git a/libraries/adb/src/utils/base64.ts b/libraries/adb/src/utils/base64.ts index fd0fa542..052a3301 100644 --- a/libraries/adb/src/utils/base64.ts +++ b/libraries/adb/src/utils/base64.ts @@ -1,23 +1,31 @@ -// Array is faster than object literal or `Map` -const charToIndex: number[] = []; -const indexToChar: number[] = []; -const paddingChar = "=".charCodeAt(0); +const [charToIndex, indexToChar, paddingChar] = /* #__PURE__ */ (() => { + // Array is faster than object literal or `Map` + const charToIndex: number[] = []; + const indexToChar: number[] = []; + const paddingChar = "=".charCodeAt(0); -function addRange(start: string, end: string) { - const charCodeStart = start.charCodeAt(0); - const charCodeEnd = end.charCodeAt(0); + function addRange(start: string, end: string) { + const charCodeStart = start.charCodeAt(0); + const charCodeEnd = end.charCodeAt(0); - for (let charCode = charCodeStart; charCode <= charCodeEnd; charCode += 1) { - charToIndex[charCode] = indexToChar.length; - indexToChar.push(charCode); + for ( + let charCode = charCodeStart; + charCode <= charCodeEnd; + charCode += 1 + ) { + charToIndex[charCode] = indexToChar.length; + indexToChar.push(charCode); + } } -} -addRange("A", "Z"); -addRange("a", "z"); -addRange("0", "9"); -addRange("+", "+"); -addRange("/", "/"); + addRange("A", "Z"); + addRange("a", "z"); + addRange("0", "9"); + addRange("+", "+"); + addRange("/", "/"); + + return [charToIndex, indexToChar, paddingChar]; +})(); /** * Calculate the required length of the output buffer for the given input length. diff --git a/libraries/adb/src/utils/no-op.ts b/libraries/adb/src/utils/no-op.ts index 2e56f8ff..75b7e678 100644 --- a/libraries/adb/src/utils/no-op.ts +++ b/libraries/adb/src/utils/no-op.ts @@ -1,3 +1,4 @@ +/* #__NO_SIDE_EFFECTS__ */ export const NOOP = () => { // no-op }; diff --git a/libraries/android-bin/src/dumpsys.ts b/libraries/android-bin/src/dumpsys.ts index 71379de1..e1b425b7 100644 --- a/libraries/android-bin/src/dumpsys.ts +++ b/libraries/android-bin/src/dumpsys.ts @@ -31,7 +31,32 @@ const BatteryDumpFields: Record< current: { type: "number", field: "current" }, }; +const Status = { + Unknown: 1, + Charging: 2, + Discharging: 3, + NotCharging: 4, + Full: 5, +} as const; + +const Health = { + Unknown: 1, + Good: 2, + Overheat: 3, + Dead: 4, + OverVoltage: 5, + UnspecifiedFailure: 6, + Cold: 7, +} as const; + +const Battery = { + Status, + Health, +}; + export class DumpSys extends AdbCommandBase { + static readonly Battery = Battery; + async diskStats() { const output = await this.adb.subprocess.spawnAndWaitLegacy([ "dumpsys", @@ -113,6 +138,9 @@ export class DumpSys extends AdbCommandBase { export namespace DumpSys { export namespace Battery { + export type Status = (typeof Status)[keyof typeof Status]; + export type Health = (typeof Health)[keyof typeof Health]; + export interface Info { acPowered: boolean; usbPowered: boolean; @@ -131,23 +159,5 @@ export namespace DumpSys { technology?: string; current?: number; } - - export enum Status { - Unknown = 1, - Charging, - Discharging, - NotCharging, - Full, - } - - export enum Health { - Unknown = 1, - Good, - Overheat, - Dead, - OverVoltage, - UnspecifiedFailure, - Cold, - } } } diff --git a/libraries/android-bin/src/logcat.ts b/libraries/android-bin/src/logcat.ts index 51c115bc..867284e4 100644 --- a/libraries/android-bin/src/logcat.ts +++ b/libraries/android-bin/src/logcat.ts @@ -17,30 +17,39 @@ import Struct, { decodeUtf8 } from "@yume-chan/struct"; // so instead of adding to core library, it's implemented here // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/include/android/log.h;l=141;drc=82b5738732161dbaafb2e2f25cce19cd26b9157d -export enum LogId { - All = -1, - Main, - Radio, - Events, - System, - Crash, - Stats, - Security, - Kernel, -} +export const LogId = { + All: -1, + Main: 0, + Radio: 1, + Events: 2, + System: 3, + Crash: 4, + Stats: 5, + Security: 6, + Kernel: 7, +} as const; + +export type LogId = (typeof LogId)[keyof typeof LogId]; + +const LogIdName = + /* #__PURE__ */ + Object.fromEntries(Object.entries(LogId).map(([k, v]) => [v, k])); // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/include/android/log.h;l=73;drc=82b5738732161dbaafb2e2f25cce19cd26b9157d -export enum AndroidLogPriority { - Unknown, - Default, - Verbose, - Debug, - Info, - Warn, - Error, - Fatal, - Silent, -} +export const AndroidLogPriority = { + Unknown: 0, + Default: 1, + Verbose: 2, + Debug: 3, + Info: 4, + Warn: 5, + Error: 6, + Fatal: 7, + Silent: 8, +} as const; + +export type AndroidLogPriority = + (typeof AndroidLogPriority)[keyof typeof AndroidLogPriority]; // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=140;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79 export const AndroidLogPriorityToCharacter: Record = @@ -56,16 +65,18 @@ export const AndroidLogPriorityToCharacter: Record = [AndroidLogPriority.Silent]: "S", }; -export enum LogcatFormat { - Brief, - Process, - Tag, - Thread, - Raw, - Time, - ThreadTime, - Long, -} +export const LogcatFormat = { + Brief: 0, + Process: 1, + Tag: 2, + Thread: 3, + Raw: 4, + Time: 5, + ThreadTime: 6, + Long: 7, +} as const; + +export type LogcatFormat = (typeof LogcatFormat)[keyof typeof LogcatFormat]; export interface LogcatFormatModifiers { microseconds?: boolean; @@ -88,23 +99,25 @@ export interface LogcatOptions { const NANOSECONDS_PER_SECOND = 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({ littleEndian: true }) - .uint16("payloadSize") - .uint16("headerSize") - .int32("pid") - .uint32("tid") - .uint32("seconds") - .uint32("nanoseconds") - .uint32("logId") - .uint32("uid") - .extra({ - get timestamp() { - return ( - BigInt(this.seconds) * NANOSECONDS_PER_SECOND + - BigInt(this.nanoseconds) - ); - }, - }); +export const LoggerEntry = + /* #__PURE__ */ + new Struct({ littleEndian: true }) + .uint16("payloadSize") + .uint16("headerSize") + .int32("pid") + .uint32("tid") + .uint32("seconds") + .uint32("nanoseconds") + .uint32("logId") + .uint32("uid") + .extra({ + get timestamp() { + return ( + BigInt(this.seconds) * NANOSECONDS_PER_SECOND + + BigInt(this.nanoseconds) + ); + }, + }); export type LoggerEntry = (typeof LoggerEntry)["TDeserializeResult"]; @@ -385,7 +398,7 @@ export interface LogSize { export class Logcat extends AdbCommandBase { static logIdToName(id: LogId): string { - return LogId[id]; + return LogIdName[id]!; } static logNameToId(name: string): LogId { diff --git a/libraries/scrcpy-decoder-webcodecs/src/video/decoder.ts b/libraries/scrcpy-decoder-webcodecs/src/video/decoder.ts index 112e0f3b..14677270 100644 --- a/libraries/scrcpy-decoder-webcodecs/src/video/decoder.ts +++ b/libraries/scrcpy-decoder-webcodecs/src/video/decoder.ts @@ -92,10 +92,9 @@ class VideoFrameCapturer { } } -const VideoFrameCapturerPool = /*@__PURE__*/ new Pool( - () => new VideoFrameCapturer(), - 4, -); +const VideoFrameCapturerPool = + /* #__PURE__ */ + new Pool(() => new VideoFrameCapturer(), 4); export interface WebCodecsVideoDecoderInit { /** diff --git a/libraries/scrcpy/src/control/empty.ts b/libraries/scrcpy/src/control/empty.ts index a860e121..c8e18600 100644 --- a/libraries/scrcpy/src/control/empty.ts +++ b/libraries/scrcpy/src/control/empty.ts @@ -1,3 +1,3 @@ import { Struct } from "@yume-chan/struct"; -export const EmptyControlMessage = new Struct().uint8("type"); +export const EmptyControlMessage = /* #__PURE__ */ new Struct().uint8("type"); diff --git a/libraries/scrcpy/src/control/inject-keycode.ts b/libraries/scrcpy/src/control/inject-keycode.ts index 9a3712a2..142d3477 100644 --- a/libraries/scrcpy/src/control/inject-keycode.ts +++ b/libraries/scrcpy/src/control/inject-keycode.ts @@ -205,12 +205,14 @@ export enum AndroidKeyCode { AndroidPaste, } -export const ScrcpyInjectKeyCodeControlMessage = new Struct() - .uint8("type") - .uint8("action", placeholder()) - .uint32("keyCode", placeholder()) - .uint32("repeat") - .uint32("metaState", placeholder()); +export const ScrcpyInjectKeyCodeControlMessage = + /* #__PURE__ */ + new Struct() + .uint8("type") + .uint8("action", placeholder()) + .uint32("keyCode", placeholder()) + .uint32("repeat") + .uint32("metaState", placeholder()); export type ScrcpyInjectKeyCodeControlMessage = (typeof ScrcpyInjectKeyCodeControlMessage)["TInit"]; diff --git a/libraries/scrcpy/src/control/inject-text.ts b/libraries/scrcpy/src/control/inject-text.ts index adab62a0..dea92916 100644 --- a/libraries/scrcpy/src/control/inject-text.ts +++ b/libraries/scrcpy/src/control/inject-text.ts @@ -1,9 +1,11 @@ import Struct from "@yume-chan/struct"; -export const ScrcpyInjectTextControlMessage = new Struct() - .uint8("type") - .uint32("length") - .string("text", { lengthField: "length" }); +export const ScrcpyInjectTextControlMessage = + /* #__PURE__ */ + new Struct() + .uint8("type") + .uint32("length") + .string("text", { lengthField: "length" }); export type ScrcpyInjectTextControlMessage = (typeof ScrcpyInjectTextControlMessage)["TInit"]; diff --git a/libraries/scrcpy/src/control/set-screen-power-mode.ts b/libraries/scrcpy/src/control/set-screen-power-mode.ts index 6d45e252..47eb7855 100644 --- a/libraries/scrcpy/src/control/set-screen-power-mode.ts +++ b/libraries/scrcpy/src/control/set-screen-power-mode.ts @@ -6,9 +6,11 @@ export enum AndroidScreenPowerMode { Normal = 2, } -export const ScrcpySetScreenPowerModeControlMessage = new Struct() - .uint8("type") - .uint8("mode", placeholder()); +export const ScrcpySetScreenPowerModeControlMessage = + /* #__PURE__ */ + new Struct() + .uint8("type") + .uint8("mode", placeholder()); export type ScrcpySetScreenPowerModeControlMessage = (typeof ScrcpySetScreenPowerModeControlMessage)["TInit"]; diff --git a/libraries/scrcpy/src/options/1_16/message.ts b/libraries/scrcpy/src/options/1_16/message.ts index 1125501b..c493a4bd 100644 --- a/libraries/scrcpy/src/options/1_16/message.ts +++ b/libraries/scrcpy/src/options/1_16/message.ts @@ -23,37 +23,43 @@ export const SCRCPY_CONTROL_MESSAGE_TYPES_1_16: readonly ScrcpyControlMessageTyp /* 10 */ ScrcpyControlMessageType.RotateDevice, ]; -export const ScrcpyMediaStreamRawPacket = new Struct() - .uint64("pts") - .uint32("size") - .uint8Array("data", { lengthField: "size" }); +export const ScrcpyMediaStreamRawPacket = + /* #__PURE__ */ + new Struct() + .uint64("pts") + .uint32("size") + .uint8Array("data", { lengthField: "size" }); export const SCRCPY_MEDIA_PACKET_FLAG_CONFIG = 1n << 63n; -export const ScrcpyInjectTouchControlMessage1_16 = new Struct() - .uint8("type") - .uint8("action", placeholder()) - .uint64("pointerId") - .uint32("pointerX") - .uint32("pointerY") - .uint16("screenWidth") - .uint16("screenHeight") - .field("pressure", ScrcpyUnsignedFloatFieldDefinition) - .uint32("buttons"); +export const ScrcpyInjectTouchControlMessage1_16 = + /* #__PURE__ */ + new Struct() + .uint8("type") + .uint8("action", placeholder()) + .uint64("pointerId") + .uint32("pointerX") + .uint32("pointerY") + .uint16("screenWidth") + .uint16("screenHeight") + .field("pressure", ScrcpyUnsignedFloatFieldDefinition) + .uint32("buttons"); export type ScrcpyInjectTouchControlMessage1_16 = (typeof ScrcpyInjectTouchControlMessage1_16)["TInit"]; export const ScrcpyBackOrScreenOnControlMessage1_16 = EmptyControlMessage; -export const ScrcpySetClipboardControlMessage1_15 = new Struct() - .uint8("type") - .uint32("length") - .string("content", { lengthField: "length" }); +export const ScrcpySetClipboardControlMessage1_15 = + /* #__PURE__ */ + new Struct() + .uint8("type") + .uint32("length") + .string("content", { lengthField: "length" }); export type ScrcpySetClipboardControlMessage1_15 = (typeof ScrcpySetClipboardControlMessage1_15)["TInit"]; -export const ScrcpyClipboardDeviceMessage = new Struct() - .uint32("length") - .string("content", { lengthField: "length" }); +export const ScrcpyClipboardDeviceMessage = + /* #__PURE__ */ + new Struct().uint32("length").string("content", { lengthField: "length" }); diff --git a/libraries/scrcpy/src/options/1_16/scroll.ts b/libraries/scrcpy/src/options/1_16/scroll.ts index a3e85ac8..25ba76c5 100644 --- a/libraries/scrcpy/src/options/1_16/scroll.ts +++ b/libraries/scrcpy/src/options/1_16/scroll.ts @@ -8,14 +8,16 @@ export interface ScrcpyScrollController { ): Uint8Array | undefined; } -export const ScrcpyInjectScrollControlMessage1_16 = new Struct() - .uint8("type") - .uint32("pointerX") - .uint32("pointerY") - .uint16("screenWidth") - .uint16("screenHeight") - .int32("scrollX") - .int32("scrollY"); +export const ScrcpyInjectScrollControlMessage1_16 = + /* #__PURE__ */ + new Struct() + .uint8("type") + .uint32("pointerX") + .uint32("pointerY") + .uint16("screenWidth") + .uint16("screenHeight") + .int32("scrollX") + .int32("scrollY"); /** * Old version of Scrcpy server only supports integer values for scroll. diff --git a/libraries/scrcpy/src/options/1_18.ts b/libraries/scrcpy/src/options/1_18.ts index b6d6f9f4..2df783aa 100644 --- a/libraries/scrcpy/src/options/1_18.ts +++ b/libraries/scrcpy/src/options/1_18.ts @@ -42,9 +42,11 @@ export interface ScrcpyOptionsInit1_18 powerOffOnClose?: boolean; } -export const ScrcpyBackOrScreenOnControlMessage1_18 = new Struct() - .concat(ScrcpyBackOrScreenOnControlMessage1_16) - .uint8("action", placeholder()); +export const ScrcpyBackOrScreenOnControlMessage1_18 = + /* #__PURE__ */ + new Struct() + .concat(ScrcpyBackOrScreenOnControlMessage1_16) + .uint8("action", placeholder()); export type ScrcpyBackOrScreenOnControlMessage1_18 = (typeof ScrcpyBackOrScreenOnControlMessage1_18)["TInit"]; diff --git a/libraries/scrcpy/src/options/1_21.ts b/libraries/scrcpy/src/options/1_21.ts index 03b0d74c..49732c68 100644 --- a/libraries/scrcpy/src/options/1_21.ts +++ b/libraries/scrcpy/src/options/1_21.ts @@ -10,7 +10,9 @@ 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().uint64("sequence"); +export const ScrcpyAckClipboardDeviceMessage = + /* #__PURE__ */ + new Struct().uint64("sequence"); export interface ScrcpyOptionsInit1_21 extends ScrcpyOptionsInit1_18 { clipboardAutosync?: boolean; @@ -20,12 +22,14 @@ function toSnakeCase(input: string): string { return input.replace(/([A-Z])/g, "_$1").toLowerCase(); } -export const ScrcpySetClipboardControlMessage1_21 = new Struct() - .uint8("type") - .uint64("sequence") - .int8("paste", placeholder()) - .uint32("length") - .string("content", { lengthField: "length" }); +export const ScrcpySetClipboardControlMessage1_21 = + /* #__PURE__ */ + new Struct() + .uint8("type") + .uint64("sequence") + .int8("paste", placeholder()) + .uint32("length") + .string("content", { lengthField: "length" }); export type ScrcpySetClipboardControlMessage1_21 = (typeof ScrcpySetClipboardControlMessage1_21)["TInit"]; diff --git a/libraries/scrcpy/src/options/1_22/scroll.ts b/libraries/scrcpy/src/options/1_22/scroll.ts index b75e766d..38cf1514 100644 --- a/libraries/scrcpy/src/options/1_22/scroll.ts +++ b/libraries/scrcpy/src/options/1_22/scroll.ts @@ -5,9 +5,9 @@ import { ScrcpyScrollController1_16, } from "../1_16/index.js"; -export const ScrcpyInjectScrollControlMessage1_22 = new Struct() - .concat(ScrcpyInjectScrollControlMessage1_16) - .int32("buttons"); +export const ScrcpyInjectScrollControlMessage1_22 = + /* #__PURE__ */ + new Struct().concat(ScrcpyInjectScrollControlMessage1_16).int32("buttons"); export type ScrcpyInjectScrollControlMessage1_22 = (typeof ScrcpyInjectScrollControlMessage1_22)["TInit"]; diff --git a/libraries/scrcpy/src/options/1_25/scroll.ts b/libraries/scrcpy/src/options/1_25/scroll.ts index d0fb19fa..2c671bc4 100644 --- a/libraries/scrcpy/src/options/1_25/scroll.ts +++ b/libraries/scrcpy/src/options/1_25/scroll.ts @@ -27,15 +27,17 @@ const ScrcpySignedFloatFieldDefinition = new NumberFieldDefinition( ScrcpySignedFloatNumberVariant, ); -export const ScrcpyInjectScrollControlMessage1_25 = new Struct() - .uint8("type", ScrcpyControlMessageType.InjectScroll as const) - .uint32("pointerX") - .uint32("pointerY") - .uint16("screenWidth") - .uint16("screenHeight") - .field("scrollX", ScrcpySignedFloatFieldDefinition) - .field("scrollY", ScrcpySignedFloatFieldDefinition) - .int32("buttons"); +export const ScrcpyInjectScrollControlMessage1_25 = + /* #__PURE__ */ + new Struct() + .uint8("type", ScrcpyControlMessageType.InjectScroll as const) + .uint32("pointerX") + .uint32("pointerY") + .uint16("screenWidth") + .uint16("screenHeight") + .field("scrollX", ScrcpySignedFloatFieldDefinition) + .field("scrollY", ScrcpySignedFloatFieldDefinition) + .int32("buttons"); export type ScrcpyInjectScrollControlMessage1_25 = (typeof ScrcpyInjectScrollControlMessage1_25)["TInit"]; diff --git a/libraries/scrcpy/src/options/2_0.ts b/libraries/scrcpy/src/options/2_0.ts index 07d4bb4b..d6b7dcfe 100644 --- a/libraries/scrcpy/src/options/2_0.ts +++ b/libraries/scrcpy/src/options/2_0.ts @@ -30,17 +30,19 @@ import type { } from "./types.js"; import { ScrcpyOptions } from "./types.js"; -export const ScrcpyInjectTouchControlMessage2_0 = new Struct() - .uint8("type") - .uint8("action", placeholder()) - .uint64("pointerId") - .uint32("pointerX") - .uint32("pointerY") - .uint16("screenWidth") - .uint16("screenHeight") - .field("pressure", ScrcpyUnsignedFloatFieldDefinition) - .uint32("actionButton") - .uint32("buttons"); +export const ScrcpyInjectTouchControlMessage2_0 = + /* #__PURE__ */ + new Struct() + .uint8("type") + .uint8("action", placeholder()) + .uint64("pointerId") + .uint32("pointerX") + .uint32("pointerY") + .uint16("screenWidth") + .uint16("screenHeight") + .field("pressure", ScrcpyUnsignedFloatFieldDefinition) + .uint32("actionButton") + .uint32("buttons"); export type ScrcpyInjectTouchControlMessage2_0 = (typeof ScrcpyInjectTouchControlMessage2_0)["TInit"]; diff --git a/libraries/stream-extra/src/consumable.ts b/libraries/stream-extra/src/consumable.ts index 6f997ca0..66c04ce0 100644 --- a/libraries/stream-extra/src/consumable.ts +++ b/libraries/stream-extra/src/consumable.ts @@ -17,6 +17,121 @@ function isPromiseLike(value: unknown): value is PromiseLike { } export class Consumable { + static readonly WritableStream = class WritableStream< + in T, + > extends NativeWritableStream> { + static async write( + writer: WritableStreamDefaultWriter>, + value: T, + ) { + const consumable = new Consumable(value); + await writer.write(consumable); + await consumable.consumed; + } + + constructor( + sink: Consumable.WritableStreamSink, + strategy?: QueuingStrategy, + ) { + let wrappedStrategy: QueuingStrategy> | undefined; + if (strategy) { + wrappedStrategy = {}; + if ("highWaterMark" in strategy) { + wrappedStrategy.highWaterMark = strategy.highWaterMark; + } + if ("size" in strategy) { + wrappedStrategy.size = (chunk) => { + return strategy.size!( + chunk instanceof Consumable ? chunk.value : chunk, + ); + }; + } + } + + super( + { + start(controller) { + return sink.start?.(controller); + }, + async write(chunk, controller) { + await chunk.tryConsume((chunk) => + sink.write?.(chunk, controller), + ); + }, + abort(reason) { + return sink.abort?.(reason); + }, + close() { + return sink.close?.(); + }, + }, + wrappedStrategy, + ); + } + }; + + static readonly ReadableStream = class ReadableStream< + T, + > extends NativeReadableStream> { + static async enqueue( + controller: { enqueue: (chunk: Consumable) => void }, + chunk: T, + ) { + const output = new Consumable(chunk); + controller.enqueue(output); + await output.consumed; + } + + constructor( + source: Consumable.ReadableStreamSource, + strategy?: QueuingStrategy, + ) { + let wrappedController: + | Consumable.ReadableStreamController + | undefined; + + let wrappedStrategy: QueuingStrategy> | undefined; + if (strategy) { + wrappedStrategy = {}; + if ("highWaterMark" in strategy) { + wrappedStrategy.highWaterMark = strategy.highWaterMark; + } + if ("size" in strategy) { + wrappedStrategy.size = (chunk) => { + return strategy.size!(chunk.value); + }; + } + } + + super( + { + async start(controller) { + wrappedController = { + async enqueue(chunk) { + await ReadableStream.enqueue(controller, chunk); + }, + close() { + controller.close(); + }, + error(reason) { + controller.error(reason); + }, + }; + + await source.start?.(wrappedController); + }, + async pull() { + await source.pull?.(wrappedController!); + }, + async cancel(reason) { + await source.cancel?.(reason); + }, + }, + wrappedStrategy, + ); + } + }; + readonly #task: Task; readonly #resolver: PromiseResolver; @@ -76,58 +191,7 @@ export namespace Consumable { close?(): void | PromiseLike; } - export class WritableStream extends NativeWritableStream< - Consumable - > { - static async write( - writer: WritableStreamDefaultWriter>, - value: T, - ) { - const consumable = new Consumable(value); - await writer.write(consumable); - await consumable.consumed; - } - - constructor( - sink: WritableStreamSink, - strategy?: QueuingStrategy, - ) { - let wrappedStrategy: QueuingStrategy> | undefined; - if (strategy) { - wrappedStrategy = {}; - if ("highWaterMark" in strategy) { - wrappedStrategy.highWaterMark = strategy.highWaterMark; - } - if ("size" in strategy) { - wrappedStrategy.size = (chunk) => { - return strategy.size!( - chunk instanceof Consumable ? chunk.value : chunk, - ); - }; - } - } - - super( - { - start(controller) { - return sink.start?.(controller); - }, - async write(chunk, controller) { - await chunk.tryConsume((chunk) => - sink.write?.(chunk, controller), - ); - }, - abort(reason) { - return sink.abort?.(reason); - }, - close() { - return sink.close?.(); - }, - }, - wrappedStrategy, - ); - } - } + export type WritableStream = typeof Consumable.WritableStream; export interface ReadableStreamController { enqueue(chunk: T): Promise; @@ -145,61 +209,5 @@ export namespace Consumable { cancel?(reason: unknown): void | PromiseLike; } - export class ReadableStream extends NativeReadableStream> { - static async enqueue( - controller: { enqueue: (chunk: Consumable) => void }, - chunk: T, - ) { - const output = new Consumable(chunk); - controller.enqueue(output); - await output.consumed; - } - - constructor( - source: ReadableStreamSource, - strategy?: QueuingStrategy, - ) { - let wrappedController: ReadableStreamController | undefined; - - let wrappedStrategy: QueuingStrategy> | undefined; - if (strategy) { - wrappedStrategy = {}; - if ("highWaterMark" in strategy) { - wrappedStrategy.highWaterMark = strategy.highWaterMark; - } - if ("size" in strategy) { - wrappedStrategy.size = (chunk) => { - return strategy.size!(chunk.value); - }; - } - } - - super( - { - async start(controller) { - wrappedController = { - async enqueue(chunk) { - await ReadableStream.enqueue(controller, chunk); - }, - close() { - controller.close(); - }, - error(reason) { - controller.error(reason); - }, - }; - - await source.start?.(wrappedController); - }, - async pull() { - await source.pull?.(wrappedController!); - }, - async cancel(reason) { - await source.cancel?.(reason); - }, - }, - wrappedStrategy, - ); - } - } + export type ReadableStream = typeof Consumable.ReadableStream; } diff --git a/libraries/stream-extra/src/maybe-consumable-ns.ts b/libraries/stream-extra/src/maybe-consumable-ns.ts new file mode 100644 index 00000000..bc4de1ee --- /dev/null +++ b/libraries/stream-extra/src/maybe-consumable-ns.ts @@ -0,0 +1,90 @@ +import { Consumable } from "./consumable.js"; +import type { MaybeConsumable } from "./maybe-consumable.js"; +import type { + QueuingStrategy, + WritableStreamDefaultController, +} from "./stream.js"; +import { + WritableStream as NativeWritableStream, + TransformStream, +} from "./stream.js"; + +export function getValue(value: MaybeConsumable): T { + return value instanceof Consumable ? value.value : value; +} + +export function tryConsume( + value: T, + callback: (value: T extends Consumable ? U : T) => R, +): R { + if (value instanceof Consumable) { + return value.tryConsume(callback); + } else { + return callback(value as never); + } +} + +export class UnwrapStream extends TransformStream, T> { + constructor() { + super({ + transform(chunk, controller) { + tryConsume(chunk, (chunk) => { + controller.enqueue(chunk as T); + }); + }, + }); + } +} + +export interface WritableStreamSink { + start?( + controller: WritableStreamDefaultController, + ): void | PromiseLike; + write?( + chunk: T, + controller: WritableStreamDefaultController, + ): void | PromiseLike; + abort?(reason: unknown): void | PromiseLike; + close?(): void | PromiseLike; +} + +export class WritableStream extends NativeWritableStream< + MaybeConsumable +> { + constructor(sink: WritableStreamSink, strategy?: QueuingStrategy) { + let wrappedStrategy: QueuingStrategy> | undefined; + if (strategy) { + wrappedStrategy = {}; + if ("highWaterMark" in strategy) { + wrappedStrategy.highWaterMark = strategy.highWaterMark; + } + if ("size" in strategy) { + wrappedStrategy.size = (chunk) => { + return strategy.size!( + chunk instanceof Consumable ? chunk.value : chunk, + ); + }; + } + } + + super( + { + start(controller) { + return sink.start?.(controller); + }, + async write(chunk, controller) { + await tryConsume(chunk, (chunk) => + sink.write?.(chunk as T, controller), + ); + }, + abort(reason) { + return sink.abort?.(reason); + }, + close() { + return sink.close?.(); + }, + }, + wrappedStrategy, + ); + } +} diff --git a/libraries/stream-extra/src/maybe-consumable.ts b/libraries/stream-extra/src/maybe-consumable.ts index bb3dc55d..34606984 100644 --- a/libraries/stream-extra/src/maybe-consumable.ts +++ b/libraries/stream-extra/src/maybe-consumable.ts @@ -1,101 +1,5 @@ -import { Consumable } from "./consumable.js"; -import type { - QueuingStrategy, - WritableStreamDefaultController, -} from "./stream.js"; -import { - WritableStream as NativeWritableStream, - TransformStream, -} from "./stream.js"; +import type { Consumable } from "./consumable.js"; export type MaybeConsumable = T | Consumable; -export namespace MaybeConsumable { - export function getValue(value: MaybeConsumable): T { - return value instanceof Consumable ? value.value : value; - } - - export function tryConsume( - value: T, - callback: (value: T extends Consumable ? U : T) => R, - ): R { - if (value instanceof Consumable) { - return value.tryConsume(callback); - } else { - return callback(value as never); - } - } - - export class UnwrapStream extends TransformStream< - MaybeConsumable, - T - > { - constructor() { - super({ - transform(chunk, controller) { - MaybeConsumable.tryConsume(chunk, (chunk) => { - controller.enqueue(chunk as T); - }); - }, - }); - } - } - - export interface WritableStreamSink { - start?( - controller: WritableStreamDefaultController, - ): void | PromiseLike; - write?( - chunk: T, - controller: WritableStreamDefaultController, - ): void | PromiseLike; - abort?(reason: unknown): void | PromiseLike; - close?(): void | PromiseLike; - } - - export class WritableStream extends NativeWritableStream< - MaybeConsumable - > { - constructor( - sink: WritableStreamSink, - strategy?: QueuingStrategy, - ) { - let wrappedStrategy: - | QueuingStrategy> - | undefined; - if (strategy) { - wrappedStrategy = {}; - if ("highWaterMark" in strategy) { - wrappedStrategy.highWaterMark = strategy.highWaterMark; - } - if ("size" in strategy) { - wrappedStrategy.size = (chunk) => { - return strategy.size!( - chunk instanceof Consumable ? chunk.value : chunk, - ); - }; - } - } - - super( - { - start(controller) { - return sink.start?.(controller); - }, - async write(chunk, controller) { - await MaybeConsumable.tryConsume(chunk, (chunk) => - sink.write?.(chunk as T, controller), - ); - }, - abort(reason) { - return sink.abort?.(reason); - }, - close() { - return sink.close?.(); - }, - }, - wrappedStrategy, - ); - } - } -} +export * as MaybeConsumable from "./maybe-consumable-ns.js"; diff --git a/libraries/stream-extra/src/stream.ts b/libraries/stream-extra/src/stream.ts index 4e977030..e358c042 100644 --- a/libraries/stream-extra/src/stream.ts +++ b/libraries/stream-extra/src/stream.ts @@ -32,15 +32,13 @@ interface GlobalExtension { TransformStream: typeof TransformStreamType; } -const Global = globalThis as unknown as GlobalExtension; - -export const AbortController = Global.AbortController; - export type ReadableStream = ReadableStreamType; -export const ReadableStream = Global.ReadableStream; - export type WritableStream = WritableStreamType; -export const WritableStream = Global.WritableStream; - export type TransformStream = TransformStreamType; -export const TransformStream = Global.TransformStream; + +export const { + AbortController, + ReadableStream, + WritableStream, + TransformStream, +} = globalThis as unknown as GlobalExtension; diff --git a/libraries/struct/src/struct.spec.ts b/libraries/struct/src/struct.spec.ts index 19c2c027..666b51df 100644 --- a/libraries/struct/src/struct.spec.ts +++ b/libraries/struct/src/struct.spec.ts @@ -33,7 +33,7 @@ class MockDeserializationStream implements ExactReadable { describe("Struct", () => { describe(".constructor", () => { it("should initialize fields", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); assert.deepStrictEqual(struct.options, StructDefaultOptions); assert.strictEqual(struct.size, 0); }); @@ -82,7 +82,7 @@ describe("Struct", () => { } it("should push a field and update size", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); const field1 = "foo"; const fieldDefinition1 = new MockFieldDefinition(4); @@ -104,7 +104,7 @@ describe("Struct", () => { }); it("should throw an error if field name already exists", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); const fieldName = "foo"; struct.field(fieldName, new MockFieldDefinition(4)); assert.throws(() => { @@ -115,7 +115,7 @@ describe("Struct", () => { describe("#number", () => { it("`int8` should append an `int8` field", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); struct.int8("foo"); assert.strictEqual(struct.size, 1); @@ -125,7 +125,7 @@ describe("Struct", () => { }); it("`uint8` should append an `uint8` field", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); struct.uint8("foo"); assert.strictEqual(struct.size, 1); @@ -135,7 +135,7 @@ describe("Struct", () => { }); it("`int16` should append an `int16` field", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); struct.int16("foo"); assert.strictEqual(struct.size, 2); @@ -145,7 +145,7 @@ describe("Struct", () => { }); it("`uint16` should append an `uint16` field", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); struct.uint16("foo"); assert.strictEqual(struct.size, 2); @@ -155,7 +155,7 @@ describe("Struct", () => { }); it("`int32` should append an `int32` field", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); struct.int32("foo"); assert.strictEqual(struct.size, 4); @@ -165,7 +165,7 @@ describe("Struct", () => { }); it("`uint32` should append an `uint32` field", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); struct.uint32("foo"); assert.strictEqual(struct.size, 4); @@ -175,7 +175,7 @@ describe("Struct", () => { }); it("`int64` should append an `int64` field", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); struct.int64("foo"); assert.strictEqual(struct.size, 8); @@ -185,7 +185,7 @@ describe("Struct", () => { }); it("`uint64` should append an `uint64` field", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); struct.uint64("foo"); assert.strictEqual(struct.size, 8); @@ -197,7 +197,7 @@ describe("Struct", () => { describe("#uint8ArrayLike", () => { describe("FixedLengthBufferLikeFieldDefinition", () => { it("`#uint8Array` with fixed length", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); struct.uint8Array("foo", { length: 10 }); assert.strictEqual(struct.size, 10); @@ -214,7 +214,7 @@ describe("Struct", () => { }); it("`#string` with fixed length", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); struct.string("foo", { length: 10 }); assert.strictEqual(struct.size, 10); @@ -233,7 +233,9 @@ describe("Struct", () => { describe("VariableLengthBufferLikeFieldDefinition", () => { it("`#uint8Array` with variable length", () => { - const struct = new Struct().int8("barLength"); + const struct = /* #__PURE__ */ new Struct().int8( + "barLength", + ); assert.strictEqual(struct.size, 1); struct.uint8Array("bar", { lengthField: "barLength" }); @@ -255,7 +257,9 @@ describe("Struct", () => { }); it("`#string` with variable length", () => { - const struct = new Struct().int8("barLength"); + const struct = /* #__PURE__ */ new Struct().int8( + "barLength", + ); assert.strictEqual(struct.size, 1); struct.string("bar", { lengthField: "barLength" }); @@ -280,9 +284,11 @@ describe("Struct", () => { describe("#concat", () => { it("should append all fields from other struct", () => { - const sub = new Struct().int16("int16").int32("int32"); + const sub = /* #__PURE__ */ new Struct() + .int16("int16") + .int32("int32"); - const struct = new Struct() + const struct = /* #__PURE__ */ new Struct() .int8("int8") .concat(sub) .int64("int64"); @@ -312,7 +318,9 @@ describe("Struct", () => { describe("#deserialize", () => { it("should deserialize without dynamic size fields", () => { - const struct = new Struct().int8("foo").int16("bar"); + const struct = /* #__PURE__ */ new Struct() + .int8("foo") + .int16("bar"); const stream = new MockDeserializationStream(); stream.readExactly.mock.mockImplementationOnce( @@ -339,7 +347,7 @@ describe("Struct", () => { }); it("should deserialize with dynamic size fields", () => { - const struct = new Struct() + const struct = /* #__PURE__ */ new Struct() .int8("fooLength") .uint8Array("foo", { lengthField: "fooLength" }); @@ -376,7 +384,10 @@ describe("Struct", () => { describe("#extra", () => { it("should accept plain field", () => { - const struct = new Struct().extra({ foo: 42, bar: true }); + const struct = /* #__PURE__ */ new Struct().extra({ + foo: 42, + bar: true, + }); const stream = new MockDeserializationStream(); const result = struct.deserialize(stream); @@ -411,7 +422,7 @@ describe("Struct", () => { }); it("should accept accessors", () => { - const struct = new Struct().extra({ + const struct = /* #__PURE__ */ new Struct().extra({ get foo() { return 42; }, @@ -438,7 +449,7 @@ describe("Struct", () => { describe("#postDeserialize", () => { it("can throw errors", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); const callback = mock.fn(() => { throw new Error("mock"); }); @@ -450,7 +461,7 @@ describe("Struct", () => { }); it("can replace return value", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); const callback = mock.fn(() => "mock"); struct.postDeserialize(callback); @@ -461,7 +472,7 @@ describe("Struct", () => { }); it("can return nothing", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); const callback = mock.fn(); struct.postDeserialize(callback); @@ -473,7 +484,7 @@ describe("Struct", () => { }); it("should overwrite callback", () => { - const struct = new Struct(); + const struct = /* #__PURE__ */ new Struct(); const callback1 = mock.fn(); struct.postDeserialize(callback1); @@ -492,7 +503,9 @@ describe("Struct", () => { describe("#serialize", () => { it("should serialize without dynamic size fields", () => { - const struct = new Struct().int8("foo").int16("bar"); + const struct = /* #__PURE__ */ new Struct() + .int8("foo") + .int16("bar"); const result = new Uint8Array( struct.serialize({ foo: 0x42, bar: 0x1024 }), @@ -505,7 +518,7 @@ describe("Struct", () => { }); it("should serialize with dynamic size fields", () => { - const struct = new Struct() + const struct = /* #__PURE__ */ new Struct() .int8("fooLength") .uint8Array("foo", { lengthField: "fooLength" }); diff --git a/libraries/struct/src/utils.ts b/libraries/struct/src/utils.ts index 5ca94ba8..e3108404 100644 --- a/libraries/struct/src/utils.ts +++ b/libraries/struct/src/utils.ts @@ -82,9 +82,10 @@ interface GlobalExtension { const { TextEncoder, TextDecoder } = globalThis as unknown as GlobalExtension; -const SharedEncoder = new TextEncoder(); -const SharedDecoder = new TextDecoder(); +const SharedEncoder = /* #__PURE__ */ new TextEncoder(); +const SharedDecoder = /* #__PURE__ */ new TextDecoder(); +/* #__NO_SIDE_EFFECTS__ */ export function encodeUtf8(input: string): Uint8Array { return SharedEncoder.encode(input); }