@nmtjs/common 0.14.5 → 0.15.0-beta.10

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/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2024 Denis Ilchyshyn
1
+ Copyright (c) 2025 Denys Ilchyshyn
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
package/README.md CHANGED
@@ -6,4 +6,4 @@
6
6
  - binary data streaming and event subscriptions
7
7
  - contract-based API
8
8
  - end-to-end type safety
9
- - CPU-intensive task execution on separate workers
9
+ - CPU-intensive task execution on separate workers
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Combines multiple AbortSignals into one that aborts when any of the source signals abort.
3
+ *
4
+ * This is a custom implementation to work around memory leaks in Node.js's AbortSignal.any().
5
+ * Bun's implementation is fine, so we use the native version there.
6
+ *
7
+ * @see https://github.com/nodejs/node/issues/54614
8
+ */
9
+ export declare function anyAbortSignal(...signals: (AbortSignal | undefined)[]): AbortSignal;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Combines multiple AbortSignals into one that aborts when any of the source signals abort.
3
+ *
4
+ * This is a custom implementation to work around memory leaks in Node.js's AbortSignal.any().
5
+ * Bun's implementation is fine, so we use the native version there.
6
+ *
7
+ * @see https://github.com/nodejs/node/issues/54614
8
+ */
9
+ export function anyAbortSignal(...signals) {
10
+ const filtered = signals.filter(Boolean);
11
+ if (filtered.length === 0) {
12
+ return new AbortController().signal;
13
+ }
14
+ if (filtered.length === 1) {
15
+ return filtered[0];
16
+ }
17
+ // Use native implementation on Bun (no memory leak there)
18
+ if ('Bun' in globalThis) {
19
+ return AbortSignal.any(filtered);
20
+ }
21
+ // Custom implementation for Node.js to avoid memory leaks
22
+ const controller = new AbortController();
23
+ // Check if any signal is already aborted
24
+ for (const signal of filtered) {
25
+ if (signal.aborted) {
26
+ controller.abort();
27
+ return controller.signal;
28
+ }
29
+ }
30
+ // Track cleanup functions
31
+ const cleanups = [];
32
+ const onAbort = () => {
33
+ controller.abort();
34
+ // Clean up all listeners immediately after abort
35
+ cleanup();
36
+ };
37
+ const cleanup = () => {
38
+ for (const fn of cleanups) {
39
+ fn();
40
+ }
41
+ cleanups.length = 0;
42
+ };
43
+ // Attach listeners to all signals
44
+ for (const signal of filtered) {
45
+ signal.addEventListener('abort', onAbort, { once: true });
46
+ cleanups.push(() => signal.removeEventListener('abort', onAbort));
47
+ }
48
+ return controller.signal;
49
+ }
50
+ //# sourceMappingURL=abortSignal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abortSignal.js","sourceRoot":"","sources":["../src/abortSignal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAG,OAAoC;IAEvC,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAkB,CAAA;IAEzD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,eAAe,EAAE,CAAC,MAAM,CAAA;IACrC,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAA;IACpB,CAAC;IAED,0DAA0D;IAC1D,IAAI,KAAK,IAAI,UAAU,EAAE,CAAC;QACxB,OAAO,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAClC,CAAC;IAED,0DAA0D;IAC1D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;IAExC,yCAAyC;IACzC,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,UAAU,CAAC,KAAK,EAAE,CAAA;YAClB,OAAO,UAAU,CAAC,MAAM,CAAA;QAC1B,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,MAAM,QAAQ,GAAmB,EAAE,CAAA;IAEnC,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,UAAU,CAAC,KAAK,EAAE,CAAA;QAClB,iDAAiD;QACjD,OAAO,EAAE,CAAA;IACX,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,EAAE,EAAE,CAAA;QACN,CAAC;QACD,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;IACrB,CAAC,CAAA;IAED,kCAAkC;IAClC,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QACzD,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IACnE,CAAC;IAED,OAAO,UAAU,CAAC,MAAM,CAAA;AAC1B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const MAX_UINT32: number;
2
+ export declare const MAX_UINT16: number;
3
+ export declare const MAX_UINT8: number;
@@ -0,0 +1,4 @@
1
+ export const MAX_UINT32 = 2 ** 32 - 1;
2
+ export const MAX_UINT16 = 2 ** 16 - 1;
3
+ export const MAX_UINT8 = 2 ** 8 - 1;
4
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;AACrC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;AACrC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,5 @@
1
+ export * from './abortSignal.ts';
2
+ export * from './constants.ts';
3
+ export * from './streams.ts';
1
4
  export * from './types.ts';
