@async/framework 0.7.0 → 0.8.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.
@@ -105,7 +105,8 @@
105
105
  router: context.router,
106
106
  loader: context.loader,
107
107
  cache: context.cache,
108
- abort: activeAbort
108
+ abort: activeAbort,
109
+ scheduler: context.scheduler
109
110
  });
110
111
  }
111
112
  return server;
@@ -119,6 +120,9 @@
119
120
  get cache() {
120
121
  return registry._context?.().cache;
121
122
  },
123
+ get scheduler() {
124
+ return registry._context?.().scheduler;
125
+ },
122
126
  get version() {
123
127
  return runVersion;
124
128
  },
@@ -195,11 +199,20 @@
195
199
  _bindRegistry(nextRegistry, nextId) {
196
200
  registry = nextRegistry;
197
201
  registeredId = nextId;
198
- queueMicrotask(() => {
202
+ const start = () => {
199
203
  if (registry === nextRegistry && status === "idle") {
200
204
  state.refresh();
201
205
  }
202
- });
206
+ };
207
+ const scheduler = registry._context?.().scheduler;
208
+ if (scheduler) {
209
+ scheduler.enqueue("async", start, {
210
+ scope: registeredId,
211
+ key: `asyncSignal:${registeredId}:initial`
212
+ });
213
+ } else {
214
+ queueMicrotask(start);
215
+ }
203
216
  },
204
217
 
205
218
  _dispose() {
@@ -235,11 +248,26 @@
235
248
  for (const dependency of dependencies) {
236
249
  const dependencyId = String(dependency).split(".")[0];
237
250
  if (dependencyId && dependencyId !== registeredId) {
238
- dependencyCleanups.add(registry.subscribe(dependency, () => state.refresh()));
251
+ dependencyCleanups.add(registry.subscribe(dependency, () => scheduleRefresh()));
239
252
  }
240
253
  }
241
254
  }
242
255
 
256
+ function scheduleRefresh() {
257
+ if (activeAbort && !activeAbort.aborted) {
258
+ activeAbort.cancel(new Error(`Async signal "${registeredId}" dependency changed.`));
259
+ }
260
+ const scheduler = registry?._context?.().scheduler;
261
+ if (!scheduler) {
262
+ state.refresh();
263
+ return;
264
+ }
265
+ scheduler.enqueue("async", () => state.refresh(), {
266
+ scope: registeredId,
267
+ key: `asyncSignal:${registeredId}:refresh`
268
+ });
269
+ }
270
+
243
271
  function notify() {
244
272
  for (const subscriber of [...subscribers]) {
245
273
  subscriber(state);
@@ -876,7 +904,8 @@
876
904
  server: registry._context?.().server,
877
905
  router: registry._context?.().router,
878
906
  loader: registry._context?.().loader,
879
- cache: registry._context?.().cache
907
+ cache: registry._context?.().cache,
908
+ scheduler: registry._context?.().scheduler
880
909
  }));
881
910
  });
882
911
  }
@@ -904,6 +933,8 @@
904
933
  const registryCleanups = new Map();
905
934
  const runtimeContext = {};
906
935
  const boundEntries = new Set();
936
+ let subscriptionCounter = 0;
937
+ let effectCounter = 0;
907
938
 
