@zingage/scheduling 0.0.0-stage → 1.1.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,98 @@
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. `timeSlicedMap` and `timeSlicedForEach` also accept generator callbacks.
33
+
34
+ ## Synchronous callbacks
35
+
36
+ Ordinary callbacks, predicates, key functions, and `measure` work must be synchronous. The types accept any result, including generic results such as `(item) => item`. TypeScript cannot reject a promise result without also rejecting generic results. So the rule is checked at runtime: if a non-generator callback returns a promise or other thenable (an object or function with a `then` method), the helper throws a `TypeError` before it uses the value. An `async` callback compiles, and throws on its first call. For async work, use a generator callback with `yield* waitFor(...)`.
37
+
38
+ 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.
39
+
40
+ ## Helpers
41
+
42
+ | Helper | Result |
43
+ | -------------------------- | ---------------------------------------------------------------------------- |
44
+ | `timeSlicedForEach` | Runs callbacks in input order; returns `Promise<void>`. |
45
+ | `timeSlicedMap` | Returns transformed values in input order. |
46
+ | `timeSlicedFlatMap` | Flattens each callback's list incrementally, including large lists. |
47
+ | `timeSlicedFilter` | Returns matching items in input order. |
48
+ | `timeSlicedSome` | Returns `true` at the first match; stops there. |
49
+ | `timeSlicedFind` | Returns the first match, or `undefined`; stops there. |
50
+ | `timeSlicedGroupBy` | Returns a `Map` with first-seen key order and input order within each group. |
51
+ | `timeSlicedSort` | Returns a new stable sorted array with a synchronous comparator. |
52
+ | `timeSlicedMapFromEntries` | Builds a `Map`; the last value wins for repeated keys. |
53
+
54
+ 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.
55
+
56
+ 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.
57
+
58
+ ## Generator callbacks
59
+
60
+ `timeSlicedMap` and `timeSlicedForEach` finish each generator callback before starting the next item. Use `yield* waitFor(promise)` to suspend with an inferred result type:
61
+
62
+ ```ts
63
+ import { timeSlicedMap, waitFor } from "@zingage/scheduling";
64
+
65
+ const results = await timeSlicedMap(
66
+ items,
67
+ function* (item) {
68
+ const data = yield* waitFor(load(item));
69
+ return process(data);
70
+ },
71
+ budget,
72
+ );
73
+ ```
74
+
75
+ 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.
76
+
77
+ 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.
78
+
79
+ ## Sorting
80
+
81
+ `timeSlicedSort(items, compare, budget?)` returns a new array and preserves the
82
+ input order of values that compare equal. It uses a stable merge sort and checks
83
+ the budget after each merge write, including tail copies. Input copying and
84
+ result extraction also use the budget. Comparators must be synchronous; one
85
+ comparator call cannot be interrupted. The comparator receives all values,
86
+ including `undefined`. A `NaN` comparison result acts like zero.
87
+
88
+ ```ts
89
+ const budget = new TimeSliceBudget();
90
+ const sorted = await timeSlicedSort(
91
+ [3, 1, 2],
92
+ (left, right) => left - right,
93
+ budget,
94
+ );
95
+ ```
96
+
97
+ For async sort keys, use generator callbacks with `timeSlicedMap` to load the
98
+ keys, then pass a synchronous comparator to `timeSlicedSort`.
@@ -0,0 +1,4 @@
1
+ export { waitFor, timeSlicedFilter, timeSlicedFlatMap, timeSlicedForEach, timeSlicedGroupBy, timeSlicedMap, timeSlicedMapFromEntries, TimeSliceBudget, } from "./time-slicing.js";
2
+ export { timeSlicedSort } from "./time-sliced-sort.js";
3
+ export { timeSlicedFind, timeSlicedSome } from "./time-sliced-search.js";
4
+ //# 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;AACvD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { waitFor, timeSlicedFilter, timeSlicedFlatMap, timeSlicedForEach, timeSlicedGroupBy, timeSlicedMap, timeSlicedMapFromEntries, TimeSliceBudget, } from "./time-slicing.js";
2
+ export { timeSlicedSort } from "./time-sliced-sort.js";
3
+ export { timeSlicedFind, timeSlicedSome } from "./time-sliced-search.js";
4
+ //# 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;AACvD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,10 @@
1
+ export declare const GENERATOR_HINT = "For async work, use a generator callback with `yield* waitFor(...)`.";
2
+ export declare const TRAVERSAL_HINT = "For async work, use a generator callback with `yield* waitFor(...)` in timeSlicedMap or timeSlicedForEach first.";
3
+ /**
4
+ * Returns `value` unchanged, or throws a `TypeError` if it is a thenable.
5
+ *
6
+ * TypeScript cannot reject a promise result without also rejecting generic
7
+ * results, so the synchronous callback rule is checked here at runtime.
8
+ */
9
+ export declare function assertNotThenable<Value>(value: Value, helper: string, hint: string): Value;
10
+ //# sourceMappingURL=synchronous-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synchronous-result.d.ts","sourceRoot":"","sources":["../src/synchronous-result.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,yEAC6C,CAAC;AACzE,eAAO,MAAM,cAAc,qHACyF,CAAC;AAErH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EACrC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,KAAK,CAYP"}
@@ -0,0 +1,18 @@
1
+ export const GENERATOR_HINT = "For async work, use a generator callback with `yield* waitFor(...)`.";
2
+ export const TRAVERSAL_HINT = "For async work, use a generator callback with `yield* waitFor(...)` in timeSlicedMap or timeSlicedForEach first.";
3
+ /**
4
+ * Returns `value` unchanged, or throws a `TypeError` if it is a thenable.
5
+ *
6
+ * TypeScript cannot reject a promise result without also rejecting generic
7
+ * results, so the synchronous callback rule is checked here at runtime.
8
+ */
9
+ export function assertNotThenable(value, helper, hint) {
10
+ if (((typeof value === "object" && value !== null) ||
11
+ typeof value === "function") &&
12
+ "then" in value &&
13
+ typeof value.then === "function") {
14
+ throw new TypeError(`${helper} received a promise or other thenable from a synchronous callback. ${hint}`);
15
+ }
16
+ return value;
17
+ }
18
+ //# sourceMappingURL=synchronous-result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synchronous-result.js","sourceRoot":"","sources":["../src/synchronous-result.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GACzB,sEAAsE,CAAC;AACzE,MAAM,CAAC,MAAM,cAAc,GACzB,kHAAkH,CAAC;AAErH;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAY,EACZ,MAAc,EACd,IAAY;IAEZ,IACE,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;QAC5C,OAAO,KAAK,KAAK,UAAU,CAAC;QAC9B,MAAM,IAAI,KAAK;QACf,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,EAChC,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,GAAG,MAAM,sEAAsE,IAAI,EAAE,CACtF,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { TimeSliceBudget } from "./time-slicing.js";
2
+ /**
3
+ * Returns whether any item matches a synchronous predicate, in input order.
4
+ *
5
+ * Stops at the first match. It does not advance the iterable past that item,
6
+ * and it builds no intermediate array. Measures each predicate call, including
7
+ * calls that throw. One predicate call cannot be interrupted. Throws a
8
+ * `TypeError` if the predicate returns a promise or other thenable.
9
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
10
+ *
11
+ * @example Check for a negative number.
12
+ * const hasNegative = await timeSlicedSome([3, -1, 2], (value) => value < 0);
13
+ *
14
+ * @example Share a budget with other work, and stop early on a lazy source.
15
+ * const budget = new TimeSliceBudget();
16
+ * const found = await timeSlicedSome(map.values(), (entry) => entry.active, budget);
17
+ */
18
+ export declare function timeSlicedSome<Item>(items: Iterable<Item>, predicate: (item: Item, index: number) => boolean, budget?: TimeSliceBudget): Promise<boolean>;
19
+ /**
20
+ * Returns the first item that matches a type-guard predicate, narrowed.
21
+ *
22
+ * @example Find the first string.
23
+ * const text = await timeSlicedFind(
24
+ * [1, "a", 2],
25
+ * (value): value is string => typeof value === "string",
26
+ * );
27
+ */
28
+ export declare function timeSlicedFind<Item, Found extends Item>(items: Iterable<Item>, predicate: (item: Item, index: number) => item is Found, budget?: TimeSliceBudget): Promise<Found | undefined>;
29
+ /**
30
+ * Returns the first item that matches a synchronous predicate, or `undefined`.
31
+ *
32
+ * Stops at the first match. It does not advance the iterable past that item,
33
+ * and it builds no intermediate array. Measures each predicate call, including
34
+ * calls that throw. One predicate call cannot be interrupted. Throws a
35
+ * `TypeError` if the predicate returns a promise or other thenable. As with
36
+ * `Array.prototype.find`, a matching `undefined` item also returns `undefined`.
37
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
38
+ *
39
+ * @example Find the first even number.
40
+ * const even = await timeSlicedFind([1, 4, 6], (value) => value % 2 === 0);
41
+ *
42
+ * @example Use the index and a shared budget.
43
+ * const budget = new TimeSliceBudget();
44
+ * const late = await timeSlicedFind(["a", "b"], (_, index) => index > 0, budget);
45
+ */
46
+ export declare function timeSlicedFind<Item>(items: Iterable<Item>, predicate: (item: Item, index: number) => boolean, budget?: TimeSliceBudget): Promise<Item | undefined>;
47
+ //# sourceMappingURL=time-sliced-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-sliced-search.d.ts","sourceRoot":"","sources":["../src/time-sliced-search.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AA0BpD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAAC,IAAI,EACvC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,EACjD,MAAM,kBAAwB,GAC7B,OAAO,CAAC,OAAO,CAAC,CAKlB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,KAAK,SAAS,IAAI,EACrD,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,KAAK,EACvD,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;AAC9B;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,IAAI,EACjC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,EACjD,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { TRAVERSAL_HINT, assertNotThenable } from "./synchronous-result.js";
2
+ import { TimeSliceBudget } from "./time-slicing.js";
3
+ async function findFirstMatch(helper, items, predicate, budget) {
4
+ const position = { index: 0 };
5
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Stop at the first match; native iteration closes the iterator without advancing it.
6
+ for (const item of items) {
7
+ const matched = budget.measure(() => assertNotThenable(predicate(item, position.index), helper, TRAVERSAL_HINT));
8
+ position.index += 1;
9
+ const pending = budget.yieldIfNeeded();
10
+ if (pending !== undefined)
11
+ await pending;
12
+ if (matched)
13
+ return { item };
14
+ }
15
+ return undefined;
16
+ }
17
+ /**
18
+ * Returns whether any item matches a synchronous predicate, in input order.
19
+ *
20
+ * Stops at the first match. It does not advance the iterable past that item,
21
+ * and it builds no intermediate array. Measures each predicate call, including
22
+ * calls that throw. One predicate call cannot be interrupted. Throws a
23
+ * `TypeError` if the predicate returns a promise or other thenable.
24
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
25
+ *
26
+ * @example Check for a negative number.
27
+ * const hasNegative = await timeSlicedSome([3, -1, 2], (value) => value < 0);
28
+ *
29
+ * @example Share a budget with other work, and stop early on a lazy source.
30
+ * const budget = new TimeSliceBudget();
31
+ * const found = await timeSlicedSome(map.values(), (entry) => entry.active, budget);
32
+ */
33
+ export async function timeSlicedSome(items, predicate, budget = new TimeSliceBudget()) {
34
+ return ((await findFirstMatch("timeSlicedSome", items, predicate, budget)) !==
35
+ undefined);
36
+ }
37
+ export async function timeSlicedFind(items, predicate, budget = new TimeSliceBudget()) {
38
+ return (await findFirstMatch("timeSlicedFind", items, predicate, budget))
39
+ ?.item;
40
+ }
41
+ //# sourceMappingURL=time-sliced-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-sliced-search.js","sourceRoot":"","sources":["../src/time-sliced-search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,KAAK,UAAU,cAAc,CAC3B,MAAc,EACd,KAAqB,EACrB,SAAiD,EACjD,MAAuB;IAEvB,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAC9B,+IAA+I;IAC/I,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAClC,iBAAiB,CACf,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,EAC/B,MAAM,EACN,cAAc,CACf,CACF,CAAC;QACF,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC;QACpB,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,OAAO,CAAC;QACzC,IAAI,OAAO;YAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC/B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAAqB,EACrB,SAAiD,EACjD,MAAM,GAAG,IAAI,eAAe,EAAE;IAE9B,OAAO,CACL,CAAC,MAAM,cAAc,CAAC,gBAAgB,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAClE,SAAS,CACV,CAAC;AACJ,CAAC;AAsCD,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAAqB,EACrB,SAAiD,EACjD,MAAM,GAAG,IAAI,eAAe,EAAE;IAE9B,OAAO,CAAC,MAAM,cAAc,CAAC,gBAAgB,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QACvE,EAAE,IAAI,CAAC;AACX,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,210 @@
1
+ /**
2
+ * Tracks measured synchronous work before yielding to the event loop.
3
+ *
4
+ * A new budget allows 4 ms by default. Related operations can share an instance.
5
+ * Promise waits and unmeasured work do not consume it. One synchronous segment
6
+ * cannot be interrupted. Nested measurements on the same budget count once.
7
+ *
8
+ * @example Share the remaining allowance across related operations.
9
+ * const budget = new TimeSliceBudget();
10
+ * const doubled = await timeSlicedMap([1, 2], (value) => value * 2, budget);
11
+ * await timeSlicedForEach(doubled, (value) => console.log(value), budget);
12
+ */
13
+ export declare class TimeSliceBudget {
14
+ #private;
15
+ /**
16
+ * Creates a budget with a positive, finite allowance in milliseconds.
17
+ *
18
+ * `now` must be monotonic and return milliseconds. The default uses
19
+ * `performance.now`. The default scheduler uses promise-based `setImmediate`.
20
+ * A custom scheduler must let the event loop advance; a resolved promise does not.
21
+ *
22
+ * @example Use a smaller allowance for one operation.
23
+ * const budget = new TimeSliceBudget(2);
24
+ */
25
+ constructor(sliceMs?: number, now?: () => DOMHighResTimeStamp, yieldToEventLoop?: () => Promise<void>);
26
+ /**
27
+ * Runs synchronous work and charges its elapsed time, including when it throws.
28
+ *
29
+ * Nested measurements on this budget count once. This method does not yield.
30
+ * Call `yieldIfNeeded` at a safe boundary after handling the result.
31
+ * `work` must be synchronous. If it returns a promise or other thenable, this
32
+ * method throws a `TypeError` after charging the elapsed time.
33
+ *
34
+ * @example Measure one segment and then check the budget.
35
+ * const budget = new TimeSliceBudget();
36
+ * const value = budget.measure(() => 2 * 3);
37
+ * const pending = budget.yieldIfNeeded();
38
+ * if (pending !== undefined) await pending;
39
+ */
40
+ measure<Result>(work: () => Result): Result;
41
+ /**
42
+ * Returns a yield promise if the allowance is exhausted, or `undefined` otherwise.
43
+ *
44
+ * Replenishes before scheduling the yield. Connected work can use the next
45
+ * allowance while this caller waits. Resuming does not replenish it again.
46
+ *
47
+ * @example Avoid a microtask suspension when the budget still has time.
48
+ * const budget = new TimeSliceBudget();
49
+ * const pending = budget.yieldIfNeeded();
50
+ * if (pending !== undefined) await pending;
51
+ */
52
+ yieldIfNeeded(): Promise<void> | undefined;
53
+ }
54
+ type CallbackGenerator<Result> = Generator<PromiseLike<unknown>, Result, unknown>;
55
+ /**
56
+ * Suspends a generator callback until a promise settles, preserving its value type.
57
+ *
58
+ * Use `yield* waitFor(promise)` inside a generator passed to `timeSlicedMap` or
59
+ * `timeSlicedForEach`. Rejections enter the generator through `throw()`, so its
60
+ * `catch` and `finally` blocks run normally. Falsy and void results are supported.
61
+ *
62
+ * @example Infer the resolved value without annotating the generator.
63
+ * const lengths = await timeSlicedMap(["Ada"], function* (name) {
64
+ * const text = yield* waitFor(Promise.resolve(name));
65
+ * return text.length;
66
+ * });
67
+ *
68
+ * @example Recover from a rejected promise inside the generator.
69
+ * const values = await timeSlicedMap([1], function* (value) {
70
+ * try {
71
+ * return yield* waitFor(Promise.reject<number>(new TypeError("unavailable")));
72
+ * } catch {
73
+ * return value;
74
+ * }
75
+ * });
76
+ */
77
+ export declare function waitFor<Value>(promise: PromiseLike<Value>): CallbackGenerator<Value>;
78
+ /**
79
+ * Runs callbacks sequentially in input order and discards their return values.
80
+ *
81
+ * Accepts synchronous callbacks or generators that yield promises. Each generator
82
+ * finishes before the next item starts. Measures synchronous callback work and
83
+ * every generator `next()`/`throw()` segment, excluding promise waits.
84
+ * Iterator advancement is unmeasured. Unhandled failures stop the traversal.
85
+ * A non-generator callback must be synchronous. If it returns a promise or
86
+ * other thenable (an `async` function, for example), the traversal throws a
87
+ * `TypeError` on that first call. The type checker does not catch this.
88
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
89
+ *
90
+ * @example Run a synchronous visitor.
91
+ * await timeSlicedForEach([1, 2], (value) => console.log(value));
92
+ *
93
+ * @example Await work sequentially with a typed generator callback.
94
+ * await timeSlicedForEach([1, 2], function* (value) {
95
+ * const doubled = yield* waitFor(Promise.resolve(value * 2));
96
+ * console.log(doubled);
97
+ * });
98
+ */
99
+ export declare function timeSlicedForEach<Item, Result>(items: Iterable<Item>, callback: (item: Item, index: number) => CallbackGenerator<Result>, budget?: TimeSliceBudget): Promise<void>;
100
+ /**
101
+ * Runs a synchronous visitor in input order with an optional processing budget.
102
+ *
103
+ * Throws a `TypeError` if the visitor returns a promise or other thenable.
104
+ *
105
+ * @example
106
+ * await timeSlicedForEach([1, 2], (value) => console.log(value));
107
+ */
108
+ export declare function timeSlicedForEach<Item, Result>(items: Iterable<Item>, callback: (item: Item, index: number) => Result, budget?: TimeSliceBudget): Promise<void>;
109
+ /**
110
+ * Returns transformed values in input order using sequential callbacks.
111
+ *
112
+ * Accepts synchronous callbacks or generators that yield promises. Each generator
113
+ * finishes before the next item starts. Measures every synchronous segment,
114
+ * including `throw()` recovery and result accumulation. Promise waits and iterator
115
+ * advancement are unmeasured. Ordinary async callbacks are not supported: if a
116
+ * non-generator callback returns a promise or other thenable, the traversal
117
+ * throws a `TypeError` on that first call. The type checker does not catch this.
118
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
119
+ *
120
+ * @example Transform synchronous values.
121
+ * const doubled = await timeSlicedMap([1, 2], (value) => value * 2);
122
+ *
123
+ * @example Resume measured work after each promise settles.
124
+ * const doubled = await timeSlicedMap([1, 2], function* (value) {
125
+ * const factor = yield* waitFor(Promise.resolve(2));
126
+ * return value * factor;
127
+ * });
128
+ */
129
+ export declare function timeSlicedMap<Item, Result>(items: Iterable<Item>, callback: (item: Item, index: number) => CallbackGenerator<Result>, budget?: TimeSliceBudget): Promise<Result[]>;
130
+ /**
131
+ * Maps synchronous values in input order with an optional processing budget.
132
+ *
133
+ * Throws a `TypeError` if the callback returns a promise or other thenable.
134
+ *
135
+ * @example
136
+ * const doubled = await timeSlicedMap([1, 2], (value) => value * 2);
137
+ */
138
+ export declare function timeSlicedMap<Item, Result>(items: Iterable<Item>, callback: (item: Item, index: number) => Result, budget?: TimeSliceBudget): Promise<Result[]>;
139
+ /**
140
+ * Maps each item to a synchronous list and flattens the lists in input order.
141
+ *
142
+ * Measures callbacks and appends. It can yield within one large returned list.
143
+ * One callback cannot be interrupted. For asynchronous mapping, first use a
144
+ * generator callback with `timeSlicedMap`, then flatten the returned lists.
145
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
146
+ *
147
+ * @example Flatten synchronous results.
148
+ * const values = await timeSlicedFlatMap([1, 2], (value) => [value, value * 2]);
149
+ *
150
+ * @example Fetch lists with generators and share their budget with flattening.
151
+ * const budget = new TimeSliceBudget();
152
+ * const pages = await timeSlicedMap([1, 2], function* (value) {
153
+ * return yield* waitFor(Promise.resolve([value, value * 2]));
154
+ * }, budget);
155
+ * const values = await timeSlicedFlatMap(pages, (page) => page, budget);
156
+ */
157
+ export declare function timeSlicedFlatMap<Item, Result>(items: Iterable<Item>, callback: (item: Item, index: number) => readonly Result[], budget?: TimeSliceBudget): Promise<Result[]>;
158
+ /**
159
+ * Returns items whose synchronous predicate is true, preserving input order.
160
+ *
161
+ * Measures predicate work and result accumulation. One predicate call cannot be
162
+ * interrupted. Throws a `TypeError` if the predicate returns a promise or other
163
+ * thenable. The optional budget defaults to a new independent instance.
164
+ *
165
+ * @example Keep odd numbers.
166
+ * const odd = await timeSlicedFilter([1, 2, 3], (value) => value % 2 === 1);
167
+ *
168
+ * @example Compose filtering inside a generator with a shared budget.
169
+ * const budget = new TimeSliceBudget();
170
+ * const groups = await timeSlicedMap([[1, 2, 3]], function* (values) {
171
+ * return yield* waitFor(timeSlicedFilter(values, (value) => value > 1, budget));
172
+ * }, budget);
173
+ */
174
+ export declare function timeSlicedFilter<Item>(items: Iterable<Item>, predicate: (item: Item, index: number) => boolean, budget?: TimeSliceBudget): Promise<Item[]>;
175
+ /**
176
+ * Groups items by a synchronous key, preserving key and group insertion order.
177
+ *
178
+ * Uses `Map` key equality. Measures key selection and group accumulation.
179
+ * `keyOf` must be synchronous. If it returns a promise or other thenable, the
180
+ * operation throws a `TypeError` before it stores the key.
181
+ * The optional budget defaults to a new independent `TimeSliceBudget`.
182
+ *
183
+ * @example Group strings by length.
184
+ * const groups = await timeSlicedGroupBy(["a", "bb", "c"], (text) => text.length);
185
+ *
186
+ * @example Await grouping inside a generator and share its budget.
187
+ * const budget = new TimeSliceBudget();
188
+ * const groupedPages = await timeSlicedMap([[1, 2, 3]], function* (values) {
189
+ * return yield* waitFor(timeSlicedGroupBy(values, (value) => value % 2, budget));
190
+ * }, budget);
191
+ */
192
+ export declare function timeSlicedGroupBy<Item, Key>(items: Iterable<Item>, keyOf: (item: Item, index: number) => Key, budget?: TimeSliceBudget): Promise<Map<Key, Item[]>>;
193
+ /**
194
+ * Builds a Map from entries in input order; the last value for a repeated key wins.
195
+ *
196
+ * Measures entry insertion. Replacing a value preserves the key's first insertion
197
+ * position. The optional budget defaults to a new independent `TimeSliceBudget`.
198
+ *
199
+ * @example Build a Map while preserving last-value-wins behavior.
200
+ * const map = await timeSlicedMapFromEntries([["a", 1], ["a", 2]] as const);
201
+ *
202
+ * @example Await Map construction inside a generator with a shared budget.
203
+ * const budget = new TimeSliceBudget();
204
+ * const maps = await timeSlicedMap([1, 2], function* (value) {
205
+ * return yield* waitFor(timeSlicedMapFromEntries([["key", value]] as const, budget));
206
+ * }, budget);
207
+ */
208
+ export declare function timeSlicedMapFromEntries<Key, Value>(entries: Iterable<readonly [Key, Value]>, budget?: TimeSliceBudget): Promise<Map<Key, Value>>;
209
+ export {};
210
+ //# 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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"}