@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.
Files changed (155) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +21 -0
  3. package/README.md +139 -0
  4. package/http/accepts.js +69 -0
  5. package/http/authorization.js +65 -0
  6. package/http/body.js +311 -0
  7. package/http/cache-control.js +123 -0
  8. package/http/compress.js +157 -0
  9. package/http/cookie.js +39 -0
  10. package/http/cors.js +79 -0
  11. package/http/csrf.js +76 -0
  12. package/http/handler.js +74 -0
  13. package/http/index.js +13 -0
  14. package/http/json-api-query.js +53 -0
  15. package/http/logger.js +167 -0
  16. package/http/main.js +140 -0
  17. package/http/precondition.js +53 -0
  18. package/http/prefer.js +36 -0
  19. package/http/rate-limit.js +66 -0
  20. package/http/security-headers.js +62 -0
  21. package/http/send.js +399 -0
  22. package/http/timeout.js +47 -0
  23. package/http/url.js +47 -0
  24. package/http/validate.js +84 -0
  25. package/lib/accepts.js +49 -0
  26. package/lib/attach-instance.js +23 -0
  27. package/lib/authorization.js +187 -0
  28. package/lib/body/multiparse.js +173 -0
  29. package/lib/body/multipart/headers.js +69 -0
  30. package/lib/body/writer.js +73 -0
  31. package/lib/cookie/cookie.js +192 -0
  32. package/lib/cookie/index.js +14 -0
  33. package/lib/cookie/jar.js +106 -0
  34. package/lib/cookie/parse.js +101 -0
  35. package/lib/cors.js +191 -0
  36. package/lib/csrf.js +96 -0
  37. package/lib/from-connect.js +69 -0
  38. package/lib/json-api-query/index.js +25 -0
  39. package/lib/json-api-query/schema.json +105 -0
  40. package/lib/json-api-query/validate.js +56 -0
  41. package/lib/link.js +96 -0
  42. package/lib/prefer.js +52 -0
  43. package/lib/query.js +113 -0
  44. package/lib/rate-limit.js +115 -0
  45. package/lib/sanitize-quoted-string.js +28 -0
  46. package/lib/security-headers.js +125 -0
  47. package/lib/validate.js +80 -0
  48. package/lib/vary.js +40 -0
  49. package/package.json +158 -0
  50. package/types/http/accepts.d.ts +8 -0
  51. package/types/http/authorization.d.ts +8 -0
  52. package/types/http/body.d.ts +20 -0
  53. package/types/http/cache-control.d.ts +16 -0
  54. package/types/http/compress.d.ts +5 -0
  55. package/types/http/cookie.d.ts +2 -0
  56. package/types/http/cors.d.ts +9 -0
  57. package/types/http/csrf.d.ts +9 -0
  58. package/types/http/handler.d.ts +2 -0
  59. package/types/http/index.d.ts +1 -0
  60. package/types/http/json-api-query.d.ts +2 -0
  61. package/types/http/logger.d.ts +9 -0
  62. package/types/http/main.d.ts +142 -0
  63. package/types/http/precondition.d.ts +44 -0
  64. package/types/http/prefer.d.ts +2 -0
  65. package/types/http/rate-limit.d.ts +17 -0
  66. package/types/http/security-headers.d.ts +10 -0
  67. package/types/http/send.d.ts +8 -0
  68. package/types/http/timeout.d.ts +5 -0
  69. package/types/http/url.d.ts +2 -0
  70. package/types/http/validate.d.ts +6 -0
  71. package/types/lib/accepts.d.ts +7 -0
  72. package/types/lib/attach-instance.d.ts +19 -0
  73. package/types/lib/authorization.d.ts +6 -0
  74. package/types/lib/body/multiparse.d.ts +9 -0
  75. package/types/lib/body/multipart/headers.d.ts +2 -0
  76. package/types/lib/body/writer.d.ts +2 -0
  77. package/types/lib/cookie/cookie.d.ts +32 -0
  78. package/types/lib/cookie/index.d.ts +2 -0
  79. package/types/lib/cookie/jar.d.ts +8 -0
  80. package/types/lib/cookie/parse.d.ts +19 -0
  81. package/types/lib/cors.d.ts +9 -0
  82. package/types/lib/csrf.d.ts +32 -0
  83. package/types/lib/from-connect.d.ts +47 -0
  84. package/types/lib/json-api-query/index.d.ts +123 -0
  85. package/types/lib/json-api-query/validate.d.ts +5 -0
  86. package/types/lib/link.d.ts +37 -0
  87. package/types/lib/prefer.d.ts +36 -0
  88. package/types/lib/query.d.ts +6 -0
  89. package/types/lib/rate-limit.d.ts +76 -0
  90. package/types/lib/sanitize-quoted-string.d.ts +19 -0
  91. package/types/lib/security-headers.d.ts +24 -0
  92. package/types/lib/validate.d.ts +16 -0
  93. package/types/lib/vary.d.ts +17 -0
  94. package/types/utils/attempt.d.ts +2 -0
  95. package/types/utils/buffers/index.d.ts +2 -0
  96. package/types/utils/buffers/match.d.ts +10 -0
  97. package/types/utils/buffers/split.d.ts +10 -0
  98. package/types/utils/compose-with.d.ts +40 -0
  99. package/types/utils/compose.d.ts +83 -0
  100. package/types/utils/flat-array.d.ts +2 -0
  101. package/types/utils/get.d.ts +5 -0
  102. package/types/utils/http-errors.d.ts +22 -0
  103. package/types/utils/iterables/buffer-split.d.ts +2 -0
  104. package/types/utils/iterables/chain.d.ts +2 -0
  105. package/types/utils/iterables/exec-all.d.ts +2 -0
  106. package/types/utils/iterables/filter.d.ts +2 -0
  107. package/types/utils/iterables/for-each.d.ts +2 -0
  108. package/types/utils/iterables/from-stream.d.ts +2 -0
  109. package/types/utils/iterables/index.d.ts +10 -0
  110. package/types/utils/iterables/map.d.ts +2 -0
  111. package/types/utils/iterables/range.d.ts +24 -0
  112. package/types/utils/iterables/reduce.d.ts +2 -0
  113. package/types/utils/iterables/take.d.ts +2 -0
  114. package/types/utils/observables/buffer-split.d.ts +2 -0
  115. package/types/utils/observables/chain.d.ts +2 -0
  116. package/types/utils/observables/index.d.ts +4 -0
  117. package/types/utils/observables/map.d.ts +2 -0
  118. package/types/utils/observables/take.d.ts +2 -0
  119. package/types/utils/pick.d.ts +2 -0
  120. package/types/utils/set.d.ts +2 -0
  121. package/types/utils/streams/index.d.ts +2 -0
  122. package/types/utils/streams/meter.d.ts +5 -0
  123. package/types/utils/streams/tee.d.ts +2 -0
  124. package/types/utils/type.d.ts +2 -0
  125. package/utils/attempt.js +37 -0
  126. package/utils/buffers/index.js +13 -0
  127. package/utils/buffers/match.js +96 -0
  128. package/utils/buffers/split.js +55 -0
  129. package/utils/compose-with.js +232 -0
  130. package/utils/compose.js +165 -0
  131. package/utils/flat-array.js +24 -0
  132. package/utils/get.js +39 -0
  133. package/utils/http-errors.js +113 -0
  134. package/utils/iterables/buffer-split.js +117 -0
  135. package/utils/iterables/chain.js +32 -0
  136. package/utils/iterables/exec-all.js +42 -0
  137. package/utils/iterables/filter.js +35 -0
  138. package/utils/iterables/for-each.js +33 -0
  139. package/utils/iterables/from-stream.js +29 -0
  140. package/utils/iterables/index.js +21 -0
  141. package/utils/iterables/map.js +47 -0
  142. package/utils/iterables/range.js +34 -0
  143. package/utils/iterables/reduce.js +43 -0
  144. package/utils/iterables/take.js +36 -0
  145. package/utils/observables/buffer-split.js +109 -0
  146. package/utils/observables/chain.js +33 -0
  147. package/utils/observables/index.js +19 -0
  148. package/utils/observables/map.js +34 -0
  149. package/utils/observables/take.js +40 -0
  150. package/utils/pick.js +41 -0
  151. package/utils/set.js +38 -0
  152. package/utils/streams/index.js +11 -0
  153. package/utils/streams/meter.js +98 -0
  154. package/utils/streams/tee.js +84 -0
  155. 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
+ };