@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.
@@ -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);
@@ -865,7 +893,8 @@ const __signalsModule = (() => {
865
893
  server: registry._context?.().server,
866
894
  router: registry._context?.().router,
867
895
  loader: registry._context?.().loader,
868
- cache: registry._context?.().cache
896
+ cache: registry._context?.().cache,
897
+ scheduler: registry._context?.().scheduler
869
898
  }));
870
899
  });
871
900
  }
@@ -893,6 +922,8 @@ const __signalsModule = (() => {
893
922
  const registryCleanups = new Map();
894
923
  const runtimeContext = {};
895
924
  const boundEntries = new Set();
925
+ let subscriptionCounter = 0;
926
+ let effectCounter = 0;
896
927
 
897
928
  const registry = attachRegistryInspection({
898
929
  register(id, signalLike) {
@@ -968,17 +999,21 @@ const __signalsModule = (() => {
968
999
  return createRef(registry, id);
969
1000
  },
970
1001
 
971
- subscribe(path, fn) {
1002
+ subscribe(path, fn, options = {}) {
972
1003
  if (typeof fn !== "function") {
973
1004
  throw new TypeError("subscribe(path, fn) requires a function.");
974
1005
  }
975
1006
  const parsed = parsePath(path, entries);
976
1007
  const entry = requireEntry(entries, parsed.id);
1008
+ const subscriptionId = ++subscriptionCounter;
977
1009
  return entry.subscribe(() => {
978
- fn(registry.get(parsed.path), {
1010
+ scheduleCallback(() => fn(registry.get(parsed.path), {
979
1011
  id: parsed.id,
980
1012
  path: parsed.path,
981
1013
  signal: entry
1014
+ }), {
1015
+ ...options,
1016
+ key: options.key ?? `signal:${parsed.path}:${subscriptionId}`
982
1017
  });
983
1018
  });
984
1019
  },
@@ -996,10 +1031,12 @@ const __signalsModule = (() => {
996
1031
  return registry.ref(id);
997
1032
  },
998
1033
 
999
- effect(fn) {
1034
+ effect(fn, options = {}) {
1000
1035
  let cleanup;
1001
1036
  let dependencyCleanups = [];
1002
1037
  let stopped = false;
1038
+ const scheduler = options.scheduler;
1039
+ const effectId = ++effectCounter;
1003
1040
 
1004
1041
  const run = () => {
1005
1042
  if (stopped) {
@@ -1018,10 +1055,22 @@ const __signalsModule = (() => {
1018
1055
  server: runtimeContext.server,
1019
1056
  router: runtimeContext.router,
1020
1057
  loader: runtimeContext.loader,
1021
- cache: runtimeContext.cache
1058
+ cache: runtimeContext.cache,
1059
+ scheduler: runtimeContext.scheduler
1022
1060
  }));
1023
1061
  cleanup = outcome.value;
1024
- 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
+ });
1025
1074
  };
1026
1075
 
1027
1076
  run();
@@ -1098,6 +1147,17 @@ const __signalsModule = (() => {
1098
1147
  registryCleanups.set(id, cleanup);
1099
1148
  }
1100
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
+ }
1101
1161
  }
1102
1162
 
1103
1163
  function normalizeSignal(signalLike) {
@@ -1564,10 +1624,15 @@ const __componentModule = (() => {
1564
1624
  html,
1565
1625
  attach(target) {
1566
1626
  for (const hook of attachHooks) {
1567
- const cleanup = hook(target);
1568
- if (typeof cleanup === "function") {
1569
- cleanups.push(cleanup);
1570
- }
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);
1571
1636
  }
1572
1637
  },
1573
1638
  mount(target) {
@@ -1575,7 +1640,17 @@ const __componentModule = (() => {
1575
1640
  },
1576
1641
  visible(target, observeVisible) {
1577
1642
  for (const hook of visibleHooks) {
1578
- 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
+ });
1579
1654
  if (typeof cleanup === "function") {
1580
1655
  cleanups.push(cleanup);
1581
1656
  }
@@ -1585,6 +1660,7 @@ const __componentModule = (() => {
1585
1660
  while (destroyHooks.length > 0) {
1586
1661
  destroyHooks.pop()?.();
1587
1662
  }
1663
+ runtime.scheduler?.markScopeDestroyed(scope);
1588
1664
  while (cleanups.length > 0) {
1589
1665
  cleanups.pop()?.();
1590
1666
  }
@@ -1593,10 +1669,24 @@ const __componentModule = (() => {
1593
1669
  }
1594
1670
  }
1595
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
+ }
1596
1686
  }
1597
1687
 
1598
1688
  function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks, renderScopedTemplate }) {
1599
- const { signals, handlers, loader, server, router, cache } = runtime;
1689
+ const { signals, handlers, loader, server, router, cache, scheduler } = runtime;
1600
1690
  const generatedHandlers = new WeakMap();
1601
1691
  let generatedHandlerCounter = 0;
1602
1692
  let generatedSignalCounter = 0;
@@ -1608,6 +1698,7 @@ const __componentModule = (() => {
1608
1698
  server,
1609
1699
  router,
1610
1700
  cache,
1701
+ scheduler,
1611
1702
 
1612
1703
  signal(name, initial) {
1613
1704
  if (arguments.length === 1) {
@@ -1652,7 +1743,11 @@ const __componentModule = (() => {
1652
1743
  },
1653
1744
 
1654
1745
  effect(fn) {
1655
- const cleanup = signals.effect(() => fn.call(context));
1746
+ const cleanup = signals.effect(() => fn.call(context), {
1747
+ scheduler,
1748
+ phase: "effect",
1749
+ scope
1750
+ });
1656
1751
  cleanups.push(cleanup);
1657
1752
  return cleanup;
1658
1753
  },
@@ -1774,93 +1869,10 @@ const __componentModule = (() => {
1774
1869
  })();
1775
1870
 
1776
1871
  const __serverModule = (() => {
1777
- const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
1778
1872
  const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
1779
1873
  const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
1780
1874
  const appliedServerValues = new WeakSet();
1781
1875
 
1782
- function createServerRegistry(initialMap = {}, options = {}) {
1783
- const registryStore = options.registry ?? createRegistryStore();
1784
- const type = options.type ?? "server";
1785
- const entries = registryStore._map(type);
1786
- const defaults = {};
1787
-
1788
- const registry = attachRegistryInspection({
1789
- register(id, fn) {
1790
- assertServerId(id);
1791
- if (typeof fn !== "function") {
1792
- throw new TypeError(`Server function "${id}" must be a function.`);
1793
- }
1794
- if (entries.has(id)) {
1795
- throw new Error(`Server function "${id}" is already registered.`);
1796
- }
1797
- entries.set(id, fn);
1798
- return id;
1799
- },
1800
-
1801
- registerMany(map) {
1802
- for (const [id, fn] of Object.entries(map ?? {})) {
1803
- registry.register(id, fn);
1804
- }
1805
- return registry;
1806
- },
1807
-
1808
- unregister(id) {
1809
- assertServerId(id);
1810
- return entries.delete(id);
1811
- },
1812
-
1813
- resolve(id) {
1814
- assertServerId(id);
1815
- return entries.get(id);
1816
- },
1817
-
1818
- async run(id, args = [], context = {}) {
1819
- assertServerId(id);
1820
- const fn = registry.resolve(id);
1821
- if (!fn) {
1822
- throw new Error(`Server function "${id}" is not registered.`);
1823
- }
1824
-
1825
- let runContext;
1826
- const server = createServerNamespace((childId, childArgs, childContext = {}) => {
1827
- return registry.run(childId, childArgs, { ...runContext, ...childContext });
1828
- }, {}, () => runContext);
1829
-
1830
- const mergedContext = {
1831
- ...defaults,
1832
- ...context,
1833
- cache: defaults.cache ?? context.cache
1834
- };
1835
-
1836
- runContext = {
1837
- ...mergedContext,
1838
- id,
1839
- args,
1840
- input: mergedContext.input,
1841
- signals: createSignalReader(mergedContext.signals),
1842
- abort: mergedContext.abort,
1843
- cache: mergedContext.cache,
1844
- server
1845
- };
1846
-
1847
- return fn.call(runContext, ...args);
1848
- },
1849
-
1850
- _setContext(context = {}) {
1851
- Object.assign(defaults, context);
1852
- return registry;
1853
- },
1854
-
1855
- _adoptMany() {
1856
- return registry;
1857
- }
1858
- }, registryStore, type);
1859
-
1860
- registry.registerMany(initialMap);
1861
- return createServerNamespace((id, args, context) => registry.run(id, args, context), registry, () => defaults);
1862
- }
1863
-
1864
1876
  function createServerProxy({
1865
1877
  endpoint = "/__async/server",
1866
1878
  fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
@@ -1868,13 +1880,14 @@ const __serverModule = (() => {
1868
1880
  loader,
1869
1881
  router,
1870
1882
  cache,
1883
+ scheduler,
1871
1884
  headers = {}
1872
1885
  } = {}) {
1873
1886
  if (typeof fetchImpl !== "function") {
1874
1887
  throw new TypeError("createServerProxy(...) requires fetch to be available.");
1875
1888
  }
1876
1889
 
1877
- const defaults = { signals, loader, router, cache };
1890
+ const defaults = { signals, loader, router, cache, scheduler };
1878
1891
 
1879
1892
  async function run(id, args = [], context = {}) {
1880
1893
  assertServerId(id);
@@ -2213,7 +2226,7 @@ const __serverModule = (() => {
2213
2226
  throw new TypeError("Server function id must be a non-empty string.");
2214
2227
  }
2215
2228
  }
2216
- return { createServerRegistry, createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput };
2229
+ return { createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
2217
2230
  })();
2218
2231
 
2219
2232
  const __handlersModule = (() => {
@@ -2416,18 +2429,325 @@ const __handlersModule = (() => {
2416
2429
  return { createHandlerRegistry, parseHandlerRef, isHandlerToken };
2417
2430
  })();
2418
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
+ isScopeDestroyed(scope) {
2598
+ return scope !== undefined && destroyedScopes.has(scope);
2599
+ },
2600
+
2601
+ inspect() {
2602
+ const counts = {};
2603
+ for (const [phase, queue] of queues) {
2604
+ counts[phase] = queue.filter((job) => !job.canceled).length;
2605
+ }
2606
+ return {
2607
+ strategy,
2608
+ phases: [...phases],
2609
+ pending: counts,
2610
+ scopesDestroyed: destroyedScopes.size,
2611
+ flushing,
2612
+ scheduled
2613
+ };
2614
+ },
2615
+
2616
+ destroy() {
2617
+ destroyed = true;
2618
+ for (const queue of queues.values()) {
2619
+ for (const job of queue) {
2620
+ job.cancel();
2621
+ }
2622
+ queue.length = 0;
2623
+ }
2624
+ keyedJobs.clear();
2625
+ destroyedScopes.clear();
2626
+ }
2627
+ };
2628
+
2629
+ return api;
2630
+
2631
+ function requestFlush() {
2632
+ if (strategy === "manual" || destroyed || flushing || batchDepth > 0 || scheduled) {
2633
+ return;
2634
+ }
2635
+ scheduled = true;
2636
+ scheduleMicrotask(() => {
2637
+ if (!destroyed) {
2638
+ void api.flush();
2639
+ }
2640
+ });
2641
+ }
2642
+
2643
+ async function flushPhase(phase, scope) {
2644
+ const queue = queues.get(phase);
2645
+ const remaining = [];
2646
+ const runnable = [];
2647
+
2648
+ for (const job of queue.splice(0)) {
2649
+ if (job.canceled) {
2650
+ continue;
2651
+ }
2652
+ if (scope !== undefined && job.scope !== scope) {
2653
+ remaining.push(job);
2654
+ continue;
2655
+ }
2656
+ runnable.push(job);
2657
+ }
2658
+
2659
+ queue.push(...remaining);
2660
+
2661
+ for (const job of runnable) {
2662
+ if (job.key) {
2663
+ keyedJobs.delete(job.key);
2664
+ }
2665
+ if (job.canceled || (job.scope !== undefined && destroyedScopes.has(job.scope))) {
2666
+ continue;
2667
+ }
2668
+ try {
2669
+ await job.fn();
2670
+ } catch (error) {
2671
+ if (onError) {
2672
+ onError(error, job);
2673
+ } else {
2674
+ throw error;
2675
+ }
2676
+ }
2677
+ }
2678
+ }
2679
+
2680
+ function hasJobs() {
2681
+ for (const queue of queues.values()) {
2682
+ if (queue.some((job) => !job.canceled)) {
2683
+ return true;
2684
+ }
2685
+ }
2686
+ return false;
2687
+ }
2688
+
2689
+ function hasJobsForScope(scope) {
2690
+ for (const queue of queues.values()) {
2691
+ if (queue.some((job) => !job.canceled && job.scope === scope)) {
2692
+ return true;
2693
+ }
2694
+ }
2695
+ return false;
2696
+ }
2697
+
2698
+ function assertActive() {
2699
+ if (destroyed) {
2700
+ throw new Error("Scheduler has been destroyed.");
2701
+ }
2702
+ }
2703
+
2704
+ function assertPhase(phase) {
2705
+ if (!queues.has(phase)) {
2706
+ throw new Error(`Unknown scheduler phase "${phase}".`);
2707
+ }
2708
+ }
2709
+
2710
+ function scopeKey(scope) {
2711
+ if (scope === undefined) {
2712
+ return "global";
2713
+ }
2714
+ if ((typeof scope === "object" && scope !== null) || typeof scope === "function") {
2715
+ if (!objectScopeIds.has(scope)) {
2716
+ objectScopeIds.set(scope, `scope:${++scopeCounter}`);
2717
+ }
2718
+ return objectScopeIds.get(scope);
2719
+ }
2720
+ return String(scope);
2721
+ }
2722
+ }
2723
+
2724
+ function scheduleMicrotask(fn) {
2725
+ if (typeof queueMicrotask === "function") {
2726
+ queueMicrotask(fn);
2727
+ return;
2728
+ }
2729
+ Promise.resolve().then(fn);
2730
+ }
2731
+
2732
+ function noop() {}
2733
+ return { createScheduler };
2734
+ })();
2735
+
2419
2736
  const __loaderModule = (() => {
2420
2737
  const { renderComponent } = __componentModule;
2421
2738
  const { createHandlerRegistry } = __handlersModule;
2739
+ const { createScheduler } = __schedulerModule;
2422
2740
  const { createSignalRegistry, isSignalRef } = __signalsModule;
2423
2741
  const { matchAttribute, normalizeAttributeConfig, readAttribute } = __attributesModule;
2424
2742
  const inlineBindingPrefix = "__async:inline:";
2425
2743
 
2426
- function Loader({ root, signals, handlers, server, router, cache, attributes } = {}) {
2744
+ function Loader({ root, signals, handlers, server, router, cache, attributes, scheduler } = {}) {
2427
2745
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
2428
2746
  const rootNode = root ?? documentRef;
2429
2747
  const signalRegistry = signals ?? createSignalRegistry();
2430
2748
  const handlerRegistry = handlers ?? createHandlerRegistry();
2749
+ const schedulerInstance = scheduler ?? createScheduler();
2750
+ const ownsScheduler = !scheduler;
2431
2751
  const attributeConfig = normalizeAttributeConfig(attributes);
2432
2752
  const cleanups = new Set();
2433
2753
  const eventBindings = new WeakMap();
@@ -2448,6 +2768,7 @@ const __loaderModule = (() => {
2448
2768
  server,
2449
2769
  router,
2450
2770
  cache,
2771
+ scheduler: schedulerInstance,
2451
2772
  attributes: attributeConfig,
2452
2773
 
2453
2774
  start() {
@@ -2487,6 +2808,7 @@ const __loaderModule = (() => {
2487
2808
  server: api.server,
2488
2809
  router: api.router,
2489
2810
  cache: api.cache,
2811
+ scheduler: schedulerInstance,
2490
2812
  attributes: attributeConfig
2491
2813
  });
2492
2814
  cleanupChildren(target);
@@ -2507,6 +2829,9 @@ const __loaderModule = (() => {
2507
2829
  runCleanup(cleanup);
2508
2830
  }
2509
2831
  cleanups.clear();
2832
+ if (ownsScheduler) {
2833
+ schedulerInstance.destroy();
2834
+ }
2510
2835
  },
2511
2836
 
2512
2837
  _observeVisible(target, fn) {
@@ -2524,13 +2849,14 @@ const __loaderModule = (() => {
2524
2849
  }
2525
2850
  };
2526
2851
 
2527
- signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
2852
+ signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache, scheduler: schedulerInstance });
2528
2853
  api.server?._setContext?.({
2529
2854
  signals: signalRegistry,
2530
2855
  handlers: handlerRegistry,
2531
2856
  loader: api,
2532
2857
  router: api.router,
2533
- cache: api.cache
2858
+ cache: api.cache,
2859
+ scheduler: schedulerInstance
2534
2860
  });
2535
2861
 
2536
2862
  function bindEventAttributes(scope) {
@@ -2562,18 +2888,19 @@ const __loaderModule = (() => {
2562
2888
 
2563
2889
  const listener = async (event) => {
2564
2890
  try {
2565
- await handlerRegistry.run(ref, {
2891
+ await schedulerInstance.batch(() => handlerRegistry.run(ref, {
2566
2892
  signals: signalRegistry,
2567
2893
  handlers: handlerRegistry,
2568
2894
  loader: api,
2569
2895
  server: api.server,
2570
2896
  router: api.router,
2571
2897
  cache: api.cache,
2898
+ scheduler: schedulerInstance,
2572
2899
  event,
2573
2900
  element,
2574
2901
  el: element,
2575
2902
  root: rootNode
2576
- });
2903
+ }));
2577
2904
  } catch (error) {
2578
2905
  dispatchAsyncError(element, error);
2579
2906
  }
@@ -2689,7 +3016,12 @@ const __loaderModule = (() => {
2689
3016
 
2690
3017
  const read = () => readBinding(path, options);
2691
3018
  apply(read());
2692
- addCleanup(subscribeBinding(path, () => apply(read())), element);
3019
+ addCleanup(subscribeBinding(path, () => {
3020
+ schedulerInstance.enqueue("binding", () => apply(read()), {
3021
+ scope: element,
3022
+ key
3023
+ });
3024
+ }), element);
2693
3025
  }
2694
3026
 
2695
3027
  function bindValueWriter(element, path) {
@@ -2750,7 +3082,12 @@ const __loaderModule = (() => {
2750
3082
  const state = {
2751
3083
  id,
2752
3084
  templates,
2753
- cleanup: signalRegistry.subscribe(`${id}.$status`, () => renderBoundary(boundary))
3085
+ cleanup: signalRegistry.subscribe(`${id}.$status`, () => {
3086
+ schedulerInstance.enqueue("binding", () => renderBoundary(boundary), {
3087
+ scope: boundary,
3088
+ key: `boundary:${id}`
3089
+ });
3090
+ })
2754
3091
  };
2755
3092
  boundaryState.set(boundary, state);
2756
3093
  addCleanup(state.cleanup, boundary);
@@ -2790,7 +3127,7 @@ const __loaderModule = (() => {
2790
3127
  }
2791
3128
  mountedElements.add(element);
2792
3129
  for (const ref of refs) {
2793
- runPseudo(element, ref);
3130
+ scheduleLifecycle(element, () => runPseudo(element, ref), `attach:${ref}`);
2794
3131
  }
2795
3132
  }
2796
3133
 
@@ -2803,7 +3140,7 @@ const __loaderModule = (() => {
2803
3140
  continue;
2804
3141
  }
2805
3142
  visibleElements.add(element);
2806
- addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
3143
+ addCleanup(observeVisible(element, () => scheduleLifecycle(element, () => runPseudo(element, ref), `visible:${ref}`)), element);
2807
3144
  }
2808
3145
  }
2809
3146
 
@@ -2827,6 +3164,7 @@ const __loaderModule = (() => {
2827
3164
  server: api.server,
2828
3165
  router: api.router,
2829
3166
  cache: api.cache,
3167
+ scheduler: schedulerInstance,
2830
3168
  element,
2831
3169
  el: element,
2832
3170
  root: rootNode
@@ -2845,10 +3183,13 @@ const __loaderModule = (() => {
2845
3183
  const ownerWindow = target.ownerDocument?.defaultView ?? globalThis;
2846
3184
  const Observer = ownerWindow.IntersectionObserver ?? globalThis.IntersectionObserver;
2847
3185
  if (!Observer) {
2848
- queueMicrotask(() => {
3186
+ schedulerInstance.enqueue("lifecycle", () => {
2849
3187
  if (!destroyed) {
2850
3188
  fn(target);
2851
3189
  }
3190
+ }, {
3191
+ scope: target,
3192
+ key: "visible:fallback"
2852
3193
  });
2853
3194
  return () => {};
2854
3195
  }
@@ -2903,6 +3244,7 @@ const __loaderModule = (() => {
2903
3244
  }
2904
3245
  for (const element of elementsIn(node)) {
2905
3246
  runScopedCleanups(element);
3247
+ schedulerInstance.markScopeDestroyed(element);
2906
3248
  }
2907
3249
  }
2908
3250
 
@@ -2926,6 +3268,13 @@ const __loaderModule = (() => {
2926
3268
  }
2927
3269
  }
2928
3270
 
3271
+ function scheduleLifecycle(element, fn, key) {
3272
+ schedulerInstance.enqueue("lifecycle", fn, {
3273
+ scope: element,
3274
+ key
3275
+ });
3276
+ }
3277
+
2929
3278
  return api;
2930
3279
  }
2931
3280
 
@@ -3256,6 +3605,7 @@ const __partialsModule = (() => {
3256
3605
  const __routerModule = (() => {
3257
3606
  const { Loader } = __loaderModule;
3258
3607
  const { createHandlerRegistry } = __handlersModule;
3608
+ const { createScheduler } = __schedulerModule;
3259
3609
  const { createSignalRegistry } = __signalsModule;
3260
3610
  const { applyServerResult } = __serverModule;
3261
3611
  const { createRegistryStore } = __registryStoreModule;
@@ -3376,12 +3726,15 @@ const __routerModule = (() => {
3376
3726
  partials,
3377
3727
  fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
3378
3728
  routeEndpoint = "/__async/route",
3379
- attributes
3729
+ attributes,
3730
+ scheduler
3380
3731
  } = {}) {
3381
3732
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
3382
3733
  const rootNode = root ?? documentRef;
3383
3734
  const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
3384
3735
  const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
3736
+ const schedulerInstance = scheduler ?? loader?.scheduler ?? createScheduler();
3737
+ const ownsScheduler = !scheduler && !loader?.scheduler;
3385
3738
  const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
3386
3739
  const loaderInstance =
3387
3740
  loader ??
@@ -3391,6 +3744,7 @@ const __routerModule = (() => {
3391
3744
  handlers: handlerRegistry,
3392
3745
  server,
3393
3746
  cache,
3747
+ scheduler: schedulerInstance,
3394
3748
  attributes: attributeConfig
3395
3749
  });
3396
3750
  const ownsLoader = !loader;
@@ -3410,12 +3764,13 @@ const __routerModule = (() => {
3410
3764
  server,
3411
3765
  cache,
3412
3766
  partials,
3767
+ scheduler: schedulerInstance,
3413
3768
  attributes: attributeConfig,
3414
3769
 
3415
3770
  start() {
3416
3771
  assertActive();
3417
3772
  loaderInstance.router = api;
3418
- signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache });
3773
+ signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache, scheduler: schedulerInstance });
3419
3774
  if (ownsLoader) {
3420
3775
  loaderInstance.start();
3421
3776
  }
@@ -3479,6 +3834,9 @@ const __routerModule = (() => {
3479
3834
  cleanup();
3480
3835
  }
3481
3836
  cleanups.clear();
3837
+ if (ownsScheduler) {
3838
+ schedulerInstance.destroy();
3839
+ }
3482
3840
  }
3483
3841
  };
3484
3842
 
@@ -3584,13 +3942,16 @@ const __routerModule = (() => {
3584
3942
  loader: loaderInstance,
3585
3943
  router: api,
3586
3944
  cache,
3945
+ scheduler: schedulerInstance,
3587
3946
  abort: navigation?.abort
3588
3947
  });
3948
+ await schedulerInstance.flush();
3589
3949
  if (!isActiveNavigation(navigation)) {
3590
3950
  return;
3591
3951
  }
3592
3952
  if (result?.html != null && !result.boundary && !result.redirect) {
3593
3953
  loaderInstance.swap(boundary, result.html);
3954
+ await schedulerInstance.flush();
3594
3955
  }
3595
3956
  if (result?.redirect || options.history === false) {
3596
3957
  return;
@@ -3635,6 +3996,7 @@ const __routerModule = (() => {
3635
3996
  loader: loaderInstance,
3636
3997
  server,
3637
3998
  cache,
3999
+ scheduler: schedulerInstance,
3638
4000
  abort: navigation?.abort
3639
4001
  };
3640
4002
  }
@@ -3821,15 +4183,17 @@ const __appModule = (() => {
3821
4183
  const { Loader } = __loaderModule;
3822
4184
  const { createPartialRegistry } = __partialsModule;
3823
4185
  const { createRouteRegistry, createRouter } = __routerModule;
3824
- const { createServerRegistry } = __serverModule;
4186
+ const { createScheduler } = __schedulerModule;
4187
+ const { createServerNamespace } = __serverModule;
3825
4188
  const { createSignal, createSignalRegistry } = __signalsModule;
3826
4189
  const { createRegistryStore } = __registryStoreModule;
3827
4190
  const { attributeName, normalizeAttributeConfig } = __attributesModule;
3828
4191
  const registryTypes = new Set(["signal", "handler", "server", "partial", "route", "component"]);
3829
4192
 
3830
- function defineApp(initial) {
4193
+ function defineApp(initial, options = {}) {
3831
4194
  const registry = createRegistryStore(undefined, { target: "browser" });
3832
4195
  const runtimes = new Set();
4196
+ const createRuntime = options.createRuntime ?? createApp;
3833
4197
 
3834
4198
  const app = {
3835
4199
  registry,
@@ -3848,7 +4212,7 @@ const __appModule = (() => {
3848
4212
  },
3849
4213
 
3850
4214
  start(options = {}) {
3851
- const runtime = createApp(app, options).start();
4215
+ const runtime = createRuntime(app, options).start();
3852
4216
  app.runtime = runtime;
3853
4217
  return runtime;
3854
4218
  },
@@ -3873,13 +4237,18 @@ const __appModule = (() => {
3873
4237
  function createApp(appOrDefinition = Async, options = {}) {
3874
4238
  const app = isAppHub(appOrDefinition) ? appOrDefinition : defineApp(appOrDefinition ?? {});
3875
4239
  const target = options.target ?? "browser";
4240
+ const scheduler = options.scheduler ?? options.loader?.scheduler ?? createScheduler({
4241
+ strategy: target === "server" ? "manual" : "microtask"
4242
+ });
4243
+ const ownsScheduler = !options.scheduler && !options.loader?.scheduler;
3876
4244
  const attributes = normalizeAttributeConfig(options.attributes);
3877
4245
  const registry = options.registry ?? app.registry.view({ target });
3878
4246
  const signals = options.signals ?? createSignalRegistry(undefined, { registry, type: "signal" });
3879
4247
  const handlers = options.handlers ?? createHandlerRegistry(undefined, { registry, type: "handler" });
3880
4248
  const serverCache = createCacheRegistry(undefined, { registry, type: "cache.server" });
3881
4249
  const browserCache = createCacheRegistry(undefined, { registry, type: "cache.browser" });
3882
- const server = options.server ?? createServerRegistry(undefined, { registry, type: "server" });
4250
+ const serverFactory = options.serverFactory ?? createServerReferenceRegistry;
4251
+ const server = options.server ?? serverFactory(undefined, { registry, type: "server" });
3883
4252
  const partials = options.partials ?? createPartialRegistry(undefined, { registry, type: "partial" });
3884
4253
  const routes = options.routes ?? createRouteRegistry(undefined, { registry, type: "route" });
3885
4254
  const components = options.components ?? createComponentRegistry(undefined, { registry, type: "component" });
@@ -3907,6 +4276,7 @@ const __appModule = (() => {
3907
4276
  },
3908
4277
  loader,
3909
4278
  router,
4279
+ scheduler,
3910
4280
  attributes,
3911
4281
 
3912
4282
  start() {
@@ -3923,12 +4293,13 @@ const __appModule = (() => {
3923
4293
  handlers,
3924
4294
  server,
3925
4295
  cache: browserCache,
4296
+ scheduler,
3926
4297
  attributes
3927
4298
  });
3928
4299
  runtime.loader = loader;
3929
4300
 
3930
4301
  configureServerContext({ cache: browserCache });
3931
- signals._setContext?.({ server, loader, cache: browserCache });
4302
+ signals._setContext?.({ server, loader, cache: browserCache, scheduler });
3932
4303
 
3933
4304
  loader.start();
3934
4305
 
@@ -3944,6 +4315,7 @@ const __appModule = (() => {
3944
4315
  server,
3945
4316
  cache: browserCache,
3946
4317
  partials,
4318
+ scheduler,
3947
4319
  fetch: options.fetch,
3948
4320
  routeEndpoint: options.routeEndpoint,
3949
4321
  attributes
@@ -3955,7 +4327,7 @@ const __appModule = (() => {
3955
4327
  }
3956
4328
  } else {
3957
4329
  configureServerContext({ cache: serverCache });
3958
- signals._setContext?.({ server, cache: serverCache });
4330
+ signals._setContext?.({ server, cache: serverCache, scheduler });
3959
4331
  }
3960
4332
 
3961
4333
  return runtime;
@@ -3969,9 +4341,10 @@ const __appModule = (() => {
3969
4341
  async render(url) {
3970
4342
  assertActive();
3971
4343
  configureServerContext({ cache: serverCache });
3972
- signals._setContext?.({ server, cache: serverCache });
4344
+ signals._setContext?.({ server, cache: serverCache, scheduler });
3973
4345
  const matched = routes.match(url);
3974
4346
  if (!matched) {
4347
+ await scheduler.flush();
3975
4348
  return {
3976
4349
  html: renderDocument("", { status: 404, signals, browserCache, boundary: options.boundary ?? "route", attributes }),
3977
4350
  status: 404,
@@ -3991,8 +4364,8 @@ const __appModule = (() => {
3991
4364
  cache: serverCache,
3992
4365
  browserCache,
3993
4366
  partials,
3994
- request: options.request,
3995
- locals: options.locals
4367
+ scheduler,
4368
+ ...currentRequestContext()
3996
4369
  })
3997
4370
  : { html: "" };
3998
4371
 
@@ -4005,6 +4378,8 @@ const __appModule = (() => {
4005
4378
  browserCache.restore(result.cache.browser);
4006
4379
  }
4007
4380
 
4381
+ await scheduler.flush();
4382
+
4008
4383
  const status = result.status ?? 200;
4009
4384
  return {
4010
4385
  html: renderDocument(result.html, { status, signals, browserCache, boundary: result.boundary ?? options.boundary ?? "route", attributes }),
@@ -4023,6 +4398,9 @@ const __appModule = (() => {
4023
4398
  router?.destroy?.();
4024
4399
  loader?.destroy?.();
4025
4400
  signals.destroy?.();
4401
+ if (ownsScheduler) {
4402
+ scheduler.destroy();
4403
+ }
4026
4404
  },
4027
4405
 
4028
4406
  _applyUse(normalized) {
@@ -4043,11 +4421,23 @@ const __appModule = (() => {
4043
4421
  loader,
4044
4422
  router,
4045
4423
  cache,
4046
- request: options.request,
4047
- locals: options.locals
4424
+ scheduler,
4425
+ requestContext: options.requestContext,
4426
+ ...currentRequestContext()
4048
4427
  });
4049
4428
  }
4050
4429
 
4430
+ function currentRequestContext() {
4431
+ const context = readRequestContextLike(options.requestContext);
4432
+ return {
4433
+ requestContext: context,
4434
+ request: context.request ?? options.request,
4435
+ headers: context.headers ?? options.headers,
4436
+ cookies: context.cookies ?? options.cookies,
4437
+ locals: context.locals ?? options.locals
4438
+ };
4439
+ }
4440
+
4051
4441
  function assertActive() {
4052
4442
  if (destroyed) {
4053
4443
  throw new Error("Async app runtime has been destroyed.");
@@ -4201,6 +4591,77 @@ const __appModule = (() => {
4201
4591
  }
4202
4592
  }
4203
4593
 
4594
+ function createServerReferenceRegistry(initialMap = {}, options = {}) {
4595
+ const registry = options.registry ?? createRegistryStore();
4596
+ const type = options.type ?? "server";
4597
+ const defaults = {};
4598
+
4599
+ const reference = {
4600
+ registry,
4601
+
4602
+ register(id, value) {
4603
+ registry.register(type, id, value);
4604
+ return id;
4605
+ },
4606
+
4607
+ registerMany(map) {
4608
+ for (const [id, value] of Object.entries(map ?? {})) {
4609
+ reference.register(id, value);
4610
+ }
4611
+ return reference;
4612
+ },
4613
+
4614
+ unregister(id) {
4615
+ return registry.unregister(type, id);
4616
+ },
4617
+
4618
+ resolve() {
4619
+ return undefined;
4620
+ },
4621
+
4622
+ async run(id) {
4623
+ throw new Error(`Server command "${id}" cannot run without a server proxy or server registry.`);
4624
+ },
4625
+
4626
+ keys() {
4627
+ return registry.keys(type);
4628
+ },
4629
+
4630
+ entries() {
4631
+ return registry.entries(type);
4632
+ },
4633
+
4634
+ inspect() {
4635
+ return registry.entries(type);
4636
+ },
4637
+
4638
+ _setContext(context = {}) {
4639
+ Object.assign(defaults, context);
4640
+ return reference;
4641
+ },
4642
+
4643
+ _adoptMany() {
4644
+ return reference;
4645
+ }
4646
+ };
4647
+
4648
+ reference.registerMany(initialMap);
4649
+ return createServerNamespace((id, args, context) => reference.run(id, args, context), reference, () => defaults);
4650
+ }
4651
+
4652
+ function readRequestContextLike(store) {
4653
+ if (!store) {
4654
+ return {};
4655
+ }
4656
+ if (typeof store.get === "function") {
4657
+ return store.get() ?? {};
4658
+ }
4659
+ if (typeof store.getStore === "function") {
4660
+ return store.getStore() ?? {};
4661
+ }
4662
+ return {};
4663
+ }
4664
+
4204
4665
  function normalizeEntries(type, entries = {}) {
4205
4666
  if (type !== "signal") {
4206
4667
  return { ...(entries ?? {}) };
@@ -4252,6 +4713,312 @@ const __appModule = (() => {
4252
4713
  return { defineApp, createApp, readSnapshot, Async };
4253
4714
  })();
4254
4715
 
4716
+ const __boundaryReceiverModule = (() => {
4717
+ const defaultRecentLimit = 50;
4718
+
4719
+ function createBoundaryReceiver(options = {}) {
4720
+ const loader = options.loader;
4721
+ const signals = options.signals ?? loader?.signals;
4722
+ const cache = options.cache ?? loader?.cache;
4723
+ const scheduler = options.scheduler ?? loader?.scheduler;
4724
+ const router = options.router ?? loader?.router;
4725
+ const recentLimit = options.recentLimit ?? defaultRecentLimit;
4726
+ const throwOnError = options.throwOnError === true;
4727
+ const onApply = typeof options.onApply === "function" ? options.onApply : undefined;
4728
+ const onIgnore = typeof options.onIgnore === "function" ? options.onIgnore : undefined;
4729
+ const onError = typeof options.onError === "function" ? options.onError : undefined;
4730
+ const isScopeDestroyed = typeof options.isScopeDestroyed === "function"
4731
+ ? options.isScopeDestroyed
4732
+ : (scope) => scheduler?.isScopeDestroyed?.(scope) ?? scheduler?.inspectDestroyed?.(scope) ?? false;
4733
+
4734
+ if (!loader || typeof loader.swap !== "function") {
4735
+ throw new TypeError("createBoundaryReceiver(...) requires a loader with swap(boundary, html).");
4736
+ }
4737
+ if (!Number.isInteger(recentLimit) || recentLimit < 0) {
4738
+ throw new TypeError("createBoundaryReceiver(...) recentLimit must be a non-negative integer.");
4739
+ }
4740
+
4741
+ const boundaries = new Map();
4742
+ const recent = [];
4743
+ let destroyed = false;
4744
+
4745
+ const receiver = {
4746
+ async apply(patch) {
4747
+ if (destroyed) {
4748
+ throw new Error("Boundary receiver has been destroyed.");
4749
+ }
4750
+
4751
+ const normalized = validatePatch(patch);
4752
+ const record = boundaryRecord(normalized.boundary);
4753
+ if (normalized.seq <= record.lastSeq) {
4754
+ const result = {
4755
+ status: "ignored-stale",
4756
+ boundary: normalized.boundary,
4757
+ seq: normalized.seq,
4758
+ lastSeq: record.lastSeq
4759
+ };
4760
+ record.ignored += 1;
4761
+ record.lastStatus = result.status;
4762
+ remember(result);
4763
+ onIgnore?.(result, patch);
4764
+ return result;
4765
+ }
4766
+
4767
+ if (normalized.parentScope !== undefined && isScopeDestroyed(normalized.parentScope)) {
4768
+ const result = {
4769
+ status: "ignored-destroyed",
4770
+ boundary: normalized.boundary,
4771
+ seq: normalized.seq,
4772
+ parentScope: normalized.parentScope
4773
+ };
4774
+ record.ignored += 1;
4775
+ record.lastStatus = result.status;
4776
+ remember(result);
4777
+ onIgnore?.(result, patch);
4778
+ return result;
4779
+ }
4780
+
4781
+ record.lastSeq = normalized.seq;
4782
+
4783
+ if (Object.hasOwn(normalized, "error")) {
4784
+ const error = toStableError(normalized.error);
4785
+ const result = {
4786
+ status: "errored",
4787
+ boundary: normalized.boundary,
4788
+ seq: normalized.seq,
4789
+ error
4790
+ };
4791
+ record.errored += 1;
4792
+ record.lastStatus = result.status;
4793
+ remember(result);
4794
+ onError?.(error, result, patch);
4795
+ if (throwOnError) {
4796
+ throw error;
4797
+ }
4798
+ return result;
4799
+ }
4800
+
4801
+ if (normalized.signals) {
4802
+ if (!signals || typeof signals.set !== "function") {
4803
+ throw new Error("Boundary patch includes signals, but no signal registry is available.");
4804
+ }
4805
+ for (const [path, value] of Object.entries(normalized.signals)) {
4806
+ signals.set(path, value);
4807
+ }
4808
+ }
4809
+
4810
+ if (normalized.cache?.browser) {
4811
+ if (!cache || typeof cache.restore !== "function") {
4812
+ throw new Error("Boundary patch includes browser cache, but no cache registry is available.");
4813
+ }
4814
+ cache.restore(normalized.cache.browser);
4815
+ }
4816
+
4817
+ if (normalized.html != null) {
4818
+ loader.swap(normalized.boundary, normalized.html);
4819
+ }
4820
+
4821
+ await flushScheduler(scheduler, normalized.scope);
4822
+
4823
+ if (normalized.redirect) {
4824
+ await followRedirect(normalized.redirect, router, loader);
4825
+ const result = {
4826
+ status: "redirected",
4827
+ boundary: normalized.boundary,
4828
+ seq: normalized.seq,
4829
+ redirect: normalized.redirect
4830
+ };
4831
+ record.applied += 1;
4832
+ record.lastStatus = result.status;
4833
+ remember(result);
4834
+ onApply?.(result, patch);
4835
+ return result;
4836
+ }
4837
+
4838
+ const result = {
4839
+ status: "applied",
4840
+ boundary: normalized.boundary,
4841
+ seq: normalized.seq
4842
+ };
4843
+ record.applied += 1;
4844
+ record.lastStatus = result.status;
4845
+ remember(result);
4846
+ onApply?.(result, patch);
4847
+ return result;
4848
+ },
4849
+
4850
+ inspect() {
4851
+ const snapshot = {};
4852
+ for (const [boundary, record] of boundaries) {
4853
+ snapshot[boundary] = {
4854
+ lastSeq: record.lastSeq,
4855
+ applied: record.applied,
4856
+ ignored: record.ignored,
4857
+ lastStatus: record.lastStatus
4858
+ };
4859
+ if (record.errored > 0) {
4860
+ snapshot[boundary].errored = record.errored;
4861
+ }
4862
+ }
4863
+ return {
4864
+ destroyed,
4865
+ boundaries: snapshot,
4866
+ recent: recent.map((entry) => ({ ...entry }))
4867
+ };
4868
+ },
4869
+
4870
+ reset(boundary) {
4871
+ if (boundary === undefined) {
4872
+ boundaries.clear();
4873
+ recent.length = 0;
4874
+ return receiver;
4875
+ }
4876
+ assertBoundary(boundary);
4877
+ boundaries.delete(boundary);
4878
+ for (let index = recent.length - 1; index >= 0; index -= 1) {
4879
+ if (recent[index].boundary === boundary) {
4880
+ recent.splice(index, 1);
4881
+ }
4882
+ }
4883
+ return receiver;
4884
+ },
4885
+
4886
+ destroy() {
4887
+ destroyed = true;
4888
+ boundaries.clear();
4889
+ recent.length = 0;
4890
+ }
4891
+ };
4892
+
4893
+ return receiver;
4894
+
4895
+ function boundaryRecord(boundary) {
4896
+ if (!boundaries.has(boundary)) {
4897
+ boundaries.set(boundary, {
4898
+ lastSeq: -Infinity,
4899
+ applied: 0,
4900
+ ignored: 0,
4901
+ errored: 0,
4902
+ lastStatus: undefined
4903
+ });
4904
+ }
4905
+ return boundaries.get(boundary);
4906
+ }
4907
+
4908
+ function remember(result) {
4909
+ if (recentLimit === 0) {
4910
+ return;
4911
+ }
4912
+ recent.push(toRecentEntry(result));
4913
+ while (recent.length > recentLimit) {
4914
+ recent.shift();
4915
+ }
4916
+ }
4917
+ }
4918
+
4919
+ function validatePatch(patch) {
4920
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
4921
+ throw new TypeError("receiver.apply(patch) requires a boundary patch object.");
4922
+ }
4923
+
4924
+ assertBoundary(patch.boundary);
4925
+ if (typeof patch.seq !== "number" || !Number.isFinite(patch.seq)) {
4926
+ throw new TypeError("Boundary patch seq must be a finite number.");
4927
+ }
4928
+
4929
+ if (patch.signals !== undefined && !isPlainObject(patch.signals)) {
4930
+ throw new TypeError("Boundary patch signals must be an object.");
4931
+ }
4932
+ if (patch.cache !== undefined && !isPlainObject(patch.cache)) {
4933
+ throw new TypeError("Boundary patch cache must be an object.");
4934
+ }
4935
+ if (patch.cache?.browser !== undefined && !isPlainObject(patch.cache.browser)) {
4936
+ throw new TypeError("Boundary patch cache.browser must be an object.");
4937
+ }
4938
+ if (patch.redirect !== undefined && (typeof patch.redirect !== "string" || patch.redirect.length === 0)) {
4939
+ throw new TypeError("Boundary patch redirect must be a non-empty string.");
4940
+ }
4941
+ if (patch.parentScope !== undefined && typeof patch.parentScope !== "string") {
4942
+ throw new TypeError("Boundary patch parentScope must be a string.");
4943
+ }
4944
+ if (patch.scope !== undefined && typeof patch.scope !== "string") {
4945
+ throw new TypeError("Boundary patch scope must be a string.");
4946
+ }
4947
+
4948
+ const hasHtml = Object.hasOwn(patch, "html") && patch.html != null;
4949
+ const hasSignals = patch.signals && Object.keys(patch.signals).length > 0;
4950
+ const hasBrowserCache = patch.cache?.browser && Object.keys(patch.cache.browser).length > 0;
4951
+ const hasRedirect = Boolean(patch.redirect);
4952
+ const hasError = Object.hasOwn(patch, "error");
4953
+ if (!hasHtml && !hasSignals && !hasBrowserCache && !hasRedirect && !hasError) {
4954
+ throw new TypeError("Boundary patch must include html, signals, cache.browser, redirect, or error.");
4955
+ }
4956
+
4957
+ return patch;
4958
+ }
4959
+
4960
+ function assertBoundary(boundary) {
4961
+ if (typeof boundary !== "string" || boundary.length === 0) {
4962
+ throw new TypeError("Boundary patch boundary must be a non-empty string.");
4963
+ }
4964
+ }
4965
+
4966
+ async function flushScheduler(scheduler, scope) {
4967
+ if (!scheduler) {
4968
+ return;
4969
+ }
4970
+ if (scope !== undefined && typeof scheduler.flushScope === "function") {
4971
+ await scheduler.flushScope(scope);
4972
+ return;
4973
+ }
4974
+ if (typeof scheduler.flush === "function") {
4975
+ await scheduler.flush();
4976
+ }
4977
+ }
4978
+
4979
+ async function followRedirect(redirect, router, loader) {
4980
+ if (router && typeof router.navigate === "function") {
4981
+ await router.navigate(redirect);
4982
+ return;
4983
+ }
4984
+ const location = loader?.root?.ownerDocument?.defaultView?.location ?? globalThis.location;
4985
+ location?.assign?.(redirect);
4986
+ }
4987
+
4988
+ function toStableError(value) {
4989
+ if (value instanceof Error) {
4990
+ return value;
4991
+ }
4992
+ if (value && typeof value === "object" && typeof value.message === "string") {
4993
+ return Object.assign(new Error(value.message), value);
4994
+ }
4995
+ return new Error(String(value));
4996
+ }
4997
+
4998
+ function toRecentEntry(result) {
4999
+ const entry = {
5000
+ boundary: result.boundary,
5001
+ seq: result.seq,
5002
+ status: result.status
5003
+ };
5004
+ if (result.status === "ignored-stale") {
5005
+ entry.lastSeq = result.lastSeq;
5006
+ }
5007
+ if (result.status === "ignored-destroyed" && result.parentScope !== undefined) {
5008
+ entry.parentScope = result.parentScope;
5009
+ }
5010
+ if (result.status === "redirected") {
5011
+ entry.redirect = result.redirect;
5012
+ }
5013
+ return entry;
5014
+ }
5015
+
5016
+ function isPlainObject(value) {
5017
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
5018
+ }
5019
+ return { createBoundaryReceiver };
5020
+ })();
5021
+
4255
5022
  const __delayModule = (() => {
4256
5023
  function delay(ms, signal) {
4257
5024
  if (signal?.aborted) {
@@ -4293,6 +5060,7 @@ const { defineApp: defineApp } = __appModule;
4293
5060
  const { readSnapshot: readSnapshot } = __appModule;
4294
5061
  const { attributeName: attributeName } = __attributesModule;
4295
5062
  const { defineAttributeConfig: defineAttributeConfig } = __attributesModule;
5063
+ const { createBoundaryReceiver: createBoundaryReceiver } = __boundaryReceiverModule;
4296
5064
  const { createCacheRegistry: createCacheRegistry } = __cacheModule;
4297
5065
  const { defineCache: defineCache } = __cacheModule;
4298
5066
  const { component: component } = __componentModule;
@@ -4309,12 +5077,15 @@ const { createRouteRegistry: createRouteRegistry } = __routerModule;
4309
5077
  const { createRouter: createRouter } = __routerModule;
4310
5078
  const { defineRoute: defineRoute } = __routerModule;
4311
5079
  const { route: route } = __routerModule;
5080
+ const { createScheduler: createScheduler } = __schedulerModule;
5081
+ const { applyServerResult: applyServerResult } = __serverModule;
4312
5082
  const { createServerProxy: createServerProxy } = __serverModule;
4313
- const { createServerRegistry: createServerRegistry } = __serverModule;
5083
+ const { resolveServerCommandArguments: resolveServerCommandArguments } = __serverModule;
5084
+ const { unwrapServerResult: unwrapServerResult } = __serverModule;
4314
5085
  const { computed: computed } = __signalsModule;
4315
5086
  const { createSignal: createSignal } = __signalsModule;
4316
5087
  const { createSignalRegistry: createSignalRegistry } = __signalsModule;
4317
5088
  const { effect: effect } = __signalsModule;
4318
5089
  const { signal: signal } = __signalsModule;
4319
5090
 
4320
- export { 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 };
5091
+ export { 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 };