@atproto/common 0.7.5 → 0.8.0
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.
- package/CHANGELOG.md +28 -0
- package/dist/logger.d.ts +15 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +17 -7
- package/dist/logger.js.map +1 -1
- package/dist/streams.d.ts +74 -2
- package/dist/streams.d.ts.map +1 -1
- package/dist/streams.js +211 -2
- package/dist/streams.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @atproto/common
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#5382](https://github.com/bluesky-social/atproto/pull/5382) [`898c469`](https://github.com/bluesky-social/atproto/commit/898c469cf568b941f38f9c3371997a513ac05cfa) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `fanOut`, a pipeline-destination counterpart to `Tee` that owns its output sinks: an output that errors or ends early is dropped while the others (and the input) keep flowing, and the input is only torn down once every sink has died. Also harden `Tee` so a branch that dies mid-write (e.g. one created with `autoDestroy: false`, which errors without emitting `close`) can no longer stall the main stream.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#5382](https://github.com/bluesky-social/atproto/pull/5382) [`898c469`](https://github.com/bluesky-social/atproto/commit/898c469cf568b941f38f9c3371997a513ac05cfa) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Deprecate `forwardStreamErrors`
|
|
12
|
+
|
|
13
|
+
- [#5382](https://github.com/bluesky-social/atproto/pull/5382) [`898c469`](https://github.com/bluesky-social/atproto/commit/898c469cf568b941f38f9c3371997a513ac05cfa) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Deprecate `cloneStream`
|
|
14
|
+
|
|
15
|
+
- [#5382](https://github.com/bluesky-social/atproto/pull/5382) [`898c469`](https://github.com/bluesky-social/atproto/commit/898c469cf568b941f38f9c3371997a513ac05cfa) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Make `createError` argument of `MaxSizeChecker` optional
|
|
16
|
+
|
|
17
|
+
- [#5382](https://github.com/bluesky-social/atproto/pull/5382) [`898c469`](https://github.com/bluesky-social/atproto/commit/898c469cf568b941f38f9c3371997a513ac05cfa) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Migrate test runner to vitest
|
|
18
|
+
|
|
19
|
+
- [#5382](https://github.com/bluesky-social/atproto/pull/5382) [`898c469`](https://github.com/bluesky-social/atproto/commit/898c469cf568b941f38f9c3371997a513ac05cfa) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `Tee` stream utility
|
|
20
|
+
|
|
21
|
+
- [#5382](https://github.com/bluesky-social/atproto/pull/5382) [`898c469`](https://github.com/bluesky-social/atproto/commit/898c469cf568b941f38f9c3371997a513ac05cfa) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `HashPassThrough` utility
|
|
22
|
+
|
|
23
|
+
## 0.7.6
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- [#4805](https://github.com/bluesky-social/atproto/pull/4805) [`b66870d`](https://github.com/bluesky-social/atproto/commit/b66870d5a1efbcfa0cc07498c7cfcf3f0f243e86) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Export new `createLogger` helper
|
|
28
|
+
|
|
29
|
+
- [#4805](https://github.com/bluesky-social/atproto/pull/4805) [`b66870d`](https://github.com/bluesky-social/atproto/commit/b66870d5a1efbcfa0cc07498c7cfcf3f0f243e86) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Deprecate `subsystemLogger` in favor of `createLogger`
|
|
30
|
+
|
|
3
31
|
## 0.7.5
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
import { type Logger } from 'pino';
|
|
2
|
-
export declare
|
|
1
|
+
import { type Logger, type SerializerFn } from 'pino';
|
|
2
|
+
export declare function createLogger(name: string, options?: {
|
|
3
|
+
serializers?: {
|
|
4
|
+
[key: string]: SerializerFn;
|
|
5
|
+
};
|
|
6
|
+
msgPrefix?: string;
|
|
7
|
+
}): Logger;
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated The use of singleton loggers is deprecated as they don't allow
|
|
10
|
+
* for proper configuration of serializers, and are not really used across the
|
|
11
|
+
* "atproto" mono repo. Indeed, whenever used, loggers are created and
|
|
12
|
+
* re-exported from a central place, achieving the same effect as a singleton
|
|
13
|
+
* logger, but with more flexibility. Use {@link createLogger} instead.
|
|
14
|
+
*/
|
|
15
|
+
export declare function subsystemLogger(name: string): Logger;
|
|
3
16
|
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAqB,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAqB,MAAM,MAAM,CAAA;AAcxE,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,WAAW,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;KAAE,CAAA;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GACA,MAAM,CAOR;AAID;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpD"}
|
package/dist/logger.js
CHANGED
|
@@ -5,17 +5,27 @@ const level = process.env.LOG_LEVEL || 'info';
|
|
|
5
5
|
const systems = process.env.LOG_SYSTEMS?.trim()
|
|
6
6
|
? process.env.LOG_SYSTEMS.replace(',', ' ').split(/\s+/).filter(Boolean)
|
|
7
7
|
: null;
|
|
8
|
-
const rootLogger = pino({ enabled, level }, dest ? destination(dest) : undefined);
|
|
9
|
-
|
|
10
|
-
export const subsystemLogger = (name) => {
|
|
11
|
-
if (subsystems.has(name))
|
|
12
|
-
return subsystems.get(name);
|
|
8
|
+
const rootLogger = /*#__PURE__*/ pino({ enabled, level }, dest ? destination(dest) : undefined);
|
|
9
|
+
export function createLogger(name, options) {
|
|
13
10
|
// can't disable child loggers, so we just set their level to "silent"
|
|
14
11
|
// to effectively turn them off
|
|
15
12
|
const subsystemEnabled = !systems || systems.includes(name);
|
|
16
13
|
const subsystemLevel = enabled && subsystemEnabled ? level : 'silent';
|
|
17
|
-
|
|
14
|
+
return rootLogger.child({ name }, { ...options, level: subsystemLevel });
|
|
15
|
+
}
|
|
16
|
+
const subsystems = new Map();
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated The use of singleton loggers is deprecated as they don't allow
|
|
19
|
+
* for proper configuration of serializers, and are not really used across the
|
|
20
|
+
* "atproto" mono repo. Indeed, whenever used, loggers are created and
|
|
21
|
+
* re-exported from a central place, achieving the same effect as a singleton
|
|
22
|
+
* logger, but with more flexibility. Use {@link createLogger} instead.
|
|
23
|
+
*/
|
|
24
|
+
export function subsystemLogger(name) {
|
|
25
|
+
if (subsystems.has(name))
|
|
26
|
+
return subsystems.get(name);
|
|
27
|
+
const logger = createLogger(name);
|
|
18
28
|
subsystems.set(name, logger);
|
|
19
29
|
return logger;
|
|
20
|
-
}
|
|
30
|
+
}
|
|
21
31
|
//# sourceMappingURL=logger.js.map
|
package/dist/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,WAAW,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAExE,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,CAAA;AACpE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;AACxC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM,CAAA;AAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE;IAC7C,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IACxE,CAAC,CAAC,IAAI,CAAA;AAER,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CACnC,EAAE,OAAO,EAAE,KAAK,EAAE,EAClB,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CACrC,CAAA;AAED,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,OAGC;IAED,sEAAsE;IACtE,+BAA+B;IAC/B,MAAM,gBAAgB,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC3D,MAAM,cAAc,GAAG,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAA;IAErE,OAAO,UAAU,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAA;AAE5C;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,CAAE,CAAA;IACtD,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IACjC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC5B,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import { type Logger, type SerializerFn, destination, pino } from 'pino'\n\nconst enabled = /^(true|t|1)$/i.test(process.env.LOG_ENABLED ?? '0')\nconst dest = process.env.LOG_DESTINATION\nconst level = process.env.LOG_LEVEL || 'info'\nconst systems = process.env.LOG_SYSTEMS?.trim()\n ? process.env.LOG_SYSTEMS.replace(',', ' ').split(/\\s+/).filter(Boolean)\n : null\n\nconst rootLogger = /*#__PURE__*/ pino(\n { enabled, level },\n dest ? destination(dest) : undefined,\n)\n\nexport function createLogger(\n name: string,\n options?: {\n serializers?: { [key: string]: SerializerFn }\n msgPrefix?: string\n },\n): Logger {\n // can't disable child loggers, so we just set their level to \"silent\"\n // to effectively turn them off\n const subsystemEnabled = !systems || systems.includes(name)\n const subsystemLevel = enabled && subsystemEnabled ? level : 'silent'\n\n return rootLogger.child({ name }, { ...options, level: subsystemLevel })\n}\n\nconst subsystems = new Map<string, Logger>()\n\n/**\n * @deprecated The use of singleton loggers is deprecated as they don't allow\n * for proper configuration of serializers, and are not really used across the\n * \"atproto\" mono repo. Indeed, whenever used, loggers are created and\n * re-exported from a central place, achieving the same effect as a singleton\n * logger, but with more flexibility. Use {@link createLogger} instead.\n */\nexport function subsystemLogger(name: string): Logger {\n if (subsystems.has(name)) return subsystems.get(name)!\n const logger = createLogger(name)\n subsystems.set(name, logger)\n return logger\n}\n"]}
|
package/dist/streams.d.ts
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type HashOptions } from 'node:crypto';
|
|
2
|
+
import { type Duplex, Readable, type Stream, Transform, type TransformCallback, Writable } from 'node:stream';
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated use native stream piping instead.
|
|
5
|
+
*/
|
|
2
6
|
export declare const forwardStreamErrors: (...streams: Stream[]) => void;
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated This function has several flaws:
|
|
9
|
+
* - It only works if all the stream are setup during the same tick
|
|
10
|
+
* - It does not apply any backpressure to the source stream, which can lead to
|
|
11
|
+
* memory issues
|
|
12
|
+
* Use {@link Tee} instead, which is a more robust implementation of the same
|
|
13
|
+
* concept.
|
|
14
|
+
*/
|
|
3
15
|
export declare const cloneStream: (stream: Readable) => Readable;
|
|
16
|
+
/**
|
|
17
|
+
* A {@link Transform} that forwards every chunk downstream while mirroring a
|
|
18
|
+
* copy into a "branch" {@link Writable} (exposed through {@link Tee.branch}, e.g.
|
|
19
|
+
* to be cached). The tee is paced by the slower of its two consumers, bounding
|
|
20
|
+
* how much data gets buffered. The branch is completed when the source ends,
|
|
21
|
+
* and torn down if the tee errors or is destroyed early (e.g. the client
|
|
22
|
+
* disconnected).
|
|
23
|
+
*
|
|
24
|
+
* Consuming the branch is best-effort: its failures are swallowed here and must
|
|
25
|
+
* never break the main stream.
|
|
26
|
+
*
|
|
27
|
+
* Because the tee is a {@link Transform}, its *main* output is a downstream
|
|
28
|
+
* pipeline stage — so `pipeline([readable, tee, main])` tears the tee (and the
|
|
29
|
+
* source) down as soon as `main` fails. If you need the branch to keep going
|
|
30
|
+
* after the main consumer disappears (or vice-versa), use {@link fanOut}, which
|
|
31
|
+
* owns both outputs instead of exposing one as a pipeline stage.
|
|
32
|
+
*/
|
|
33
|
+
export declare class Tee extends Transform {
|
|
34
|
+
#private;
|
|
35
|
+
readonly branch: Writable;
|
|
36
|
+
constructor(branch: Writable | ((readable: Readable) => void));
|
|
37
|
+
get branchWritable(): boolean;
|
|
38
|
+
_transform(chunk: unknown, _enc: BufferEncoding, cb: TransformCallback): void;
|
|
39
|
+
_flush(cb: TransformCallback): void;
|
|
40
|
+
_destroy(err: Error | null, cb: (err?: Error | null) => void): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A {@link Writable} that mirrors everything written to it into several
|
|
44
|
+
* independent "sinks", pacing the input by the slowest sink that is still
|
|
45
|
+
* alive. Unlike {@link Tee}, the sinks are *owned* by the fan-out rather than
|
|
46
|
+
* being downstream pipeline stages, which lets the two error domains be kept
|
|
47
|
+
* apart:
|
|
48
|
+
*
|
|
49
|
+
* - An *output* (sink) that errors or ends early is simply dropped; the other
|
|
50
|
+
* sinks — and the input — keep going. Its error is swallowed (best-effort),
|
|
51
|
+
* exactly like a {@link Tee} branch.
|
|
52
|
+
* - The *input* (writable side) is only torn down once *every* sink has died,
|
|
53
|
+
* or if the input itself fails — in which case the failure is propagated to
|
|
54
|
+
* every surviving sink.
|
|
55
|
+
*
|
|
56
|
+
* Designed to be the destination of a pipeline:
|
|
57
|
+
*
|
|
58
|
+
* ```
|
|
59
|
+
* pipeline([readable, ...transforms, fanOut(main, branch)])
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* If `main` disconnects (e.g. an HTTP response whose client went away), `branch`
|
|
63
|
+
* still receives the full stream (e.g. to finish populating a cache), and
|
|
64
|
+
* vice-versa. A sink may also be provided as a function receiving a
|
|
65
|
+
* {@link Readable} to consume, mirroring {@link Tee}'s constructor.
|
|
66
|
+
*/
|
|
67
|
+
export declare function fanOut(...outputs: ReadonlyArray<Writable | ((readable: Readable) => void)>): Writable;
|
|
4
68
|
export declare const streamSize: (stream: Readable) => Promise<number>;
|
|
5
69
|
export declare const streamToBytes: (stream: AsyncIterable<Uint8Array>) => Promise<Uint8Array<ArrayBuffer>>;
|
|
6
70
|
export declare const streamToNodeBuffer: (stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>) => Promise<Buffer>;
|
|
@@ -17,8 +81,16 @@ export declare class MaxSizeChecker extends Transform {
|
|
|
17
81
|
maxSize: number;
|
|
18
82
|
createError: () => Error;
|
|
19
83
|
totalSize: number;
|
|
20
|
-
constructor(maxSize: number, createError
|
|
84
|
+
constructor(maxSize: number, createError?: () => Error);
|
|
85
|
+
_transform(chunk: Uint8Array, _enc: BufferEncoding, cb: TransformCallback): void;
|
|
86
|
+
}
|
|
87
|
+
export declare class HashPassThrough extends Transform {
|
|
88
|
+
#private;
|
|
89
|
+
private readonly hash;
|
|
90
|
+
constructor(algorithm: string, options?: HashOptions);
|
|
21
91
|
_transform(chunk: Uint8Array, _enc: BufferEncoding, cb: TransformCallback): void;
|
|
92
|
+
_flush(cb: TransformCallback): void;
|
|
93
|
+
get digest(): Buffer<ArrayBuffer>;
|
|
22
94
|
}
|
|
23
95
|
export declare function decodeStream(stream: Readable, contentEncoding?: string | string[]): Readable;
|
|
24
96
|
export declare function decodeStream(stream: AsyncIterable<Uint8Array>, contentEncoding?: string | string[]): AsyncIterable<Uint8Array> | Readable;
|
package/dist/streams.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAEX,QAAQ,EACR,KAAK,MAAM,EACX,SAAS,EACT,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"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"}
|
package/dist/streams.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { PassThrough, Readable, Transform, Writable, pipeline, } from 'node:stream';
|
|
2
3
|
import { createBrotliDecompress, createGunzip, createInflate } from 'node:zlib';
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated use native stream piping instead.
|
|
6
|
+
*/
|
|
3
7
|
export const forwardStreamErrors = (...streams) => {
|
|
4
8
|
for (let i = 1; i < streams.length; ++i) {
|
|
5
9
|
const prev = streams[i - 1];
|
|
@@ -7,11 +11,194 @@ export const forwardStreamErrors = (...streams) => {
|
|
|
7
11
|
prev.once('error', (err) => next.emit('error', err));
|
|
8
12
|
}
|
|
9
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated This function has several flaws:
|
|
16
|
+
* - It only works if all the stream are setup during the same tick
|
|
17
|
+
* - It does not apply any backpressure to the source stream, which can lead to
|
|
18
|
+
* memory issues
|
|
19
|
+
* Use {@link Tee} instead, which is a more robust implementation of the same
|
|
20
|
+
* concept.
|
|
21
|
+
*/
|
|
10
22
|
export const cloneStream = (stream) => {
|
|
11
23
|
const passthrough = new PassThrough();
|
|
12
24
|
forwardStreamErrors(stream, passthrough);
|
|
13
25
|
return stream.pipe(passthrough);
|
|
14
26
|
};
|
|
27
|
+
/**
|
|
28
|
+
* A {@link Transform} that forwards every chunk downstream while mirroring a
|
|
29
|
+
* copy into a "branch" {@link Writable} (exposed through {@link Tee.branch}, e.g.
|
|
30
|
+
* to be cached). The tee is paced by the slower of its two consumers, bounding
|
|
31
|
+
* how much data gets buffered. The branch is completed when the source ends,
|
|
32
|
+
* and torn down if the tee errors or is destroyed early (e.g. the client
|
|
33
|
+
* disconnected).
|
|
34
|
+
*
|
|
35
|
+
* Consuming the branch is best-effort: its failures are swallowed here and must
|
|
36
|
+
* never break the main stream.
|
|
37
|
+
*
|
|
38
|
+
* Because the tee is a {@link Transform}, its *main* output is a downstream
|
|
39
|
+
* pipeline stage — so `pipeline([readable, tee, main])` tears the tee (and the
|
|
40
|
+
* source) down as soon as `main` fails. If you need the branch to keep going
|
|
41
|
+
* after the main consumer disappears (or vice-versa), use {@link fanOut}, which
|
|
42
|
+
* owns both outputs instead of exposing one as a pipeline stage.
|
|
43
|
+
*/
|
|
44
|
+
export class Tee extends Transform {
|
|
45
|
+
#branchFailed = false;
|
|
46
|
+
constructor(branch) {
|
|
47
|
+
super();
|
|
48
|
+
if (typeof branch === 'function') {
|
|
49
|
+
const passthrough = new PassThrough({ autoDestroy: true });
|
|
50
|
+
branch(passthrough);
|
|
51
|
+
this.branch = passthrough;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
this.branch = branch;
|
|
55
|
+
}
|
|
56
|
+
// The branch is best-effort: its failures must never break or stall the
|
|
57
|
+
// main stream.
|
|
58
|
+
this.branch.on('error', () => {
|
|
59
|
+
this.#branchFailed = true;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
get branchWritable() {
|
|
63
|
+
return !this.#branchFailed && this.branch.writable;
|
|
64
|
+
}
|
|
65
|
+
_transform(chunk, _enc, cb) {
|
|
66
|
+
// Forward downstream, applying backpressure if either branch is slower.
|
|
67
|
+
if (!this.branchWritable || this.branch.write(chunk)) {
|
|
68
|
+
cb(null, chunk);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const done = () => {
|
|
72
|
+
this.branch.off('drain', done);
|
|
73
|
+
this.branch.off('close', done);
|
|
74
|
+
this.branch.off('error', done);
|
|
75
|
+
cb(null, chunk);
|
|
76
|
+
};
|
|
77
|
+
// Resume on 'drain' (branch caught up) but also on 'close'/'error' so the
|
|
78
|
+
// tee is never stuck if the branch dies mid-write — e.g. a branch created
|
|
79
|
+
// with `autoDestroy: false`, which errors without emitting 'close'.
|
|
80
|
+
this.branch.once('drain', done);
|
|
81
|
+
this.branch.once('close', done);
|
|
82
|
+
this.branch.once('error', done);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
_flush(cb) {
|
|
86
|
+
if (this.branchWritable)
|
|
87
|
+
this.branch.end();
|
|
88
|
+
cb();
|
|
89
|
+
}
|
|
90
|
+
_destroy(err, cb) {
|
|
91
|
+
// A failed branch has already torn itself down (or given up); leave it be
|
|
92
|
+
// rather than emitting a second, spurious error on it.
|
|
93
|
+
if (this.branchWritable) {
|
|
94
|
+
this.branch.destroy(err ?? new Error('Tee destroyed'));
|
|
95
|
+
}
|
|
96
|
+
cb(err);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A {@link Writable} that mirrors everything written to it into several
|
|
101
|
+
* independent "sinks", pacing the input by the slowest sink that is still
|
|
102
|
+
* alive. Unlike {@link Tee}, the sinks are *owned* by the fan-out rather than
|
|
103
|
+
* being downstream pipeline stages, which lets the two error domains be kept
|
|
104
|
+
* apart:
|
|
105
|
+
*
|
|
106
|
+
* - An *output* (sink) that errors or ends early is simply dropped; the other
|
|
107
|
+
* sinks — and the input — keep going. Its error is swallowed (best-effort),
|
|
108
|
+
* exactly like a {@link Tee} branch.
|
|
109
|
+
* - The *input* (writable side) is only torn down once *every* sink has died,
|
|
110
|
+
* or if the input itself fails — in which case the failure is propagated to
|
|
111
|
+
* every surviving sink.
|
|
112
|
+
*
|
|
113
|
+
* Designed to be the destination of a pipeline:
|
|
114
|
+
*
|
|
115
|
+
* ```
|
|
116
|
+
* pipeline([readable, ...transforms, fanOut(main, branch)])
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* If `main` disconnects (e.g. an HTTP response whose client went away), `branch`
|
|
120
|
+
* still receives the full stream (e.g. to finish populating a cache), and
|
|
121
|
+
* vice-versa. A sink may also be provided as a function receiving a
|
|
122
|
+
* {@link Readable} to consume, mirroring {@link Tee}'s constructor.
|
|
123
|
+
*/
|
|
124
|
+
export function fanOut(...outputs) {
|
|
125
|
+
const sinks = outputs.map((output) => {
|
|
126
|
+
if (typeof output !== 'function')
|
|
127
|
+
return output;
|
|
128
|
+
const passthrough = new PassThrough();
|
|
129
|
+
output(passthrough);
|
|
130
|
+
return passthrough;
|
|
131
|
+
});
|
|
132
|
+
// Sinks still able to receive data. A sink that errors or closes is dropped
|
|
133
|
+
// and never written to again; its 'error' is swallowed here so a dead sink can
|
|
134
|
+
// neither crash the process nor disturb the surviving sinks (other listeners,
|
|
135
|
+
// e.g. a reader consuming it, still observe the error).
|
|
136
|
+
const alive = new Set(sinks);
|
|
137
|
+
const drop = (sink) => alive.delete(sink);
|
|
138
|
+
for (const sink of sinks) {
|
|
139
|
+
sink.on('error', () => drop(sink));
|
|
140
|
+
sink.on('close', () => drop(sink));
|
|
141
|
+
}
|
|
142
|
+
// Whether the input ended cleanly, so we can distinguish a normal completion
|
|
143
|
+
// (let the sinks finish the data we handed them) from a teardown (destroy
|
|
144
|
+
// them).
|
|
145
|
+
let ended = false;
|
|
146
|
+
return new Writable({
|
|
147
|
+
write(chunk, _enc, cb) {
|
|
148
|
+
// Write to every live sink, then invoke `cb` once they have all either
|
|
149
|
+
// accepted the chunk, drained, or died — pacing the input by the slowest
|
|
150
|
+
// surviving sink.
|
|
151
|
+
let waiting = 1; // guard so `cb` can't fire before the loop completes
|
|
152
|
+
const settle = () => {
|
|
153
|
+
if (--waiting > 0)
|
|
154
|
+
return;
|
|
155
|
+
// If every sink died while handling this chunk, tear the input down.
|
|
156
|
+
cb(alive.size === 0 ? new Error('fanOut: all sinks ended') : null);
|
|
157
|
+
};
|
|
158
|
+
for (const sink of alive) {
|
|
159
|
+
if (!sink.writable) {
|
|
160
|
+
drop(sink);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (sink.write(chunk))
|
|
164
|
+
continue;
|
|
165
|
+
waiting++;
|
|
166
|
+
const done = () => {
|
|
167
|
+
sink.off('drain', done);
|
|
168
|
+
sink.off('close', done);
|
|
169
|
+
sink.off('error', done);
|
|
170
|
+
settle();
|
|
171
|
+
};
|
|
172
|
+
// Resume on 'drain', but also on 'close'/'error' so a sink that dies
|
|
173
|
+
// mid-write (e.g. one created with autoDestroy:false, which errors
|
|
174
|
+
// without emitting 'close') never leaves the fan-out stuck.
|
|
175
|
+
sink.once('drain', done);
|
|
176
|
+
sink.once('close', done);
|
|
177
|
+
sink.once('error', done);
|
|
178
|
+
}
|
|
179
|
+
settle();
|
|
180
|
+
},
|
|
181
|
+
final(cb) {
|
|
182
|
+
ended = true;
|
|
183
|
+
for (const sink of alive) {
|
|
184
|
+
if (sink.writable)
|
|
185
|
+
sink.end();
|
|
186
|
+
}
|
|
187
|
+
cb();
|
|
188
|
+
},
|
|
189
|
+
destroy(err, cb) {
|
|
190
|
+
// A clean end (`_final` already ran) leaves the sinks to flush what they
|
|
191
|
+
// were handed; any other teardown propagates to the sinks we own.
|
|
192
|
+
if (!ended || err) {
|
|
193
|
+
for (const sink of alive) {
|
|
194
|
+
if (sink.writable)
|
|
195
|
+
sink.destroy(err ?? undefined);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
cb(err);
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
}
|
|
15
202
|
export const streamSize = async (stream) => {
|
|
16
203
|
let size = 0;
|
|
17
204
|
for await (const chunk of stream) {
|
|
@@ -116,7 +303,7 @@ export const bytesToStream = (bytes) => {
|
|
|
116
303
|
return stream;
|
|
117
304
|
};
|
|
118
305
|
export class MaxSizeChecker extends Transform {
|
|
119
|
-
constructor(maxSize, createError) {
|
|
306
|
+
constructor(maxSize, createError = () => new Error(`Max size of ${maxSize} bytes exceeded`)) {
|
|
120
307
|
super();
|
|
121
308
|
this.maxSize = maxSize;
|
|
122
309
|
this.createError = createError;
|
|
@@ -132,6 +319,28 @@ export class MaxSizeChecker extends Transform {
|
|
|
132
319
|
}
|
|
133
320
|
}
|
|
134
321
|
}
|
|
322
|
+
export class HashPassThrough extends Transform {
|
|
323
|
+
#digest;
|
|
324
|
+
constructor(algorithm, options) {
|
|
325
|
+
super();
|
|
326
|
+
this.hash = createHash(algorithm, options);
|
|
327
|
+
}
|
|
328
|
+
_transform(chunk, _enc, cb) {
|
|
329
|
+
this.hash.update(chunk);
|
|
330
|
+
cb(null, chunk);
|
|
331
|
+
}
|
|
332
|
+
_flush(cb) {
|
|
333
|
+
const digest = this.hash.digest();
|
|
334
|
+
this.#digest = digest;
|
|
335
|
+
this.emit('hash', digest);
|
|
336
|
+
cb();
|
|
337
|
+
}
|
|
338
|
+
get digest() {
|
|
339
|
+
if (this.#digest)
|
|
340
|
+
return this.#digest;
|
|
341
|
+
throw new Error('Hash not yet computed. Wait for the stream to finish.');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
135
344
|
export function decodeStream(stream, contentEncoding) {
|
|
136
345
|
const decoders = createDecoders(contentEncoding);
|
|
137
346
|
if (decoders.length === 0)
|
package/dist/streams.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streams.js","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Duplex,\n PassThrough,\n Readable,\n type Stream,\n Transform,\n type TransformCallback,\n pipeline,\n} from 'node:stream'\nimport { createBrotliDecompress, createGunzip, createInflate } from 'node:zlib'\n\nexport const forwardStreamErrors = (...streams: Stream[]) => {\n for (let i = 1; i < streams.length; ++i) {\n const prev = streams[i - 1]\n const next = streams[i]\n\n prev.once('error', (err) => next.emit('error', err))\n }\n}\n\nexport const cloneStream = (stream: Readable): Readable => {\n const passthrough = new PassThrough()\n forwardStreamErrors(stream, passthrough)\n return stream.pipe(passthrough)\n}\n\nexport const streamSize = async (stream: Readable): Promise<number> => {\n let size = 0\n for await (const chunk of stream) {\n size += Buffer.byteLength(chunk)\n }\n return size\n}\n\nexport const streamToBytes = async (stream: AsyncIterable<Uint8Array>) =>\n // @NOTE Though Buffer is a sub-class of Uint8Array, we have observed\n // inconsistencies when using a Buffer in place of Uint8Array. For this\n // reason, we convert the Buffer to a Uint8Array.\n new Uint8Array(await streamToNodeBuffer(stream))\n\n// streamToBuffer identifier name already taken by @atproto/common-web\nexport const streamToNodeBuffer = async (\n stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,\n): Promise<Buffer> => {\n const chunks: Uint8Array[] = []\n let totalLength = 0 // keep track of total length for Buffer.concat\n for await (const chunk of stream) {\n if (chunk instanceof Uint8Array) {\n chunks.push(chunk)\n totalLength += Buffer.byteLength(chunk)\n } else {\n throw new TypeError('expected Uint8Array')\n }\n }\n return Buffer.concat(chunks, totalLength)\n}\n\nexport const byteIterableToStream = (\n iter: AsyncIterable<Uint8Array>,\n): Readable => {\n return Readable.from(iter, { objectMode: false })\n}\n\n/**\n * Coalesce a stream of Uint8Array chunks into larger chunks of at least the\n * specified size ({@link minChunkSize}). This is useful for optimizing\n * downstream processing that benefits from larger chunk sizes, such as\n * compression or hashing.\n */\nexport const coalesceByteStream = (\n stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,\n minChunkSize: number,\n): Readable => {\n if (!Number.isInteger(minChunkSize) || minChunkSize < 1) {\n throw new TypeError('minChunkSize must be a positive integer')\n }\n\n // @NOTE On Node 22, this *does* return a PassThrough (\"@types/node\"\n // incorrectly types it as Writable).\n return pipeline(stream, coalesce, (_err) => {\n // Errors are expected to be handled through the stream\n }) as PassThrough\n\n // @NOTE This implementation is not NodeJS specific and could be exported as\n // utility (from \"@atproto/common-web\") if needed. We don't do it now to avoid\n // increasing the API surface of our packages.\n async function* coalesce(\n iter: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,\n ): AsyncGenerator<Uint8Array> {\n // @NOTE This implementation avoids as many un-necessary copies as possible\n // and only buffers incoming chunks when they are smaller than the\n // coalescing buffer.\n\n let buffer = new Uint8Array(minChunkSize)\n let offset = 0\n\n for await (const chunk of iter) {\n const freeSpace = buffer.length - offset\n if (freeSpace > chunk.length) {\n // If the incoming chunk is smaller than the free space, we copy the\n // entire chunk into the coalescing buffer and continue to the next\n // chunk\n buffer.set(chunk, offset)\n offset += chunk.length\n } else if (offset === 0 && chunk.length >= minChunkSize) {\n // If the coalescing buffer is empty and the incoming chunk is larger\n // than the coalescing buffer, we can skip any copying and yield the\n // incoming chunk directly\n yield chunk\n } else {\n // Otherwise, we need to copy as much of the incoming chunk as we can\n // into the coalescing buffer and yield the full coalescing buffer.\n buffer.set(chunk.subarray(0, freeSpace), offset)\n yield buffer\n\n // We create a new coalescing buffer (for future use)\n buffer = new Uint8Array(minChunkSize)\n offset = 0\n\n const remainingBytes = chunk.subarray(freeSpace)\n if (remainingBytes.length > minChunkSize) {\n // If the remaining of chunk is still too big to fit in the\n // coalescing buffer, we yield it directly without copying it\n yield remainingBytes\n } else if (remainingBytes.length > 0) {\n // Otherwise, we copy the remaining bytes into the coalescing buffer\n // and continue to the next chunk\n buffer.set(remainingBytes, offset)\n offset += remainingBytes.length\n }\n }\n }\n\n // Yield any remaining bytes in the coalescing buffer\n if (offset > 0) {\n yield buffer.subarray(0, offset)\n }\n }\n}\n\nexport const bytesToStream = (bytes: Uint8Array): Readable => {\n const stream = new Readable()\n stream.push(bytes)\n stream.push(null)\n return stream\n}\n\nexport class MaxSizeChecker extends Transform {\n totalSize = 0\n constructor(\n public maxSize: number,\n public createError: () => Error,\n ) {\n super()\n }\n _transform(chunk: Uint8Array, _enc: BufferEncoding, cb: TransformCallback) {\n this.totalSize += chunk.length\n if (this.totalSize > this.maxSize) {\n cb(this.createError())\n } else {\n cb(null, chunk)\n }\n }\n}\n\nexport function decodeStream(\n stream: Readable,\n contentEncoding?: string | string[],\n): Readable\nexport function decodeStream(\n stream: AsyncIterable<Uint8Array>,\n contentEncoding?: string | string[],\n): AsyncIterable<Uint8Array> | Readable\nexport function decodeStream(\n stream: Readable | AsyncIterable<Uint8Array>,\n contentEncoding?: string | string[],\n): Readable | AsyncIterable<Uint8Array> {\n const decoders = createDecoders(contentEncoding)\n if (decoders.length === 0) return stream\n return pipeline([stream as Readable, ...decoders], () => {}) as Duplex\n}\n\n/**\n * Create a series of decoding streams based on the content-encoding header. The\n * resulting streams should be piped together to decode the content.\n *\n * @see {@link https://datatracker.ietf.org/doc/html/rfc9110#section-8.4.1}\n */\nexport function createDecoders(contentEncoding?: string | string[]): Duplex[] {\n const decoders: Duplex[] = []\n\n if (contentEncoding?.length) {\n const encodings: string[] = Array.isArray(contentEncoding)\n ? contentEncoding.flatMap(commaSplit)\n : contentEncoding.split(',')\n for (const encoding of encodings) {\n const normalizedEncoding = normalizeEncoding(encoding)\n\n // @NOTE\n // > The default (identity) encoding [...] is used only in the\n // > Accept-Encoding header, and SHOULD NOT be used in the\n // > Content-Encoding header.\n if (normalizedEncoding === 'identity') continue\n\n decoders.push(createDecoder(normalizedEncoding))\n }\n }\n\n return decoders.reverse()\n}\n\nfunction commaSplit(header: string): string[] {\n return header.split(',')\n}\n\nfunction normalizeEncoding(encoding: string) {\n // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1\n // > All content-coding values are case-insensitive...\n return encoding.trim().toLowerCase()\n}\n\nfunction createDecoder(normalizedEncoding: string): Duplex {\n switch (normalizedEncoding) {\n // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2\n case 'gzip':\n case 'x-gzip':\n return createGunzip()\n case 'deflate':\n return createInflate()\n case 'br':\n return createBrotliDecompress()\n case 'identity':\n return new PassThrough()\n default:\n throw new TypeError(\n `Unsupported content-encoding: \"${normalizedEncoding}\"`,\n )\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"streams.js","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Hash, type HashOptions, createHash } from 'node:crypto'\nimport {\n type Duplex,\n PassThrough,\n Readable,\n type Stream,\n Transform,\n type TransformCallback,\n Writable,\n pipeline,\n} from 'node:stream'\nimport { createBrotliDecompress, createGunzip, createInflate } from 'node:zlib'\n\n/**\n * @deprecated use native stream piping instead.\n */\nexport const forwardStreamErrors = (...streams: Stream[]) => {\n for (let i = 1; i < streams.length; ++i) {\n const prev = streams[i - 1]\n const next = streams[i]\n\n prev.once('error', (err) => next.emit('error', err))\n }\n}\n\n/**\n * @deprecated This function has several flaws:\n * - It only works if all the stream are setup during the same tick\n * - It does not apply any backpressure to the source stream, which can lead to\n * memory issues\n * Use {@link Tee} instead, which is a more robust implementation of the same\n * concept.\n */\nexport const cloneStream = (stream: Readable): Readable => {\n const passthrough = new PassThrough()\n forwardStreamErrors(stream, passthrough)\n return stream.pipe(passthrough)\n}\n\n/**\n * A {@link Transform} that forwards every chunk downstream while mirroring a\n * copy into a \"branch\" {@link Writable} (exposed through {@link Tee.branch}, e.g.\n * to be cached). The tee is paced by the slower of its two consumers, bounding\n * how much data gets buffered. The branch is completed when the source ends,\n * and torn down if the tee errors or is destroyed early (e.g. the client\n * disconnected).\n *\n * Consuming the branch is best-effort: its failures are swallowed here and must\n * never break the main stream.\n *\n * Because the tee is a {@link Transform}, its *main* output is a downstream\n * pipeline stage — so `pipeline([readable, tee, main])` tears the tee (and the\n * source) down as soon as `main` fails. If you need the branch to keep going\n * after the main consumer disappears (or vice-versa), use {@link fanOut}, which\n * owns both outputs instead of exposing one as a pipeline stage.\n */\nexport class Tee extends Transform {\n readonly branch: Writable\n #branchFailed = false\n\n constructor(branch: Writable | ((readable: Readable) => void)) {\n super()\n if (typeof branch === 'function') {\n const passthrough = new PassThrough({ autoDestroy: true })\n branch(passthrough)\n this.branch = passthrough\n } else {\n this.branch = branch\n }\n\n // The branch is best-effort: its failures must never break or stall the\n // main stream.\n this.branch.on('error', () => {\n this.#branchFailed = true\n })\n }\n\n get branchWritable() {\n return !this.#branchFailed && this.branch.writable\n }\n\n _transform(chunk: unknown, _enc: BufferEncoding, cb: TransformCallback) {\n // Forward downstream, applying backpressure if either branch is slower.\n if (!this.branchWritable || this.branch.write(chunk)) {\n cb(null, chunk)\n } else {\n const done = () => {\n this.branch.off('drain', done)\n this.branch.off('close', done)\n this.branch.off('error', done)\n cb(null, chunk)\n }\n // Resume on 'drain' (branch caught up) but also on 'close'/'error' so the\n // tee is never stuck if the branch dies mid-write — e.g. a branch created\n // with `autoDestroy: false`, which errors without emitting 'close'.\n this.branch.once('drain', done)\n this.branch.once('close', done)\n this.branch.once('error', done)\n }\n }\n\n _flush(cb: TransformCallback) {\n if (this.branchWritable) this.branch.end()\n cb()\n }\n\n _destroy(err: Error | null, cb: (err?: Error | null) => void) {\n // A failed branch has already torn itself down (or given up); leave it be\n // rather than emitting a second, spurious error on it.\n if (this.branchWritable) {\n this.branch.destroy(err ?? new Error('Tee destroyed'))\n }\n\n cb(err)\n }\n}\n\n/**\n * A {@link Writable} that mirrors everything written to it into several\n * independent \"sinks\", pacing the input by the slowest sink that is still\n * alive. Unlike {@link Tee}, the sinks are *owned* by the fan-out rather than\n * being downstream pipeline stages, which lets the two error domains be kept\n * apart:\n *\n * - An *output* (sink) that errors or ends early is simply dropped; the other\n * sinks — and the input — keep going. Its error is swallowed (best-effort),\n * exactly like a {@link Tee} branch.\n * - The *input* (writable side) is only torn down once *every* sink has died,\n * or if the input itself fails — in which case the failure is propagated to\n * every surviving sink.\n *\n * Designed to be the destination of a pipeline:\n *\n * ```\n * pipeline([readable, ...transforms, fanOut(main, branch)])\n * ```\n *\n * If `main` disconnects (e.g. an HTTP response whose client went away), `branch`\n * still receives the full stream (e.g. to finish populating a cache), and\n * vice-versa. A sink may also be provided as a function receiving a\n * {@link Readable} to consume, mirroring {@link Tee}'s constructor.\n */\nexport function fanOut(\n ...outputs: ReadonlyArray<Writable | ((readable: Readable) => void)>\n): Writable {\n const sinks = outputs.map((output) => {\n if (typeof output !== 'function') return output\n const passthrough = new PassThrough()\n output(passthrough)\n return passthrough\n })\n\n // Sinks still able to receive data. A sink that errors or closes is dropped\n // and never written to again; its 'error' is swallowed here so a dead sink can\n // neither crash the process nor disturb the surviving sinks (other listeners,\n // e.g. a reader consuming it, still observe the error).\n const alive = new Set<Writable>(sinks)\n const drop = (sink: Writable) => alive.delete(sink)\n for (const sink of sinks) {\n sink.on('error', () => drop(sink))\n sink.on('close', () => drop(sink))\n }\n\n // Whether the input ended cleanly, so we can distinguish a normal completion\n // (let the sinks finish the data we handed them) from a teardown (destroy\n // them).\n let ended = false\n\n return new Writable({\n write(chunk, _enc, cb) {\n // Write to every live sink, then invoke `cb` once they have all either\n // accepted the chunk, drained, or died — pacing the input by the slowest\n // surviving sink.\n let waiting = 1 // guard so `cb` can't fire before the loop completes\n const settle = () => {\n if (--waiting > 0) return\n // If every sink died while handling this chunk, tear the input down.\n cb(alive.size === 0 ? new Error('fanOut: all sinks ended') : null)\n }\n\n for (const sink of alive) {\n if (!sink.writable) {\n drop(sink)\n continue\n }\n if (sink.write(chunk)) continue\n\n waiting++\n const done = () => {\n sink.off('drain', done)\n sink.off('close', done)\n sink.off('error', done)\n settle()\n }\n // Resume on 'drain', but also on 'close'/'error' so a sink that dies\n // mid-write (e.g. one created with autoDestroy:false, which errors\n // without emitting 'close') never leaves the fan-out stuck.\n sink.once('drain', done)\n sink.once('close', done)\n sink.once('error', done)\n }\n\n settle()\n },\n final(cb) {\n ended = true\n for (const sink of alive) {\n if (sink.writable) sink.end()\n }\n cb()\n },\n destroy(err, cb) {\n // A clean end (`_final` already ran) leaves the sinks to flush what they\n // were handed; any other teardown propagates to the sinks we own.\n if (!ended || err) {\n for (const sink of alive) {\n if (sink.writable) sink.destroy(err ?? undefined)\n }\n }\n cb(err)\n },\n })\n}\n\nexport const streamSize = async (stream: Readable): Promise<number> => {\n let size = 0\n for await (const chunk of stream) {\n size += Buffer.byteLength(chunk)\n }\n return size\n}\n\nexport const streamToBytes = async (stream: AsyncIterable<Uint8Array>) =>\n // @NOTE Though Buffer is a sub-class of Uint8Array, we have observed\n // inconsistencies when using a Buffer in place of Uint8Array. For this\n // reason, we convert the Buffer to a Uint8Array.\n new Uint8Array(await streamToNodeBuffer(stream))\n\n// streamToBuffer identifier name already taken by @atproto/common-web\nexport const streamToNodeBuffer = async (\n stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,\n): Promise<Buffer> => {\n const chunks: Uint8Array[] = []\n let totalLength = 0 // keep track of total length for Buffer.concat\n for await (const chunk of stream) {\n if (chunk instanceof Uint8Array) {\n chunks.push(chunk)\n totalLength += Buffer.byteLength(chunk)\n } else {\n throw new TypeError('expected Uint8Array')\n }\n }\n return Buffer.concat(chunks, totalLength)\n}\n\nexport const byteIterableToStream = (\n iter: AsyncIterable<Uint8Array>,\n): Readable => {\n return Readable.from(iter, { objectMode: false })\n}\n\n/**\n * Coalesce a stream of Uint8Array chunks into larger chunks of at least the\n * specified size ({@link minChunkSize}). This is useful for optimizing\n * downstream processing that benefits from larger chunk sizes, such as\n * compression or hashing.\n */\nexport const coalesceByteStream = (\n stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,\n minChunkSize: number,\n): Readable => {\n if (!Number.isInteger(minChunkSize) || minChunkSize < 1) {\n throw new TypeError('minChunkSize must be a positive integer')\n }\n\n // @NOTE On Node 22, this *does* return a PassThrough (\"@types/node\"\n // incorrectly types it as Writable).\n return pipeline(stream, coalesce, (_err) => {\n // Errors are expected to be handled through the stream\n }) as PassThrough\n\n // @NOTE This implementation is not NodeJS specific and could be exported as\n // utility (from \"@atproto/common-web\") if needed. We don't do it now to avoid\n // increasing the API surface of our packages.\n async function* coalesce(\n iter: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,\n ): AsyncGenerator<Uint8Array> {\n // @NOTE This implementation avoids as many un-necessary copies as possible\n // and only buffers incoming chunks when they are smaller than the\n // coalescing buffer.\n\n let buffer = new Uint8Array(minChunkSize)\n let offset = 0\n\n for await (const chunk of iter) {\n const freeSpace = buffer.length - offset\n if (freeSpace > chunk.length) {\n // If the incoming chunk is smaller than the free space, we copy the\n // entire chunk into the coalescing buffer and continue to the next\n // chunk\n buffer.set(chunk, offset)\n offset += chunk.length\n } else if (offset === 0 && chunk.length >= minChunkSize) {\n // If the coalescing buffer is empty and the incoming chunk is larger\n // than the coalescing buffer, we can skip any copying and yield the\n // incoming chunk directly\n yield chunk\n } else {\n // Otherwise, we need to copy as much of the incoming chunk as we can\n // into the coalescing buffer and yield the full coalescing buffer.\n buffer.set(chunk.subarray(0, freeSpace), offset)\n yield buffer\n\n // We create a new coalescing buffer (for future use)\n buffer = new Uint8Array(minChunkSize)\n offset = 0\n\n const remainingBytes = chunk.subarray(freeSpace)\n if (remainingBytes.length > minChunkSize) {\n // If the remaining of chunk is still too big to fit in the\n // coalescing buffer, we yield it directly without copying it\n yield remainingBytes\n } else if (remainingBytes.length > 0) {\n // Otherwise, we copy the remaining bytes into the coalescing buffer\n // and continue to the next chunk\n buffer.set(remainingBytes, offset)\n offset += remainingBytes.length\n }\n }\n }\n\n // Yield any remaining bytes in the coalescing buffer\n if (offset > 0) {\n yield buffer.subarray(0, offset)\n }\n }\n}\n\nexport const bytesToStream = (bytes: Uint8Array): Readable => {\n const stream = new Readable()\n stream.push(bytes)\n stream.push(null)\n return stream\n}\n\nexport class MaxSizeChecker extends Transform {\n totalSize = 0\n constructor(\n public maxSize: number,\n public createError: () => Error = () =>\n new Error(`Max size of ${maxSize} bytes exceeded`),\n ) {\n super()\n }\n _transform(chunk: Uint8Array, _enc: BufferEncoding, cb: TransformCallback) {\n this.totalSize += chunk.length\n if (this.totalSize > this.maxSize) {\n cb(this.createError())\n } else {\n cb(null, chunk)\n }\n }\n}\n\nexport class HashPassThrough extends Transform {\n private readonly hash: Hash\n #digest?: Buffer<ArrayBuffer>\n\n constructor(algorithm: string, options?: HashOptions) {\n super()\n this.hash = createHash(algorithm, options)\n }\n\n _transform(chunk: Uint8Array, _enc: BufferEncoding, cb: TransformCallback) {\n this.hash.update(chunk)\n cb(null, chunk)\n }\n\n _flush(cb: TransformCallback) {\n const digest = this.hash.digest()\n this.#digest = digest\n this.emit('hash', digest)\n cb()\n }\n\n get digest(): Buffer<ArrayBuffer> {\n if (this.#digest) return this.#digest\n throw new Error('Hash not yet computed. Wait for the stream to finish.')\n }\n}\n\nexport function decodeStream(\n stream: Readable,\n contentEncoding?: string | string[],\n): Readable\nexport function decodeStream(\n stream: AsyncIterable<Uint8Array>,\n contentEncoding?: string | string[],\n): AsyncIterable<Uint8Array> | Readable\nexport function decodeStream(\n stream: Readable | AsyncIterable<Uint8Array>,\n contentEncoding?: string | string[],\n): Readable | AsyncIterable<Uint8Array> {\n const decoders = createDecoders(contentEncoding)\n if (decoders.length === 0) return stream\n return pipeline([stream as Readable, ...decoders], () => {}) as Duplex\n}\n\n/**\n * Create a series of decoding streams based on the content-encoding header. The\n * resulting streams should be piped together to decode the content.\n *\n * @see {@link https://datatracker.ietf.org/doc/html/rfc9110#section-8.4.1}\n */\nexport function createDecoders(contentEncoding?: string | string[]): Duplex[] {\n const decoders: Duplex[] = []\n\n if (contentEncoding?.length) {\n const encodings: string[] = Array.isArray(contentEncoding)\n ? contentEncoding.flatMap(commaSplit)\n : contentEncoding.split(',')\n for (const encoding of encodings) {\n const normalizedEncoding = normalizeEncoding(encoding)\n\n // @NOTE\n // > The default (identity) encoding [...] is used only in the\n // > Accept-Encoding header, and SHOULD NOT be used in the\n // > Content-Encoding header.\n if (normalizedEncoding === 'identity') continue\n\n decoders.push(createDecoder(normalizedEncoding))\n }\n }\n\n return decoders.reverse()\n}\n\nfunction commaSplit(header: string): string[] {\n return header.split(',')\n}\n\nfunction normalizeEncoding(encoding: string) {\n // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1\n // > All content-coding values are case-insensitive...\n return encoding.trim().toLowerCase()\n}\n\nfunction createDecoder(normalizedEncoding: string): Duplex {\n switch (normalizedEncoding) {\n // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2\n case 'gzip':\n case 'x-gzip':\n return createGunzip()\n case 'deflate':\n return createInflate()\n case 'br':\n return createBrotliDecompress()\n case 'identity':\n return new PassThrough()\n default:\n throw new TypeError(\n `Unsupported content-encoding: \"${normalizedEncoding}\"`,\n )\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/common",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Shared web-platform-friendly code for atproto libraries",
|
|
6
6
|
"keywords": [
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"@atproto/lex-data": "^0.1.7"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"
|
|
39
|
-
"
|
|
38
|
+
"uint8arrays": "^5.0.0",
|
|
39
|
+
"vitest": "^4.1.10"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
|
-
"test": "
|
|
42
|
+
"test": "vitest run",
|
|
43
43
|
"build": "tsc --build tsconfig.build.json"
|
|
44
44
|
}
|
|
45
45
|
}
|