@async/framework 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,325 @@
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
+ isScopeDestroyed(scope) {
2609
+ return scope !== undefined && destroyedScopes.has(scope);
2610
+ },
2611
+
2612
+ inspect() {
2613
+ const counts = {};
2614
+ for (const [phase, queue] of queues) {
2615
+ counts[phase] = queue.filter((job) => !job.canceled).length;
2616
+ }
2617
+ return {
2618
+ strategy,
2619
+ phases: [...phases],
2620
+ pending: counts,
2621
+ scopesDestroyed: destroyedScopes.size,
2622
+ flushing,
2623
+ scheduled
2624
+ };
2625
+ },
2626
+
2627
+ destroy() {
2628
+ destroyed = true;
2629
+ for (const queue of queues.values()) {
2630
+ for (const job of queue) {
2631
+ job.cancel();
2632
+ }
2633
+ queue.length = 0;
2634
+ }
2635
+ keyedJobs.clear();
2636
+ destroyedScopes.clear();
2637
+ }
2638
+ };
2639
+
2640
+ return api;
2641
+
2642
+ function requestFlush() {
2643
+ if (strategy === "manual" || destroyed || flushing || batchDepth > 0 || scheduled) {
2644
+ return;
2645
+ }
2646
+ scheduled = true;
2647
+ scheduleMicrotask(() => {
2648
+ if (!destroyed) {
2649
+ void api.flush();
2650
+ }
2651
+ });
2652
+ }
2653
+
2654
+ async function flushPhase(phase, scope) {
2655
+ const queue = queues.get(phase);
2656
+ const remaining = [];
2657
+ const runnable = [];
2658
+
2659
+ for (const job of queue.splice(0)) {
2660
+ if (job.canceled) {
2661
+ continue;
2662
+ }
2663
+ if (scope !== undefined && job.scope !== scope) {
2664
+ remaining.push(job);
2665
+ continue;
2666
+ }
2667
+ runnable.push(job);
2668
+ }
2669
+
2670
+ queue.push(...remaining);
2671
+
2672
+ for (const job of runnable) {
2673
+ if (job.key) {
2674
+ keyedJobs.delete(job.key);
2675
+ }
2676
+ if (job.canceled || (job.scope !== undefined && destroyedScopes.has(job.scope))) {
2677
+ continue;
2678
+ }
2679
+ try {
2680
+ await job.fn();
2681
+ } catch (error) {
2682
+ if (onError) {
2683
+ onError(error, job);
2684
+ } else {
2685
+ throw error;
2686
+ }
2687
+ }
2688
+ }
2689
+ }
2690
+
2691
+ function hasJobs() {
2692
+ for (const queue of queues.values()) {
2693
+ if (queue.some((job) => !job.canceled)) {
2694
+ return true;
2695
+ }
2696
+ }
2697
+ return false;
2698
+ }
2699
+
2700
+ function hasJobsForScope(scope) {
2701
+ for (const queue of queues.values()) {
2702
+ if (queue.some((job) => !job.canceled && job.scope === scope)) {
2703
+ return true;
2704
+ }
2705
+ }
2706
+ return false;
2707
+ }
2708
+
2709
+ function assertActive() {
2710
+ if (destroyed) {
2711
+ throw new Error("Scheduler has been destroyed.");
2712
+ }
2713
+ }
2714
+
2715
+ function assertPhase(phase) {
2716
+ if (!queues.has(phase)) {
2717
+ throw new Error(`Unknown scheduler phase "${phase}".`);
2718
+ }
2719
+ }
2720
+
2721
+ function scopeKey(scope) {
2722
+ if (scope === undefined) {
2723
+ return "global";
2724
+ }
2725
+ if ((typeof scope === "object" && scope !== null) || typeof scope === "function") {
2726
+ if (!objectScopeIds.has(scope)) {
2727
+ objectScopeIds.set(scope, `scope:${++scopeCounter}`);
2728
+ }
2729
+ return objectScopeIds.get(scope);
2730
+ }
2731
+ return String(scope);
2732
+ }
2733
+ }
2734
+
2735
+ function scheduleMicrotask(fn) {
2736
+ if (typeof queueMicrotask === "function") {
2737
+ queueMicrotask(fn);
2738
+ return;
2739
+ }
2740
+ Promise.resolve().then(fn);
2741
+ }
2742
+
2743
+ function noop() {}
2744
+ return { createScheduler };
2745
+ })();
2746
+
2430
2747
  const __loaderModule = (() => {
2431
2748
  const { renderComponent } = __componentModule;
2432
2749
  const { createHandlerRegistry } = __handlersModule;
2750
+ const { createScheduler } = __schedulerModule;
2433
2751
  const { createSignalRegistry, isSignalRef } = __signalsModule;
2434
2752
  const { matchAttribute, normalizeAttributeConfig, readAttribute } = __attributesModule;
2435
2753
  const inlineBindingPrefix = "__async:inline:";
2436
2754
 
2437
- function Loader({ root, signals, handlers, server, router, cache, attributes } = {}) {
2755
+ function Loader({ root, signals, handlers, server, router, cache, attributes, scheduler } = {}) {
2438
2756
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
2439
2757
  const rootNode = root ?? documentRef;
2440
2758
  const signalRegistry = signals ?? createSignalRegistry();
2441
2759
  const handlerRegistry = handlers ?? createHandlerRegistry();
2760
+ const schedulerInstance = scheduler ?? createScheduler();
2761
+ const ownsScheduler = !scheduler;
2442
2762
  const attributeConfig = normalizeAttributeConfig(attributes);
2443
2763
  const cleanups = new Set();
2444
2764
  const eventBindings = new WeakMap();
@@ -2459,6 +2779,7 @@
2459
2779
  server,
2460
2780
  router,
2461
2781
  cache,
2782
+ scheduler: schedulerInstance,
2462
2783
  attributes: attributeConfig,
2463
2784
 
2464
2785
  start() {
@@ -2498,6 +2819,7 @@
2498
2819
  server: api.server,
2499
2820
  router: api.router,
2500
2821
  cache: api.cache,
2822
+ scheduler: schedulerInstance,
2501
2823
  attributes: attributeConfig
2502
2824
  });
2503
2825
  cleanupChildren(target);
@@ -2518,6 +2840,9 @@
2518
2840
  runCleanup(cleanup);
2519
2841
  }
2520
2842
  cleanups.clear();
2843
+ if (ownsScheduler) {
2844
+ schedulerInstance.destroy();
2845
+ }
2521
2846
  },
2522
2847
 
2523
2848
  _observeVisible(target, fn) {
@@ -2535,13 +2860,14 @@
2535
2860
  }
2536
2861
  };
