@mantiq/helpers 0.5.22 → 0.5.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantiq/helpers",
3
- "version": "0.5.22",
3
+ "version": "0.5.23",
4
4
  "description": "Str, Arr, Num, Collection utilities",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/Collection.ts CHANGED
@@ -547,9 +547,23 @@ export class LazyCollection<T> implements Iterable<T> {
547
547
  })
548
548
  }
549
549
 
550
- /** Take the first N items */
550
+ /** Take the first N items (positive) or last N items (negative) */
551
551
  take(count: number): LazyCollection<T> {
552
552
  const source = this.source
553
+ if (count < 0) {
554
+ // Negative: take the last |count| items — requires buffering
555
+ const absCount = -count
556
+ return new LazyCollection({
557
+ *[Symbol.iterator]() {
558
+ const buffer: T[] = []
559
+ for (const item of source) {
560
+ buffer.push(item)
561
+ if (buffer.length > absCount) buffer.shift()
562
+ }
563
+ yield* buffer
564
+ },
565
+ })
566
+ }
553
567
  return new LazyCollection({
554
568
  *[Symbol.iterator]() {
555
569
  let taken = 0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Named parallel task runner.
3
+ *
4
+ * Unlike the `parallel()` function (which takes an array of thunks),
5
+ * `Parallel.run()` accepts a named record so results are strongly typed
6
+ * by key — no index juggling.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const { users, posts } = await Parallel.run({
11
+ * users: () => db.query('SELECT * FROM users'),
12
+ * posts: () => db.query('SELECT * FROM posts'),
13
+ * })
14
+ * ```
15
+ *
16
+ * `Parallel.map()` provides a concurrent-limited map over arrays:
17
+ * ```ts
18
+ * const thumbnails = await Parallel.map(images, (img) => resize(img), 5)
19
+ * ```
20
+ */
21
+ export class Parallel {
22
+ /**
23
+ * Run named async tasks concurrently, returning a typed record of results.
24
+ */
25
+ static async run<T extends Record<string, () => Promise<any>>>(
26
+ tasks: T,
27
+ ): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>> }> {
28
+ const entries = Object.entries(tasks)
29
+ const results = await Promise.all(entries.map(([, fn]) => fn()))
30
+ return Object.fromEntries(entries.map(([key], i) => [key, results[i]])) as any
31
+ }
32
+
33
+ /**
34
+ * Map over items concurrently with an optional concurrency limit.
35
+ *
36
+ * @param items - Array of items to process
37
+ * @param fn - Async function to apply to each item
38
+ * @param concurrency - Maximum number of concurrent tasks (default: Infinity)
39
+ */
40
+ static async map<T, R>(
41
+ items: T[],
42
+ fn: (item: T, index: number) => Promise<R>,
43
+ concurrency = Infinity,
44
+ ): Promise<R[]> {
45
+ if (items.length === 0) return []
46
+
47
+ if (concurrency >= items.length) {
48
+ return Promise.all(items.map((item, index) => fn(item, index)))
49
+ }
50
+
51
+ const results: R[] = new Array(items.length)
52
+ let nextIndex = 0
53
+
54
+ async function runNext(): Promise<void> {
55
+ while (nextIndex < items.length) {
56
+ const idx = nextIndex++
57
+ results[idx] = await fn(items[idx]!, idx)
58
+ }
59
+ }
60
+
61
+ const workers = Array.from(
62
+ { length: Math.min(concurrency, items.length) },
63
+ () => runNext(),
64
+ )
65
+
66
+ await Promise.all(workers)
67
+ return results
68
+ }
69
+ }
package/src/index.ts CHANGED
@@ -19,6 +19,9 @@ export type { HttpResponse, HttpError, HttpMiddleware, RetryConfig } from './Htt
19
19
  export { HttpFake } from './HttpFake.ts'
20
20
  export type { StubResponse, StubHandler } from './HttpFake.ts'
21
21
 
22
+ // ── Async (class-based) ─────────────────────────────────────────
23
+ export { Parallel } from './async/Parallel.ts'
24
+
22
25
  // ── Async utilities ──────────────────────────────────────────────
23
26
  export {
24
27
  parseDuration,