@async/framework 0.7.0 → 0.9.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/src/component.js CHANGED
@@ -113,10 +113,15 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
113
113
  html,
114
114
  attach(target) {
115
115
  for (const hook of attachHooks) {
116
- const cleanup = hook(target);
117
- if (typeof cleanup === "function") {
118
- cleanups.push(cleanup);
119
- }
116
+ runtime.scheduler?.enqueue("lifecycle", () => {
117
+ const cleanup = hook(target);
118
+ if (typeof cleanup === "function") {
119
+ cleanups.push(cleanup);
120
+ }
121
+ }, {
122
+ scope,
123
+ key: `attach:${attachHooks.indexOf(hook)}`
124
+ }) ?? runAttachHook(hook, target);
120
125
  }
121
126
  },
122
127
  mount(target) {
@@ -124,7 +129,17 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
124
129
  },
125
130
  visible(target, observeVisible) {
126
131
  for (const hook of visibleHooks) {
127
- const cleanup = observeVisible(target, hook);
132
+ const cleanup = observeVisible(target, () => {
133
+ runtime.scheduler?.enqueue("lifecycle", () => {
134
+ const hookCleanup = hook(target);
135
+ if (typeof hookCleanup === "function") {
136
+ cleanups.push(hookCleanup);
137
+ }
138
+ }, {
139
+ scope,
140
+ key: `visible:${visibleHooks.indexOf(hook)}`
141
+ }) ?? runVisibleHook(hook, target);
142
+ });
128
143
  if (typeof cleanup === "function") {
129
144
  cleanups.push(cleanup);
130
145
  }
@@ -134,6 +149,7 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
134
149
  while (destroyHooks.length > 0) {
135
150
  destroyHooks.pop()?.();
136
151
  }
152
+ runtime.scheduler?.markScopeDestroyed(scope);
137
153
  while (cleanups.length > 0) {
138
154
  cleanups.pop()?.();
139
155
  }
@@ -142,10 +158,24 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
142
158
  }
143
159
  }
144
160
  };
161
+
162
+ function runAttachHook(hook, target) {
163
+ const cleanup = hook(target);
164
+ if (typeof cleanup === "function") {
165
+ cleanups.push(cleanup);
166
+ }
167
+ }
168
+
169
+ function runVisibleHook(hook, target) {
170
+ const cleanup = hook(target);
171
+ if (typeof cleanup === "function") {
172
+ cleanups.push(cleanup);
173
+ }
174
+ }
145
175
  }
146
176
 