2537
2862
 
2538
- signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
2863
+ signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache, scheduler: schedulerInstance });
2539
2864
  api.server?._setContext?.({
2540
2865
  signals: signalRegistry,
2541
2866
  handlers: handlerRegistry,
2542
2867
  loader: api,
2543
2868
  router: api.router,
2544
- cache: api.cache
2869
+ cache: api.cache,
2870
+ scheduler: schedulerInstance
2545
2871
  });
2546
2872
 
2547
2873
  function bindEventAttributes(scope) {
@@ -2573,18 +2899,19 @@
2573
2899
 
2574
2900
  const listener = async (event) => {
2575
2901
  try {
2576
- await handlerRegistry.run(ref, {
2902
+ await schedulerInstance.batch(() => handlerRegistry.run(ref, {
2577
2903
  signals: signalRegistry,
2578
2904
  handlers: handlerRegistry,
2579
2905
  loader: api,
2580
2906
  server: api.server,
2581
2907
  router: api.router,
2582
2908
  cache: api.cache,
2909
+ scheduler: schedulerInstance,
2583
2910
  event,
2584
2911
  element,
2585
2912
  el: element,
2586
2913
  root: rootNode
2587
- });
2914
+ }));
2588
2915
  } catch (error) {
2589
2916
  dispatchAsyncError(element, error);
2590
2917
  }
@@ -2700,7 +3027,12 @@
2700
3027
 
2701
3028
  const read = () => readBinding(path, options);
2702
3029
  apply(read());
2703
- addCleanup(subscribeBinding(path, () => apply(read())), element);
3030
+ addCleanup(subscribeBinding(path, () => {
3031
+ schedulerInstance.enqueue("binding", () => apply(read()), {
3032
+ scope: element,
3033
+ key
3034
+ });
3035
+ }), element);
2704
3036
  }
2705
3037
 
2706
3038
  function bindValueWriter(element, path) {
@@ -2761,7 +3093,12 @@
2761
3093
  const state = {
2762
3094
  id,
2763
3095
  templates,
2764
- cleanup: signalRegistry.subscribe(`${id}.$status`, () => renderBoundary(boundary))
3096
+ cleanup: signalRegistry.subscribe(`${id}.$status`, () => {
3097
+ schedulerInstance.enqueue("binding", () => renderBoundary(boundary), {
3098
+ scope: boundary,
3099
+ key: `boundary:${id}`
3100
+ });
3101
+ })
2765
3102
  };
2766
3103
  boundaryState.set(boundary, state);
2767
3104
  addCleanup(state.cleanup, boundary);
@@ -2801,7 +3138,7 @@
2801
3138
  }
2802
3139
  mountedElements.add(element);
2803
3140
  for (const ref of refs) {
2804
- runPseudo(element, ref);
3141
+ scheduleLifecycle(element, () => runPseudo(element, ref), `attach:${ref}`);
2805
3142
  }
2806
3143
  }
2807
3144
 
@@ -2814,7 +3151,7 @@
2814
3151
  continue;
2815
3152
  }
2816
3153
  visibleElements.add(element);
