@yume-chan/adb 0.0.11 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/CHANGELOG.json +39 -0
  2. package/CHANGELOG.md +23 -1
  3. package/README.md +24 -17
  4. package/esm/adb.d.ts +10 -9
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +87 -64
  7. package/esm/adb.js.map +1 -1
  8. package/esm/auth.d.ts +6 -6
  9. package/esm/auth.d.ts.map +1 -1
  10. package/esm/auth.js +8 -6
  11. package/esm/auth.js.map +1 -1
  12. package/esm/backend.d.ts +2 -2
  13. package/esm/commands/install.d.ts.map +1 -1
  14. package/esm/commands/install.js +5 -7
  15. package/esm/commands/install.js.map +1 -1
  16. package/esm/commands/reverse.d.ts +7 -10
  17. package/esm/commands/reverse.d.ts.map +1 -1
  18. package/esm/commands/reverse.js +9 -15
  19. package/esm/commands/reverse.js.map +1 -1
  20. package/esm/commands/subprocess/index.d.ts +19 -20
  21. package/esm/commands/subprocess/index.d.ts.map +1 -1
  22. package/esm/commands/subprocess/index.js +36 -31
  23. package/esm/commands/subprocess/index.js.map +1 -1
  24. package/esm/commands/subprocess/legacy.d.ts +14 -7
  25. package/esm/commands/subprocess/legacy.d.ts.map +1 -1
  26. package/esm/commands/subprocess/legacy.js +14 -5
  27. package/esm/commands/subprocess/legacy.js.map +1 -1
  28. package/esm/commands/subprocess/protocol.d.ts +3 -2
  29. package/esm/commands/subprocess/protocol.d.ts.map +1 -1
  30. package/esm/commands/subprocess/protocol.js +6 -4
  31. package/esm/commands/subprocess/protocol.js.map +1 -1
  32. package/esm/commands/subprocess/protocols/index.d.ts +4 -0
  33. package/esm/commands/subprocess/protocols/index.d.ts.map +1 -0
  34. package/esm/commands/subprocess/protocols/index.js +4 -0
  35. package/esm/commands/subprocess/protocols/index.js.map +1 -0
  36. package/esm/commands/subprocess/protocols/none.d.ts +35 -0
  37. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -0
  38. package/esm/commands/subprocess/protocols/none.js +53 -0
  39. package/esm/commands/subprocess/protocols/none.js.map +1 -0
  40. package/esm/commands/subprocess/protocols/shell.d.ts +39 -0
  41. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -0
  42. package/esm/commands/subprocess/protocols/shell.js +155 -0
  43. package/esm/commands/subprocess/protocols/shell.js.map +1 -0
  44. package/esm/commands/subprocess/protocols/types.d.ts +50 -0
  45. package/esm/commands/subprocess/protocols/types.d.ts.map +1 -0
  46. package/esm/commands/subprocess/protocols/types.js +2 -0
  47. package/esm/commands/subprocess/protocols/types.js.map +1 -0
  48. package/esm/commands/subprocess/types.d.ts +9 -6
  49. package/esm/commands/subprocess/types.d.ts.map +1 -1
  50. package/esm/commands/sync/list.d.ts +25 -1
  51. package/esm/commands/sync/list.d.ts.map +1 -1
  52. package/esm/commands/sync/list.js +40 -5
  53. package/esm/commands/sync/list.js.map +1 -1
  54. package/esm/commands/sync/pull.d.ts.map +1 -1
  55. package/esm/commands/sync/pull.js +6 -3
  56. package/esm/commands/sync/pull.js.map +1 -1
  57. package/esm/commands/sync/request.d.ts +1 -0
  58. package/esm/commands/sync/request.d.ts.map +1 -1
  59. package/esm/commands/sync/request.js +1 -0
  60. package/esm/commands/sync/request.js.map +1 -1
  61. package/esm/commands/sync/response.d.ts +2 -1
  62. package/esm/commands/sync/response.d.ts.map +1 -1
  63. package/esm/commands/sync/response.js +2 -1
  64. package/esm/commands/sync/response.js.map +1 -1
  65. package/esm/commands/sync/stat.d.ts +13 -1
  66. package/esm/commands/sync/stat.d.ts.map +1 -1
  67. package/esm/commands/sync/stat.js +20 -7
  68. package/esm/commands/sync/stat.js.map +1 -1
  69. package/esm/commands/sync/sync.d.ts +13 -35
  70. package/esm/commands/sync/sync.d.ts.map +1 -1
  71. package/esm/commands/sync/sync.js +39 -9
  72. package/esm/commands/sync/sync.js.map +1 -1
  73. package/esm/crypto.d.ts +23 -10
  74. package/esm/crypto.d.ts.map +1 -1
  75. package/esm/crypto.js +64 -46
  76. package/esm/crypto.js.map +1 -1
  77. package/esm/features.d.ts +4 -2
  78. package/esm/features.d.ts.map +1 -1
  79. package/esm/features.js +6 -2
  80. package/esm/features.js.map +1 -1
  81. package/esm/packet.d.ts +21 -2
  82. package/esm/packet.d.ts.map +1 -1
  83. package/esm/packet.js +2 -2
  84. package/esm/packet.js.map +1 -1
  85. package/esm/socket/dispatcher.d.ts +34 -23
  86. package/esm/socket/dispatcher.d.ts.map +1 -1
  87. package/esm/socket/dispatcher.js +92 -70
  88. package/esm/socket/dispatcher.js.map +1 -1
  89. package/esm/socket/socket.d.ts +29 -8
  90. package/esm/socket/socket.d.ts.map +1 -1
  91. package/esm/socket/socket.js +41 -27
  92. package/esm/socket/socket.js.map +1 -1
  93. package/esm/stream/detect.d.ts +1 -17
  94. package/esm/stream/detect.d.ts.map +1 -1
  95. package/esm/stream/detect.js +4 -5
  96. package/esm/stream/detect.js.map +1 -1
  97. package/esm/stream/detect.polyfill.d.ts.map +1 -1
  98. package/esm/stream/detect.polyfill.js +4 -1
  99. package/esm/stream/detect.polyfill.js.map +1 -1
  100. package/esm/stream/transform.d.ts +37 -32
  101. package/esm/stream/transform.d.ts.map +1 -1
  102. package/esm/stream/transform.js +106 -92
  103. package/esm/stream/transform.js.map +1 -1
  104. package/esm/utils/encoding.d.ts.map +1 -1
  105. package/esm/utils/encoding.js.map +1 -1
  106. package/esm/utils/index.d.ts +1 -1
  107. package/esm/utils/index.d.ts.map +1 -1
  108. package/esm/utils/index.js +1 -1
  109. package/esm/utils/index.js.map +1 -1
  110. package/package.json +6 -6
  111. package/src/adb.ts +105 -76
  112. package/src/auth.ts +21 -18
  113. package/src/backend.ts +2 -2
  114. package/src/commands/install.ts +7 -9
  115. package/src/commands/reverse.ts +13 -24
  116. package/src/commands/subprocess/index.ts +57 -42
  117. package/src/commands/subprocess/protocols/index.ts +3 -0
  118. package/src/commands/subprocess/{legacy.ts → protocols/none.ts} +20 -10
  119. package/src/commands/subprocess/{protocol.ts → protocols/shell.ts} +22 -17
  120. package/src/commands/subprocess/{types.ts → protocols/types.ts} +13 -9
  121. package/src/commands/sync/list.ts +51 -5
  122. package/src/commands/sync/pull.ts +6 -3
  123. package/src/commands/sync/request.ts +1 -0
  124. package/src/commands/sync/response.ts +8 -2
  125. package/src/commands/sync/stat.ts +36 -9
  126. package/src/commands/sync/sync.ts +55 -19
  127. package/src/crypto.ts +72 -56
  128. package/src/features.ts +6 -2
  129. package/src/packet.ts +17 -6
  130. package/src/socket/dispatcher.ts +133 -88
  131. package/src/socket/socket.ts +61 -48
  132. package/src/stream/detect.polyfill.ts +4 -1
  133. package/src/stream/detect.ts +4 -24
  134. package/src/stream/transform.ts +133 -116
  135. package/src/utils/index.ts +1 -1
  136. package/tsconfig.build.tsbuildinfo +1 -1
  137. package/src/utils/encoding.ts +0 -13
