@async/framework 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +93 -15
- package/{framework.d.ts → browser.d.ts} +71 -4
- package/{framework.js → browser.js} +791 -147
- package/browser.min.js +1 -0
- package/browser.ts +4781 -0
- package/{framework.umd.js → browser.umd.js} +791 -147
- package/browser.umd.min.js +1 -0
- package/package.json +45 -30
- package/server.d.ts +640 -0
- package/src/app.js +143 -12
- package/src/async-signal.js +32 -4
- package/src/browser.js +15 -0
- package/src/cache.js +27 -3
- package/src/component.js +42 -7
- package/src/index.js +5 -2
- package/src/loader.js +42 -10
- package/src/request-context.js +40 -0
- package/src/router.js +113 -16
- package/src/scheduler.js +296 -0
- package/src/server-entry.js +20 -0
- package/src/server-registry.js +97 -0
- package/src/server.js +49 -89
- package/src/signals.js +38 -6
- package/framework.min.js +0 -3648
- package/framework.ts +0 -3
- package/framework.umd.min.js +0 -3671
|
@@ -105,7 +105,8 @@
|
|
|
105
105
|
router: context.router,
|
|
106
106
|
loader: context.loader,
|
|
107
107
|
cache: context.cache,
|
|
108
|
-
abort: activeAbort
|
|
108
|
+
abort: activeAbort,
|
|
109
|
+
scheduler: context.scheduler
|
|
109
110
|
});
|
|
110
111
|
}
|
|
111
112
|
return server;
|
|
@@ -119,6 +120,9 @@
|
|
|
119
120
|
get cache() {
|
|
120
121
|
return registry._context?.().cache;
|
|
121
122
|
},
|
|
123
|
+
get scheduler() {
|
|
124
|
+
return registry._context?.().scheduler;
|
|
125
|
+
},
|
|
122
126
|
get version() {
|
|
123
127
|
return runVersion;
|
|
124
128
|
},
|
|
@@ -195,11 +199,20 @@
|
|
|
195
199
|
_bindRegistry(nextRegistry, nextId) {
|
|
196
200
|
registry = nextRegistry;
|
|
197
201
|
registeredId = nextId;
|
|
198
|
-
|
|
202
|
+
const start = () => {
|
|
199
203
|
if (registry === nextRegistry && status === "idle") {
|
|
200
204
|
state.refresh();
|
|
201
205
|
}
|
|
202
|
-
}
|
|
206
|
+
};
|
|
207
|
+
const scheduler = registry._context?.().scheduler;
|
|
208
|
+
if (scheduler) {
|
|
209
|
+
scheduler.enqueue("async", start, {
|
|
210
|
+
scope: registeredId,
|
|
211
|
+
key: `asyncSignal:${registeredId}:initial`
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
queueMicrotask(start);
|
|
215
|
+
}
|
|
203
216
|
},
|
|
204
217
|
|
|
205
218
|
_dispose() {
|
|
@@ -235,11 +248,26 @@
|
|
|
235
248
|
for (const dependency of dependencies) {
|
|
236
249
|
const dependencyId = String(dependency).split(".")[0];
|
|
237
250
|
if (dependencyId && dependencyId !== registeredId) {
|
|
238
|
-
dependencyCleanups.add(registry.subscribe(dependency, () =>
|
|
251
|
+
dependencyCleanups.add(registry.subscribe(dependency, () => scheduleRefresh()));
|
|
239
252
|
}
|
|
240
253
|
}
|
|
241
254
|
}
|
|
242
255
|
|
|
256
|
+
function scheduleRefresh() {
|
|
257
|
+
if (activeAbort && !activeAbort.aborted) {
|
|
258
|
+
activeAbort.cancel(new Error(`Async signal "${registeredId}" dependency changed.`));
|
|
259
|
+
}
|
|
260
|
+
const scheduler = registry?._context?.().scheduler;
|
|
261
|
+
if (!scheduler) {
|
|
262
|
+
state.refresh();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
scheduler.enqueue("async", () => state.refresh(), {
|
|
266
|
+
scope: registeredId,
|
|
267
|
+
key: `asyncSignal:${registeredId}:refresh`
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
243
271
|
function notify() {
|
|
244
272
|
for (const subscriber of [...subscribers]) {
|
|
245
273
|
subscriber(state);
|
|
@@ -547,6 +575,7 @@
|
|
|
547
575
|
const registryStore = registry ?? createRegistryStore();
|
|
548
576
|
const definitions = registryStore._map(type);
|
|
549
577
|
const entries = registryStore._map(`${type}.entries`);
|
|
578
|
+
const pending = new Map();
|
|
550
579
|
|
|
551
580
|
const registryApi = attachRegistryInspection({
|
|
552
581
|
register(id, definition = defineCache()) {
|
|
@@ -608,19 +637,37 @@
|
|
|
608
637
|
if (cached !== undefined) {
|
|
609
638
|
return cached;
|
|
610
639
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
640
|
+
if (pending.has(key)) {
|
|
641
|
+
return pending.get(key);
|
|
642
|
+
}
|
|
643
|
+
let promise;
|
|
644
|
+
promise = Promise.resolve()
|
|
645
|
+
.then(fn)
|
|
646
|
+
.then((value) => {
|
|
647
|
+
if (pending.get(key) === promise) {
|
|
648
|
+
registryApi.set(key, value, options);
|
|
649
|
+
}
|
|
650
|
+
return value;
|
|
651
|
+
})
|
|
652
|
+
.finally(() => {
|
|
653
|
+
if (pending.get(key) === promise) {
|
|
654
|
+
pending.delete(key);
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
pending.set(key, promise);
|
|
658
|
+
return promise;
|
|
614
659
|
},
|
|
615
660
|
|
|
616
661
|
delete(key) {
|
|
617
662
|
assertKey(key);
|
|
663
|
+
pending.delete(key);
|
|
618
664
|
return entries.delete(key);
|
|
619
665
|
},
|
|
620
666
|
|
|
621
667
|
clear(prefix) {
|
|
622
668
|
if (prefix === undefined) {
|
|
623
669
|
entries.clear();
|
|
670
|
+
pending.clear();
|
|
624
671
|
return registryApi;
|
|
625
672
|
}
|
|
626
673
|
for (const key of [...entries.keys()]) {
|
|
@@ -628,6 +675,11 @@
|
|
|
628
675
|
entries.delete(key);
|
|
629
676
|
}
|
|
630
677
|
}
|
|
678
|
+
for (const key of [...pending.keys()]) {
|
|
679
|
+
if (key.startsWith(prefix)) {
|
|
680
|
+
pending.delete(key);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
631
683
|
return registryApi;
|
|
632
684
|
},
|
|
633
685
|
|
|
@@ -852,7 +904,8 @@
|
|
|
852
904
|
server: registry._context?.().server,
|
|
853
905
|
router: registry._context?.().router,
|
|
854
906
|
loader: registry._context?.().loader,
|
|
855
|
-
cache: registry._context?.().cache
|
|
907
|
+
cache: registry._context?.().cache,
|
|
908
|
+
scheduler: registry._context?.().scheduler
|
|
856
909
|
}));
|
|
857
910
|
});
|
|
858
911
|
}
|
|
@@ -880,6 +933,8 @@
|
|
|
880
933
|
const registryCleanups = new Map();
|
|
881
934
|
const runtimeContext = {};
|
|
882
935
|
const boundEntries = new Set();
|
|
936
|
+
let subscriptionCounter = 0;
|
|
937
|
+
let effectCounter = 0;
|
|
883
938
|
|
|
884
939
|
const registry = attachRegistryInspection({
|
|
885
940
|
register(id, signalLike) {
|
|
@@ -955,17 +1010,21 @@
|
|
|
955
1010
|
return createRef(registry, id);
|
|
956
1011
|
},
|
|
957
1012
|
|
|
958
|
-
subscribe(path, fn) {
|
|
1013
|
+
subscribe(path, fn, options = {}) {
|
|
959
1014
|
if (typeof fn !== "function") {
|
|
960
1015
|
throw new TypeError("subscribe(path, fn) requires a function.");
|
|
961
1016
|
}
|
|
962
1017
|
const parsed = parsePath(path, entries);
|
|
963
1018
|
const entry = requireEntry(entries, parsed.id);
|
|
1019
|
+
const subscriptionId = ++subscriptionCounter;
|
|
964
1020
|
return entry.subscribe(() => {
|
|
965
|
-
fn(registry.get(parsed.path), {
|
|
1021
|
+
scheduleCallback(() => fn(registry.get(parsed.path), {
|
|
966
1022
|
id: parsed.id,
|
|
967
1023
|
path: parsed.path,
|
|
968
1024
|
signal: entry
|
|
1025
|
+
}), {
|
|
1026
|
+
...options,
|
|
1027
|
+
key: options.key ?? `signal:${parsed.path}:${subscriptionId}`
|
|
969
1028
|
});
|
|
970
1029
|
});
|
|
971
1030
|
},
|
|
@@ -983,10 +1042,12 @@
|
|
|
983
1042
|
return registry.ref(id);
|
|
984
1043
|
},
|
|
985
1044
|
|
|
986
|
-
effect(fn) {
|
|
1045
|
+
effect(fn, options = {}) {
|
|
987
1046
|
let cleanup;
|
|
988
1047
|
let dependencyCleanups = [];
|
|
989
1048
|
let stopped = false;
|
|
1049
|
+
const scheduler = options.scheduler;
|
|
1050
|
+
const effectId = ++effectCounter;
|
|
990
1051
|
|
|
991
1052
|
const run = () => {
|
|
992
1053
|
if (stopped) {
|
|
@@ -1005,10 +1066,22 @@
|
|
|
1005
1066
|
server: runtimeContext.server,
|
|
1006
1067
|
router: runtimeContext.router,
|
|
1007
1068
|
loader: runtimeContext.loader,
|
|
1008
|
-
cache: runtimeContext.cache
|
|
1069
|
+
cache: runtimeContext.cache,
|
|
1070
|
+
scheduler: runtimeContext.scheduler
|
|
1009
1071
|
}));
|
|
1010
1072
|
cleanup = outcome.value;
|
|
1011
|
-
dependencyCleanups = outcome.dependencies.map((dependency) => registry.subscribe(dependency,
|
|
1073
|
+
dependencyCleanups = outcome.dependencies.map((dependency) => registry.subscribe(dependency, scheduleRun));
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
const scheduleRun = () => {
|
|
1077
|
+
if (!scheduler) {
|
|
1078
|
+
run();
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
scheduler.enqueue(options.phase ?? "effect", run, {
|
|
1082
|
+
scope: options.scope,
|
|
1083
|
+
key: options.key ?? `effect:${effectId}`
|
|
1084
|
+
});
|
|
1012
1085
|
};
|
|
1013
1086
|
|
|
1014
1087
|
run();
|
|
@@ -1085,6 +1158,17 @@
|
|
|
1085
1158
|
registryCleanups.set(id, cleanup);
|
|
1086
1159
|
}
|
|
1087
1160
|
}
|
|
1161
|
+
|
|
1162
|
+
function scheduleCallback(fn, options = {}) {
|
|
1163
|
+
const scheduler = options.scheduler;
|
|
1164
|
+
if (!scheduler || options.phase === "sync") {
|
|
1165
|
+
return fn();
|
|
1166
|
+
}
|
|
1167
|
+
return scheduler.enqueue(options.phase ?? "effect", fn, {
|
|
1168
|
+
scope: options.scope,
|
|
1169
|
+
key: options.key
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1088
1172
|
}
|
|
1089
1173
|
|
|
1090
1174
|
function normalizeSignal(signalLike) {
|
|
@@ -1551,10 +1635,15 @@
|
|
|
1551
1635
|
html,
|
|
1552
1636
|
attach(target) {
|
|
1553
1637
|
for (const hook of attachHooks) {
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1638
|
+
runtime.scheduler?.enqueue("lifecycle", () => {
|
|
1639
|
+
const cleanup = hook(target);
|
|
1640
|
+
if (typeof cleanup === "function") {
|
|
1641
|
+
cleanups.push(cleanup);
|
|
1642
|
+
}
|
|
1643
|
+
}, {
|
|
1644
|
+
scope,
|
|
1645
|
+
key: `attach:${attachHooks.indexOf(hook)}`
|
|
1646
|
+
}) ?? runAttachHook(hook, target);
|
|
1558
1647
|
}
|
|
1559
1648
|
},
|
|
1560
1649
|
mount(target) {
|
|
@@ -1562,7 +1651,17 @@
|
|
|
1562
1651
|
},
|
|
1563
1652
|
visible(target, observeVisible) {
|
|
1564
1653
|
for (const hook of visibleHooks) {
|
|
1565
|
-
const cleanup = observeVisible(target,
|
|
1654
|
+
const cleanup = observeVisible(target, () => {
|
|
1655
|
+
runtime.scheduler?.enqueue("lifecycle", () => {
|
|
1656
|
+
const hookCleanup = hook(target);
|
|
1657
|
+
if (typeof hookCleanup === "function") {
|
|
1658
|
+
cleanups.push(hookCleanup);
|
|
1659
|
+
}
|
|
1660
|
+
}, {
|
|
1661
|
+
scope,
|
|
1662
|
+
key: `visible:${visibleHooks.indexOf(hook)}`
|
|
1663
|
+
}) ?? runVisibleHook(hook, target);
|
|
1664
|
+
});
|
|
1566
1665
|
if (typeof cleanup === "function") {
|
|
1567
1666
|
cleanups.push(cleanup);
|
|
1568
1667
|
}
|
|
@@ -1572,6 +1671,7 @@
|
|
|
1572
1671
|
while (destroyHooks.length > 0) {
|
|
1573
1672
|
destroyHooks.pop()?.();
|
|
1574
1673
|
}
|
|
1674
|
+
runtime.scheduler?.markScopeDestroyed(scope);
|
|
1575
1675
|
while (cleanups.length > 0) {
|
|
1576
1676
|
cleanups.pop()?.();
|
|
1577
1677
|
}
|
|
@@ -1580,10 +1680,24 @@
|
|
|
1580
1680
|
}
|
|
1581
1681
|
}
|
|
1582
1682
|
};
|
|
1683
|
+
|
|
1684
|
+
function runAttachHook(hook, target) {
|
|
1685
|
+
const cleanup = hook(target);
|
|
1686
|
+
if (typeof cleanup === "function") {
|
|
1687
|
+
cleanups.push(cleanup);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function runVisibleHook(hook, target) {
|
|
1692
|
+
const cleanup = hook(target);
|
|
1693
|
+
if (typeof cleanup === "function") {
|
|
1694
|
+
cleanups.push(cleanup);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1583
1697
|
}
|
|
1584
1698
|
|
|
1585
1699
|
function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks, renderScopedTemplate }) {
|
|
1586
|
-
const { signals, handlers, loader, server, router, cache } = runtime;
|
|
1700
|
+
const { signals, handlers, loader, server, router, cache, scheduler } = runtime;
|
|
1587
1701
|
const generatedHandlers = new WeakMap();
|
|
1588
1702
|
let generatedHandlerCounter = 0;
|
|
1589
1703
|
let generatedSignalCounter = 0;
|
|
@@ -1595,6 +1709,7 @@
|
|
|
1595
1709
|
server,
|
|
1596
1710
|
router,
|
|
1597
1711
|
cache,
|
|
1712
|
+
scheduler,
|
|
1598
1713
|
|
|
1599
1714
|
signal(name, initial) {
|
|
1600
1715
|
if (arguments.length === 1) {
|
|
@@ -1639,7 +1754,11 @@
|
|
|
1639
1754
|
},
|
|
1640
1755
|
|
|
1641
1756
|
effect(fn) {
|
|
1642
|
-
const cleanup = signals.effect(() => fn.call(context)
|
|
1757
|
+
const cleanup = signals.effect(() => fn.call(context), {
|
|
1758
|
+
scheduler,
|
|
1759
|
+
phase: "effect",
|
|
1760
|
+
scope
|
|
1761
|
+
});
|
|
1643
1762
|
cleanups.push(cleanup);
|
|
1644
1763
|
return cleanup;
|
|
1645
1764
|
},
|
|
@@ -1761,90 +1880,9 @@
|
|
|
1761
1880
|
})();
|
|
1762
1881
|
|
|
1763
1882
|
const __serverModule = (() => {
|
|
1764
|
-
const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
|
|
1765
1883
|
const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
const registryStore = options.registry ?? createRegistryStore();
|
|
1769
|
-
const type = options.type ?? "server";
|
|
1770
|
-
const entries = registryStore._map(type);
|
|
1771
|
-
const defaults = {};
|
|
1772
|
-
|
|
1773
|
-
const registry = attachRegistryInspection({
|
|
1774
|
-
register(id, fn) {
|
|
1775
|
-
assertServerId(id);
|
|
1776
|
-
if (typeof fn !== "function") {
|
|
1777
|
-
throw new TypeError(`Server function "${id}" must be a function.`);
|
|
1778
|
-
}
|
|
1779
|
-
if (entries.has(id)) {
|
|
1780
|
-
throw new Error(`Server function "${id}" is already registered.`);
|
|
1781
|
-
}
|
|
1782
|
-
entries.set(id, fn);
|
|
1783
|
-
return id;
|
|
1784
|
-
},
|
|
1785
|
-
|
|
1786
|
-
registerMany(map) {
|
|
1787
|
-
for (const [id, fn] of Object.entries(map ?? {})) {
|
|
1788
|
-
registry.register(id, fn);
|
|
1789
|
-
}
|
|
1790
|
-
return registry;
|
|
1791
|
-
},
|
|
1792
|
-
|
|
1793
|
-
unregister(id) {
|
|
1794
|
-
assertServerId(id);
|
|
1795
|
-
return entries.delete(id);
|
|
1796
|
-
},
|
|
1797
|
-
|
|
1798
|
-
resolve(id) {
|
|
1799
|
-
assertServerId(id);
|
|
1800
|
-
return entries.get(id);
|
|
1801
|
-
},
|
|
1802
|
-
|
|
1803
|
-
async run(id, args = [], context = {}) {
|
|
1804
|
-
assertServerId(id);
|
|
1805
|
-
const fn = registry.resolve(id);
|
|
1806
|
-
if (!fn) {
|
|
1807
|
-
throw new Error(`Server function "${id}" is not registered.`);
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
let runContext;
|
|
1811
|
-
const server = createServerNamespace((childId, childArgs, childContext = {}) => {
|
|
1812
|
-
return registry.run(childId, childArgs, { ...runContext, ...childContext });
|
|
1813
|
-
}, {}, () => runContext);
|
|
1814
|
-
|
|
1815
|
-
const mergedContext = {
|
|
1816
|
-
...defaults,
|
|
1817
|
-
...context,
|
|
1818
|
-
cache: defaults.cache ?? context.cache
|
|
1819
|
-
};
|
|
1820
|
-
|
|
1821
|
-
runContext = {
|
|
1822
|
-
...mergedContext,
|
|
1823
|
-
id,
|
|
1824
|
-
args,
|
|
1825
|
-
input: mergedContext.input,
|
|
1826
|
-
signals: createSignalReader(mergedContext.signals),
|
|
1827
|
-
abort: mergedContext.abort,
|
|
1828
|
-
cache: mergedContext.cache,
|
|
1829
|
-
server
|
|
1830
|
-
};
|
|
1831
|
-
|
|
1832
|
-
return fn.call(runContext, ...args);
|
|
1833
|
-
},
|
|
1834
|
-
|
|
1835
|
-
_setContext(context = {}) {
|
|
1836
|
-
Object.assign(defaults, context);
|
|
1837
|
-
return registry;
|
|
1838
|
-
},
|
|
1839
|
-
|
|
1840
|
-
_adoptMany() {
|
|
1841
|
-
return registry;
|
|
1842
|
-
}
|
|
1843
|
-
}, registryStore, type);
|
|
1844
|
-
|
|
1845
|
-
registry.registerMany(initialMap);
|
|
1846
|
-
return createServerNamespace((id, args, context) => registry.run(id, args, context), registry, () => defaults);
|
|
1847
|
-
}
|
|
1884
|
+
const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
|
|
1885
|
+
const appliedServerValues = new WeakSet();
|
|
1848
1886
|
|
|
1849
1887
|
function createServerProxy({
|
|
1850
1888
|
endpoint = "/__async/server",
|
|
@@ -1853,13 +1891,14 @@
|
|
|
1853
1891
|
loader,
|
|
1854
1892
|
router,
|
|
1855
1893
|
cache,
|
|
1894
|
+
scheduler,
|
|
1856
1895
|
headers = {}
|
|
1857
1896
|
} = {}) {
|
|
1858
1897
|
if (typeof fetchImpl !== "function") {
|
|
1859
1898
|
throw new TypeError("createServerProxy(...) requires fetch to be available.");
|
|
1860
1899
|
}
|
|
1861
1900
|
|
|
1862
|
-
const defaults = { signals, loader, router, cache };
|
|
1901
|
+
const defaults = { signals, loader, router, cache, scheduler };
|
|
1863
1902
|
|
|
1864
1903
|
async function run(id, args = [], context = {}) {
|
|
1865
1904
|
assertServerId(id);
|
|
@@ -1869,6 +1908,7 @@
|
|
|
1869
1908
|
input: context.input ?? defaultInput(runContext),
|
|
1870
1909
|
signals: context.signalValues ?? snapshotSignalPaths(context.signalPaths, runContext.signals)
|
|
1871
1910
|
};
|
|
1911
|
+
assertJsonTransportable(body);
|
|
1872
1912
|
|
|
1873
1913
|
const response = await fetchImpl(joinEndpoint(endpoint, id), {
|
|
1874
1914
|
method: "POST",
|
|
@@ -1886,7 +1926,7 @@
|
|
|
1886
1926
|
|
|
1887
1927
|
const result = await readServerResponse(response);
|
|
1888
1928
|
await applyServerResult(result, runContext);
|
|
1889
|
-
return unwrapServerResult(result);
|
|
1929
|
+
return markAppliedServerValue(unwrapServerResult(result));
|
|
1890
1930
|
}
|
|
1891
1931
|
|
|
1892
1932
|
return createServerNamespace(run, {
|
|
@@ -1921,6 +1961,9 @@
|
|
|
1921
1961
|
if (!isServerEnvelope(result)) {
|
|
1922
1962
|
return result;
|
|
1923
1963
|
}
|
|
1964
|
+
if (result[appliedServerResult] || appliedServerValues.has(result)) {
|
|
1965
|
+
return result;
|
|
1966
|
+
}
|
|
1924
1967
|
|
|
1925
1968
|
if (result.signals && context.signals) {
|
|
1926
1969
|
for (const [path, value] of Object.entries(result.signals)) {
|
|
@@ -1944,6 +1987,12 @@
|
|
|
1944
1987
|
throw toError(result.error);
|
|
1945
1988
|
}
|
|
1946
1989
|
|
|
1990
|
+
Object.defineProperty(result, appliedServerResult, {
|
|
1991
|
+
configurable: true,
|
|
1992
|
+
enumerable: false,
|
|
1993
|
+
value: true
|
|
1994
|
+
});
|
|
1995
|
+
|
|
1947
1996
|
return result;
|
|
1948
1997
|
}
|
|
1949
1998
|
|
|
@@ -1954,6 +2003,13 @@
|
|
|
1954
2003
|
return result;
|
|
1955
2004
|
}
|
|
1956
2005
|
|
|
2006
|
+
function markAppliedServerValue(value) {
|
|
2007
|
+
if (value && typeof value === "object") {
|
|
2008
|
+
appliedServerValues.add(value);
|
|
2009
|
+
}
|
|
2010
|
+
return value;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
1957
2013
|
function defaultInput(context = {}) {
|
|
1958
2014
|
const form = findForm(context);
|
|
1959
2015
|
if (form) {
|
|
@@ -2131,6 +2187,30 @@
|
|
|
2131
2187
|
return output;
|
|
2132
2188
|
}
|
|
2133
2189
|
|
|
2190
|
+
function assertJsonTransportable(value, seen = new Set()) {
|
|
2191
|
+
if (value == null || typeof value !== "object") {
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
if (seen.has(value)) {
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
seen.add(value);
|
|
2198
|
+
|
|
2199
|
+
const tag = Object.prototype.toString.call(value);
|
|
2200
|
+
if (tag === "[object File]" || tag === "[object Blob]" || tag === "[object FormData]") {
|
|
2201
|
+
throw new Error("Server proxy JSON transport does not support File, Blob, or FormData values yet.");
|
|
2202
|
+
}
|
|
2203
|
+
if (Array.isArray(value)) {
|
|
2204
|
+
for (const item of value) {
|
|
2205
|
+
assertJsonTransportable(item, seen);
|
|
2206
|
+
}
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
for (const item of Object.values(value)) {
|
|
2210
|
+
assertJsonTransportable(item, seen);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2134
2214
|
function joinEndpoint(endpoint, id) {
|
|
2135
2215
|
return `${String(endpoint).replace(/\/$/, "")}/${encodeURIComponent(id)}`;
|
|
2136
2216
|
}
|
|
@@ -2157,7 +2237,7 @@
|
|
|
2157
2237
|
throw new TypeError("Server function id must be a non-empty string.");
|
|
2158
2238
|
}
|
|
2159
2239
|
}
|
|
2160
|
-
return {
|
|
2240
|
+
return { createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
|
|
2161
2241
|
})();
|
|
2162
2242
|
|
|
2163
2243
|
const __handlersModule = (() => {
|
|
@@ -2360,18 +2440,321 @@
|
|
|
2360
2440
|
return { createHandlerRegistry, parseHandlerRef, isHandlerToken };
|
|
2361
2441
|
})();
|
|
2362
2442
|
|
|
2443
|
+
const __schedulerModule = (() => {
|
|
2444
|
+
const defaultPhases = ["binding", "lifecycle", "effect", "async", "post"];
|
|
2445
|
+
|
|
2446
|
+
function createScheduler(options = {}) {
|
|
2447
|
+
const phases = [...(options.phases ?? defaultPhases)];
|
|
2448
|
+
const queues = new Map(phases.map((phase) => [phase, []]));
|
|
2449
|
+
const keyedJobs = new Map();
|
|
2450
|
+
const destroyedScopes = new Set();
|
|
2451
|
+
const objectScopeIds = new WeakMap();
|
|
2452
|
+
const onError = typeof options.onError === "function" ? options.onError : undefined;
|
|
2453
|
+
const maxDepth = options.maxDepth ?? 100;
|
|
2454
|
+
const strategy = options.strategy ?? "microtask";
|
|
2455
|
+
let destroyed = false;
|
|
2456
|
+
let flushing = false;
|
|
2457
|
+
let scheduled = false;
|
|
2458
|
+
let batchDepth = 0;
|
|
2459
|
+
let jobCounter = 0;
|
|
2460
|
+
let scopeCounter = 0;
|
|
2461
|
+
|
|
2462
|
+
const api = {
|
|
2463
|
+
strategy,
|
|
2464
|
+
phases,
|
|
2465
|
+
|
|
2466
|
+
batch(fn) {
|
|
2467
|
+
if (typeof fn !== "function") {
|
|
2468
|
+
throw new TypeError("scheduler.batch(fn) requires a function.");
|
|
2469
|
+
}
|
|
2470
|
+
assertActive();
|
|
2471
|
+
batchDepth += 1;
|
|
2472
|
+
let asyncBatch = false;
|
|
2473
|
+
try {
|
|
2474
|
+
const value = fn();
|
|
2475
|
+
if (value && typeof value.then === "function") {
|
|
2476
|
+
asyncBatch = true;
|
|
2477
|
+
return value.finally(() => {
|
|
2478
|
+
batchDepth -= 1;
|
|
2479
|
+
requestFlush();
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
return value;
|
|
2483
|
+
} finally {
|
|
2484
|
+
if (!asyncBatch && batchDepth > 0) {
|
|
2485
|
+
batchDepth -= 1;
|
|
2486
|
+
requestFlush();
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
},
|
|
2490
|
+
|
|
2491
|
+
enqueue(phase, fn, options = {}) {
|
|
2492
|
+
assertActive();
|
|
2493
|
+
assertPhase(phase);
|
|
2494
|
+
if (typeof fn !== "function") {
|
|
2495
|
+
throw new TypeError("scheduler.enqueue(phase, fn) requires a function.");
|
|
2496
|
+
}
|
|
2497
|
+
const scope = options.scope;
|
|
2498
|
+
if (scope !== undefined && destroyedScopes.has(scope)) {
|
|
2499
|
+
return noop;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
const dedupeKey = options.key === undefined ? undefined : `${phase}:${scopeKey(scope)}:${String(options.key)}`;
|
|
2503
|
+
if (dedupeKey && keyedJobs.has(dedupeKey)) {
|
|
2504
|
+
return keyedJobs.get(dedupeKey).cancel;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
const job = {
|
|
2508
|
+
id: ++jobCounter,
|
|
2509
|
+
phase,
|
|
2510
|
+
fn,
|
|
2511
|
+
scope,
|
|
2512
|
+
boundary: options.boundary,
|
|
2513
|
+
key: dedupeKey,
|
|
2514
|
+
canceled: false,
|
|
2515
|
+
cancel() {
|
|
2516
|
+
job.canceled = true;
|
|
2517
|
+
if (job.key) {
|
|
2518
|
+
keyedJobs.delete(job.key);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
queues.get(phase).push(job);
|
|
2523
|
+
if (job.key) {
|
|
2524
|
+
keyedJobs.set(job.key, job);
|
|
2525
|
+
}
|
|
2526
|
+
requestFlush();
|
|
2527
|
+
return job.cancel;
|
|
2528
|
+
},
|
|
2529
|
+
|
|
2530
|
+
afterFlush(fn, options = {}) {
|
|
2531
|
+
return api.enqueue("post", fn, options);
|
|
2532
|
+
},
|
|
2533
|
+
|
|
2534
|
+
async flush() {
|
|
2535
|
+
assertActive();
|
|
2536
|
+
if (flushing) {
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
scheduled = false;
|
|
2540
|
+
flushing = true;
|
|
2541
|
+
let depth = 0;
|
|
2542
|
+
try {
|
|
2543
|
+
while (hasJobs()) {
|
|
2544
|
+
depth += 1;
|
|
2545
|
+
if (depth > maxDepth) {
|
|
2546
|
+
throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
|
|
2547
|
+
}
|
|
2548
|
+
for (const phase of phases) {
|
|
2549
|
+
await flushPhase(phase);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
} finally {
|
|
2553
|
+
flushing = false;
|
|
2554
|
+
if (hasJobs()) {
|
|
2555
|
+
requestFlush();
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
},
|
|
2559
|
+
|
|
2560
|
+
async flushScope(scope) {
|
|
2561
|
+
assertActive();
|
|
2562
|
+
if (flushing) {
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
scheduled = false;
|
|
2566
|
+
flushing = true;
|
|
2567
|
+
let depth = 0;
|
|
2568
|
+
try {
|
|
2569
|
+
while (hasJobsForScope(scope)) {
|
|
2570
|
+
depth += 1;
|
|
2571
|
+
if (depth > maxDepth) {
|
|
2572
|
+
throw new Error(`Scheduler exceeded maxDepth ${maxDepth}.`);
|
|
2573
|
+
}
|
|
2574
|
+
for (const phase of phases) {
|
|
2575
|
+
await flushPhase(phase, scope);
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
} finally {
|
|
2579
|
+
flushing = false;
|
|
2580
|
+
if (hasJobs()) {
|
|
2581
|
+
requestFlush();
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
},
|
|
2585
|
+
|
|
2586
|
+
cancelScope(scope) {
|
|
2587
|
+
if (scope === undefined) {
|
|
2588
|
+
return api;
|
|
2589
|
+
}
|
|
2590
|
+
for (const queue of queues.values()) {
|
|
2591
|
+
for (const job of queue) {
|
|
2592
|
+
if (job.scope === scope) {
|
|
2593
|
+
job.cancel();
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
return api;
|
|
2598
|
+
},
|
|
2599
|
+
|
|
2600
|
+
markScopeDestroyed(scope) {
|
|
2601
|
+
if (scope !== undefined) {
|
|
2602
|
+
destroyedScopes.add(scope);
|
|
2603
|
+
api.cancelScope(scope);
|
|
2604
|
+
}
|
|
2605
|
+
return api;
|
|
2606
|
+
},
|
|
2607
|
+
|
|
2608
|
+
inspect() {
|
|
2609
|
+
const counts = {};
|
|
2610
|
+
for (const [phase, queue] of queues) {
|
|
2611
|
+
counts[phase] = queue.filter((job) => !job.canceled).length;
|
|
2612
|
+
}
|
|
2613
|
+
return {
|
|
2614
|
+
strategy,
|
|
2615
|
+
phases: [...phases],
|
|
2616
|
+
pending: counts,
|
|
2617
|
+
scopesDestroyed: destroyedScopes.size,
|
|
2618
|
+
flushing,
|
|
2619
|
+
scheduled
|
|
2620
|
+
};
|
|
2621
|
+
},
|
|
2622
|
+
|
|
2623
|
+
destroy() {
|
|
2624
|
+
destroyed = true;
|
|
2625
|
+
for (const queue of queues.values()) {
|
|
2626
|
+
for (const job of queue) {
|
|
2627
|
+
job.cancel();
|
|
2628
|
+
}
|
|
2629
|
+
queue.length = 0;
|
|
2630
|
+
}
|
|
2631
|
+
keyedJobs.clear();
|
|
2632
|
+
destroyedScopes.clear();
|
|
2633
|
+
}
|
|
2634
|
+
};
|
|
2635
|
+
|
|
2636
|
+
return api;
|
|
2637
|
+
|
|
2638
|
+
function requestFlush() {
|
|
2639
|
+
if (strategy === "manual" || destroyed || flushing || batchDepth > 0 || scheduled) {
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
scheduled = true;
|
|
2643
|
+
scheduleMicrotask(() => {
|
|
2644
|
+
if (!destroyed) {
|
|
2645
|
+
void api.flush();
|
|
2646
|
+
}
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
async function flushPhase(phase, scope) {
|
|
2651
|
+
const queue = queues.get(phase);
|
|
2652
|
+
const remaining = [];
|
|
2653
|
+
const runnable = [];
|
|
2654
|
+
|
|
2655
|
+
for (const job of queue.splice(0)) {
|
|
2656
|
+
if (job.canceled) {
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
if (scope !== undefined && job.scope !== scope) {
|
|
2660
|
+
remaining.push(job);
|
|
2661
|
+
continue;
|
|
2662
|
+
}
|
|
2663
|
+
runnable.push(job);
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
queue.push(...remaining);
|
|
2667
|
+
|
|
2668
|
+
for (const job of runnable) {
|
|
2669
|
+
if (job.key) {
|
|
2670
|
+
keyedJobs.delete(job.key);
|
|
2671
|
+
}
|
|
2672
|
+
if (job.canceled || (job.scope !== undefined && destroyedScopes.has(job.scope))) {
|
|
2673
|
+
continue;
|
|
2674
|
+
}
|
|
2675
|
+
try {
|
|
2676
|
+
await job.fn();
|
|
2677
|
+
} catch (error) {
|
|
2678
|
+
if (onError) {
|
|
2679
|
+
onError(error, job);
|
|
2680
|
+
} else {
|
|
2681
|
+
throw error;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
function hasJobs() {
|
|
2688
|
+
for (const queue of queues.values()) {
|
|
2689
|
+
if (queue.some((job) => !job.canceled)) {
|
|
2690
|
+
return true;
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
return false;
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
function hasJobsForScope(scope) {
|
|
2697
|
+
for (const queue of queues.values()) {
|
|
2698
|
+
if (queue.some((job) => !job.canceled && job.scope === scope)) {
|
|
2699
|
+
return true;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
return false;
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
function assertActive() {
|
|
2706
|
+
if (destroyed) {
|
|
2707
|
+
throw new Error("Scheduler has been destroyed.");
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
function assertPhase(phase) {
|
|
2712
|
+
if (!queues.has(phase)) {
|
|
2713
|
+
throw new Error(`Unknown scheduler phase "${phase}".`);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
function scopeKey(scope) {
|
|
2718
|
+
if (scope === undefined) {
|
|
2719
|
+
return "global";
|
|
2720
|
+
}
|
|
2721
|
+
if ((typeof scope === "object" && scope !== null) || typeof scope === "function") {
|
|
2722
|
+
if (!objectScopeIds.has(scope)) {
|
|
2723
|
+
objectScopeIds.set(scope, `scope:${++scopeCounter}`);
|
|
2724
|
+
}
|
|
2725
|
+
return objectScopeIds.get(scope);
|
|
2726
|
+
}
|
|
2727
|
+
return String(scope);
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
function scheduleMicrotask(fn) {
|
|
2732
|
+
if (typeof queueMicrotask === "function") {
|
|
2733
|
+
queueMicrotask(fn);
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
Promise.resolve().then(fn);
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
function noop() {}
|
|
2740
|
+
return { createScheduler };
|
|
2741
|
+
})();
|
|
2742
|
+
|
|
2363
2743
|
const __loaderModule = (() => {
|
|
2364
2744
|
const { renderComponent } = __componentModule;
|
|
2365
2745
|
const { createHandlerRegistry } = __handlersModule;
|
|
2746
|
+
const { createScheduler } = __schedulerModule;
|
|
2366
2747
|
const { createSignalRegistry, isSignalRef } = __signalsModule;
|
|
2367
2748
|
const { matchAttribute, normalizeAttributeConfig, readAttribute } = __attributesModule;
|
|
2368
2749
|
const inlineBindingPrefix = "__async:inline:";
|
|
2369
2750
|
|
|
2370
|
-
function Loader({ root, signals, handlers, server, router, cache, attributes } = {}) {
|
|
2751
|
+
function Loader({ root, signals, handlers, server, router, cache, attributes, scheduler } = {}) {
|
|
2371
2752
|
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
2372
2753
|
const rootNode = root ?? documentRef;
|
|
2373
2754
|
const signalRegistry = signals ?? createSignalRegistry();
|
|
2374
2755
|
const handlerRegistry = handlers ?? createHandlerRegistry();
|
|
2756
|
+
const schedulerInstance = scheduler ?? createScheduler();
|
|
2757
|
+
const ownsScheduler = !scheduler;
|
|
2375
2758
|
const attributeConfig = normalizeAttributeConfig(attributes);
|
|
2376
2759
|
const cleanups = new Set();
|
|
2377
2760
|
const eventBindings = new WeakMap();
|
|
@@ -2392,6 +2775,7 @@
|
|
|
2392
2775
|
server,
|
|
2393
2776
|
router,
|
|
2394
2777
|
cache,
|
|
2778
|
+
scheduler: schedulerInstance,
|
|
2395
2779
|
attributes: attributeConfig,
|
|
2396
2780
|
|
|
2397
2781
|
start() {
|
|
@@ -2431,6 +2815,7 @@
|
|
|
2431
2815
|
server: api.server,
|
|
2432
2816
|
router: api.router,
|
|
2433
2817
|
cache: api.cache,
|
|
2818
|
+
scheduler: schedulerInstance,
|
|
2434
2819
|
attributes: attributeConfig
|
|
2435
2820
|
});
|
|
2436
2821
|
cleanupChildren(target);
|
|
@@ -2451,6 +2836,9 @@
|
|
|
2451
2836
|
runCleanup(cleanup);
|
|
2452
2837
|
}
|
|
2453
2838
|
cleanups.clear();
|
|
2839
|
+
if (ownsScheduler) {
|
|
2840
|
+
schedulerInstance.destroy();
|
|
2841
|
+
}
|
|
2454
2842
|
},
|
|
2455
2843
|
|
|
2456
2844
|
_observeVisible(target, fn) {
|
|
@@ -2468,13 +2856,14 @@
|
|
|
2468
2856
|
}
|
|
2469
2857
|
};
|
|
2470
2858
|
|
|
2471
|
-
signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
|
|
2859
|
+
signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache, scheduler: schedulerInstance });
|
|
2472
2860
|
api.server?._setContext?.({
|
|
2473
2861
|
signals: signalRegistry,
|
|
2474
2862
|
handlers: handlerRegistry,
|
|
2475
2863
|
loader: api,
|
|
2476
2864
|
router: api.router,
|
|
2477
|
-
cache: api.cache
|
|
2865
|
+
cache: api.cache,
|
|
2866
|
+
scheduler: schedulerInstance
|
|
2478
2867
|
});
|
|
2479
2868
|
|
|
2480
2869
|
function bindEventAttributes(scope) {
|
|
@@ -2506,18 +2895,19 @@
|
|
|
2506
2895
|
|
|
2507
2896
|
const listener = async (event) => {
|
|
2508
2897
|
try {
|
|
2509
|
-
await handlerRegistry.run(ref, {
|
|
2898
|
+
await schedulerInstance.batch(() => handlerRegistry.run(ref, {
|
|
2510
2899
|
signals: signalRegistry,
|
|
2511
2900
|
handlers: handlerRegistry,
|
|
2512
2901
|
loader: api,
|
|
2513
2902
|
server: api.server,
|
|
2514
2903
|
router: api.router,
|
|
2515
2904
|
cache: api.cache,
|
|
2905
|
+
scheduler: schedulerInstance,
|
|
2516
2906
|
event,
|
|
2517
2907
|
element,
|
|
2518
2908
|
el: element,
|
|
2519
2909
|
root: rootNode
|
|
2520
|
-
});
|
|
2910
|
+
}));
|
|
2521
2911
|
} catch (error) {
|
|
2522
2912
|
dispatchAsyncError(element, error);
|
|
2523
2913
|
}
|
|
@@ -2633,7 +3023,12 @@
|
|
|
2633
3023
|
|
|
2634
3024
|
const read = () => readBinding(path, options);
|
|
2635
3025
|
apply(read());
|
|
2636
|
-
addCleanup(subscribeBinding(path, () =>
|
|
3026
|
+
addCleanup(subscribeBinding(path, () => {
|
|
3027
|
+
schedulerInstance.enqueue("binding", () => apply(read()), {
|
|
3028
|
+
scope: element,
|
|
3029
|
+
key
|
|
3030
|
+
});
|
|
3031
|
+
}), element);
|
|
2637
3032
|
}
|
|
2638
3033
|
|
|
2639
3034
|
function bindValueWriter(element, path) {
|
|
@@ -2694,7 +3089,12 @@
|
|
|
2694
3089
|
const state = {
|
|
2695
3090
|
id,
|
|
2696
3091
|
templates,
|
|
2697
|
-
cleanup: signalRegistry.subscribe(`${id}.$status`, () =>
|
|
3092
|
+
cleanup: signalRegistry.subscribe(`${id}.$status`, () => {
|
|
3093
|
+
schedulerInstance.enqueue("binding", () => renderBoundary(boundary), {
|
|
3094
|
+
scope: boundary,
|
|
3095
|
+
key: `boundary:${id}`
|
|
3096
|
+
});
|
|
3097
|
+
})
|
|
2698
3098
|
};
|
|
2699
3099
|
boundaryState.set(boundary, state);
|
|
2700
3100
|
addCleanup(state.cleanup, boundary);
|
|
@@ -2734,7 +3134,7 @@
|
|
|
2734
3134
|
}
|
|
2735
3135
|
mountedElements.add(element);
|
|
2736
3136
|
for (const ref of refs) {
|
|
2737
|
-
runPseudo(element, ref);
|
|
3137
|
+
scheduleLifecycle(element, () => runPseudo(element, ref), `attach:${ref}`);
|
|
2738
3138
|
}
|
|
2739
3139
|
}
|
|
2740
3140
|
|
|
@@ -2747,7 +3147,7 @@
|
|
|
2747
3147
|
continue;
|
|
2748
3148
|
}
|
|
2749
3149
|
visibleElements.add(element);
|
|
2750
|
-
addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
|
|
3150
|
+
addCleanup(observeVisible(element, () => scheduleLifecycle(element, () => runPseudo(element, ref), `visible:${ref}`)), element);
|
|
2751
3151
|
}
|
|
2752
3152
|
}
|
|
2753
3153
|
|
|
@@ -2771,6 +3171,7 @@
|
|
|
2771
3171
|
server: api.server,
|
|
2772
3172
|
router: api.router,
|
|
2773
3173
|
cache: api.cache,
|
|
3174
|
+
scheduler: schedulerInstance,
|
|
2774
3175
|
element,
|
|
2775
3176
|
el: element,
|
|
2776
3177
|
root: rootNode
|
|
@@ -2789,10 +3190,13 @@
|
|
|
2789
3190
|
const ownerWindow = target.ownerDocument?.defaultView ?? globalThis;
|
|
2790
3191
|
const Observer = ownerWindow.IntersectionObserver ?? globalThis.IntersectionObserver;
|
|
2791
3192
|
if (!Observer) {
|
|
2792
|
-
|
|
3193
|
+
schedulerInstance.enqueue("lifecycle", () => {
|
|
2793
3194
|
if (!destroyed) {
|
|
2794
3195
|
fn(target);
|
|
2795
3196
|
}
|
|
3197
|
+
}, {
|
|
3198
|
+
scope: target,
|
|
3199
|
+
key: "visible:fallback"
|
|
2796
3200
|
});
|
|
2797
3201
|
return () => {};
|
|
2798
3202
|
}
|
|
@@ -2847,6 +3251,7 @@
|
|
|
2847
3251
|
}
|
|
2848
3252
|
for (const element of elementsIn(node)) {
|
|
2849
3253
|
runScopedCleanups(element);
|
|
3254
|
+
schedulerInstance.markScopeDestroyed(element);
|
|
2850
3255
|
}
|
|
2851
3256
|
}
|
|
2852
3257
|
|
|
@@ -2870,6 +3275,13 @@
|
|
|
2870
3275
|
}
|
|
2871
3276
|
}
|
|
2872
3277
|
|
|
3278
|
+
function scheduleLifecycle(element, fn, key) {
|
|
3279
|
+
schedulerInstance.enqueue("lifecycle", fn, {
|
|
3280
|
+
scope: element,
|
|
3281
|
+
key
|
|
3282
|
+
});
|
|
3283
|
+
}
|
|
3284
|
+
|
|
2873
3285
|
return api;
|
|
2874
3286
|
}
|
|
2875
3287
|
|
|
@@ -3200,6 +3612,7 @@
|
|
|
3200
3612
|
const __routerModule = (() => {
|
|
3201
3613
|
const { Loader } = __loaderModule;
|
|
3202
3614
|
const { createHandlerRegistry } = __handlersModule;
|
|
3615
|
+
const { createScheduler } = __schedulerModule;
|
|
3203
3616
|
const { createSignalRegistry } = __signalsModule;
|
|
3204
3617
|
const { applyServerResult } = __serverModule;
|
|
3205
3618
|
const { createRegistryStore } = __registryStoreModule;
|
|
@@ -3230,6 +3643,7 @@
|
|
|
3230
3643
|
const nextRoute = normalizeRoute(pattern, definition);
|
|
3231
3644
|
entries.set(pattern, nextRoute.definition);
|
|
3232
3645
|
routes.push(nextRoute);
|
|
3646
|
+
sortRoutes(routes);
|
|
3233
3647
|
return nextRoute;
|
|
3234
3648
|
},
|
|
3235
3649
|
|
|
@@ -3302,6 +3716,7 @@
|
|
|
3302
3716
|
const nextRoute = normalizeRoute(pattern, definition);
|
|
3303
3717
|
entries.set(pattern, nextRoute.definition);
|
|
3304
3718
|
routes.push(nextRoute);
|
|
3719
|
+
sortRoutes(routes);
|
|
3305
3720
|
}
|
|
3306
3721
|
}
|
|
3307
3722
|
|
|
@@ -3318,12 +3733,15 @@
|
|
|
3318
3733
|
partials,
|
|
3319
3734
|
fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
3320
3735
|
routeEndpoint = "/__async/route",
|
|
3321
|
-
attributes
|
|
3736
|
+
attributes,
|
|
3737
|
+
scheduler
|
|
3322
3738
|
} = {}) {
|
|
3323
3739
|
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
3324
3740
|
const rootNode = root ?? documentRef;
|
|
3325
3741
|
const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
|
|
3326
3742
|
const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
|
|
3743
|
+
const schedulerInstance = scheduler ?? loader?.scheduler ?? createScheduler();
|
|
3744
|
+
const ownsScheduler = !scheduler && !loader?.scheduler;
|
|
3327
3745
|
const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
|
|
3328
3746
|
const loaderInstance =
|
|
3329
3747
|
loader ??
|
|
@@ -3333,11 +3751,14 @@
|
|
|
3333
3751
|
handlers: handlerRegistry,
|
|
3334
3752
|
server,
|
|
3335
3753
|
cache,
|
|
3754
|
+
scheduler: schedulerInstance,
|
|
3336
3755
|
attributes: attributeConfig
|
|
3337
3756
|
});
|
|
3338
3757
|
const ownsLoader = !loader;
|
|
3339
3758
|
const cleanups = new Set();
|
|
3340
3759
|
let destroyed = false;
|
|
3760
|
+
let navigationVersion = 0;
|
|
3761
|
+
let activeNavigation;
|
|
3341
3762
|
|
|
3342
3763
|
const api = {
|
|
3343
3764
|
mode,
|
|
@@ -3350,12 +3771,13 @@
|
|
|
3350
3771
|
server,
|
|
3351
3772
|
cache,
|
|
3352
3773
|
partials,
|
|
3774
|
+
scheduler: schedulerInstance,
|
|
3353
3775
|
attributes: attributeConfig,
|
|
3354
3776
|
|
|
3355
3777
|
start() {
|
|
3356
3778
|
assertActive();
|
|
3357
3779
|
loaderInstance.router = api;
|
|
3358
|
-
signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache });
|
|
3780
|
+
signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache, scheduler: schedulerInstance });
|
|
3359
3781
|
if (ownsLoader) {
|
|
3360
3782
|
loaderInstance.start();
|
|
3361
3783
|
}
|
|
@@ -3377,7 +3799,7 @@
|
|
|
3377
3799
|
},
|
|
3378
3800
|
|
|
3379
3801
|
match(url) {
|
|
3380
|
-
return routes.match(url);
|
|
3802
|
+
return routes.match(resolveUrl(url));
|
|
3381
3803
|
},
|
|
3382
3804
|
|
|
3383
3805
|
prefetch(url) {
|
|
@@ -3402,7 +3824,7 @@
|
|
|
3402
3824
|
return null;
|
|
3403
3825
|
}
|
|
3404
3826
|
|
|
3405
|
-
const target =
|
|
3827
|
+
const target = resolveUrl(url);
|
|
3406
3828
|
if (mode === "ssr-spa") {
|
|
3407
3829
|
return fetchRoutePartial(target, options);
|
|
3408
3830
|
}
|
|
@@ -3414,10 +3836,14 @@
|
|
|
3414
3836
|
return;
|
|
3415
3837
|
}
|
|
3416
3838
|
destroyed = true;
|
|
3839
|
+
activeNavigation?.controller.abort(new Error("Router has been destroyed."));
|
|
3417
3840
|
for (const cleanup of cleanups) {
|
|
3418
3841
|
cleanup();
|
|
3419
3842
|
}
|
|
3420
3843
|
cleanups.clear();
|
|
3844
|
+
if (ownsScheduler) {
|
|
3845
|
+
schedulerInstance.destroy();
|
|
3846
|
+
}
|
|
3421
3847
|
}
|
|
3422
3848
|
};
|
|
3423
3849
|
|
|
@@ -3453,24 +3879,37 @@
|
|
|
3453
3879
|
async function renderLocalRoutePartial(target, options = {}) {
|
|
3454
3880
|
const matched = api.match(target);
|
|
3455
3881
|
if (!matched) {
|
|
3882
|
+
beginNavigation(target, null);
|
|
3456
3883
|
setNoRouteError(target);
|
|
3457
3884
|
return null;
|
|
3458
3885
|
}
|
|
3459
3886
|
|
|
3887
|
+
const navigation = beginNavigation(target, matched);
|
|
3460
3888
|
setMatchedRouterState(target, matched, { pending: true, error: null });
|
|
3461
3889
|
|
|
3462
3890
|
try {
|
|
3463
3891
|
if (!matched.route?.partial || !partials?.resolve?.(matched.route.partial)) {
|
|
3464
3892
|
const error = new Error(`Route "${target.pathname}" does not have a registered partial.`);
|
|
3465
|
-
|
|
3893
|
+
if (isActiveNavigation(navigation)) {
|
|
3894
|
+
setRouterState({ pending: false, error });
|
|
3895
|
+
}
|
|
3466
3896
|
return null;
|
|
3467
3897
|
}
|
|
3468
3898
|
|
|
3469
|
-
const result = await partials.render(matched.route.partial, matched.params, contextFor(matched));
|
|
3470
|
-
|
|
3899
|
+
const result = await partials.render(matched.route.partial, matched.params, contextFor(matched, navigation));
|
|
3900
|
+
if (!isActiveNavigation(navigation)) {
|
|
3901
|
+
return null;
|
|
3902
|
+
}
|
|
3903
|
+
await applyNavigationResult(result, target, options, navigation);
|
|
3904
|
+
if (!isActiveNavigation(navigation)) {
|
|
3905
|
+
return null;
|
|
3906
|
+
}
|
|
3471
3907
|
setRouterState({ pending: false, error: null });
|
|
3472
3908
|
return result;
|
|
3473
3909
|
} catch (error) {
|
|
3910
|
+
if (!isActiveNavigation(navigation)) {
|
|
3911
|
+
return null;
|
|
3912
|
+
}
|
|
3474
3913
|
setRouterState({ pending: false, error });
|
|
3475
3914
|
throw error;
|
|
3476
3915
|
}
|
|
@@ -3478,28 +3917,48 @@
|
|
|
3478
3917
|
|
|
3479
3918
|
async function fetchRoutePartial(target, options = {}) {
|
|
3480
3919
|
const matched = api.match(target);
|
|
3920
|
+
const navigation = beginNavigation(target, matched);
|
|
3481
3921
|
setMatchedRouterState(target, matched, { pending: true, error: null });
|
|
3482
3922
|
|
|
3483
3923
|
try {
|
|
3484
|
-
const result = await fetchRoute(target.href);
|
|
3485
|
-
|
|
3924
|
+
const result = await fetchRoute(target.href, { signal: navigation.abort });
|
|
3925
|
+
if (!isActiveNavigation(navigation)) {
|
|
3926
|
+
return null;
|
|
3927
|
+
}
|
|
3928
|
+
await applyNavigationResult(result, target, options, navigation);
|
|
3929
|
+
if (!isActiveNavigation(navigation)) {
|
|
3930
|
+
return null;
|
|
3931
|
+
}
|
|
3486
3932
|
setRouterState({ pending: false, error: null });
|
|
3487
3933
|
return result;
|
|
3488
3934
|
} catch (error) {
|
|
3935
|
+
if (!isActiveNavigation(navigation)) {
|
|
3936
|
+
return null;
|
|
3937
|
+
}
|
|
3489
3938
|
setRouterState({ pending: false, error });
|
|
3490
3939
|
throw error;
|
|
3491
3940
|
}
|
|
3492
3941
|
}
|
|
3493
3942
|
|
|
3494
|
-
async function applyNavigationResult(result, target, options) {
|
|
3943
|
+
async function applyNavigationResult(result, target, options, navigation) {
|
|
3944
|
+
if (!isActiveNavigation(navigation)) {
|
|
3945
|
+
return;
|
|
3946
|
+
}
|
|
3495
3947
|
await applyServerResult(result, {
|
|
3496
3948
|
signals: signalRegistry,
|
|
3497
3949
|
loader: loaderInstance,
|
|
3498
3950
|
router: api,
|
|
3499
|
-
cache
|
|
3951
|
+
cache,
|
|
3952
|
+
scheduler: schedulerInstance,
|
|
3953
|
+
abort: navigation?.abort
|
|
3500
3954
|
});
|
|
3955
|
+
await schedulerInstance.flush();
|
|
3956
|
+
if (!isActiveNavigation(navigation)) {
|
|
3957
|
+
return;
|
|
3958
|
+
}
|
|
3501
3959
|
if (result?.html != null && !result.boundary && !result.redirect) {
|
|
3502
3960
|
loaderInstance.swap(boundary, result.html);
|
|
3961
|
+
await schedulerInstance.flush();
|
|
3503
3962
|
}
|
|
3504
3963
|
if (result?.redirect || options.history === false) {
|
|
3505
3964
|
return;
|
|
@@ -3511,14 +3970,15 @@
|
|
|
3511
3970
|
documentRef.defaultView?.history?.pushState?.({}, "", target.href);
|
|
3512
3971
|
}
|
|
3513
3972
|
|
|
3514
|
-
async function fetchRoute(url, { prefetch = false } = {}) {
|
|
3973
|
+
async function fetchRoute(url, { prefetch = false, signal } = {}) {
|
|
3515
3974
|
if (typeof fetchImpl !== "function") {
|
|
3516
3975
|
throw new Error("Router navigation requires a partial registry or fetch.");
|
|
3517
3976
|
}
|
|
3518
3977
|
const response = await fetchImpl(`${routeEndpoint}?to=${encodeURIComponent(String(url))}`, {
|
|
3519
3978
|
headers: {
|
|
3520
3979
|
accept: "application/json, text/html"
|
|
3521
|
-
}
|
|
3980
|
+
},
|
|
3981
|
+
signal
|
|
3522
3982
|
});
|
|
3523
3983
|
if (!response.ok) {
|
|
3524
3984
|
throw new Error(`Route "${url}" failed with ${response.status}.`);
|
|
@@ -3533,7 +3993,7 @@
|
|
|
3533
3993
|
return { boundary, html: await response.text() };
|
|
3534
3994
|
}
|
|
3535
3995
|
|
|
3536
|
-
function contextFor(matched) {
|
|
3996
|
+
function contextFor(matched, navigation) {
|
|
3537
3997
|
return {
|
|
3538
3998
|
params: matched.params,
|
|
3539
3999
|
route: matched.route,
|
|
@@ -3543,8 +4003,27 @@
|
|
|
3543
4003
|
loader: loaderInstance,
|
|
3544
4004
|
server,
|
|
3545
4005
|
cache,
|
|
3546
|
-
|
|
4006
|
+
scheduler: schedulerInstance,
|
|
4007
|
+
abort: navigation?.abort
|
|
4008
|
+
};
|
|
4009
|
+
}
|
|
4010
|
+
|
|
4011
|
+
function beginNavigation(target, matched) {
|
|
4012
|
+
activeNavigation?.controller.abort(new Error(`Router navigation superseded by ${target.pathname}${target.search}.`));
|
|
4013
|
+
const controller = new AbortController();
|
|
4014
|
+
const navigation = {
|
|
4015
|
+
id: ++navigationVersion,
|
|
4016
|
+
controller,
|
|
4017
|
+
abort: controller.signal,
|
|
4018
|
+
target,
|
|
4019
|
+
matched
|
|
3547
4020
|
};
|
|
4021
|
+
activeNavigation = navigation;
|
|
4022
|
+
return navigation;
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
function isActiveNavigation(navigation) {
|
|
4026
|
+
return !destroyed && navigation && activeNavigation?.id === navigation.id && !navigation.abort.aborted;
|
|
3548
4027
|
}
|
|
3549
4028
|
|
|
3550
4029
|
function updateStateFromLocation() {
|
|
@@ -3581,7 +4060,14 @@
|
|
|
3581
4060
|
}
|
|
3582
4061
|
|
|
3583
4062
|
function currentUrl() {
|
|
3584
|
-
return
|
|
4063
|
+
return resolveUrl(documentRef.defaultView?.location?.href ?? "http://localhost/");
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
function resolveUrl(url) {
|
|
4067
|
+
if (url instanceof URL) {
|
|
4068
|
+
return url;
|
|
4069
|
+
}
|
|
4070
|
+
return new URL(String(url), documentRef.defaultView?.location?.href ?? "http://localhost/");
|
|
3585
4071
|
}
|
|
3586
4072
|
|
|
3587
4073
|
function assertActive() {
|
|
@@ -3598,6 +4084,7 @@
|
|
|
3598
4084
|
pattern,
|
|
3599
4085
|
regex,
|
|
3600
4086
|
keys,
|
|
4087
|
+
score: routeScore(pattern),
|
|
3601
4088
|
definition: normalized
|
|
3602
4089
|
};
|
|
3603
4090
|
}
|
|
@@ -3666,6 +4153,28 @@
|
|
|
3666
4153
|
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3667
4154
|
}
|
|
3668
4155
|
|
|
4156
|
+
function sortRoutes(routes) {
|
|
4157
|
+
routes.sort((left, right) => right.score - left.score || right.pattern.length - left.pattern.length);
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
function routeScore(pattern) {
|
|
4161
|
+
if (pattern === "*") {
|
|
4162
|
+
return -1;
|
|
4163
|
+
}
|
|
4164
|
+
return pattern
|
|
4165
|
+
.split("/")
|
|
4166
|
+
.filter(Boolean)
|
|
4167
|
+
.reduce((score, segment) => {
|
|
4168
|
+
if (segment === "*") {
|
|
4169
|
+
return score;
|
|
4170
|
+
}
|
|
4171
|
+
if (segment.startsWith(":")) {
|
|
4172
|
+
return score + 2;
|
|
4173
|
+
}
|
|
4174
|
+
return score + 4;
|
|
4175
|
+
}, pattern === "/" ? 3 : 0);
|
|
4176
|
+
}
|
|
4177
|
+
|
|
3669
4178
|
function assertPattern(pattern) {
|
|
3670
4179
|
if (typeof pattern !== "string" || (pattern !== "*" && !pattern.startsWith("/"))) {
|
|
3671
4180
|
throw new TypeError("Route pattern must be a path string or \"*\".");
|
|
@@ -3681,15 +4190,17 @@
|
|
|
3681
4190
|
const { Loader } = __loaderModule;
|
|
3682
4191
|
const { createPartialRegistry } = __partialsModule;
|
|
3683
4192
|
const { createRouteRegistry, createRouter } = __routerModule;
|
|
3684
|
-
const {
|
|
4193
|
+
const { createScheduler } = __schedulerModule;
|
|
4194
|
+
const { createServerNamespace } = __serverModule;
|
|
3685
4195
|
const { createSignal, createSignalRegistry } = __signalsModule;
|
|
3686
4196
|
const { createRegistryStore } = __registryStoreModule;
|
|
3687
4197
|
const { attributeName, normalizeAttributeConfig } = __attributesModule;
|
|
3688
4198
|
const registryTypes = new Set(["signal", "handler", "server", "partial", "route", "component"]);
|
|
3689
4199
|
|
|
3690
|
-
function defineApp(initial) {
|
|
4200
|
+
function defineApp(initial, options = {}) {
|
|
3691
4201
|
const registry = createRegistryStore(undefined, { target: "browser" });
|
|
3692
4202
|
const runtimes = new Set();
|
|
4203
|
+
const createRuntime = options.createRuntime ?? createApp;
|
|
3693
4204
|
|
|
3694
4205
|
const app = {
|
|
3695
4206
|
registry,
|
|
@@ -3708,7 +4219,7 @@
|
|
|
3708
4219
|
},
|
|
3709
4220
|
|
|
3710
4221
|
start(options = {}) {
|
|
3711
|
-
const runtime =
|
|
4222
|
+
const runtime = createRuntime(app, options).start();
|
|
3712
4223
|
app.runtime = runtime;
|
|
3713
4224
|
return runtime;
|
|
3714
4225
|
},
|
|
@@ -3733,13 +4244,18 @@
|
|
|
3733
4244
|
function createApp(appOrDefinition = Async, options = {}) {
|
|
3734
4245
|
const app = isAppHub(appOrDefinition) ? appOrDefinition : defineApp(appOrDefinition ?? {});
|
|
3735
4246
|
const target = options.target ?? "browser";
|
|
4247
|
+
const scheduler = options.scheduler ?? options.loader?.scheduler ?? createScheduler({
|
|
4248
|
+
strategy: target === "server" ? "manual" : "microtask"
|
|
4249
|
+
});
|
|
4250
|
+
const ownsScheduler = !options.scheduler && !options.loader?.scheduler;
|
|
3736
4251
|
const attributes = normalizeAttributeConfig(options.attributes);
|
|
3737
4252
|
const registry = options.registry ?? app.registry.view({ target });
|
|
3738
4253
|
const signals = options.signals ?? createSignalRegistry(undefined, { registry, type: "signal" });
|
|
3739
4254
|
const handlers = options.handlers ?? createHandlerRegistry(undefined, { registry, type: "handler" });
|
|
3740
4255
|
const serverCache = createCacheRegistry(undefined, { registry, type: "cache.server" });
|
|
3741
4256
|
const browserCache = createCacheRegistry(undefined, { registry, type: "cache.browser" });
|
|
3742
|
-
const
|
|
4257
|
+
const serverFactory = options.serverFactory ?? createServerReferenceRegistry;
|
|
4258
|
+
const server = options.server ?? serverFactory(undefined, { registry, type: "server" });
|
|
3743
4259
|
const partials = options.partials ?? createPartialRegistry(undefined, { registry, type: "partial" });
|
|
3744
4260
|
const routes = options.routes ?? createRouteRegistry(undefined, { registry, type: "route" });
|
|
3745
4261
|
const components = options.components ?? createComponentRegistry(undefined, { registry, type: "component" });
|
|
@@ -3749,7 +4265,7 @@
|
|
|
3749
4265
|
let started = false;
|
|
3750
4266
|
let destroyed = false;
|
|
3751
4267
|
|
|
3752
|
-
applySnapshot(signals, browserCache, options.snapshot);
|
|
4268
|
+
applySnapshot(signals, browserCache, options.snapshot ?? (target === "browser" ? readSnapshot(options.root, { attributes }) : undefined));
|
|
3753
4269
|
attachServerCache(server, serverCache);
|
|
3754
4270
|
|
|
3755
4271
|
const runtime = {
|
|
@@ -3767,6 +4283,7 @@
|
|
|
3767
4283
|
},
|
|
3768
4284
|
loader,
|
|
3769
4285
|
router,
|
|
4286
|
+
scheduler,
|
|
3770
4287
|
attributes,
|
|
3771
4288
|
|
|
3772
4289
|
start() {
|
|
@@ -3783,12 +4300,13 @@
|
|
|
3783
4300
|
handlers,
|
|
3784
4301
|
server,
|
|
3785
4302
|
cache: browserCache,
|
|
4303
|
+
scheduler,
|
|
3786
4304
|
attributes
|
|
3787
4305
|
});
|
|
3788
4306
|
runtime.loader = loader;
|
|
3789
4307
|
|
|
3790
4308
|
configureServerContext({ cache: browserCache });
|
|
3791
|
-
signals._setContext?.({ server, loader, cache: browserCache });
|
|
4309
|
+
signals._setContext?.({ server, loader, cache: browserCache, scheduler });
|
|
3792
4310
|
|
|
3793
4311
|
loader.start();
|
|
3794
4312
|
|
|
@@ -3804,6 +4322,7 @@
|
|
|
3804
4322
|
server,
|
|
3805
4323
|
cache: browserCache,
|
|
3806
4324
|
partials,
|
|
4325
|
+
scheduler,
|
|
3807
4326
|
fetch: options.fetch,
|
|
3808
4327
|
routeEndpoint: options.routeEndpoint,
|
|
3809
4328
|
attributes
|
|
@@ -3815,7 +4334,7 @@
|
|
|
3815
4334
|
}
|
|
3816
4335
|
} else {
|
|
3817
4336
|
configureServerContext({ cache: serverCache });
|
|
3818
|
-
signals._setContext?.({ server, cache: serverCache });
|
|
4337
|
+
signals._setContext?.({ server, cache: serverCache, scheduler });
|
|
3819
4338
|
}
|
|
3820
4339
|
|
|
3821
4340
|
return runtime;
|
|
@@ -3829,9 +4348,10 @@
|
|
|
3829
4348
|
async render(url) {
|
|
3830
4349
|
assertActive();
|
|
3831
4350
|
configureServerContext({ cache: serverCache });
|
|
3832
|
-
signals._setContext?.({ server, cache: serverCache });
|
|
4351
|
+
signals._setContext?.({ server, cache: serverCache, scheduler });
|
|
3833
4352
|
const matched = routes.match(url);
|
|
3834
4353
|
if (!matched) {
|
|
4354
|
+
await scheduler.flush();
|
|
3835
4355
|
return {
|
|
3836
4356
|
html: renderDocument("", { status: 404, signals, browserCache, boundary: options.boundary ?? "route", attributes }),
|
|
3837
4357
|
status: 404,
|
|
@@ -3851,8 +4371,8 @@
|
|
|
3851
4371
|
cache: serverCache,
|
|
3852
4372
|
browserCache,
|
|
3853
4373
|
partials,
|
|
3854
|
-
|
|
3855
|
-
|
|
4374
|
+
scheduler,
|
|
4375
|
+
...currentRequestContext()
|
|
3856
4376
|
})
|
|
3857
4377
|
: { html: "" };
|
|
3858
4378
|
|
|
@@ -3865,6 +4385,8 @@
|
|
|
3865
4385
|
browserCache.restore(result.cache.browser);
|
|
3866
4386
|
}
|
|
3867
4387
|
|
|
4388
|
+
await scheduler.flush();
|
|
4389
|
+
|
|
3868
4390
|
const status = result.status ?? 200;
|
|
3869
4391
|
return {
|
|
3870
4392
|
html: renderDocument(result.html, { status, signals, browserCache, boundary: result.boundary ?? options.boundary ?? "route", attributes }),
|
|
@@ -3883,6 +4405,9 @@
|
|
|
3883
4405
|
router?.destroy?.();
|
|
3884
4406
|
loader?.destroy?.();
|
|
3885
4407
|
signals.destroy?.();
|
|
4408
|
+
if (ownsScheduler) {
|
|
4409
|
+
scheduler.destroy();
|
|
4410
|
+
}
|
|
3886
4411
|
},
|
|
3887
4412
|
|
|
3888
4413
|
_applyUse(normalized) {
|
|
@@ -3903,11 +4428,23 @@
|
|
|
3903
4428
|
loader,
|
|
3904
4429
|
router,
|
|
3905
4430
|
cache,
|
|
3906
|
-
|
|
3907
|
-
|
|
4431
|
+
scheduler,
|
|
4432
|
+
requestContext: options.requestContext,
|
|
4433
|
+
...currentRequestContext()
|
|
3908
4434
|
});
|
|
3909
4435
|
}
|
|
3910
4436
|
|
|
4437
|
+
function currentRequestContext() {
|
|
4438
|
+
const context = readRequestContextLike(options.requestContext);
|
|
4439
|
+
return {
|
|
4440
|
+
requestContext: context,
|
|
4441
|
+
request: context.request ?? options.request,
|
|
4442
|
+
headers: context.headers ?? options.headers,
|
|
4443
|
+
cookies: context.cookies ?? options.cookies,
|
|
4444
|
+
locals: context.locals ?? options.locals
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
|
|
3911
4448
|
function assertActive() {
|
|
3912
4449
|
if (destroyed) {
|
|
3913
4450
|
throw new Error("Async app runtime has been destroyed.");
|
|
@@ -3917,6 +4454,38 @@
|
|
|
3917
4454
|
|
|
3918
4455
|
const Async = defineApp();
|
|
3919
4456
|
|
|
4457
|
+
function readSnapshot(root = globalThis.document, { attributes } = {}) {
|
|
4458
|
+
const attributeConfig = normalizeAttributeConfig(attributes);
|
|
4459
|
+
const snapshotAttr = attributeName(attributeConfig, "async", "snapshot");
|
|
4460
|
+
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
4461
|
+
const rootNode = root ?? documentRef;
|
|
4462
|
+
if (!rootNode?.querySelectorAll && !documentRef?.querySelectorAll) {
|
|
4463
|
+
return {};
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
for (const searchRoot of new Set([rootNode, documentRef])) {
|
|
4467
|
+
if (!searchRoot?.querySelectorAll) {
|
|
4468
|
+
continue;
|
|
4469
|
+
}
|
|
4470
|
+
for (const script of searchRoot.querySelectorAll("script[type='application/json'], script")) {
|
|
4471
|
+
if (!script.hasAttribute?.(snapshotAttr)) {
|
|
4472
|
+
continue;
|
|
4473
|
+
}
|
|
4474
|
+
const source = script.textContent?.trim() ?? "";
|
|
4475
|
+
if (!source) {
|
|
4476
|
+
return {};
|
|
4477
|
+
}
|
|
4478
|
+
try {
|
|
4479
|
+
return JSON.parse(source);
|
|
4480
|
+
} catch (cause) {
|
|
4481
|
+
throw new Error(`Could not parse Async snapshot: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
|
|
4486
|
+
return {};
|
|
4487
|
+
}
|
|
4488
|
+
|
|
3920
4489
|
function applyUseToRuntime(runtime, normalized) {
|
|
3921
4490
|
applyRegistryUse(runtime.signals, runtime.registry, normalized.signal);
|
|
3922
4491
|
applyRegistryUse(runtime.handlers, runtime.registry, normalized.handler);
|
|
@@ -4029,6 +4598,77 @@
|
|
|
4029
4598
|
}
|
|
4030
4599
|
}
|
|
4031
4600
|
|
|
4601
|
+
function createServerReferenceRegistry(initialMap = {}, options = {}) {
|
|
4602
|
+
const registry = options.registry ?? createRegistryStore();
|
|
4603
|
+
const type = options.type ?? "server";
|
|
4604
|
+
const defaults = {};
|
|
4605
|
+
|
|
4606
|
+
const reference = {
|
|
4607
|
+
registry,
|
|
4608
|
+
|
|
4609
|
+
register(id, value) {
|
|
4610
|
+
registry.register(type, id, value);
|
|
4611
|
+
return id;
|
|
4612
|
+
},
|
|
4613
|
+
|
|
4614
|
+
registerMany(map) {
|
|
4615
|
+
for (const [id, value] of Object.entries(map ?? {})) {
|
|
4616
|
+
reference.register(id, value);
|
|
4617
|
+
}
|
|
4618
|
+
return reference;
|
|
4619
|
+
},
|
|
4620
|
+
|
|
4621
|
+
unregister(id) {
|
|
4622
|
+
return registry.unregister(type, id);
|
|
4623
|
+
},
|
|
4624
|
+
|
|
4625
|
+
resolve() {
|
|
4626
|
+
return undefined;
|
|
4627
|
+
},
|
|
4628
|
+
|
|
4629
|
+
async run(id) {
|
|
4630
|
+
throw new Error(`Server command "${id}" cannot run without a server proxy or server registry.`);
|
|
4631
|
+
},
|
|
4632
|
+
|
|
4633
|
+
keys() {
|
|
4634
|
+
return registry.keys(type);
|
|
4635
|
+
},
|
|
4636
|
+
|
|
4637
|
+
entries() {
|
|
4638
|
+
return registry.entries(type);
|
|
4639
|
+
},
|
|
4640
|
+
|
|
4641
|
+
inspect() {
|
|
4642
|
+
return registry.entries(type);
|
|
4643
|
+
},
|
|
4644
|
+
|
|
4645
|
+
_setContext(context = {}) {
|
|
4646
|
+
Object.assign(defaults, context);
|
|
4647
|
+
return reference;
|
|
4648
|
+
},
|
|
4649
|
+
|
|
4650
|
+
_adoptMany() {
|
|
4651
|
+
return reference;
|
|
4652
|
+
}
|
|
4653
|
+
};
|
|
4654
|
+
|
|
4655
|
+
reference.registerMany(initialMap);
|
|
4656
|
+
return createServerNamespace((id, args, context) => reference.run(id, args, context), reference, () => defaults);
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4659
|
+
function readRequestContextLike(store) {
|
|
4660
|
+
if (!store) {
|
|
4661
|
+
return {};
|
|
4662
|
+
}
|
|
4663
|
+
if (typeof store.get === "function") {
|
|
4664
|
+
return store.get() ?? {};
|
|
4665
|
+
}
|
|
4666
|
+
if (typeof store.getStore === "function") {
|
|
4667
|
+
return store.getStore() ?? {};
|
|
4668
|
+
}
|
|
4669
|
+
return {};
|
|
4670
|
+
}
|
|
4671
|
+
|
|
4032
4672
|
function normalizeEntries(type, entries = {}) {
|
|
4033
4673
|
if (type !== "signal") {
|
|
4034
4674
|
return { ...(entries ?? {}) };
|
|
@@ -4077,7 +4717,7 @@
|
|
|
4077
4717
|
function escapeScriptJson(value) {
|
|
4078
4718
|
return JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
4079
4719
|
}
|
|
4080
|
-
return { defineApp, createApp, Async };
|
|
4720
|
+
return { defineApp, createApp, readSnapshot, Async };
|
|
4081
4721
|
})();
|
|
4082
4722
|
|
|
4083
4723
|
const __delayModule = (() => {
|
|
@@ -4118,6 +4758,7 @@
|
|
|
4118
4758
|
const { Async: Async } = __appModule;
|
|
4119
4759
|
const { createApp: createApp } = __appModule;
|
|
4120
4760
|
const { defineApp: defineApp } = __appModule;
|
|
4761
|
+
const { readSnapshot: readSnapshot } = __appModule;
|
|
4121
4762
|
const { attributeName: attributeName } = __attributesModule;
|
|
4122
4763
|
const { defineAttributeConfig: defineAttributeConfig } = __attributesModule;
|
|
4123
4764
|
const { createCacheRegistry: createCacheRegistry } = __cacheModule;
|
|
@@ -4136,14 +4777,17 @@
|
|
|
4136
4777
|
const { createRouter: createRouter } = __routerModule;
|
|
4137
4778
|
const { defineRoute: defineRoute } = __routerModule;
|
|
4138
4779
|
const { route: route } = __routerModule;
|
|
4780
|
+
const { createScheduler: createScheduler } = __schedulerModule;
|
|
4781
|
+
const { applyServerResult: applyServerResult } = __serverModule;
|
|
4139
4782
|
const { createServerProxy: createServerProxy } = __serverModule;
|
|
4140
|
-
const {
|
|
4783
|
+
const { resolveServerCommandArguments: resolveServerCommandArguments } = __serverModule;
|
|
4784
|
+
const { unwrapServerResult: unwrapServerResult } = __serverModule;
|
|
4141
4785
|
const { computed: computed } = __signalsModule;
|
|
4142
4786
|
const { createSignal: createSignal } = __signalsModule;
|
|
4143
4787
|
const { createSignalRegistry: createSignalRegistry } = __signalsModule;
|
|
4144
4788
|
const { effect: effect } = __signalsModule;
|
|
4145
4789
|
const { signal: signal } = __signalsModule;
|
|
4146
|
-
const api = { asyncSignal, Async, createApp, defineApp, attributeName, defineAttributeConfig, createCacheRegistry, defineCache, component, createComponentRegistry, defineComponent, delay, createHandlerRegistry, html, Loader, AsyncLoader, createPartialRegistry, createRegistryStore, createRouteRegistry, createRouter, defineRoute, route, createServerProxy,
|
|
4790
|
+
const api = { asyncSignal, Async, createApp, defineApp, readSnapshot, attributeName, defineAttributeConfig, createCacheRegistry, defineCache, component, createComponentRegistry, defineComponent, delay, createHandlerRegistry, html, Loader, AsyncLoader, createPartialRegistry, createRegistryStore, createRouteRegistry, createRouter, defineRoute, route, createScheduler, applyServerResult, createServerProxy, resolveServerCommandArguments, unwrapServerResult, computed, createSignal, createSignalRegistry, effect, signal };
|
|
4147
4791
|
assertNoUmdNamespaceConflicts(api, Async);
|
|
4148
4792
|
Object.assign(Async, api);
|
|
4149
4793
|
Async.Async = Async;
|