2817
- addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
3154
+ addCleanup(observeVisible(element, () => scheduleLifecycle(element, () => runPseudo(element, ref), `visible:${ref}`)), element);
2818
3155
  }
2819
3156
  }
2820
3157
 
@@ -2838,6 +3175,7 @@
2838
3175
  server: api.server,
2839
3176
  router: api.router,
2840
3177
  cache: api.cache,
3178
+ scheduler: schedulerInstance,
2841
3179
  element,
2842
3180
  el: element,
2843
3181
  root: rootNode
@@ -2856,10 +3194,13 @@
2856
3194
  const ownerWindow = target.ownerDocument?.defaultView ?? globalThis;
2857
3195
  const Observer = ownerWindow.IntersectionObserver ?? globalThis.IntersectionObserver;
2858
3196
  if (!Observer) {
2859
- queueMicrotask(() => {
3197
+ schedulerInstance.enqueue("lifecycle", () => {
2860
3198
  if (!destroyed) {
2861
3199
  fn(target);
2862
3200
  }
3201
+ }, {
3202
+ scope: target,
3203
+ key: "visible:fallback"
2863
3204
  });
2864
3205
  return () => {};
2865
3206
  }
@@ -2914,6 +3255,7 @@
2914
3255
  }
2915
3256
  for (const element of elementsIn(node)) {
2916
3257
  runScopedCleanups(element);
3258
+ schedulerInstance.markScopeDestroyed(element);
2917
3259
  }
2918
3260
  }
2919
3261
 
@@ -2937,6 +3279,13 @@
2937
3279
  }
2938
3280
  }
2939
3281
 
3282
+ function scheduleLifecycle(element, fn, key) {
3283
+ schedulerInstance.enqueue("lifecycle", fn, {
3284
+ scope: element,
3285
+ key
3286
+ });
3287
+ }
3288
+
2940
3289
  return api;
2941
3290
  }
2942
3291
 
@@ -3267,6 +3616,7 @@
3267
3616
  const __routerModule = (() => {
3268
3617
  const { Loader } = __loaderModule;
3269
3618
  const { createHandlerRegistry } = __handlersModule;
3619
+ const { createScheduler } = __schedulerModule;
3270
3620
  const { createSignalRegistry } = __signalsModule;
3271
3621
  const { applyServerResult } = __serverModule;
3272
3622
  const { createRegistryStore } = __registryStoreModule;
@@ -3387,12 +3737,15 @@
3387
3737
  partials,
3388
3738
  fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
3389
3739
  routeEndpoint = "/__async/route",
3390
- attributes
3740
+ attributes,
3741
+ scheduler
3391
3742
  } = {}) {
3392
3743
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
3393
3744
  const rootNode = root ?? documentRef;
3394
3745
  const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
3395
3746
  const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
3747
+ const schedulerInstance = scheduler ?? loader?.scheduler ?? createScheduler();
3748
+ const ownsScheduler = !scheduler && !loader?.scheduler;
3396
3749
  const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
3397
3750
  const loaderInstance =
3398
3751
  loader ??
@@ -3402,6 +3755,7 @@
3402
3755
  handlers: handlerRegistry,
3403
3756
  server,
3404
3757
  cache,
3758
+ scheduler: schedulerInstance,
3405
3759
  attributes: attributeConfig
3406
3760
  });
3407
3761
  const ownsLoader = !loader;
@@ -3421,12 +3775,13 @@
3421
3775
  server,
3422
3776
  cache,
3423
3777
  partials,
3778
+ scheduler: schedulerInstance,
3424
3779
  attributes: attributeConfig,
3425
3780
 
3426
3781
  start() {
3427
3782
  assertActive();
3428
3783
  loaderInstance.router = api;
3429
- signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache });
3784
+ signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache, scheduler: schedulerInstance });
3430
3785
  if (ownsLoader) {
3431
3786
  loaderInstance.start();
3432
3787
  }
@@ -3490,6 +3845,9 @@
3490
3845
  cleanup();
3491
3846
  }
3492
3847
  cleanups.clear();
3848
+ if (ownsScheduler) {
3849
+ schedulerInstance.destroy();
3850
+ }
3493
3851
  }
3494
3852
  };
3495
3853
 
@@ -3595,13 +3953,16 @@
3595
3953
  loader: loaderInstance,
3596
3954
  router: api,
3597
3955
  cache,
3956
+ scheduler: schedulerInstance,
3598
3957
  abort: navigation?.abort
3599
3958
  });
3959
+ await schedulerInstance.flush();
3600
3960
  if (!isActiveNavigation(navigation)) {
3601
3961
  return;
3602
3962
  }
3603
3963
  if (result?.html != null && !result.boundary && !result.redirect) {
3604
3964
  loaderInstance.swap(boundary, result.html);
3965
+ await schedulerInstance.flush();
3605
3966
  }