2
5
  export * from './utils.ts';
package/dist/index.js CHANGED
@@ -1,2 +1,6 @@
1
+ export * from "./abortSignal.js";
2
+ export * from "./constants.js";
3
+ export * from "./streams.js";
1
4
  export * from "./types.js";
2
5
  export * from "./utils.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}
@@ -0,0 +1,15 @@
1
+ import type { Async } from './types.ts';
2
+ export interface DuplexStreamOptions<O = unknown, I = O> {
3
+ start?: (controller: ReadableStreamDefaultController<O>) => void;
4
+ pull?: (controller: ReadableStreamDefaultController<O>) => Async<void>;
5
+ cancel?: (reason: unknown) => void;
6
+ transform?: (chunk: I) => O;
7
+ close?: () => void;
8
+ readableStrategy?: QueuingStrategy<O>;
9
+ writableStrategy?: QueuingStrategy<I>;
10
+ }
11
+ export declare class DuplexStream<O = unknown, I = O> {
12
+ readonly readable: ReadableStream<O>;
13
+ readonly writable: WritableStream<I>;
14
+ constructor(options?: DuplexStreamOptions<O, I>);
15
+ }
@@ -0,0 +1,34 @@
1
+ // TODO: add proper queueing/backpressure strategy support
2
+ export class DuplexStream {
3
+ readable;
4
+ writable;
5
+ constructor(options = {}) {
6
+ this.readable = new ReadableStream({
7
+ cancel: options.cancel,
8
+ start: (controller) => {
9
+ // @ts-expect-error
10
+ this.writable = new WritableStream({
11
+ write: (_chunk) => {
12
+ const chunk = options?.transform
13
+ ? options?.transform(_chunk)
14
+ : _chunk;
15
+ controller.enqueue(chunk);
16
+ },
17
+ abort: (reason) => controller.error(reason),
18
+ close: () => {
19
+ options?.close?.();
20
+ try {
21
+ controller.close();
22
+ }
23
+ catch {
24
+ // Controller may already be closed (e.g., via cancel)
25
+ }
26
+ },
27
+ }, options.writableStrategy);
28
+ options.start?.(controller);
29
+ },
30
+ pull: options?.pull,
31
+ }, options.readableStrategy);
32
+ }
33
+ }
34
+ //# sourceMappingURL=streams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streams.js","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAc1D,MAAM,OAAO,YAAY;IACd,QAAQ,CAAmB;IAC3B,QAAQ,CAAoB;IAErC,YAAY,UAAqC,EAAE;QACjD,IAAI,CAAC,QAAQ,GAAG,IAAI,cAAc,CAChC;YACE,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,KAAK,EAAE,CAAC,UAAU,EAAE,EAAE;gBACpB,mBAAmB;gBACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,cAAc,CAChC;oBACE,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE;wBAChB,MAAM,KAAK,GAAG,OAAO,EAAE,SAAS;4BAC9B,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,MAAM,CAAC;4BAC5B,CAAC,CAAC,MAAM,CAAA;wBACV,UAAU,CAAC,OAAO,CAAC,KAAU,CAAC,CAAA;oBAChC,CAAC;oBACD,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;oBAC3C,KAAK,EAAE,GAAG,EAAE;wBACV,OAAO,EAAE,KAAK,EAAE,EAAE,CAAA;wBAClB,IAAI,CAAC;4BACH,UAAU,CAAC,KAAK,EAAE,CAAA;wBACpB,CAAC;wBAAC,MAAM,CAAC;4BACP,sDAAsD;wBACxD,CAAC;oBACH,CAAC;iBACF,EACD,OAAO,CAAC,gBAAgB,CACzB,CAAA;gBACD,OAAO,CAAC,KAAK,EAAE,CAAC,UAAU,CAAC,CAAA;YAC7B,CAAC;YACD,IAAI,EAAE,OAAO,EAAE,IAAI;SACpB,EACD,OAAO,CAAC,gBAAgB,CACzB,CAAA;IACH,CAAC;CACF"}
package/dist/types.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ declare const TSErrorSymbol: unique symbol;
2
+ export type TSError<T extends string = string> = `Error: ${T}` & {
3
+ [TSErrorSymbol]: true;
4
+ };
1
5
  export interface TypeProvider {
2
6
  readonly input: unknown;
3
7
  readonly output: unknown;
@@ -28,4 +32,5 @@ type MergeTypes<TypesArray extends any[], Res = {}> = TypesArray extends [
28
32
  type OnlyFirst<F, S> = F & {
29
33
  [Key in keyof Omit<S, keyof F>]?: never;
30
34
  };
35
+ export type Pattern = RegExp | string | ((value: string) => boolean);
31
36
  export {};
package/dist/types.js CHANGED
@@ -1 +1,3 @@
1
+ const TSErrorSymbol = Symbol('TSError');
1
2
  export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,aAAa,GAAkB,MAAM,CAAC,SAAS,CAAC,CAAA"}
package/dist/utils.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import type { Callback } from './types.ts';
1
+ import type { Callback, Pattern } from './types.ts';
2
2
  export declare const noopFn: () => void;
3
3
  export declare function merge<T extends any[]>(...objects: T): any;
4
+ export declare function unique<T>(array: Iterable<T>): Iterable<T>;
4
5
  export declare function defer<T extends Callback>(cb: T, ms?: number, ...args: Parameters<T>): Promise<Awaited<ReturnType<T>>>;
5
6
  export declare function range(count: number, start?: number): {
6
7
  [Symbol.iterator](): {
@@ -13,19 +14,23 @@ export declare function range(count: number, start?: number): {
13
14
  export declare function debounce(cb: Callback, delay: number): ((...args: any[]) => void) & {
14
15
  clear: () => any;
15
16
  };
16
- export interface InteractivePromise<T = any> {
17
+ export interface Future<T = any> {
17
18
  promise: Promise<T>;
18
19
  resolve: (value: T) => void;
19
20
  reject: (error: any) => void;
20
- toArgs: () => [resolve: this['resolve'], reject: this['reject']];
21
21
  }
22
- export declare function createPromise<T>(): InteractivePromise<T>;
22
+ export declare function createFuture<T>(): Future<T>;
23
23
  export declare function onAbort<T extends Callback>(signal: AbortSignal, cb: T, reason?: any): () => void;
24
- export declare function withTimeout(value: Promise<any>, timeout: number, timeoutError: Error, controller?: AbortController): Promise<unknown>;
24
+ export declare function withTimeout(value: Promise<any>, timeout: number, timeoutError: Error): Promise<any>;
25
25
  export declare function tryCaptureStackTrace(depth?: number): string | undefined;
26
26
  export declare function isGeneratorFunction(value: any): value is GeneratorFunction;
27
27
  export declare function isAsyncGeneratorFunction(value: any): value is AsyncGeneratorFunction;
28
+ export declare function isAsyncIterable(value: any): value is AsyncIterable<unknown>;
28
29
  export declare function throwError(message: string, ErrorClass?: ErrorConstructor): never;
29
30
  export declare function once(target: EventTarget, event: string): Promise<void>;
30
31
  export declare function onceAborted(signal: AbortSignal): Promise<void>;
31
32
  export declare function isAbortError(error: any): boolean;
33
+ /**
34
+ * Very simple pattern matching function.
35
+ */
36
+ export declare function match(value: string, pattern: Pattern): boolean;
package/dist/utils.js CHANGED
@@ -2,6 +2,9 @@ export const noopFn = () => { };
2
2
  export function merge(...objects) {
3
3
  return Object.assign({}, ...objects);
4
4
  }
5
+ export function unique(array) {
6
+ return new Set(array).values();
7
+ }
5
8
  export function defer(cb, ms = 1, ...args) {
6
9
  return new Promise((resolve, reject) => setTimeout(async () => {
7
10
  try {
@@ -39,7 +42,7 @@ export function debounce(cb, delay) {
39
42
  return Object.assign(fn, { clear });
40
43
  }
41
44
  // TODO: Promise.withResolvers?
42
- export function createPromise() {
45
+ export function createFuture() {
43
46
  let resolve;
44
47
  let reject;
45
48
  const promise = new Promise((res, rej) => {
@@ -47,29 +50,31 @@ export function createPromise() {
47
50
  reject = rej;
48
51
  });
49
52
  // @ts-expect-error
50
- return { resolve, reject, promise, toArgs: () => [resolve, reject] };
53
+ return { resolve, reject, promise };
51
54
  }
52
55
  export function onAbort(signal, cb, reason) {
53
56
  const listener = () => cb(reason ?? signal.reason);
54
57
  signal.addEventListener('abort', listener, { once: true });
55
58
  return () => signal.removeEventListener('abort', listener);
56
59
  }
57
- export function withTimeout(value, timeout, timeoutError, controller) {
58
- return new Promise((resolve, reject) => {
59
- const timer = setTimeout(reject, timeout, timeoutError);
60
- const clearTimer = () => clearTimeout(timer);
61
- const rejectWithTimeout = (error) => {
62
- reject(error);
63
- controller?.abort(error);
64
- };
65
- value.then(resolve).catch(rejectWithTimeout).finally(clearTimer);
66
- });
60
+ export function withTimeout(value, timeout, timeoutError) {
61
+ return Promise.race([
62
+ value,
63
+ new Promise((_, reject) => setTimeout(reject, timeout, timeoutError)),
64
+ ]);
67
65
  }
68
66
  export function tryCaptureStackTrace(depth = 0) {
69
- return (new Error().stack
70
- ?.split('\n')
71
- .slice(4 + depth)
72
- .join('\n') ?? undefined);
67
+ const traceLines = new Error().stack?.split('\n');
68
+ if (traceLines) {
69
+ for (const traceLine of traceLines) {
70
+ const trimmed = traceLine.trim();
71
+ if (trimmed.startsWith('at eval (') && trimmed.endsWith(')')) {
72
+ const trace = trimmed.slice(9, -1);
73
+ return trace;
74
+ }
75
+ }
76
+ }
77
+ return undefined;
73
78
  }
74
79
  export function isGeneratorFunction(value) {
75
80
  return (typeof value === 'function' &&
@@ -79,6 +84,9 @@ export function isAsyncGeneratorFunction(value) {
79
84
  return (typeof value === 'function' &&
80
85
  value.constructor.name === 'AsyncGeneratorFunction');
81
86
  }
87
+ export function isAsyncIterable(value) {
88
+ return value && typeof value === 'object' && Symbol.asyncIterator in value;
89
+ }
82
90
  export function throwError(message, ErrorClass = Error) {
83
91
  throw new ErrorClass(message);
84
92
  }
@@ -97,3 +105,32 @@ export function isAbortError(error) {
97
105
  (error.code === 20 || error.code === 'ABORT_ERR')) ||
98
106
  (error instanceof Event && error.type === 'abort'));
99
107
  }
108
+ /**
109
+ * Very simple pattern matching function.
110
+ */
111
+ export function match(value, pattern) {
112
+ if (typeof pattern === 'function') {
113
+ return pattern(value);
114
+ }
115
+ else if (typeof pattern === 'string') {
116
+ if (pattern === '*' || pattern === '**') {
117
+ return true;
118
+ }
119
+ else if (pattern.at(0) === '*' && pattern.at(-1) === '*') {
120
+ return value.includes(pattern.slice(1, -1));
121
+ }
122
+ else if (pattern.at(-1) === '*') {
123
+ return value.startsWith(pattern.slice(0, -1));
124
+ }
125
+ else if (pattern.at(0) === '*') {
126
+ return value.endsWith(pattern.slice(1));
127
+ }
128
+ else {
129
+ return value === pattern;
130
+ }
131
+ }
132
+ else {
133
+ return pattern.test(value);
134
+ }
135
+ }
136
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -2,20 +2,18 @@
2
2
  "name": "@nmtjs/common",
3
3
  "type": "module",
4
4
  "exports": {
5
- ".": {
6
- "types": "./dist/index.d.ts",
7
- "import": "./dist/index.js",
8
- "module-sync": "./dist/index.js"
9
- }
5
+ ".": "./dist/index.js"
10
6
  },
11
7
  "files": [
12
8
  "dist",
9
+ "src",
13
10
  "LICENSE.md",
14
11
  "README.md"
15
12
  ],
16
- "version": "0.14.5",
13
+ "version": "0.15.0-beta.10",
17
14
  "scripts": {
18
- "build": "tsc",
15
+ "clean-build": "rm -rf ./dist",
16
+ "build": "tsc --declaration --sourceMap",
19
17
  "type-check": "tsc --noEmit"
20
18
  }
21
19
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Combines multiple AbortSignals into one that aborts when any of the source signals abort.
3
+ *
4
+ * This is a custom implementation to work around memory leaks in Node.js's AbortSignal.any().
5
+ * Bun's implementation is fine, so we use the native version there.
6
+ *
7
+ * @see https://github.com/nodejs/node/issues/54614
8
+ */
9
+ export function anyAbortSignal(
10
+ ...signals: (AbortSignal | undefined)[]
11
+ ): AbortSignal {
12
+ const filtered = signals.filter(Boolean) as AbortSignal[]
13
+
14
+ if (filtered.length === 0) {
15
+ return new AbortController().signal
16
+ }
17
+
18
+ if (filtered.length === 1) {
19
+ return filtered[0]
20
+ }
21
+
22
+ // Use native implementation on Bun (no memory leak there)
23
+ if ('Bun' in globalThis) {
24
+ return AbortSignal.any(filtered)
25
+ }
26
+
27
+ // Custom implementation for Node.js to avoid memory leaks
28
+ const controller = new AbortController()
29
+
30
+ // Check if any signal is already aborted
31
+ for (const signal of filtered) {
32
+ if (signal.aborted) {
33
+ controller.abort()
34
+ return controller.signal
35
+ }
36
+ }
37
+
38
+ // Track cleanup functions
39
+ const cleanups: (() => void)[] = []
40
+
41
+ const onAbort = () => {
42
+ controller.abort()
43
+ // Clean up all listeners immediately after abort
44
+ cleanup()
45
+ }
46
+
47
+ const cleanup = () => {
48
+ for (const fn of cleanups) {
49
+ fn()
50
+ }
51
+ cleanups.length = 0
52
+ }
53
+
54
+ // Attach listeners to all signals
55
+ for (const signal of filtered) {
56
+ signal.addEventListener('abort', onAbort, { once: true })
57
+ cleanups.push(() => signal.removeEventListener('abort', onAbort))
58
+ }
59
+
60
+ return controller.signal
61
+ }
@@ -0,0 +1,3 @@
1
+ export const MAX_UINT32 = 2 ** 32 - 1
2
+ export const MAX_UINT16 = 2 ** 16 - 1
3
+ export const MAX_UINT8 = 2 ** 8 - 1
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './abortSignal.ts'
2
+ export * from './constants.ts'
3
+ export * from './streams.ts'
4
+ export * from './types.ts'
5
+ export * from './utils.ts'
package/src/streams.ts ADDED
@@ -0,0 +1,52 @@
1
+ // TODO: add proper queueing/backpressure strategy support
2
+
3
+ import type { Async } from './types.ts'
4
+
5
+ export interface DuplexStreamOptions<O = unknown, I = O> {
6
+ start?: (controller: ReadableStreamDefaultController<O>) => void
7
+ pull?: (controller: ReadableStreamDefaultController<O>) => Async<void>
8
+ cancel?: (reason: unknown) => void
9
+ transform?: (chunk: I) => O
10
+ close?: () => void
11
+ readableStrategy?: QueuingStrategy<O>
12
+ writableStrategy?: QueuingStrategy<I>
13
+ }
14
+
15
+ export class DuplexStream<O = unknown, I = O> {
16
+ readonly readable: ReadableStream<O>
17
+ readonly writable!: WritableStream<I>
18
+
19
+ constructor(options: DuplexStreamOptions<O, I> = {}) {
20
+ this.readable = new ReadableStream<O>(
21
+ {
22
+ cancel: options.cancel,
23
+ start: (controller) => {
24
+ // @ts-expect-error
25
+ this.writable = new WritableStream<I>(
26
+ {
27
+ write: (_chunk) => {
28
+ const chunk = options?.transform
29
+ ? options?.transform(_chunk)
30
+ : _chunk
31
+ controller.enqueue(chunk as O)
32
+ },
33
+ abort: (reason) => controller.error(reason),
34
+ close: () => {
35
+ options?.close?.()
36
+ try {
37
+ controller.close()
38
+ } catch {
39
+ // Controller may already be closed (e.g., via cancel)
40
+ }
41
+ },
42
+ },
43
+ options.writableStrategy,
44
+ )
45
+ options.start?.(controller)
46
+ },
47
+ pull: options?.pull,
48
+ },
49
+ options.readableStrategy,
50
+ )
51
+ }
52
+ }
package/src/types.ts ADDED
@@ -0,0 +1,80 @@
1
+ const TSErrorSymbol: unique symbol = Symbol('TSError')
2
+
3
+ export type TSError<T extends string = string> = `Error: ${T}` & {
4
+ [TSErrorSymbol]: true
5
+ }
6
+
7
+ export interface TypeProvider {
8
+ readonly input: unknown
9
+ readonly output: unknown
10
+ }
11
+
12
+ export type CallTypeProvider<T extends TypeProvider, V> = (T & {
13
+ input: V
14
+ })['output']
15
+
16
+ export type ClassConstructor<T = any, A extends any[] = any[]> =
17
+ | (abstract new (
18
+ ...args: A
19
+ ) => T)
20
+ | (new (
21
+ ...args: A
22
+ ) => T)
23
+
24
+ export type ClassInstance<T> = T extends ClassConstructor<infer U> ? U : never
25
+ export type ClassConstructorArgs<T, A = never> = T extends ClassConstructor<
26
+ any,
27
+ infer U
28
+ >
29
+ ? U
30
+ : A
31
+
32
+ export type Callback<T extends any[] = any[], R = any> = (...args: T) => R
33
+ export type OmitFirstItem<T extends any[]> = T extends [any, ...infer U]
34
+ ? U
35
+ : []
36
+ export type ErrorClass = new (...args: any[]) => Error
37
+ export type Extra = Record<string, any>
38
+ export type Async<T> = T | Promise<T>
39
+
40
+ export type ArrayMap<T extends readonly any[], K extends keyof T[number]> = {
41
+ [I in keyof T]: T[I][K]
42
+ }
43
+
44
+ export type UnionToIntersection<U> = (
45
+ U extends any
46
+ ? (k: U) => void
47
+ : never
48
+ ) extends (k: infer I) => void
49
+ ? I
50
+ : never
51
+
52
+ export type Merge<
53
+ T1 extends Record<string, any>,
54
+ T2 extends Record<string, any>,
55
+ > = {
56
+ [K in keyof T1 | keyof T2]: K extends keyof T2
57
+ ? T2[K]
58
+ : K extends keyof T1
59
+ ? T1[K]
60
+ : never
61
+ }
62
+
63
+ export type OneOf<
64
+ TypesArray extends any[],
65
+ Res = never,
66
+ AllProperties = MergeTypes<TypesArray>,
67
+ > = TypesArray extends [infer Head, ...infer Rem]
68
+ ? OneOf<Rem, Res | OnlyFirst<Head, AllProperties>, AllProperties>
69
+ : Res
70
+
71
+ type MergeTypes<TypesArray extends any[], Res = {}> = TypesArray extends [
72
+ infer Head,
73
+ ...infer Rem,
74
+ ]
75
+ ? MergeTypes<Rem, Res & Head>
76
+ : Res
77
+
78
+ type OnlyFirst<F, S> = F & { [Key in keyof Omit<S, keyof F>]?: never }
79
+
80
+ export type Pattern = RegExp | string | ((value: string) => boolean)
package/src/utils.ts ADDED
@@ -0,0 +1,174 @@
1
+ import type { Callback, Pattern } from './types.ts'
2
+
3
+ export const noopFn = () => {}
4
+
5
+ export function merge<T extends any[]>(...objects: T) {
6
+ return Object.assign({}, ...objects)
7
+ }
8
+
9
+ export function unique<T>(array: Iterable<T>): Iterable<T> {
10
+ return new Set(array).values()
11
+ }
12
+
13
+ export function defer<T extends Callback>(
14
+ cb: T,
15
+ ms = 1,
16
+ ...args: Parameters<T>
17
+ ): Promise<Awaited<ReturnType<T>>> {
18
+ return new Promise((resolve, reject) =>
19
+ setTimeout(async () => {
20
+ try {
21
+ resolve(await cb(...args))
22
+ } catch (error) {
23
+ reject(error)
24
+ }
25
+ }, ms),
26
+ )
27
+ }
28
+
29
+ export function range(count: number, start = 0) {
30
+ let current = start
31
+ return {
32
+ [Symbol.iterator]() {
33
+ return {
34
+ next() {
35
+ if (current < count) {
36
+ return { done: false, value: current++ }
37
+ } else {
38
+ return { done: true, value: current }
39
+ }
40
+ },
41
+ }
42
+ },
43
+ }
44
+ }
45
+
46
+ export function debounce(cb: Callback, delay: number) {
47
+ let timer: any
48
+ const clear = () => timer && clearTimeout(timer)
49
+ const fn = (...args: any[]) => {
50
+ clear()
51
+ timer = setTimeout(cb, delay, ...args)
52
+ }
53
+ return Object.assign(fn, { clear })
54
+ }
55
+
56
+ // TODO: Promise.withResolvers?
57
+ export interface Future<T = any> {
58
+ promise: Promise<T>
59
+ resolve: (value: T) => void
60
+ reject: (error: any) => void
61
+ }
62
+ // TODO: Promise.withResolvers?
63
+ export function createFuture<T>(): Future<T> {
64
+ let resolve: Future<T>['resolve']
65
+ let reject: Future<T>['reject']
66
+ const promise = new Promise<T>((res, rej) => {
67
+ resolve = res
68
+ reject = rej
69
+ })
70
+ // @ts-expect-error
71
+ return { resolve, reject, promise }
72
+ }
73
+
74
+ export function onAbort<T extends Callback>(
75
+ signal: AbortSignal,
76
+ cb: T,
77
+ reason?: any,
78
+ ) {
79
+ const listener = () => cb(reason ?? signal.reason)
80
+ signal.addEventListener('abort', listener, { once: true })
81
+ return () => signal.removeEventListener('abort', listener)
82
+ }
83
+
84
+ export function withTimeout(
85
+ value: Promise<any>,
86
+ timeout: number,
87
+ timeoutError: Error,
88
+ ) {
89
+ return Promise.race([
90
+ value,
91
+ new Promise((_, reject) => setTimeout(reject, timeout, timeoutError)),
92
+ ])
93
+ }
94
+
95
+ export function tryCaptureStackTrace(depth = 0) {
96
+ const traceLines = new Error().stack?.split('\n')
97
+ if (traceLines) {
98
+ for (const traceLine of traceLines) {
99
+ const trimmed = traceLine.trim()
100
+
101
+ if (trimmed.startsWith('at eval (') && trimmed.endsWith(')')) {
102
+ const trace = trimmed.slice(9, -1)
103
+ return trace
104
+ }
105
+ }
106
+ }
107
+ return undefined
108
+ }
109
+
110
+ export function isGeneratorFunction(value: any): value is GeneratorFunction {
111
+ return (
112
+ typeof value === 'function' &&
113
+ value.constructor.name === 'GeneratorFunction'
114
+ )
115
+ }
116
+
117
+ export function isAsyncGeneratorFunction(
118
+ value: any,
119
+ ): value is AsyncGeneratorFunction {
120
+ return (
121
+ typeof value === 'function' &&
122
+ value.constructor.name === 'AsyncGeneratorFunction'
123
+ )
124
+ }
125
+ export function isAsyncIterable(value: any): value is AsyncIterable<unknown> {
126
+ return value && typeof value === 'object' && Symbol.asyncIterator in value
127
+ }
128
+
129
+ export function throwError(message: string, ErrorClass = Error): never {
130
+ throw new ErrorClass(message)
131
+ }
132
+
133
+ export function once(target: EventTarget, event: string) {
134
+ return new Promise<void>((resolve) => {
135
+ target.addEventListener(event, () => resolve(), { once: true })
136
+ })
137
+ }
138
+
139
+ export function onceAborted(signal: AbortSignal) {
140
+ return once(signal, 'abort')
141
+ }
142
+
143
+ export function isAbortError(error) {
144
+ return (
145
+ (error instanceof Error &&
146
+ error.name === 'AbortError' &&
147
+ 'code' in error &&
148
+ (error.code === 20 || error.code === 'ABORT_ERR')) ||
149
+ (error instanceof Event && error.type === 'abort')
150
+ )
151
+ }
152
+
153
+ /**
154
+ * Very simple pattern matching function.
155
+ */
156
+ export function match(value: string, pattern: Pattern) {
157
+ if (typeof pattern === 'function') {
158
+ return pattern(value)
159
+ } else if (typeof pattern === 'string') {
160
+ if (pattern === '*' || pattern === '**') {
161
+ return true
162
+ } else if (pattern.at(0) === '*' && pattern.at(-1) === '*') {
163
+ return value.includes(pattern.slice(1, -1))
164
+ } else if (pattern.at(-1) === '*') {
165
+ return value.startsWith(pattern.slice(0, -1))
166
+ } else if (pattern.at(0) === '*') {
167
+ return value.endsWith(pattern.slice(1))
168
+ } else {
169
+ return value === pattern
170
+ }
171
+ } else {
172
+ return pattern.test(value)
173
+ }
174
+ }