908
939
  const registry = attachRegistryInspection({
909
940
  register(id, signalLike) {
@@ -979,17 +1010,21 @@
979
1010
  return createRef(registry, id);
980
1011
  },
981
1012
 
982
- subscribe(path, fn) {
1013
+ subscribe(path, fn, options = {}) {
983
1014
  if (typeof fn !== "function") {
984
1015
  throw new TypeError("subscribe(path, fn) requires a function.");
985
1016
  }
986
1017
  const parsed = parsePath(path, entries);
987
1018
  const entry = requireEntry(entries, parsed.id);
1019
+ const subscriptionId = ++subscriptionCounter;
988
1020
  return entry.subscribe(() => {
989
- fn(registry.get(parsed.path), {
1021
+ scheduleCallback(() => fn(registry.get(parsed.path), {
990
1022
  id: parsed.id,
991
1023
  path: parsed.path,
992
1024
  signal: entry
1025
+ }), {
1026
+ ...options,
1027
+ key: options.key ?? `signal:${parsed.path}:${subscriptionId}`
993
1028
  });
994
1029
  });
995
1030
  },
@@ -1007,10 +1042,12 @@
1007
1042
  return registry.ref(id);
1008
1043
  },
1009
1044
 
1010
- effect(fn) {
1045
+ effect(fn, options = {}) {
1011
1046
  let cleanup;
1012
1047
  let dependencyCleanups = [];
1013
1048
  let stopped = false;
1049
+ const scheduler = options.scheduler;
1050
+ const effectId = ++effectCounter;
1014
1051
 
1015
1052
  const run = () => {
1016
1053
  if (stopped) {
@@ -1029,10 +1066,22 @@
1029
1066
  server: runtimeContext.server,
1030
1067
  router: runtimeContext.router,
1031
1068
  loader: runtimeContext.loader,
1032
- cache: runtimeContext.cache
1069
+ cache: runtimeContext.cache,
1070
+ scheduler: runtimeContext.scheduler
1033
1071
  }));
1034
1072
  cleanup = outcome.value;
1035
- dependencyCleanups = outcome.dependencies.map((dependency) => registry.subscribe(dependency, run));
1073
+ dependencyCleanups = outcome.dependencies.map((dependency) => registry.subscribe(dependency, scheduleRun));
1074
+ };
1075
+
1076
+ const scheduleRun = () => {
1077
+ if (!scheduler) {
1078
+ run();
1079
+ return;
1080
+ }
1081
+ scheduler.enqueue(options.phase ?? "effect", run, {
1082
+ scope: options.scope,
1083
+ key: options.key ?? `effect:${effectId}`
1084
+ });
1036
1085
  };
1037
1086
 
1038
1087
  run();
@@ -1109,6 +1158,17 @@
1109
1158
  registryCleanups.set(id, cleanup);
1110
1159
  }
1111
1160
  }
1161
+
1162
+ function scheduleCallback(fn, options = {}) {
1163
+ const scheduler = options.scheduler;
1164
+ if (!scheduler || options.phase === "sync") {
1165
+ return fn();
1166
+ }
1167
+ return scheduler.enqueue(options.phase ?? "effect", fn, {
1168
+ scope: options.scope,
1169
+ key: options.key
1170
+ });
1171
+ }
1112
1172
  }
1113
1173
 
1114
1174
  function normalizeSignal(signalLike) {
@@ -1575,10 +1635,15 @@
1575
1635
  html,
1576
1636
  attach(target) {
1577
1637
  for (const hook of attachHooks) {
1578
- const cleanup = hook(target);
1579
- if (typeof cleanup === "function") {
1580
- cleanups.push(cleanup);
1581
- }
1638
+ runtime.scheduler?.enqueue("lifecycle", () => {
1639
+ const cleanup = hook(target);
1640
+ if (typeof cleanup === "function") {
1641
+ cleanups.push(cleanup);
1642
+ }
1643
+ }, {
1644
+ scope,
1645
+ key: `attach:${attachHooks.indexOf(hook)}`
1646
+ }) ?? runAttachHook(hook, target);
1582
1647
  }
1583
1648
  },
1584
1649
  mount(target) {
@@ -1586,7 +1651,17 @@
1586
1651
  },
1587
1652
  visible(target, observeVisible) {
1588
1653
  for (const hook of visibleHooks) {
1589
- const cleanup = observeVisible(target, hook);
1654
+ const cleanup = observeVisible(target, () => {
1655
+ runtime.scheduler?.enqueue("lifecycle", () => {
1656
+ const hookCleanup = hook(target);
1657
+ if (typeof hookCleanup === "function") {
1658
+ cleanups.push(hookCleanup);
1659
+ }
1660
+ }, {
1661
+ scope,
1662
+ key: `visible:${visibleHooks.indexOf(hook)}`
1663
+ }) ?? runVisibleHook(hook, target);
1664
+ });
1590
1665
  if (typeof cleanup === "function") {
1591
1666
  cleanups.push(cleanup);
1592
1667
  }
@@ -1596,6 +1671,7 @@
1596
1671
  while (destroyHooks.length > 0) {
1597
1672
  destroyHooks.pop()?.();
1598
1673
  }
1674
+ runtime.scheduler?.markScopeDestroyed(scope);
1599
1675
  while (cleanups.length > 0) {
1600
1676
  cleanups.pop()?.();
1601
1677
  }
@@ -1604,10 +1680,24 @@
1604
1680
  }
1605
1681
  }
1606
1682
  };
1683
+
1684
+ function runAttachHook(hook, target) {
1685
+ const cleanup = hook(target);
1686
+ if (typeof cleanup === "function") {
1687
+ cleanups.push(cleanup);
1688
+ }
1689
+ }
1690
+
1691
+ function runVisibleHook(hook, target) {
1692
+ const cleanup = hook(target);
1693
+ if (typeof cleanup === "function") {
1694
+ cleanups.push(cleanup);
1695
+ }
1696
+ }
1607
1697
  }
1608
1698
 
1609
1699
  function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks, renderScopedTemplate }) {
1610
- const { signals, handlers, loader, server, router, cache } = runtime;
1700
+ const { signals, handlers, loader, server, router, cache, scheduler } = runtime;
1611
1701
  const generatedHandlers = new WeakMap();
1612
1702
  let generatedHandlerCounter = 0;
1613
1703
  let generatedSignalCounter = 0;
@@ -1619,6 +1709,7 @@
1619
1709
  server,
1620
1710
  router,
1621
1711
  cache,
1712
+ scheduler,
1622
1713
 
1623
1714
  signal(name, initial) {
1624
1715
  if (arguments.length === 1) {
@@ -1663,7 +1754,11 @@
1663
1754
  },
1664
1755
 
1665
1756
  effect(fn) {
1666
- const cleanup = signals.effect(() => fn.call(context));
1757
+ const cleanup = signals.effect(() => fn.call(context), {
1758
+ scheduler,
1759
+ phase: "effect",
1760
+ scope
1761
+ });
1667
1762
  cleanups.push(cleanup);
1668
1763
  return cleanup;
1669
1764
  },
@@ -1785,93 +1880,10 @@
1785
1880
  })();
1786
1881
 