3606
3967
  if (result?.redirect || options.history === false) {
3607
3968
  return;
@@ -3646,6 +4007,7 @@
3646
4007
  loader: loaderInstance,
3647
4008
  server,
3648
4009
  cache,
4010
+ scheduler: schedulerInstance,
3649
4011
  abort: navigation?.abort
3650
4012
  };
3651
4013
  }
@@ -3832,15 +4194,17 @@
3832
4194
  const { Loader } = __loaderModule;
3833
4195
  const { createPartialRegistry } = __partialsModule;
3834
4196
  const { createRouteRegistry, createRouter } = __routerModule;
3835
- const { createServerRegistry } = __serverModule;
4197
+ const { createScheduler } = __schedulerModule;
4198
+ const { createServerNamespace } = __serverModule;
3836
4199
  const { createSignal, createSignalRegistry } = __signalsModule;
3837
4200
  const { createRegistryStore } = __registryStoreModule;
3838
4201
  const { attributeName, normalizeAttributeConfig } = __attributesModule;
3839
4202
  const registryTypes = new Set(["signal", "handler", "server", "partial", "route", "component"]);
3840
4203
 
3841
- function defineApp(initial) {
4204
+ function defineApp(initial, options = {}) {
3842
4205
  const registry = createRegistryStore(undefined, { target: "browser" });
3843
4206
  const runtimes = new Set();
4207
+ const createRuntime = options.createRuntime ?? createApp;
3844
4208
 
3845
4209
  const app = {
3846
4210
  registry,
@@ -3859,7 +4223,7 @@
3859
4223
  },
3860
4224
 
3861
4225
  start(options = {}) {
3862
- const runtime = createApp(app, options).start();
4226
+ const runtime = createRuntime(app, options).start();
3863
4227
  app.runtime = runtime;
3864
4228
  return runtime;
3865
4229
  },
@@ -3884,13 +4248,18 @@
3884
4248
  function createApp(appOrDefinition = Async, options = {}) {
3885
4249
  const app = isAppHub(appOrDefinition) ? appOrDefinition : defineApp(appOrDefinition ?? {});
3886
4250
  const target = options.target ?? "browser";
4251
+ const scheduler = options.scheduler ?? options.loader?.scheduler ?? createScheduler({
4252
+ strategy: target === "server" ? "manual" : "microtask"
4253
+ });
4254
+ const ownsScheduler = !options.scheduler && !options.loader?.scheduler;
3887
4255
  const attributes = normalizeAttributeConfig(options.attributes);
3888
4256
  const registry = options.registry ?? app.registry.view({ target });
3889
4257
  const signals = options.signals ?? createSignalRegistry(undefined, { registry, type: "signal" });
3890
4258
  const handlers = options.handlers ?? createHandlerRegistry(undefined, { registry, type: "handler" });
3891
4259
  const serverCache = createCacheRegistry(undefined, { registry, type: "cache.server" });
3892
4260
  const browserCache = createCacheRegistry(undefined, { registry, type: "cache.browser" });
3893
- const server = options.server ?? createServerRegistry(undefined, { registry, type: "server" });
4261
+ const serverFactory = options.serverFactory ?? createServerReferenceRegistry;
4262
+ const server = options.server ?? serverFactory(undefined, { registry, type: "server" });
3894
4263
  const partials = options.partials ?? createPartialRegistry(undefined, { registry, type: "partial" });
3895
4264
  const routes = options.routes ?? createRouteRegistry(undefined, { registry, type: "route" });
3896
4265
  const components = options.components ?? createComponentRegistry(undefined, { registry, type: "component" });
@@ -3918,6 +4287,7 @@
3918
4287
  },
3919
4288
  loader,
3920
4289
  router,
4290
+ scheduler,
3921
4291
  attributes,
3922
4292
 
3923
4293
  start() {
@@ -3934,12 +4304,13 @@
3934
4304
  handlers,
3935
4305
  server,
3936
4306
  cache: browserCache,
4307
+ scheduler,
3937
4308
  attributes
3938
4309
  });
3939
4310
  runtime.loader = loader;
3940
4311
 
3941
4312
  configureServerContext({ cache: browserCache });
3942
- signals._setContext?.({ server, loader, cache: browserCache });
4313
+ signals._setContext?.({ server, loader, cache: browserCache, scheduler });
3943
4314
 
3944
4315
  loader.start();
3945
4316
 
@@ -3955,6 +4326,7 @@
3955
4326
  server,
3956
4327
  cache: browserCache,
3957
4328
  partials,
4329
+ scheduler,
3958
4330
  fetch: options.fetch,
3959
4331
  routeEndpoint: options.routeEndpoint,
3960
4332
  attributes
@@ -3966,7 +4338,7 @@
3966
4338
  }
3967
4339
  } else {
3968
4340
  configureServerContext({ cache: serverCache });
3969
- signals._setContext?.({ server, cache: serverCache });
4341
+ signals._setContext?.({ server, cache: serverCache, scheduler });
3970
4342
  }
