@yume-chan/stream-extra 0.0.17

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 (92) hide show
  1. package/CHANGELOG.json +11 -0
  2. package/CHANGELOG.md +9 -0
  3. package/LICENSE +21 -0
  4. package/README.md +11 -0
  5. package/esm/buffered-transform.d.ts +11 -0
  6. package/esm/buffered-transform.d.ts.map +1 -0
  7. package/esm/buffered-transform.js +50 -0
  8. package/esm/buffered-transform.js.map +1 -0
  9. package/esm/buffered.d.ts +28 -0
  10. package/esm/buffered.d.ts.map +1 -0
  11. package/esm/buffered.js +131 -0
  12. package/esm/buffered.js.map +1 -0
  13. package/esm/chunk.d.ts +5 -0
  14. package/esm/chunk.d.ts.map +1 -0
  15. package/esm/chunk.js +15 -0
  16. package/esm/chunk.js.map +1 -0
  17. package/esm/decode-utf8.d.ts +5 -0
  18. package/esm/decode-utf8.d.ts.map +1 -0
  19. package/esm/decode-utf8.js +12 -0
  20. package/esm/decode-utf8.js.map +1 -0
  21. package/esm/duplex.d.ts +48 -0
  22. package/esm/duplex.d.ts.map +1 -0
  23. package/esm/duplex.js +89 -0
  24. package/esm/duplex.js.map +1 -0
  25. package/esm/gather-string.d.ts +7 -0
  26. package/esm/gather-string.d.ts.map +1 -0
  27. package/esm/gather-string.js +14 -0
  28. package/esm/gather-string.js.map +1 -0
  29. package/esm/index.d.ts +16 -0
  30. package/esm/index.d.ts.map +1 -0
  31. package/esm/index.js +16 -0
  32. package/esm/index.js.map +1 -0
  33. package/esm/inspect.d.ts +5 -0
  34. package/esm/inspect.d.ts.map +1 -0
  35. package/esm/inspect.js +12 -0
  36. package/esm/inspect.js.map +1 -0
  37. package/esm/native.d.ts +240 -0
  38. package/esm/native.d.ts.map +1 -0
  39. package/esm/native.js +60 -0
  40. package/esm/native.js.map +1 -0
  41. package/esm/pipe-from.d.ts +13 -0
  42. package/esm/pipe-from.d.ts.map +1 -0
  43. package/esm/pipe-from.js +27 -0
  44. package/esm/pipe-from.js.map +1 -0
  45. package/esm/push-readable.d.ts +12 -0
  46. package/esm/push-readable.d.ts.map +1 -0
  47. package/esm/push-readable.js +47 -0
  48. package/esm/push-readable.js.map +1 -0
  49. package/esm/split-string.d.ts +5 -0
  50. package/esm/split-string.d.ts.map +1 -0
  51. package/esm/split-string.js +25 -0
  52. package/esm/split-string.js.map +1 -0
  53. package/esm/stream.d.ts +18 -0
  54. package/esm/stream.d.ts.map +1 -0
  55. package/esm/stream.js +4 -0
  56. package/esm/stream.js.map +1 -0
  57. package/esm/struct-deserialize.d.ts +7 -0
  58. package/esm/struct-deserialize.d.ts.map +1 -0
  59. package/esm/struct-deserialize.js +9 -0
  60. package/esm/struct-deserialize.js.map +1 -0
  61. package/esm/struct-serialize.d.ts +6 -0
  62. package/esm/struct-serialize.d.ts.map +1 -0
  63. package/esm/struct-serialize.js +11 -0
  64. package/esm/struct-serialize.js.map +1 -0
  65. package/esm/wrap-readable.d.ts +21 -0
  66. package/esm/wrap-readable.d.ts.map +1 -0
  67. package/esm/wrap-readable.js +57 -0
  68. package/esm/wrap-readable.js.map +1 -0
  69. package/esm/wrap-writable.d.ts +13 -0
  70. package/esm/wrap-writable.d.ts.map +1 -0
  71. package/esm/wrap-writable.js +53 -0
  72. package/esm/wrap-writable.js.map +1 -0
  73. package/package.json +46 -0
  74. package/src/buffered-transform.ts +58 -0
  75. package/src/buffered.ts +148 -0
  76. package/src/chunk.ts +15 -0
  77. package/src/decode-utf8.ts +12 -0
  78. package/src/duplex.ts +120 -0
  79. package/src/gather-string.ts +15 -0
  80. package/src/index.ts +15 -0
  81. package/src/inspect.ts +12 -0
  82. package/src/native.ts +362 -0
  83. package/src/pipe-from.ts +27 -0
  84. package/src/push-readable.ts +62 -0
  85. package/src/split-string.ts +29 -0
  86. package/src/stream.ts +22 -0
  87. package/src/struct-deserialize.ts +12 -0
  88. package/src/struct-serialize.ts +13 -0
  89. package/src/wrap-readable.ts +70 -0
  90. package/src/wrap-writable.ts +65 -0
  91. package/tsconfig.build.json +3 -0
  92. package/tsconfig.build.tsbuildinfo +1 -0