1787
1882
  const __serverModule = (() => {
1788
- const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
1789
1883
  const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
1790
1884
  const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
1791
1885
  const appliedServerValues = new WeakSet();
1792
1886
 
1793
- function createServerRegistry(initialMap = {}, options = {}) {
1794
- const registryStore = options.registry ?? createRegistryStore();
1795
- const type = options.type ?? "server";
1796
- const entries = registryStore._map(type);
1797
- const defaults = {};
1798
-
1799
- const registry = attachRegistryInspection({
1800
- register(id, fn) {
1801
- assertServerId(id);
1802
- if (typeof fn !== "function") {
1803
- throw new TypeError(`Server function "${id}" must be a function.`);
1804
- }
1805
- if (entries.has(id)) {
1806
- throw new Error(`Server function "${id}" is already registered.`);
1807
- }
1808
- entries.set(id, fn);
1809
- return id;
1810
- },
1811
-
1812
- registerMany(map) {
1813
- for (const [id, fn] of Object.entries(map ?? {})) {
1814
- registry.register(id, fn);
1815
- }
1816
- return registry;
1817
- },
1818
-
1819
- unregister(id) {
1820
- assertServerId(id);
1821
- return entries.delete(id);
1822
- },
1823
-
1824
- resolve(id) {
1825
- assertServerId(id);
1826
- return entries.get(id);
1827
- },
1828
-
1829
- async run(id, args = [], context = {}) {
1830
- assertServerId(id);
1831
- const fn = registry.resolve(id);
1832
- if (!fn) {
1833
- throw new Error(`Server function "${id}" is not registered.`);
1834
- }
1835
-
1836
- let runContext;
1837
- const server = createServerNamespace((childId, childArgs, childContext = {}) => {
1838
- return registry.run(childId, childArgs, { ...runContext, ...childContext });
1839
- }, {}, () => runContext);
1840
-
1841
- const mergedContext = {
1842
- ...defaults,
1843
- ...context,
1844
- cache: defaults.cache ?? context.cache
1845
- };
1846
-
1847
- runContext = {
1848
- ...mergedContext,
1849
- id,
1850
- args,
1851
- input: mergedContext.input,
1852
- signals: createSignalReader(mergedContext.signals),
1853
- abort: mergedContext.abort,
1854
- cache: mergedContext.cache,
1855
- server
1856
- };
1857
-
1858
- return fn.call(runContext, ...args);
1859
- },
1860
-
1861
- _setContext(context = {}) {
1862
- Object.assign(defaults, context);
1863
- return registry;
1864
- },
1865
-
1866
- _adoptMany() {
1867
- return registry;
1868
- }
1869
- }, registryStore, type);
1870
-
1871
- registry.registerMany(initialMap);
1872
- return createServerNamespace((id, args, context) => registry.run(id, args, context), registry, () => defaults);
1873
- }
1874
-
1875
1887
  function createServerProxy({
1876
1888
  endpoint = "/__async/server",
1877
1889
  fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
@@ -1879,13 +1891,14 @@
1879
1891
  loader,
1880
1892
  router,
1881
1893
  cache,
1894
+ scheduler,
1882
1895
  headers = {}
1883
1896
  } = {}) {
1884
1897
  if (typeof fetchImpl !== "function") {
1885
1898
  throw new TypeError("createServerProxy(...) requires fetch to be available.");
1886
1899
  }
1887
1900
 
1888
- const defaults = { signals, loader, router, cache };
1901
+ const defaults = { signals, loader, router, cache, scheduler };
1889
1902
 
1890
1903
  async function run(id, args = [], context = {}) {
1891
1904
  assertServerId(id);
@@ -2224,7 +2237,7 @@
2224
2237
  throw new TypeError("Server function id must be a non-empty string.");
2225
2238
  }
2226
2239
  }
2227
- return { createServerRegistry, createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput };
2240
+ return { createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
2228
2241
  })();
2229
2242
 
2230
2243
  const __handlersModule = (() => {
@@ -2427,18 +2440,321 @@
2427
2440
  return { createHandlerRegistry, parseHandlerRef, isHandlerToken };
2428
2441
  })();
2429
2442
 
2443
+ const __schedulerModule = (() => {
2444
+ const defaultPhases = ["binding", "lifecycle", "effect", "async", "post"];
2445
+
2446
+ function createScheduler(options = {}) {
2447
+ const phases = [...(options.phases ?? defaultPhases)];
2448
+ const queues = new Map(phases.map((phase) => [phase, []]));
2449
+ const keyedJobs = new Map();
2450
+ const destroyedScopes = new Set();
2451
+ const objectScopeIds = new WeakMap();
2452
+ const onError = typeof options.onError === "function" ? options.onError : undefined;
2453
+ const maxDepth = options.maxDepth ?? 100;
2454
+ const strategy = options.strategy ?? "microtask";
2455
+ let destroyed = false;
2456
+ let flushing = false;
2457
+ let scheduled = false;
2458
+ let batchDepth = 0;
2459
+ let jobCounter = 0;
2460
+ let scopeCounter = 0;
2461
+
2462
+ const api = {
2463
+ strategy,
2464
+ phases,
2465
+
2466
+ batch(fn) {
2467
+ if (typeof fn !== "function") {
2468
+ throw new TypeError("scheduler.batch(fn) requires a function.");
2469
+ }
2470
+ assertActive();
2471
+ batchDepth += 1;
2472
+ let asyncBatch = false;
2473
+ try {
2474
+ const value = fn();
2475
+ if (value && typeof value.then === "function") {
2476
+ asyncBatch = true;
2477
+ return value.finally(() => {
2478
+ batchDepth -= 1;
2479
+ requestFlush();
2480
+ });
2481
+ }
2482
+ return value;
2483
+ } finally {
2484
+ if (!asyncBatch && batchDepth > 0) {
2485
+ batchDepth -= 1;
2486
+ requestFlush();
2487
+ }
2488
+ }
2489
+ },
2490
+
2491
+ enqueue(phase, fn, options = {}) {
2492
+ assertActive();
2493
+ assertPhase(phase);
2494
+ if (typeof fn !== "function") {
2495
+ throw new TypeError("scheduler.enqueue(phase, fn) requires a function.");
2496
+ }
2497
+ const scope = options.scope;
2498
+ if (scope !== undefined && destroyedScopes.has(scope)) {
2499
+ return noop;
2500
+ }
2501
+
2502
+ const dedupeKey = options.key === undefined ? undefined : `${phase}:${scopeKey(scope)}:${String(options.key)}`;
2503
+ if (dedupeKey && keyedJobs.has(dedupeKey)) {
2504
+ return keyedJobs.get(dedupeKey).cancel;
2505
+ }
2506
+
2507
+ const job = {
2508
+ id: ++jobCounter,
2509
+ phase,
2510
+ fn,
2511
+ scope,
2512
+ boundary: options.boundary,
2513
+ key: dedupeKey,
2514
+ canceled: false,
2515
+ cancel() {
2516
+ job.canceled = true;
2517
+ if (job.key) {
2518
+ keyedJobs.delete(job.key);
2519
+ }
2520
+ }
2521
+ };
2522
+ queues.get(phase).push(job);
2523
+ if (job.key) {
2524
+ keyedJobs.set(job.key, job);
2525
+ }
2526
+ requestFlush();
2527
+ return job.cancel;
2528
+ },
2529
+
2530
+ afterFlush(fn, options = {}) {
2531
+ return api.enqueue("post", fn, options);
2532
+ },
2533
+
2534
+ async flush() {
2535
+ assertActive();
2536
+ if (flushing) {
2537
+ return;
2538
+ }
2539
+ scheduled = false;
2540
+ flushing = true;
2541
+ let depth = 0;
2542
+ try {
2543
+ while (hasJobs()) {
2544
+ depth += 1;
2545
+ if (depth > maxDepth) {
2546
+ throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
2547
+ }
2548
+ for (const phase of phases) {
2549
+ await flushPhase(phase);
2550
+ }
2551
+ }
2552
+ } finally {
2553
+ flushing = false;
2554
+ if (hasJobs()) {
2555
+ requestFlush();
2556
+ }
2557
+ }
2558
+ },
2559
+
2560
+ async flushScope(scope) {
2561
+ assertActive();
2562
+ if (flushing) {
2563
+ return;
2564
+ }
2565
+ scheduled = false;
2566
+ flushing = true;
2567
+ let depth = 0;
2568
+ try {
2569
+ while (hasJobsForScope(scope)) {
2570
+ depth += 1;
2571
+ if (depth > maxDepth) {
2572
+ throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
2573
+ }
2574
+ for (const phase of phases) {
2575
+ await flushPhase(phase, scope);
2576
+ }
2577
+ }
2578
+ } finally {
2579
+ flushing = false;
2580
+ if (hasJobs()) {
2581
+ requestFlush();
2582
+ }
2583
+ }
2584
+ },
2585
+
2586
+ cancelScope(scope) {
2587
+ if (scope === undefined) {
2588
+ return api;
2589
+ }
2590
+ for (const queue of queues.values()) {
2591
+ for (const job of queue) {
2592
+ if (job.scope === scope) {
2593
+ job.cancel();
2594
+ }
2595
+ }
2596
+ }
2597
+ return api;
2598
+ },
2599
+
2600
+ markScopeDestroyed(scope) {
2601
+ if (scope !== undefined) {
2602
+ destroyedScopes.add(scope);
2603
+ api.cancelScope(scope);
2604
+ }
2605
+ return api;
2606
+ },
2607
+
2608
+ inspect() {
2609
+ const counts = {};
2610
+ for (const [phase, queue] of queues) {
2611
+ counts[phase] = queue.filter((job) => !job.canceled).length;
2612
+ }
2613
+ return {
2614
+ strategy,
2615
+ phases: [...phases],
2616
+ pending: counts,
2617
+ scopesDestroyed: destroyedScopes.size,
2618
+ flushing,
2619
+ scheduled
2620
+ };
2621
+ },
2622
+
2623
+ destroy() {
2624
+ destroyed = true;
2625
+ for (const queue of queues.values()) {
2626
+ for (const job of queue) {
2627
+ job.cancel();
2628
+ }
2629
+ queue.length = 0;
2630
+ }
2631
+ keyedJobs.clear();
2632
+ destroyedScopes.clear();
2633
+ }
2634
+ };
2635
+
2636
+ return api;
2637
+
2638
+ function requestFlush() {
2639
+ if (strategy === "manual" || destroyed || flushing || batchDepth > 0 || scheduled) {
2640
+ return;
2641
+ }
2642
+ scheduled = true;
2643
+ scheduleMicrotask(() => {
2644
+ if (!destroyed) {
2645
+ void api.flush();
2646
+ }
2647
+ });
2648
+ }
2649
+
2650
+ async function flushPhase(phase, scope) {
2651
+ const queue = queues.get(phase);
2652
+ const remaining = [];
2653
+ const runnable = [];
2654
+
2655
+ for (const job of queue.splice(0)) {
2656
+ if (job.canceled) {
2657
+ continue;
2658
+ }
2659
+ if (scope !== undefined && job.scope !== scope) {
2660
+ remaining.push(job);
2661
+ continue;
2662
+ }
2663
+ runnable.push(job);
2664
+ }
2665
+
2666
+ queue.push(...remaining);
2667
+
2668
+ for (const job of runnable) {
2669
+ if (job.key) {
2670
+ keyedJobs.delete(job.key);
2671
+ }
2672
+ if (job.canceled || (job.scope !== undefined && destroyedScopes.has(job.scope))) {
2673
+ continue;
2674
+ }
2675
+ try {
2676
+ await job.fn();
2677
+ } catch (error) {
2678
+ if (onError) {
2679
+ onError(error, job);
2680
+ } else {
2681
+ throw error;
2682
+ }
2683
+ }
2684
+ }
2685
+ }
2686
+
2687
+ function hasJobs() {
2688
+ for (const queue of queues.values()) {
2689
+ if (queue.some((job) => !job.canceled)) {
2690
+ return true;
2691
+ }
2692
+ }
2693
+ return false;
2694
+ }
2695
+
2696
+ function hasJobsForScope(scope) {
2697
+ for (const queue of queues.values()) {
2698
+ if (queue.some((job) => !job.canceled && job.scope === scope)) {
2699
+ return true;
2700
+ }
2701
+ }
2702
+ return false;
2703
+ }
2704
+
2705
+ function assertActive() {
2706
+ if (destroyed) {
2707
+ throw new Error("Scheduler has been destroyed.");
2708
+ }
2709
+ }
2710
+
2711
+ function assertPhase(phase) {
2712
+ if (!queues.has(phase)) {
2713
+ throw new Error(`Unknown scheduler phase "${phase}".`);
2714
+ }
2715
+ }
2716
+
2717
+ function scopeKey(scope) {
2718
+ if (scope === undefined) {
2719
+ return "global";
2720
+ }
2721
+ if ((typeof scope === "object" && scope !== null) || typeof scope === "function") {
2722
+ if (!objectScopeIds.has(scope)) {
2723
+ objectScopeIds.set(scope, `scope:${++scopeCounter}`);
2724
+ }
2725
+ return objectScopeIds.get(scope);
2726
+ }
2727
+ return String(scope);
2728
+ }
2729
+ }
2730
+
2731
+ function scheduleMicrotask(fn) {
2732
+ if (typeof queueMicrotask === "function") {
2733
+ queueMicrotask(fn);
2734
+ return;
2735
+ }
2736
+ Promise.resolve().then(fn);
2737
+ }
2738
+
2739
+ function noop() {}
2740
+ return { createScheduler };
2741
+ })();
2742
+
2430
2743
  const __loaderModule = (() => {
2431
2744
  const { renderComponent } = __componentModule;
2432
2745
  const { createHandlerRegistry } = __handlersModule;
2746
+ const { createScheduler } = __schedulerModule;
2433
2747
  const { createSignalRegistry, isSignalRef } = __signalsModule;
2434
2748
  const { matchAttribute, normalizeAttributeConfig, readAttribute } = __attributesModule;
2435
2749
  const inlineBindingPrefix = "__async:inline:";
2436
2750
 
2437
- function Loader({ root, signals, handlers, server, router, cache, attributes } = {}) {
2751
+ function Loader({ root, signals, handlers, server, router, cache, attributes, scheduler } = {}) {
2438
2752
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
2439
2753
  const rootNode = root ?? documentRef;
2440
2754
  const signalRegistry = signals ?? createSignalRegistry();
2441
2755
  const handlerRegistry = handlers ?? createHandlerRegistry();
2756
+ const schedulerInstance = scheduler ?? createScheduler();
2757
+ const ownsScheduler = !scheduler;
2442
2758
  const attributeConfig = normalizeAttributeConfig(attributes);
2443
2759
  const cleanups = new Set();
2444
2760
  const eventBindings = new WeakMap();
@@ -2459,6 +2775,7 @@
2459
2775
  server,
2460
2776
  router,
2461
2777
  cache,
2778
+ scheduler: schedulerInstance,
2462
2779
  attributes: attributeConfig,
2463
2780
 
2464
2781
  start() {
@@ -2498,6 +2815,7 @@
2498
2815
  server: api.server,
2499
2816
  router: api.router,
2500
2817
  cache: api.cache,
2818
+ scheduler: schedulerInstance,
2501
2819
  attributes: attributeConfig
2502
2820
  });
2503
2821
  cleanupChildren(target);
@@ -2518,6 +2836,9 @@
2518
2836
  runCleanup(cleanup);
2519
2837
  }
2520
2838
  cleanups.clear();
2839
+ if (ownsScheduler) {
2840
+ schedulerInstance.destroy();
2841
+ }
2521
2842
  },
2522
2843
 
2523
2844
  _observeVisible(target, fn) {
@@ -2535,13 +2856,14 @@
2535
2856
  }
2536
2857
  };
2537
2858
 
2538
- signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
2859
+ signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache, scheduler: schedulerInstance });
2539
2860
  api.server?._setContext?.({
2540
2861
  signals: signalRegistry,
2541
2862
  handlers: handlerRegistry,
2542
2863
  loader: api,
2543
2864
  router: api.router,
2544
- cache: api.cache
2865
+ cache: api.cache,
2866
+ scheduler: schedulerInstance
2545
2867
  });