3971
4343
 
3972
4344
  return runtime;
@@ -3980,9 +4352,10 @@
3980
4352
  async render(url) {
3981
4353
  assertActive();
3982
4354
  configureServerContext({ cache: serverCache });
3983
- signals._setContext?.({ server, cache: serverCache });
4355
+ signals._setContext?.({ server, cache: serverCache, scheduler });
3984
4356
  const matched = routes.match(url);
3985
4357
  if (!matched) {
4358
+ await scheduler.flush();
3986
4359
  return {
3987
4360
  html: renderDocument("", { status: 404, signals, browserCache, boundary: options.boundary ?? "route", attributes }),
3988
4361
  status: 404,
@@ -4002,8 +4375,8 @@
4002
4375
  cache: serverCache,
4003
4376
  browserCache,
4004
4377
  partials,
4005
- request: options.request,
4006
- locals: options.locals
4378
+ scheduler,
4379
+ ...currentRequestContext()
4007
4380
  })
4008
4381
  : { html: "" };
4009
4382
 
@@ -4016,6 +4389,8 @@
4016
4389
  browserCache.restore(result.cache.browser);
4017
4390
  }
4018
4391
 
4392
+ await scheduler.flush();
4393
+
4019
4394
  const status = result.status ?? 200;
4020
4395
  return {
4021
4396
  html: renderDocument(result.html, { status, signals, browserCache, boundary: result.boundary ?? options.boundary ?? "route", attributes }),
@@ -4034,6 +4409,9 @@
4034
4409
  router?.destroy?.();
4035
4410
  loader?.destroy?.();
4036
4411
  signals.destroy?.();
4412
+ if (ownsScheduler) {
4413
+ scheduler.destroy();
4414
+ }
4037
4415
  },
4038
4416
 
4039
4417
  _applyUse(normalized) {
@@ -4054,11 +4432,23 @@
4054
4432
  loader,
4055
4433
  router,
4056
4434
  cache,
4057
- request: options.request,
4058
- locals: options.locals
4435
+ scheduler,
4436
+ requestContext: options.requestContext,
4437
+ ...currentRequestContext()
4059
4438
  });
4060
4439
  }
4061
4440
 
4441
+ function currentRequestContext() {
4442
+ const context = readRequestContextLike(options.requestContext);
4443
+ return {
4444
+ requestContext: context,
4445
+ request: context.request ?? options.request,
4446
+ headers: context.headers ?? options.headers,
4447
+ cookies: context.cookies ?? options.cookies,
4448
+ locals: context.locals ?? options.locals
4449
+ };
4450
+ }
4451
+
4062
4452
  function assertActive() {
4063
4453
  if (destroyed) {
4064
4454
  throw new Error("Async app runtime has been destroyed.");
@@ -4212,6 +4602,77 @@
4212
4602
  }
4213
4603
  }
4214
4604
 
4605
+ function createServerReferenceRegistry(initialMap = {}, options = {}) {
4606
+ const registry = options.registry ?? createRegistryStore();
4607
+ const type = options.type ?? "server";
4608
+ const defaults = {};
4609
+
4610
+ const reference = {
4611
+ registry,
4612
+
4613
+ register(id, value) {
4614
+ registry.register(type, id, value);
4615
+ return id;
4616
+ },
4617
+
4618
+ registerMany(map) {
4619
+ for (const [id, value] of Object.entries(map ?? {})) {
4620
+ reference.register(id, value);
4621
+ }
4622
+ return reference;
4623
+ },
4624
+
4625
+ unregister(id) {
4626
+ return registry.unregister(type, id);
4627
+ },
4628
+
4629
+ resolve() {
4630
+ return undefined;
4631
+ },
4632
+
4633
+ async run(id) {
4634
+ throw new Error(`Server command "${id}" cannot run without a server proxy or server registry.`);
4635
+ },
4636
+
4637
+ keys() {
4638
+ return registry.keys(type);
4639
+ },
4640
+
4641
+ entries() {
4642
+ return registry.entries(type);
4643
+ },
4644
+
4645
+ inspect() {
4646
+ return registry.entries(type);
4647
+ },
4648
+
4649
+ _setContext(context = {}) {
4650
+ Object.assign(defaults, context);
4651
+ return reference;
4652
+ },
4653
+
4654
+ _adoptMany() {
4655
+ return reference;
4656
+ }
4657
+ };
4658
+
4659
+ reference.registerMany(initialMap);
4660
+ return createServerNamespace((id, args, context) => reference.run(id, args, context), reference, () => defaults);
4661
+ }
4662
+
4663
+ function readRequestContextLike(store) {
4664
+ if (!store) {
4665
+ return {};
4666
+ }
4667
+ if (typeof store.get === "function") {
4668
+ return store.get() ?? {};
4669
+ }
4670
+ if (typeof store.getStore === "function") {
4671
+ return store.getStore() ?? {};
4672
+ }
4673
+ return {};
4674
+ }
4675
+
4215
4676
  function normalizeEntries(type, entries = {}) {
4216
4677
  if (type !== "signal") {
4217
4678
  return { ...(entries ?? {}) };
@@ -4263,6 +4724,312 @@
4263
4724
  return { defineApp, createApp, readSnapshot, Async };
4264
4725
  })();