147
177
  function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks, renderScopedTemplate }) {
148
- const { signals, handlers, loader, server, router, cache } = runtime;
178
+ const { signals, handlers, loader, server, router, cache, scheduler } = runtime;
149
179
  const generatedHandlers = new WeakMap();
150
180
  let generatedHandlerCounter = 0;
151
181
  let generatedSignalCounter = 0;
@@ -157,6 +187,7 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
157
187
  server,
158
188
  router,
159
189
  cache,
190
+ scheduler,
160
191
 
161
192
  signal(name, initial) {
162
193
  if (arguments.length === 1) {
@@ -201,7 +232,11 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
201
232
  },
202
233
 
203
234
  effect(fn) {
204
- const cleanup = signals.effect(() => fn.call(context));
235
+ const cleanup = signals.effect(() => fn.call(context), {
236
+ scheduler,
237
+ phase: "effect",
238
+ scope
239
+ });
205
240
  cleanups.push(cleanup);
206
241
  return cleanup;
207
242
  },
package/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { asyncSignal } from "./async-signal.js";
2
- export { Async, createApp, defineApp, readSnapshot } from "./app.js";
2
+ export { Async, createApp, defineApp, readSnapshot } from "./server-entry.js";
3
3
  export { attributeName, defineAttributeConfig } from "./attributes.js";
4
+ export { createBoundaryReceiver } from "./boundary-receiver.js";
4
5
  export { createCacheRegistry, defineCache } from "./cache.js";
5
6
  export { component, createComponentRegistry, defineComponent } from "./component.js";
6
7
  export { delay } from "./delay.js";
@@ -10,5 +11,8 @@ export { Loader, AsyncLoader } from "./loader.js";
10
11
  export { createPartialRegistry } from "./partials.js";
11
12
  export { createRegistryStore } from "./registry-store.js";
12
13
  export { createRouteRegistry, createRouter, defineRoute, route } from "./router.js";
13
- export { createServerProxy, createServerRegistry } from "./server.js";
14
+ export { createScheduler } from "./scheduler.js";
15
+ export { createRequestContextStore } from "./request-context.js";
16
+ export { applyServerResult, createServerProxy, resolveServerCommandArguments, unwrapServerResult } from "./server.js";
17
+ export { createServerRegistry } from "./server-registry.js";
14
18
  export { computed, createSignal, createSignalRegistry, effect, signal } from "./signals.js";
package/src/loader.js CHANGED
@@ -1,15 +1,18 @@
1
1
  import { renderComponent } from "./component.js";
2
2
  import { createHandlerRegistry } from "./handlers.js";
3
+ import { createScheduler } from "./scheduler.js";
3
4
  import { createSignalRegistry, isSignalRef } from "./signals.js";
4
5
  import { matchAttribute, normalizeAttributeConfig, readAttribute } from "./attributes.js";
5
6
 
6
7
  const inlineBindingPrefix = "__async:inline:";
7
8
 
8
- export function Loader({ root, signals, handlers, server, router, cache, attributes } = {}) {
9
+ export function Loader({ root, signals, handlers, server, router, cache, attributes, scheduler } = {}) {
9
10
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
10
11
  const rootNode = root ?? documentRef;
11
12
  const signalRegistry = signals ?? createSignalRegistry();
12
13
  const handlerRegistry = handlers ?? createHandlerRegistry();
14
+ const schedulerInstance = scheduler ?? createScheduler();
15
+ const ownsScheduler = !scheduler;
13
16
  const attributeConfig = normalizeAttributeConfig(attributes);
14
17
  const cleanups = new Set();
15
18
  const eventBindings = new WeakMap();
@@ -30,6 +33,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
30
33
  server,
31
34
  router,
32
35
  cache,
36
+ scheduler: schedulerInstance,
33
37
  attributes: attributeConfig,
34
38
 
35
39
  start() {
@@ -69,6 +73,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
69
73
  server: api.server,
70
74
  router: api.router,
71
75
  cache: api.cache,
76
+ scheduler: schedulerInstance,
72
77
  attributes: attributeConfig
73
78
  });
74
79
  cleanupChildren(target);
@@ -89,6 +94,9 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
89
94
  runCleanup(cleanup);
90
95
  }
91
96
  cleanups.clear();
97
+ if (ownsScheduler) {
98
+ schedulerInstance.destroy();
99
+ }
92
100
  },
93
101
 
94
102
  _observeVisible(target, fn) {
@@ -106,13 +114,14 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
106
114
  }
107
115
  };
108
116
 
109
- signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
117
+ signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache, scheduler: schedulerInstance });
110
118
  api.server?._setContext?.({
111
119
  signals: signalRegistry,
112
120
  handlers: handlerRegistry,
113
121
  loader: api,
114
122
  router: api.router,
115
- cache: api.cache
123
+ cache: api.cache,
124
+ scheduler: schedulerInstance
116
125
  });
117
126
 