2546
2868
 
2547
2869
  function bindEventAttributes(scope) {
@@ -2573,18 +2895,19 @@
2573
2895
 
2574
2896
  const listener = async (event) => {
2575
2897
  try {
2576
- await handlerRegistry.run(ref, {
2898
+ await schedulerInstance.batch(() => handlerRegistry.run(ref, {
2577
2899
  signals: signalRegistry,
2578
2900
  handlers: handlerRegistry,
2579
2901
  loader: api,
2580
2902
  server: api.server,
2581
2903
  router: api.router,
2582
2904
  cache: api.cache,
2905
+ scheduler: schedulerInstance,
2583
2906
  event,
2584
2907
  element,
2585
2908
  el: element,
2586
2909
  root: rootNode
2587
- });
2910
+ }));
2588
2911
  } catch (error) {
2589
2912
  dispatchAsyncError(element, error);
2590
2913
  }
@@ -2700,7 +3023,12 @@
2700
3023
 
2701
3024
  const read = () => readBinding(path, options);
2702
3025
  apply(read());
2703
- addCleanup(subscribeBinding(path, () => apply(read())), element);
3026
+ addCleanup(subscribeBinding(path, () => {
3027
+ schedulerInstance.enqueue("binding", () => apply(read()), {
3028
+ scope: element,
3029
+ key
3030
+ });
3031
+ }), element);
2704
3032
  }
2705
3033
 
2706
3034
  function bindValueWriter(element, path) {
@@ -2761,7 +3089,12 @@
2761
3089
  const state = {
2762
3090
  id,
2763
3091
  templates,
2764
- cleanup: signalRegistry.subscribe(`${id}.$status`, () => renderBoundary(boundary))
3092
+ cleanup: signalRegistry.subscribe(`${id}.$status`, () => {
3093
+ schedulerInstance.enqueue("binding", () => renderBoundary(boundary), {
3094
+ scope: boundary,
3095
+ key: `boundary:${id}`
3096
+ });
3097
+ })
2765
3098
  };
2766
3099
  boundaryState.set(boundary, state);
2767
3100
  addCleanup(state.cleanup, boundary);
@@ -2801,7 +3134,7 @@
2801
3134
  }
2802
3135
  mountedElements.add(element);
2803
3136
  for (const ref of refs) {
2804
- runPseudo(element, ref);
3137
+ scheduleLifecycle(element, () => runPseudo(element, ref), `attach:${ref}`);
2805
3138
  }
2806
3139
  }
