@zingage/scheduling 1.0.0 → 1.2.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.
Files changed (57) hide show
  1. package/README.md +190 -5
  2. package/dist/batcher.d.ts +231 -0
  3. package/dist/batcher.d.ts.map +1 -0
  4. package/dist/batcher.js +391 -0
  5. package/dist/batcher.js.map +1 -0
  6. package/dist/collapsed-task-creator.d.ts +235 -0
  7. package/dist/collapsed-task-creator.d.ts.map +1 -0
  8. package/dist/collapsed-task-creator.js +491 -0
  9. package/dist/collapsed-task-creator.js.map +1 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +6 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/logger.d.ts +16 -0
  15. package/dist/logger.d.ts.map +1 -0
  16. package/dist/logger.js +14 -0
  17. package/dist/logger.js.map +1 -0
  18. package/dist/pending-promises-tracker.d.ts +32 -0
  19. package/dist/pending-promises-tracker.d.ts.map +1 -0
  20. package/dist/pending-promises-tracker.js +60 -0
  21. package/dist/pending-promises-tracker.js.map +1 -0
  22. package/dist/performance-now.d.ts +14 -0
  23. package/dist/performance-now.d.ts.map +1 -0
  24. package/dist/performance-now.js +10 -0
  25. package/dist/performance-now.js.map +1 -0
  26. package/dist/scheduling-contract-error.d.ts +9 -0
  27. package/dist/scheduling-contract-error.d.ts.map +1 -0
  28. package/dist/scheduling-contract-error.js +9 -0
  29. package/dist/scheduling-contract-error.js.map +1 -0
  30. package/dist/synchronous-result.d.ts +10 -0
  31. package/dist/synchronous-result.d.ts.map +1 -0
  32. package/dist/synchronous-result.js +18 -0
  33. package/dist/synchronous-result.js.map +1 -0
  34. package/dist/time-sliced-search.d.ts +47 -0
  35. package/dist/time-sliced-search.d.ts.map +1 -0
  36. package/dist/time-sliced-search.js +41 -0
  37. package/dist/time-sliced-search.js.map +1 -0
  38. package/dist/time-slicing.d.ts +20 -7
  39. package/dist/time-slicing.d.ts.map +1 -1
  40. package/dist/time-slicing.js +14 -8
  41. package/dist/time-slicing.js.map +1 -1
  42. package/dist/wrap-with-task-collapsing.d.ts +30 -0
  43. package/dist/wrap-with-task-collapsing.d.ts.map +1 -0
  44. package/dist/wrap-with-task-collapsing.js +80 -0
  45. package/dist/wrap-with-task-collapsing.js.map +1 -0
  46. package/package.json +10 -4
  47. package/src/batcher.ts +614 -0
  48. package/src/collapsed-task-creator.ts +774 -0
  49. package/src/index.ts +23 -0
  50. package/src/logger.ts +32 -0
  51. package/src/pending-promises-tracker.ts +62 -0
  52. package/src/performance-now.ts +21 -0
  53. package/src/scheduling-contract-error.ts +8 -0
  54. package/src/synchronous-result.ts +28 -0
  55. package/src/time-sliced-search.ts +98 -0
  56. package/src/time-slicing.ts +50 -26
  57. package/src/wrap-with-task-collapsing.ts +103 -0
