@async/framework 0.6.0 → 0.7.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 +15 -0
- package/README.md +13 -2
- package/framework.d.ts +3 -0
- package/framework.js +205 -21
- package/framework.min.js +190 -18
- package/framework.ts +4320 -2
- package/framework.umd.js +205 -21
- package/framework.umd.min.js +190 -18
- package/package.json +1 -1
- package/src/app.js +33 -1
- package/src/cache.js +27 -3
- package/src/index.js +1 -1
- package/src/router.js +98 -14
- package/src/server.js +44 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0 - 2026-06-17
|
|
4
|
+
|
|
5
|
+
- Added router navigation abort/version guards so stale route partials cannot
|
|
6
|
+
clobber newer navigations, and route partial contexts receive `this.abort`.
|
|
7
|
+
- Added ranked route matching so static and dynamic routes win over wildcard
|
|
8
|
+
fallbacks regardless of registration order.
|
|
9
|
+
- Added `readSnapshot(...)` and automatic browser activation from SSR snapshot
|
|
10
|
+
scripts.
|
|
11
|
+
- Fixed repeated server result application when proxy/server envelopes pass
|
|
12
|
+
through namespace or handler callers.
|
|
13
|
+
- Added in-flight `cache.getOrSet(...)` deduplication and clear proxy errors for
|
|
14
|
+
`File`, `Blob`, and `FormData` values that the JSON transport cannot send.
|
|
15
|
+
- Changed `framework.ts` from a source facade to a bundled TypeScript source
|
|
16
|
+
entrypoint.
|
|
17
|
+
|
|
3
18
|
## 0.6.0 - 2026-06-17
|
|
4
19
|
|
|
5
20
|
- Added `Loader` as the canonical public loader factory, including
|
package/README.md
CHANGED
|
@@ -100,7 +100,7 @@ production:
|
|
|
100
100
|
| `framework.min.js` | ESM | Compact browser module bundle |
|
|
101
101
|
| `framework.umd.js` | UMD | Readable script-tag/CommonJS-style bundle |
|
|
102
102
|
| `framework.umd.min.js` | UMD | Compact script-tag/CommonJS-style bundle and default CDN file |
|
|
103
|
-
| `framework.ts` | TypeScript source
|
|
103
|
+
| `framework.ts` | Bundled TypeScript source | TS-aware runtimes and higher-layer tooling |
|
|
104
104
|
| `framework.d.ts` | Type declarations | TypeScript declarations for the public API |
|
|
105
105
|
|
|
106
106
|
```html
|
|
@@ -215,6 +215,7 @@ import {
|
|
|
215
215
|
delay,
|
|
216
216
|
effect,
|
|
217
217
|
html,
|
|
218
|
+
readSnapshot,
|
|
218
219
|
route,
|
|
219
220
|
signal
|
|
220
221
|
} from "@async/framework";
|
|
@@ -411,6 +412,10 @@ await delay(250, this.abort);
|
|
|
411
412
|
If a dependency read through `this.signals.get(...)` changes, the async signal
|
|
412
413
|
reruns and the previous run is aborted.
|
|
413
414
|
|
|
415
|
+
Dependency reads are captured while the async signal function starts running.
|
|
416
|
+
Read signal dependencies before the first `await`; reads that happen later are
|
|
417
|
+
ordinary reads and do not create refresh subscriptions.
|
|
418
|
+
|
|
414
419
|
## HTML Protocol
|
|
415
420
|
|
|
416
421
|
Loader scans regular HTML attributes:
|
|
@@ -819,11 +824,17 @@ hydrate, diff, patch, or rerender:
|
|
|
819
824
|
```js
|
|
820
825
|
createApp(browserApp, {
|
|
821
826
|
root: document,
|
|
822
|
-
snapshot,
|
|
823
827
|
server: createServerProxy({ endpoint: "/__async/server" })
|
|
824
828
|
}).start();
|
|
825
829
|
```
|
|
826
830
|
|
|
831
|
+
If an `async:snapshot` script is present under the root or document,
|
|
832
|
+
`createApp(...)` reads it automatically. You can also inspect it directly:
|
|
833
|
+
|
|
834
|
+
```js
|
|
835
|
+
const snapshot = readSnapshot(document);
|
|
836
|
+
```
|
|
837
|
+
|
|
827
838
|
## Components
|
|
828
839
|
|
|
829
840
|
Components are scoped fragment functions. They return strings or `html`
|
package/framework.d.ts
CHANGED
|
@@ -262,6 +262,7 @@ export interface PartialContext {
|
|
|
262
262
|
cache?: CacheRegistry;
|
|
263
263
|
browserCache?: CacheRegistry;
|
|
264
264
|
partials: PartialRegistry;
|
|
265
|
+
abort?: AbortSignal;
|
|
265
266
|
request?: Request;
|
|
266
267
|
locals?: unknown;
|
|
267
268
|
[key: string]: unknown;
|
|
@@ -506,6 +507,7 @@ export interface AsyncNamespace extends AppHub {
|
|
|
506
507
|
asyncSignal: typeof asyncSignal;
|
|
507
508
|
createApp: typeof createApp;
|
|
508
509
|
defineApp: typeof defineApp;
|
|
510
|
+
readSnapshot: typeof readSnapshot;
|
|
509
511
|
attributeName: typeof attributeName;
|
|
510
512
|
defineAttributeConfig: typeof defineAttributeConfig;
|
|
511
513
|
createCacheRegistry: typeof createCacheRegistry;
|
|
@@ -537,6 +539,7 @@ export declare function asyncSignal<T = unknown>(id: string, fn: AsyncSignalFunc
|
|
|
537
539
|
export declare const Async: AppHub;
|
|
538
540
|
export declare function createApp(appOrDefinition?: AppHub | AppDefinition, options?: CreateAppOptions): AppRuntime;
|
|
539
541
|
export declare function defineApp(initial?: AppDefinition): AppHub;
|
|
542
|
+
export declare function readSnapshot(root?: Document | Element, options?: { attributes?: AttributeConfig }): { signals?: Record<string, unknown>; cache?: { browser?: Record<string, unknown> } };
|
|
540
543
|
export declare function attributeName(attributes: AttributeConfig | undefined, type: keyof NormalizedAttributeConfig, name: string): string;
|
|
541
544
|
export declare function defineAttributeConfig(config?: AttributeConfig): NormalizedAttributeConfig;
|
|
542
545
|
export declare function createCacheRegistry(initialMap?: Record<string, CacheDefinition | CacheDefinitionOptions>, options?: { now?: () => number; registry?: RegistryStore; type?: "cache.browser" | "cache.server" }): CacheRegistry;
|
package/framework.js
CHANGED
|
@@ -536,6 +536,7 @@ const __cacheModule = (() => {
|
|
|
536
536
|
const registryStore = registry ?? createRegistryStore();
|
|
537
537
|
const definitions = registryStore._map(type);
|
|
538
538
|
const entries = registryStore._map(`${type}.entries`);
|
|
539
|
+
const pending = new Map();
|
|
539
540
|
|
|
540
541
|
const registryApi = attachRegistryInspection({
|
|
541
542
|
register(id, definition = defineCache()) {
|
|
@@ -597,19 +598,37 @@ const __cacheModule = (() => {
|
|
|
597
598
|
if (cached !== undefined) {
|
|
598
599
|
return cached;
|
|
599
600
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
601
|
+
if (pending.has(key)) {
|
|
602
|
+
return pending.get(key);
|
|
603
|
+
}
|
|
604
|
+
let promise;
|
|
605
|
+
promise = Promise.resolve()
|
|
606
|
+
.then(fn)
|
|
607
|
+
.then((value) => {
|
|
608
|
+
if (pending.get(key) === promise) {
|
|
609
|
+
registryApi.set(key, value, options);
|
|
610
|
+
}
|
|
611
|
+
return value;
|
|
612
|
+
})
|
|
613
|
+
.finally(() => {
|
|
614
|
+
if (pending.get(key) === promise) {
|
|
615
|
+
pending.delete(key);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
pending.set(key, promise);
|
|
619
|
+
return promise;
|
|
603
620
|
},
|
|
604
621
|
|
|
605
622
|
delete(key) {
|
|
606
623
|
assertKey(key);
|
|
624
|
+
pending.delete(key);
|
|
607
625
|
return entries.delete(key);
|
|
608
626
|
},
|
|
609
627
|
|
|
610
628
|
clear(prefix) {
|
|
611
629
|
if (prefix === undefined) {
|
|
612
630
|
entries.clear();
|
|
631
|
+
pending.clear();
|
|
613
632
|
return registryApi;
|
|
614
633
|
}
|
|
615
634
|
for (const key of [...entries.keys()]) {
|
|
@@ -617,6 +636,11 @@ const __cacheModule = (() => {
|
|
|
617
636
|
entries.delete(key);
|
|
618
637
|
}
|
|
619
638
|
}
|
|
639
|
+
for (const key of [...pending.keys()]) {
|
|
640
|
+
if (key.startsWith(prefix)) {
|
|
641
|
+
pending.delete(key);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
620
644
|
return registryApi;
|
|
621
645
|
},
|
|
622
646
|
|
|
@@ -1752,6 +1776,8 @@ const __componentModule = (() => {
|
|
|
1752
1776
|
const __serverModule = (() => {
|
|
1753
1777
|
const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
|
|
1754
1778
|
const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
|
|
1779
|
+
const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
|
|
1780
|
+
const appliedServerValues = new WeakSet();
|
|
1755
1781
|
|
|
1756
1782
|
function createServerRegistry(initialMap = {}, options = {}) {
|
|
1757
1783
|
const registryStore = options.registry ?? createRegistryStore();
|
|
@@ -1858,6 +1884,7 @@ const __serverModule = (() => {
|
|
|
1858
1884
|
input: context.input ?? defaultInput(runContext),
|
|
1859
1885
|
signals: context.signalValues ?? snapshotSignalPaths(context.signalPaths, runContext.signals)
|
|
1860
1886
|
};
|
|
1887
|
+
assertJsonTransportable(body);
|
|
1861
1888
|
|
|
1862
1889
|
const response = await fetchImpl(joinEndpoint(endpoint, id), {
|
|
1863
1890
|
method: "POST",
|
|
@@ -1875,7 +1902,7 @@ const __serverModule = (() => {
|
|
|
1875
1902
|
|
|
1876
1903
|
const result = await readServerResponse(response);
|
|
1877
1904
|
await applyServerResult(result, runContext);
|
|
1878
|
-
return unwrapServerResult(result);
|
|
1905
|
+
return markAppliedServerValue(unwrapServerResult(result));
|
|
1879
1906
|
}
|
|
1880
1907
|
|
|
1881
1908
|
return createServerNamespace(run, {
|
|
@@ -1910,6 +1937,9 @@ const __serverModule = (() => {
|
|
|
1910
1937
|
if (!isServerEnvelope(result)) {
|
|
1911
1938
|
return result;
|
|
1912
1939
|
}
|
|
1940
|
+
if (result[appliedServerResult] || appliedServerValues.has(result)) {
|
|
1941
|
+
return result;
|
|
1942
|
+
}
|
|
1913
1943
|
|
|
1914
1944
|
if (result.signals && context.signals) {
|
|
1915
1945
|
for (const [path, value] of Object.entries(result.signals)) {
|
|
@@ -1933,6 +1963,12 @@ const __serverModule = (() => {
|
|
|
1933
1963
|
throw toError(result.error);
|
|
1934
1964
|
}
|
|
1935
1965
|
|
|
1966
|
+
Object.defineProperty(result, appliedServerResult, {
|
|
1967
|
+
configurable: true,
|
|
1968
|
+
enumerable: false,
|
|
1969
|
+
value: true
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1936
1972
|
return result;
|
|
1937
1973
|
}
|
|
1938
1974
|
|
|
@@ -1943,6 +1979,13 @@ const __serverModule = (() => {
|
|
|
1943
1979
|
return result;
|
|
1944
1980
|
}
|
|
1945
1981
|
|
|
1982
|
+
function markAppliedServerValue(value) {
|
|
1983
|
+
if (value && typeof value === "object") {
|
|
1984
|
+
appliedServerValues.add(value);
|
|
1985
|
+
}
|
|
1986
|
+
return value;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1946
1989
|
function defaultInput(context = {}) {
|
|
1947
1990
|
const form = findForm(context);
|
|
1948
1991
|
if (form) {
|
|
@@ -2120,6 +2163,30 @@ const __serverModule = (() => {
|
|
|
2120
2163
|
return output;
|
|
2121
2164
|
}
|
|
2122
2165
|
|
|
2166
|
+
function assertJsonTransportable(value, seen = new Set()) {
|
|
2167
|
+
if (value == null || typeof value !== "object") {
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
if (seen.has(value)) {
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
seen.add(value);
|
|
2174
|
+
|
|
2175
|
+
const tag = Object.prototype.toString.call(value);
|
|
2176
|
+
if (tag === "[object File]" || tag === "[object Blob]" || tag === "[object FormData]") {
|
|
2177
|
+
throw new Error("Server proxy JSON transport does not support File, Blob, or FormData values yet.");
|
|
2178
|
+
}
|
|
2179
|
+
if (Array.isArray(value)) {
|
|
2180
|
+
for (const item of value) {
|
|
2181
|
+
assertJsonTransportable(item, seen);
|
|
2182
|
+
}
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
for (const item of Object.values(value)) {
|
|
2186
|
+
assertJsonTransportable(item, seen);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2123
2190
|
function joinEndpoint(endpoint, id) {
|
|
2124
2191
|
return `${String(endpoint).replace(/\/$/, "")}/${encodeURIComponent(id)}`;
|
|
2125
2192
|
}
|
|
@@ -3219,6 +3286,7 @@ const __routerModule = (() => {
|
|
|
3219
3286
|
const nextRoute = normalizeRoute(pattern, definition);
|
|
3220
3287
|
entries.set(pattern, nextRoute.definition);
|
|
3221
3288
|
routes.push(nextRoute);
|
|
3289
|
+
sortRoutes(routes);
|
|
3222
3290
|
return nextRoute;
|
|
3223
3291
|
},
|
|
3224
3292
|
|
|
@@ -3291,6 +3359,7 @@ const __routerModule = (() => {
|
|
|
3291
3359
|
const nextRoute = normalizeRoute(pattern, definition);
|
|
3292
3360
|
entries.set(pattern, nextRoute.definition);
|
|
3293
3361
|
routes.push(nextRoute);
|
|
3362
|
+
sortRoutes(routes);
|
|
3294
3363
|
}
|
|
3295
3364
|
}
|
|
3296
3365
|
|
|
@@ -3327,6 +3396,8 @@ const __routerModule = (() => {
|
|
|
3327
3396
|
const ownsLoader = !loader;
|
|
3328
3397
|
const cleanups = new Set();
|
|
3329
3398
|
let destroyed = false;
|
|
3399
|
+
let navigationVersion = 0;
|
|
3400
|
+
let activeNavigation;
|
|
3330
3401
|
|
|
3331
3402
|
const api = {
|
|
3332
3403
|
mode,
|
|
@@ -3366,7 +3437,7 @@ const __routerModule = (() => {
|
|
|
3366
3437
|
},
|
|
3367
3438
|
|
|
3368
3439
|
match(url) {
|
|
3369
|
-
return routes.match(url);
|
|
3440
|
+
return routes.match(resolveUrl(url));
|
|
3370
3441
|
},
|
|
3371
3442
|
|
|
3372
3443
|
prefetch(url) {
|
|
@@ -3391,7 +3462,7 @@ const __routerModule = (() => {
|
|
|
3391
3462
|
return null;
|
|
3392
3463
|
}
|
|
3393
3464
|
|
|
3394
|
-
const target =
|
|
3465
|
+
const target = resolveUrl(url);
|
|
3395
3466
|
if (mode === "ssr-spa") {
|
|
3396
3467
|
return fetchRoutePartial(target, options);
|
|
3397
3468
|
}
|
|
@@ -3403,6 +3474,7 @@ const __routerModule = (() => {
|
|
|
3403
3474
|
return;
|
|
3404
3475
|
}
|
|
3405
3476
|
destroyed = true;
|
|
3477
|
+
activeNavigation?.controller.abort(new Error("Router has been destroyed."));
|
|
3406
3478
|
for (const cleanup of cleanups) {
|
|
3407
3479
|
cleanup();
|
|
3408
3480
|
}
|
|
@@ -3442,24 +3514,37 @@ const __routerModule = (() => {
|
|
|
3442
3514
|
async function renderLocalRoutePartial(target, options = {}) {
|
|
3443
3515
|
const matched = api.match(target);
|
|
3444
3516
|
if (!matched) {
|
|
3517
|
+
beginNavigation(target, null);
|
|
3445
3518
|
setNoRouteError(target);
|
|
3446
3519
|
return null;
|
|
3447
3520
|
}
|
|
3448
3521
|
|
|
3522
|
+
const navigation = beginNavigation(target, matched);
|
|
3449
3523
|
setMatchedRouterState(target, matched, { pending: true, error: null });
|
|
3450
3524
|
|
|
3451
3525
|
try {
|
|
3452
3526
|
if (!matched.route?.partial || !partials?.resolve?.(matched.route.partial)) {
|
|
3453
3527
|
const error = new Error(`Route "${target.pathname}" does not have a registered partial.`);
|
|
3454
|
-
|
|
3528
|
+
if (isActiveNavigation(navigation)) {
|
|
3529
|
+
setRouterState({ pending: false, error });
|
|
3530
|
+
}
|
|
3455
3531
|
return null;
|
|
3456
3532
|
}
|
|
3457
3533
|
|
|
3458
|
-
const result = await partials.render(matched.route.partial, matched.params, contextFor(matched));
|
|
3459
|
-
|
|
3534
|
+
const result = await partials.render(matched.route.partial, matched.params, contextFor(matched, navigation));
|
|
3535
|
+
if (!isActiveNavigation(navigation)) {
|
|
3536
|
+
return null;
|
|
3537
|
+
}
|
|
3538
|
+
await applyNavigationResult(result, target, options, navigation);
|
|
3539
|
+
if (!isActiveNavigation(navigation)) {
|
|
3540
|
+
return null;
|
|
3541
|
+
}
|
|
3460
3542
|
setRouterState({ pending: false, error: null });
|
|
3461
3543
|
return result;
|
|
3462
3544
|
} catch (error) {
|
|
3545
|
+
if (!isActiveNavigation(navigation)) {
|
|
3546
|
+
return null;
|
|
3547
|
+
}
|
|
3463
3548
|
setRouterState({ pending: false, error });
|
|
3464
3549
|
throw error;
|
|
3465
3550
|
}
|
|
@@ -3467,26 +3552,43 @@ const __routerModule = (() => {
|
|
|
3467
3552
|
|
|
3468
3553
|
async function fetchRoutePartial(target, options = {}) {
|
|
3469
3554
|
const matched = api.match(target);
|
|
3555
|
+
const navigation = beginNavigation(target, matched);
|
|
3470
3556
|
setMatchedRouterState(target, matched, { pending: true, error: null });
|
|
3471
3557
|
|
|
3472
3558
|
try {
|
|
3473
|
-
const result = await fetchRoute(target.href);
|
|
3474
|
-
|
|
3559
|
+
const result = await fetchRoute(target.href, { signal: navigation.abort });
|
|
3560
|
+
if (!isActiveNavigation(navigation)) {
|
|
3561
|
+
return null;
|
|
3562
|
+
}
|
|
3563
|
+
await applyNavigationResult(result, target, options, navigation);
|
|
3564
|
+
if (!isActiveNavigation(navigation)) {
|
|
3565
|
+
return null;
|
|
3566
|
+
}
|
|
3475
3567
|
setRouterState({ pending: false, error: null });
|
|
3476
3568
|
return result;
|
|
3477
3569
|
} catch (error) {
|
|
3570
|
+
if (!isActiveNavigation(navigation)) {
|
|
3571
|
+
return null;
|
|
3572
|
+
}
|
|
3478
3573
|
setRouterState({ pending: false, error });
|
|
3479
3574
|
throw error;
|
|
3480
3575
|
}
|
|
3481
3576
|
}
|
|
3482
3577
|
|
|
3483
|
-
async function applyNavigationResult(result, target, options) {
|
|
3578
|
+
async function applyNavigationResult(result, target, options, navigation) {
|
|
3579
|
+
if (!isActiveNavigation(navigation)) {
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3484
3582
|
await applyServerResult(result, {
|
|
3485
3583
|
signals: signalRegistry,
|
|
3486
3584
|
loader: loaderInstance,
|
|
3487
3585
|
router: api,
|
|
3488
|
-
cache
|
|
3586
|
+
cache,
|
|
3587
|
+
abort: navigation?.abort
|
|
3489
3588
|
});
|
|
3589
|
+
if (!isActiveNavigation(navigation)) {
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3490
3592
|
if (result?.html != null && !result.boundary && !result.redirect) {
|
|
3491
3593
|
loaderInstance.swap(boundary, result.html);
|
|
3492
3594
|
}
|
|
@@ -3500,14 +3602,15 @@ const __routerModule = (() => {
|
|
|
3500
3602
|
documentRef.defaultView?.history?.pushState?.({}, "", target.href);
|
|
3501
3603
|
}
|
|
3502
3604
|
|
|
3503
|
-
async function fetchRoute(url, { prefetch = false } = {}) {
|
|
3605
|
+
async function fetchRoute(url, { prefetch = false, signal } = {}) {
|
|
3504
3606
|
if (typeof fetchImpl !== "function") {
|
|
3505
3607
|
throw new Error("Router navigation requires a partial registry or fetch.");
|
|
3506
3608
|
}
|
|
3507
3609
|
const response = await fetchImpl(`${routeEndpoint}?to=${encodeURIComponent(String(url))}`, {
|
|
3508
3610
|
headers: {
|
|
3509
3611
|
accept: "application/json, text/html"
|
|
3510
|
-
}
|
|
3612
|
+
},
|
|
3613
|
+
signal
|
|
3511
3614
|
});
|
|
3512
3615
|
if (!response.ok) {
|
|
3513
3616
|
throw new Error(`Route "${url}" failed with ${response.status}.`);
|
|
@@ -3522,7 +3625,7 @@ const __routerModule = (() => {
|
|
|
3522
3625
|
return { boundary, html: await response.text() };
|
|
3523
3626
|
}
|
|
3524
3627
|
|
|
3525
|
-
function contextFor(matched) {
|
|
3628
|
+
function contextFor(matched, navigation) {
|
|
3526
3629
|
return {
|
|
3527
3630
|
params: matched.params,
|
|
3528
3631
|
route: matched.route,
|
|
@@ -3532,8 +3635,26 @@ const __routerModule = (() => {
|
|
|
3532
3635
|
loader: loaderInstance,
|
|
3533
3636
|
server,
|
|
3534
3637
|
cache,
|
|
3535
|
-
abort:
|
|
3638
|
+
abort: navigation?.abort
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
function beginNavigation(target, matched) {
|
|
3643
|
+
activeNavigation?.controller.abort(new Error(`Router navigation superseded by ${target.pathname}${target.search}.`));
|
|
3644
|
+
const controller = new AbortController();
|
|
3645
|
+
const navigation = {
|
|
3646
|
+
id: ++navigationVersion,
|
|
3647
|
+
controller,
|
|
3648
|
+
abort: controller.signal,
|
|
3649
|
+
target,
|
|
3650
|
+
matched
|
|
3536
3651
|
};
|
|
3652
|
+
activeNavigation = navigation;
|
|
3653
|
+
return navigation;
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
function isActiveNavigation(navigation) {
|
|
3657
|
+
return !destroyed && navigation && activeNavigation?.id === navigation.id && !navigation.abort.aborted;
|
|
3537
3658
|
}
|
|
3538
3659
|
|
|
3539
3660
|
function updateStateFromLocation() {
|
|
@@ -3570,7 +3691,14 @@ const __routerModule = (() => {
|
|
|
3570
3691
|
}
|
|
3571
3692
|
|
|
3572
3693
|
function currentUrl() {
|
|
3573
|
-
return
|
|
3694
|
+
return resolveUrl(documentRef.defaultView?.location?.href ?? "http://localhost/");
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
function resolveUrl(url) {
|
|
3698
|
+
if (url instanceof URL) {
|
|
3699
|
+
return url;
|
|
3700
|
+
}
|
|
3701
|
+
return new URL(String(url), documentRef.defaultView?.location?.href ?? "http://localhost/");
|
|
3574
3702
|
}
|
|
3575
3703
|
|
|
3576
3704
|
function assertActive() {
|
|
@@ -3587,6 +3715,7 @@ const __routerModule = (() => {
|
|
|
3587
3715
|
pattern,
|
|
3588
3716
|
regex,
|
|
3589
3717
|
keys,
|
|
3718
|
+
score: routeScore(pattern),
|
|
3590
3719
|
definition: normalized
|
|
3591
3720
|
};
|
|
3592
3721
|
}
|
|
@@ -3655,6 +3784,28 @@ const __routerModule = (() => {
|
|
|
3655
3784
|
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3656
3785
|
}
|
|
3657
3786
|
|
|
3787
|
+
function sortRoutes(routes) {
|
|
3788
|
+
routes.sort((left, right) => right.score - left.score || right.pattern.length - left.pattern.length);
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
function routeScore(pattern) {
|
|
3792
|
+
if (pattern === "*") {
|
|
3793
|
+
return -1;
|
|
3794
|
+
}
|
|
3795
|
+
return pattern
|
|
3796
|
+
.split("/")
|
|
3797
|
+
.filter(Boolean)
|
|
3798
|
+
.reduce((score, segment) => {
|
|
3799
|
+
if (segment === "*") {
|
|
3800
|
+
return score;
|
|
3801
|
+
}
|
|
3802
|
+
if (segment.startsWith(":")) {
|
|
3803
|
+
return score + 2;
|
|
3804
|
+
}
|
|
3805
|
+
return score + 4;
|
|
3806
|
+
}, pattern === "/" ? 3 : 0);
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3658
3809
|
function assertPattern(pattern) {
|
|
3659
3810
|
if (typeof pattern !== "string" || (pattern !== "*" && !pattern.startsWith("/"))) {
|
|
3660
3811
|
throw new TypeError("Route pattern must be a path string or \"*\".");
|
|
@@ -3738,7 +3889,7 @@ const __appModule = (() => {
|
|
|
3738
3889
|
let started = false;
|
|
3739
3890
|
let destroyed = false;
|
|
3740
3891
|
|
|
3741
|
-
applySnapshot(signals, browserCache, options.snapshot);
|
|
3892
|
+
applySnapshot(signals, browserCache, options.snapshot ?? (target === "browser" ? readSnapshot(options.root, { attributes }) : undefined));
|
|
3742
3893
|
attachServerCache(server, serverCache);
|
|
3743
3894
|
|
|
3744
3895
|
const runtime = {
|
|
@@ -3906,6 +4057,38 @@ const __appModule = (() => {
|
|
|
3906
4057
|
|
|
3907
4058
|
const Async = defineApp();
|
|
3908
4059
|
|
|
4060
|
+
function readSnapshot(root = globalThis.document, { attributes } = {}) {
|
|
4061
|
+
const attributeConfig = normalizeAttributeConfig(attributes);
|
|
4062
|
+
const snapshotAttr = attributeName(attributeConfig, "async", "snapshot");
|
|
4063
|
+
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
4064
|
+
const rootNode = root ?? documentRef;
|
|
4065
|
+
if (!rootNode?.querySelectorAll && !documentRef?.querySelectorAll) {
|
|
4066
|
+
return {};
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4069
|
+
for (const searchRoot of new Set([rootNode, documentRef])) {
|
|
4070
|
+
if (!searchRoot?.querySelectorAll) {
|
|
4071
|
+
continue;
|
|
4072
|
+
}
|
|
4073
|
+
for (const script of searchRoot.querySelectorAll("script[type='application/json'], script")) {
|
|
4074
|
+
if (!script.hasAttribute?.(snapshotAttr)) {
|
|
4075
|
+
continue;
|
|
4076
|
+
}
|
|
4077
|
+
const source = script.textContent?.trim() ?? "";
|
|
4078
|
+
if (!source) {
|
|
4079
|
+
return {};
|
|
4080
|
+
}
|
|
4081
|
+
try {
|
|
4082
|
+
return JSON.parse(source);
|
|
4083
|
+
} catch (cause) {
|
|
4084
|
+
throw new Error(`Could not parse Async snapshot: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
return {};
|
|
4090
|
+
}
|
|
4091
|
+
|
|
3909
4092
|
function applyUseToRuntime(runtime, normalized) {
|
|
3910
4093
|
applyRegistryUse(runtime.signals, runtime.registry, normalized.signal);
|
|
3911
4094
|
applyRegistryUse(runtime.handlers, runtime.registry, normalized.handler);
|
|
@@ -4066,7 +4249,7 @@ const __appModule = (() => {
|
|
|
4066
4249
|
function escapeScriptJson(value) {
|
|
4067
4250
|
return JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
4068
4251
|
}
|
|
4069
|
-
return { defineApp, createApp, Async };
|
|
4252
|
+
return { defineApp, createApp, readSnapshot, Async };
|
|
4070
4253
|
})();
|
|
4071
4254
|
|
|
4072
4255
|
const __delayModule = (() => {
|
|
@@ -4107,6 +4290,7 @@ const { asyncSignal: asyncSignal } = __asyncSignalModule;
|
|
|
4107
4290
|
const { Async: Async } = __appModule;
|
|
4108
4291
|
const { createApp: createApp } = __appModule;
|
|
4109
4292
|
const { defineApp: defineApp } = __appModule;
|
|
4293
|
+
const { readSnapshot: readSnapshot } = __appModule;
|
|
4110
4294
|
const { attributeName: attributeName } = __attributesModule;
|
|
4111
4295
|
const { defineAttributeConfig: defineAttributeConfig } = __attributesModule;
|
|
4112
4296
|
const { createCacheRegistry: createCacheRegistry } = __cacheModule;
|
|
@@ -4133,4 +4317,4 @@ const { createSignalRegistry: createSignalRegistry } = __signalsModule;
|
|
|
4133
4317
|
const { effect: effect } = __signalsModule;
|
|
4134
4318
|
const { signal: signal } = __signalsModule;
|
|
4135
4319
|
|
|
4136
|
-
export { asyncSignal, Async, createApp, defineApp, attributeName, defineAttributeConfig, createCacheRegistry, defineCache, component, createComponentRegistry, defineComponent, delay, createHandlerRegistry, html, Loader, AsyncLoader, createPartialRegistry, createRegistryStore, createRouteRegistry, createRouter, defineRoute, route, createServerProxy, createServerRegistry, computed, createSignal, createSignalRegistry, effect, signal };
|
|
4320
|
+
export { asyncSignal, Async, createApp, defineApp, readSnapshot, attributeName, defineAttributeConfig, createCacheRegistry, defineCache, component, createComponentRegistry, defineComponent, delay, createHandlerRegistry, html, Loader, AsyncLoader, createPartialRegistry, createRegistryStore, createRouteRegistry, createRouter, defineRoute, route, createServerProxy, createServerRegistry, computed, createSignal, createSignalRegistry, effect, signal };
|