2807
3140
 
@@ -2814,7 +3147,7 @@
2814
3147
  continue;
2815
3148
  }
2816
3149
  visibleElements.add(element);
2817
- addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
3150
+ addCleanup(observeVisible(element, () => scheduleLifecycle(element, () => runPseudo(element, ref), `visible:${ref}`)), element);
2818
3151
  }
2819
3152
  }
2820
3153
 
@@ -2838,6 +3171,7 @@
2838
3171
  server: api.server,
2839
3172
  router: api.router,
2840
3173
  cache: api.cache,
3174
+ scheduler: schedulerInstance,
2841
3175
  element,
2842
3176
  el: element,
2843
3177
  root: rootNode
@@ -2856,10 +3190,13 @@
2856
3190
  const ownerWindow = target.ownerDocument?.defaultView ?? globalThis;
2857
3191
  const Observer = ownerWindow.IntersectionObserver ?? globalThis.IntersectionObserver;
2858
3192
  if (!Observer) {
2859
- queueMicrotask(() => {
3193
+ schedulerInstance.enqueue("lifecycle", () => {
2860
3194
  if (!destroyed) {
2861
3195
  fn(target);
2862
3196
  }
3197
+ }, {
3198
+ scope: target,
3199
+ key: "visible:fallback"
2863
3200
  });
2864
3201
  return () => {};
2865
3202
  }
@@ -2914,6 +3251,7 @@
2914
3251
  }
2915
3252
  for (const element of elementsIn(node)) {
2916
3253
  runScopedCleanups(element);
3254
+ schedulerInstance.markScopeDestroyed(element);
2917
3255
  }
2918
3256
  }