118
127
  function bindEventAttributes(scope) {
@@ -144,18 +153,19 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
144
153
 
145
154
  const listener = async (event) => {
146
155
  try {
147
- await handlerRegistry.run(ref, {
156
+ await schedulerInstance.batch(() => handlerRegistry.run(ref, {
148
157
  signals: signalRegistry,
149
158
  handlers: handlerRegistry,
150
159
  loader: api,
151
160
  server: api.server,
152
161
  router: api.router,
153
162
  cache: api.cache,
163
+ scheduler: schedulerInstance,
154
164
  event,
155
165
  element,
156
166
  el: element,
157
167
  root: rootNode
158
- });
168
+ }));
159
169
  } catch (error) {
160
170
  dispatchAsyncError(element, error);
161
171
  }
@@ -271,7 +281,12 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
271
281
 
272
282
  const read = () => readBinding(path, options);
273
283
  apply(read());
274
- addCleanup(subscribeBinding(path, () => apply(read())), element);
284
+ addCleanup(subscribeBinding(path, () => {
285
+ schedulerInstance.enqueue("binding", () => apply(read()), {
286
+ scope: element,
287
+ key
288
+ });
289
+ }), element);
275
290
  }
276
291
 
277
292
  function bindValueWriter(element, path) {
@@ -332,7 +347,12 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
332
347
  const state = {
333
348
  id,
334
349
  templates,
335
- cleanup: signalRegistry.subscribe(`${id}.$status`, () => renderBoundary(boundary))
350
+ cleanup: signalRegistry.subscribe(`${id}.$status`, () => {
351
+ schedulerInstance.enqueue("binding", () => renderBoundary(boundary), {
352
+ scope: boundary,
353
+ key: `boundary:${id}`
354
+ });
355
+ })
336
356
  };
337
357
  boundaryState.set(boundary, state);
338
358
  addCleanup(state.cleanup, boundary);
@@ -372,7 +392,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
372
392
  }
373
393
  mountedElements.add(element);
374
394
  for (const ref of refs) {
375
- runPseudo(element, ref);
395
+ scheduleLifecycle(element, () => runPseudo(element, ref), `attach:${ref}`);
376
396
  }
377
397
  }
378
398
 
@@ -385,7 +405,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
385
405
  continue;
386
406
  }
387
407
  visibleElements.add(element);
388
- addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
408
+ addCleanup(observeVisible(element, () => scheduleLifecycle(element, () => runPseudo(element, ref), `visible:${ref}`)), element);
389
409
  }
390
410
  }
391
411
 
@@ -409,6 +429,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
409
429
  server: api.server,
410
430
  router: api.router,
411
431
  cache: api.cache,
432
+ scheduler: schedulerInstance,
412
433
  element,
413
434
  el: element,
414
435
  root: rootNode
@@ -427,10 +448,13 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
427
448
  const ownerWindow = target.ownerDocument?.defaultView ?? globalThis;
428
449
  const Observer = ownerWindow.IntersectionObserver ?? globalThis.IntersectionObserver;
429
450
  if (!Observer) {
430
- queueMicrotask(() => {
451
+ schedulerInstance.enqueue("lifecycle", () => {
431
452
  if (!destroyed) {
432
453
  fn(target);
433
454
  }
455
+ }, {
456
+ scope: target,
457
+ key: "visible:fallback"
434
458
  });
435
459
  return () => {};
436
460
  }
@@ -485,6 +509,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
485
509
  }
486
510
  for (const element of elementsIn(node)) {
487
511
  runScopedCleanups(element);
512
+ schedulerInstance.markScopeDestroyed(element);
488
513
  }
489
514
  }
490
515
 
@@ -508,6 +533,13 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
508
533
  }
509
534
  }
510
535
 
536
+ function scheduleLifecycle(element, fn, key) {
537
+ schedulerInstance.enqueue("lifecycle", fn, {
538
+ scope: element,
539
+ key
540
+ });
541
+ }
542
+
511
543
  return api;
512
544
  }
513
545
 
@@ -0,0 +1,40 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
3
+ export function createRequestContextStore() {
4
+ const storage = new AsyncLocalStorage();
5
+
6
+ return {
7
+ storage,
8
+
9
+ run(context, fn, ...args) {
10
+ if (typeof fn !== "function") {
11
+ throw new TypeError("requestContext.run(context, fn) requires a function.");
12
+ }
13
+ return storage.run(context ?? {}, fn, ...args);
14
+ },
15
+
16
+ get() {
17
+ return storage.getStore();
18
+ },
19
+
20
+ snapshot() {
21
+ return { ...(storage.getStore() ?? {}) };
22
+ }
23
+ };
24
+ }
25
+
26
+ export function readRequestContext(store) {
27
+ if (!store) {
28
+ return {};
29
+ }
30
+ if (typeof store.get === "function") {
31
+ return store.get() ?? {};
32
+ }
33
+ if (typeof store.getStore === "function") {
34
+ return store.getStore() ?? {};
35
+ }
36
+ if (typeof store === "object") {
37
+ return store;
38
+ }
39
+ return {};
40
+ }
package/src/router.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Loader } from "./loader.js";
2
2
  import { createHandlerRegistry } from "./handlers.js";