@@ -0,0 +1,491 @@
1
+ import { partition } from "es-toolkit";
2
+ import { SchedulingContractError } from "./scheduling-contract-error.js";
3
+ /**
4
+ * A task that its `schedule` call has scheduled. Only these tasks are in
5
+ * `#latestPendingTaskByKey` and `#live`, so a failed `schedule` call leaves no
6
+ * task behind.
7
+ */
8
+ class Task {
9
+ /** The parts of the task that existed before its `schedule` call returned. */
10
+ start;
11
+ /**
12
+ * The handle that the task's `schedule` call returned. `startOrRideTasks`
13
+ * returns it to every request on the task.
14
+ */
15
+ handle;
16
+ /** The result that the task's `schedule` call returned. */
17
+ result;
18
+ /**
19
+ * Undefined once the task can never be cancelled: from the start when its
20
+ * starting request has no signal, after a later request without a signal
21
+ * (see `SharedTaskView.signal`), or once the task settles. In the last two
22
+ * cases, the task keeps the signal that `schedule` already received, but
23
+ * nothing can abort it any more.
24
+ */
25
+ #cancellation;
26
+ /**
27
+ * @param controller The controller whose signal is `start.signal`, or
28
+ * undefined when the starting request has no signal.
29
+ * @param onSettle Called once, when `result` settles, after the task has
30
+ * become uncancellable and removed its abort listeners.
31
+ */
32
+ constructor(start, controller, { result, handle }, onSettle) {
33
+ this.start = start;
34
+ this.#cancellation = controller && {
35
+ controller,
36
+ uncancelledRequests: 0,
37
+ lastCancelReason: undefined,
38
+ abortListeners: [],
39
+ };
40
+ this.handle = handle;
41
+ this.result = result;
42
+ // Handling both outcomes also marks a rejection as handled: every task
43
+ // request may be cancelled before anyone awaits the result.
44
+ const settle = () => {
45
+ this.#makeUncancelable();
46
+ onSettle();
47
+ };
48
+ // oxlint-disable-next-line no-void -- Runs settle bookkeeping; callers await the result themselves.
49
+ void result.then(settle, settle);
50
+ }
51
+ /**
52
+ * Keeps the task's signal from aborting until `release`, even if every
53
+ * request on the task is cancelled meanwhile. `startOrRideTasks` holds each
54
+ * task it starts or rides until it has recorded all of its requests, so a
55
+ * cancellation during `schedule`, or during another task's abort, cannot
56
+ * cancel a task that a request of the call will keep alive.
57
+ *
58
+ * A hold counts as one uncancelled request. Holds can nest, because a
59
+ * nested `startOrRideTasks` call can ride a task that an outer call holds.
60
+ * It is inert on an uncancellable task, which has no signal to abort.
61
+ */
62
+ hold() {
63
+ if (this.#cancellation)
64
+ this.#cancellation.uncancelledRequests += 1;
65
+ }
66
+ /**
67
+ * Ends one `hold`. Aborts the task's signal if no request keeps the task
68
+ * alive. Like `hold`, it is inert on an uncancellable task.
69
+ */
70
+ release() {
71
+ const cancellation = this.#cancellation;
72
+ if (cancellation === undefined)
73
+ return;
74
+ cancellation.uncancelledRequests -= 1;
75
+ if (cancellation.uncancelledRequests === 0) {
76
+ cancellation.controller.abort(cancellation.lastCancelReason);
77
+ }
78
+ }
79
+ /**
80
+ * Records a task request on this task, whether the request started the
81
+ * task or rides it. `signal` and `rode` are the request's. Call it only
82
+ * while holding the task: a request whose signal already aborted is not
83
+ * counted, so only `release` can abort the task for it.
84
+ */
85
+ addRequest(signal, rode) {
86
+ if (rode)
87
+ this.start.collapsedRequestCount += 1;
88
+ // An uncancellable task needs no count: no task request can cancel it.
89
+ const cancellation = this.#cancellation;
90
+ if (cancellation === undefined)
91
+ return;
92
+ // A request w/o a signal can never be cancelled, so neither can the task.
93
+ if (signal === undefined) {
94
+ this.#makeUncancelable();
95
+ return;
96
+ }
97
+ // `schedule` runs user code, so a signal can abort after `startOrRideTasks`
98
+ // checked it.
99
+ if (signal.aborted) {
100
+ cancellation.lastCancelReason = signal.reason;
101
+ return;
102
+ }
103
+ cancellation.uncancelledRequests += 1;
104
+ const listener = () => {
105
+ cancellation.lastCancelReason = signal.reason;
106
+ this.release();
107
+ };
108
+ signal.addEventListener("abort", listener, { once: true });
109
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- The task owns its listener list, and removes every listener later.
110
+ cancellation.abortListeners.push([signal, listener]);
111
+ }
112
+ /**
113
+ * Clears `#cancellation`, and removes its abort listeners, which can no
114
+ * longer abort the task. Calling it again does nothing.
115
+ */
116
+ #makeUncancelable() {
117
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Removes each listener for its side effect.
118
+ this.#cancellation?.abortListeners.forEach(([signal, listener]) => {
119
+ signal.removeEventListener("abort", listener);
120
+ });
121
+ this.#cancellation = undefined;
122
+ }
123
+ }
124
+ /**
125
+ * Builds a task's start, and the controller that aborts its signal. The
126
+ * controller stays out of the start because `schedule` receives the start,
127
+ * and only the task may abort its own signal.
128
+ */
129
+ function newTaskStart(input, cancellable, now, startReason) {
130
+ const controller = cancellable ? new AbortController() : undefined;
131
+ return {
132
+ start: {
133
+ startReason,
134
+ startedAt: now,
135
+ input,
136
+ signal: controller?.signal,
137
+ collapsedRequestCount: 0,
138
+ },
139
+ controller,
140
+ };
141
+ }
142
+ /**
143
+ * Lets requests for the same work share one pending task, with optional
144
+ * cancellation.
145
+ *
146
+ * Imagine you have a function that, when called, kicks off a task, and
147
+ * returns a promise representing the result of the task. Now, suppose the task
148
+ * is expensive, but not effectful, like fetching from a REST API. If your code
149
+ * attempts to run this task many times in quick succession, or many times in
150
+ * parallel, this can be wasteful (if the result will always or likely be the
151
+ * same) and/or overload whatever's doing work as part of the task (eg the API).
152
+ *
153
+ * There are a large number of npm packages that make it easy to better schedule
154
+ * this work (e.g., queueing some parallel runs of the task behind others, if
155
+ * the number of tasks already running exceeds some concurrency limit) or to
156
+ * reuse the results of some runs of the task for others (by debouncing or
157
+ * throttling calls to the function that starts the task). However, none of them
158
+ * have quite the -- admittedly a bit quirky -- semantics that we want, and that
159
+ * we implement here.
160
+ *
161
+ * Specifically, a task request reuses a pending task, instead of starting a
162
+ * new one, if and only if: 1) its input has the same key as the task's input
163
+ * [a la memoize]; 2) the task started no more than `collapseTasksMs` ago [a la
164
+ * throttle]; and 3) the task is still running [unique], and is not cancelled.
165
+ * In other words, it memoizes task creation, but then reverts to starting a
166
+ * new task after a certain number of milliseconds or after the last-started
167
+ * task finishes, whichever is first. So it's sort of a combination of memoize,
168
+ * throttle, and promise chaining.
169
+ *
170
+ * Beyond that collapsing, it adds three things:
171
+ *
172
+ * - One `startOrRideTasks` call takes many task requests. Each task request
173
+ * either starts a new task or rides a pending task for its input's key. The
174
+ * tasks that one call starts are scheduled together, in one `schedule` call,
175
+ * so a bulk caller's new inputs become one unit of work. Task requests that
176
+ * ride start nothing.
177
+ * - Each task has an optional AbortSignal that aborts when every request on the
178
+ * task is cancelled. `schedule` passes it to whatever runs the work, such as
179
+ * a batcher's queue or a producer that supports cancellation. See
180
+ * `SharedTaskView.signal`.
181
+ * - It counts the task requests that ride each task, for diagnostics.
182
+ *
183
+ * For the simple case, with one call per task request and no cancellation,
184
+ * `wrapWithTaskCollapsing` wraps a task-starting function with this class.
185
+ *
186
+ * Terms:
187
+ * - An input is the value that a task's work needs, such as a producer request.
188
+ * `makeKey` derives its key.
189
+ * - A task request is one input plus an optional signal that cancels the task
190
+ * request. It is one item of `startOrRideTasks`'s `taskRequests`.
191
+ * - A task request starts a task or rides one. A task's requests are the task
192
+ * request that started it and every task request that rides it.
193
+ * - A task request is cancelled when its signal aborts. A task is cancelled
194
+ * when all of its requests are cancelled.
195
+ *
196
+ * The creator itself does not queue, batch or limit work. The `schedule`
197
+ * callback owns that.
198
+ *
199
+ * A task stops being ridable when it settles, when it is cancelled, or when it
200
+ * started more than `collapseTasksMs` ago. A cancelled task can still be
201
+ * running, but a new task request must not ride work that is being cancelled.
202
+ */
203
+ export class CollapsedTaskCreator {
204
+ #collapseTasksMs;
205
+ #makeKey;
206
+ #schedule;
207
+ /**
208
+ * For each key, the newest pending task: the task that a new task request
209
+ * for that key rides, unless it is too old or cancelled. A cancelled task
210
+ * stays here until it settles or a newer task replaces it; the lookup in
211
+ * `startOrRideTasks` checks its signal, so no request rides it. A newer
212
+ * task replaces an older one whose start is outside `collapseTasksMs`, or
213
+ * a cancelled one; the replaced task keeps running, tracked only in
214
+ * `#live`.
215
+ */
216
+ #latestPendingTaskByKey = new Map();
217
+ /**
218
+ * Every task that has not settled. `#latestPendingTaskByKey` leaves out two
219
+ * kinds of unsettled task: tasks with no key, and tasks that a newer task
220
+ * replaced. `drain` must still wait for all of them.
221
+ */
222
+ #live = new Set();
223
+ /**
224
+ * The keys of the tasks that a running `schedule` call is starting. Those
225
+ * tasks enter `#latestPendingTaskByKey` only after `schedule` returns. So
226
+ * when `schedule` runs a producer synchronously, and that producer requests
227
+ * its own key, a nested call could not ride the task and would start it
228
+ * again, without end. The nested call throws instead.
229
+ *
230
+ * Tasks with no key are never reserved, because nothing can ride them. A
231
+ * key stays here only while its `schedule` call runs. The set holds keys
232
+ * from several calls only while `schedule` calls nest, with different keys.
233
+ */
234
+ #schedulingKeys = new Set();
235
+ constructor(options) {
236
+ this.#collapseTasksMs = options.collapseTasksMs;
237
+ this.#makeKey = options.makeKey;
238
+ this.#schedule = options.schedule;
239
+ }
240
+ /**
241
+ * Finds the pending task that a task request for `key` may ride, or says
242
+ * why there is none. A task that is both too old and cancelled reports
243
+ * `pendingTaskTooOld`.
244
+ */
245
+ #findRidable(now, key) {
246
+ const existing = this.#latestPendingTaskByKey.get(key);
247
+ if (!existing)
248
+ return { startReason: "noPendingTask" };
249
+ const { signal, startedAt } = existing.start;
250
+ if (now - startedAt > this.#collapseTasksMs) {
251
+ return { startReason: "pendingTaskTooOld" };
252
+ }
253
+ if (signal?.aborted)
254
+ return { startReason: "pendingTaskCancelled" };
255
+ return { ride: existing };
256
+ }
257
+ #setupNewTask(start, controller, scheduleResult, key) {
258
+ const task = new Task(start, controller, scheduleResult, () => {
259
+ this.#live.delete(task);
260
+ // Only remove this task if the map still holds it for its key. The map
261
+ // could hold a newer one if this task was replaced for being too old or
262
+ // cancelled.
263
+ if (key !== undefined && this.#latestPendingTaskByKey.get(key) === task) {
264
+ this.#latestPendingTaskByKey.delete(key);
265
+ }
266
+ });
267
+ this.#live.add(task);
268
+ // If the key is defined, this task can be the existing pending task
269
+ // for future requests with the same key, so register it.
270
+ if (key !== undefined) {
271
+ this.#latestPendingTaskByKey.set(key, task);
272
+ }
273
+ return task;
274
+ }
275
+ /**
276
+ * Each task request rides a pending task for its input's key, or starts a
277
+ * new task. Schedules the new tasks with one `schedule` call. When every
278
+ * task request rides, it does not call `schedule`.
279
+ *
280
+ * A task request whose signal is already aborted, or aborts during
281
+ * `makeKey`, starts and rides nothing. Its `result` rejects with the
282
+ * signal's reason, and the call's other task requests are not affected.
283
+ *
284
+ * It changes no state until `schedule` returns, except that it holds the
285
+ * tasks it rides. So when `makeKey` throws, `schedule` throws, or
286
+ * `schedule` returns the wrong number of entries, it releases those tasks,
287
+ * throws, and changes nothing else. It also holds
288
+ * each task it starts until it has recorded all of its task requests (see
289
+ * `Task.hold`).
290
+ *
291
+ * The tasks this call starts become ridable only after `schedule`
292
+ * returns. A `schedule` may call `startOrRideTasks` again, for example by
293
+ * running a producer that reads other keys. But a nested call that
294
+ * requests a key this call is starting throws a `SchedulingContractError`:
295
+ * that request depends on its own result.
296
+ *
297
+ * @param taskRequests Each task request's input, and an optional signal
298
+ * that cancels the task request.
299
+ * @param context Passed unchanged to `schedule`, if this call starts any
300
+ * task. A nested call passes its own context. A creator whose `schedule`
301
+ * needs none keeps the default `Context`, `void`, so its callers omit
302
+ * it. TypeScript lets a caller omit a trailing argument only when its
303
+ * type accepts `void`; `undefined` does not count.
304
+ * @returns One entry per task request, in order. `result` is the task's
305
+ * shared result. It does not reject when only this task request is
306
+ * cancelled, because the task's other requests may still need the
307
+ * result; a caller races its own signal. `handle` is the handle that the
308
+ * task's `schedule` returned. A caller that wants its work run at once
309
+ * can dispatch the still-queued tasks behind these handles, including
310
+ * the tasks it rode. It is undefined for a cancelled task request.
311
+ * `rode` is true when the task request rode a task that it did not
312
+ * start.
313
+ */
314
+ startOrRideTasks(taskRequests, context) {
315
+ // A cancelled task request needs no key. Signals are read again after
316
+ // every key: `makeKey` is user code and may abort a signal.
317
+ const keys = taskRequests.map(({ input, signal }) => signal?.aborted ? undefined : this.#makeKey(input));
318
+ // A cancelled task request starts and rides nothing, and gets only its
319
+ // own rejection. The split happens once, here, so that a later abort
320
+ // cannot change which requests this call treats as cancelled.
321
+ const [cancelled, live] = partition(taskRequests.map((request, i) => ({ request, i, key: keys[i] })), ({ request: { signal } }) => signal?.aborted === true);
322
+ if (live.some(({ key }) => key !== undefined && this.#schedulingKeys.has(key))) {
323
+ throw new SchedulingContractError("schedule requested a key that it is starting a task for");
324
+ }
325
+ // Group. Requests with the same key share one task, so each group
326
+ // is one task that this call touches.
327
+ const requestsByKey = Map.groupBy(live, ({ key }) => key);
328
+ // Decide. Each group rides the pending task for its key, or starts
329
+ // a new task. The group's first request decides, as if it came alone.
330
+ const now = Date.now();
331
+ const decisions = requestsByKey
332
+ .entries()
333
+ .flatMap(([key, reqs]) => {
334
+ if (key === undefined) {
335
+ return reqs.map(({ request: { input, signal }, i }) => ({
336
+ type: "START",
337
+ key: undefined,
338
+ ...newTaskStart(input, signal !== undefined, now, "noKey"),
339
+ indexes: [i],
340
+ }));
341
+ }
342
+ const ridableLookup = this.#findRidable(now, key);
343
+ if ("ride" in ridableLookup) {
344
+ return [
345
+ {
346
+ type: "RIDE",
347
+ task: ridableLookup.ride,
348
+ indexes: reqs.map(({ i }) => i),
349
+ },
350
+ ];
351
+ }
352
+ // oxlint-disable-next-line typescript/no-non-null-assertion -- Map.groupBy never creates an empty group.
353
+ const { input, signal } = reqs[0].request;
354
+ return [
355
+ {
356
+ type: "START",
357
+ key,
358
+ ...newTaskStart(input, signal !== undefined, now, ridableLookup.startReason),
359
+ indexes: reqs.map(({ i }) => i),
360
+ },
361
+ ];
362
+ })
363
+ // Map.groupBy puts every request with no key in one group, at the
364
+ // position of the first one. Sort by first request, so that `schedule`
365
+ // receives the started tasks in task request order.
366
+ .toArray()
367
+ .toSorted((a, b) => a.indexes[0] - b.indexes[0]);
368
+ // Schedule the new tasks.
369
+ const startDecisions = decisions.filter((d) => d.type === "START");
370
+ const taskStarts = startDecisions.map((d) => d.start);
371
+ // Every task this call touches, held as `Task.hold` describes: the
372
+ // ridden tasks now, and each started task when its requests are
373
+ // recorded. All releases wait until the end, because a release can abort
374
+ // a task and run user code.
375
+ const heldTasks = new Set(decisions.filter((d) => d.type === "RIDE").map((d) => d.task));
376
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Holds each task for its side effect.
377
+ heldTasks.forEach((task) => task.hold());
378
+ try {
379
+ // Reserve keys for the tasks we're about to schedule (to prevent
380
+ // infinite loop on re-entrant calls).
381
+ const scheduled = (() => {
382
+ if (taskStarts.length === 0) {
383
+ return [];
384
+ }
385
+ const scheduledKeys = new Set(startDecisions.map((d) => d.key).filter((key) => key !== undefined));
386
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Reserves each key for its side effect.
387
+ scheduledKeys.forEach((key) => this.#schedulingKeys.add(key));
388
+ try {
389
+ return this.#schedule(taskStarts, context);
390
+ }
391
+ finally {
392
+ // Free the keys before any release: a release can abort a
393
+ // task, and its abort listeners may request those keys, which are
394
+ // ridable by then.
395
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Frees each key for its side effect.
396
+ scheduledKeys.forEach((key) => this.#schedulingKeys.delete(key));
397
+ }
398
+ })();
399
+ if (scheduled.length !== taskStarts.length) {
400
+ throw new SchedulingContractError("schedule must return one scheduled task per task to start");
401
+ }
402
+ // Create a Task for each new task, and set up the initial bookkeeping
403
+ // for it. The map lets the record loop below, which walks RIDE and
404
+ // START decisions interleaved, find each START's new task.
405
+ const newTasksByStartDecision = new Map(startDecisions.map((startDecision, i) => {
406
+ const { start, controller, key } = startDecision;
407
+ const task = this.#setupNewTask(start, controller,
408
+ // oxlint-disable-next-line typescript/no-non-null-assertion -- The length check above guarantees one entry per start decision.
409
+ scheduled[i], key);
410
+ return [start, task];
411
+ }));
412
+ // Record every request on its task, and build one entry per
413
+ // request, back in request order. Every request in a ridden group
414
+ // rode; in a started group, only the first did not.
415
+ const result = Array(taskRequests.length);
416
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Fills each cancelled request's result slot.
417
+ cancelled.forEach(({ request: { signal }, i }) => {
418
+ // An aborted signal's reason never changes.
419
+ // oxlint-disable-next-line typescript/prefer-promise-reject-errors -- Rejects with the signal's abort reason, which can be any value.
420
+ const rejected = Promise.reject(signal?.reason);
421
+ // A caller may never await it.
422
+ // oxlint-disable-next-line no-void -- Marks the rejection handled; the caller still gets the rejected promise.
423
+ void rejected.catch(() => { });
424
+ result[i] = { result: rejected, handle: undefined, rode: false };
425
+ });
426
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Records each decision's requests for their side effects.
427
+ decisions.forEach((d) => {
428
+ switch (d.type) {
429
+ case "RIDE": {
430
+ const outcome = {
431
+ result: d.task.result,
432
+ handle: d.task.handle,
433
+ rode: true,
434
+ };
435
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Records each request on its task for its side effect.
436
+ d.indexes.forEach((reqIdx) => {
437
+ // NB: task is still held from before schedule()
438
+ // oxlint-disable-next-line typescript/no-non-null-assertion -- Each index comes from taskRequests.
439
+ d.task.addRequest(taskRequests[reqIdx].signal, true);
440
+ result[reqIdx] = outcome;
441
+ });
442
+ break;
443
+ }
444
+ case "START": {
445
+ // oxlint-disable-next-line typescript/no-non-null-assertion -- The map holds a task for every start decision.
446
+ const task = newTasksByStartDecision.get(d.start);
447
+ // See `heldTasks`. Redundant when the key is undefined: that task
448
+ // has exactly one request, and nothing can ride it.
449
+ task.hold();
450
+ heldTasks.add(task);
451
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Records each request on its task for its side effect.
452
+ d.indexes.forEach((reqIdx, i) => {
453
+ const rode = i > 0;
454
+ // oxlint-disable-next-line typescript/no-non-null-assertion -- Each index comes from taskRequests.
455
+ task.addRequest(taskRequests[reqIdx].signal, rode);
456
+ result[reqIdx] = {
457
+ result: task.result,
458
+ handle: task.handle,
459
+ rode,
460
+ };
461
+ });
462
+ break;
463
+ }
464
+ default:
465
+ throw new Error(`Unexpected decision: ${String(d)}`);
466
+ }
467
+ });
468
+ // The loop above fills every position of `result`, one per task
469
+ // request, but TS can't prove that it matches the mapped tuple type.
470
+ return result;
471
+ }
472
+ finally {
473
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Releases each task for its side effect.
474
+ heldTasks.forEach((task) => task.release());
475
+ }
476
+ }
477
+ /**
478
+ * Waits until every task has settled, including cancelled ones. It loops
479
+ * because work that settles can start new tasks, for example a producer
480
+ * that reads other keys. Those tasks join `#live` after the snapshot that
481
+ * one `allSettled` waits on.
482
+ */
483
+ async drain() {
484
+ // oxlint-disable-next-line zingage/no-restricted-syntax -- Loops until no task is live, because settling work can start new tasks.
485
+ while (this.#live.size > 0) {
486
+ // oxlint-disable-next-line typescript/promise-function-async -- Passes the existing promises; an async wrapper adds nothing.
487
+ await Promise.allSettled([...this.#live].map((task) => task.result));
488
+ }
489
+ }
490
+ }
491
+ //# sourceMappingURL=collapsed-task-creator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collapsed-task-creator.js","sourceRoot":"","sources":["../src/collapsed-task-creator.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,10 @@
1
1
  export { waitFor, timeSlicedFilter, timeSlicedFlatMap, timeSlicedForEach, timeSlicedGroupBy, timeSlicedMap, timeSlicedMapFromEntries, TimeSliceBudget, } from "./time-slicing.js";
2
2
  export { timeSlicedSort } from "./time-sliced-sort.js";
3
+ export { timeSlicedFind, timeSlicedSome } from "./time-sliced-search.js";
4
+ export { CollapsedTaskCreator, type ScheduleTasks, type SharedTaskView, type TaskRequestOutcome, type TaskRequestOutcomes, type TaskStartReason, } from "./collapsed-task-creator.js";
5
+ export { wrapWithTaskCollapsing } from "./wrap-with-task-collapsing.js";
6
+ export { Batcher, DEFAULT_MAX_BATCH_SIZE, type BatchHandle, type BatchItem, type BatchOptions, type DispatchedItem, type DispatchInfo, type Enqueued, } from "./batcher.js";
7
+ export { PendingPromisesTracker } from "./pending-promises-tracker.js";
8
+ export { SchedulingContractError } from "./scheduling-contract-error.js";
9
+ export type { Logger, SchedulingComponentName } from "./logger.js";
3
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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;AACzE,OAAO,EACL,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EACL,OAAO,EACP,sBAAsB,EACtB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,QAAQ,GACd,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,YAAY,EAAE,MAAM,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,9 @@
1
1
  export { waitFor, timeSlicedFilter, timeSlicedFlatMap, timeSlicedForEach, timeSlicedGroupBy, timeSlicedMap, timeSlicedMapFromEntries, TimeSliceBudget, } from "./time-slicing.js";
2
2
  export { timeSlicedSort } from "./time-sliced-sort.js";
3
+ export { timeSlicedFind, timeSlicedSome } from "./time-sliced-search.js";
4
+ export { CollapsedTaskCreator, } from "./collapsed-task-creator.js";
5
+ export { wrapWithTaskCollapsing } from "./wrap-with-task-collapsing.js";
6
+ export { Batcher, DEFAULT_MAX_BATCH_SIZE, } from "./batcher.js";
7
+ export { PendingPromisesTracker } from "./pending-promises-tracker.js";
8
+ export { SchedulingContractError } from "./scheduling-contract-error.js";
3
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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"}
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;AACzE,OAAO,EACL,oBAAoB,GAMrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EACL,OAAO,EACP,sBAAsB,GAOvB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /** The components in this package that write log lines. */
2
+ export type SchedulingComponentName = "batcher" | "collapsed-task-creator";
3
+ /**
4
+ * The signature of a custom log function. `Batcher` and
5
+ * `wrapWithTaskCollapsing` accept one, and call it when they log a message.
6
+ *
7
+ * A logger that accepts more component names, such as a logger shared with
8
+ * other packages, is also a valid `Logger`.
9
+ */
10
+ export type Logger = (component: SchedulingComponentName, level: "trace" | "debug" | "info" | "warn" | "error" | "fatal", message: string, data?: unknown) => void;
11
+ /** The default logger of each component. */
12
+ export declare const defaultLoggersByComponent: {
13
+ batcher: Logger;
14
+ "collapsed-task-creator": Logger;
15
+ };
16
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,2DAA2D;AAC3D,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,wBAAwB,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,MAAM,MAAM,GAAG,CACnB,SAAS,EAAE,uBAAuB,EAClC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,EAC9D,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,OAAO,KACX,IAAI,CAAC;AAUV,4CAA4C;AAC5C,eAAO,MAAM,yBAAyB;;;CAGa,CAAC"}
package/dist/logger.js ADDED
@@ -0,0 +1,14 @@
1
+ import { default as debug } from "debug";
2
+ /** A logger that writes to the `debug` namespace `@zingage/scheduling:<name>`. */
3
+ function debugLogger(name) {
4
+ const write = debug(`@zingage/scheduling:${name}`);
5
+ return (_, level, message, data) => {
6
+ write(`(${level}) ${message} %O`, data);
7
+ };
8
+ }
9
+ /** The default logger of each component. */
10
+ export const defaultLoggersByComponent = {
11
+ batcher: debugLogger("batcher"),
12
+ "collapsed-task-creator": debugLogger("collapsed-task-creator"),
13
+ };
14
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC;AAmBzC,kFAAkF;AAClF,SAAS,WAAW,CAAC,IAA6B;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;IACnD,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACjC,KAAK,CAAC,IAAI,KAAK,KAAK,OAAO,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC;AACJ,CAAC;AAED,4CAA4C;AAC5C,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC;IAC/B,wBAAwB,EAAE,WAAW,CAAC,wBAAwB,CAAC;CACd,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Tracks promises until they settle, so that an owner's `close` can wait for
3
+ * the work it accepted.
4
+ *
5
+ * Each tracked promise leaves the tracker when it settles. Tracking never
6
+ * creates an unhandled rejection: the tracker observes a rejection, but the
7
+ * caller still owns the promise and its rejection.
8
+ *
9
+ * The tracker has no closed state. Each owner decides what new work does
10
+ * after its own `close` starts, and in what order it drains its trackers.
11
+ */
12
+ export declare class PendingPromisesTracker {
13
+ #private;
14
+ /** The number of tracked promises that have not settled yet. */
15
+ get size(): number;
16
+ /** Tracks `promise` until it settles, and returns it unchanged. */
17
+ track<T>(promise: Promise<T>): Promise<T>;
18
+ /**
19
+ * Calls `start` and tracks its result. The promise is tracked before
20
+ * `start` runs, so a `drain` that `start` itself triggers synchronously
21
+ * still waits for it. A synchronous throw from `start` becomes a rejection.
22
+ */
23
+ run<T>(start: () => Promise<T>): Promise<T>;
24
+ /**
25
+ * Resolves once no tracked promise is pending, including promises tracked
26
+ * while it waits. It loops because settling work can start more work, for
27
+ * example a producer that reads other keys from its own wrapper. It never
28
+ * rejects.
29
+ */
30
+ drain(): Promise<void>;
31
+ }
32
+ //# sourceMappingURL=pending-promises-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-promises-tracker.d.ts","sourceRoot":"","sources":["../src/pending-promises-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,qBAAa,sBAAsB;;IAGjC,gEAAgE;IAChE,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,mEAAmE;IAEnE,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQxC;IAED;;;;OAIG;IAEH,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAU1C;IAED;;;;;OAKG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAK3B;CACF"}