2919
3257
 
@@ -2937,6 +3275,13 @@
2937
3275
  }
2938
3276
  }
2939
3277
 
3278
+ function scheduleLifecycle(element, fn, key) {
3279
+ schedulerInstance.enqueue("lifecycle", fn, {
3280
+ scope: element,
3281
+ key
3282
+ });
3283
+ }
3284
+
2940
3285
  return api;
2941
3286
  }
2942
3287
 
@@ -3267,6 +3612,7 @@
3267
3612
  const __routerModule = (() => {
3268
3613
  const { Loader } = __loaderModule;
3269
3614
  const { createHandlerRegistry } = __handlersModule;
3615
+ const { createScheduler } = __schedulerModule;
3270
3616
  const { createSignalRegistry } = __signalsModule;
3271
3617
  const { applyServerResult } = __serverModule;
3272
3618
  const { createRegistryStore } = __registryStoreModule;
@@ -3387,12 +3733,15 @@
3387
3733
  partials,
3388
3734
  fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
3389
3735
  routeEndpoint = "/__async/route",
3390
- attributes
3736
+ attributes,
3737
+ scheduler
3391
3738
  } = {}) {
3392
3739
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
3393
3740
  const rootNode = root ?? documentRef;
3394
3741
  const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
3395
3742
  const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
3743
+ const schedulerInstance = scheduler ?? loader?.scheduler ?? createScheduler();
3744
+ const ownsScheduler = !scheduler && !loader?.scheduler;
3396
3745
  const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
3397
3746
  const loaderInstance =
3398
3747
  loader ??
@@ -3402,6 +3751,7 @@
3402
3751
  handlers: handlerRegistry,
3403
3752
  server,
3404
3753
  cache,
3754
+ scheduler: schedulerInstance,
3405
3755
  attributes: attributeConfig
3406
3756
  });
3407
3757
  const ownsLoader = !loader;
@@ -3421,12 +3771,13 @@
3421
3771
  server,
3422
3772
  cache,
3423
3773
  partials,
3774
+ scheduler: schedulerInstance,
3424
3775
  attributes: attributeConfig,
3425
3776
 
3426
3777
  start() {
3427
3778
  assertActive();
3428
3779
  loaderInstance.router = api;
3429
- signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache });
3780
+ signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache, scheduler: schedulerInstance });
3430
3781
  if (ownsLoader) {
3431
3782
  loaderInstance.start();
3432
3783
  }
@@ -3490,6 +3841,9 @@
3490
3841
  cleanup();
3491
3842
  }
3492
3843
  cleanups.clear();
3844
+ if (ownsScheduler) {
3845
+ schedulerInstance.destroy();
3846
+ }
3493
3847
  }
3494
3848
  };
3495
3849
 
@@ -3595,13 +3949,16 @@
3595
3949
  loader: loaderInstance,
3596
3950
  router: api,
3597
3951
  cache,
3952
+ scheduler: schedulerInstance,
3598
3953
  abort: navigation?.abort
3599
3954
  });
3955
+ await schedulerInstance.flush();
3600
3956
  if (!isActiveNavigation(navigation)) {
3601
3957
  return;
3602
3958
  }
3603
3959
  if (result?.html != null && !result.boundary && !result.redirect) {
3604
3960
  loaderInstance.swap(boundary, result.html);
3961
+ await schedulerInstance.flush();
3605
3962
  }
3606
3963
  if (result?.redirect || options.history === false) {
3607
3964
  return;
@@ -3646,6 +4003,7 @@
3646
4003
  loader: loaderInstance,
3647
4004
  server,
3648
4005
  cache,
4006
+ scheduler: schedulerInstance,
3649
4007
  abort: navigation?.abort
3650
4008
  };
3651
4009
  }
@@ -3832,15 +4190,17 @@
3832
4190
  const { Loader } = __loaderModule;
3833
4191
  const { createPartialRegistry } = __partialsModule;
3834
4192
  const { createRouteRegistry, createRouter } = __routerModule;