3
+ import { createScheduler } from "./scheduler.js";
3
4
  import { createSignalRegistry } from "./signals.js";
4
5
  import { applyServerResult } from "./server.js";
5
6
  import { createRegistryStore } from "./registry-store.js";
@@ -121,12 +122,15 @@ export function createRouter({
121
122
  partials,
122
123
  fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
123
124
  routeEndpoint = "/__async/route",
124
- attributes
125
+ attributes,
126
+ scheduler
125
127
  } = {}) {
126
128
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
127
129
  const rootNode = root ?? documentRef;
128
130
  const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
129
131
  const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
132
+ const schedulerInstance = scheduler ?? loader?.scheduler ?? createScheduler();
133
+ const ownsScheduler = !scheduler && !loader?.scheduler;
130
134
  const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
131
135
  const loaderInstance =
132
136
  loader ??
@@ -136,6 +140,7 @@ export function createRouter({
136
140
  handlers: handlerRegistry,
137
141
  server,
138
142
  cache,
143
+ scheduler: schedulerInstance,
139
144
  attributes: attributeConfig
140
145
  });
141
146
  const ownsLoader = !loader;
@@ -155,12 +160,13 @@ export function createRouter({
155
160
  server,
156
161
  cache,
157
162
  partials,
163
+ scheduler: schedulerInstance,
158
164
  attributes: attributeConfig,
159
165
 
160
166
  start() {
161
167
  assertActive();
162
168
  loaderInstance.router = api;
163
- signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache });
169
+ signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache, scheduler: schedulerInstance });
164
170
  if (ownsLoader) {
165
171
  loaderInstance.start();
166
172
  }
@@ -224,6 +230,9 @@ export function createRouter({
224
230
  cleanup();
225
231
  }
226
232
  cleanups.clear();
233
+ if (ownsScheduler) {
234
+ schedulerInstance.destroy();
235
+ }
227
236
  }
228
237
  };
229
238
 
@@ -329,13 +338,16 @@ export function createRouter({
329
338
  loader: loaderInstance,
330
339
  router: api,
331
340
  cache,
341
+ scheduler: schedulerInstance,
332
342
  abort: navigation?.abort
333
343
  });
344
+ await schedulerInstance.flush();
334
345
  if (!isActiveNavigation(navigation)) {
335
346
  return;
336
347
  }
337
348
  if (result?.html != null && !result.boundary && !result.redirect) {
338
349
  loaderInstance.swap(boundary, result.html);
350
+ await schedulerInstance.flush();
339
351
  }