4265
4726
 
4727
+ const __boundaryReceiverModule = (() => {
4728
+ const defaultRecentLimit = 50;
4729
+
4730
+ function createBoundaryReceiver(options = {}) {
4731
+ const loader = options.loader;
4732
+ const signals = options.signals ?? loader?.signals;
4733
+ const cache = options.cache ?? loader?.cache;
4734
+ const scheduler = options.scheduler ?? loader?.scheduler;
4735
+ const router = options.router ?? loader?.router;
4736
+ const recentLimit = options.recentLimit ?? defaultRecentLimit;
4737
+ const throwOnError = options.throwOnError === true;
4738
+ const onApply = typeof options.onApply === "function" ? options.onApply : undefined;
4739
+ const onIgnore = typeof options.onIgnore === "function" ? options.onIgnore : undefined;
4740
+ const onError = typeof options.onError === "function" ? options.onError : undefined;
4741
+ const isScopeDestroyed = typeof options.isScopeDestroyed === "function"
4742
+ ? options.isScopeDestroyed
4743
+ : (scope) => scheduler?.isScopeDestroyed?.(scope) ?? scheduler?.inspectDestroyed?.(scope) ?? false;
4744
+
4745
+ if (!loader || typeof loader.swap !== "function") {
4746
+ throw new TypeError("createBoundaryReceiver(...) requires a loader with swap(boundary, html).");
4747
+ }
4748
+ if (!Number.isInteger(recentLimit) || recentLimit < 0) {
4749
+ throw new TypeError("createBoundaryReceiver(...) recentLimit must be a non-negative integer.");
4750
+ }
4751
+
4752
+ const boundaries = new Map();
4753
+ const recent = [];
4754
+ let destroyed = false;
4755
+
4756
+ const receiver = {
4757
+ async apply(patch) {
4758
+ if (destroyed) {
4759
+ throw new Error("Boundary receiver has been destroyed.");
4760
+ }
4761
+
4762
+ const normalized = validatePatch(patch);
4763
+ const record = boundaryRecord(normalized.boundary);
4764
+ if (normalized.seq <= record.lastSeq) {
4765
+ const result = {
4766
+ status: "ignored-stale",
4767
+ boundary: normalized.boundary,
4768
+ seq: normalized.seq,
4769
+ lastSeq: record.lastSeq
4770
+ };
4771
+ record.ignored += 1;
4772
+ record.lastStatus = result.status;
4773
+ remember(result);
4774
+ onIgnore?.(result, patch);
4775
+ return result;
4776
+ }
4777
+
4778
+ if (normalized.parentScope !== undefined && isScopeDestroyed(normalized.parentScope)) {
4779
+ const result = {
4780
+ status: "ignored-destroyed",
4781
+ boundary: normalized.boundary,
4782
+ seq: normalized.seq,
4783
+ parentScope: normalized.parentScope
4784
+ };
4785
+ record.ignored += 1;
4786
+ record.lastStatus = result.status;
4787
+ remember(result);
4788
+ onIgnore?.(result, patch);
4789
+ return result;
4790
+ }
4791
+
4792
+ record.lastSeq = normalized.seq;
4793
+
4794
+ if (Object.hasOwn(normalized, "error")) {
4795
+ const error = toStableError(normalized.error);
4796
+ const result = {
4797
+ status: "errored",
4798
+ boundary: normalized.boundary,
4799
+ seq: normalized.seq,
4800
+ error
4801
+ };
4802
+ record.errored += 1;
4803
+ record.lastStatus = result.status;
4804
+ remember(result);
4805
+ onError?.(error, result, patch);
4806
+ if (throwOnError) {
4807
+ throw error;
4808
+ }
4809
+ return result;
4810
+ }
4811
+
4812
+ if (normalized.signals) {
4813
+ if (!signals || typeof signals.set !== "function") {
4814
+ throw new Error("Boundary patch includes signals, but no signal registry is available.");
4815
+ }
4816
+ for (const [path, value] of Object.entries(normalized.signals)) {
4817
+ signals.set(path, value);
4818
+ }
4819
+ }
4820
+
4821
+ if (normalized.cache?.browser) {
4822
+ if (!cache || typeof cache.restore !== "function") {
4823
+ throw new Error("Boundary patch includes browser cache, but no cache registry is available.");
4824
+ }
4825
+ cache.restore(normalized.cache.browser);
4826
+ }
4827
+
4828
+ if (normalized.html != null) {
4829
+ loader.swap(normalized.boundary, normalized.html);
4830
+ }
4831
+
4832
+ await flushScheduler(scheduler, normalized.scope);
4833
+
4834
+ if (normalized.redirect) {
4835
+ await followRedirect(normalized.redirect, router, loader);
4836
+ const result = {
4837
+ status: "redirected",
4838
+ boundary: normalized.boundary,
4839
+ seq: normalized.seq,
4840
+ redirect: normalized.redirect
4841
+ };
4842
+ record.applied += 1;
4843
+ record.lastStatus = result.status;
4844
+ remember(result);
4845
+ onApply?.(result, patch);
4846
+ return result;
4847
+ }
4848
+
4849
+ const result = {
4850
+ status: "applied",
4851
+ boundary: normalized.boundary,
4852
+ seq: normalized.seq
4853
+ };
4854
+ record.applied += 1;
4855
+ record.lastStatus = result.status;
4856
+ remember(result);
4857
+ onApply?.(result, patch);
4858
+ return result;
4859
+ },
4860
+
4861
+ inspect() {
4862
+ const snapshot = {};
4863
+ for (const [boundary, record] of boundaries) {
4864
+ snapshot[boundary] = {
4865
+ lastSeq: record.lastSeq,
4866
+ applied: record.applied,
4867
+ ignored: record.ignored,
4868
+ lastStatus: record.lastStatus
4869
+ };
4870
+ if (record.errored > 0) {
4871
+ snapshot[boundary].errored = record.errored;
4872
+ }
4873
+ }
4874
+ return {
4875
+ destroyed,
4876
+ boundaries: snapshot,
4877
+ recent: recent.map((entry) => ({ ...entry }))
4878
+ };
4879
+ },
4880
+
4881
+ reset(boundary) {
4882
+ if (boundary === undefined) {
4883
+ boundaries.clear();
4884
+ recent.length = 0;
4885
+ return receiver;
4886
+ }
4887
+ assertBoundary(boundary);
4888
+ boundaries.delete(boundary);
4889
+ for (let index = recent.length - 1; index >= 0; index -= 1) {
4890
+ if (recent[index].boundary === boundary) {
4891
+ recent.splice(index, 1);
4892
+ }
4893
+ }
4894
+ return receiver;
4895
+ },
4896
+
4897
+ destroy() {
4898
+ destroyed = true;
4899
+ boundaries.clear();
4900
+ recent.length = 0;
4901
+ }
4902
+ };
4903
+
4904
+ return receiver;
4905
+
4906
+ function boundaryRecord(boundary) {
4907
+ if (!boundaries.has(boundary)) {
4908
+ boundaries.set(boundary, {
4909
+ lastSeq: -Infinity,
4910
+ applied: 0,
4911
+ ignored: 0,
4912
+ errored: 0,
4913
+ lastStatus: undefined
4914
+ });
4915
+ }
4916
+ return boundaries.get(boundary);
4917
+ }
4918
+
4919
+ function remember(result) {
4920
+ if (recentLimit === 0) {
4921
+ return;
4922
+ }
4923
+ recent.push(toRecentEntry(result));
4924
+ while (recent.length > recentLimit) {
4925
+ recent.shift();
4926
+ }
4927
+ }
4928
+ }
4929
+
4930
+ function validatePatch(patch) {
4931
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
4932
+ throw new TypeError("receiver.apply(patch) requires a boundary patch object.");
4933
+ }
4934
+
4935
+ assertBoundary(patch.boundary);
4936
+ if (typeof patch.seq !== "number" || !Number.isFinite(patch.seq)) {
4937
+ throw new TypeError("Boundary patch seq must be a finite number.");
4938
+ }
4939
+
4940
+ if (patch.signals !== undefined && !isPlainObject(patch.signals)) {
4941
+ throw new TypeError("Boundary patch signals must be an object.");
4942
+ }
4943
+ if (patch.cache !== undefined && !isPlainObject(patch.cache)) {
4944
+ throw new TypeError("Boundary patch cache must be an object.");
4945
+ }
4946
+ if (patch.cache?.browser !== undefined && !isPlainObject(patch.cache.browser)) {
4947
+ throw new TypeError("Boundary patch cache.browser must be an object.");
4948
+ }
4949
+ if (patch.redirect !== undefined && (typeof patch.redirect !== "string" || patch.redirect.length === 0)) {
4950
+ throw new TypeError("Boundary patch redirect must be a non-empty string.");
4951
+ }
4952
+ if (patch.parentScope !== undefined && typeof patch.parentScope !== "string") {
4953
+ throw new TypeError("Boundary patch parentScope must be a string.");
4954
+ }
4955
+ if (patch.scope !== undefined && typeof patch.scope !== "string") {
4956
+ throw new TypeError("Boundary patch scope must be a string.");
4957
+ }
4958
+
4959
+ const hasHtml = Object.hasOwn(patch, "html") && patch.html != null;
4960
+ const hasSignals = patch.signals && Object.keys(patch.signals).length > 0;
4961
+ const hasBrowserCache = patch.cache?.browser && Object.keys(patch.cache.browser).length > 0;
4962
+ const hasRedirect = Boolean(patch.redirect);
4963
+ const hasError = Object.hasOwn(patch, "error");
4964
+ if (!hasHtml && !hasSignals && !hasBrowserCache && !hasRedirect && !hasError) {
4965
+ throw new TypeError("Boundary patch must include html, signals, cache.browser, redirect, or error.");
4966
+ }
4967
+
4968
+ return patch;
4969
+ }
4970
+
4971
+ function assertBoundary(boundary) {
4972
+ if (typeof boundary !== "string" || boundary.length === 0) {
4973
+ throw new TypeError("Boundary patch boundary must be a non-empty string.");
4974
+ }
4975
+ }
4976
+
4977
+ async function flushScheduler(scheduler, scope) {
4978
+ if (!scheduler) {
4979
+ return;
4980
+ }
4981
+ if (scope !== undefined && typeof scheduler.flushScope === "function") {
4982
+ await scheduler.flushScope(scope);
4983
+ return;
4984
+ }
4985
+ if (typeof scheduler.flush === "function") {
4986
+ await scheduler.flush();
4987
+ }
4988
+ }
4989
+
4990
+ async function followRedirect(redirect, router, loader) {
4991
+ if (router && typeof router.navigate === "function") {
4992
+ await router.navigate(redirect);
4993
+ return;
4994
+ }
4995
+ const location = loader?.root?.ownerDocument?.defaultView?.location ?? globalThis.location;
4996
+ location?.assign?.(redirect);
4997
+ }
4998
+
4999
+ function toStableError(value) {
5000
+ if (value instanceof Error) {
5001
+ return value;
5002
+ }
5003
+ if (value && typeof value === "object" && typeof value.message === "string") {
5004
+ return Object.assign(new Error(value.message), value);
5005
+ }
5006
+ return new Error(String(value));
5007
+ }
5008
+
5009
+ function toRecentEntry(result) {
5010
+ const entry = {
5011
+ boundary: result.boundary,
5012
+ seq: result.seq,
5013
+ status: result.status
5014
+ };
5015
+ if (result.status === "ignored-stale") {
5016
+ entry.lastSeq = result.lastSeq;
5017
+ }
5018
+ if (result.status === "ignored-destroyed" && result.parentScope !== undefined) {
5019
+ entry.parentScope = result.parentScope;
5020
+ }
5021
+ if (result.status === "redirected") {
5022
+ entry.redirect = result.redirect;
5023
+ }
5024
+ return entry;
5025
+ }
5026
+
5027
+ function isPlainObject(value) {
5028
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
5029
+ }
5030
+ return { createBoundaryReceiver };
5031
+ })();
5032
+
4266
5033
  const __delayModule = (() => {
4267
5034
  function delay(ms, signal) {
4268
5035
  if (signal?.aborted) {
@@ -4304,6 +5071,7 @@
4304
5071
  const { readSnapshot: readSnapshot } = __appModule;
4305
5072
  const { attributeName: attributeName } = __attributesModule;
4306
5073
  const { defineAttributeConfig: defineAttributeConfig } = __attributesModule;
5074
+ const { createBoundaryReceiver: createBoundaryReceiver } = __boundaryReceiverModule;
4307
5075
  const { createCacheRegistry: createCacheRegistry } = __cacheModule;
4308
5076
  const { defineCache: defineCache } = __cacheModule;
4309
5077
  const { component: component } = __componentModule;
@@ -4320,14 +5088,17 @@
4320
5088
  const { createRouter: createRouter } = __routerModule;
4321
5089
  const { defineRoute: defineRoute } = __routerModule;
4322
5090
  const { route: route } = __routerModule;
5091
+ const { createScheduler: createScheduler } = __schedulerModule;
5092
+ const { applyServerResult: applyServerResult } = __serverModule;
4323
5093
  const { createServerProxy: createServerProxy } = __serverModule;
4324
- const { createServerRegistry: createServerRegistry } = __serverModule;
5094
+ const { resolveServerCommandArguments: resolveServerCommandArguments } = __serverModule;
5095
+ const { unwrapServerResult: unwrapServerResult } = __serverModule;
4325
5096
  const { computed: computed } = __signalsModule;
4326
5097
  const { createSignal: createSignal } = __signalsModule;
4327
5098
  const { createSignalRegistry: createSignalRegistry } = __signalsModule;
4328
5099
  const { effect: effect } = __signalsModule;
4329
5100
  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 };
5101
+ const api = { asyncSignal, Async, createApp, defineApp, readSnapshot, attributeName, defineAttributeConfig, createBoundaryReceiver, 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
5102
  assertNoUmdNamespaceConflicts(api, Async);
4332
5103
  Object.assign(Async, api);
4333
5104
  Async.Async = Async;