@async/framework 0.6.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.
@@ -94,7 +94,8 @@ const __asyncSignalModule = (() => {
94
94
  router: context.router,
95
95
  loader: context.loader,
96
96
  cache: context.cache,
97
- abort: activeAbort
97
+ abort: activeAbort,
98
+ scheduler: context.scheduler
98
99
  });
99
100
  }
100
101
  return server;
@@ -108,6 +109,9 @@ const __asyncSignalModule = (() => {
108
109
  get cache() {
109
110
  return registry._context?.().cache;
110
111
  },
112
+ get scheduler() {
113
+ return registry._context?.().scheduler;
114
+ },
111
115
  get version() {
112
116
  return runVersion;
113
117
  },
@@ -184,11 +188,20 @@ const __asyncSignalModule = (() => {
184
188
  _bindRegistry(nextRegistry, nextId) {
185
189
  registry = nextRegistry;
186
190
  registeredId = nextId;
187
- queueMicrotask(() => {
191
+ const start = () => {
188
192
  if (registry === nextRegistry && status === "idle") {
189
193
  state.refresh();
190
194
  }
191
- });
195
+ };
196
+ const scheduler = registry._context?.().scheduler;
197
+ if (scheduler) {
198
+ scheduler.enqueue("async", start, {
199
+ scope: registeredId,
200
+ key: `asyncSignal:${registeredId}:initial`
201
+ });
202
+ } else {
203
+ queueMicrotask(start);
204
+ }
192
205
  },
193
206
 
194
207
  _dispose() {
@@ -224,11 +237,26 @@ const __asyncSignalModule = (() => {
224
237
  for (const dependency of dependencies) {
225
238
  const dependencyId = String(dependency).split(".")[0];
226
239
  if (dependencyId && dependencyId !== registeredId) {
227
- dependencyCleanups.add(registry.subscribe(dependency, () => state.refresh()));
240
+ dependencyCleanups.add(registry.subscribe(dependency, () => scheduleRefresh()));
228
241
  }
229
242
  }
230
243
  }
231
244
 
245
+ function scheduleRefresh() {
246
+ if (activeAbort && !activeAbort.aborted) {
247
+ activeAbort.cancel(new Error(`Async signal "${registeredId}" dependency changed.`));
248
+ }
249
+ const scheduler = registry?._context?.().scheduler;
250
+ if (!scheduler) {
251
+ state.refresh();
252
+ return;
253
+ }
254
+ scheduler.enqueue("async", () => state.refresh(), {
255
+ scope: registeredId,
256
+ key: `asyncSignal:${registeredId}:refresh`
257
+ });
258
+ }
259
+
232
260
  function notify() {
233
261
  for (const subscriber of [...subscribers]) {
234
262
  subscriber(state);
@@ -536,6 +564,7 @@ const __cacheModule = (() => {
536
564
  const registryStore = registry ?? createRegistryStore();
537
565
  const definitions = registryStore._map(type);
538
566
  const entries = registryStore._map(`${type}.entries`);
567
+ const pending = new Map();
539
568
 
540
569
  const registryApi = attachRegistryInspection({
541
570
  register(id, definition = defineCache()) {
@@ -597,19 +626,37 @@ const __cacheModule = (() => {
597
626
  if (cached !== undefined) {
598
627
  return cached;
599
628
  }
600
- const value = await fn();
601
- registryApi.set(key, value, options);
602
- return value;
629
+ if (pending.has(key)) {
630
+ return pending.get(key);
631
+ }
632
+ let promise;
633
+ promise = Promise.resolve()
634
+ .then(fn)
635
+ .then((value) => {
636
+ if (pending.get(key) === promise) {
637
+ registryApi.set(key, value, options);
638
+ }
639
+ return value;
640
+ })
641
+ .finally(() => {
642
+ if (pending.get(key) === promise) {
643
+ pending.delete(key);
644
+ }
645
+ });
646
+ pending.set(key, promise);
647
+ return promise;
603
648
  },
604
649
 
605
650
  delete(key) {
606
651
  assertKey(key);
652
+ pending.delete(key);
607
653
  return entries.delete(key);
608
654
  },
609
655
 
610
656
  clear(prefix) {
611
657
  if (prefix === undefined) {
612
658
  entries.clear();
659
+ pending.clear();
613
660
  return registryApi;
614
661
  }
615
662
  for (const key of [...entries.keys()]) {
@@ -617,6 +664,11 @@ const __cacheModule = (() => {
617
664
  entries.delete(key);
618
665
  }
619
666
  }
667
+ for (const key of [...pending.keys()]) {
668
+ if (key.startsWith(prefix)) {
669
+ pending.delete(key);
670
+ }
671
+ }
620
672
  return registryApi;
621
673
  },
622
674
 
@@ -841,7 +893,8 @@ const __signalsModule = (() => {
841
893
  server: registry._context?.().server,
842
894
  router: registry._context?.().router,
843
895
  loader: registry._context?.().loader,
844
- cache: registry._context?.().cache
896
+ cache: registry._context?.().cache,
897
+ scheduler: registry._context?.().scheduler
845
898
  }));
846
899
  });
847
900
  }
@@ -869,6 +922,8 @@ const __signalsModule = (() => {
869
922
  const registryCleanups = new Map();
870
923
  const runtimeContext = {};
871
924
  const boundEntries = new Set();
925
+ let subscriptionCounter = 0;
926
+ let effectCounter = 0;
872
927
 
873
928
  const registry = attachRegistryInspection({
874
929
  register(id, signalLike) {
@@ -944,17 +999,21 @@ const __signalsModule = (() => {
944
999
  return createRef(registry, id);
945
1000
  },
946
1001
 
947
- subscribe(path, fn) {
1002
+ subscribe(path, fn, options = {}) {
948
1003
  if (typeof fn !== "function") {
949
1004
  throw new TypeError("subscribe(path, fn) requires a function.");
950
1005
  }
951
1006
  const parsed = parsePath(path, entries);
952
1007
  const entry = requireEntry(entries, parsed.id);
1008
+ const subscriptionId = ++subscriptionCounter;
953
1009
  return entry.subscribe(() => {
954
- fn(registry.get(parsed.path), {
1010
+ scheduleCallback(() => fn(registry.get(parsed.path), {
955
1011
  id: parsed.id,
956
1012
  path: parsed.path,
957
1013
  signal: entry
1014
+ }), {
1015
+ ...options,
1016
+ key: options.key ?? `signal:${parsed.path}:${subscriptionId}`
958
1017
  });
959
1018
  });
960
1019
  },
@@ -972,10 +1031,12 @@ const __signalsModule = (() => {
972
1031
  return registry.ref(id);
973
1032
  },
974
1033
 
975
- effect(fn) {
1034
+ effect(fn, options = {}) {
976
1035
  let cleanup;
977
1036
  let dependencyCleanups = [];
978
1037
  let stopped = false;
1038
+ const scheduler = options.scheduler;
1039
+ const effectId = ++effectCounter;
979
1040
 
980
1041
  const run = () => {
981
1042
  if (stopped) {
@@ -994,10 +1055,22 @@ const __signalsModule = (() => {
994
1055
  server: runtimeContext.server,
995
1056
  router: runtimeContext.router,
996
1057
  loader: runtimeContext.loader,
997
- cache: runtimeContext.cache
1058
+ cache: runtimeContext.cache,
1059
+ scheduler: runtimeContext.scheduler
998
1060
  }));
999
1061
  cleanup = outcome.value;
1000
- dependencyCleanups = outcome.dependencies.map((dependency) => registry.subscribe(dependency, run));
1062
+ dependencyCleanups = outcome.dependencies.map((dependency) => registry.subscribe(dependency, scheduleRun));
1063
+ };
1064
+
1065
+ const scheduleRun = () => {
1066
+ if (!scheduler) {
1067
+ run();
1068
+ return;
1069
+ }
1070
+ scheduler.enqueue(options.phase ?? "effect", run, {
1071
+ scope: options.scope,
1072
+ key: options.key ?? `effect:${effectId}`
1073
+ });
1001
1074
  };
1002
1075
 
1003
1076
  run();
@@ -1074,6 +1147,17 @@ const __signalsModule = (() => {
1074
1147
  registryCleanups.set(id, cleanup);
1075
1148
  }
1076
1149
  }
1150
+
1151
+ function scheduleCallback(fn, options = {}) {
1152
+ const scheduler = options.scheduler;
1153
+ if (!scheduler || options.phase === "sync") {
1154
+ return fn();
1155
+ }
1156
+ return scheduler.enqueue(options.phase ?? "effect", fn, {
1157
+ scope: options.scope,
1158
+ key: options.key
1159
+ });
1160
+ }
1077
1161
  }
1078
1162
 
1079
1163
  function normalizeSignal(signalLike) {
@@ -1540,10 +1624,15 @@ const __componentModule = (() => {
1540
1624
  html,
1541
1625
  attach(target) {
1542
1626
  for (const hook of attachHooks) {
1543
- const cleanup = hook(target);
1544
- if (typeof cleanup === "function") {
1545
- cleanups.push(cleanup);
1546
- }
1627
+ runtime.scheduler?.enqueue("lifecycle", () => {
1628
+ const cleanup = hook(target);
1629
+ if (typeof cleanup === "function") {
1630
+ cleanups.push(cleanup);
1631
+ }
1632
+ }, {
1633
+ scope,
1634
+ key: `attach:${attachHooks.indexOf(hook)}`
1635
+ }) ?? runAttachHook(hook, target);
1547
1636
  }
1548
1637
  },
1549
1638
  mount(target) {
@@ -1551,7 +1640,17 @@ const __componentModule = (() => {
1551
1640
  },
1552
1641
  visible(target, observeVisible) {
1553
1642
  for (const hook of visibleHooks) {
1554
- const cleanup = observeVisible(target, hook);
1643
+ const cleanup = observeVisible(target, () => {
1644
+ runtime.scheduler?.enqueue("lifecycle", () => {
1645
+ const hookCleanup = hook(target);
1646
+ if (typeof hookCleanup === "function") {
1647
+ cleanups.push(hookCleanup);
1648
+ }
1649
+ }, {
1650
+ scope,
1651
+ key: `visible:${visibleHooks.indexOf(hook)}`
1652
+ }) ?? runVisibleHook(hook, target);
1653
+ });
1555
1654
  if (typeof cleanup === "function") {
1556
1655
  cleanups.push(cleanup);
1557
1656
  }
@@ -1561,6 +1660,7 @@ const __componentModule = (() => {
1561
1660
  while (destroyHooks.length > 0) {
1562
1661
  destroyHooks.pop()?.();
1563
1662
  }
1663
+ runtime.scheduler?.markScopeDestroyed(scope);
1564
1664
  while (cleanups.length > 0) {
1565
1665
  cleanups.pop()?.();
1566
1666
  }
@@ -1569,10 +1669,24 @@ const __componentModule = (() => {
1569
1669
  }
1570
1670
  }
1571
1671
  };
1672
+
1673
+ function runAttachHook(hook, target) {
1674
+ const cleanup = hook(target);
1675
+ if (typeof cleanup === "function") {
1676
+ cleanups.push(cleanup);
1677
+ }
1678
+ }
1679
+
1680
+ function runVisibleHook(hook, target) {
1681
+ const cleanup = hook(target);
1682
+ if (typeof cleanup === "function") {
1683
+ cleanups.push(cleanup);
1684
+ }
1685
+ }
1572
1686
  }
1573
1687
 
1574
1688
  function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks, renderScopedTemplate }) {
1575
- const { signals, handlers, loader, server, router, cache } = runtime;
1689
+ const { signals, handlers, loader, server, router, cache, scheduler } = runtime;
1576
1690
  const generatedHandlers = new WeakMap();
1577
1691
  let generatedHandlerCounter = 0;
1578
1692
  let generatedSignalCounter = 0;
@@ -1584,6 +1698,7 @@ const __componentModule = (() => {
1584
1698
  server,
1585
1699
  router,
1586
1700
  cache,
1701
+ scheduler,
1587
1702
 
1588
1703
  signal(name, initial) {
1589
1704
  if (arguments.length === 1) {
@@ -1628,7 +1743,11 @@ const __componentModule = (() => {
1628
1743
  },
1629
1744
 
1630
1745
  effect(fn) {
1631
- const cleanup = signals.effect(() => fn.call(context));
1746
+ const cleanup = signals.effect(() => fn.call(context), {
1747
+ scheduler,
1748
+ phase: "effect",
1749
+ scope
1750
+ });
1632
1751
  cleanups.push(cleanup);
1633
1752
  return cleanup;
1634
1753
  },
@@ -1750,90 +1869,9 @@ const __componentModule = (() => {
1750
1869
  })();
1751
1870
 
1752
1871
  const __serverModule = (() => {
1753
- const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
1754
1872
  const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
1755
-
1756
- function createServerRegistry(initialMap = {}, options = {}) {
1757
- const registryStore = options.registry ?? createRegistryStore();
1758
- const type = options.type ?? "server";
1759
- const entries = registryStore._map(type);
1760
- const defaults = {};
1761
-
1762
- const registry = attachRegistryInspection({
1763
- register(id, fn) {
1764
- assertServerId(id);
1765
- if (typeof fn !== "function") {
1766
- throw new TypeError(`Server function "${id}" must be a function.`);
1767
- }
1768
- if (entries.has(id)) {
1769
- throw new Error(`Server function "${id}" is already registered.`);
1770
- }
1771
- entries.set(id, fn);
1772
- return id;
1773
- },
1774
-
1775
- registerMany(map) {
1776
- for (const [id, fn] of Object.entries(map ?? {})) {
1777
- registry.register(id, fn);
1778
- }
1779
- return registry;
1780
- },
1781
-
1782
- unregister(id) {
1783
- assertServerId(id);
1784
- return entries.delete(id);
1785
- },
1786
-
1787
- resolve(id) {
1788
- assertServerId(id);
1789
- return entries.get(id);
1790
- },
1791
-
1792
- async run(id, args = [], context = {}) {
1793
- assertServerId(id);
1794
- const fn = registry.resolve(id);
1795
- if (!fn) {
1796
- throw new Error(`Server function "${id}" is not registered.`);
1797
- }
1798
-
1799
- let runContext;
1800
- const server = createServerNamespace((childId, childArgs, childContext = {}) => {
1801
- return registry.run(childId, childArgs, { ...runContext, ...childContext });
1802
- }, {}, () => runContext);
1803
-
1804
- const mergedContext = {
1805
- ...defaults,
1806
- ...context,
1807
- cache: defaults.cache ?? context.cache
1808
- };
1809
-
1810
- runContext = {
1811
- ...mergedContext,
1812
- id,
1813
- args,
1814
- input: mergedContext.input,
1815
- signals: createSignalReader(mergedContext.signals),
1816
- abort: mergedContext.abort,
1817
- cache: mergedContext.cache,
1818
- server
1819
- };
1820
-
1821
- return fn.call(runContext, ...args);
1822
- },
1823
-
1824
- _setContext(context = {}) {
1825
- Object.assign(defaults, context);
1826
- return registry;
1827
- },
1828
-
1829
- _adoptMany() {
1830
- return registry;
1831
- }
1832
- }, registryStore, type);
1833
-
1834
- registry.registerMany(initialMap);
1835
- return createServerNamespace((id, args, context) => registry.run(id, args, context), registry, () => defaults);
1836
- }
1873
+ const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
1874
+ const appliedServerValues = new WeakSet();
1837
1875
 
1838
1876
  function createServerProxy({
1839
1877
  endpoint = "/__async/server",
@@ -1842,13 +1880,14 @@ const __serverModule = (() => {
1842
1880
  loader,
1843
1881
  router,
1844
1882
  cache,
1883
+ scheduler,
1845
1884
  headers = {}
1846
1885
  } = {}) {
1847
1886
  if (typeof fetchImpl !== "function") {
1848
1887
  throw new TypeError("createServerProxy(...) requires fetch to be available.");
1849
1888
  }
1850
1889
 
1851
- const defaults = { signals, loader, router, cache };
1890
+ const defaults = { signals, loader, router, cache, scheduler };
1852
1891
 
1853
1892
  async function run(id, args = [], context = {}) {
1854
1893
  assertServerId(id);
@@ -1858,6 +1897,7 @@ const __serverModule = (() => {
1858
1897
  input: context.input ?? defaultInput(runContext),
1859
1898
  signals: context.signalValues ?? snapshotSignalPaths(context.signalPaths, runContext.signals)
1860
1899
  };
1900
+ assertJsonTransportable(body);
1861
1901
 
1862
1902
  const response = await fetchImpl(joinEndpoint(endpoint, id), {
1863
1903
  method: "POST",
@@ -1875,7 +1915,7 @@ const __serverModule = (() => {
1875
1915
 
1876
1916
  const result = await readServerResponse(response);
1877
1917
  await applyServerResult(result, runContext);
1878
- return unwrapServerResult(result);
1918
+ return markAppliedServerValue(unwrapServerResult(result));
1879
1919
  }
1880
1920
 
1881
1921
  return createServerNamespace(run, {
@@ -1910,6 +1950,9 @@ const __serverModule = (() => {
1910
1950
  if (!isServerEnvelope(result)) {
1911
1951
  return result;
1912
1952
  }
1953
+ if (result[appliedServerResult] || appliedServerValues.has(result)) {
1954
+ return result;
1955
+ }
1913
1956
 
1914
1957
  if (result.signals && context.signals) {
1915
1958
  for (const [path, value] of Object.entries(result.signals)) {
@@ -1933,6 +1976,12 @@ const __serverModule = (() => {
1933
1976
  throw toError(result.error);
1934
1977
  }
1935
1978
 
1979
+ Object.defineProperty(result, appliedServerResult, {
1980
+ configurable: true,
1981
+ enumerable: false,
1982
+ value: true
1983
+ });
1984
+
1936
1985
  return result;
1937
1986
  }
1938
1987
 
@@ -1943,6 +1992,13 @@ const __serverModule = (() => {
1943
1992
  return result;
1944
1993
  }
1945
1994
 
1995
+ function markAppliedServerValue(value) {
1996
+ if (value && typeof value === "object") {
1997
+ appliedServerValues.add(value);
1998
+ }
1999
+ return value;
2000
+ }
2001
+
1946
2002
  function defaultInput(context = {}) {
1947
2003
  const form = findForm(context);
1948
2004
  if (form) {
@@ -2120,6 +2176,30 @@ const __serverModule = (() => {
2120
2176
  return output;
2121
2177
  }
2122
2178
 
2179
+ function assertJsonTransportable(value, seen = new Set()) {
2180
+ if (value == null || typeof value !== "object") {
2181
+ return;
2182
+ }
2183
+ if (seen.has(value)) {
2184
+ return;
2185
+ }
2186
+ seen.add(value);
2187
+
2188
+ const tag = Object.prototype.toString.call(value);
2189
+ if (tag === "[object File]" || tag === "[object Blob]" || tag === "[object FormData]") {
2190
+ throw new Error("Server proxy JSON transport does not support File, Blob, or FormData values yet.");
2191
+ }
2192
+ if (Array.isArray(value)) {
2193
+ for (const item of value) {
2194
+ assertJsonTransportable(item, seen);
2195
+ }
2196
+ return;
2197
+ }
2198
+ for (const item of Object.values(value)) {
2199
+ assertJsonTransportable(item, seen);
2200
+ }
2201
+ }
2202
+
2123
2203
  function joinEndpoint(endpoint, id) {
2124
2204
  return `${String(endpoint).replace(/\/$/, "")}/${encodeURIComponent(id)}`;
2125
2205
  }
@@ -2146,7 +2226,7 @@ const __serverModule = (() => {
2146
2226
  throw new TypeError("Server function id must be a non-empty string.");
2147
2227
  }
2148
2228
  }
2149
- return { createServerRegistry, createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput };
2229
+ return { createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
2150
2230
  })();
2151
2231
 
2152
2232
  const __handlersModule = (() => {
@@ -2349,18 +2429,321 @@ const __handlersModule = (() => {
2349
2429
  return { createHandlerRegistry, parseHandlerRef, isHandlerToken };
2350
2430
  })();
2351
2431
 
2432
+ const __schedulerModule = (() => {
2433
+ const defaultPhases = ["binding", "lifecycle", "effect", "async", "post"];
2434
+
2435
+ function createScheduler(options = {}) {
2436
+ const phases = [...(options.phases ?? defaultPhases)];
2437
+ const queues = new Map(phases.map((phase) => [phase, []]));
2438
+ const keyedJobs = new Map();
2439
+ const destroyedScopes = new Set();
2440
+ const objectScopeIds = new WeakMap();
2441
+ const onError = typeof options.onError === "function" ? options.onError : undefined;
2442
+ const maxDepth = options.maxDepth ?? 100;
2443
+ const strategy = options.strategy ?? "microtask";
2444
+ let destroyed = false;
2445
+ let flushing = false;
2446
+ let scheduled = false;
2447
+ let batchDepth = 0;
2448
+ let jobCounter = 0;
2449
+ let scopeCounter = 0;
2450
+
2451
+ const api = {
2452
+ strategy,
2453
+ phases,
2454
+
2455
+ batch(fn) {
2456
+ if (typeof fn !== "function") {
2457
+ throw new TypeError("scheduler.batch(fn) requires a function.");
2458
+ }
2459
+ assertActive();
2460
+ batchDepth += 1;
2461
+ let asyncBatch = false;
2462
+ try {
2463
+ const value = fn();
2464
+ if (value && typeof value.then === "function") {
2465
+ asyncBatch = true;
2466
+ return value.finally(() => {
2467
+ batchDepth -= 1;
2468
+ requestFlush();
2469
+ });
2470
+ }
2471
+ return value;
2472
+ } finally {
2473
+ if (!asyncBatch && batchDepth > 0) {
2474
+ batchDepth -= 1;
2475
+ requestFlush();
2476
+ }
2477
+ }
2478
+ },
2479
+
2480
+ enqueue(phase, fn, options = {}) {
2481
+ assertActive();
2482
+ assertPhase(phase);
2483
+ if (typeof fn !== "function") {
2484
+ throw new TypeError("scheduler.enqueue(phase, fn) requires a function.");
2485
+ }
2486
+ const scope = options.scope;
2487
+ if (scope !== undefined && destroyedScopes.has(scope)) {
2488
+ return noop;
2489
+ }
2490
+
2491
+ const dedupeKey = options.key === undefined ? undefined : `${phase}:${scopeKey(scope)}:${String(options.key)}`;
2492
+ if (dedupeKey && keyedJobs.has(dedupeKey)) {
2493
+ return keyedJobs.get(dedupeKey).cancel;
2494
+ }
2495
+
2496
+ const job = {
2497
+ id: ++jobCounter,
2498
+ phase,
2499
+ fn,
2500
+ scope,
2501
+ boundary: options.boundary,
2502
+ key: dedupeKey,
2503
+ canceled: false,
2504
+ cancel() {
2505
+ job.canceled = true;
2506
+ if (job.key) {
2507
+ keyedJobs.delete(job.key);
2508
+ }
2509
+ }
2510
+ };
2511
+ queues.get(phase).push(job);
2512
+ if (job.key) {
2513
+ keyedJobs.set(job.key, job);
2514
+ }
2515
+ requestFlush();
2516
+ return job.cancel;
2517
+ },
2518
+
2519
+ afterFlush(fn, options = {}) {
2520
+ return api.enqueue("post", fn, options);
2521
+ },
2522
+
2523
+ async flush() {
2524
+ assertActive();
2525
+ if (flushing) {
2526
+ return;
2527
+ }
2528
+ scheduled = false;
2529
+ flushing = true;
2530
+ let depth = 0;
2531
+ try {
2532
+ while (hasJobs()) {
2533
+ depth += 1;
2534
+ if (depth > maxDepth) {
2535
+ throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
2536
+ }
2537
+ for (const phase of phases) {
2538
+ await flushPhase(phase);
2539
+ }
2540
+ }
2541
+ } finally {
2542
+ flushing = false;
2543
+ if (hasJobs()) {
2544
+ requestFlush();
2545
+ }
2546
+ }
2547
+ },
2548
+
2549
+ async flushScope(scope) {
2550
+ assertActive();
2551
+ if (flushing) {
2552
+ return;
2553
+ }
2554
+ scheduled = false;
2555
+ flushing = true;
2556
+ let depth = 0;
2557
+ try {
2558
+ while (hasJobsForScope(scope)) {
2559
+ depth += 1;
2560
+ if (depth > maxDepth) {
2561
+ throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
2562
+ }
2563
+ for (const phase of phases) {
2564
+ await flushPhase(phase, scope);
2565
+ }
2566
+ }
2567
+ } finally {
2568
+ flushing = false;
2569
+ if (hasJobs()) {
2570
+ requestFlush();
2571
+ }
2572
+ }
2573
+ },
2574
+
2575
+ cancelScope(scope) {
2576
+ if (scope === undefined) {
2577
+ return api;
2578
+ }
2579
+ for (const queue of queues.values()) {
2580
+ for (const job of queue) {
2581
+ if (job.scope === scope) {
2582
+ job.cancel();
2583
+ }
2584
+ }
2585
+ }
2586
+ return api;
2587
+ },
2588
+
2589
+ markScopeDestroyed(scope) {
2590
+ if (scope !== undefined) {
2591
+ destroyedScopes.add(scope);
2592
+ api.cancelScope(scope);
2593
+ }
2594
+ return api;
2595
+ },
2596
+
2597
+ inspect() {
2598
+ const counts = {};
2599
+ for (const [phase, queue] of queues) {
2600
+ counts[phase] = queue.filter((job) => !job.canceled).length;
2601
+ }
2602
+ return {
2603
+ strategy,
2604
+ phases: [...phases],
2605
+ pending: counts,
2606
+ scopesDestroyed: destroyedScopes.size,
2607
+ flushing,
2608
+ scheduled
2609
+ };
2610
+ },
2611
+
2612
+ destroy() {
2613
+ destroyed = true;
2614
+ for (const queue of queues.values()) {
2615
+ for (const job of queue) {
2616
+ job.cancel();
2617
+ }
2618
+ queue.length = 0;
2619
+ }
2620
+ keyedJobs.clear();
2621
+ destroyedScopes.clear();
2622
+ }
2623
+ };
2624
+
2625
+ return api;
2626
+
2627
+ function requestFlush() {
2628
+ if (strategy === "manual" || destroyed || flushing || batchDepth > 0 || scheduled) {
2629
+ return;
2630
+ }
2631
+ scheduled = true;
2632
+ scheduleMicrotask(() => {
2633
+ if (!destroyed) {
2634
+ void api.flush();
2635
+ }
2636
+ });
2637
+ }
2638
+
2639
+ async function flushPhase(phase, scope) {
2640
+ const queue = queues.get(phase);
2641
+ const remaining = [];
2642
+ const runnable = [];
2643
+
2644
+ for (const job of queue.splice(0)) {
2645
+ if (job.canceled) {
2646
+ continue;
2647
+ }
2648
+ if (scope !== undefined && job.scope !== scope) {
2649
+ remaining.push(job);
2650
+ continue;
2651
+ }
2652
+ runnable.push(job);
2653
+ }
2654
+
2655
+ queue.push(...remaining);
2656
+
2657
+ for (const job of runnable) {
2658
+ if (job.key) {
2659
+ keyedJobs.delete(job.key);
2660
+ }
2661
+ if (job.canceled || (job.scope !== undefined && destroyedScopes.has(job.scope))) {
2662
+ continue;
2663
+ }
2664
+ try {
2665
+ await job.fn();
2666
+ } catch (error) {
2667
+ if (onError) {
2668
+ onError(error, job);
2669
+ } else {
2670
+ throw error;
2671
+ }
2672
+ }
2673
+ }
2674
+ }
2675
+
2676
+ function hasJobs() {
2677
+ for (const queue of queues.values()) {
2678
+ if (queue.some((job) => !job.canceled)) {
2679
+ return true;
2680
+ }
2681
+ }
2682
+ return false;
2683
+ }
2684
+
2685
+ function hasJobsForScope(scope) {
2686
+ for (const queue of queues.values()) {
2687
+ if (queue.some((job) => !job.canceled && job.scope === scope)) {
2688
+ return true;
2689
+ }
2690
+ }
2691
+ return false;
2692
+ }
2693
+
2694
+ function assertActive() {
2695
+ if (destroyed) {
2696
+ throw new Error("Scheduler has been destroyed.");
2697
+ }
2698
+ }
2699
+
2700
+ function assertPhase(phase) {
2701
+ if (!queues.has(phase)) {
2702
+ throw new Error(`Unknown scheduler phase "${phase}".`);
2703
+ }
2704
+ }
2705
+
2706
+ function scopeKey(scope) {
2707
+ if (scope === undefined) {
2708
+ return "global";
2709
+ }
2710
+ if ((typeof scope === "object" && scope !== null) || typeof scope === "function") {
2711
+ if (!objectScopeIds.has(scope)) {
2712
+ objectScopeIds.set(scope, `scope:${++scopeCounter}`);
2713
+ }
2714
+ return objectScopeIds.get(scope);
2715
+ }
2716
+ return String(scope);
2717
+ }
2718
+ }
2719
+
2720
+ function scheduleMicrotask(fn) {
2721
+ if (typeof queueMicrotask === "function") {
2722
+ queueMicrotask(fn);
2723
+ return;
2724
+ }
2725
+ Promise.resolve().then(fn);
2726
+ }
2727
+
2728
+ function noop() {}
2729
+ return { createScheduler };
2730
+ })();
2731
+
2352
2732
  const __loaderModule = (() => {
2353
2733
  const { renderComponent } = __componentModule;
2354
2734
  const { createHandlerRegistry } = __handlersModule;
2735
+ const { createScheduler } = __schedulerModule;
2355
2736
  const { createSignalRegistry, isSignalRef } = __signalsModule;
2356
2737
  const { matchAttribute, normalizeAttributeConfig, readAttribute } = __attributesModule;
2357
2738
  const inlineBindingPrefix = "__async:inline:";
2358
2739
 
2359
- function Loader({ root, signals, handlers, server, router, cache, attributes } = {}) {
2740
+ function Loader({ root, signals, handlers, server, router, cache, attributes, scheduler } = {}) {
2360
2741
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
2361
2742
  const rootNode = root ?? documentRef;
2362
2743
  const signalRegistry = signals ?? createSignalRegistry();
2363
2744
  const handlerRegistry = handlers ?? createHandlerRegistry();
2745
+ const schedulerInstance = scheduler ?? createScheduler();
2746
+ const ownsScheduler = !scheduler;
2364
2747
  const attributeConfig = normalizeAttributeConfig(attributes);
2365
2748
  const cleanups = new Set();
2366
2749
  const eventBindings = new WeakMap();
@@ -2381,6 +2764,7 @@ const __loaderModule = (() => {
2381
2764
  server,
2382
2765
  router,
2383
2766
  cache,
2767
+ scheduler: schedulerInstance,
2384
2768
  attributes: attributeConfig,
2385
2769
 
2386
2770
  start() {
@@ -2420,6 +2804,7 @@ const __loaderModule = (() => {
2420
2804
  server: api.server,
2421
2805
  router: api.router,
2422
2806
  cache: api.cache,
2807
+ scheduler: schedulerInstance,
2423
2808
  attributes: attributeConfig
2424
2809
  });
2425
2810
  cleanupChildren(target);
@@ -2440,6 +2825,9 @@ const __loaderModule = (() => {
2440
2825
  runCleanup(cleanup);
2441
2826
  }
2442
2827
  cleanups.clear();
2828
+ if (ownsScheduler) {
2829
+ schedulerInstance.destroy();
2830
+ }
2443
2831
  },
2444
2832
 
2445
2833
  _observeVisible(target, fn) {
@@ -2457,13 +2845,14 @@ const __loaderModule = (() => {
2457
2845
  }
2458
2846
  };
2459
2847
 
2460
- signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
2848
+ signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache, scheduler: schedulerInstance });
2461
2849
  api.server?._setContext?.({
2462
2850
  signals: signalRegistry,
2463
2851
  handlers: handlerRegistry,
2464
2852
  loader: api,
2465
2853
  router: api.router,
2466
- cache: api.cache
2854
+ cache: api.cache,
2855
+ scheduler: schedulerInstance
2467
2856
  });
2468
2857
 
2469
2858
  function bindEventAttributes(scope) {
@@ -2495,18 +2884,19 @@ const __loaderModule = (() => {
2495
2884
 
2496
2885
  const listener = async (event) => {
2497
2886
  try {
2498
- await handlerRegistry.run(ref, {
2887
+ await schedulerInstance.batch(() => handlerRegistry.run(ref, {
2499
2888
  signals: signalRegistry,
2500
2889
  handlers: handlerRegistry,
2501
2890
  loader: api,
2502
2891
  server: api.server,
2503
2892
  router: api.router,
2504
2893
  cache: api.cache,
2894
+ scheduler: schedulerInstance,
2505
2895
  event,
2506
2896
  element,
2507
2897
  el: element,
2508
2898
  root: rootNode
2509
- });
2899
+ }));
2510
2900
  } catch (error) {
2511
2901
  dispatchAsyncError(element, error);
2512
2902
  }
@@ -2622,7 +3012,12 @@ const __loaderModule = (() => {
2622
3012
 
2623
3013
  const read = () => readBinding(path, options);
2624
3014
  apply(read());
2625
- addCleanup(subscribeBinding(path, () => apply(read())), element);
3015
+ addCleanup(subscribeBinding(path, () => {
3016
+ schedulerInstance.enqueue("binding", () => apply(read()), {
3017
+ scope: element,
3018
+ key
3019
+ });
3020
+ }), element);
2626
3021
  }
2627
3022
 
2628
3023
  function bindValueWriter(element, path) {
@@ -2683,7 +3078,12 @@ const __loaderModule = (() => {
2683
3078
  const state = {
2684
3079
  id,
2685
3080
  templates,
2686
- cleanup: signalRegistry.subscribe(`${id}.$status`, () => renderBoundary(boundary))
3081
+ cleanup: signalRegistry.subscribe(`${id}.$status`, () => {
3082
+ schedulerInstance.enqueue("binding", () => renderBoundary(boundary), {
3083
+ scope: boundary,
3084
+ key: `boundary:${id}`
3085
+ });
3086
+ })
2687
3087
  };
2688
3088
  boundaryState.set(boundary, state);
2689
3089
  addCleanup(state.cleanup, boundary);
@@ -2723,7 +3123,7 @@ const __loaderModule = (() => {
2723
3123
  }
2724
3124
  mountedElements.add(element);
2725
3125
  for (const ref of refs) {
2726
- runPseudo(element, ref);
3126
+ scheduleLifecycle(element, () => runPseudo(element, ref), `attach:${ref}`);
2727
3127
  }
2728
3128
  }
2729
3129
 
@@ -2736,7 +3136,7 @@ const __loaderModule = (() => {
2736
3136
  continue;
2737
3137
  }
2738
3138
  visibleElements.add(element);
2739
- addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
3139
+ addCleanup(observeVisible(element, () => scheduleLifecycle(element, () => runPseudo(element, ref), `visible:${ref}`)), element);
2740
3140
  }
2741
3141
  }
2742
3142
 
@@ -2760,6 +3160,7 @@ const __loaderModule = (() => {
2760
3160
  server: api.server,
2761
3161
  router: api.router,
2762
3162
  cache: api.cache,
3163
+ scheduler: schedulerInstance,
2763
3164
  element,
2764
3165
  el: element,
2765
3166
  root: rootNode
@@ -2778,10 +3179,13 @@ const __loaderModule = (() => {
2778
3179
  const ownerWindow = target.ownerDocument?.defaultView ?? globalThis;
2779
3180
  const Observer = ownerWindow.IntersectionObserver ?? globalThis.IntersectionObserver;
2780
3181
  if (!Observer) {
2781
- queueMicrotask(() => {
3182
+ schedulerInstance.enqueue("lifecycle", () => {
2782
3183
  if (!destroyed) {
2783
3184
  fn(target);
2784
3185
  }
3186
+ }, {
3187
+ scope: target,
3188
+ key: "visible:fallback"
2785
3189
  });
2786
3190
  return () => {};
2787
3191
  }
@@ -2836,6 +3240,7 @@ const __loaderModule = (() => {
2836
3240
  }
2837
3241
  for (const element of elementsIn(node)) {
2838
3242
  runScopedCleanups(element);
3243
+ schedulerInstance.markScopeDestroyed(element);
2839
3244
  }
2840
3245
  }
2841
3246
 
@@ -2859,6 +3264,13 @@ const __loaderModule = (() => {
2859
3264
  }
2860
3265
  }
2861
3266
 
3267
+ function scheduleLifecycle(element, fn, key) {
3268
+ schedulerInstance.enqueue("lifecycle", fn, {
3269
+ scope: element,
3270
+ key
3271
+ });
3272
+ }
3273
+
2862
3274
  return api;
2863
3275
  }
2864
3276
 
@@ -3189,6 +3601,7 @@ const __partialsModule = (() => {
3189
3601
  const __routerModule = (() => {
3190
3602
  const { Loader } = __loaderModule;
3191
3603
  const { createHandlerRegistry } = __handlersModule;
3604
+ const { createScheduler } = __schedulerModule;
3192
3605
  const { createSignalRegistry } = __signalsModule;
3193
3606
  const { applyServerResult } = __serverModule;
3194
3607
  const { createRegistryStore } = __registryStoreModule;
@@ -3219,6 +3632,7 @@ const __routerModule = (() => {
3219
3632
  const nextRoute = normalizeRoute(pattern, definition);
3220
3633
  entries.set(pattern, nextRoute.definition);
3221
3634
  routes.push(nextRoute);
3635
+ sortRoutes(routes);
3222
3636
  return nextRoute;
3223
3637
  },
3224
3638
 
@@ -3291,6 +3705,7 @@ const __routerModule = (() => {
3291
3705
  const nextRoute = normalizeRoute(pattern, definition);
3292
3706
  entries.set(pattern, nextRoute.definition);
3293
3707
  routes.push(nextRoute);
3708
+ sortRoutes(routes);
3294
3709
  }
3295
3710
  }
3296
3711
 
@@ -3307,12 +3722,15 @@ const __routerModule = (() => {
3307
3722
  partials,
3308
3723
  fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
3309
3724
  routeEndpoint = "/__async/route",
3310
- attributes
3725
+ attributes,
3726
+ scheduler
3311
3727
  } = {}) {
3312
3728
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
3313
3729
  const rootNode = root ?? documentRef;
3314
3730
  const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
3315
3731
  const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
3732
+ const schedulerInstance = scheduler ?? loader?.scheduler ?? createScheduler();
3733
+ const ownsScheduler = !scheduler && !loader?.scheduler;
3316
3734
  const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
3317
3735
  const loaderInstance =
3318
3736
  loader ??
@@ -3322,11 +3740,14 @@ const __routerModule = (() => {
3322
3740
  handlers: handlerRegistry,
3323
3741
  server,
3324
3742
  cache,
3743
+ scheduler: schedulerInstance,
3325
3744
  attributes: attributeConfig
3326
3745
  });
3327
3746
  const ownsLoader = !loader;
3328
3747
  const cleanups = new Set();
3329
3748
  let destroyed = false;
3749
+ let navigationVersion = 0;
3750
+ let activeNavigation;
3330
3751
 
3331
3752
  const api = {
3332
3753
  mode,
@@ -3339,12 +3760,13 @@ const __routerModule = (() => {
3339
3760
  server,
3340
3761
  cache,
3341
3762
  partials,
3763
+ scheduler: schedulerInstance,
3342
3764
  attributes: attributeConfig,
3343
3765
 
3344
3766
  start() {
3345
3767
  assertActive();
3346
3768
  loaderInstance.router = api;
3347
- signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache });
3769
+ signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache, scheduler: schedulerInstance });
3348
3770
  if (ownsLoader) {
3349
3771
  loaderInstance.start();
3350
3772
  }
@@ -3366,7 +3788,7 @@ const __routerModule = (() => {
3366
3788
  },
3367
3789
 
3368
3790
  match(url) {
3369
- return routes.match(url);
3791
+ return routes.match(resolveUrl(url));
3370
3792
  },
3371
3793
 
3372
3794
  prefetch(url) {
@@ -3391,7 +3813,7 @@ const __routerModule = (() => {
3391
3813
  return null;
3392
3814
  }
3393
3815
 
3394
- const target = toUrl(url);
3816
+ const target = resolveUrl(url);
3395
3817
  if (mode === "ssr-spa") {
3396
3818
  return fetchRoutePartial(target, options);
3397
3819
  }
@@ -3403,10 +3825,14 @@ const __routerModule = (() => {
3403
3825
  return;
3404
3826
  }
3405
3827
  destroyed = true;
3828
+ activeNavigation?.controller.abort(new Error("Router has been destroyed."));
3406
3829
  for (const cleanup of cleanups) {
3407
3830
  cleanup();
3408
3831
  }
3409
3832
  cleanups.clear();
3833
+ if (ownsScheduler) {
3834
+ schedulerInstance.destroy();
3835
+ }
3410
3836
  }
3411
3837
  };
3412
3838
 
@@ -3442,24 +3868,37 @@ const __routerModule = (() => {
3442
3868
  async function renderLocalRoutePartial(target, options = {}) {
3443
3869
  const matched = api.match(target);
3444
3870
  if (!matched) {
3871
+ beginNavigation(target, null);
3445
3872
  setNoRouteError(target);
3446
3873
  return null;
3447
3874
  }
3448
3875
 
3876
+ const navigation = beginNavigation(target, matched);
3449
3877
  setMatchedRouterState(target, matched, { pending: true, error: null });
3450
3878
 
3451
3879
  try {
3452
3880
  if (!matched.route?.partial || !partials?.resolve?.(matched.route.partial)) {
3453
3881
  const error = new Error(`Route "${target.pathname}" does not have a registered partial.`);
3454
- setRouterState({ pending: false, error });
3882
+ if (isActiveNavigation(navigation)) {
3883
+ setRouterState({ pending: false, error });
3884
+ }
3455
3885
  return null;
3456
3886
  }
3457
3887
 
3458
- const result = await partials.render(matched.route.partial, matched.params, contextFor(matched));
3459
- await applyNavigationResult(result, target, options);
3888
+ const result = await partials.render(matched.route.partial, matched.params, contextFor(matched, navigation));
3889
+ if (!isActiveNavigation(navigation)) {
3890
+ return null;
3891
+ }
3892
+ await applyNavigationResult(result, target, options, navigation);
3893
+ if (!isActiveNavigation(navigation)) {
3894
+ return null;
3895
+ }
3460
3896
  setRouterState({ pending: false, error: null });
3461
3897
  return result;
3462
3898
  } catch (error) {
3899
+ if (!isActiveNavigation(navigation)) {
3900
+ return null;
3901
+ }
3463
3902
  setRouterState({ pending: false, error });
3464
3903
  throw error;
3465
3904
  }
@@ -3467,28 +3906,48 @@ const __routerModule = (() => {
3467
3906
 
3468
3907
  async function fetchRoutePartial(target, options = {}) {
3469
3908
  const matched = api.match(target);
3909
+ const navigation = beginNavigation(target, matched);
3470
3910
  setMatchedRouterState(target, matched, { pending: true, error: null });
3471
3911
 
3472
3912
  try {
3473
- const result = await fetchRoute(target.href);
3474
- await applyNavigationResult(result, target, options);
3913
+ const result = await fetchRoute(target.href, { signal: navigation.abort });
3914
+ if (!isActiveNavigation(navigation)) {
3915
+ return null;
3916
+ }
3917
+ await applyNavigationResult(result, target, options, navigation);
3918
+ if (!isActiveNavigation(navigation)) {
3919
+ return null;
3920
+ }
3475
3921
  setRouterState({ pending: false, error: null });
3476
3922
  return result;
3477
3923
  } catch (error) {
3924
+ if (!isActiveNavigation(navigation)) {
3925
+ return null;
3926
+ }
3478
3927
  setRouterState({ pending: false, error });
3479
3928
  throw error;
3480
3929
  }
3481
3930
  }
3482
3931
 
3483
- async function applyNavigationResult(result, target, options) {
3932
+ async function applyNavigationResult(result, target, options, navigation) {
3933
+ if (!isActiveNavigation(navigation)) {
3934
+ return;
3935
+ }
3484
3936
  await applyServerResult(result, {
3485
3937
  signals: signalRegistry,
3486
3938
  loader: loaderInstance,
3487
3939
  router: api,
3488
- cache
3940
+ cache,
3941
+ scheduler: schedulerInstance,
3942
+ abort: navigation?.abort
3489
3943
  });
3944
+ await schedulerInstance.flush();
3945
+ if (!isActiveNavigation(navigation)) {
3946
+ return;
3947
+ }
3490
3948
  if (result?.html != null && !result.boundary && !result.redirect) {
3491
3949
  loaderInstance.swap(boundary, result.html);
3950
+ await schedulerInstance.flush();
3492
3951
  }
3493
3952
  if (result?.redirect || options.history === false) {
3494
3953
  return;
@@ -3500,14 +3959,15 @@ const __routerModule = (() => {
3500
3959
  documentRef.defaultView?.history?.pushState?.({}, "", target.href);
3501
3960
  }
3502
3961
 
3503
- async function fetchRoute(url, { prefetch = false } = {}) {
3962
+ async function fetchRoute(url, { prefetch = false, signal } = {}) {
3504
3963
  if (typeof fetchImpl !== "function") {
3505
3964
  throw new Error("Router navigation requires a partial registry or fetch.");
3506
3965
  }
3507
3966
  const response = await fetchImpl(`${routeEndpoint}?to=${encodeURIComponent(String(url))}`, {
3508
3967
  headers: {
3509
3968
  accept: "application/json, text/html"
3510
- }
3969
+ },
3970
+ signal
3511
3971
  });
3512
3972
  if (!response.ok) {
3513
3973
  throw new Error(`Route "${url}" failed with ${response.status}.`);
@@ -3522,7 +3982,7 @@ const __routerModule = (() => {
3522
3982
  return { boundary, html: await response.text() };
3523
3983
  }
3524
3984
 
3525
- function contextFor(matched) {
3985
+ function contextFor(matched, navigation) {
3526
3986
  return {
3527
3987
  params: matched.params,
3528
3988
  route: matched.route,
@@ -3532,8 +3992,27 @@ const __routerModule = (() => {
3532
3992
  loader: loaderInstance,
3533
3993
  server,
3534
3994
  cache,
3535
- abort: undefined
3995
+ scheduler: schedulerInstance,
3996
+ abort: navigation?.abort
3997
+ };
3998
+ }
3999
+
4000
+ function beginNavigation(target, matched) {
4001
+ activeNavigation?.controller.abort(new Error(`Router navigation superseded by ${target.pathname}${target.search}.`));
4002
+ const controller = new AbortController();
4003
+ const navigation = {
4004
+ id: ++navigationVersion,
4005
+ controller,
4006
+ abort: controller.signal,
4007
+ target,
4008
+ matched
3536
4009
  };
4010
+ activeNavigation = navigation;
4011
+ return navigation;
4012
+ }
4013
+
4014
+ function isActiveNavigation(navigation) {
4015
+ return !destroyed && navigation && activeNavigation?.id === navigation.id && !navigation.abort.aborted;
3537
4016
  }
3538
4017
 
3539
4018
  function updateStateFromLocation() {
@@ -3570,7 +4049,14 @@ const __routerModule = (() => {
3570
4049
  }
3571
4050
 
3572
4051
  function currentUrl() {
3573
- return toUrl(documentRef.defaultView?.location?.href ?? "http://localhost/");
4052
+ return resolveUrl(documentRef.defaultView?.location?.href ?? "http://localhost/");
4053
+ }
4054
+
4055
+ function resolveUrl(url) {
4056
+ if (url instanceof URL) {
4057
+ return url;
4058
+ }
4059
+ return new URL(String(url), documentRef.defaultView?.location?.href ?? "http://localhost/");
3574
4060
  }
3575
4061
 
3576
4062
  function assertActive() {
@@ -3587,6 +4073,7 @@ const __routerModule = (() => {
3587
4073
  pattern,
3588
4074
  regex,
3589
4075
  keys,
4076
+ score: routeScore(pattern),
3590
4077
  definition: normalized
3591
4078
  };
3592
4079
  }
@@ -3655,6 +4142,28 @@ const __routerModule = (() => {
3655
4142
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3656
4143
  }
3657
4144
 
4145
+ function sortRoutes(routes) {
4146
+ routes.sort((left, right) => right.score - left.score || right.pattern.length - left.pattern.length);
4147
+ }
4148
+
4149
+ function routeScore(pattern) {
4150
+ if (pattern === "*") {
4151
+ return -1;
4152
+ }
4153
+ return pattern
4154
+ .split("/")
4155
+ .filter(Boolean)
4156
+ .reduce((score, segment) => {
4157
+ if (segment === "*") {
4158
+ return score;
4159
+ }
4160
+ if (segment.startsWith(":")) {
4161
+ return score + 2;
4162
+ }
4163
+ return score + 4;
4164
+ }, pattern === "/" ? 3 : 0);
4165
+ }
4166
+
3658
4167
  function assertPattern(pattern) {
3659
4168
  if (typeof pattern !== "string" || (pattern !== "*" && !pattern.startsWith("/"))) {
3660
4169
  throw new TypeError("Route pattern must be a path string or \"*\".");
@@ -3670,15 +4179,17 @@ const __appModule = (() => {
3670
4179
  const { Loader } = __loaderModule;
3671
4180
  const { createPartialRegistry } = __partialsModule;
3672
4181
  const { createRouteRegistry, createRouter } = __routerModule;
3673
- const { createServerRegistry } = __serverModule;
4182
+ const { createScheduler } = __schedulerModule;
4183
+ const { createServerNamespace } = __serverModule;
3674
4184
  const { createSignal, createSignalRegistry } = __signalsModule;
3675
4185
  const { createRegistryStore } = __registryStoreModule;
3676
4186
  const { attributeName, normalizeAttributeConfig } = __attributesModule;
3677
4187
  const registryTypes = new Set(["signal", "handler", "server", "partial", "route", "component"]);
3678
4188
 
3679
- function defineApp(initial) {
4189
+ function defineApp(initial, options = {}) {
3680
4190
  const registry = createRegistryStore(undefined, { target: "browser" });
3681
4191
  const runtimes = new Set();
4192
+ const createRuntime = options.createRuntime ?? createApp;
3682
4193
 
3683
4194
  const app = {
3684
4195
  registry,
@@ -3697,7 +4208,7 @@ const __appModule = (() => {
3697
4208
  },
3698
4209
 
3699
4210
  start(options = {}) {
3700
- const runtime = createApp(app, options).start();
4211
+ const runtime = createRuntime(app, options).start();
3701
4212
  app.runtime = runtime;
3702
4213
  return runtime;
3703
4214
  },
@@ -3722,13 +4233,18 @@ const __appModule = (() => {
3722
4233
  function createApp(appOrDefinition = Async, options = {}) {
3723
4234
  const app = isAppHub(appOrDefinition) ? appOrDefinition : defineApp(appOrDefinition ?? {});
3724
4235
  const target = options.target ?? "browser";
4236
+ const scheduler = options.scheduler ?? options.loader?.scheduler ?? createScheduler({
4237
+ strategy: target === "server" ? "manual" : "microtask"
4238
+ });
4239
+ const ownsScheduler = !options.scheduler && !options.loader?.scheduler;
3725
4240
  const attributes = normalizeAttributeConfig(options.attributes);
3726
4241
  const registry = options.registry ?? app.registry.view({ target });
3727
4242
  const signals = options.signals ?? createSignalRegistry(undefined, { registry, type: "signal" });
3728
4243
  const handlers = options.handlers ?? createHandlerRegistry(undefined, { registry, type: "handler" });
3729
4244
  const serverCache = createCacheRegistry(undefined, { registry, type: "cache.server" });
3730
4245
  const browserCache = createCacheRegistry(undefined, { registry, type: "cache.browser" });
3731
- const server = options.server ?? createServerRegistry(undefined, { registry, type: "server" });
4246
+ const serverFactory = options.serverFactory ?? createServerReferenceRegistry;
4247
+ const server = options.server ?? serverFactory(undefined, { registry, type: "server" });
3732
4248
  const partials = options.partials ?? createPartialRegistry(undefined, { registry, type: "partial" });
3733
4249
  const routes = options.routes ?? createRouteRegistry(undefined, { registry, type: "route" });
3734
4250
  const components = options.components ?? createComponentRegistry(undefined, { registry, type: "component" });
@@ -3738,7 +4254,7 @@ const __appModule = (() => {
3738
4254
  let started = false;
3739
4255
  let destroyed = false;
3740
4256
 
3741
- applySnapshot(signals, browserCache, options.snapshot);
4257
+ applySnapshot(signals, browserCache, options.snapshot ?? (target === "browser" ? readSnapshot(options.root, { attributes }) : undefined));
3742
4258
  attachServerCache(server, serverCache);
3743
4259
 
3744
4260
  const runtime = {
@@ -3756,6 +4272,7 @@ const __appModule = (() => {
3756
4272
  },
3757
4273
  loader,
3758
4274
  router,
4275
+ scheduler,
3759
4276
  attributes,
3760
4277
 
3761
4278
  start() {
@@ -3772,12 +4289,13 @@ const __appModule = (() => {
3772
4289
  handlers,
3773
4290
  server,
3774
4291
  cache: browserCache,
4292
+ scheduler,
3775
4293
  attributes
3776
4294
  });
3777
4295
  runtime.loader = loader;
3778
4296
 
3779
4297
  configureServerContext({ cache: browserCache });
3780
- signals._setContext?.({ server, loader, cache: browserCache });
4298
+ signals._setContext?.({ server, loader, cache: browserCache, scheduler });
3781
4299
 
3782
4300
  loader.start();
3783
4301
 
@@ -3793,6 +4311,7 @@ const __appModule = (() => {
3793
4311
  server,
3794
4312
  cache: browserCache,
3795
4313
  partials,
4314
+ scheduler,
3796
4315
  fetch: options.fetch,
3797
4316
  routeEndpoint: options.routeEndpoint,
3798
4317
  attributes
@@ -3804,7 +4323,7 @@ const __appModule = (() => {
3804
4323
  }
3805
4324
  } else {
3806
4325
  configureServerContext({ cache: serverCache });
3807
- signals._setContext?.({ server, cache: serverCache });
4326
+ signals._setContext?.({ server, cache: serverCache, scheduler });
3808
4327
  }
3809
4328
 
3810
4329
  return runtime;
@@ -3818,9 +4337,10 @@ const __appModule = (() => {
3818
4337
  async render(url) {
3819
4338
  assertActive();
3820
4339
  configureServerContext({ cache: serverCache });
3821
- signals._setContext?.({ server, cache: serverCache });
4340
+ signals._setContext?.({ server, cache: serverCache, scheduler });
3822
4341
  const matched = routes.match(url);
3823
4342
  if (!matched) {
4343
+ await scheduler.flush();
3824
4344
  return {
3825
4345
  html: renderDocument("", { status: 404, signals, browserCache, boundary: options.boundary ?? "route", attributes }),
3826
4346
  status: 404,
@@ -3840,8 +4360,8 @@ const __appModule = (() => {
3840
4360
  cache: serverCache,
3841
4361
  browserCache,
3842
4362
  partials,
3843
- request: options.request,
3844
- locals: options.locals
4363
+ scheduler,
4364
+ ...currentRequestContext()
3845
4365
  })
3846
4366
  : { html: "" };
3847
4367
 
@@ -3854,6 +4374,8 @@ const __appModule = (() => {
3854
4374
  browserCache.restore(result.cache.browser);
3855
4375
  }
3856
4376
 
4377
+ await scheduler.flush();
4378
+
3857
4379
  const status = result.status ?? 200;
3858
4380
  return {
3859
4381
  html: renderDocument(result.html, { status, signals, browserCache, boundary: result.boundary ?? options.boundary ?? "route", attributes }),
@@ -3872,6 +4394,9 @@ const __appModule = (() => {
3872
4394
  router?.destroy?.();
3873
4395
  loader?.destroy?.();
3874
4396
  signals.destroy?.();
4397
+ if (ownsScheduler) {
4398
+ scheduler.destroy();
4399
+ }
3875
4400
  },
3876
4401
 
3877
4402
  _applyUse(normalized) {
@@ -3892,11 +4417,23 @@ const __appModule = (() => {
3892
4417
  loader,
3893
4418
  router,
3894
4419
  cache,
3895
- request: options.request,
3896
- locals: options.locals
4420
+ scheduler,
4421
+ requestContext: options.requestContext,
4422
+ ...currentRequestContext()
3897
4423
  });
3898
4424
  }
3899
4425
 
4426
+ function currentRequestContext() {
4427
+ const context = readRequestContextLike(options.requestContext);
4428
+ return {
4429
+ requestContext: context,
4430
+ request: context.request ?? options.request,
4431
+ headers: context.headers ?? options.headers,
4432
+ cookies: context.cookies ?? options.cookies,
4433
+ locals: context.locals ?? options.locals
4434
+ };
4435
+ }
4436
+
3900
4437
  function assertActive() {
3901
4438
  if (destroyed) {
3902
4439
  throw new Error("Async app runtime has been destroyed.");
@@ -3906,6 +4443,38 @@ const __appModule = (() => {
3906
4443
 
3907
4444
  const Async = defineApp();
3908
4445
 
4446
+ function readSnapshot(root = globalThis.document, { attributes } = {}) {
4447
+ const attributeConfig = normalizeAttributeConfig(attributes);
4448
+ const snapshotAttr = attributeName(attributeConfig, "async", "snapshot");
4449
+ const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
4450
+ const rootNode = root ?? documentRef;
4451
+ if (!rootNode?.querySelectorAll && !documentRef?.querySelectorAll) {
4452
+ return {};
4453
+ }
4454
+
4455
+ for (const searchRoot of new Set([rootNode, documentRef])) {
4456
+ if (!searchRoot?.querySelectorAll) {
4457
+ continue;
4458
+ }
4459
+ for (const script of searchRoot.querySelectorAll("script[type='application/json'], script")) {
4460
+ if (!script.hasAttribute?.(snapshotAttr)) {
4461
+ continue;
4462
+ }
4463
+ const source = script.textContent?.trim() ?? "";
4464
+ if (!source) {
4465
+ return {};
4466
+ }
4467
+ try {
4468
+ return JSON.parse(source);
4469
+ } catch (cause) {
4470
+ throw new Error(`Could not parse Async snapshot: ${cause instanceof Error ? cause.message : String(cause)}`);
4471
+ }
4472
+ }
4473
+ }
4474
+
4475
+ return {};
4476
+ }
4477
+
3909
4478
  function applyUseToRuntime(runtime, normalized) {
3910
4479
  applyRegistryUse(runtime.signals, runtime.registry, normalized.signal);
3911
4480
  applyRegistryUse(runtime.handlers, runtime.registry, normalized.handler);
@@ -4018,6 +4587,77 @@ const __appModule = (() => {
4018
4587
  }
4019
4588
  }
4020
4589
 
4590
+ function createServerReferenceRegistry(initialMap = {}, options = {}) {
4591
+ const registry = options.registry ?? createRegistryStore();
4592
+ const type = options.type ?? "server";
4593
+ const defaults = {};
4594
+
4595
+ const reference = {
4596
+ registry,
4597
+
4598
+ register(id, value) {
4599
+ registry.register(type, id, value);
4600
+ return id;
4601
+ },
4602
+
4603
+ registerMany(map) {
4604
+ for (const [id, value] of Object.entries(map ?? {})) {
4605
+ reference.register(id, value);
4606
+ }
4607
+ return reference;
4608
+ },
4609
+
4610
+ unregister(id) {
4611
+ return registry.unregister(type, id);
4612
+ },
4613
+
4614
+ resolve() {
4615
+ return undefined;
4616
+ },
4617
+
4618
+ async run(id) {
4619
+ throw new Error(`Server command "${id}" cannot run without a server proxy or server registry.`);
4620
+ },
4621
+
4622
+ keys() {
4623
+ return registry.keys(type);
4624
+ },
4625
+
4626
+ entries() {
4627
+ return registry.entries(type);
4628
+ },
4629
+
4630
+ inspect() {
4631
+ return registry.entries(type);
4632
+ },
4633
+
4634
+ _setContext(context = {}) {
4635
+ Object.assign(defaults, context);
4636
+ return reference;
4637
+ },
4638
+
4639
+ _adoptMany() {
4640
+ return reference;
4641
+ }
4642
+ };
4643
+
4644
+ reference.registerMany(initialMap);
4645
+ return createServerNamespace((id, args, context) => reference.run(id, args, context), reference, () => defaults);
4646
+ }
4647
+
4648
+ function readRequestContextLike(store) {
4649
+ if (!store) {
4650
+ return {};
4651
+ }
4652
+ if (typeof store.get === "function") {
4653
+ return store.get() ?? {};
4654
+ }
4655
+ if (typeof store.getStore === "function") {
4656
+ return store.getStore() ?? {};
4657
+ }
4658
+ return {};
4659
+ }
4660
+
4021
4661
  function normalizeEntries(type, entries = {}) {
4022
4662
  if (type !== "signal") {
4023
4663
  return { ...(entries ?? {}) };
@@ -4066,7 +4706,7 @@ const __appModule = (() => {
4066
4706
  function escapeScriptJson(value) {
4067
4707
  return JSON.stringify(value).replaceAll("<", "\\u003c");
4068
4708
  }
4069
- return { defineApp, createApp, Async };
4709
+ return { defineApp, createApp, readSnapshot, Async };
4070
4710
  })();
4071
4711
 
4072
4712
  const __delayModule = (() => {
@@ -4107,6 +4747,7 @@ const { asyncSignal: asyncSignal } = __asyncSignalModule;
4107
4747
  const { Async: Async } = __appModule;
4108
4748
  const { createApp: createApp } = __appModule;
4109
4749
  const { defineApp: defineApp } = __appModule;
4750
+ const { readSnapshot: readSnapshot } = __appModule;
4110
4751
  const { attributeName: attributeName } = __attributesModule;
4111
4752
  const { defineAttributeConfig: defineAttributeConfig } = __attributesModule;
4112
4753
  const { createCacheRegistry: createCacheRegistry } = __cacheModule;
@@ -4125,12 +4766,15 @@ const { createRouteRegistry: createRouteRegistry } = __routerModule;
4125
4766
  const { createRouter: createRouter } = __routerModule;
4126
4767
  const { defineRoute: defineRoute } = __routerModule;
4127
4768
  const { route: route } = __routerModule;
4769
+ const { createScheduler: createScheduler } = __schedulerModule;
4770
+ const { applyServerResult: applyServerResult } = __serverModule;
4128
4771
  const { createServerProxy: createServerProxy } = __serverModule;
4129
- const { createServerRegistry: createServerRegistry } = __serverModule;
4772
+ const { resolveServerCommandArguments: resolveServerCommandArguments } = __serverModule;
4773
+ const { unwrapServerResult: unwrapServerResult } = __serverModule;
4130
4774
  const { computed: computed } = __signalsModule;
4131
4775
  const { createSignal: createSignal } = __signalsModule;
4132
4776
  const { createSignalRegistry: createSignalRegistry } = __signalsModule;
4133
4777
  const { effect: effect } = __signalsModule;
4134
4778
  const { signal: signal } = __signalsModule;
4135
4779
 
4136
- export { asyncSignal, Async, createApp, defineApp, 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 };
4780
+ export { 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 };