@zingage/scheduling 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,92 @@
1
- # Temporary Holding Version
1
+ # @zingage/scheduling
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Cooperative collection helpers for synchronous work and generator callbacks in Node.js. Each helper defaults to a new independent budget with a 4 ms processing allowance.
4
+
5
+ ```ts
6
+ import {
7
+ TimeSliceBudget,
8
+ timeSlicedForEach,
9
+ timeSlicedMap,
10
+ } from "@zingage/scheduling";
11
+
12
+ // Independent calls do not consume each other's budget.
13
+ await Promise.all([
14
+ timeSlicedMap(firstItems, transform),
15
+ timeSlicedMap(secondItems, transform),
16
+ ]);
17
+
18
+ // Connected calls share the remaining allowance explicitly.
19
+ const budget = new TimeSliceBudget();
20
+ await Promise.all([
21
+ timeSlicedMap(firstItems, transform, budget),
22
+ timeSlicedForEach(secondItems, processItem, budget),
23
+ ]);
24
+ ```
25
+
26
+ ## Budget semantics
27
+
28
+ The package measures elapsed time around synchronous callbacks and result accumulation. It subtracts that measured time from the budget. Waiting at a yield and unrelated work between measured steps do not consume the budget.
29
+
30
+ If A uses 1 ms and finishes, connected B gets the remaining 3 ms. If A exhausts the budget, A replenishes it before yielding. B can then use the new 4 ms while A waits. A does not replenish the budget when it resumes. Unrelated C uses a separate budget and cannot consume A/B's allowance.
31
+
32
+ One callback or iterator step cannot be interrupted. Nested helpers started inside a synchronous callback cannot interrupt that containing callback. Their work counts once toward a shared budget. Garbage collection and operating-system pauses inside a measured step count as elapsed processing time. This is not CPU-time accounting or a hard event-loop delay bound. Work outside the helpers does not count. Ordinary callbacks must be synchronous. `timeSlicedMap` and `timeSlicedForEach` also accept generator callbacks. They do not accept async functions.
33
+
34
+ The constructor is `new TimeSliceBudget(sliceMs = DEFAULT_TIME_SLICE_MS, now = performance.now, yieldToEventLoop = setImmediate)`. `sliceMs` must be positive and finite. `now` must be a monotonic clock in milliseconds. The budget binds the clock to `performance` because Node's default clock requires that receiver. The default yield function is Node's promise-based `setImmediate`. A custom yield function must let the event loop advance; `Promise.resolve()` does not provide that behavior. Optionally pass a budget directly as each helper's last argument to share its allowance.
35
+
36
+ ## Helpers
37
+
38
+ | Helper | Result |
39
+ | -------------------------- | ---------------------------------------------------------------------------- |
40
+ | `timeSlicedForEach` | Runs callbacks in input order; returns `Promise<void>`. |
41
+ | `timeSlicedMap` | Returns transformed values in input order. |
42
+ | `timeSlicedFlatMap` | Flattens each callback's list incrementally, including large lists. |
43
+ | `timeSlicedFilter` | Returns matching items in input order. |
44
+ | `timeSlicedGroupBy` | Returns a `Map` with first-seen key order and input order within each group. |
45
+ | `timeSlicedSort` | Returns a new stable sorted array with a synchronous comparator. |
46
+ | `timeSlicedMapFromEntries` | Builds a `Map`; the last value wins for repeated keys. |
47
+
48
+ All helpers accept synchronous iterables. Callbacks receive the item and its zero-based index. Callback, iterator, and yield errors reject the operation. Callback failures still consume their measured processing time. The loops use native `for…of` cleanup rules. Callback and yield failures close the active iterator when it provides `return()`, while failures from `next()` do not. An existing callback or yield error takes precedence if cleanup also throws. Iterator advancement does not consume the budget.
49
+
50
+ The package does not infer parent operations or use global budgets. It does not serialize mutations or impose ordering between concurrent calls. Backend consumers use this package. The former `time-sliced-map.ts` utility has been removed.
51
+
52
+ ## Generator callbacks
53
+
54
+ `timeSlicedMap` and `timeSlicedForEach` finish each generator callback before starting the next item. Use `yield* waitFor(promise)` to suspend with an inferred result type:
55
+
56
+ ```ts
57
+ import { timeSlicedMap, waitFor } from "@zingage/scheduling";
58
+
59
+ const results = await timeSlicedMap(
60
+ items,
61
+ function* (item) {
62
+ const data = yield* waitFor(load(item));
63
+ return process(data);
64
+ },
65
+ budget,
66
+ );
67
+ ```
68
+
69
+ The budget measures every `next()` and `throw()` call, including synchronous work after suspension. Promise waits do not consume the budget. A rejected promise enters the generator through `throw()`, so generator `catch` and `finally` blocks run normally. Recovery can yield another promise. An unhandled error rejects the traversal and prevents later callbacks from starting. If the scheduler fails, the helper closes the callback generator and awaits its yielded cleanup. Cleanup does not call the failed scheduler again. The original scheduler error takes precedence over cleanup errors.
70
+
71
+ Generator callbacks may also yield promises directly, but their resumed values have type `unknown`. Use `waitFor` for typed values. A synchronous segment cannot be interrupted. Break expensive callbacks into smaller generator segments. Nested helpers share accounting, but cannot interrupt the synchronous segment that starts them.
72
+
73
+ ## Sorting
74
+
75
+ `timeSlicedSort(items, compare, budget?)` returns a new array and preserves the
76
+ input order of values that compare equal. It uses a stable merge sort and checks
77
+ the budget after each merge write, including tail copies. Input copying and
78
+ result extraction also use the budget. Comparators must be synchronous; one
79
+ comparator call cannot be interrupted. The comparator receives all values,
80
+ including `undefined`. A `NaN` comparison result acts like zero.
81
+
82
+ ```ts
83
+ const budget = new TimeSliceBudget();
84
+ const sorted = await timeSlicedSort(
85
+ [3, 1, 2],
86
+ (left, right) => left - right,
87
+ budget,
88
+ );
89
+ ```
90
+
91
+ For async sort keys, use generator callbacks with `timeSlicedMap` to load the
92
+ keys, then pass a synchronous comparator to `timeSlicedSort`.
@@ -0,0 +1,3 @@
1
+ export { waitFor, timeSlicedFilter, timeSlicedFlatMap, timeSlicedForEach, timeSlicedGroupBy, timeSlicedMap, timeSlicedMapFromEntries, TimeSliceBudget, } from "./time-slicing.js";
2
+ export { timeSlicedSort } from "./time-sliced-sort.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,wBAAwB,EACxB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { waitFor, timeSlicedFilter, timeSlicedFlatMap, timeSlicedForEach, timeSlicedGroupBy, timeSlicedMap, timeSlicedMapFromEntries, TimeSliceBudget, } from "./time-slicing.js";
2
+ export { timeSlicedSort } from "./time-sliced-sort.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,wBAAwB,EACxB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { TimeSliceBudget } from "./time-slicing.js";
2
+ /**
3
+ * Returns a new, stably sorted array using a synchronous comparator.
4
+ *
5
+ * A negative result puts the left value first; a positive result puts the right
6
+ * value first. Zero and NaN preserve input order. The comparator receives every
7
+ * input value, including undefined. The input is not reordered.
8
+ *
9
+ * Measures copying, comparisons, and merge writes. Checks the budget after each
10
+ * merge write, including writes that need no comparison. One comparator call
11
+ * cannot be interrupted. A nested helper cannot interrupt its containing
12
+ * measured segment. Uses O(n log n) work and O(n) additional memory.
13
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
14
+ *
15
+ * @example Sort a readonly input without reordering it.
16
+ * const sorted = await timeSlicedSort([3, 1, 2] as const, (left, right) => left - right);
17
+ *
18
+ * @example Load values in a generator and share its budget with sorting.
19
+ * const budget = new TimeSliceBudget();
20
+ * const sortedPages = await timeSlicedMap([1, 2], function* (page) {
21
+ * const values = yield* waitFor(Promise.resolve([page + 1, page]));
22
+ * return yield* waitFor(timeSlicedSort(values, (left, right) => left - right, budget));
23
+ * }, budget);
24
+ */
25
+ export declare function timeSlicedSort<Item>(items: Iterable<Item>, compare: (left: Item, right: Item) => number, budget?: TimeSliceBudget): Promise<Item[]>;
26
+ //# sourceMappingURL=time-sliced-sort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-sliced-sort.d.ts","sourceRoot":"","sources":["../src/time-sliced-sort.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAqB,MAAM,mBAAmB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,cAAc,CAAC,IAAI,EACvC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,KAAK,MAAM,EAC5C,MAAM,kBAAwB,GAC7B,OAAO,CAAC,IAAI,EAAE,CAAC,CAgEjB"}
@@ -0,0 +1,79 @@
1
+ import assert from "node:assert/strict";
2
+ import { TimeSliceBudget, timeSlicedForEach } from "./time-slicing.js";
3
+ /**
4
+ * Returns a new, stably sorted array using a synchronous comparator.
5
+ *
6
+ * A negative result puts the left value first; a positive result puts the right
7
+ * value first. Zero and NaN preserve input order. The comparator receives every
8
+ * input value, including undefined. The input is not reordered.
9
+ *
10
+ * Measures copying, comparisons, and merge writes. Checks the budget after each
11
+ * merge write, including writes that need no comparison. One comparator call
12
+ * cannot be interrupted. A nested helper cannot interrupt its containing
13
+ * measured segment. Uses O(n log n) work and O(n) additional memory.
14
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
15
+ *
16
+ * @example Sort a readonly input without reordering it.
17
+ * const sorted = await timeSlicedSort([3, 1, 2] as const, (left, right) => left - right);
18
+ *
19
+ * @example Load values in a generator and share its budget with sorting.
20
+ * const budget = new TimeSliceBudget();
21
+ * const sortedPages = await timeSlicedMap([1, 2], function* (page) {
22
+ * const values = yield* waitFor(Promise.resolve([page + 1, page]));
23
+ * return yield* waitFor(timeSlicedSort(values, (left, right) => left - right, budget));
24
+ * }, budget);
25
+ */
26
+ export async function timeSlicedSort(items, compare, budget = new TimeSliceBudget()) {
27
+ const buffers = {
28
+ source: [],
29
+ target: [],
30
+ width: 1,
31
+ };
32
+ /* oxlint-disable zingage/no-restricted-syntax -- Stable merging uses local buffers and sequential index updates to avoid quadratic copying and per-item promises. */
33
+ await timeSlicedForEach(items, (value) => {
34
+ buffers.source.push({ value });
35
+ }, budget);
36
+ const length = buffers.source.length;
37
+ while (buffers.width < length) {
38
+ const run = { start: 0 };
39
+ while (run.start < length) {
40
+ const middle = Math.min(run.start + buffers.width, length);
41
+ const end = Math.min(run.start + 2 * buffers.width, length);
42
+ const position = { left: run.start, right: middle, output: run.start };
43
+ while (position.output < end) {
44
+ budget.measure(() => {
45
+ const left = position.left < middle ? buffers.source[position.left] : undefined;
46
+ const right = position.right < end ? buffers.source[position.right] : undefined;
47
+ assert.ok(left !== undefined || position.left === middle);
48
+ assert.ok(right !== undefined || position.right === end);
49
+ if (left !== undefined &&
50
+ (right === undefined || !(compare(left.value, right.value) > 0))) {
51
+ buffers.target[position.output] = left;
52
+ position.left += 1;
53
+ }
54
+ else {
55
+ assert.ok(right);
56
+ buffers.target[position.output] = right;
57
+ position.right += 1;
58
+ }
59
+ position.output += 1;
60
+ });
61
+ const pending = budget.yieldIfNeeded();
62
+ if (pending !== undefined)
63
+ await pending;
64
+ }
65
+ run.start = end;
66
+ }
67
+ const previous = buffers.source;
68
+ buffers.source = buffers.target;
69
+ buffers.target = previous;
70
+ buffers.width *= 2;
71
+ }
72
+ const result = [];
73
+ await timeSlicedForEach(buffers.source, (entry) => {
74
+ result.push(entry.value);
75
+ }, budget);
76
+ /* oxlint-enable zingage/no-restricted-syntax */
77
+ return result;
78
+ }
79
+ //# sourceMappingURL=time-sliced-sort.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-sliced-sort.js","sourceRoot":"","sources":["../src/time-sliced-sort.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,197 @@
1
+ type Synchronous<Result> = Result extends PromiseLike<unknown> ? never : Result;
2
+ /**
3
+ * Tracks measured synchronous work before yielding to the event loop.
4
+ *
5
+ * A new budget allows 4 ms by default. Related operations can share an instance.
6
+ * Promise waits and unmeasured work do not consume it. One synchronous segment
7
+ * cannot be interrupted. Nested measurements on the same budget count once.
8
+ *
9
+ * @example Share the remaining allowance across related operations.
10
+ * const budget = new TimeSliceBudget();
11
+ * const doubled = await timeSlicedMap([1, 2], (value) => value * 2, budget);
12
+ * await timeSlicedForEach(doubled, (value) => console.log(value), budget);
13
+ */
14
+ export declare class TimeSliceBudget {
15
+ #private;
16
+ /**
17
+ * Creates a budget with a positive, finite allowance in milliseconds.
18
+ *
19
+ * `now` must be monotonic and return milliseconds. The default uses
20
+ * `performance.now`. The default scheduler uses promise-based `setImmediate`.
21
+ * A custom scheduler must let the event loop advance; a resolved promise does not.
22
+ *
23
+ * @example Use a smaller allowance for one operation.
24
+ * const budget = new TimeSliceBudget(2);
25
+ */
26
+ constructor(sliceMs?: number, now?: () => DOMHighResTimeStamp, yieldToEventLoop?: () => Promise<void>);
27
+ /**
28
+ * Runs synchronous work and charges its elapsed time, including when it throws.
29
+ *
30
+ * Nested measurements on this budget count once. This method does not yield.
31
+ * Call `yieldIfNeeded` at a safe boundary after handling the result.
32
+ *
33
+ * @example Measure one segment and then check the budget.
34
+ * const budget = new TimeSliceBudget();
35
+ * const value = budget.measure(() => 2 * 3);
36
+ * const pending = budget.yieldIfNeeded();
37
+ * if (pending !== undefined) await pending;
38
+ */
39
+ measure<Result>(work: () => Result & Synchronous<Result>): Result;
40
+ /**
41
+ * Returns a yield promise if the allowance is exhausted, or `undefined` otherwise.
42
+ *
43
+ * Replenishes before scheduling the yield. Connected work can use the next
44
+ * allowance while this caller waits. Resuming does not replenish it again.
45
+ *
46
+ * @example Avoid a microtask suspension when the budget still has time.
47
+ * const budget = new TimeSliceBudget();
48
+ * const pending = budget.yieldIfNeeded();
49
+ * if (pending !== undefined) await pending;
50
+ */
51
+ yieldIfNeeded(): Promise<void> | undefined;
52
+ }
53
+ type CallbackGenerator<Result> = Generator<PromiseLike<unknown>, Result, unknown>;
54
+ /**
55
+ * Suspends a generator callback until a promise settles, preserving its value type.
56
+ *
57
+ * Use `yield* waitFor(promise)` inside a generator passed to `timeSlicedMap` or
58
+ * `timeSlicedForEach`. Rejections enter the generator through `throw()`, so its
59
+ * `catch` and `finally` blocks run normally. Falsy and void results are supported.
60
+ *
61
+ * @example Infer the resolved value without annotating the generator.
62
+ * const lengths = await timeSlicedMap(["Ada"], function* (name) {
63
+ * const text = yield* waitFor(Promise.resolve(name));
64
+ * return text.length;
65
+ * });
66
+ *
67
+ * @example Recover from a rejected promise inside the generator.
68
+ * const values = await timeSlicedMap([1], function* (value) {
69
+ * try {
70
+ * return yield* waitFor(Promise.reject<number>(new TypeError("unavailable")));
71
+ * } catch {
72
+ * return value;
73
+ * }
74
+ * });
75
+ */
76
+ export declare function waitFor<Value>(promise: PromiseLike<Value>): CallbackGenerator<Value>;
77
+ /**
78
+ * Runs callbacks sequentially in input order and discards their return values.
79
+ *
80
+ * Accepts synchronous callbacks or generators that yield promises. Each generator
81
+ * finishes before the next item starts. Measures synchronous callback work and
82
+ * every generator `next()`/`throw()` segment, excluding promise waits.
83
+ * Iterator advancement is unmeasured. Unhandled failures stop the traversal.
84
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
85
+ *
86
+ * @example Run a synchronous visitor.
87
+ * await timeSlicedForEach([1, 2], (value) => console.log(value));
88
+ *
89
+ * @example Await work sequentially with a typed generator callback.
90
+ * await timeSlicedForEach([1, 2], function* (value) {
91
+ * const doubled = yield* waitFor(Promise.resolve(value * 2));
92
+ * console.log(doubled);
93
+ * });
94
+ */
95
+ export declare function timeSlicedForEach<Item, Result>(items: Iterable<Item>, callback: (item: Item, index: number) => CallbackGenerator<Result>, budget?: TimeSliceBudget): Promise<void>;
96
+ /**
97
+ * Runs a synchronous visitor in input order with an optional processing budget.
98
+ *
99
+ * @example
100
+ * await timeSlicedForEach([1, 2], (value) => console.log(value));
101
+ */
102
+ export declare function timeSlicedForEach<Item, Result>(items: Iterable<Item>, callback: (item: Item, index: number) => Result & Synchronous<Result>, budget?: TimeSliceBudget): Promise<void>;
103
+ /**
104
+ * Returns transformed values in input order using sequential callbacks.
105
+ *
106
+ * Accepts synchronous callbacks or generators that yield promises. Each generator
107
+ * finishes before the next item starts. Measures every synchronous segment,
108
+ * including `throw()` recovery and result accumulation. Promise waits and iterator
109
+ * advancement are unmeasured. Ordinary async callbacks are not supported.
110
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
111
+ *
112
+ * @example Transform synchronous values.
113
+ * const doubled = await timeSlicedMap([1, 2], (value) => value * 2);
114
+ *
115
+ * @example Resume measured work after each promise settles.
116
+ * const doubled = await timeSlicedMap([1, 2], function* (value) {
117
+ * const factor = yield* waitFor(Promise.resolve(2));
118
+ * return value * factor;
119
+ * });
120
+ */
121
+ export declare function timeSlicedMap<Item, Result>(items: Iterable<Item>, callback: (item: Item, index: number) => CallbackGenerator<Result>, budget?: TimeSliceBudget): Promise<Result[]>;
122
+ /**
123
+ * Maps synchronous values in input order with an optional processing budget.
124
+ *
125
+ * @example
126
+ * const doubled = await timeSlicedMap([1, 2], (value) => value * 2);
127
+ */
128
+ export declare function timeSlicedMap<Item, Result>(items: Iterable<Item>, callback: (item: Item, index: number) => Result & Synchronous<Result>, budget?: TimeSliceBudget): Promise<Result[]>;
129
+ /**
130
+ * Maps each item to a synchronous list and flattens the lists in input order.
131
+ *
132
+ * Measures callbacks and appends. It can yield within one large returned list.
133
+ * One callback cannot be interrupted. For asynchronous mapping, first use a
134
+ * generator callback with `timeSlicedMap`, then flatten the returned lists.
135
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
136
+ *
137
+ * @example Flatten synchronous results.
138
+ * const values = await timeSlicedFlatMap([1, 2], (value) => [value, value * 2]);
139
+ *
140
+ * @example Fetch lists with generators and share their budget with flattening.
141
+ * const budget = new TimeSliceBudget();
142
+ * const pages = await timeSlicedMap([1, 2], function* (value) {
143
+ * return yield* waitFor(Promise.resolve([value, value * 2]));
144
+ * }, budget);
145
+ * const values = await timeSlicedFlatMap(pages, (page) => page, budget);
146
+ */
147
+ export declare function timeSlicedFlatMap<Item, Result>(items: Iterable<Item>, callback: (item: Item, index: number) => readonly Result[], budget?: TimeSliceBudget): Promise<Result[]>;
148
+ /**
149
+ * Returns items whose synchronous predicate is true, preserving input order.
150
+ *
151
+ * Measures predicate work and result accumulation. One predicate call cannot be
152
+ * interrupted. The optional budget defaults to a new independent instance.
153
+ *
154
+ * @example Keep odd numbers.
155
+ * const odd = await timeSlicedFilter([1, 2, 3], (value) => value % 2 === 1);
156
+ *
157
+ * @example Compose filtering inside a generator with a shared budget.
158
+ * const budget = new TimeSliceBudget();
159
+ * const groups = await timeSlicedMap([[1, 2, 3]], function* (values) {
160
+ * return yield* waitFor(timeSlicedFilter(values, (value) => value > 1, budget));
161
+ * }, budget);
162
+ */
163
+ export declare function timeSlicedFilter<Item>(items: Iterable<Item>, predicate: (item: Item, index: number) => boolean, budget?: TimeSliceBudget): Promise<Item[]>;
164
+ /**
165
+ * Groups items by a synchronous key, preserving key and group insertion order.
166
+ *
167
+ * Uses `Map` key equality. Measures key selection and group accumulation.
168
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
169
+ *
170
+ * @example Group strings by length.
171
+ * const groups = await timeSlicedGroupBy(["a", "bb", "c"], (text) => text.length);
172
+ *
173
+ * @example Await grouping inside a generator and share its budget.
174
+ * const budget = new TimeSliceBudget();
175
+ * const groupedPages = await timeSlicedMap([[1, 2, 3]], function* (values) {
176
+ * return yield* waitFor(timeSlicedGroupBy(values, (value) => value % 2, budget));
177
+ * }, budget);
178
+ */
179
+ export declare function timeSlicedGroupBy<Item, Key>(items: Iterable<Item>, keyOf: (item: Item, index: number) => Key & Synchronous<Key>, budget?: TimeSliceBudget): Promise<Map<Key, Item[]>>;
180
+ /**
181
+ * Builds a Map from entries in input order; the last value for a repeated key wins.
182
+ *
183
+ * Measures entry insertion. Replacing a value preserves the key's first insertion
184
+ * position. The optional budget defaults to a new independent `TimeSliceBudget`.
185
+ *
186
+ * @example Build a Map while preserving last-value-wins behavior.
187
+ * const map = await timeSlicedMapFromEntries([["a", 1], ["a", 2]] as const);
188
+ *
189
+ * @example Await Map construction inside a generator with a shared budget.
190
+ * const budget = new TimeSliceBudget();
191
+ * const maps = await timeSlicedMap([1, 2], function* (value) {
192
+ * return yield* waitFor(timeSlicedMapFromEntries([["key", value]] as const, budget));
193
+ * }, budget);
194
+ */
195
+ export declare function timeSlicedMapFromEntries<Key, Value>(entries: Iterable<readonly [Key, Value]>, budget?: TimeSliceBudget): Promise<Map<Key, Value>>;
196
+ export {};
197
+ //# sourceMappingURL=time-slicing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-slicing.d.ts","sourceRoot":"","sources":["../src/time-slicing.ts"],"names":[],"mappings":"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"}