@async/framework 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  // Generated by scripts/build-framework-bundle.js. Do not edit by hand.
2
2
  // @ts-nocheck
3
- // Bundled TypeScript source entry for TS-aware runtimes and higher-layer tooling.
3
+ // Bundled browser TypeScript source entry for TS-aware runtimes and higher-layer tooling.
4
4
 
5
5
  const __asyncSignalModule = (() => {
6
6
  const asyncSignalKind = Symbol.for("@async/framework.asyncSignal");
@@ -95,7 +95,8 @@ const __asyncSignalModule = (() => {
95
95
  router: context.router,
96
96
  loader: context.loader,
97
97
  cache: context.cache,
98
- abort: activeAbort
98
+ abort: activeAbort,
99
+ scheduler: context.scheduler
99
100
  });
100
101
  }
101
102
  return server;
@@ -109,6 +110,9 @@ const __asyncSignalModule = (() => {
109
110
  get cache() {
110
111
  return registry._context?.().cache;
111
112
  },
113
+ get scheduler() {
114
+ return registry._context?.().scheduler;
115
+ },
112
116
  get version() {
113
117
  return runVersion;
114
118
  },
@@ -185,11 +189,20 @@ const __asyncSignalModule = (() => {
185
189
  _bindRegistry(nextRegistry, nextId) {
186
190
  registry = nextRegistry;
187
191
  registeredId = nextId;
188
- queueMicrotask(() => {
192
+ const start = () => {
189
193
  if (registry === nextRegistry && status === "idle") {
190
194
  state.refresh();
191
195
  }
192
- });
196
+ };
197
+ const scheduler = registry._context?.().scheduler;
198
+ if (scheduler) {
199
+ scheduler.enqueue("async", start, {
200
+ scope: registeredId,
201
+ key: `asyncSignal:${registeredId}:initial`
202
+ });
203
+ } else {
204
+ queueMicrotask(start);
205
+ }
193
206
  },
194
207
 
195
208
  _dispose() {
@@ -225,11 +238,26 @@ const __asyncSignalModule = (() => {
225
238
  for (const dependency of dependencies) {
226
239
  const dependencyId = String(dependency).split(".")[0];
227
240
  if (dependencyId && dependencyId !== registeredId) {
228
- dependencyCleanups.add(registry.subscribe(dependency, () => state.refresh()));
241
+ dependencyCleanups.add(registry.subscribe(dependency, () => scheduleRefresh()));
229
242
  }
230
243
  }
231
244
  }
232
245
 
246
+ function scheduleRefresh() {
247
+ if (activeAbort && !activeAbort.aborted) {
248
+ activeAbort.cancel(new Error(`Async signal "${registeredId}" dependency changed.`));
249
+ }
250
+ const scheduler = registry?._context?.().scheduler;
251
+ if (!scheduler) {
252
+ state.refresh();
253
+ return;
254
+ }
255
+ scheduler.enqueue("async", () => state.refresh(), {
256
+ scope: registeredId,
257
+ key: `asyncSignal:${registeredId}:refresh`
258
+ });
259
+ }
260
+
233
261
  function notify() {
234
262
  for (const subscriber of [...subscribers]) {
235
263
  subscriber(state);
@@ -866,7 +894,8 @@ const __signalsModule = (() => {
866
894
  server: registry._context?.().server,
867
895
  router: registry._context?.().router,
868
896
  loader: registry._context?.().loader,
869
- cache: registry._context?.().cache
897
+ cache: registry._context?.().cache,
898
+ scheduler: registry._context?.().scheduler
870
899
  }));
871
900
  });
872
901
  }
@@ -894,6 +923,8 @@ const __signalsModule = (() => {
894
923
  const registryCleanups = new Map();
895
924
  const runtimeContext = {};
896
925
  const boundEntries = new Set();
926
+ let subscriptionCounter = 0;
927
+ let effectCounter = 0;
897
928
 
898
929
  const registry = attachRegistryInspection({
899
930
  register(id, signalLike) {
@@ -969,17 +1000,21 @@ const __signalsModule = (() => {
969
1000
  return createRef(registry, id);
970
1001
  },
971
1002
 
972
- subscribe(path, fn) {
1003
+ subscribe(path, fn, options = {}) {
973
1004
  if (typeof fn !== "function") {
974
1005
  throw new TypeError("subscribe(path, fn) requires a function.");
975
1006
  }
976
1007
  const parsed = parsePath(path, entries);
977
1008
  const entry = requireEntry(entries, parsed.id);
1009
+ const subscriptionId = ++subscriptionCounter;
978
1010
  return entry.subscribe(() => {
979
- fn(registry.get(parsed.path), {
1011
+ scheduleCallback(() => fn(registry.get(parsed.path), {
980
1012
  id: parsed.id,
981
1013
  path: parsed.path,
982
1014
  signal: entry
1015
+ }), {
1016
+ ...options,
1017
+ key: options.key ?? `signal:${parsed.path}:${subscriptionId}`
983
1018
  });
984
1019
  });
985
1020
  },
@@ -997,10 +1032,12 @@ const __signalsModule = (() => {
997
1032
  return registry.ref(id);
998
1033
  },
999
1034
 
1000
- effect(fn) {
1035
+ effect(fn, options = {}) {
1001
1036
  let cleanup;
1002
1037
  let dependencyCleanups = [];
1003
1038
  let stopped = false;
1039
+ const scheduler = options.scheduler;
1040
+ const effectId = ++effectCounter;
1004
1041
 
1005
1042
  const run = () => {
1006
1043
  if (stopped) {
@@ -1019,10 +1056,22 @@ const __signalsModule = (() => {
1019
1056
  server: runtimeContext.server,
1020
1057
  router: runtimeContext.router,
1021
1058
  loader: runtimeContext.loader,
1022
- cache: runtimeContext.cache
1059
+ cache: runtimeContext.cache,
1060
+ scheduler: runtimeContext.scheduler
1023
1061
  }));
1024
1062
  cleanup = outcome.value;
1025
- dependencyCleanups = outcome.dependencies.map((dependency) => registry.subscribe(dependency, run));
1063
+ dependencyCleanups = outcome.dependencies.map((dependency) => registry.subscribe(dependency, scheduleRun));
1064
+ };
1065
+
1066
+ const scheduleRun = () => {
1067
+ if (!scheduler) {
1068
+ run();
1069
+ return;
1070
+ }
1071
+ scheduler.enqueue(options.phase ?? "effect", run, {
1072
+ scope: options.scope,
1073
+ key: options.key ?? `effect:${effectId}`
1074
+ });
1026
1075
  };
1027
1076
 
1028
1077
  run();
@@ -1099,6 +1148,17 @@ const __signalsModule = (() => {
1099
1148
  registryCleanups.set(id, cleanup);
1100
1149
  }
1101
1150
  }
1151
+
1152
+ function scheduleCallback(fn, options = {}) {
1153
+ const scheduler = options.scheduler;
1154
+ if (!scheduler || options.phase === "sync") {
1155
+ return fn();
1156
+ }
1157
+ return scheduler.enqueue(options.phase ?? "effect", fn, {
1158
+ scope: options.scope,
1159
+ key: options.key
1160
+ });
1161
+ }
1102
1162
  }
1103
1163
 
1104
1164
  function normalizeSignal(signalLike) {
@@ -1565,10 +1625,15 @@ const __componentModule = (() => {
1565
1625
  html,
1566
1626
  attach(target) {
1567
1627
  for (const hook of attachHooks) {
1568
- const cleanup = hook(target);
1569
- if (typeof cleanup === "function") {
1570
- cleanups.push(cleanup);
1571
- }
1628
+ runtime.scheduler?.enqueue("lifecycle", () => {
1629
+ const cleanup = hook(target);
1630
+ if (typeof cleanup === "function") {
1631
+ cleanups.push(cleanup);
1632
+ }
1633
+ }, {
1634
+ scope,
1635
+ key: `attach:${attachHooks.indexOf(hook)}`
1636
+ }) ?? runAttachHook(hook, target);
1572
1637
  }
1573
1638
  },
1574
1639
  mount(target) {
@@ -1576,7 +1641,17 @@ const __componentModule = (() => {
1576
1641
  },
1577
1642
  visible(target, observeVisible) {
1578
1643
  for (const hook of visibleHooks) {
1579
- const cleanup = observeVisible(target, hook);
1644
+ const cleanup = observeVisible(target, () => {
1645
+ runtime.scheduler?.enqueue("lifecycle", () => {
1646
+ const hookCleanup = hook(target);
1647
+ if (typeof hookCleanup === "function") {
1648
+ cleanups.push(hookCleanup);
1649
+ }
1650
+ }, {
1651
+ scope,
1652
+ key: `visible:${visibleHooks.indexOf(hook)}`
1653
+ }) ?? runVisibleHook(hook, target);
1654
+ });
1580
1655
  if (typeof cleanup === "function") {
1581
1656
  cleanups.push(cleanup);
1582
1657
  }
@@ -1586,6 +1661,7 @@ const __componentModule = (() => {
1586
1661
  while (destroyHooks.length > 0) {
1587
1662
  destroyHooks.pop()?.();
1588
1663
  }
1664
+ runtime.scheduler?.markScopeDestroyed(scope);
1589
1665
  while (cleanups.length > 0) {
1590
1666
  cleanups.pop()?.();
1591
1667
  }
@@ -1594,10 +1670,24 @@ const __componentModule = (() => {
1594
1670
  }
1595
1671
  }
1596
1672
  };
1673
+
1674
+ function runAttachHook(hook, target) {
1675
+ const cleanup = hook(target);
1676
+ if (typeof cleanup === "function") {
1677
+ cleanups.push(cleanup);
1678
+ }
1679
+ }
1680
+
1681
+ function runVisibleHook(hook, target) {
1682
+ const cleanup = hook(target);
1683
+ if (typeof cleanup === "function") {
1684
+ cleanups.push(cleanup);
1685
+ }
1686
+ }
1597
1687
  }
1598
1688
 
1599
1689
  function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks, renderScopedTemplate }) {
1600
- const { signals, handlers, loader, server, router, cache } = runtime;
1690
+ const { signals, handlers, loader, server, router, cache, scheduler } = runtime;
1601
1691
  const generatedHandlers = new WeakMap();
1602
1692
  let generatedHandlerCounter = 0;
1603
1693
  let generatedSignalCounter = 0;
@@ -1609,6 +1699,7 @@ const __componentModule = (() => {
1609
1699
  server,
1610
1700
  router,
1611
1701
  cache,
1702
+ scheduler,
1612
1703
 
1613
1704
  signal(name, initial) {
1614
1705
  if (arguments.length === 1) {
@@ -1653,7 +1744,11 @@ const __componentModule = (() => {
1653
1744
  },
1654
1745
 
1655
1746
  effect(fn) {
1656
- const cleanup = signals.effect(() => fn.call(context));
1747
+ const cleanup = signals.effect(() => fn.call(context), {
1748
+ scheduler,
1749
+ phase: "effect",
1750
+ scope
1751
+ });
1657
1752
  cleanups.push(cleanup);
1658
1753
  return cleanup;
1659
1754
  },
@@ -1775,93 +1870,10 @@ const __componentModule = (() => {
1775
1870
  })();
1776
1871
 
1777
1872
  const __serverModule = (() => {
1778
- const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
1779
1873
  const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
1780
1874
  const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
1781
1875
  const appliedServerValues = new WeakSet();
1782
1876
 
1783
- function createServerRegistry(initialMap = {}, options = {}) {
1784
- const registryStore = options.registry ?? createRegistryStore();
1785
- const type = options.type ?? "server";
1786
- const entries = registryStore._map(type);
1787
- const defaults = {};
1788
-
1789
- const registry = attachRegistryInspection({
1790
- register(id, fn) {
1791
- assertServerId(id);
1792
- if (typeof fn !== "function") {
1793
- throw new TypeError(`Server function "${id}" must be a function.`);
1794
- }
1795
- if (entries.has(id)) {
1796
- throw new Error(`Server function "${id}" is already registered.`);
1797
- }
1798
- entries.set(id, fn);
1799
- return id;
1800
- },
1801
-
1802
- registerMany(map) {
1803
- for (const [id, fn] of Object.entries(map ?? {})) {
1804
- registry.register(id, fn);
1805
- }
1806
- return registry;
1807
- },
1808
-
1809
- unregister(id) {
1810
- assertServerId(id);
1811
- return entries.delete(id);
1812
- },
1813
-
1814
- resolve(id) {
1815
- assertServerId(id);
1816
- return entries.get(id);
1817
- },
1818
-
1819
- async run(id, args = [], context = {}) {
1820
- assertServerId(id);
1821
- const fn = registry.resolve(id);
1822
- if (!fn) {
1823
- throw new Error(`Server function "${id}" is not registered.`);
1824
- }
1825
-
1826
- let runContext;
1827
- const server = createServerNamespace((childId, childArgs, childContext = {}) => {
1828
- return registry.run(childId, childArgs, { ...runContext, ...childContext });
1829
- }, {}, () => runContext);
1830
-
1831
- const mergedContext = {
1832
- ...defaults,
1833
- ...context,
1834
- cache: defaults.cache ?? context.cache
1835
- };
1836
-
1837
- runContext = {
1838
- ...mergedContext,
1839
- id,
1840
- args,
1841
- input: mergedContext.input,
1842
- signals: createSignalReader(mergedContext.signals),
1843
- abort: mergedContext.abort,
1844
- cache: mergedContext.cache,
1845
- server
1846
- };
1847
-
1848
- return fn.call(runContext, ...args);
1849
- },
1850
-
1851
- _setContext(context = {}) {
1852
- Object.assign(defaults, context);
1853
- return registry;
1854
- },
1855
-
1856
- _adoptMany() {
1857
- return registry;
1858
- }
1859
- }, registryStore, type);
1860
-
1861
- registry.registerMany(initialMap);
1862
- return createServerNamespace((id, args, context) => registry.run(id, args, context), registry, () => defaults);
1863
- }
1864
-
1865
1877
  function createServerProxy({
1866
1878
  endpoint = "/__async/server",
1867
1879
  fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
@@ -1869,13 +1881,14 @@ const __serverModule = (() => {
1869
1881
  loader,
1870
1882
  router,
1871
1883
  cache,
1884
+ scheduler,
1872
1885
  headers = {}
1873
1886
  } = {}) {
1874
1887
  if (typeof fetchImpl !== "function") {
1875
1888
  throw new TypeError("createServerProxy(...) requires fetch to be available.");
1876
1889
  }
1877
1890
 
1878
- const defaults = { signals, loader, router, cache };
1891
+ const defaults = { signals, loader, router, cache, scheduler };
1879
1892
 
1880
1893
  async function run(id, args = [], context = {}) {
1881
1894
  assertServerId(id);
@@ -2214,7 +2227,7 @@ const __serverModule = (() => {
2214
2227
  throw new TypeError("Server function id must be a non-empty string.");
2215
2228
  }
2216
2229
  }
2217
- return { createServerRegistry, createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput };
2230
+ return { createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
2218
2231
  })();
2219
2232
 
2220
2233
  const __handlersModule = (() => {
@@ -2417,18 +2430,321 @@ const __handlersModule = (() => {
2417
2430
  return { createHandlerRegistry, parseHandlerRef, isHandlerToken };
2418
2431
  })();
2419
2432
 
2433
+ const __schedulerModule = (() => {
2434
+ const defaultPhases = ["binding", "lifecycle", "effect", "async", "post"];
2435
+
2436
+ function createScheduler(options = {}) {
2437
+ const phases = [...(options.phases ?? defaultPhases)];
2438
+ const queues = new Map(phases.map((phase) => [phase, []]));
2439
+ const keyedJobs = new Map();
2440
+ const destroyedScopes = new Set();
2441
+ const objectScopeIds = new WeakMap();
2442
+ const onError = typeof options.onError === "function" ? options.onError : undefined;
2443
+ const maxDepth = options.maxDepth ?? 100;
2444
+ const strategy = options.strategy ?? "microtask";
2445
+ let destroyed = false;
2446
+ let flushing = false;
2447
+ let scheduled = false;
2448
+ let batchDepth = 0;
2449
+ let jobCounter = 0;
2450
+ let scopeCounter = 0;
2451
+
2452
+ const api = {
2453
+ strategy,
2454
+ phases,
2455
+
2456
+ batch(fn) {
2457
+ if (typeof fn !== "function") {
2458
+ throw new TypeError("scheduler.batch(fn) requires a function.");
2459
+ }
2460
+ assertActive();
2461
+ batchDepth += 1;
2462
+ let asyncBatch = false;
2463
+ try {
2464
+ const value = fn();
2465
+ if (value && typeof value.then === "function") {
2466
+ asyncBatch = true;
2467
+ return value.finally(() => {
2468
+ batchDepth -= 1;
2469
+ requestFlush();
2470
+ });
2471
+ }
2472
+ return value;
2473
+ } finally {
2474
+ if (!asyncBatch && batchDepth > 0) {
2475
+ batchDepth -= 1;
2476
+ requestFlush();
2477
+ }
2478
+ }
2479
+ },
2480
+
2481
+ enqueue(phase, fn, options = {}) {
2482
+ assertActive();
2483
+ assertPhase(phase);
2484
+ if (typeof fn !== "function") {
2485
+ throw new TypeError("scheduler.enqueue(phase, fn) requires a function.");
2486
+ }
2487
+ const scope = options.scope;
2488
+ if (scope !== undefined && destroyedScopes.has(scope)) {
2489
+ return noop;
2490
+ }
2491
+
2492
+ const dedupeKey = options.key === undefined ? undefined : `${phase}:${scopeKey(scope)}:${String(options.key)}`;
2493
+ if (dedupeKey && keyedJobs.has(dedupeKey)) {
2494
+ return keyedJobs.get(dedupeKey).cancel;
2495
+ }
2496
+
2497
+ const job = {
2498
+ id: ++jobCounter,
2499
+ phase,
2500
+ fn,
2501
+ scope,
2502
+ boundary: options.boundary,
2503
+ key: dedupeKey,
2504
+ canceled: false,
2505
+ cancel() {
2506
+ job.canceled = true;
2507
+ if (job.key) {
2508
+ keyedJobs.delete(job.key);
2509
+ }
2510
+ }
2511
+ };
2512
+ queues.get(phase).push(job);
2513
+ if (job.key) {
2514
+ keyedJobs.set(job.key, job);
2515
+ }
2516
+ requestFlush();
2517
+ return job.cancel;
2518
+ },
2519
+
2520
+ afterFlush(fn, options = {}) {
2521
+ return api.enqueue("post", fn, options);
2522
+ },
2523
+
2524
+ async flush() {
2525
+ assertActive();
2526
+ if (flushing) {
2527
+ return;
2528
+ }
2529
+ scheduled = false;
2530
+ flushing = true;
2531
+ let depth = 0;
2532
+ try {
2533
+ while (hasJobs()) {
2534
+ depth += 1;
2535
+ if (depth > maxDepth) {
2536
+ throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
2537
+ }
2538
+ for (const phase of phases) {
2539
+ await flushPhase(phase);
2540
+ }
2541
+ }
2542
+ } finally {
2543
+ flushing = false;
2544
+ if (hasJobs()) {
2545
+ requestFlush();
2546
+ }
2547
+ }
2548
+ },
2549
+
2550
+ async flushScope(scope) {
2551
+ assertActive();
2552
+ if (flushing) {
2553
+ return;
2554
+ }
2555
+ scheduled = false;
2556
+ flushing = true;
2557
+ let depth = 0;
2558
+ try {
2559
+ while (hasJobsForScope(scope)) {
2560
+ depth += 1;
2561
+ if (depth > maxDepth) {
2562
+ throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
2563
+ }
2564
+ for (const phase of phases) {
2565
+ await flushPhase(phase, scope);
2566
+ }
2567
+ }
2568
+ } finally {
2569
+ flushing = false;
2570
+ if (hasJobs()) {
2571
+ requestFlush();
2572
+ }
2573
+ }
2574
+ },
2575
+
2576
+ cancelScope(scope) {
2577
+ if (scope === undefined) {
2578
+ return api;
2579
+ }
2580
+ for (const queue of queues.values()) {
2581
+ for (const job of queue) {
2582
+ if (job.scope === scope) {
2583
+ job.cancel();
2584
+ }
2585
+ }
2586
+ }
2587
+ return api;
2588
+ },
2589
+
2590
+ markScopeDestroyed(scope) {
2591
+ if (scope !== undefined) {
2592
+ destroyedScopes.add(scope);
2593
+ api.cancelScope(scope);
2594
+ }
2595
+ return api;
2596
+ },
2597
+
2598
+ inspect() {
2599
+ const counts = {};
2600
+ for (const [phase, queue] of queues) {
2601
+ counts[phase] = queue.filter((job) => !job.canceled).length;
2602
+ }
2603
+ return {
2604
+ strategy,
2605
+ phases: [...phases],
2606
+ pending: counts,
2607
+ scopesDestroyed: destroyedScopes.size,
2608
+ flushing,
2609
+ scheduled
2610
+ };
2611
+ },
2612
+
2613
+ destroy() {
2614
+ destroyed = true;
2615
+ for (const queue of queues.values()) {
2616
+ for (const job of queue) {
2617
+ job.cancel();
2618
+ }
2619
+ queue.length = 0;
2620
+ }
2621
+ keyedJobs.clear();
2622
+ destroyedScopes.clear();
2623
+ }
2624
+ };
2625
+
2626
+ return api;
2627
+
2628
+ function requestFlush() {
2629
+ if (strategy === "manual" || destroyed || flushing || batchDepth > 0 || scheduled) {
2630
+ return;
2631
+ }
2632
+ scheduled = true;
2633
+ scheduleMicrotask(() => {
2634
+ if (!destroyed) {
2635
+ void api.flush();
2636
+ }
2637
+ });
2638
+ }
2639
+
2640
+ async function flushPhase(phase, scope) {
2641
+ const queue = queues.get(phase);
2642
+ const remaining = [];
2643
+ const runnable = [];
2644
+
2645
+ for (const job of queue.splice(0)) {
2646
+ if (job.canceled) {
2647
+ continue;
2648
+ }
2649
+ if (scope !== undefined && job.scope !== scope) {
2650
+ remaining.push(job);
2651
+ continue;
2652
+ }
2653
+ runnable.push(job);
2654
+ }
2655
+
2656
+ queue.push(...remaining);
2657
+
2658
+ for (const job of runnable) {
2659
+ if (job.key) {
2660
+ keyedJobs.delete(job.key);
2661
+ }
2662
+ if (job.canceled || (job.scope !== undefined && destroyedScopes.has(job.scope))) {
2663
+ continue;
2664
+ }
2665
+ try {
2666
+ await job.fn();
2667
+ } catch (error) {
2668
+ if (onError) {
2669
+ onError(error, job);
2670
+ } else {
2671
+ throw error;
2672
+ }
2673
+ }
2674
+ }
2675
+ }
2676
+
2677
+ function hasJobs() {
2678
+ for (const queue of queues.values()) {
2679
+ if (queue.some((job) => !job.canceled)) {
2680
+ return true;
2681
+ }
2682
+ }
2683
+ return false;
2684
+ }
2685
+
2686
+ function hasJobsForScope(scope) {
2687
+ for (const queue of queues.values()) {
2688
+ if (queue.some((job) => !job.canceled && job.scope === scope)) {
2689
+ return true;
2690
+ }
2691
+ }
2692
+ return false;
2693
+ }
2694
+
2695
+ function assertActive() {
2696
+ if (destroyed) {
2697
+ throw new Error("Scheduler has been destroyed.");
2698
+ }
2699
+ }
2700
+
2701
+ function assertPhase(phase) {
2702
+ if (!queues.has(phase)) {
2703
+ throw new Error(`Unknown scheduler phase "${phase}".`);
2704
+ }
2705
+ }
2706
+
2707
+ function scopeKey(scope) {
2708
+ if (scope === undefined) {
2709
+ return "global";
2710
+ }
2711
+ if ((typeof scope === "object" && scope !== null) || typeof scope === "function") {
2712
+ if (!objectScopeIds.has(scope)) {
2713
+ objectScopeIds.set(scope, `scope:${++scopeCounter}`);
2714
+ }
2715
+ return objectScopeIds.get(scope);
2716
+ }
2717
+ return String(scope);
2718
+ }
2719
+ }
2720
+
2721
+ function scheduleMicrotask(fn) {
2722
+ if (typeof queueMicrotask === "function") {
2723
+ queueMicrotask(fn);
2724
+ return;
2725
+ }
2726
+ Promise.resolve().then(fn);
2727
+ }
2728
+
2729
+ function noop() {}
2730
+ return { createScheduler };
2731
+ })();
2732
+
2420
2733
  const __loaderModule = (() => {
2421
2734
  const { renderComponent } = __componentModule;
2422
2735
  const { createHandlerRegistry } = __handlersModule;
2736
+ const { createScheduler } = __schedulerModule;
2423
2737
  const { createSignalRegistry, isSignalRef } = __signalsModule;
2424
2738
  const { matchAttribute, normalizeAttributeConfig, readAttribute } = __attributesModule;
2425
2739
  const inlineBindingPrefix = "__async:inline:";
2426
2740
 
2427
- function Loader({ root, signals, handlers, server, router, cache, attributes } = {}) {
2741
+ function Loader({ root, signals, handlers, server, router, cache, attributes, scheduler } = {}) {
2428
2742
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
2429
2743
  const rootNode = root ?? documentRef;
2430
2744
  const signalRegistry = signals ?? createSignalRegistry();
2431
2745
  const handlerRegistry = handlers ?? createHandlerRegistry();
2746
+ const schedulerInstance = scheduler ?? createScheduler();
2747
+ const ownsScheduler = !scheduler;
2432
2748
  const attributeConfig = normalizeAttributeConfig(attributes);
2433
2749
  const cleanups = new Set();
2434
2750
  const eventBindings = new WeakMap();
@@ -2449,6 +2765,7 @@ const __loaderModule = (() => {
2449
2765
  server,
2450
2766
  router,
2451
2767
  cache,
2768
+ scheduler: schedulerInstance,
2452
2769
  attributes: attributeConfig,
2453
2770
 
2454
2771
  start() {
@@ -2488,6 +2805,7 @@ const __loaderModule = (() => {
2488
2805
  server: api.server,
2489
2806
  router: api.router,
2490
2807
  cache: api.cache,
2808
+ scheduler: schedulerInstance,
2491
2809
  attributes: attributeConfig
2492
2810
  });
2493
2811
  cleanupChildren(target);
@@ -2508,6 +2826,9 @@ const __loaderModule = (() => {
2508
2826
  runCleanup(cleanup);
2509
2827
  }
2510
2828
  cleanups.clear();
2829
+ if (ownsScheduler) {
2830
+ schedulerInstance.destroy();
2831
+ }
2511
2832
  },
2512
2833
 
2513
2834
  _observeVisible(target, fn) {
@@ -2525,13 +2846,14 @@ const __loaderModule = (() => {
2525
2846
  }
2526
2847
  };
2527
2848
 
2528
- signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
2849
+ signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache, scheduler: schedulerInstance });
2529
2850
  api.server?._setContext?.({
2530
2851
  signals: signalRegistry,
2531
2852
  handlers: handlerRegistry,
2532
2853
  loader: api,
2533
2854
  router: api.router,
2534
- cache: api.cache
2855
+ cache: api.cache,
2856
+ scheduler: schedulerInstance
2535
2857
  });
2536
2858
 
2537
2859
  function bindEventAttributes(scope) {
@@ -2563,18 +2885,19 @@ const __loaderModule = (() => {
2563
2885
 
2564
2886
  const listener = async (event) => {
2565
2887
  try {
2566
- await handlerRegistry.run(ref, {
2888
+ await schedulerInstance.batch(() => handlerRegistry.run(ref, {
2567
2889
  signals: signalRegistry,
2568
2890
  handlers: handlerRegistry,
2569
2891
  loader: api,
2570
2892
  server: api.server,
2571
2893
  router: api.router,
2572
2894
  cache: api.cache,
2895
+ scheduler: schedulerInstance,
2573
2896
  event,
2574
2897
  element,
2575
2898
  el: element,
2576
2899
  root: rootNode
2577
- });
2900
+ }));
2578
2901
  } catch (error) {
2579
2902
  dispatchAsyncError(element, error);
2580
2903
  }
@@ -2690,7 +3013,12 @@ const __loaderModule = (() => {
2690
3013
 
2691
3014
  const read = () => readBinding(path, options);
2692
3015
  apply(read());
2693
- addCleanup(subscribeBinding(path, () => apply(read())), element);
3016
+ addCleanup(subscribeBinding(path, () => {
3017
+ schedulerInstance.enqueue("binding", () => apply(read()), {
3018
+ scope: element,
3019
+ key
3020
+ });
3021
+ }), element);
2694
3022
  }
2695
3023
 
2696
3024
  function bindValueWriter(element, path) {
@@ -2751,7 +3079,12 @@ const __loaderModule = (() => {
2751
3079
  const state = {
2752
3080
  id,
2753
3081
  templates,
2754
- cleanup: signalRegistry.subscribe(`${id}.$status`, () => renderBoundary(boundary))
3082
+ cleanup: signalRegistry.subscribe(`${id}.$status`, () => {
3083
+ schedulerInstance.enqueue("binding", () => renderBoundary(boundary), {
3084
+ scope: boundary,
3085
+ key: `boundary:${id}`
3086
+ });
3087
+ })
2755
3088
  };
2756
3089
  boundaryState.set(boundary, state);
2757
3090
  addCleanup(state.cleanup, boundary);
@@ -2791,7 +3124,7 @@ const __loaderModule = (() => {
2791
3124
  }
2792
3125
  mountedElements.add(element);
2793
3126
  for (const ref of refs) {
2794
- runPseudo(element, ref);
3127
+ scheduleLifecycle(element, () => runPseudo(element, ref), `attach:${ref}`);
2795
3128
  }
2796
3129
  }
2797
3130
 
@@ -2804,7 +3137,7 @@ const __loaderModule = (() => {
2804
3137
  continue;
2805
3138
  }
2806
3139
  visibleElements.add(element);
2807
- addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
3140
+ addCleanup(observeVisible(element, () => scheduleLifecycle(element, () => runPseudo(element, ref), `visible:${ref}`)), element);
2808
3141
  }
2809
3142
  }
2810
3143
 
@@ -2828,6 +3161,7 @@ const __loaderModule = (() => {
2828
3161
  server: api.server,
2829
3162
  router: api.router,
2830
3163
  cache: api.cache,
3164
+ scheduler: schedulerInstance,
2831
3165
  element,
2832
3166
  el: element,
2833
3167
  root: rootNode
@@ -2846,10 +3180,13 @@ const __loaderModule = (() => {
2846
3180
  const ownerWindow = target.ownerDocument?.defaultView ?? globalThis;
2847
3181
  const Observer = ownerWindow.IntersectionObserver ?? globalThis.IntersectionObserver;
2848
3182
  if (!Observer) {
2849
- queueMicrotask(() => {
3183
+ schedulerInstance.enqueue("lifecycle", () => {
2850
3184
  if (!destroyed) {
2851
3185
  fn(target);
2852
3186
  }
3187
+ }, {
3188
+ scope: target,
3189
+ key: "visible:fallback"
2853
3190
  });
2854
3191
  return () => {};
2855
3192
  }
@@ -2904,6 +3241,7 @@ const __loaderModule = (() => {
2904
3241
  }
2905
3242
  for (const element of elementsIn(node)) {
2906
3243
  runScopedCleanups(element);
3244
+ schedulerInstance.markScopeDestroyed(element);
2907
3245
  }
2908
3246
  }
2909
3247
 
@@ -2927,6 +3265,13 @@ const __loaderModule = (() => {
2927
3265
  }
2928
3266
  }
2929
3267
 
3268
+ function scheduleLifecycle(element, fn, key) {
3269
+ schedulerInstance.enqueue("lifecycle", fn, {
3270
+ scope: element,
3271
+ key
3272
+ });
3273
+ }
3274
+
2930
3275
  return api;
2931
3276
  }
2932
3277
 
@@ -3257,6 +3602,7 @@ const __partialsModule = (() => {
3257
3602
  const __routerModule = (() => {
3258
3603
  const { Loader } = __loaderModule;
3259
3604
  const { createHandlerRegistry } = __handlersModule;
3605
+ const { createScheduler } = __schedulerModule;
3260
3606
  const { createSignalRegistry } = __signalsModule;
3261
3607
  const { applyServerResult } = __serverModule;
3262
3608
  const { createRegistryStore } = __registryStoreModule;
@@ -3377,12 +3723,15 @@ const __routerModule = (() => {
3377
3723
  partials,
3378
3724
  fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
3379
3725
  routeEndpoint = "/__async/route",
3380
- attributes
3726
+ attributes,
3727
+ scheduler
3381
3728
  } = {}) {
3382
3729
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
3383
3730
  const rootNode = root ?? documentRef;
3384
3731
  const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
3385
3732
  const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
3733
+ const schedulerInstance = scheduler ?? loader?.scheduler ?? createScheduler();
3734
+ const ownsScheduler = !scheduler && !loader?.scheduler;
3386
3735
  const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
3387
3736
  const loaderInstance =
3388
3737
  loader ??
@@ -3392,6 +3741,7 @@ const __routerModule = (() => {
3392
3741
  handlers: handlerRegistry,
3393
3742
  server,
3394
3743
  cache,
3744
+ scheduler: schedulerInstance,
3395
3745
  attributes: attributeConfig
3396
3746
  });
3397
3747
  const ownsLoader = !loader;
@@ -3411,12 +3761,13 @@ const __routerModule = (() => {
3411
3761
  server,
3412
3762
  cache,
3413
3763
  partials,
3764
+ scheduler: schedulerInstance,
3414
3765
  attributes: attributeConfig,
3415
3766
 
3416
3767
  start() {
3417
3768
  assertActive();
3418
3769
  loaderInstance.router = api;
3419
- signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache });
3770
+ signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache, scheduler: schedulerInstance });
3420
3771
  if (ownsLoader) {
3421
3772
  loaderInstance.start();
3422
3773
  }
@@ -3480,6 +3831,9 @@ const __routerModule = (() => {
3480
3831
  cleanup();
3481
3832
  }
3482
3833
  cleanups.clear();
3834
+ if (ownsScheduler) {
3835
+ schedulerInstance.destroy();
3836
+ }
3483
3837
  }
3484
3838
  };
3485
3839
 
@@ -3585,13 +3939,16 @@ const __routerModule = (() => {
3585
3939
  loader: loaderInstance,
3586
3940
  router: api,
3587
3941
  cache,
3942
+ scheduler: schedulerInstance,
3588
3943
  abort: navigation?.abort
3589
3944
  });
3945
+ await schedulerInstance.flush();
3590
3946
  if (!isActiveNavigation(navigation)) {
3591
3947
  return;
3592
3948
  }
3593
3949
  if (result?.html != null && !result.boundary && !result.redirect) {
3594
3950
  loaderInstance.swap(boundary, result.html);
3951
+ await schedulerInstance.flush();
3595
3952
  }
3596
3953
  if (result?.redirect || options.history === false) {
3597
3954
  return;
@@ -3636,6 +3993,7 @@ const __routerModule = (() => {
3636
3993
  loader: loaderInstance,
3637
3994
  server,
3638
3995
  cache,
3996
+ scheduler: schedulerInstance,
3639
3997
  abort: navigation?.abort
3640
3998
  };
3641
3999
  }
@@ -3822,15 +4180,17 @@ const __appModule = (() => {
3822
4180
  const { Loader } = __loaderModule;
3823
4181
  const { createPartialRegistry } = __partialsModule;
3824
4182
  const { createRouteRegistry, createRouter } = __routerModule;
3825
- const { createServerRegistry } = __serverModule;
4183
+ const { createScheduler } = __schedulerModule;
4184
+ const { createServerNamespace } = __serverModule;
3826
4185
  const { createSignal, createSignalRegistry } = __signalsModule;
3827
4186
  const { createRegistryStore } = __registryStoreModule;
3828
4187
  const { attributeName, normalizeAttributeConfig } = __attributesModule;
3829
4188
  const registryTypes = new Set(["signal", "handler", "server", "partial", "route", "component"]);
3830
4189
 
3831
- function defineApp(initial) {
4190
+ function defineApp(initial, options = {}) {
3832
4191
  const registry = createRegistryStore(undefined, { target: "browser" });
3833
4192
  const runtimes = new Set();
4193
+ const createRuntime = options.createRuntime ?? createApp;
3834
4194
 
3835
4195
  const app = {
3836
4196
  registry,
@@ -3849,7 +4209,7 @@ const __appModule = (() => {
3849
4209
  },
3850
4210
 
3851
4211
  start(options = {}) {
3852
- const runtime = createApp(app, options).start();
4212
+ const runtime = createRuntime(app, options).start();
3853
4213
  app.runtime = runtime;
3854
4214
  return runtime;
3855
4215
  },
@@ -3874,13 +4234,18 @@ const __appModule = (() => {
3874
4234
  function createApp(appOrDefinition = Async, options = {}) {
3875
4235
  const app = isAppHub(appOrDefinition) ? appOrDefinition : defineApp(appOrDefinition ?? {});
3876
4236
  const target = options.target ?? "browser";
4237
+ const scheduler = options.scheduler ?? options.loader?.scheduler ?? createScheduler({
4238
+ strategy: target === "server" ? "manual" : "microtask"
4239
+ });
4240
+ const ownsScheduler = !options.scheduler && !options.loader?.scheduler;
3877
4241
  const attributes = normalizeAttributeConfig(options.attributes);
3878
4242
  const registry = options.registry ?? app.registry.view({ target });
3879
4243
  const signals = options.signals ?? createSignalRegistry(undefined, { registry, type: "signal" });
3880
4244
  const handlers = options.handlers ?? createHandlerRegistry(undefined, { registry, type: "handler" });
3881
4245
  const serverCache = createCacheRegistry(undefined, { registry, type: "cache.server" });
3882
4246
  const browserCache = createCacheRegistry(undefined, { registry, type: "cache.browser" });
3883
- const server = options.server ?? createServerRegistry(undefined, { registry, type: "server" });
4247
+ const serverFactory = options.serverFactory ?? createServerReferenceRegistry;
4248
+ const server = options.server ?? serverFactory(undefined, { registry, type: "server" });
3884
4249
  const partials = options.partials ?? createPartialRegistry(undefined, { registry, type: "partial" });
3885
4250
  const routes = options.routes ?? createRouteRegistry(undefined, { registry, type: "route" });
3886
4251
  const components = options.components ?? createComponentRegistry(undefined, { registry, type: "component" });
@@ -3908,6 +4273,7 @@ const __appModule = (() => {
3908
4273
  },
3909
4274
  loader,
3910
4275
  router,
4276
+ scheduler,
3911
4277
  attributes,
3912
4278
 
3913
4279
  start() {
@@ -3924,12 +4290,13 @@ const __appModule = (() => {
3924
4290
  handlers,
3925
4291
  server,
3926
4292
  cache: browserCache,
4293
+ scheduler,
3927
4294
  attributes
3928
4295
  });
3929
4296
  runtime.loader = loader;
3930
4297
 
3931
4298
  configureServerContext({ cache: browserCache });
3932
- signals._setContext?.({ server, loader, cache: browserCache });
4299
+ signals._setContext?.({ server, loader, cache: browserCache, scheduler });
3933
4300
 
3934
4301
  loader.start();
3935
4302
 
@@ -3945,6 +4312,7 @@ const __appModule = (() => {
3945
4312
  server,
3946
4313
  cache: browserCache,
3947
4314
  partials,
4315
+ scheduler,
3948
4316
  fetch: options.fetch,
3949
4317
  routeEndpoint: options.routeEndpoint,
3950
4318
  attributes
@@ -3956,7 +4324,7 @@ const __appModule = (() => {
3956
4324
  }
3957
4325
  } else {
3958
4326
  configureServerContext({ cache: serverCache });
3959
- signals._setContext?.({ server, cache: serverCache });
4327
+ signals._setContext?.({ server, cache: serverCache, scheduler });
3960
4328
  }
3961
4329
 
3962
4330
  return runtime;
@@ -3970,9 +4338,10 @@ const __appModule = (() => {
3970
4338
  async render(url) {
3971
4339
  assertActive();
3972
4340
  configureServerContext({ cache: serverCache });
3973
- signals._setContext?.({ server, cache: serverCache });
4341
+ signals._setContext?.({ server, cache: serverCache, scheduler });
3974
4342
  const matched = routes.match(url);
3975
4343
  if (!matched) {
4344
+ await scheduler.flush();
3976
4345
  return {
3977
4346
  html: renderDocument("", { status: 404, signals, browserCache, boundary: options.boundary ?? "route", attributes }),
3978
4347
  status: 404,
@@ -3992,8 +4361,8 @@ const __appModule = (() => {
3992
4361
  cache: serverCache,
3993
4362
  browserCache,
3994
4363
  partials,
3995
- request: options.request,
3996
- locals: options.locals
4364
+ scheduler,
4365
+ ...currentRequestContext()
3997
4366
  })
3998
4367
  : { html: "" };
3999
4368
 
@@ -4006,6 +4375,8 @@ const __appModule = (() => {
4006
4375
  browserCache.restore(result.cache.browser);
4007
4376
  }
4008
4377
 
4378
+ await scheduler.flush();
4379
+
4009
4380
  const status = result.status ?? 200;
4010
4381
  return {
4011
4382
  html: renderDocument(result.html, { status, signals, browserCache, boundary: result.boundary ?? options.boundary ?? "route", attributes }),
@@ -4024,6 +4395,9 @@ const __appModule = (() => {
4024
4395
  router?.destroy?.();
4025
4396
  loader?.destroy?.();
4026
4397
  signals.destroy?.();
4398
+ if (ownsScheduler) {
4399
+ scheduler.destroy();
4400
+ }
4027
4401
  },
4028
4402
 
4029
4403
  _applyUse(normalized) {
@@ -4044,11 +4418,23 @@ const __appModule = (() => {
4044
4418
  loader,
4045
4419
  router,
4046
4420
  cache,
4047
- request: options.request,
4048
- locals: options.locals
4421
+ scheduler,
4422
+ requestContext: options.requestContext,
4423
+ ...currentRequestContext()
4049
4424
  });
4050
4425
  }
4051
4426
 
4427
+ function currentRequestContext() {
4428
+ const context = readRequestContextLike(options.requestContext);
4429
+ return {
4430
+ requestContext: context,
4431
+ request: context.request ?? options.request,
4432
+ headers: context.headers ?? options.headers,
4433
+ cookies: context.cookies ?? options.cookies,
4434
+ locals: context.locals ?? options.locals
4435
+ };
4436
+ }
4437
+
4052
4438
  function assertActive() {
4053
4439
  if (destroyed) {
4054
4440
  throw new Error("Async app runtime has been destroyed.");
@@ -4202,6 +4588,77 @@ const __appModule = (() => {
4202
4588
  }
4203
4589
  }
4204
4590
 
4591
+ function createServerReferenceRegistry(initialMap = {}, options = {}) {
4592
+ const registry = options.registry ?? createRegistryStore();
4593
+ const type = options.type ?? "server";
4594
+ const defaults = {};
4595
+
4596
+ const reference = {
4597
+ registry,
4598
+
4599
+ register(id, value) {
4600
+ registry.register(type, id, value);
4601
+ return id;
4602
+ },
4603
+
4604
+ registerMany(map) {
4605
+ for (const [id, value] of Object.entries(map ?? {})) {
4606
+ reference.register(id, value);
4607
+ }
4608
+ return reference;
4609
+ },
4610
+
4611
+ unregister(id) {
4612
+ return registry.unregister(type, id);
4613
+ },
4614
+
4615
+ resolve() {
4616
+ return undefined;
4617
+ },
4618
+
4619
+ async run(id) {
4620
+ throw new Error(`Server command "${id}" cannot run without a server proxy or server registry.`);
4621
+ },
4622
+
4623
+ keys() {
4624
+ return registry.keys(type);
4625
+ },
4626
+
4627
+ entries() {
4628
+ return registry.entries(type);
4629
+ },
4630
+
4631
+ inspect() {
4632
+ return registry.entries(type);
4633
+ },
4634
+
4635
+ _setContext(context = {}) {
4636
+ Object.assign(defaults, context);
4637
+ return reference;
4638
+ },
4639
+
4640
+ _adoptMany() {
4641
+ return reference;
4642
+ }
4643
+ };
4644
+
4645
+ reference.registerMany(initialMap);
4646
+ return createServerNamespace((id, args, context) => reference.run(id, args, context), reference, () => defaults);
4647
+ }
4648
+
4649
+ function readRequestContextLike(store) {
4650
+ if (!store) {
4651
+ return {};
4652
+ }
4653
+ if (typeof store.get === "function") {
4654
+ return store.get() ?? {};
4655
+ }
4656
+ if (typeof store.getStore === "function") {
4657
+ return store.getStore() ?? {};
4658
+ }
4659
+ return {};
4660
+ }
4661
+
4205
4662
  function normalizeEntries(type, entries = {}) {
4206
4663
  if (type !== "signal") {
4207
4664
  return { ...(entries ?? {}) };
@@ -4310,12 +4767,15 @@ const { createRouteRegistry: createRouteRegistry } = __routerModule;
4310
4767
  const { createRouter: createRouter } = __routerModule;
4311
4768
  const { defineRoute: defineRoute } = __routerModule;
4312
4769
  const { route: route } = __routerModule;
4770
+ const { createScheduler: createScheduler } = __schedulerModule;
4771
+ const { applyServerResult: applyServerResult } = __serverModule;
4313
4772
  const { createServerProxy: createServerProxy } = __serverModule;
4314
- const { createServerRegistry: createServerRegistry } = __serverModule;
4773
+ const { resolveServerCommandArguments: resolveServerCommandArguments } = __serverModule;
4774
+ const { unwrapServerResult: unwrapServerResult } = __serverModule;
4315
4775
  const { computed: computed } = __signalsModule;
4316
4776
  const { createSignal: createSignal } = __signalsModule;
4317
4777
  const { createSignalRegistry: createSignalRegistry } = __signalsModule;
4318
4778
  const { effect: effect } = __signalsModule;
4319
4779
  const { signal: signal } = __signalsModule;
4320
4780
 
4321
- 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 };
4781
+ export { asyncSignal, Async, createApp, defineApp, readSnapshot, attributeName, defineAttributeConfig, createCacheRegistry, defineCache, component, createComponentRegistry, defineComponent, delay, createHandlerRegistry, html, Loader, AsyncLoader, createPartialRegistry, createRegistryStore, createRouteRegistry, createRouter, defineRoute, route, createScheduler, applyServerResult, createServerProxy, resolveServerCommandArguments, unwrapServerResult, computed, createSignal, createSignalRegistry, effect, signal };