@@ -0,0 +1,57 @@
1
+ import { ReadableStream } from "./stream.js";
2
+ function getWrappedReadableStream(wrapper, controller) {
3
+ if ('start' in wrapper) {
4
+ return wrapper.start(controller);
5
+ }
6
+ else if (typeof wrapper === 'function') {
7
+ return wrapper(controller);
8
+ }
9
+ else {
10
+ // Can't use `wrapper instanceof ReadableStream`
11
+ // Because we want to be compatible with any ReadableStream-like objects
12
+ return wrapper;
13
+ }
14
+ }
15
+ /**
16
+ * This class has multiple usages:
17
+ *
18
+ * 1. Get notified when the stream is cancelled or closed.
19
+ * 2. Synchronously create a `ReadableStream` by asynchronously return another `ReadableStream`.
20
+ * 3. Convert native `ReadableStream`s to polyfilled ones so they can `pipe` between.
21
+ */
22
+ export class WrapReadableStream extends ReadableStream {
23
+ readable;
24
+ reader;
25
+ constructor(wrapper) {
26
+ super({
27
+ start: async (controller) => {
28
+ // `start` is invoked before `ReadableStream`'s constructor finish,
29
+ // so using `this` synchronously causes
30
+ // "Must call super constructor in derived class before accessing 'this' or returning from derived constructor".
31
+ // Queue a microtask to avoid this.
32
+ await Promise.resolve();
33
+ this.readable = await getWrappedReadableStream(wrapper, controller);
34
+ this.reader = this.readable.getReader();
35
+ },
36
+ cancel: async (reason) => {
37
+ await this.reader.cancel(reason);
38
+ if ('cancel' in wrapper) {
39
+ await wrapper.cancel?.(reason);
40
+ }
41
+ },
42
+ pull: async (controller) => {
43
+ const result = await this.reader.read();
44
+ if (result.done) {
45
+ controller.close();
46
+ if ('close' in wrapper) {
47
+ await wrapper.close?.();
48
+ }
49
+ }
50
+ else {
51
+ controller.enqueue(result.value);
52
+ }
53
+ }
54
+ });
55
+ }
56
+ }
57
+ //# sourceMappingURL=wrap-readable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-readable.js","sourceRoot":"","sources":["../src/wrap-readable.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAgE,MAAM,aAAa,CAAC;AAU3G,SAAS,wBAAwB,CAC7B,OAAkF,EAClF,UAA8C;IAE9C,IAAI,OAAO,IAAI,OAAO,EAAE;QACpB,OAAO,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;KACpC;SAAM,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE;QACtC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;KAC9B;SAAM;QACH,gDAAgD;QAChD,wEAAwE;QACxE,OAAO,OAAO,CAAC;KAClB;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,kBAAsB,SAAQ,cAAiB;IACjD,QAAQ,CAAqB;IAE5B,MAAM,CAAkC;IAEhD,YAAmB,OAAkF;QACjG,KAAK,CAAC;YACF,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;gBACxB,mEAAmE;gBACnE,uCAAuC;gBACvC,gHAAgH;gBAChH,mCAAmC;gBACnC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;gBAExB,IAAI,CAAC,QAAQ,GAAG,MAAM,wBAAwB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;gBACpE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;YAC5C,CAAC;YACD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;gBACrB,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACjC,IAAI,QAAQ,IAAI,OAAO,EAAE;oBACrB,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;iBAClC;YACL,CAAC;YACD,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;gBACvB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACxC,IAAI,MAAM,CAAC,IAAI,EAAE;oBACb,UAAU,CAAC,KAAK,EAAE,CAAC;oBACnB,IAAI,OAAO,IAAI,OAAO,EAAE;wBACpB,MAAM,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;qBAC3B;iBACJ;qBAAM;oBACH,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;iBACpC;YACL,CAAC;SACJ,CAAC,CAAC;IACP,CAAC;CACJ"}
@@ -0,0 +1,13 @@
1
+ import type { ValueOrPromise } from "@yume-chan/struct";
2
+ import { WritableStream } from "./stream.js";
3
+ export declare type WrapWritableStreamStart<T> = () => ValueOrPromise<WritableStream<T>>;
4
+ export interface WritableStreamWrapper<T> {
5
+ start: WrapWritableStreamStart<T>;
6
+ close?(): Promise<void>;
7
+ }
8
+ export declare class WrapWritableStream<T> extends WritableStream<T> {
9
+ writable: WritableStream<T>;
10
+ private writer;
11
+ constructor(wrapper: WritableStream<T> | WrapWritableStreamStart<T> | WritableStreamWrapper<T>);
12
+ }
13
+ //# sourceMappingURL=wrap-writable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-writable.d.ts","sourceRoot":"","sources":["../src/wrap-writable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,cAAc,EAA+B,MAAM,aAAa,CAAC;AAE1E,oBAAY,uBAAuB,CAAC,CAAC,IAAI,MAAM,cAAc,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AAEjF,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACpC,KAAK,EAAE,uBAAuB,CAAC,CAAC,CAAC,CAAC;IAClC,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAgBD,qBAAa,kBAAkB,CAAC,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IACjD,QAAQ,EAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAEpC,OAAO,CAAC,MAAM,CAAkC;gBAE7B,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,uBAAuB,CAAC,CAAC,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC;CAmCxG"}
@@ -0,0 +1,53 @@
1
+ import { WritableStream } from "./stream.js";
2
+ async function getWrappedWritableStream(wrapper) {
3
+ if ('start' in wrapper) {
4
+ return await wrapper.start();
5
+ }
6
+ else if (typeof wrapper === 'function') {
7
+ return await wrapper();
8
+ }
9
+ else {
10
+ // Can't use `wrapper instanceof WritableStream`
11
+ // Because we want to be compatible with any WritableStream-like objects
12
+ return wrapper;
13
+ }
14
+ }
15
+ export class WrapWritableStream extends WritableStream {
16
+ writable;
17
+ writer;
18
+ constructor(wrapper) {
19
+ super({
20
+ start: async () => {
21
+ // `start` is invoked before `ReadableStream`'s constructor finish,
22
+ // so using `this` synchronously causes
23
+ // "Must call super constructor in derived class before accessing 'this' or returning from derived constructor".
24
+ // Queue a microtask to avoid this.
25
+ await Promise.resolve();
26
+ this.writable = await getWrappedWritableStream(wrapper);
27
+ this.writer = this.writable.getWriter();
28
+ },
29
+ write: async (chunk) => {
30
+ // Maintain back pressure
31
+ await this.writer.ready;
32
+ await this.writer.write(chunk);
33
+ },
34
+ abort: async (reason) => {
35
+ await this.writer.abort(reason);
36
+ if ('close' in wrapper) {
37
+ await wrapper.close?.();
38
+ }
39
+ },
40
+ close: async () => {
41
+ // Close the inner stream first.
42
+ // Usually the inner stream is a logical sub-stream over the outer stream,
43
+ // closing the outer stream first will make the inner stream incapable of
44
+ // sending data in its `close` handler.
45
+ await this.writer.close();
46
+ if ('close' in wrapper) {
47
+ await wrapper.close?.();
48
+ }
49
+ },
50
+ });
51
+ }
52
+ }
53
+ //# sourceMappingURL=wrap-writable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-writable.js","sourceRoot":"","sources":["../src/wrap-writable.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAA+B,MAAM,aAAa,CAAC;AAS1E,KAAK,UAAU,wBAAwB,CACnC,OAAkF;IAElF,IAAI,OAAO,IAAI,OAAO,EAAE;QACpB,OAAO,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;KAChC;SAAM,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE;QACtC,OAAO,MAAM,OAAO,EAAE,CAAC;KAC1B;SAAM;QACH,gDAAgD;QAChD,wEAAwE;QACxE,OAAO,OAAO,CAAC;KAClB;AACL,CAAC;AAED,MAAM,OAAO,kBAAsB,SAAQ,cAAiB;IACjD,QAAQ,CAAqB;IAE5B,MAAM,CAAkC;IAEhD,YAAmB,OAAkF;QACjG,KAAK,CAAC;YACF,KAAK,EAAE,KAAK,IAAI,EAAE;gBACd,mEAAmE;gBACnE,uCAAuC;gBACvC,gHAAgH;gBAChH,mCAAmC;gBACnC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;gBAExB,IAAI,CAAC,QAAQ,GAAG,MAAM,wBAAwB,CAAC,OAAO,CAAC,CAAC;gBACxD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;YAC5C,CAAC;YACD,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;gBACnB,yBAAyB;gBACzB,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;gBACxB,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACnC,CAAC;YACD,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;gBACpB,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBAChC,IAAI,OAAO,IAAI,OAAO,EAAE;oBACpB,MAAM,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;iBAC3B;YACL,CAAC;YACD,KAAK,EAAE,KAAK,IAAI,EAAE;gBACd,gCAAgC;gBAChC,0EAA0E;gBAC1E,yEAAyE;gBACzE,uCAAuC;gBACvC,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBAC1B,IAAI,OAAO,IAAI,OAAO,EAAE;oBACpB,MAAM,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;iBAC3B;YACL,CAAC;SACJ,CAAC,CAAC;IACP,CAAC;CACJ"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@yume-chan/stream-extra",
3
+ "version": "0.0.17",
4
+ "description": "Extensions to Web Streams API",
5
+ "keywords": [
6
+ "stream",
7
+ "web-streams-api"
8
+ ],
9
+ "license": "MIT",
10
+ "author": {
11
+ "name": "Simon Chan",
12
+ "email": "cnsimonchan@live.com",
13
+ "url": "https://chensi.moe/blog"
14
+ },
15
+ "homepage": "https://github.com/yume-chan/ya-webadb/tree/main/packages/stream-extra#readme",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/yume-chan/ya-webadb.git",
19
+ "directory": "packages/stream-extra"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/yume-chan/ya-webadb/issues"
23
+ },
24
+ "type": "module",
25
+ "main": "esm/index.js",
26
+ "types": "esm/index.d.ts",
27
+ "dependencies": {
28
+ "@yume-chan/async": "^2.2.0",
29
+ "@yume-chan/struct": "^0.0.17",
30
+ "tslib": "^2.4.0",
31
+ "web-streams-polyfill": "^4.0.0-beta.3"
32
+ },
33
+ "devDependencies": {
34
+ "@jest/globals": "^28.1.2",
35
+ "@yume-chan/ts-package-builder": "^1.0.0",
36
+ "cross-env": "^7.0.3",
37
+ "jest": "^28.1.2",
38
+ "ts-jest": "^28.0.5",
39
+ "typescript": "^4.7.4"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -b tsconfig.build.json",
43
+ "build:watch": "tsc -b tsconfig.build.json",
44
+ "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage"
45
+ }
46
+ }
@@ -0,0 +1,58 @@
1
+ import type { ValueOrPromise } from '@yume-chan/struct';
2
+ import { BufferedReadableStream, BufferedReadableStreamEndedError } from './buffered.js';
3
+ import { PushReadableStream, PushReadableStreamController } from './push-readable.js';
4
+ import { ReadableStream, ReadableWritablePair, WritableStream } from './stream.js';
5
+
6
+ // TODO: BufferedTransformStream: find better implementation
7
+ export class BufferedTransformStream<T> implements ReadableWritablePair<T, Uint8Array> {
8
+ private _readable: ReadableStream<T>;
9
+ public get readable() { return this._readable; }
10
+
11
+ private _writable: WritableStream<Uint8Array>;
12
+ public get writable() { return this._writable; }
13
+
14
+ constructor(transform: (stream: BufferedReadableStream) => ValueOrPromise<T>) {
15
+ // Convert incoming chunks to a `BufferedReadableStream`
16
+ let sourceStreamController!: PushReadableStreamController<Uint8Array>;
17
+
18
+ const buffered = new BufferedReadableStream(new PushReadableStream<Uint8Array>(
19
+ controller =>
20
+ sourceStreamController = controller,
21
+ ));
22
+
23
+ this._readable = new ReadableStream<T>({
24
+ async pull(controller) {
25
+ try {
26
+ const value = await transform(buffered);
27
+ controller.enqueue(value);
28
+ } catch (e) {
29
+ // TODO: BufferedTransformStream: The semantic of stream ending is not clear
30
+ // If the `transform` started but did not finish, it should really be an error?
31
+ // But we can't detect that, unless there is a `peek` method on buffered stream.
32
+ if (e instanceof BufferedReadableStreamEndedError) {
33
+ controller.close();
34
+ return;
35
+ }
36
+ throw e;
37
+ }
38
+ },
39
+ cancel: (reason) => {
40
+ // Propagate cancel to the source stream
41
+ // So future writes will be rejected
42
+ buffered.cancel(reason);
43
+ }
44
+ });
45
+
46
+ this._writable = new WritableStream({
47
+ async write(chunk) {
48
+ await sourceStreamController.enqueue(chunk);
49
+ },
50
+ abort() {
51
+ sourceStreamController.close();
52
+ },
53
+ close() {
54
+ sourceStreamController.close();
55
+ },
56
+ });
57
+ }
58
+ }
@@ -0,0 +1,148 @@
1
+ import { PushReadableStream } from "./push-readable.js";
2
+ import type { ReadableStream, ReadableStreamDefaultReader } from "./stream.js";
3
+
4
+ export class BufferedReadableStreamEndedError extends Error {
5
+ public constructor() {
6
+ super('Stream ended');
7
+
8
+ // Fix Error's prototype chain when compiling to ES5
9
+ Object.setPrototypeOf(this, new.target.prototype);
10
+ }
11
+ }
12
+
13
+ export class BufferedReadableStream {
14
+ private buffered: Uint8Array | undefined;
15
+ private bufferedOffset = 0;
16
+ private bufferedLength = 0;
17
+
18
+ protected readonly stream: ReadableStream<Uint8Array>;
19
+
20
+ protected readonly reader: ReadableStreamDefaultReader<Uint8Array>;
21
+
22
+ public constructor(stream: ReadableStream<Uint8Array>) {
23
+ this.stream = stream;
24
+ this.reader = stream.getReader();
25
+ }
26
+
27
+ private async readSource() {
28
+ const { done, value } = await this.reader.read();
29
+ if (done) {
30
+ throw new BufferedReadableStreamEndedError();
31
+ }
32
+ return value;
33
+ }
34
+
35
+ private async readAsync(length: number, initial?: Uint8Array) {
36
+ let result: Uint8Array;
37
+ let index: number;
38
+
39
+ if (initial) {
40
+ result = new Uint8Array(length);
41
+ result.set(initial);
42
+ index = initial.byteLength;
43
+ length -= initial.byteLength;
44
+ } else {
45
+ const array = await this.readSource();
46
+ if (array.byteLength === length) {
47
+ return array;
48
+ }
49
+
50
+ if (array.byteLength > length) {
51
+ this.buffered = array;
52
+ this.bufferedOffset = length;
53
+ this.bufferedLength = array.byteLength - length;
54
+ return array.subarray(0, length);
55
+ }
56
+
57
+ result = new Uint8Array(length);
58
+ result.set(array);
59
+ index = array.byteLength;
60
+ length -= array.byteLength;
61
+ }
62
+
63
+ while (length > 0) {
64
+ const array = await this.readSource();
65
+ if (array.byteLength === length) {
66
+ result.set(array, index);
67
+ return result;
68
+ }
69
+
70
+ if (array.byteLength > length) {
71
+ this.buffered = array;
72
+ this.bufferedOffset = length;
73
+ this.bufferedLength = array.byteLength - length;
74
+ result.set(array.subarray(0, length), index);
75
+ return result;
76
+ }
77
+
78
+ result.set(array, index);
79
+ index += array.byteLength;
80
+ length -= array.byteLength;
81
+ }
82
+
83
+ return result;
84
+ }
85
+
86
+ /**
87
+ *
88
+ * @param length
89
+ * @returns
90
+ */
91
+ public read(length: number): Uint8Array | Promise<Uint8Array> {
92
+ // PERF: Add a synchronous path for reading from internal buffer
93
+ if (this.buffered) {
94
+ const array = this.buffered;
95
+ const offset = this.bufferedOffset;
96
+ if (this.bufferedLength > length) {
97
+ // PERF: `subarray` is slow
98
+ // don't use it until absolutely necessary
99
+ this.bufferedOffset += length;
100
+ this.bufferedLength -= length;
101
+ return array.subarray(offset, offset + length);
102
+ }
103
+
104
+ this.buffered = undefined;
105
+ return this.readAsync(length, array.subarray(offset));
106
+ }
107
+
108
+ return this.readAsync(length);
109
+ }
110
+
111
+ /**
112
+ * Return a readable stream with unconsumed data (if any) and
113
+ * all data from the wrapped stream.
114
+ * @returns A `ReadableStream`
115
+ */
116
+ public release(): ReadableStream<Uint8Array> {
117
+ if (this.buffered) {
118
+ return new PushReadableStream<Uint8Array>(async controller => {
119
+ // Put the remaining data back to the stream
120
+ await controller.enqueue(this.buffered!);
121
+
122
+ // Manually pipe the stream
123
+ while (true) {
124
+ try {
125
+ const { done, value } = await this.reader.read();
126
+ if (done) {
127
+ controller.close();
128
+ break;
129
+ } else {
130
+ await controller.enqueue(value);
131
+ }
132
+ } catch (e) {
133
+ controller.error(e);
134
+ break;
135
+ }
136
+ }
137
+ });
138
+ } else {
139
+ // Simply release the reader and return the stream
140
+ this.reader.releaseLock();
141
+ return this.stream;
142
+ }
143
+ }
144
+
145
+ public cancel(reason?: any) {
146
+ return this.reader.cancel(reason);
147
+ }
148
+ }
package/src/chunk.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { TransformStream } from "./stream.js";
2
+
3
+ export class ChunkStream extends TransformStream<Uint8Array, Uint8Array>{
4
+ public constructor(size: number) {
5
+ super({
6
+ transform(chunk, controller) {
7
+ for (let start = 0; start < chunk.byteLength;) {
8
+ const end = start + size;
9
+ controller.enqueue(chunk.subarray(start, end));
10
+ start = end;
11
+ }
12
+ }
13
+ });
14
+ }
15
+ }
@@ -0,0 +1,12 @@
1
+ import { decodeUtf8 } from '@yume-chan/struct';
2
+ import { TransformStream } from "./stream.js";
3
+
4
+ export class DecodeUtf8Stream extends TransformStream<Uint8Array, string>{
5
+ public constructor() {
6
+ super({
7
+ transform(chunk, controller) {
8
+ controller.enqueue(decodeUtf8(chunk));
9
+ },
10
+ });
11
+ }
12
+ }
package/src/duplex.ts ADDED
@@ -0,0 +1,120 @@
1
+ import { PromiseResolver } from "@yume-chan/async";
2
+ import type { ValueOrPromise } from "@yume-chan/struct";
3
+ import { WritableStream, type ReadableStream, type ReadableStreamDefaultController, type WritableStreamDefaultWriter } from "./stream.js";
4
+ import { WrapReadableStream } from "./wrap-readable.js";
5
+
6
+ export interface DuplexStreamFactoryOptions {
7
+ /**
8
+ * Callback when any `ReadableStream` is cancelled (the user doesn't need any more data),
9
+ * or `WritableStream` is ended (the user won't produce any more data),
10
+ * or `DuplexStreamFactory#close` is called.
11
+ *
12
+ * Usually you want to let the other peer know that the duplex stream should be clsoed.
13
+ *
14
+ * `dispose` will automatically be called after `close` completes,
15
+ * but if you want to wait another peer for a close confirmation and call
16
+ * `DuplexStreamFactory#dispose` yourself, you can return `false`
17
+ * (or a `Promise` that resolves to `false`) to disable the automatic call.
18
+ */
19
+ close?: (() => ValueOrPromise<boolean | void>) | undefined;
20
+
21
+ /**
22
+ * Callback when any `ReadableStream` is closed (the other peer doesn't produce any more data),
23
+ * or `WritableStream` is aborted (the other peer can't receive any more data),
24
+ * or `DuplexStreamFactory#abort` is called.
25
+ *
26
+ * Usually indicates the other peer has closed the duplex stream. You can clean up
27
+ * any resources you have allocated now.
28
+ */
29
+ dispose?: (() => void | Promise<void>) | undefined;
30
+ }
31
+
32
+ /**
33
+ * A factory for creating a duplex stream.
34
+ *
35
+ * It can create multiple `ReadableStream`s and `WritableStream`s,
36
+ * when any of them is closed, all other streams will be closed as well.
37
+ */
38
+ export class DuplexStreamFactory<R, W> {
39
+ private readableControllers: ReadableStreamDefaultController<R>[] = [];
40
+ private writers: WritableStreamDefaultWriter<W>[] = [];
41
+
42
+ private _writableClosed = false;
43
+ public get writableClosed() { return this._writableClosed; }
44
+
45
+ private _closed = new PromiseResolver<void>();
46
+ public get closed() { return this._closed.promise; }
47
+
48
+ private options: DuplexStreamFactoryOptions;
49
+
50
+ public constructor(options?: DuplexStreamFactoryOptions) {
51
+ this.options = options ?? {};
52
+ }
53
+
54
+ public wrapReadable(readable: ReadableStream<R>): WrapReadableStream<R> {
55
+ return new WrapReadableStream<R>({
56
+ start: (controller) => {
57
+ this.readableControllers.push(controller);
58
+ return readable;
59
+ },
60
+ cancel: async () => {
61
+ // cancel means the local peer closes the connection first.
62
+ await this.close();
63
+ },
64
+ close: async () => {
65
+ // stream end means the remote peer closed the connection first.
66
+ await this.dispose();
67
+ },
68
+ });
69
+ }
70
+
71
+ public createWritable(stream: WritableStream<W>): WritableStream<W> {
72
+ const writer = stream.getWriter();
73
+ this.writers.push(writer);
74
+
75
+ // `WritableStream` has no way to tell if the remote peer has closed the connection.
76
+ // So it only triggers `close`.
77
+ return new WritableStream<W>({
78
+ write: async (chunk) => {
79
+ await writer.ready;
80
+ await writer.write(chunk);
81
+ },
82
+ abort: async (reason) => {
83
+ await writer.abort(reason);
84
+ await this.close();
85
+ },
86
+ close: async () => {
87
+ try { await writer.close(); } catch { }
88
+ await this.close();
89
+ },
90
+ });
91
+ }
92
+
93
+ public async close() {
94
+ if (this._writableClosed) {
95
+ return;
96
+ }
97
+ this._writableClosed = true;
98
+
99
+ // Call `close` first, so it can still write data to `WritableStream`s.
100
+ if (await this.options.close?.() !== false) {
101
+ // `close` can return `false` to disable automatic `dispose`.
102
+ await this.dispose();
103
+ }
104
+
105
+ for (const writer of this.writers) {
106
+ try { await writer.close(); } catch { }
107
+ }
108
+ }
109
+
110
+ public async dispose() {
111
+ this._writableClosed = true;
112
+ this._closed.resolve();
113
+
114
+ for (const controller of this.readableControllers) {
115
+ try { controller.close(); } catch { }
116
+ }
117
+
118
+ await this.options.dispose?.();
119
+ }
120
+ }
@@ -0,0 +1,15 @@
1
+ import { WritableStream } from "./stream.js";
2
+
3
+ export class GatherStringStream extends WritableStream<string>{
4
+ // PERF: rope (concat strings) is faster than `[].join('')`
5
+ private _result = '';
6
+ public get result() { return this._result; }
7
+
8
+ public constructor() {
9
+ super({
10
+ write: (chunk) => {
11
+ this._result += chunk;
12
+ },
13
+ });
14
+ }
15
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export * from './buffered-transform.js';
2
+ export * from './buffered.js';
3
+ export * from './chunk.js';
4
+ export * from './decode-utf8.js';
5
+ export * from './duplex.js';
6
+ export * from './gather-string.js';
7
+ export * from './inspect.js';
8
+ export * from './pipe-from.js';
9
+ export * from './push-readable.js';
10
+ export * from './split-string.js';
11
+ export * from './stream.js';
12
+ export * from './struct-deserialize.js';
13
+ export * from './struct-serialize.js';
14
+ export * from './wrap-readable.js';
15
+ export * from './wrap-writable.js';
package/src/inspect.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { TransformStream } from "./stream.js";
2
+
3
+ export class InspectStream<T> extends TransformStream<T, T> {
4
+ constructor(callback: (value: T) => void) {
5
+ super({
6
+ transform(chunk, controller) {
7
+ callback(chunk);
8
+ controller.enqueue(chunk);
9
+ }
10
+ });
11
+ }
12
+ }