3835
- const { createServerRegistry } = __serverModule;
4193
+ const { createScheduler } = __schedulerModule;
4194
+ const { createServerNamespace } = __serverModule;
3836
4195
  const { createSignal, createSignalRegistry } = __signalsModule;
3837
4196
  const { createRegistryStore } = __registryStoreModule;
3838
4197
  const { attributeName, normalizeAttributeConfig } = __attributesModule;
3839
4198
  const registryTypes = new Set(["signal", "handler", "server", "partial", "route", "component"]);
3840
4199
 
3841
- function defineApp(initial) {
4200
+ function defineApp(initial, options = {}) {
3842
4201
  const registry = createRegistryStore(undefined, { target: "browser" });
3843
4202
  const runtimes = new Set();
4203
+ const createRuntime = options.createRuntime ?? createApp;
3844
4204
 
3845
4205
  const app = {
3846
4206
  registry,
@@ -3859,7 +4219,7 @@
3859
4219
  },
3860
4220
 
3861
4221
  start(options = {}) {
3862
- const runtime = createApp(app, options).start();
4222
+ const runtime = createRuntime(app, options).start();
3863
4223
  app.runtime = runtime;
3864
4224
  return runtime;
3865
4225
  },
@@ -3884,13 +4244,18 @@
3884
4244
  function createApp(appOrDefinition = Async, options = {}) {
3885
4245
  const app = isAppHub(appOrDefinition) ? appOrDefinition : defineApp(appOrDefinition ?? {});
3886
4246
  const target = options.target ?? "browser";
4247
+ const scheduler = options.scheduler ?? options.loader?.scheduler ?? createScheduler({
4248
+ strategy: target === "server" ? "manual" : "microtask"
4249
+ });
4250
+ const ownsScheduler = !options.scheduler && !options.loader?.scheduler;
3887
4251
  const attributes = normalizeAttributeConfig(options.attributes);
3888
4252
  const registry = options.registry ?? app.registry.view({ target });
3889
4253
  const signals = options.signals ?? createSignalRegistry(undefined, { registry, type: "signal" });
3890
4254
  const handlers = options.handlers ?? createHandlerRegistry(undefined, { registry, type: "handler" });
3891
4255
  const serverCache = createCacheRegistry(undefined, { registry, type: "cache.server" });
3892
4256
  const browserCache = createCacheRegistry(undefined, { registry, type: "cache.browser" });
3893
- const server = options.server ?? createServerRegistry(undefined, { registry, type: "server" });
4257
+ const serverFactory = options.serverFactory ?? createServerReferenceRegistry;
4258
+ const server = options.server ?? serverFactory(undefined, { registry, type: "server" });
3894
4259
  const partials = options.partials ?? createPartialRegistry(undefined, { registry, type: "partial" });
3895
4260
  const routes = options.routes ?? createRouteRegistry(undefined, { registry, type: "route" });
3896
4261
  const components = options.components ?? createComponentRegistry(undefined, { registry, type: "component" });
@@ -3918,6 +4283,7 @@
3918
4283
  },
3919
4284
  loader,
3920
4285
  router,
4286
+ scheduler,
3921
4287
  attributes,
3922
4288
 
3923
4289
  start() {
@@ -3934,12 +4300,13 @@
3934
4300
  handlers,
3935
4301
  server,
3936
4302
  cache: browserCache,
4303
+ scheduler,
3937
4304
  attributes
3938
4305
  });
3939
4306
  runtime.loader = loader;
3940
4307
 
3941
4308
  configureServerContext({ cache: browserCache });
3942
- signals._setContext?.({ server, loader, cache: browserCache });
4309
+ signals._setContext?.({ server, loader, cache: browserCache, scheduler });
3943
4310
 
3944
4311
  loader.start();
3945
4312
 
@@ -3955,6 +4322,7 @@
3955
4322
  server,
3956
4323
  cache: browserCache,
3957
4324
  partials,
4325
+ scheduler,
3958
4326
  fetch: options.fetch,
3959
4327
  routeEndpoint: options.routeEndpoint,
3960
4328
  attributes
@@ -3966,7 +4334,7 @@
3966
4334
  }
3967
4335
  } else {
3968
4336
  configureServerContext({ cache: serverCache });
3969
- signals._setContext?.({ server, cache: serverCache });
4337
+ signals._setContext?.({ server, cache: serverCache, scheduler });
3970
4338
  }
3971
4339
 
3972
4340
  return runtime;
@@ -3980,9 +4348,10 @@
3980
4348
  async render(url) {
3981
4349
  assertActive();
3982
4350
  configureServerContext({ cache: serverCache });
3983
- signals._setContext?.({ server, cache: serverCache });
4351
+ signals._setContext?.({ server, cache: serverCache, scheduler });
3984
4352
  const matched = routes.match(url);
3985
4353
  if (!matched) {
4354
+ await scheduler.flush();
3986
4355
  return {
3987
4356
  html: renderDocument("", { status: 404, signals, browserCache, boundary: options.boundary ?? "route", attributes }),
3988
4357
  status: 404,
@@ -4002,8 +4371,8 @@
4002
4371
  cache: serverCache,
4003
4372
  browserCache,
4004
4373
  partials,
4005
- request: options.request,
4006
- locals: options.locals
4374
+ scheduler,
4375
+ ...currentRequestContext()
4007
4376
  })
4008
4377
  : { html: "" };
4009
4378
 
@@ -4016,6 +4385,8 @@
4016
4385
  browserCache.restore(result.cache.browser);
4017
4386
  }
4018
4387
 
4388
+ await scheduler.flush();
4389
+
4019
4390
  const status = result.status ?? 200;