@@ -1,32 +1,24 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
+ import type { Disposable } from "@yume-chan/event";
2
3
  import { AdbCommand } from '../packet.js';
3
- import { ChunkStream, DuplexStreamFactory, pipeFrom, ReadableStream, WritableStream, type PushReadableStreamController } from '../stream/index.js';
4
- import type { AdbPacketDispatcher } from './dispatcher.js';
4
+ import { ChunkStream, DuplexStreamFactory, pipeFrom, PushReadableStream, type PushReadableStreamController, type ReadableStream, type ReadableWritablePair, type WritableStream } from '../stream/index.js';
5
+ import type { AdbPacketDispatcher, Closeable } from './dispatcher.js';
5
6
 
6
7
  export interface AdbSocketInfo {
7
8
  localId: number;
8
9
  remoteId: number;
9
10
 
10
11
  localCreated: boolean;
11
-
12
12
  serviceString: string;
13
13
  }
14
14
 
15
- export interface AdbSocketConstructionOptions {
15
+ export interface AdbSocketConstructionOptions extends AdbSocketInfo {
16
16
  dispatcher: AdbPacketDispatcher;
17
17
 
18
- localId: number;
19
-
20
- remoteId: number;
21
-
22
- localCreated: boolean;
23
-
24
- serviceString: string;
25
-
26
18
  highWaterMark?: number | undefined;
27
19
  }
28
20
 
29
- export class AdbSocket implements AdbSocketInfo {
21
+ export class AdbSocketController implements AdbSocketInfo, ReadableWritablePair<Uint8Array, Uint8Array>, Closeable, Disposable {
30
22
  private readonly dispatcher!: AdbPacketDispatcher;
31
23
 
32
24
  public readonly localId!: number;
@@ -46,6 +38,9 @@ export class AdbSocket implements AdbSocketInfo {
46
38
  private _closed = false;
47
39
  public get closed() { return this._closed; }
48
40
 
41
+ private _socket: AdbSocket;
42
+ public get socket() { return this._socket; }
43
+
49
44
  public constructor(options: AdbSocketConstructionOptions) {
50
45
  Object.assign(this, options);
51
46
 
@@ -55,17 +50,32 @@ export class AdbSocket implements AdbSocketInfo {
55
50
 
56
51
  this._factory = new DuplexStreamFactory<Uint8Array, Uint8Array>({
57
52
  close: async () => {
58
- await this.close();
53
+ await this.dispatcher.sendPacket(
54
+ AdbCommand.Close,
55
+ this.localId,
56
+ this.remoteId
57
+ );
58
+
59
+ // Don't `dispose` here, we need to wait for `CLSE` response packet.
60
+ return false;
59
61
  },
60
- });
62
+ dispose: () => {
63
+ this._closed = true;
61
64
 
62
- this._readable = this._factory.createPushReadable(controller => {
63
- this._readableController = controller;
64
- }, {
65
- highWaterMark: options.highWaterMark ?? 16 * 1024,
66
- size(chunk) { return chunk.byteLength; }
65
+ // Error out the pending writes
66
+ this._writePromise?.reject(new Error('Socket closed'));
67
+ },
67
68
  });
68
69
 
70
+ this._readable = this._factory.wrapReadable(
71
+ new PushReadableStream(controller => {
72
+ this._readableController = controller;
73
+ }, {
74
+ highWaterMark: options.highWaterMark ?? 16 * 1024,
75
+ size(chunk) { return chunk.byteLength; }
76
+ })
77
+ );
78
+
69
79
  this.writable = pipeFrom(
70
80
  this._factory.createWritable({
71
81
  write: async (chunk) => {
@@ -80,50 +90,53 @@ export class AdbSocket implements AdbSocketInfo {
80
90
  await this._writePromise.promise;
81
91
  },
82
92
  }),
83
- new ChunkStream(this.dispatcher.maxPayloadSize)
93
+ new ChunkStream(this.dispatcher.options.maxPayloadSize)
84
94
  );
95
+
96
+ this._socket = new AdbSocket(this);
85
97
  }
86
98
 
87
- /**
88
- * @internal
89
- */
90
99
  public async enqueue(packet: Uint8Array) {
91
100
  await this._readableController.enqueue(packet);
92
101
  }
93
102
 
94
- /**
95
- * @internal
96
- */
97
103
  public ack() {
98
104
  this._writePromise?.resolve();
99
105
  }
100
106
 
101
107
  public async close(): Promise<void> {
102
- // Error out the pending writes
103
- this._writePromise?.reject(new Error('Socket closed'));
104
-
105
- if (!this._closed) {
106
- this._closed = true;
107
-
108
- // Only send close packet when `close` is called before `dispose`
109
- // (the client initiated the close)
110
- await this.dispatcher.sendPacket(
111
- AdbCommand.Close,
112
- this.localId,
113
- this.remoteId
114
- );
115
- }
108
+ this._factory.close();
116
109
  }
117
110
 
118
- /**
119
- * @internal
120
- */
121
111
  public dispose() {
122
- this._closed = true;
112
+ this._factory.dispose();
113
+ }
114
+ }
123
115
 
124
- this._factory.close();
116
+ /**
117
+ * AdbSocket is a duplex stream.
118
+ *
119
+ * To close it, call either `socket.close()`,
120
+ * `socket.readable.cancel()`, `socket.readable.getReader().cancel()`,
121
+ * `socket.writable.abort()`, `socket.writable.getWriter().abort()`,
122
+ * `socket.writable.close()` or `socket.writable.getWriter().close()`.
123
+ */
124
+ export class AdbSocket implements AdbSocketInfo, ReadableWritablePair<Uint8Array, Uint8Array>{
125
+ private _controller: AdbSocketController;
126
+
127
+ public get localId(): number { return this._controller.localId; }
128
+ public get remoteId(): number { return this._controller.remoteId; }
129
+ public get localCreated(): boolean { return this._controller.localCreated; }
130
+ public get serviceString(): string { return this._controller.serviceString; }
131
+
132
+ public get readable(): ReadableStream<Uint8Array> { return this._controller.readable; }
133
+ public get writable(): WritableStream<Uint8Array> { return this._controller.writable; }
134
+
135
+ public constructor(controller: AdbSocketController) {
136
+ this._controller = controller;
137
+ }
125
138
 
126
- // Close `writable` side
127
- this.close();
139
+ public close() {
140
+ return this._controller.close();
128
141
  }
129
142
  }
@@ -1,7 +1,10 @@
1
1
  // cspell: ignore ponyfill
2
2
 
3
3
  import type { AbortSignal } from "web-streams-polyfill";
4
- // TODO: import the ponyfill instead (as a library should do)
4
+ // TODO: Upgrade to `web-streams-polyfill@4.0.0-beta.2` once released.
5
+ // `web-streams-polyfill@4.0.0-beta.1` changed the default export to ponyfill,
6
+ // But it forgot to include `type` export so it's unusable.
7
+ // See https://github.com/MattiasBuelens/web-streams-polyfill/pull/107
5
8
  export * from 'web-streams-polyfill';
6
9
 
7
10
  /** A controller object that allows you to abort one or more DOM requests as and when desired. */
@@ -1,25 +1,5 @@
1
- // cspell: ignore ponyfill
1
+ // cspell: ignore vercel
2
2
 
3
- import type { AbortSignal } from "web-streams-polyfill";
4
- // TODO: import the ponyfill instead (as a library should do)
5
- export * from 'web-streams-polyfill';
6
-
7
- /** A controller object that allows you to abort one or more DOM requests as and when desired. */
8
- export interface AbortController {
9
- /**
10
- * Returns the AbortSignal object associated with this object.
11
- */
12
-
13
- readonly signal: AbortSignal;
14
- /**
15
- * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted.
16
- */
17
- abort(): void;
18
- }
19
-
20
- export let AbortController: {
21
- prototype: AbortController;
22
- new(): AbortController;
23
- };
24
-
25
- ({ AbortController } = globalThis as any);
3
+ // Always use polyfilled version because
4
+ // Vercel doesn't support Node.js 16 (`streams/web` module) yet
5
+ export * from './detect.polyfill.js';
@@ -1,93 +1,57 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
2
  import type Struct from "@yume-chan/struct";
3
- import type { StructValueType } from "@yume-chan/struct";
3
+ import type { StructValueType, ValueOrPromise } from "@yume-chan/struct";
4
4
  import { decodeUtf8 } from "../utils/index.js";
5
5
  import { BufferedStream, BufferedStreamEndedError } from "./buffered.js";
6
- import { AbortController, AbortSignal, ReadableStream, ReadableStreamDefaultReader, TransformStream, WritableStream, WritableStreamDefaultWriter, type QueuingStrategy, type ReadableStreamDefaultController, type ReadableWritablePair, type UnderlyingSink, type UnderlyingSource } from "./detect.js";
6
+ import { AbortController, AbortSignal, ReadableStream, ReadableStreamDefaultReader, TransformStream, WritableStream, WritableStreamDefaultWriter, type QueuingStrategy, type ReadableStreamDefaultController, type ReadableWritablePair, type UnderlyingSink } from "./detect.js";
7
7
 
8
8
  export interface DuplexStreamFactoryOptions {
9
- preventCloseReadableStreams?: boolean | undefined;
9
+ close?: (() => ValueOrPromise<boolean | void>) | undefined;
10
10
 
11
- close?: (() => void | Promise<void>) | undefined;
11
+ dispose?: (() => void | Promise<void>) | undefined;
12
12
  }
13
13
 
14
+ /**
15
+ * A factory for creating a duplex stream.
16
+ *
17
+ * It can create multiple `ReadableStream`s and `WritableStream`s,
18
+ * when any of them is closed, all other streams will be closed as well.
19
+ */
14
20
  export class DuplexStreamFactory<R, W> {
15
21
  private readableControllers: ReadableStreamDefaultController<R>[] = [];
16
- private pushReadableControllers: PushReadableStreamController<R>[] = [];
22
+
23
+ private _writableClosed = false;
24
+ public get writableClosed() { return this._writableClosed; }
17
25
 
18
26
  private _closed = new PromiseResolver<void>();
19
27
  public get closed() { return this._closed.promise; }
20
28
 
21
29
  private options: DuplexStreamFactoryOptions;
22
30
 
23
- private _closeRequestedByReadable = false;
24
- private _writableClosed = false;
25
-
26
31
  public constructor(options?: DuplexStreamFactoryOptions) {
27
32
  this.options = options ?? {};
28
33
  }
29
34
 
30
- public createPushReadable(source: PushReadableStreamSource<R>, strategy?: QueuingStrategy<R>): PushReadableStream<R> {
31
- return new PushReadableStream<R>(controller => {
32
- this.pushReadableControllers.push(controller);
33
-
34
- controller.abortSignal.addEventListener('abort', async () => {
35
- this._closeRequestedByReadable = true;
35
+ public wrapReadable(readable: ReadableStream<R>): WrapReadableStream<R> {
36
+ return new WrapReadableStream<R>({
37
+ start: (controller) => {
38
+ this.readableControllers.push(controller);
39
+ return readable;
40
+ },
41
+ cancel: async () => {
42
+ // cancel means the local peer closes the connection first.
36
43
  await this.close();
37
- });
38
-
39
- source({
40
- abortSignal: controller.abortSignal,
41
- async enqueue(chunk) {
42
- await controller.enqueue(chunk);
43
- },
44
- close: async () => {
45
- controller.close();
46
- this._closeRequestedByReadable = true;
47
- await this.close();
48
- },
49
- error: async (e?: any) => {
50
- controller.error(e);
51
- this._closeRequestedByReadable = true;
52
- await this.close();
53
- },
54
- });
55
- }, strategy);
56
- };
57
-
58
- public createWrapReadable(readable: ReadableStream<R>): WrapReadableStream<R, ReadableStream<R>, void> {
59
- return new WrapReadableStream<R, ReadableStream<R>, void>({
60
- async start() {
61
- return {
62
- readable,
63
- state: undefined,
64
- };
65
44
  },
66
45
  close: async () => {
67
- this._closeRequestedByReadable = true;
68
- await this.close();
46
+ // stream end means the remote peer closed the connection first.
47
+ await this.dispose();
69
48
  },
70
49
  });
71
50
  }
72
51
 
73
- public createReadable(source?: UnderlyingSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R> {
74
- return new ReadableStream<R>({
75
- start: async (controller) => {
76
- this.readableControllers.push(controller);
77
- await source?.start?.(controller);
78
- },
79
- pull: (controller) => {
80
- return source?.pull?.(controller);
81
- },
82
- cancel: async (reason) => {
83
- await source?.cancel?.(reason);
84
- this._closeRequestedByReadable = true;
85
- await this.close();
86
- },
87
- }, strategy);
88
- }
89
-
90
52
  public createWritable(sink: UnderlyingSink<W>, strategy?: QueuingStrategy<W>): WritableStream<W> {
53
+ // `WritableStream` has no way to tell if the remote peer has closed the connection.
54
+ // So it only triggers `close`.
91
55
  return new WritableStream<W>({
92
56
  start: async (controller) => {
93
57
  await sink.start?.(controller);
@@ -99,41 +63,37 @@ export class DuplexStreamFactory<R, W> {
99
63
 
100
64
  await sink.write?.(chunk, controller);
101
65
  },
102
- close: async () => {
103
- await sink.close?.();
104
- this.close();
105
- },
106
66
  abort: async (reason) => {
107
67
  await sink.abort?.(reason);
108
68
  await this.close();
109
69
  },
70
+ close: async () => {
71
+ await sink.close?.();
72
+ await this.close();
73
+ },
110
74
  }, strategy);
111
75
  }
112
76
 
113
- public async closeReadableStreams() {
114
- this._closed.resolve();
115
- await this.options.close?.();
116
-
117
- for (const controller of this.readableControllers) {
118
- try {
119
- controller.close();
120
- } catch { }
77
+ public async close() {
78
+ if (this._writableClosed) {
79
+ return;
121
80
  }
122
-
123
- for (const controller of this.pushReadableControllers) {
124
- try {
125
- controller.close();
126
- } catch { }
81
+ this._writableClosed = true;
82
+ if (await this.options.close?.() !== false) {
83
+ // `close` can return `false` to disable automatic `dispose`.
84
+ await this.dispose();
127
85
  }
128
86
  }
129
87
 
130
- public async close() {
88
+ public async dispose() {
131
89
  this._writableClosed = true;
90
+ this._closed.resolve();
132
91
 
133
- if (this._closeRequestedByReadable ||
134
- !this.options.preventCloseReadableStreams) {
135
- await this.closeReadableStreams();
92
+ for (const controller of this.readableControllers) {
93
+ try { controller.close(); } catch { }
136
94
  }
95
+
96
+ await this.options.dispose?.();
137
97
  }
138
98
  }
139
99
 
@@ -147,21 +107,21 @@ export class DecodeUtf8Stream extends TransformStream<Uint8Array, string>{
147
107
  }
148
108
  }
149
109
 
150
- export class GatherStringStream extends TransformStream<string, string>{
110
+ export class GatherStringStream extends WritableStream<string>{
151
111
  // Optimization: rope (concat strings) is faster than `[].join('')`
152
112
  private _result = '';
153
113
  public get result() { return this._result; }
154
114
 
155
115
  public constructor() {
156
116
  super({
157
- transform: (chunk) => {
117
+ write: (chunk) => {
158
118
  this._result += chunk;
159
119
  },
160
120
  });
161
121
  }
162
122
  }
163
123
 
164
- // TODO: Find other ways to implement `StructTransformStream`
124
+ // TODO: StructTransformStream: Looking for better implementation
165
125
  export class StructDeserializeStream<T extends Struct<any, any, any, any>>
166
126
  implements ReadableWritablePair<Uint8Array, StructValueType<T>>{
167
127
  private _readable: ReadableStream<StructValueType<T>>;
@@ -219,25 +179,43 @@ export class StructSerializeStream<T extends Struct<any, any, any, any>>
219
179
  }
220
180
  }
221
181
 
222
- export interface WritableStreamWrapper<T, W extends WritableStream<T>, S> {
223
- start(): Promise<{ writable: W, state: S; }>;
224
- close?(state: S): Promise<void>;
182
+ export type WrapWritableStreamStart<T> = () => ValueOrPromise<WritableStream<T>>;
183
+
184
+ export interface WritableStreamWrapper<T> {
185
+ start: WrapWritableStreamStart<T>;
186
+ close?(): Promise<void>;
187
+ }
188
+
189
+ async function getWrappedWritableStream<T>(
190
+ wrapper: WritableStream<T> | WrapWritableStreamStart<T> | WritableStreamWrapper<T>
191
+ ) {
192
+ if ('start' in wrapper) {
193
+ return await wrapper.start();
194
+ } else if (typeof wrapper === 'function') {
195
+ return await wrapper();
196
+ } else {
197
+ // Can't use `wrapper instanceof WritableStream`
198
+ // Because we want to be compatible with any WritableStream-like objects
199
+ return wrapper;
200
+ }
225
201
  }
226
202
 
227
- export class WrapWritableStream<T, W extends WritableStream<T>, S> extends WritableStream<T>{
228
- public writable!: W;
203
+ export class WrapWritableStream<T> extends WritableStream<T> {
204
+ public writable!: WritableStream<T>;
229
205
 
230
206
  private writer!: WritableStreamDefaultWriter<T>;
231
207
 
232
- private state!: S;
233
-
234
- public constructor(wrapper: WritableStreamWrapper<T, W, S>) {
208
+ public constructor(wrapper: WritableStream<T> | WrapWritableStreamStart<T> | WritableStreamWrapper<T>) {
235
209
  super({
236
210
  start: async () => {
237
- const { writable, state } = await wrapper.start();
238
- this.writable = writable;
239
- this.writer = writable.getWriter();
240
- this.state = state;
211
+ // `start` is invoked before `ReadableStream`'s constructor finish,
212
+ // so using `this` synchronously causes
213
+ // "Must call super constructor in derived class before accessing 'this' or returning from derived constructor".
214
+ // Queue a microtask to avoid this.
215
+ await Promise.resolve();
216
+
217
+ this.writable = await getWrappedWritableStream(wrapper);
218
+ this.writer = this.writable.getWriter();
241
219
  },
242
220
  write: async (chunk) => {
243
221
  // Maintain back pressure
@@ -246,45 +224,84 @@ export class WrapWritableStream<T, W extends WritableStream<T>, S> extends Writa
246
224
  },
247
225
  abort: async (reason) => {
248
226
  await this.writer.abort(reason);
249
- wrapper.close?.(this.state);
227
+ if ('close' in wrapper) {
228
+ await wrapper.close?.();
229
+ }
250
230
  },
251
231
  close: async () => {
232
+ // Close the inner stream first.
233
+ // Usually the inner stream is a logical sub-stream over the outer stream,
234
+ // closing the outer stream first will make the inner stream incapable of
235
+ // sending data in its `close` handler.
252
236
  await this.writer.close();
253
- await wrapper.close?.(this.state);
237
+ if ('close' in wrapper) {
238
+ await wrapper.close?.();
239
+ }
254
240
  },
255
241
  });
256
242
  }
257
243
  }
258
244
 
259
- export interface ReadableStreamWrapper<T, R extends ReadableStream<T>, S> {
260
- start(): Promise<{ readable: R, state: S; }>;
261
- close?(state: S): Promise<void>;
245
+ export type WrapReadableStreamStart<T> = (controller: ReadableStreamDefaultController<T>) => ValueOrPromise<ReadableStream<T>>;
246
+
247
+ export interface ReadableStreamWrapper<T> {
248
+ start: WrapReadableStreamStart<T>;
249
+ cancel?(reason?: any): ValueOrPromise<void>;
250
+ close?(): ValueOrPromise<void>;
262
251
  }
263
252
 
264
- export class WrapReadableStream<T, R extends ReadableStream<T>, S> extends ReadableStream<T>{
265
- public readable!: R;
253
+ function getWrappedReadableStream<T>(
254
+ wrapper: ReadableStream<T> | WrapReadableStreamStart<T> | ReadableStreamWrapper<T>,
255
+ controller: ReadableStreamDefaultController<T>
256
+ ) {
257
+ if ('start' in wrapper) {
258
+ return wrapper.start(controller);
259
+ } else if (typeof wrapper === 'function') {
260
+ return wrapper(controller);
261
+ } else {
262
+ // Can't use `wrapper instanceof ReadableStream`
263
+ // Because we want to be compatible with any ReadableStream-like objects
264
+ return wrapper;
265
+ }
266
+ }
266
267
 
267
- private reader!: ReadableStreamDefaultReader<T>;
268
+ /**
269
+ * This class has multiple usages:
270
+ *
271
+ * 1. Get notified when the stream is cancelled or closed.
272
+ * 2. Synchronously create a `ReadableStream` by asynchronously return another `ReadableStream`.
273
+ * 3. Convert native `ReadableStream`s to polyfilled ones so they can `pipe` between.
274
+ */
275
+ export class WrapReadableStream<T> extends ReadableStream<T>{
276
+ public readable!: ReadableStream<T>;
268
277
 
269
- private state!: S;
278
+ private reader!: ReadableStreamDefaultReader<T>;
270
279
 
271
- public constructor(wrapper: ReadableStreamWrapper<T, R, S>) {
280
+ public constructor(wrapper: ReadableStream<T> | WrapReadableStreamStart<T> | ReadableStreamWrapper<T>) {
272
281
  super({
273
- start: async () => {
274
- const { readable, state } = await wrapper.start();
275
- this.readable = readable;
276
- this.reader = readable.getReader();
277
- this.state = state;
282
+ start: async (controller) => {
283
+ // `start` is invoked before `ReadableStream`'s constructor finish,
284
+ // so using `this` synchronously causes
285
+ // "Must call super constructor in derived class before accessing 'this' or returning from derived constructor".
286
+ // Queue a microtask to avoid this.
287
+ await Promise.resolve();
288
+
289
+ this.readable = await getWrappedReadableStream(wrapper, controller);
290
+ this.reader = this.readable.getReader();
278
291
  },
279
292
  cancel: async (reason) => {
280
293
  await this.reader.cancel(reason);
281
- wrapper.close?.(this.state);
294
+ if ('cancel' in wrapper) {
295
+ await wrapper.cancel?.(reason);
296
+ }
282
297
  },
283
298
  pull: async (controller) => {
284
299
  const result = await this.reader.read();
285
300
  if (result.done) {
286
- wrapper.close?.(this.state);
287
301
  controller.close();
302
+ if ('close' in wrapper) {
303
+ await wrapper.close?.();
304
+ }
288
305
  } else {
289
306
  controller.enqueue(result.value);
290
307
  }
@@ -394,7 +411,7 @@ export class PushReadableStream<T> extends ReadableStream<T> {
394
411
  source({
395
412
  abortSignal: canceled.signal,
396
413
  async enqueue(chunk) {
397
- // Only when the stream in errored, `desiredSize` will be `null`.
414
+ // Only when the stream is errored, `desiredSize` will be `null`.
398
415
  // But since `null <= 0` is `true`
399
416
  // (`null <= 0` is evaluated as `!(null > 0)` => `!false` => `true`),
400
417
  // not handling it will cause a deadlock.
@@ -419,8 +436,8 @@ export class PushReadableStream<T> extends ReadableStream<T> {
419
436
  waterMarkLow?.resolve();
420
437
  },
421
438
  cancel: async (reason) => {
422
- waterMarkLow?.reject(reason);
423
439
  canceled.abort();
440
+ waterMarkLow?.reject(reason);
424
441
  },
425
442
  }, strategy);
426
443
  }
@@ -1,3 +1,3 @@
1
+ export { decodeUtf8, encodeUtf8 } from '@yume-chan/struct';
1
2
  export * from './auto-reset-event.js';
2
3
  export * from './base64.js';
3
- export * from './encoding.js';