340
352
  if (result?.redirect || options.history === false) {
341
353
  return;
@@ -380,6 +392,7 @@ export function createRouter({
380
392
  loader: loaderInstance,
381
393
  server,
382
394
  cache,
395
+ scheduler: schedulerInstance,
383
396
  abort: navigation?.abort
384
397
  };
385
398
  }
@@ -0,0 +1,300 @@
1
+ const defaultPhases = ["binding", "lifecycle", "effect", "async", "post"];
2
+
3
+ export function createScheduler(options = {}) {
4
+ const phases = [...(options.phases ?? defaultPhases)];
5
+ const queues = new Map(phases.map((phase) => [phase, []]));
6
+ const keyedJobs = new Map();
7
+ const destroyedScopes = new Set();
8
+ const objectScopeIds = new WeakMap();
9
+ const onError = typeof options.onError === "function" ? options.onError : undefined;
10
+ const maxDepth = options.maxDepth ?? 100;
11
+ const strategy = options.strategy ?? "microtask";
12
+ let destroyed = false;
13
+ let flushing = false;
14
+ let scheduled = false;
15
+ let batchDepth = 0;
16
+ let jobCounter = 0;
17
+ let scopeCounter = 0;
18
+
19
+ const api = {
20
+ strategy,
21
+ phases,
22
+
23
+ batch(fn) {
24
+ if (typeof fn !== "function") {
25
+ throw new TypeError("scheduler.batch(fn) requires a function.");
26
+ }
27
+ assertActive();
28
+ batchDepth += 1;
29
+ let asyncBatch = false;
30
+ try {
31
+ const value = fn();
32
+ if (value && typeof value.then === "function") {
33
+ asyncBatch = true;
34
+ return value.finally(() => {
35
+ batchDepth -= 1;
36
+ requestFlush();
37
+ });
38
+ }
39
+ return value;
40
+ } finally {
41
+ if (!asyncBatch && batchDepth > 0) {
42
+ batchDepth -= 1;
43
+ requestFlush();
44
+ }
45
+ }
46
+ },
47
+
48
+ enqueue(phase, fn, options = {}) {
49
+ assertActive();
50
+ assertPhase(phase);
51
+ if (typeof fn !== "function") {
52
+ throw new TypeError("scheduler.enqueue(phase, fn) requires a function.");
53
+ }
54
+ const scope = options.scope;
55
+ if (scope !== undefined && destroyedScopes.has(scope)) {
56
+ return noop;
57
+ }
58
+
59
+ const dedupeKey = options.key === undefined ? undefined : `${phase}:${scopeKey(scope)}:${String(options.key)}`;
60
+ if (dedupeKey && keyedJobs.has(dedupeKey)) {
61
+ return keyedJobs.get(dedupeKey).cancel;
62
+ }
63
+
64
+ const job = {
65
+ id: ++jobCounter,
66
+ phase,
67
+ fn,
68
+ scope,
69
+ boundary: options.boundary,
70
+ key: dedupeKey,
71
+ canceled: false,
72
+ cancel() {
73
+ job.canceled = true;
74
+ if (job.key) {
75
+ keyedJobs.delete(job.key);
76
+ }
77
+ }
78
+ };
79
+ queues.get(phase).push(job);
80
+ if (job.key) {
81
+ keyedJobs.set(job.key, job);
82
+ }
83
+ requestFlush();
84
+ return job.cancel;
85
+ },
86
+
87
+ afterFlush(fn, options = {}) {
88
+ return api.enqueue("post", fn, options);
89
+ },
90
+
91
+ async flush() {
92
+ assertActive();
93
+ if (flushing) {
94
+ return;
95
+ }
96
+ scheduled = false;
97
+ flushing = true;
98
+ let depth = 0;
99
+ try {
100
+ while (hasJobs()) {
101
+ depth += 1;
102
+ if (depth > maxDepth) {
103
+ throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
104
+ }
105
+ for (const phase of phases) {
106
+ await flushPhase(phase);
107
+ }
108
+ }
109
+ } finally {
110
+ flushing = false;
111
+ if (hasJobs()) {
112
+ requestFlush();
113
+ }
114
+ }
115
+ },
116
+
117
+ async flushScope(scope) {
118
+ assertActive();
119
+ if (flushing) {
120
+ return;
121
+ }
122
+ scheduled = false;
123
+ flushing = true;
124
+ let depth = 0;
125
+ try {
126
+ while (hasJobsForScope(scope)) {
127
+ depth += 1;
128
+ if (depth > maxDepth) {
129
+ throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
130
+ }
131
+ for (const phase of phases) {
132
+ await flushPhase(phase, scope);
133
+ }
134
+ }
135
+ } finally {
136
+ flushing = false;
137
+ if (hasJobs()) {
138
+ requestFlush();
139
+ }
140
+ }
141
+ },
142
+
143
+ cancelScope(scope) {
144
+ if (scope === undefined) {
145
+ return api;
146
+ }
147
+ for (const queue of queues.values()) {
148
+ for (const job of queue) {
149
+ if (job.scope === scope) {
150
+ job.cancel();
151
+ }
152
+ }
153
+ }
154
+ return api;
155
+ },
156
+
157
+ markScopeDestroyed(scope) {
158
+ if (scope !== undefined) {
159
+ destroyedScopes.add(scope);
160
+ api.cancelScope(scope);
161
+ }
162
+ return api;
163
+ },
164
+
165
+ isScopeDestroyed(scope) {
166
+ return scope !== undefined && destroyedScopes.has(scope);
167
+ },
168
+
169
+ inspect() {
170
+ const counts = {};
171
+ for (const [phase, queue] of queues) {
172
+ counts[phase] = queue.filter((job) => !job.canceled).length;
173
+ }
174
+ return {
175
+ strategy,
176
+ phases: [...phases],
177
+ pending: counts,
178
+ scopesDestroyed: destroyedScopes.size,
179
+ flushing,
180
+ scheduled
181
+ };
182
+ },
183
+
184
+ destroy() {
185
+ destroyed = true;
186
+ for (const queue of queues.values()) {
187
+ for (const job of queue) {
188
+ job.cancel();
189
+ }
190
+ queue.length = 0;
191
+ }
192
+ keyedJobs.clear();
193
+ destroyedScopes.clear();
194
+ }
195
+ };
196
+
197
+ return api;
198
+
199
+ function requestFlush() {
200
+ if (strategy === "manual" || destroyed || flushing || batchDepth > 0 || scheduled) {
201
+ return;
202
+ }
203
+ scheduled = true;
204
+ scheduleMicrotask(() => {
205
+ if (!destroyed) {
206
+ void api.flush();
207
+ }
208
+ });
209
+ }
210
+
211
+ async function flushPhase(phase, scope) {
212
+ const queue = queues.get(phase);
213
+ const remaining = [];
214
+ const runnable = [];
215
+
216
+ for (const job of queue.splice(0)) {
217
+ if (job.canceled) {
218
+ continue;
219
+ }
220
+ if (scope !== undefined && job.scope !== scope) {
221
+ remaining.push(job);
222
+ continue;
223
+ }
224
+ runnable.push(job);
225
+ }
226
+
227
+ queue.push(...remaining);
228
+
229
+ for (const job of runnable) {
230
+ if (job.key) {
231
+ keyedJobs.delete(job.key);
232
+ }
233
+ if (job.canceled || (job.scope !== undefined && destroyedScopes.has(job.scope))) {
234
+ continue;
235
+ }
236
+ try {
237
+ await job.fn();
238
+ } catch (error) {
239
+ if (onError) {
240
+ onError(error, job);
241
+ } else {
242
+ throw error;
243
+ }
244
+ }
245
+ }
246
+ }
247
+
248
+ function hasJobs() {
249
+ for (const queue of queues.values()) {
250
+ if (queue.some((job) => !job.canceled)) {
251
+ return true;
252
+ }
253
+ }
254
+ return false;
255
+ }
256
+
257
+ function hasJobsForScope(scope) {
258
+ for (const queue of queues.values()) {
259
+ if (queue.some((job) => !job.canceled && job.scope === scope)) {
260
+ return true;
261
+ }
262
+ }
263
+ return false;
264
+ }
265
+
266
+ function assertActive() {
267
+ if (destroyed) {
268
+ throw new Error("Scheduler has been destroyed.");
269
+ }
270
+ }
271
+
272
+ function assertPhase(phase) {
273
+ if (!queues.has(phase)) {
274
+ throw new Error(`Unknown scheduler phase "${phase}".`);
275
+ }
276
+ }
277
+
278
+ function scopeKey(scope) {
279
+ if (scope === undefined) {
280
+ return "global";
281
+ }
282
+ if ((typeof scope === "object" && scope !== null) || typeof scope === "function") {
283
+ if (!objectScopeIds.has(scope)) {
284
+ objectScopeIds.set(scope, `scope:${++scopeCounter}`);
285
+ }
286
+ return objectScopeIds.get(scope);
287
+ }
288
+ return String(scope);
289
+ }
290
+ }
291
+
292
+ function scheduleMicrotask(fn) {
293
+ if (typeof queueMicrotask === "function") {
294
+ queueMicrotask(fn);
295
+ return;
296
+ }
297
+ Promise.resolve().then(fn);
298
+ }
299
+
300
+ function noop() {}