refactor: remove TypeScript enum and namespace to improve tree-shaking

This commit is contained in:
Simon Chan 2024-08-30 17:27:01 +08:00
parent b1787e8ef4
commit 2c8912e9ac
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
34 changed files with 744 additions and 619 deletions

View file

@ -1,11 +1,13 @@
import type { AdbFeature } from "./features.js"; import type { AdbFeature } from "./features.js";
export enum AdbBannerKey { export const AdbBannerKey = {
Product = "ro.product.name", Product: "ro.product.name",
Model = "ro.product.model", Model: "ro.product.model",
Device = "ro.product.device", Device: "ro.product.device",
Features = "features", Features: "features",
} } as const;
export type AdbBannerKey = (typeof AdbBannerKey)[keyof typeof AdbBannerKey];
export class AdbBanner { export class AdbBanner {
static parse(banner: string) { static parse(banner: string) {

View file

@ -3,40 +3,46 @@ import Struct, { StructEmptyError } from "@yume-chan/struct";
import type { Adb } from "../adb.js"; 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 }) export const AdbFrameBufferV1 =
.uint32("bpp") /* #__PURE__ */
.uint32("size") new Struct({ littleEndian: true })
.uint32("width") .uint32("bpp")
.uint32("height") .uint32("size")
.uint32("red_offset") .uint32("width")
.uint32("red_length") .uint32("height")
.uint32("blue_offset") .uint32("red_offset")
.uint32("blue_length") .uint32("red_length")
.uint32("green_offset") .uint32("blue_offset")
.uint32("green_length") .uint32("blue_length")
.uint32("alpha_offset") .uint32("green_offset")
.uint32("alpha_length") .uint32("green_length")
.uint8Array("data", { lengthField: "size" }); .uint32("alpha_offset")
.uint32("alpha_length")
.uint8Array("data", { lengthField: "size" });
export type AdbFrameBufferV1 = (typeof AdbFrameBufferV1)["TDeserializeResult"]; export type AdbFrameBufferV1 = (typeof AdbFrameBufferV1)["TDeserializeResult"];
export const AdbFrameBufferV2 = new Struct({ littleEndian: true }) export const AdbFrameBufferV2 =
.uint32("bpp") /* #__PURE__ */
.uint32("colorSpace") new Struct({ littleEndian: true })
.uint32("size") .uint32("bpp")
.uint32("width") .uint32("colorSpace")
.uint32("height") .uint32("size")
.uint32("red_offset") .uint32("width")
.uint32("red_length") .uint32("height")
.uint32("blue_offset") .uint32("red_offset")
.uint32("blue_length") .uint32("red_length")
.uint32("green_offset") .uint32("blue_offset")
.uint32("green_length") .uint32("blue_length")
.uint32("alpha_offset") .uint32("green_offset")
.uint32("alpha_length") .uint32("green_length")
.uint8Array("data", { lengthField: "size" }); .uint32("alpha_offset")
.uint32("alpha_length")
.uint8Array("data", { lengthField: "size" });
export type AdbFrameBufferV2 = (typeof AdbFrameBufferV2)["TDeserializeResult"]; export type AdbFrameBufferV2 = (typeof AdbFrameBufferV2)["TDeserializeResult"];

View file

@ -14,9 +14,11 @@ export interface AdbForwardListener {
remoteName: string; remoteName: string;
} }
const AdbReverseStringResponse = new Struct() const AdbReverseStringResponse =
.string("length", { length: 4 }) /* #__PURE__ */
.string("content", { lengthField: "length", lengthFieldRadix: 16 }); new Struct()
.string("length", { length: 4 })
.string("content", { lengthField: "length", lengthFieldRadix: 16 });
export class AdbReverseError extends Error { export class AdbReverseError extends Error {
constructor(message: string) { constructor(message: string) {
@ -33,9 +35,9 @@ export class AdbReverseNotSupportedError extends AdbReverseError {
} }
} }
const AdbReverseErrorResponse = new Struct() const AdbReverseErrorResponse =
.concat(AdbReverseStringResponse) /* #__PURE__ */
.postDeserialize((value) => { new Struct().concat(AdbReverseStringResponse).postDeserialize((value) => {
// https://issuetracker.google.com/issues/37066218 // https://issuetracker.google.com/issues/37066218
// ADB on Android <9 can't create reverse tunnels when connected wirelessly (ADB over Wi-Fi), // 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. // and returns this confusing "more than one device/emulator" error.

View file

@ -19,20 +19,25 @@ import { encodeUtf8 } from "../../../utils/index.js";
import type { AdbSubprocessProtocol } from "./types.js"; import type { AdbSubprocessProtocol } from "./types.js";
export enum AdbShellProtocolId { export const AdbShellProtocolId = {
Stdin, Stdin: 0,
Stdout, Stdout: 1,
Stderr, Stderr: 2,
Exit, Exit: 3,
CloseStdin, CloseStdin: 4,
WindowSizeChange, WindowSizeChange: 5,
} } as const;
export type AdbShellProtocolId =
(typeof AdbShellProtocolId)[keyof typeof AdbShellProtocolId];
// This packet format is used in both directions. // This packet format is used in both directions.
export const AdbShellProtocolPacket = new Struct({ littleEndian: true }) export const AdbShellProtocolPacket =
.uint8("id", placeholder<AdbShellProtocolId>()) /* #__PURE__ */
.uint32("length") new Struct({ littleEndian: true })
.uint8Array("data", { lengthField: "length" }); .uint8("id", placeholder<AdbShellProtocolId>())
.uint32("length")
.uint8Array("data", { lengthField: "length" });
type AdbShellProtocolPacket = StructValueType<typeof AdbShellProtocolPacket>; type AdbShellProtocolPacket = StructValueType<typeof AdbShellProtocolPacket>;

View file

@ -14,18 +14,22 @@ export interface AdbSyncEntry extends AdbSyncStat {
name: string; name: string;
} }
export const AdbSyncEntryResponse = new Struct({ littleEndian: true }) export const AdbSyncEntryResponse =
.concat(AdbSyncLstatResponse) /* #__PURE__ */
.uint32("nameLength") new Struct({ littleEndian: true })
.string("name", { lengthField: "nameLength" }); .concat(AdbSyncLstatResponse)
.uint32("nameLength")
.string("name", { lengthField: "nameLength" });
export type AdbSyncEntryResponse = export type AdbSyncEntryResponse =
(typeof AdbSyncEntryResponse)["TDeserializeResult"]; (typeof AdbSyncEntryResponse)["TDeserializeResult"];
export const AdbSyncEntry2Response = new Struct({ littleEndian: true }) export const AdbSyncEntry2Response =
.concat(AdbSyncStatResponse) /* #__PURE__ */
.uint32("nameLength") new Struct({ littleEndian: true })
.string("name", { lengthField: "nameLength" }); .concat(AdbSyncStatResponse)
.uint32("nameLength")
.string("name", { lengthField: "nameLength" });
export type AdbSyncEntry2Response = export type AdbSyncEntry2Response =
(typeof AdbSyncEntry2Response)["TDeserializeResult"]; (typeof AdbSyncEntry2Response)["TDeserializeResult"];

View file

@ -6,9 +6,11 @@ import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js"; import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js";
import type { AdbSyncSocket } from "./socket.js"; import type { AdbSyncSocket } from "./socket.js";
export const AdbSyncDataResponse = new Struct({ littleEndian: true }) export const AdbSyncDataResponse =
.uint32("dataLength") /* #__PURE__ */
.uint8Array("data", { lengthField: "dataLength" }); new Struct({ littleEndian: true })
.uint32("dataLength")
.uint8Array("data", { lengthField: "dataLength" });
export type AdbSyncDataResponse = export type AdbSyncDataResponse =
(typeof AdbSyncDataResponse)["TDeserializeResult"]; (typeof AdbSyncDataResponse)["TDeserializeResult"];

View file

@ -25,9 +25,9 @@ export interface AdbSyncPushV1Options {
packetSize?: number; packetSize?: number;
} }
export const AdbSyncOkResponse = new Struct({ littleEndian: true }).uint32( export const AdbSyncOkResponse =
"unused", /* #__PURE__ */
); new Struct({ littleEndian: true }).uint32("unused");
async function pipeFileData( async function pipeFileData(
locked: AdbSyncSocketLocked, locked: AdbSyncSocketLocked,
@ -86,19 +86,22 @@ export async function adbSyncPushV1({
} }
} }
export enum AdbSyncSendV2Flags { export const AdbSyncSendV2Flags = {
None = 0, None: 0,
Brotli = 1, Brotli: 1,
/** /**
* 2 * 2
*/ */
Lz4 = 1 << 1, Lz4: 1 << 1,
/** /**
* 4 * 4
*/ */
Zstd = 1 << 2, Zstd: 1 << 2,
DryRun = 0x80000000, DryRun: 0x80000000,
} } as const;
export type AdbSyncSendV2Flags =
(typeof AdbSyncSendV2Flags)[keyof typeof AdbSyncSendV2Flags];
export interface AdbSyncPushV2Options extends AdbSyncPushV1Options { export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
/** /**
@ -110,10 +113,12 @@ export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
dryRun?: boolean; dryRun?: boolean;
} }
export const AdbSyncSendV2Request = new Struct({ littleEndian: true }) export const AdbSyncSendV2Request =
.uint32("id") /* #__PURE__ */
.uint32("mode") new Struct({ littleEndian: true })
.uint32("flags", placeholder<AdbSyncSendV2Flags>()); .uint32("id")
.uint32("mode")
.uint32("flags", placeholder<AdbSyncSendV2Flags>());
export async function adbSyncPushV2({ export async function adbSyncPushV2({
socket, socket,

View file

@ -4,22 +4,22 @@ import { encodeUtf8 } from "../../utils/index.js";
import { adbSyncEncodeId } from "./response.js"; import { adbSyncEncodeId } from "./response.js";
export namespace AdbSyncRequestId { export const AdbSyncRequestId = {
export const List = adbSyncEncodeId("LIST"); List: adbSyncEncodeId("LIST"),
export const ListV2 = adbSyncEncodeId("LIS2"); ListV2: adbSyncEncodeId("LIS2"),
export const Send = adbSyncEncodeId("SEND"); Send: adbSyncEncodeId("SEND"),
export const SendV2 = adbSyncEncodeId("SND2"); SendV2: adbSyncEncodeId("SND2"),
export const Lstat = adbSyncEncodeId("STAT"); Lstat: adbSyncEncodeId("STAT"),
export const Stat = adbSyncEncodeId("STA2"); Stat: adbSyncEncodeId("STA2"),
export const LstatV2 = adbSyncEncodeId("LST2"); LstatV2: adbSyncEncodeId("LST2"),
export const Data = adbSyncEncodeId("DATA"); Data: adbSyncEncodeId("DATA"),
export const Done = adbSyncEncodeId("DONE"); Done: adbSyncEncodeId("DONE"),
export const Receive = adbSyncEncodeId("RECV"); Receive: adbSyncEncodeId("RECV"),
} } as const;
export const AdbSyncNumberRequest = new Struct({ littleEndian: true }) export const AdbSyncNumberRequest =
.uint32("id") /* #__PURE__ */
.uint32("arg"); new Struct({ littleEndian: true }).uint32("id").uint32("arg");
export interface AdbSyncWritable { export interface AdbSyncWritable {
write(buffer: Uint8Array): Promise<void>; write(buffer: Uint8Array): Promise<void>;

View file

@ -18,32 +18,36 @@ function encodeAsciiUnchecked(value: string): Uint8Array {
* Encode ID to numbers for faster comparison * Encode ID to numbers for faster comparison
* @param value A 4-character string * @param value A 4-character string
* @returns A 32-bit integer by encoding the string as little-endian * @returns A 32-bit integer by encoding the string as little-endian
*
* #__NO_SIDE_EFFECTS__
*/ */
export function adbSyncEncodeId(value: string): number { export function adbSyncEncodeId(value: string): number {
const buffer = encodeAsciiUnchecked(value); const buffer = encodeAsciiUnchecked(value);
return getUint32LittleEndian(buffer, 0); return getUint32LittleEndian(buffer, 0);
} }
export namespace AdbSyncResponseId { export const AdbSyncResponseId = {
export const Entry = adbSyncEncodeId("DENT"); Entry: adbSyncEncodeId("DENT"),
export const Entry2 = adbSyncEncodeId("DNT2"); Entry2: adbSyncEncodeId("DNT2"),
export const Lstat = adbSyncEncodeId("STAT"); Lstat: adbSyncEncodeId("STAT"),
export const Stat = adbSyncEncodeId("STA2"); Stat: adbSyncEncodeId("STA2"),
export const Lstat2 = adbSyncEncodeId("LST2"); Lstat2: adbSyncEncodeId("LST2"),
export const Done = adbSyncEncodeId("DONE"); Done: adbSyncEncodeId("DONE"),
export const Data = adbSyncEncodeId("DATA"); Data: adbSyncEncodeId("DATA"),
export const Ok = adbSyncEncodeId("OKAY"); Ok: adbSyncEncodeId("OKAY"),
export const Fail = adbSyncEncodeId("FAIL"); Fail: adbSyncEncodeId("FAIL"),
} };
export class AdbSyncError extends Error {} export class AdbSyncError extends Error {}
export const AdbSyncFailResponse = new Struct({ littleEndian: true }) export const AdbSyncFailResponse =
.uint32("messageLength") /* #__PURE__ */
.string("message", { lengthField: "messageLength" }) new Struct({ littleEndian: true })
.postDeserialize((object) => { .uint32("messageLength")
throw new AdbSyncError(object.message); .string("message", { lengthField: "messageLength" })
}); .postDeserialize((object) => {
throw new AdbSyncError(object.message);
});
export async function adbSyncReadResponse<T>( export async function adbSyncReadResponse<T>(
stream: AsyncExactReadable, stream: AsyncExactReadable,

View file

@ -5,11 +5,13 @@ import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js";
import type { AdbSyncSocket } from "./socket.js"; import type { AdbSyncSocket } from "./socket.js";
// https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36 // https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36
export enum LinuxFileType { export const LinuxFileType = {
Directory = 0o04, Directory: 0o04,
File = 0o10, File: 0o10,
Link = 0o12, Link: 0o12,
} } as const;
export type LinuxFileType = (typeof LinuxFileType)[keyof typeof LinuxFileType];
export interface AdbSyncStat { export interface AdbSyncStat {
mode: number; mode: number;
@ -24,76 +26,92 @@ export interface AdbSyncStat {
ctime?: bigint; ctime?: bigint;
} }
export const AdbSyncLstatResponse = new Struct({ littleEndian: true }) export const AdbSyncLstatResponse =
.int32("mode") /* #__PURE__ */
.int32("size") new Struct({ littleEndian: true })
.int32("mtime") .int32("mode")
.extra({ .int32("size")
get type() { .int32("mtime")
return (this.mode >> 12) as LinuxFileType; .extra({
}, get type() {
get permission() { return (this.mode >> 12) as LinuxFileType;
return this.mode & 0b00001111_11111111; },
}, get permission() {
}) return this.mode & 0b00001111_11111111;
.postDeserialize((object) => { },
if (object.mode === 0 && object.size === 0 && object.mtime === 0) { })
throw new Error("lstat error"); .postDeserialize((object) => {
} if (object.mode === 0 && object.size === 0 && object.mtime === 0) {
}); throw new Error("lstat error");
}
});
export type AdbSyncLstatResponse = export type AdbSyncLstatResponse =
(typeof AdbSyncLstatResponse)["TDeserializeResult"]; (typeof AdbSyncLstatResponse)["TDeserializeResult"];
export enum AdbSyncStatErrorCode { export const AdbSyncStatErrorCode = {
SUCCESS = 0, SUCCESS: 0,
EACCES = 13, EACCES: 13,
EEXIST = 17, EEXIST: 17,
EFAULT = 14, EFAULT: 14,
EFBIG = 27, EFBIG: 27,
EINTR = 4, EINTR: 4,
EINVAL = 22, EINVAL: 22,
EIO = 5, EIO: 5,
EISDIR = 21, EISDIR: 21,
ELOOP = 40, ELOOP: 40,
EMFILE = 24, EMFILE: 24,
ENAMETOOLONG = 36, ENAMETOOLONG: 36,
ENFILE = 23, ENFILE: 23,
ENOENT = 2, ENOENT: 2,
ENOMEM = 12, ENOMEM: 12,
ENOSPC = 28, ENOSPC: 28,
ENOTDIR = 20, ENOTDIR: 20,
EOVERFLOW = 75, EOVERFLOW: 75,
EPERM = 1, EPERM: 1,
EROFS = 30, EROFS: 30,
ETXTBSY = 26, ETXTBSY: 26,
} } as const;
export const AdbSyncStatResponse = new Struct({ littleEndian: true }) export type AdbSyncStatErrorCode =
.uint32("error", placeholder<AdbSyncStatErrorCode>()) (typeof AdbSyncStatErrorCode)[keyof typeof AdbSyncStatErrorCode];
.uint64("dev")
.uint64("ino") const AdbSyncStatErrorName =
.uint32("mode") /* #__PURE__ */
.uint32("nlink") Object.fromEntries(
.uint32("uid") Object.entries(AdbSyncStatErrorCode).map(([key, value]) => [
.uint32("gid") value,
.uint64("size") key,
.uint64("atime") ]),
.uint64("mtime") );
.uint64("ctime")
.extra({ export const AdbSyncStatResponse =
get type() { /* #__PURE__ */
return (this.mode >> 12) as LinuxFileType; new Struct({ littleEndian: true })
}, .uint32("error", placeholder<AdbSyncStatErrorCode>())
get permission() { .uint64("dev")
return this.mode & 0b00001111_11111111; .uint64("ino")
}, .uint32("mode")
}) .uint32("nlink")
.postDeserialize((object) => { .uint32("uid")
if (object.error) { .uint32("gid")
throw new Error(AdbSyncStatErrorCode[object.error]); .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 = export type AdbSyncStatResponse =
(typeof AdbSyncStatResponse)["TDeserializeResult"]; (typeof AdbSyncStatResponse)["TDeserializeResult"];

View file

@ -1,30 +1,36 @@
import { Consumable, TransformStream } from "@yume-chan/stream-extra"; import { Consumable, TransformStream } from "@yume-chan/stream-extra";
import Struct from "@yume-chan/struct"; import Struct from "@yume-chan/struct";
export enum AdbCommand { export const AdbCommand = {
Auth = 0x48545541, // 'AUTH' Auth: 0x48545541, // 'AUTH'
Close = 0x45534c43, // 'CLSE' Close: 0x45534c43, // 'CLSE'
Connect = 0x4e584e43, // 'CNXN' Connect: 0x4e584e43, // 'CNXN'
Okay = 0x59414b4f, // 'OKAY' Okay: 0x59414b4f, // 'OKAY'
Open = 0x4e45504f, // 'OPEN' Open: 0x4e45504f, // 'OPEN'
Write = 0x45545257, // 'WRTE' Write: 0x45545257, // 'WRTE'
} } as const;
export const AdbPacketHeader = new Struct({ littleEndian: true }) export type AdbCommand = (typeof AdbCommand)[keyof typeof AdbCommand];
.uint32("command")
.uint32("arg0") export const AdbPacketHeader =
.uint32("arg1") /* #__PURE__ */
.uint32("payloadLength") new Struct({ littleEndian: true })
.uint32("checksum") .uint32("command")
.int32("magic"); .uint32("arg0")
.uint32("arg1")
.uint32("payloadLength")
.uint32("checksum")
.int32("magic");
export type AdbPacketHeader = (typeof AdbPacketHeader)["TDeserializeResult"]; export type AdbPacketHeader = (typeof AdbPacketHeader)["TDeserializeResult"];
type AdbPacketHeaderInit = (typeof AdbPacketHeader)["TInit"]; type AdbPacketHeaderInit = (typeof AdbPacketHeader)["TInit"];
export const AdbPacket = new Struct({ littleEndian: true }) export const AdbPacket =
.concat(AdbPacketHeader) /* #__PURE__ */
.uint8Array("payload", { lengthField: "payloadLength" }); new Struct({ littleEndian: true })
.concat(AdbPacketHeader)
.uint8Array("payload", { lengthField: "payloadLength" });
export type AdbPacket = (typeof AdbPacket)["TDeserializeResult"]; export type AdbPacket = (typeof AdbPacket)["TDeserializeResult"];

View file

@ -1,13 +1,15 @@
// The order follows // The order follows
// https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/transport.cpp;l=77;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd // https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/transport.cpp;l=77;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd
export enum AdbFeature { export const AdbFeature = {
ShellV2 = "shell_v2", ShellV2: "shell_v2",
Cmd = "cmd", Cmd: "cmd",
StatV2 = "stat_v2", StatV2: "stat_v2",
ListV2 = "ls_v2", ListV2: "ls_v2",
FixedPushMkdir = "fixed_push_mkdir", FixedPushMkdir: "fixed_push_mkdir",
Abb = "abb", Abb: "abb",
AbbExec = "abb_exec", AbbExec: "abb_exec",
SendReceiveV2 = "sendrecv_v2", SendReceiveV2: "sendrecv_v2",
DelayedAck = "delayed_ack", DelayedAck: "delayed_ack",
} } as const;
export type AdbFeature = (typeof AdbFeature)[keyof typeof AdbFeature];

View file

@ -1,23 +1,31 @@
// Array is faster than object literal or `Map` const [charToIndex, indexToChar, paddingChar] = /* #__PURE__ */ (() => {
const charToIndex: number[] = []; // Array is faster than object literal or `Map`
const indexToChar: number[] = []; const charToIndex: number[] = [];
const paddingChar = "=".charCodeAt(0); const indexToChar: number[] = [];
const paddingChar = "=".charCodeAt(0);
function addRange(start: string, end: string) { function addRange(start: string, end: string) {
const charCodeStart = start.charCodeAt(0); const charCodeStart = start.charCodeAt(0);
const charCodeEnd = end.charCodeAt(0); const charCodeEnd = end.charCodeAt(0);
for (let charCode = charCodeStart; charCode <= charCodeEnd; charCode += 1) { for (
charToIndex[charCode] = indexToChar.length; let charCode = charCodeStart;
indexToChar.push(charCode); charCode <= charCodeEnd;
charCode += 1
) {
charToIndex[charCode] = indexToChar.length;
indexToChar.push(charCode);
}
} }
}
addRange("A", "Z"); addRange("A", "Z");
addRange("a", "z"); addRange("a", "z");
addRange("0", "9"); addRange("0", "9");
addRange("+", "+"); addRange("+", "+");
addRange("/", "/"); addRange("/", "/");
return [charToIndex, indexToChar, paddingChar];
})();
/** /**
* Calculate the required length of the output buffer for the given input length. * Calculate the required length of the output buffer for the given input length.

View file

@ -1,3 +1,4 @@
/* #__NO_SIDE_EFFECTS__ */
export const NOOP = () => { export const NOOP = () => {
// no-op // no-op
}; };

View file

@ -31,7 +31,32 @@ const BatteryDumpFields: Record<
current: { type: "number", field: "current" }, 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 { export class DumpSys extends AdbCommandBase {
static readonly Battery = Battery;
async diskStats() { async diskStats() {
const output = await this.adb.subprocess.spawnAndWaitLegacy([ const output = await this.adb.subprocess.spawnAndWaitLegacy([
"dumpsys", "dumpsys",
@ -113,6 +138,9 @@ export class DumpSys extends AdbCommandBase {
export namespace DumpSys { export namespace DumpSys {
export namespace Battery { export namespace Battery {
export type Status = (typeof Status)[keyof typeof Status];
export type Health = (typeof Health)[keyof typeof Health];
export interface Info { export interface Info {
acPowered: boolean; acPowered: boolean;
usbPowered: boolean; usbPowered: boolean;
@ -131,23 +159,5 @@ export namespace DumpSys {
technology?: string; technology?: string;
current?: number; current?: number;
} }
export enum Status {
Unknown = 1,
Charging,
Discharging,
NotCharging,
Full,
}
export enum Health {
Unknown = 1,
Good,
Overheat,
Dead,
OverVoltage,
UnspecifiedFailure,
Cold,
}
} }
} }

View file

@ -17,30 +17,39 @@ import Struct, { decodeUtf8 } from "@yume-chan/struct";
// so instead of adding to core library, it's implemented here // 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 // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/include/android/log.h;l=141;drc=82b5738732161dbaafb2e2f25cce19cd26b9157d
export enum LogId { export const LogId = {
All = -1, All: -1,
Main, Main: 0,
Radio, Radio: 1,
Events, Events: 2,
System, System: 3,
Crash, Crash: 4,
Stats, Stats: 5,
Security, Security: 6,
Kernel, 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 // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/include/android/log.h;l=73;drc=82b5738732161dbaafb2e2f25cce19cd26b9157d
export enum AndroidLogPriority { export const AndroidLogPriority = {
Unknown, Unknown: 0,
Default, Default: 1,
Verbose, Verbose: 2,
Debug, Debug: 3,
Info, Info: 4,
Warn, Warn: 5,
Error, Error: 6,
Fatal, Fatal: 7,
Silent, 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 // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=140;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79
export const AndroidLogPriorityToCharacter: Record<AndroidLogPriority, string> = export const AndroidLogPriorityToCharacter: Record<AndroidLogPriority, string> =
@ -56,16 +65,18 @@ export const AndroidLogPriorityToCharacter: Record<AndroidLogPriority, string> =
[AndroidLogPriority.Silent]: "S", [AndroidLogPriority.Silent]: "S",
}; };
export enum LogcatFormat { export const LogcatFormat = {
Brief, Brief: 0,
Process, Process: 1,
Tag, Tag: 2,
Thread, Thread: 3,
Raw, Raw: 4,
Time, Time: 5,
ThreadTime, ThreadTime: 6,
Long, Long: 7,
} } as const;
export type LogcatFormat = (typeof LogcatFormat)[keyof typeof LogcatFormat];
export interface LogcatFormatModifiers { export interface LogcatFormatModifiers {
microseconds?: boolean; microseconds?: boolean;
@ -88,23 +99,25 @@ export interface LogcatOptions {
const NANOSECONDS_PER_SECOND = BigInt(1e9); 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 // 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 }) export const LoggerEntry =
.uint16("payloadSize") /* #__PURE__ */
.uint16("headerSize") new Struct({ littleEndian: true })
.int32("pid") .uint16("payloadSize")
.uint32("tid") .uint16("headerSize")
.uint32("seconds") .int32("pid")
.uint32("nanoseconds") .uint32("tid")
.uint32("logId") .uint32("seconds")
.uint32("uid") .uint32("nanoseconds")
.extra({ .uint32("logId")
get timestamp() { .uint32("uid")
return ( .extra({
BigInt(this.seconds) * NANOSECONDS_PER_SECOND + get timestamp() {
BigInt(this.nanoseconds) return (
); BigInt(this.seconds) * NANOSECONDS_PER_SECOND +
}, BigInt(this.nanoseconds)
}); );
},
});
export type LoggerEntry = (typeof LoggerEntry)["TDeserializeResult"]; export type LoggerEntry = (typeof LoggerEntry)["TDeserializeResult"];
@ -385,7 +398,7 @@ export interface LogSize {
export class Logcat extends AdbCommandBase { export class Logcat extends AdbCommandBase {
static logIdToName(id: LogId): string { static logIdToName(id: LogId): string {
return LogId[id]; return LogIdName[id]!;
} }
static logNameToId(name: string): LogId { static logNameToId(name: string): LogId {

View file

@ -92,10 +92,9 @@ class VideoFrameCapturer {
} }
} }
const VideoFrameCapturerPool = /*@__PURE__*/ new Pool( const VideoFrameCapturerPool =
() => new VideoFrameCapturer(), /* #__PURE__ */
4, new Pool(() => new VideoFrameCapturer(), 4);
);
export interface WebCodecsVideoDecoderInit { export interface WebCodecsVideoDecoderInit {
/** /**

View file

@ -1,3 +1,3 @@
import { Struct } from "@yume-chan/struct"; import { Struct } from "@yume-chan/struct";
export const EmptyControlMessage = new Struct().uint8("type"); export const EmptyControlMessage = /* #__PURE__ */ new Struct().uint8("type");

View file

@ -205,12 +205,14 @@ export enum AndroidKeyCode {
AndroidPaste, AndroidPaste,
} }
export const ScrcpyInjectKeyCodeControlMessage = new Struct() export const ScrcpyInjectKeyCodeControlMessage =
.uint8("type") /* #__PURE__ */
.uint8("action", placeholder<AndroidKeyEventAction>()) new Struct()
.uint32("keyCode", placeholder<AndroidKeyCode>()) .uint8("type")
.uint32("repeat") .uint8("action", placeholder<AndroidKeyEventAction>())
.uint32("metaState", placeholder<AndroidKeyEventMeta>()); .uint32("keyCode", placeholder<AndroidKeyCode>())
.uint32("repeat")
.uint32("metaState", placeholder<AndroidKeyEventMeta>());
export type ScrcpyInjectKeyCodeControlMessage = export type ScrcpyInjectKeyCodeControlMessage =
(typeof ScrcpyInjectKeyCodeControlMessage)["TInit"]; (typeof ScrcpyInjectKeyCodeControlMessage)["TInit"];

View file

@ -1,9 +1,11 @@
import Struct from "@yume-chan/struct"; import Struct from "@yume-chan/struct";
export const ScrcpyInjectTextControlMessage = new Struct() export const ScrcpyInjectTextControlMessage =
.uint8("type") /* #__PURE__ */
.uint32("length") new Struct()
.string("text", { lengthField: "length" }); .uint8("type")
.uint32("length")
.string("text", { lengthField: "length" });
export type ScrcpyInjectTextControlMessage = export type ScrcpyInjectTextControlMessage =
(typeof ScrcpyInjectTextControlMessage)["TInit"]; (typeof ScrcpyInjectTextControlMessage)["TInit"];

View file

@ -6,9 +6,11 @@ export enum AndroidScreenPowerMode {
Normal = 2, Normal = 2,
} }
export const ScrcpySetScreenPowerModeControlMessage = new Struct() export const ScrcpySetScreenPowerModeControlMessage =
.uint8("type") /* #__PURE__ */
.uint8("mode", placeholder<AndroidScreenPowerMode>()); new Struct()
.uint8("type")
.uint8("mode", placeholder<AndroidScreenPowerMode>());
export type ScrcpySetScreenPowerModeControlMessage = export type ScrcpySetScreenPowerModeControlMessage =
(typeof ScrcpySetScreenPowerModeControlMessage)["TInit"]; (typeof ScrcpySetScreenPowerModeControlMessage)["TInit"];

View file

@ -23,37 +23,43 @@ export const SCRCPY_CONTROL_MESSAGE_TYPES_1_16: readonly ScrcpyControlMessageTyp
/* 10 */ ScrcpyControlMessageType.RotateDevice, /* 10 */ ScrcpyControlMessageType.RotateDevice,
]; ];
export const ScrcpyMediaStreamRawPacket = new Struct() export const ScrcpyMediaStreamRawPacket =
.uint64("pts") /* #__PURE__ */
.uint32("size") new Struct()
.uint8Array("data", { lengthField: "size" }); .uint64("pts")
.uint32("size")
.uint8Array("data", { lengthField: "size" });
export const SCRCPY_MEDIA_PACKET_FLAG_CONFIG = 1n << 63n; export const SCRCPY_MEDIA_PACKET_FLAG_CONFIG = 1n << 63n;
export const ScrcpyInjectTouchControlMessage1_16 = new Struct() export const ScrcpyInjectTouchControlMessage1_16 =
.uint8("type") /* #__PURE__ */
.uint8("action", placeholder<AndroidMotionEventAction>()) new Struct()
.uint64("pointerId") .uint8("type")
.uint32("pointerX") .uint8("action", placeholder<AndroidMotionEventAction>())
.uint32("pointerY") .uint64("pointerId")
.uint16("screenWidth") .uint32("pointerX")
.uint16("screenHeight") .uint32("pointerY")
.field("pressure", ScrcpyUnsignedFloatFieldDefinition) .uint16("screenWidth")
.uint32("buttons"); .uint16("screenHeight")
.field("pressure", ScrcpyUnsignedFloatFieldDefinition)
.uint32("buttons");
export type ScrcpyInjectTouchControlMessage1_16 = export type ScrcpyInjectTouchControlMessage1_16 =
(typeof ScrcpyInjectTouchControlMessage1_16)["TInit"]; (typeof ScrcpyInjectTouchControlMessage1_16)["TInit"];
export const ScrcpyBackOrScreenOnControlMessage1_16 = EmptyControlMessage; export const ScrcpyBackOrScreenOnControlMessage1_16 = EmptyControlMessage;
export const ScrcpySetClipboardControlMessage1_15 = new Struct() export const ScrcpySetClipboardControlMessage1_15 =
.uint8("type") /* #__PURE__ */
.uint32("length") new Struct()
.string("content", { lengthField: "length" }); .uint8("type")
.uint32("length")
.string("content", { lengthField: "length" });
export type ScrcpySetClipboardControlMessage1_15 = export type ScrcpySetClipboardControlMessage1_15 =
(typeof ScrcpySetClipboardControlMessage1_15)["TInit"]; (typeof ScrcpySetClipboardControlMessage1_15)["TInit"];
export const ScrcpyClipboardDeviceMessage = new Struct() export const ScrcpyClipboardDeviceMessage =
.uint32("length") /* #__PURE__ */
.string("content", { lengthField: "length" }); new Struct().uint32("length").string("content", { lengthField: "length" });

View file

@ -8,14 +8,16 @@ export interface ScrcpyScrollController {
): Uint8Array | undefined; ): Uint8Array | undefined;
} }
export const ScrcpyInjectScrollControlMessage1_16 = new Struct() export const ScrcpyInjectScrollControlMessage1_16 =
.uint8("type") /* #__PURE__ */
.uint32("pointerX") new Struct()
.uint32("pointerY") .uint8("type")
.uint16("screenWidth") .uint32("pointerX")
.uint16("screenHeight") .uint32("pointerY")
.int32("scrollX") .uint16("screenWidth")
.int32("scrollY"); .uint16("screenHeight")
.int32("scrollX")
.int32("scrollY");
/** /**
* Old version of Scrcpy server only supports integer values for scroll. * Old version of Scrcpy server only supports integer values for scroll.

View file

@ -42,9 +42,11 @@ export interface ScrcpyOptionsInit1_18
powerOffOnClose?: boolean; powerOffOnClose?: boolean;
} }
export const ScrcpyBackOrScreenOnControlMessage1_18 = new Struct() export const ScrcpyBackOrScreenOnControlMessage1_18 =
.concat(ScrcpyBackOrScreenOnControlMessage1_16) /* #__PURE__ */
.uint8("action", placeholder<AndroidKeyEventAction>()); new Struct()
.concat(ScrcpyBackOrScreenOnControlMessage1_16)
.uint8("action", placeholder<AndroidKeyEventAction>());
export type ScrcpyBackOrScreenOnControlMessage1_18 = export type ScrcpyBackOrScreenOnControlMessage1_18 =
(typeof ScrcpyBackOrScreenOnControlMessage1_18)["TInit"]; (typeof ScrcpyBackOrScreenOnControlMessage1_18)["TInit"];

View file

@ -10,7 +10,9 @@ import type { ScrcpyOptionsInit1_18 } from "./1_18.js";
import { ScrcpyOptions1_18 } from "./1_18.js"; import { ScrcpyOptions1_18 } from "./1_18.js";
import { ScrcpyOptions, toScrcpyOptionValue } from "./types.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 { export interface ScrcpyOptionsInit1_21 extends ScrcpyOptionsInit1_18 {
clipboardAutosync?: boolean; clipboardAutosync?: boolean;
@ -20,12 +22,14 @@ function toSnakeCase(input: string): string {
return input.replace(/([A-Z])/g, "_$1").toLowerCase(); return input.replace(/([A-Z])/g, "_$1").toLowerCase();
} }
export const ScrcpySetClipboardControlMessage1_21 = new Struct() export const ScrcpySetClipboardControlMessage1_21 =
.uint8("type") /* #__PURE__ */
.uint64("sequence") new Struct()
.int8("paste", placeholder<boolean>()) .uint8("type")
.uint32("length") .uint64("sequence")
.string("content", { lengthField: "length" }); .int8("paste", placeholder<boolean>())
.uint32("length")
.string("content", { lengthField: "length" });
export type ScrcpySetClipboardControlMessage1_21 = export type ScrcpySetClipboardControlMessage1_21 =
(typeof ScrcpySetClipboardControlMessage1_21)["TInit"]; (typeof ScrcpySetClipboardControlMessage1_21)["TInit"];

View file

@ -5,9 +5,9 @@ import {
ScrcpyScrollController1_16, ScrcpyScrollController1_16,
} from "../1_16/index.js"; } from "../1_16/index.js";
export const ScrcpyInjectScrollControlMessage1_22 = new Struct() export const ScrcpyInjectScrollControlMessage1_22 =
.concat(ScrcpyInjectScrollControlMessage1_16) /* #__PURE__ */
.int32("buttons"); new Struct().concat(ScrcpyInjectScrollControlMessage1_16).int32("buttons");
export type ScrcpyInjectScrollControlMessage1_22 = export type ScrcpyInjectScrollControlMessage1_22 =
(typeof ScrcpyInjectScrollControlMessage1_22)["TInit"]; (typeof ScrcpyInjectScrollControlMessage1_22)["TInit"];

View file

@ -27,15 +27,17 @@ const ScrcpySignedFloatFieldDefinition = new NumberFieldDefinition(
ScrcpySignedFloatNumberVariant, ScrcpySignedFloatNumberVariant,
); );
export const ScrcpyInjectScrollControlMessage1_25 = new Struct() export const ScrcpyInjectScrollControlMessage1_25 =
.uint8("type", ScrcpyControlMessageType.InjectScroll as const) /* #__PURE__ */
.uint32("pointerX") new Struct()
.uint32("pointerY") .uint8("type", ScrcpyControlMessageType.InjectScroll as const)
.uint16("screenWidth") .uint32("pointerX")
.uint16("screenHeight") .uint32("pointerY")
.field("scrollX", ScrcpySignedFloatFieldDefinition) .uint16("screenWidth")
.field("scrollY", ScrcpySignedFloatFieldDefinition) .uint16("screenHeight")
.int32("buttons"); .field("scrollX", ScrcpySignedFloatFieldDefinition)
.field("scrollY", ScrcpySignedFloatFieldDefinition)
.int32("buttons");
export type ScrcpyInjectScrollControlMessage1_25 = export type ScrcpyInjectScrollControlMessage1_25 =
(typeof ScrcpyInjectScrollControlMessage1_25)["TInit"]; (typeof ScrcpyInjectScrollControlMessage1_25)["TInit"];

View file

@ -30,17 +30,19 @@ import type {
} from "./types.js"; } from "./types.js";
import { ScrcpyOptions } from "./types.js"; import { ScrcpyOptions } from "./types.js";
export const ScrcpyInjectTouchControlMessage2_0 = new Struct() export const ScrcpyInjectTouchControlMessage2_0 =
.uint8("type") /* #__PURE__ */
.uint8("action", placeholder<AndroidMotionEventAction>()) new Struct()
.uint64("pointerId") .uint8("type")
.uint32("pointerX") .uint8("action", placeholder<AndroidMotionEventAction>())
.uint32("pointerY") .uint64("pointerId")
.uint16("screenWidth") .uint32("pointerX")
.uint16("screenHeight") .uint32("pointerY")
.field("pressure", ScrcpyUnsignedFloatFieldDefinition) .uint16("screenWidth")
.uint32("actionButton") .uint16("screenHeight")
.uint32("buttons"); .field("pressure", ScrcpyUnsignedFloatFieldDefinition)
.uint32("actionButton")
.uint32("buttons");
export type ScrcpyInjectTouchControlMessage2_0 = export type ScrcpyInjectTouchControlMessage2_0 =
(typeof ScrcpyInjectTouchControlMessage2_0)["TInit"]; (typeof ScrcpyInjectTouchControlMessage2_0)["TInit"];

View file

@ -17,6 +17,121 @@ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
} }
export class Consumable<T> { export class Consumable<T> {
static readonly WritableStream = class WritableStream<
in T,
> extends NativeWritableStream<Consumable<T>> {
static async write<T>(
writer: WritableStreamDefaultWriter<Consumable<T>>,
value: T,
) {
const consumable = new Consumable(value);
await writer.write(consumable);
await consumable.consumed;
}
constructor(
sink: Consumable.WritableStreamSink<T>,
strategy?: QueuingStrategy<T>,
) {
let wrappedStrategy: QueuingStrategy<Consumable<T>> | 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<Consumable<T>> {
static async enqueue<T>(
controller: { enqueue: (chunk: Consumable<T>) => void },
chunk: T,
) {
const output = new Consumable(chunk);
controller.enqueue(output);
await output.consumed;
}
constructor(
source: Consumable.ReadableStreamSource<T>,
strategy?: QueuingStrategy<T>,
) {
let wrappedController:
| Consumable.ReadableStreamController<T>
| undefined;
let wrappedStrategy: QueuingStrategy<Consumable<T>> | 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 #task: Task;
readonly #resolver: PromiseResolver<void>; readonly #resolver: PromiseResolver<void>;
@ -76,58 +191,7 @@ export namespace Consumable {
close?(): void | PromiseLike<void>; close?(): void | PromiseLike<void>;
} }
export class WritableStream<in T> extends NativeWritableStream< export type WritableStream<in T> = typeof Consumable.WritableStream<T>;
Consumable<T>
> {
static async write<T>(
writer: WritableStreamDefaultWriter<Consumable<T>>,
value: T,
) {
const consumable = new Consumable(value);
await writer.write(consumable);
await consumable.consumed;
}
constructor(
sink: WritableStreamSink<T>,
strategy?: QueuingStrategy<T>,
) {
let wrappedStrategy: QueuingStrategy<Consumable<T>> | 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 interface ReadableStreamController<T> { export interface ReadableStreamController<T> {
enqueue(chunk: T): Promise<void>; enqueue(chunk: T): Promise<void>;
@ -145,61 +209,5 @@ export namespace Consumable {
cancel?(reason: unknown): void | PromiseLike<void>; cancel?(reason: unknown): void | PromiseLike<void>;
} }
export class ReadableStream<T> extends NativeReadableStream<Consumable<T>> { export type ReadableStream<T> = typeof Consumable.ReadableStream<T>;
static async enqueue<T>(
controller: { enqueue: (chunk: Consumable<T>) => void },
chunk: T,
) {
const output = new Consumable(chunk);
controller.enqueue(output);
await output.consumed;
}
constructor(
source: ReadableStreamSource<T>,
strategy?: QueuingStrategy<T>,
) {
let wrappedController: ReadableStreamController<T> | undefined;
let wrappedStrategy: QueuingStrategy<Consumable<T>> | 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,
);
}
}
} }

View file

@ -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<T>(value: MaybeConsumable<T>): T {
return value instanceof Consumable ? value.value : value;
}
export function tryConsume<T, R>(
value: T,
callback: (value: T extends Consumable<infer U> ? U : T) => R,
): R {
if (value instanceof Consumable) {
return value.tryConsume(callback);
} else {
return callback(value as never);
}
}
export class UnwrapStream<T> extends TransformStream<MaybeConsumable<T>, T> {
constructor() {
super({
transform(chunk, controller) {
tryConsume(chunk, (chunk) => {
controller.enqueue(chunk as T);
});
},
});
}
}
export interface WritableStreamSink<in T> {
start?(
controller: WritableStreamDefaultController,
): void | PromiseLike<void>;
write?(
chunk: T,
controller: WritableStreamDefaultController,
): void | PromiseLike<void>;
abort?(reason: unknown): void | PromiseLike<void>;
close?(): void | PromiseLike<void>;
}
export class WritableStream<in T> extends NativeWritableStream<
MaybeConsumable<T>
> {
constructor(sink: WritableStreamSink<T>, strategy?: QueuingStrategy<T>) {
let wrappedStrategy: QueuingStrategy<MaybeConsumable<T>> | 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,
);
}
}

View file

@ -1,101 +1,5 @@
import { Consumable } from "./consumable.js"; import type { Consumable } from "./consumable.js";
import type {
QueuingStrategy,
WritableStreamDefaultController,
} from "./stream.js";
import {
WritableStream as NativeWritableStream,
TransformStream,
} from "./stream.js";
export type MaybeConsumable<T> = T | Consumable<T>; export type MaybeConsumable<T> = T | Consumable<T>;
export namespace MaybeConsumable { export * as MaybeConsumable from "./maybe-consumable-ns.js";
export function getValue<T>(value: MaybeConsumable<T>): T {
return value instanceof Consumable ? value.value : value;
}
export function tryConsume<T, R>(
value: T,
callback: (value: T extends Consumable<infer U> ? U : T) => R,
): R {
if (value instanceof Consumable) {
return value.tryConsume(callback);
} else {
return callback(value as never);
}
}
export class UnwrapStream<T> extends TransformStream<
MaybeConsumable<T>,
T
> {
constructor() {
super({
transform(chunk, controller) {
MaybeConsumable.tryConsume(chunk, (chunk) => {
controller.enqueue(chunk as T);
});
},
});
}
}
export interface WritableStreamSink<in T> {
start?(
controller: WritableStreamDefaultController,
): void | PromiseLike<void>;
write?(
chunk: T,
controller: WritableStreamDefaultController,
): void | PromiseLike<void>;
abort?(reason: unknown): void | PromiseLike<void>;
close?(): void | PromiseLike<void>;
}
export class WritableStream<in T> extends NativeWritableStream<
MaybeConsumable<T>
> {
constructor(
sink: WritableStreamSink<T>,
strategy?: QueuingStrategy<T>,
) {
let wrappedStrategy:
| QueuingStrategy<MaybeConsumable<T>>
| 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,
);
}
}
}

View file

@ -32,15 +32,13 @@ interface GlobalExtension {
TransformStream: typeof TransformStreamType; TransformStream: typeof TransformStreamType;
} }
const Global = globalThis as unknown as GlobalExtension;
export const AbortController = Global.AbortController;
export type ReadableStream<out T> = ReadableStreamType<T>; export type ReadableStream<out T> = ReadableStreamType<T>;
export const ReadableStream = Global.ReadableStream;
export type WritableStream<in T> = WritableStreamType<T>; export type WritableStream<in T> = WritableStreamType<T>;
export const WritableStream = Global.WritableStream;
export type TransformStream<I, O> = TransformStreamType<I, O>; export type TransformStream<I, O> = TransformStreamType<I, O>;
export const TransformStream = Global.TransformStream;
export const {
AbortController,
ReadableStream,
WritableStream,
TransformStream,
} = globalThis as unknown as GlobalExtension;

View file

@ -33,7 +33,7 @@ class MockDeserializationStream implements ExactReadable {
describe("Struct", () => { describe("Struct", () => {
describe(".constructor", () => { describe(".constructor", () => {
it("should initialize fields", () => { it("should initialize fields", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
assert.deepStrictEqual(struct.options, StructDefaultOptions); assert.deepStrictEqual(struct.options, StructDefaultOptions);
assert.strictEqual(struct.size, 0); assert.strictEqual(struct.size, 0);
}); });
@ -82,7 +82,7 @@ describe("Struct", () => {
} }
it("should push a field and update size", () => { it("should push a field and update size", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
const field1 = "foo"; const field1 = "foo";
const fieldDefinition1 = new MockFieldDefinition(4); const fieldDefinition1 = new MockFieldDefinition(4);
@ -104,7 +104,7 @@ describe("Struct", () => {
}); });
it("should throw an error if field name already exists", () => { it("should throw an error if field name already exists", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
const fieldName = "foo"; const fieldName = "foo";
struct.field(fieldName, new MockFieldDefinition(4)); struct.field(fieldName, new MockFieldDefinition(4));
assert.throws(() => { assert.throws(() => {
@ -115,7 +115,7 @@ describe("Struct", () => {
describe("#number", () => { describe("#number", () => {
it("`int8` should append an `int8` field", () => { it("`int8` should append an `int8` field", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
struct.int8("foo"); struct.int8("foo");
assert.strictEqual(struct.size, 1); assert.strictEqual(struct.size, 1);
@ -125,7 +125,7 @@ describe("Struct", () => {
}); });
it("`uint8` should append an `uint8` field", () => { it("`uint8` should append an `uint8` field", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
struct.uint8("foo"); struct.uint8("foo");
assert.strictEqual(struct.size, 1); assert.strictEqual(struct.size, 1);
@ -135,7 +135,7 @@ describe("Struct", () => {
}); });
it("`int16` should append an `int16` field", () => { it("`int16` should append an `int16` field", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
struct.int16("foo"); struct.int16("foo");
assert.strictEqual(struct.size, 2); assert.strictEqual(struct.size, 2);
@ -145,7 +145,7 @@ describe("Struct", () => {
}); });
it("`uint16` should append an `uint16` field", () => { it("`uint16` should append an `uint16` field", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
struct.uint16("foo"); struct.uint16("foo");
assert.strictEqual(struct.size, 2); assert.strictEqual(struct.size, 2);
@ -155,7 +155,7 @@ describe("Struct", () => {
}); });
it("`int32` should append an `int32` field", () => { it("`int32` should append an `int32` field", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
struct.int32("foo"); struct.int32("foo");
assert.strictEqual(struct.size, 4); assert.strictEqual(struct.size, 4);
@ -165,7 +165,7 @@ describe("Struct", () => {
}); });
it("`uint32` should append an `uint32` field", () => { it("`uint32` should append an `uint32` field", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
struct.uint32("foo"); struct.uint32("foo");
assert.strictEqual(struct.size, 4); assert.strictEqual(struct.size, 4);
@ -175,7 +175,7 @@ describe("Struct", () => {
}); });
it("`int64` should append an `int64` field", () => { it("`int64` should append an `int64` field", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
struct.int64("foo"); struct.int64("foo");
assert.strictEqual(struct.size, 8); assert.strictEqual(struct.size, 8);
@ -185,7 +185,7 @@ describe("Struct", () => {
}); });
it("`uint64` should append an `uint64` field", () => { it("`uint64` should append an `uint64` field", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
struct.uint64("foo"); struct.uint64("foo");
assert.strictEqual(struct.size, 8); assert.strictEqual(struct.size, 8);
@ -197,7 +197,7 @@ describe("Struct", () => {
describe("#uint8ArrayLike", () => { describe("#uint8ArrayLike", () => {
describe("FixedLengthBufferLikeFieldDefinition", () => { describe("FixedLengthBufferLikeFieldDefinition", () => {
it("`#uint8Array` with fixed length", () => { it("`#uint8Array` with fixed length", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
struct.uint8Array("foo", { length: 10 }); struct.uint8Array("foo", { length: 10 });
assert.strictEqual(struct.size, 10); assert.strictEqual(struct.size, 10);
@ -214,7 +214,7 @@ describe("Struct", () => {
}); });
it("`#string` with fixed length", () => { it("`#string` with fixed length", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
struct.string("foo", { length: 10 }); struct.string("foo", { length: 10 });
assert.strictEqual(struct.size, 10); assert.strictEqual(struct.size, 10);
@ -233,7 +233,9 @@ describe("Struct", () => {
describe("VariableLengthBufferLikeFieldDefinition", () => { describe("VariableLengthBufferLikeFieldDefinition", () => {
it("`#uint8Array` with variable length", () => { it("`#uint8Array` with variable length", () => {
const struct = new Struct().int8("barLength"); const struct = /* #__PURE__ */ new Struct().int8(
"barLength",
);
assert.strictEqual(struct.size, 1); assert.strictEqual(struct.size, 1);
struct.uint8Array("bar", { lengthField: "barLength" }); struct.uint8Array("bar", { lengthField: "barLength" });
@ -255,7 +257,9 @@ describe("Struct", () => {
}); });
it("`#string` with variable length", () => { it("`#string` with variable length", () => {
const struct = new Struct().int8("barLength"); const struct = /* #__PURE__ */ new Struct().int8(
"barLength",
);
assert.strictEqual(struct.size, 1); assert.strictEqual(struct.size, 1);
struct.string("bar", { lengthField: "barLength" }); struct.string("bar", { lengthField: "barLength" });
@ -280,9 +284,11 @@ describe("Struct", () => {
describe("#concat", () => { describe("#concat", () => {
it("should append all fields from other struct", () => { 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") .int8("int8")
.concat(sub) .concat(sub)
.int64("int64"); .int64("int64");
@ -312,7 +318,9 @@ describe("Struct", () => {
describe("#deserialize", () => { describe("#deserialize", () => {
it("should deserialize without dynamic size fields", () => { 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(); const stream = new MockDeserializationStream();
stream.readExactly.mock.mockImplementationOnce( stream.readExactly.mock.mockImplementationOnce(
@ -339,7 +347,7 @@ describe("Struct", () => {
}); });
it("should deserialize with dynamic size fields", () => { it("should deserialize with dynamic size fields", () => {
const struct = new Struct() const struct = /* #__PURE__ */ new Struct()
.int8("fooLength") .int8("fooLength")
.uint8Array("foo", { lengthField: "fooLength" }); .uint8Array("foo", { lengthField: "fooLength" });
@ -376,7 +384,10 @@ describe("Struct", () => {
describe("#extra", () => { describe("#extra", () => {
it("should accept plain field", () => { 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 stream = new MockDeserializationStream();
const result = struct.deserialize(stream); const result = struct.deserialize(stream);
@ -411,7 +422,7 @@ describe("Struct", () => {
}); });
it("should accept accessors", () => { it("should accept accessors", () => {
const struct = new Struct().extra({ const struct = /* #__PURE__ */ new Struct().extra({
get foo() { get foo() {
return 42; return 42;
}, },
@ -438,7 +449,7 @@ describe("Struct", () => {
describe("#postDeserialize", () => { describe("#postDeserialize", () => {
it("can throw errors", () => { it("can throw errors", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
const callback = mock.fn(() => { const callback = mock.fn(() => {
throw new Error("mock"); throw new Error("mock");
}); });
@ -450,7 +461,7 @@ describe("Struct", () => {
}); });
it("can replace return value", () => { it("can replace return value", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
const callback = mock.fn(() => "mock"); const callback = mock.fn(() => "mock");
struct.postDeserialize(callback); struct.postDeserialize(callback);
@ -461,7 +472,7 @@ describe("Struct", () => {
}); });
it("can return nothing", () => { it("can return nothing", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
const callback = mock.fn(); const callback = mock.fn();
struct.postDeserialize(callback); struct.postDeserialize(callback);
@ -473,7 +484,7 @@ describe("Struct", () => {
}); });
it("should overwrite callback", () => { it("should overwrite callback", () => {
const struct = new Struct(); const struct = /* #__PURE__ */ new Struct();
const callback1 = mock.fn(); const callback1 = mock.fn();
struct.postDeserialize(callback1); struct.postDeserialize(callback1);
@ -492,7 +503,9 @@ describe("Struct", () => {
describe("#serialize", () => { describe("#serialize", () => {
it("should serialize without dynamic size fields", () => { 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( const result = new Uint8Array(
struct.serialize({ foo: 0x42, bar: 0x1024 }), struct.serialize({ foo: 0x42, bar: 0x1024 }),
@ -505,7 +518,7 @@ describe("Struct", () => {
}); });
it("should serialize with dynamic size fields", () => { it("should serialize with dynamic size fields", () => {
const struct = new Struct() const struct = /* #__PURE__ */ new Struct()
.int8("fooLength") .int8("fooLength")
.uint8Array("foo", { lengthField: "fooLength" }); .uint8Array("foo", { lengthField: "fooLength" });

View file

@ -82,9 +82,10 @@ interface GlobalExtension {
const { TextEncoder, TextDecoder } = globalThis as unknown as GlobalExtension; const { TextEncoder, TextDecoder } = globalThis as unknown as GlobalExtension;
const SharedEncoder = new TextEncoder(); const SharedEncoder = /* #__PURE__ */ new TextEncoder();
const SharedDecoder = new TextDecoder(); const SharedDecoder = /* #__PURE__ */ new TextDecoder();
/* #__NO_SIDE_EFFECTS__ */
export function encodeUtf8(input: string): Uint8Array { export function encodeUtf8(input: string): Uint8Array {
return SharedEncoder.encode(input); return SharedEncoder.encode(input);
} }