@centralping/ergo 0.1.0-beta.1
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 +25 -0
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/http/accepts.js +69 -0
- package/http/authorization.js +65 -0
- package/http/body.js +311 -0
- package/http/cache-control.js +123 -0
- package/http/compress.js +157 -0
- package/http/cookie.js +39 -0
- package/http/cors.js +79 -0
- package/http/csrf.js +76 -0
- package/http/handler.js +74 -0
- package/http/index.js +13 -0
- package/http/json-api-query.js +53 -0
- package/http/logger.js +167 -0
- package/http/main.js +140 -0
- package/http/precondition.js +53 -0
- package/http/prefer.js +36 -0
- package/http/rate-limit.js +66 -0
- package/http/security-headers.js +62 -0
- package/http/send.js +399 -0
- package/http/timeout.js +47 -0
- package/http/url.js +47 -0
- package/http/validate.js +84 -0
- package/lib/accepts.js +49 -0
- package/lib/attach-instance.js +23 -0
- package/lib/authorization.js +187 -0
- package/lib/body/multiparse.js +173 -0
- package/lib/body/multipart/headers.js +69 -0
- package/lib/body/writer.js +73 -0
- package/lib/cookie/cookie.js +192 -0
- package/lib/cookie/index.js +14 -0
- package/lib/cookie/jar.js +106 -0
- package/lib/cookie/parse.js +101 -0
- package/lib/cors.js +191 -0
- package/lib/csrf.js +96 -0
- package/lib/from-connect.js +69 -0
- package/lib/json-api-query/index.js +25 -0
- package/lib/json-api-query/schema.json +105 -0
- package/lib/json-api-query/validate.js +56 -0
- package/lib/link.js +96 -0
- package/lib/prefer.js +52 -0
- package/lib/query.js +113 -0
- package/lib/rate-limit.js +115 -0
- package/lib/sanitize-quoted-string.js +28 -0
- package/lib/security-headers.js +125 -0
- package/lib/validate.js +80 -0
- package/lib/vary.js +40 -0
- package/package.json +158 -0
- package/types/http/accepts.d.ts +8 -0
- package/types/http/authorization.d.ts +8 -0
- package/types/http/body.d.ts +20 -0
- package/types/http/cache-control.d.ts +16 -0
- package/types/http/compress.d.ts +5 -0
- package/types/http/cookie.d.ts +2 -0
- package/types/http/cors.d.ts +9 -0
- package/types/http/csrf.d.ts +9 -0
- package/types/http/handler.d.ts +2 -0
- package/types/http/index.d.ts +1 -0
- package/types/http/json-api-query.d.ts +2 -0
- package/types/http/logger.d.ts +9 -0
- package/types/http/main.d.ts +142 -0
- package/types/http/precondition.d.ts +44 -0
- package/types/http/prefer.d.ts +2 -0
- package/types/http/rate-limit.d.ts +17 -0
- package/types/http/security-headers.d.ts +10 -0
- package/types/http/send.d.ts +8 -0
- package/types/http/timeout.d.ts +5 -0
- package/types/http/url.d.ts +2 -0
- package/types/http/validate.d.ts +6 -0
- package/types/lib/accepts.d.ts +7 -0
- package/types/lib/attach-instance.d.ts +19 -0
- package/types/lib/authorization.d.ts +6 -0
- package/types/lib/body/multiparse.d.ts +9 -0
- package/types/lib/body/multipart/headers.d.ts +2 -0
- package/types/lib/body/writer.d.ts +2 -0
- package/types/lib/cookie/cookie.d.ts +32 -0
- package/types/lib/cookie/index.d.ts +2 -0
- package/types/lib/cookie/jar.d.ts +8 -0
- package/types/lib/cookie/parse.d.ts +19 -0
- package/types/lib/cors.d.ts +9 -0
- package/types/lib/csrf.d.ts +32 -0
- package/types/lib/from-connect.d.ts +47 -0
- package/types/lib/json-api-query/index.d.ts +123 -0
- package/types/lib/json-api-query/validate.d.ts +5 -0
- package/types/lib/link.d.ts +37 -0
- package/types/lib/prefer.d.ts +36 -0
- package/types/lib/query.d.ts +6 -0
- package/types/lib/rate-limit.d.ts +76 -0
- package/types/lib/sanitize-quoted-string.d.ts +19 -0
- package/types/lib/security-headers.d.ts +24 -0
- package/types/lib/validate.d.ts +16 -0
- package/types/lib/vary.d.ts +17 -0
- package/types/utils/attempt.d.ts +2 -0
- package/types/utils/buffers/index.d.ts +2 -0
- package/types/utils/buffers/match.d.ts +10 -0
- package/types/utils/buffers/split.d.ts +10 -0
- package/types/utils/compose-with.d.ts +40 -0
- package/types/utils/compose.d.ts +83 -0
- package/types/utils/flat-array.d.ts +2 -0
- package/types/utils/get.d.ts +5 -0
- package/types/utils/http-errors.d.ts +22 -0
- package/types/utils/iterables/buffer-split.d.ts +2 -0
- package/types/utils/iterables/chain.d.ts +2 -0
- package/types/utils/iterables/exec-all.d.ts +2 -0
- package/types/utils/iterables/filter.d.ts +2 -0
- package/types/utils/iterables/for-each.d.ts +2 -0
- package/types/utils/iterables/from-stream.d.ts +2 -0
- package/types/utils/iterables/index.d.ts +10 -0
- package/types/utils/iterables/map.d.ts +2 -0
- package/types/utils/iterables/range.d.ts +24 -0
- package/types/utils/iterables/reduce.d.ts +2 -0
- package/types/utils/iterables/take.d.ts +2 -0
- package/types/utils/observables/buffer-split.d.ts +2 -0
- package/types/utils/observables/chain.d.ts +2 -0
- package/types/utils/observables/index.d.ts +4 -0
- package/types/utils/observables/map.d.ts +2 -0
- package/types/utils/observables/take.d.ts +2 -0
- package/types/utils/pick.d.ts +2 -0
- package/types/utils/set.d.ts +2 -0
- package/types/utils/streams/index.d.ts +2 -0
- package/types/utils/streams/meter.d.ts +5 -0
- package/types/utils/streams/tee.d.ts +2 -0
- package/types/utils/type.d.ts +2 -0
- package/utils/attempt.js +37 -0
- package/utils/buffers/index.js +13 -0
- package/utils/buffers/match.js +96 -0
- package/utils/buffers/split.js +55 -0
- package/utils/compose-with.js +232 -0
- package/utils/compose.js +165 -0
- package/utils/flat-array.js +24 -0
- package/utils/get.js +39 -0
- package/utils/http-errors.js +113 -0
- package/utils/iterables/buffer-split.js +117 -0
- package/utils/iterables/chain.js +32 -0
- package/utils/iterables/exec-all.js +42 -0
- package/utils/iterables/filter.js +35 -0
- package/utils/iterables/for-each.js +33 -0
- package/utils/iterables/from-stream.js +29 -0
- package/utils/iterables/index.js +21 -0
- package/utils/iterables/map.js +47 -0
- package/utils/iterables/range.js +34 -0
- package/utils/iterables/reduce.js +43 -0
- package/utils/iterables/take.js +36 -0
- package/utils/observables/buffer-split.js +109 -0
- package/utils/observables/chain.js +33 -0
- package/utils/observables/index.js +19 -0
- package/utils/observables/map.js +34 -0
- package/utils/observables/take.js +40 -0
- package/utils/pick.js +41 -0
- package/utils/set.js +38 -0
- package/utils/streams/index.js +11 -0
- package/utils/streams/meter.js +98 -0
- package/utils/streams/tee.js +84 -0
- package/utils/type.js +47 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Iterable pipeline composition utility.
|
|
3
|
+
*
|
|
4
|
+
* Pipes an initial value (source iterable) through a series of generator functions,
|
|
5
|
+
* where each generator wraps the previous one. The composition is left-to-right:
|
|
6
|
+
* the first generator receives the source, the second receives the first's output, etc.
|
|
7
|
+
*
|
|
8
|
+
* This is the core primitive for building lazy data pipelines with the iterable utilities.
|
|
9
|
+
*
|
|
10
|
+
* @module utils/iterables/chain
|
|
11
|
+
* @version 0.1.0
|
|
12
|
+
* @since 0.1.0
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* import {chain, map, filter, reduce} from 'ergo/utils/iterables';
|
|
16
|
+
*
|
|
17
|
+
* chain(
|
|
18
|
+
* [1, 2, 3, 4, 5],
|
|
19
|
+
* filter(x => x % 2 === 0),
|
|
20
|
+
* map(x => x * 10),
|
|
21
|
+
* reduce((acc, x) => acc + x, 0)
|
|
22
|
+
* ); // => 60
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {...(Iterable|function)} generators - Source iterable followed by generator transform functions
|
|
27
|
+
* @returns {*} - Final pipeline result (iterable or reduced value)
|
|
28
|
+
*/
|
|
29
|
+
export default (...generators) =>
|
|
30
|
+
generators.reduce((iterable, generator) => {
|
|
31
|
+
return generator(iterable);
|
|
32
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview RegExp exec-all generator for iterating all matches in a string.
|
|
3
|
+
*
|
|
4
|
+
* Creates a generator function that iterates all non-overlapping regex matches in a
|
|
5
|
+
* string, yielding the capture groups (not the full match) for each. Ensures the `g`
|
|
6
|
+
* flag is added to avoid infinite loops with stateful regexes.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/iterables/exec-all
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import execAll from 'ergo/utils/iterables/exec-all';
|
|
14
|
+
*
|
|
15
|
+
* const findWords = execAll(/([a-z]+)/ig);
|
|
16
|
+
* [...findWords('hello world')] // => [['hello'], ['world']]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {RegExp} re - Regular expression pattern (global flag added if missing)
|
|
21
|
+
* @returns {function} - Generator function `(str) => Generator<string[]>` yielding match arrays
|
|
22
|
+
*/
|
|
23
|
+
export default re => {
|
|
24
|
+
const localRe = new RegExp(re, [...new Set([...re.flags, 'g'])].join(''));
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} str - Input string to match against the pattern
|
|
28
|
+
*/
|
|
29
|
+
return function* (str) {
|
|
30
|
+
localRe.lastIndex = 0;
|
|
31
|
+
let match;
|
|
32
|
+
|
|
33
|
+
while ((match = localRe.exec(str)) !== null) {
|
|
34
|
+
yield match.slice(1);
|
|
35
|
+
if (match[0].length === 0) {
|
|
36
|
+
// Advance past zero-length match; use code-point width for Unicode regexes
|
|
37
|
+
const cp = str.codePointAt(localRe.lastIndex);
|
|
38
|
+
localRe.lastIndex += localRe.unicode && cp !== undefined && cp > 0xffff ? 2 : 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Iterable filter generator.
|
|
3
|
+
*
|
|
4
|
+
* Creates a generator function that yields only values from the source iterable
|
|
5
|
+
* for which the predicate returns truthy. Passes the value, index, and iterable
|
|
6
|
+
* to the predicate (same signature as `Array.prototype.filter`).
|
|
7
|
+
*
|
|
8
|
+
* @module utils/iterables/filter
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import {chain, filter} from 'ergo/utils/iterables';
|
|
14
|
+
*
|
|
15
|
+
* [...chain([1, 2, 3, 4], filter(x => x % 2 === 0))] // => [2, 4]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {function} predicate - Filter predicate `(value, index, iterable) => boolean`
|
|
20
|
+
* @returns {function} - Generator function `(iterable) => Generator` yielding matched values
|
|
21
|
+
*/
|
|
22
|
+
export default predicate => {
|
|
23
|
+
/**
|
|
24
|
+
* @param {Iterable} iterable - Source iterable to filter
|
|
25
|
+
*/
|
|
26
|
+
return function* (iterable) {
|
|
27
|
+
let i = 0;
|
|
28
|
+
|
|
29
|
+
for (const val of iterable) {
|
|
30
|
+
if (predicate(val, i++, iterable)) {
|
|
31
|
+
yield val;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Iterable forEach generator (side-effect tap).
|
|
3
|
+
*
|
|
4
|
+
* Creates a generator that calls a callback for each value as a side effect,
|
|
5
|
+
* then passes the value through unchanged. Equivalent to `Array.prototype.forEach`
|
|
6
|
+
* for iterables, but composable in a `chain()` pipeline.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/iterables/for-each
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
* @requires ./map.js
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* import {chain, forEach, reduce} from 'ergo/utils/iterables';
|
|
15
|
+
*
|
|
16
|
+
* chain(
|
|
17
|
+
* [1, 2, 3],
|
|
18
|
+
* forEach(x => console.log('processing', x)),
|
|
19
|
+
* reduce((acc, x) => acc + x, 0)
|
|
20
|
+
* ); // logs each value, returns 6
|
|
21
|
+
*/
|
|
22
|
+
import map from './map.js';
|
|
23
|
+
/**
|
|
24
|
+
* Side-effect callback for each iterable value.
|
|
25
|
+
*
|
|
26
|
+
* @param {function} cb - Called with `(value, index, iterable)` for each item
|
|
27
|
+
* @returns {function} - Generator function that yields each value unchanged
|
|
28
|
+
*/
|
|
29
|
+
export default cb =>
|
|
30
|
+
map((val, ...args) => {
|
|
31
|
+
cb(val, ...args);
|
|
32
|
+
return val;
|
|
33
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Converts a Node.js Readable stream to an async iterable.
|
|
3
|
+
*
|
|
4
|
+
* All Node.js Readable streams support `Symbol.asyncIterator` natively since
|
|
5
|
+
* Node.js 12. This module simply returns the stream as-is, providing a stable
|
|
6
|
+
* interface for code that receives streams and needs to iterate them.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/iterables/from-stream
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import fromStream from 'ergo/utils/iterables/from-stream';
|
|
14
|
+
* import {Readable} from 'node:stream';
|
|
15
|
+
*
|
|
16
|
+
* const readable = Readable.from(['hello', ' ', 'world']);
|
|
17
|
+
* for await (const chunk of fromStream(readable)) {
|
|
18
|
+
* process.stdout.write(chunk.toString());
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns a Node.js Readable stream as an async iterable.
|
|
24
|
+
* All Readable streams support Symbol.asyncIterator since Node.js 12.
|
|
25
|
+
*
|
|
26
|
+
* @param {import('node:stream').Readable} stream - Readable stream to iterate
|
|
27
|
+
* @returns {AsyncIterable} - The stream itself, which natively supports async iteration
|
|
28
|
+
*/
|
|
29
|
+
export default stream => stream;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Async iterables utilities barrel export.
|
|
3
|
+
*
|
|
4
|
+
* Exports composable generator-based utilities for building lazy data pipelines.
|
|
5
|
+
* Designed for use with `chain()` to compose pipelines:
|
|
6
|
+
* `chain(source, bufferSplit(sep), map(fn), reduce(fn, init))`
|
|
7
|
+
*
|
|
8
|
+
* @module utils/iterables
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
*/
|
|
12
|
+
export {default as bufferSplit} from './buffer-split.js';
|
|
13
|
+
export {default as chain} from './chain.js';
|
|
14
|
+
export {default as execAll} from './exec-all.js';
|
|
15
|
+
export {default as filter} from './filter.js';
|
|
16
|
+
export {default as forEach} from './for-each.js';
|
|
17
|
+
export {default as fromStream} from './from-stream.js';
|
|
18
|
+
export {default as map} from './map.js';
|
|
19
|
+
export {default as range} from './range.js';
|
|
20
|
+
export {default as reduce} from './reduce.js';
|
|
21
|
+
export {default as take} from './take.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Iterable map generator.
|
|
3
|
+
*
|
|
4
|
+
* Creates a generator function that transforms each value from the source iterable
|
|
5
|
+
* using a transform function. Automatically detects async transforms and produces
|
|
6
|
+
* async generators; sync transforms produce sync generators.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/iterables/map
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
* @requires ../type.js
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* import {chain, map} from 'ergo/utils/iterables';
|
|
15
|
+
*
|
|
16
|
+
* [...chain([1, 2, 3], map(x => x * 2))] // => [2, 4, 6]
|
|
17
|
+
*
|
|
18
|
+
* // Async transform
|
|
19
|
+
* for await (const v of chain([1, 2, 3], map(async x => await fetchSomething(x)))) { ... }
|
|
20
|
+
*/
|
|
21
|
+
import type from '../type.js';
|
|
22
|
+
/**
|
|
23
|
+
* Creates a map generator function.
|
|
24
|
+
*
|
|
25
|
+
* @param {function} transform - Sync or async transform `(value, index, iterable) => newValue`
|
|
26
|
+
* @returns {function} - Generator function `(iterable) => Generator|AsyncGenerator`
|
|
27
|
+
*/
|
|
28
|
+
export default transform => {
|
|
29
|
+
/**
|
|
30
|
+
* @param {Iterable} iterable - Source iterable to transform
|
|
31
|
+
*/
|
|
32
|
+
return type(transform).startsWith('Async')
|
|
33
|
+
? async function* (iterable) {
|
|
34
|
+
let i = 0;
|
|
35
|
+
|
|
36
|
+
for await (const val of iterable) {
|
|
37
|
+
yield await transform(val, i++, iterable);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
: function* (iterable) {
|
|
41
|
+
let i = 0;
|
|
42
|
+
|
|
43
|
+
for (const val of iterable) {
|
|
44
|
+
yield transform(val, i++, iterable);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Integer range generator.
|
|
3
|
+
*
|
|
4
|
+
* Produces a sequence of integers from `start` (inclusive) to `stop` (exclusive)
|
|
5
|
+
* stepping by `step`. Mirrors Python's `range()` behaviour.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/iterables/range
|
|
8
|
+
* @version 0.1.0
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* import range from 'ergo/utils/iterables/range';
|
|
13
|
+
*
|
|
14
|
+
* [...range(5)] // => [0, 1, 2, 3, 4]
|
|
15
|
+
* [...range(1, 5)] // => [1, 2, 3, 4]
|
|
16
|
+
* [...range(0, 10, 2)] // => [0, 2, 4, 6, 8]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {number} start - Start value (or stop if only one arg)
|
|
21
|
+
* @param {number} [stop] - Exclusive upper bound
|
|
22
|
+
* @param {number} [step=1] - Step increment
|
|
23
|
+
* @returns {Generator<number>} - Sequence of integers
|
|
24
|
+
*/
|
|
25
|
+
/* eslint-disable-next-line no-param-reassign */
|
|
26
|
+
export default function* (start, stop = start + (start = 0), step = 1) {
|
|
27
|
+
if (step === 0) return;
|
|
28
|
+
|
|
29
|
+
const length = (stop - start) / step;
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < length; i++) {
|
|
32
|
+
yield i * step + start;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Iterable reduce utility.
|
|
3
|
+
*
|
|
4
|
+
* Creates a function that eagerly consumes an iterable and reduces it to a single value
|
|
5
|
+
* using a reducer function. When no `initialValue` is provided and the iterable has at
|
|
6
|
+
* least one element, the first element is used as the initial accumulator.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/iterables/reduce
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import {chain, reduce} from 'ergo/utils/iterables';
|
|
14
|
+
*
|
|
15
|
+
* chain([1, 2, 3, 4], reduce((sum, x) => sum + x, 0)) // => 10
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {function} reducer - Reducer `(accumulator, value, index, iterable) => newAccumulator`
|
|
20
|
+
* @param {*} [initialValue] - Initial accumulator value; first element used if omitted
|
|
21
|
+
* @returns {function} - Function `(iterable) => accumulated value`
|
|
22
|
+
*/
|
|
23
|
+
export default (reducer, initialValue) => {
|
|
24
|
+
/**
|
|
25
|
+
* @param {Iterable} iterable - Source iterable to reduce over
|
|
26
|
+
* @returns {*} - Accumulated value from the reducer function
|
|
27
|
+
*/
|
|
28
|
+
return function (iterable) {
|
|
29
|
+
let i = 0;
|
|
30
|
+
let accumulator = initialValue;
|
|
31
|
+
|
|
32
|
+
for (const val of iterable) {
|
|
33
|
+
if (i === 0 && accumulator === undefined) {
|
|
34
|
+
accumulator = val;
|
|
35
|
+
} else {
|
|
36
|
+
accumulator = reducer(accumulator, val, i, iterable);
|
|
37
|
+
}
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return accumulator;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Iterable take generator.
|
|
3
|
+
*
|
|
4
|
+
* Creates a generator function that yields at most `n` values from the source iterable,
|
|
5
|
+
* then returns (stops iteration).
|
|
6
|
+
*
|
|
7
|
+
* @module utils/iterables/take
|
|
8
|
+
* @version 0.1.0
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* import {chain, take} from 'ergo/utils/iterables';
|
|
13
|
+
*
|
|
14
|
+
* [...chain([1, 2, 3, 4, 5], take(3))] // => [1, 2, 3]
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {number} [n=Infinity] - Maximum number of values to yield
|
|
19
|
+
* @returns {function} - Generator function `(iterable) => Generator` yielding at most n values
|
|
20
|
+
*/
|
|
21
|
+
export default (n = Infinity) => {
|
|
22
|
+
/**
|
|
23
|
+
* @param {Iterable} iterable - Source iterable to take elements from
|
|
24
|
+
*/
|
|
25
|
+
return function* (iterable) {
|
|
26
|
+
let i = 0;
|
|
27
|
+
|
|
28
|
+
for (const val of iterable) {
|
|
29
|
+
if (i++ >= n) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
yield val;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Observable Buffer split async generator.
|
|
3
|
+
*
|
|
4
|
+
* Push-model variant of `utils/iterables/buffer-split`. Accepts chunks via generator
|
|
5
|
+
* `.next(chunk)` calls and routes split buffers to a downstream generator. Designed for
|
|
6
|
+
* integration with observable-style pipelines built with `utils/observables/chain`.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/observables/buffer-split
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
* @requires ../buffers/split.js
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Used internally by the multipart body parser's streaming pipeline
|
|
15
|
+
*/
|
|
16
|
+
import split from '../buffers/split.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {import('node:buffer').Buffer|string} [separator=Buffer.from('')] - Byte pattern to split on
|
|
20
|
+
* @param {number} [limit=Infinity] - Maximum number of parts to yield
|
|
21
|
+
* @returns {function} - Async generator function `(generator) => AsyncGenerator<[number, import('node:buffer').Buffer]>`
|
|
22
|
+
*/
|
|
23
|
+
export default (separator = Buffer.from(''), limit = Infinity) => {
|
|
24
|
+
const sepBuffer = Buffer.isBuffer(separator) ? separator : Buffer.from(separator);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {Generator} generator - Downstream async generator receiving split buffer segments via `.next()`
|
|
28
|
+
*/
|
|
29
|
+
return async function* (generator) {
|
|
30
|
+
let partialCarryover = Buffer.from('');
|
|
31
|
+
let partialCarryoverIndexInc = false;
|
|
32
|
+
let index = 0;
|
|
33
|
+
let value;
|
|
34
|
+
let done;
|
|
35
|
+
let err;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
let buffers;
|
|
39
|
+
let lookup;
|
|
40
|
+
let partial;
|
|
41
|
+
|
|
42
|
+
while (index < limit) {
|
|
43
|
+
let chunk = yield value;
|
|
44
|
+
|
|
45
|
+
// Append the previous iteration's carryover from a partial match
|
|
46
|
+
// for the separator.
|
|
47
|
+
const pCoLen = partialCarryover.length;
|
|
48
|
+
|
|
49
|
+
if (pCoLen > 0) {
|
|
50
|
+
chunk = Buffer.concat([partialCarryover, chunk], pCoLen + chunk.length);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const incStart = partialCarryoverIndexInc && partial ? -1 : 0;
|
|
54
|
+
|
|
55
|
+
({buffers, lookup, partial} = split(chunk, sepBuffer, {limit, lookup, partial}));
|
|
56
|
+
|
|
57
|
+
partialCarryoverIndexInc = buffers.length > 1;
|
|
58
|
+
|
|
59
|
+
if (partial > 0) {
|
|
60
|
+
if (partial === buffers.at(-1).length) {
|
|
61
|
+
partialCarryover = buffers.pop();
|
|
62
|
+
} else {
|
|
63
|
+
const last = buffers.at(-1);
|
|
64
|
+
partialCarryover = last.slice(-partial);
|
|
65
|
+
buffers[buffers.length - 1] = last.slice(0, -partial);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
partialCarryover = Buffer.from('');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < buffers.length; i++) {
|
|
72
|
+
if (i > incStart) {
|
|
73
|
+
index++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (index >= limit) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const buffer = buffers[i];
|
|
81
|
+
({value, done} = await generator.next([index, buffer]));
|
|
82
|
+
|
|
83
|
+
if (done) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
err = e;
|
|
90
|
+
throw e;
|
|
91
|
+
} finally {
|
|
92
|
+
if (!err) {
|
|
93
|
+
if (partialCarryoverIndexInc) {
|
|
94
|
+
index++;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Push the carryover if a buffer ends with a partial match
|
|
98
|
+
if (partialCarryover.length && index < limit) {
|
|
99
|
+
({value} = await generator.next([index, partialCarryover]));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
({value = value} = await generator.return());
|
|
103
|
+
|
|
104
|
+
/* eslint-disable-next-line no-unsafe-finally */
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Observable pipeline composition utility.
|
|
3
|
+
*
|
|
4
|
+
* Push-model variant of `utils/iterables/chain`. Chains observable generators in
|
|
5
|
+
* right-to-left order (each generator wraps the previous, pushing data downstream).
|
|
6
|
+
* The last generator in the array is the sink.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/observables/chain
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
* @requires ../type.js
|
|
12
|
+
*/
|
|
13
|
+
import type from '../type.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {...(function|Generator)} generators - Generator functions or instances; last is the sink
|
|
17
|
+
* @returns {Generator} - Chained generator pipeline composed right-to-left
|
|
18
|
+
*/
|
|
19
|
+
export default (...generators) => {
|
|
20
|
+
let lastIterator = generators.pop();
|
|
21
|
+
|
|
22
|
+
if (type(lastIterator).endsWith('GeneratorFunction')) {
|
|
23
|
+
lastIterator = lastIterator();
|
|
24
|
+
lastIterator.next();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return generators.reduceRight((prevIterator, generator) => {
|
|
28
|
+
const nextIterator = generator(prevIterator);
|
|
29
|
+
nextIterator.next();
|
|
30
|
+
|
|
31
|
+
return nextIterator;
|
|
32
|
+
}, lastIterator);
|
|
33
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Observable (push-based) utilities barrel export.
|
|
3
|
+
*
|
|
4
|
+
* Provides generator-based push-model primitives for event-driven pipelines where
|
|
5
|
+
* data is pushed in (via `.next(value)`) rather than pulled. Used for streaming
|
|
6
|
+
* body parsing and other real-time data flows.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/observables
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
* @requires ./buffer-split.js
|
|
12
|
+
* @requires ./chain.js
|
|
13
|
+
* @requires ./map.js
|
|
14
|
+
* @requires ./take.js
|
|
15
|
+
*/
|
|
16
|
+
export {default as bufferSplit} from './buffer-split.js';
|
|
17
|
+
export {default as chain} from './chain.js';
|
|
18
|
+
export {default as map} from './map.js';
|
|
19
|
+
export {default as take} from './take.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Observable map generator.
|
|
3
|
+
*
|
|
4
|
+
* Push-model transform generator. Each value pushed in via `.next(value)` is
|
|
5
|
+
* transformed and forwarded downstream via the iterator's `.next()`.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/observables/map
|
|
8
|
+
* @version 0.1.0
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // Used in observable chain pipelines built with utils/observables/chain
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {function} transform - Transform `(value, index) => newValue`
|
|
17
|
+
* @returns {function} - Generator function `(iterator) => Generator` that maps values
|
|
18
|
+
*/
|
|
19
|
+
export default transform => {
|
|
20
|
+
/**
|
|
21
|
+
* @param {Iterator} iterator - Source iterator to transform
|
|
22
|
+
*/
|
|
23
|
+
return function* (iterator) {
|
|
24
|
+
let i = 0;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
while (true) {
|
|
28
|
+
iterator.next(transform(yield, i++));
|
|
29
|
+
}
|
|
30
|
+
} finally {
|
|
31
|
+
iterator.return();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Observable take generator.
|
|
3
|
+
*
|
|
4
|
+
* Push-model variant of `utils/iterables/take`. Passes at most `n` values to the
|
|
5
|
+
* downstream iterator, then calls `.return()` to signal completion.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/observables/take
|
|
8
|
+
* @version 0.1.0
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // Used in observable chain pipelines built with utils/observables/chain
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {number} [n=Infinity] - Maximum number of values to pass through
|
|
17
|
+
* @returns {function} - Generator function `(iterator) => Generator` that yields at most n values
|
|
18
|
+
*/
|
|
19
|
+
export default (n = Infinity) => {
|
|
20
|
+
/**
|
|
21
|
+
* @param {Iterator} iterator - Downstream generator receiving forwarded values via `.next()`
|
|
22
|
+
*/
|
|
23
|
+
return function* (iterator) {
|
|
24
|
+
let i = 0;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
while (true) {
|
|
28
|
+
const val = yield;
|
|
29
|
+
|
|
30
|
+
if (i++ >= n) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
iterator.next(val);
|
|
35
|
+
}
|
|
36
|
+
} finally {
|
|
37
|
+
iterator.return();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
};
|
package/utils/pick.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Object property picker with dot-notation and rename support.
|
|
3
|
+
*
|
|
4
|
+
* Extracts specified paths from an object into a new object. Supports:
|
|
5
|
+
* - String paths: `pick(obj, 'a.b')` extracts `obj.a.b` as `result.a.b`
|
|
6
|
+
* - Rename tuples: `pick(obj, ['a.b', 'c'])` extracts `obj.a.b` as `result.c`
|
|
7
|
+
*
|
|
8
|
+
* @module utils/pick
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
* @requires ./get.js
|
|
12
|
+
* @requires ./set.js
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* import pick from 'ergo/utils/pick';
|
|
16
|
+
*
|
|
17
|
+
* pick({a: {b: 1}, c: 2}, 'a.b', 'c')
|
|
18
|
+
* // => {a: {b: 1}, c: 2}
|
|
19
|
+
*
|
|
20
|
+
* pick({user: {name: 'Alice'}}, ['user.name', 'name'])
|
|
21
|
+
* // => {name: 'Alice'}
|
|
22
|
+
*/
|
|
23
|
+
import get from './get.js';
|
|
24
|
+
import set from './set.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Picks values from an object at the specified dot-notation paths.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} [obj={}] - Source object
|
|
30
|
+
* @param {...(string|[string, string])} paths - Paths to extract; each may be a string
|
|
31
|
+
* or a `[readPath, setPath]` rename tuple
|
|
32
|
+
* @returns {object} - New object with the extracted (and optionally renamed) values
|
|
33
|
+
*/
|
|
34
|
+
export default (obj = {}, ...paths) =>
|
|
35
|
+
paths.flat().reduce((o, path) => {
|
|
36
|
+
const [readPath, setPath = path] = Array.isArray(path) ? path : [path];
|
|
37
|
+
|
|
38
|
+
set(o, setPath, get(obj, readPath));
|
|
39
|
+
|
|
40
|
+
return o;
|
|
41
|
+
}, Object.create(null));
|
package/utils/set.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Deep property setter with dot-notation path support.
|
|
3
|
+
*
|
|
4
|
+
* Sets a value at a dot-delimited path in an object, creating intermediate objects
|
|
5
|
+
* or arrays as needed. Uses `Object.hasOwn()` to check for existing nodes.
|
|
6
|
+
* Array nodes are created when the next path segment is a valid integer string.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/set
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import set from 'ergo/utils/set';
|
|
14
|
+
*
|
|
15
|
+
* const obj = {};
|
|
16
|
+
* set(obj, 'a.b.c', 42); // obj => {a: {b: {c: 42}}}
|
|
17
|
+
* set(obj, 'tags.0', 'x'); // obj.tags => ['x']
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {object} obj - Target object
|
|
22
|
+
* @param {string} [path=''] - Dot-delimited property path
|
|
23
|
+
* @param {*} val - Value to assign
|
|
24
|
+
* @returns {*} - The assigned value
|
|
25
|
+
*/
|
|
26
|
+
export default (obj, path = '', val) => {
|
|
27
|
+
const subPaths = path.split('.');
|
|
28
|
+
|
|
29
|
+
return (subPaths
|
|
30
|
+
.slice(0, -1)
|
|
31
|
+
.reduce(
|
|
32
|
+
(o, p, i) =>
|
|
33
|
+
Object.hasOwn(o, p)
|
|
34
|
+
? o[p]
|
|
35
|
+
: (o[p] = Number.isNaN(Number(subPaths[i + 1])) ? Object.create(null) : []),
|
|
36
|
+
obj
|
|
37
|
+
)[subPaths.at(-1)] = val);
|
|
38
|
+
};
|