4020
4391
  return {
4021
4392
  html: renderDocument(result.html, { status, signals, browserCache, boundary: result.boundary ?? options.boundary ?? "route", attributes }),
@@ -4034,6 +4405,9 @@
4034
4405
  router?.destroy?.();
4035
4406
  loader?.destroy?.();
4036
4407
  signals.destroy?.();
4408
+ if (ownsScheduler) {
4409
+ scheduler.destroy();
4410
+ }
4037
4411
  },
4038
4412
 
4039
4413
  _applyUse(normalized) {
@@ -4054,11 +4428,23 @@
4054
4428
  loader,
4055
4429
  router,
4056
4430
  cache,
4057
- request: options.request,
4058
- locals: options.locals
4431
+ scheduler,
4432
+ requestContext: options.requestContext,
4433
+ ...currentRequestContext()
4059
4434
  });
4060
4435
  }
4061
4436
 
4437
+ function currentRequestContext() {
4438
+ const context = readRequestContextLike(options.requestContext);
4439
+ return {
4440
+ requestContext: context,
4441
+ request: context.request ?? options.request,
4442
+ headers: context.headers ?? options.headers,
4443
+ cookies: context.cookies ?? options.cookies,
4444
+ locals: context.locals ?? options.locals
4445
+ };
4446
+ }
4447
+
4062
4448
  function assertActive() {
4063
4449
  if (destroyed) {
4064
4450
  throw new Error("Async app runtime has been destroyed.");
@@ -4212,6 +4598,77 @@
4212
4598
  }
4213
4599
  }
4214
4600
 
4601
+ function createServerReferenceRegistry(initialMap = {}, options = {}) {
4602
+ const registry = options.registry ?? createRegistryStore();
4603
+ const type = options.type ?? "server";
4604
+ const defaults = {};
4605
+
4606
+ const reference = {
4607
+ registry,
4608
+
4609
+ register(id, value) {
4610
+ registry.register(type, id, value);
4611
+ return id;
4612
+ },
4613
+
4614
+ registerMany(map) {
4615
+ for (const [id, value] of Object.entries(map ?? {})) {
4616
+ reference.register(id, value);
4617
+ }
4618
+ return reference;
4619
+ },
4620
+
4621
+ unregister(id) {
4622
+ return registry.unregister(type, id);
4623
+ },
4624
+
4625
+ resolve() {
4626
+ return undefined;
4627
+ },
4628
+
4629
+ async run(id) {
4630
+ throw new Error(`Server command "${id}" cannot run without a server proxy or server registry.`);
4631
+ },
4632
+
4633
+ keys() {
4634
+ return registry.keys(type);
4635
+ },
4636
+
4637
+ entries() {
4638
+ return registry.entries(type);
4639
+ },
4640
+
4641
+ inspect() {
4642
+ return registry.entries(type);
4643
+ },
4644
+
4645
+ _setContext(context = {}) {
4646
+ Object.assign(defaults, context);
4647
+ return reference;
4648
+ },
4649
+
4650
+ _adoptMany() {
4651
+ return reference;
4652
+ }
4653
+ };
4654
+
4655
+ reference.registerMany(initialMap);
4656
+ return createServerNamespace((id, args, context) => reference.run(id, args, context), reference, () => defaults);
4657
+ }
4658
+
4659
+ function readRequestContextLike(store) {
4660
+ if (!store) {
4661
+ return {};
4662
+ }
4663
+ if (typeof store.get === "function") {
4664
+ return store.get() ?? {};
4665
+ }
4666
+ if (typeof store.getStore === "function") {
4667
+ return store.getStore() ?? {};
4668
+ }
4669
+ return {};
4670
+ }
4671
+
4215
4672
  function normalizeEntries(type, entries = {}) {
4216
4673
  if (type !== "signal") {
4217
4674
  return { ...(entries ?? {}) };
@@ -4320,14 +4777,17 @@
4320
4777
  const { createRouter: createRouter } = __routerModule;
4321
4778
  const { defineRoute: defineRoute } = __routerModule;
4322
4779
  const { route: route } = __routerModule;
4780
+ const { createScheduler: createScheduler } = __schedulerModule;
4781
+ const { applyServerResult: applyServerResult } = __serverModule;
4323
4782
  const { createServerProxy: createServerProxy } = __serverModule;
4324
- const { createServerRegistry: createServerRegistry } = __serverModule;
4783
+ const { resolveServerCommandArguments: resolveServerCommandArguments } = __serverModule;
4784
+ const { unwrapServerResult: unwrapServerResult } = __serverModule;
4325
4785
  const { computed: computed } = __signalsModule;
4326
4786
  const { createSignal: createSignal } = __signalsModule;
4327
4787
  const { createSignalRegistry: createSignalRegistry } = __signalsModule;
4328
4788
  const { effect: effect } = __signalsModule;
4329
4789
  const { signal: signal } = __signalsModule;
4330
- const api = { asyncSignal, Async, createApp, defineApp, readSnapshot, attributeName, defineAttributeConfig, createCacheRegistry, defineCache, component, createComponentRegistry, defineComponent, delay, createHandlerRegistry, html, Loader, AsyncLoader, createPartialRegistry, createRegistryStore, createRouteRegistry, createRouter, defineRoute, route, createServerProxy, createServerRegistry, computed, createSignal, createSignalRegistry, effect, signal };
4790
+ const api = { asyncSignal, Async, createApp, defineApp, readSnapshot, attributeName, defineAttributeConfig, createCacheRegistry, defineCache, component, createComponentRegistry, defineComponent, delay, createHandlerRegistry, html, Loader, AsyncLoader, createPartialRegistry, createRegistryStore, createRouteRegistry, createRouter, defineRoute, route, createScheduler, applyServerResult, createServerProxy, resolveServerCommandArguments, unwrapServerResult, computed, createSignal, createSignalRegistry, effect, signal };
4331
4791
  assertNoUmdNamespaceConflicts(api, Async);
4332
4792
  Object.assign(Async, api);
4333
4793
  Async.Async = Async;