@ogygia/core 0.12.3 → 0.13.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/dist/LateIsland.svelte +3 -2
- package/dist/LateIslandAwait.svelte +6 -2
- package/dist/Region.svelte +22 -33
- package/dist/boundary.d.ts +0 -2
- package/dist/boundary.js +8 -6
- package/dist/compiler/debarrel/barrel.d.ts +15 -8
- package/dist/compiler/debarrel/barrel.js +92 -45
- package/dist/compiler/debarrel/plugin.js +41 -17
- package/dist/compiler/dev/dev-hmr.d.ts +3 -0
- package/dist/compiler/dev/dev-hmr.js +3 -0
- package/dist/compiler/driver.js +11 -4
- package/dist/compiler/flags.js +21 -7
- package/dist/compiler/fouc-css.js +5 -1
- package/dist/compiler/kit.js +5 -1
- package/dist/compiler/link/build-output.js +12 -3
- package/dist/compiler/macros/bake.js +18 -4
- package/dist/compiler/macros/dollar.js +6 -1
- package/dist/compiler/macros/source.js +6 -1
- package/dist/compiler/macros/store.js +10 -2
- package/dist/compiler/macros/wire.js +6 -1
- package/dist/compiler/parse/scan.js +10 -3
- package/dist/compiler/region/transform.js +188 -74
- package/dist/context-bridge.js +13 -5
- package/dist/context.d.ts +9 -0
- package/dist/context.js +30 -1
- package/dist/document.d.ts +1 -1
- package/dist/document.js +7 -1
- package/dist/early-holes-head.d.ts +5 -0
- package/dist/early-holes-head.js +20 -0
- package/dist/early-holes.d.ts +34 -0
- package/dist/early-holes.js +51 -0
- package/dist/entry-hydrate.js +9 -12
- package/dist/events/bus.js +7 -2
- package/dist/events/schema.d.ts +2 -2
- package/dist/fn-transport.js +4 -3
- package/dist/hooks.js +94 -163
- package/dist/insights/page-assets.js +11 -3
- package/dist/insights/page-insights.d.ts +0 -2
- package/dist/insights/page-insights.js +14 -10
- package/dist/insights/session-insights.js +2 -2
- package/dist/internal-instruments.d.ts +3 -1
- package/dist/internal-instruments.js +3 -1
- package/dist/internal-server.d.ts +3 -2
- package/dist/internal-server.js +3 -2
- package/dist/internal.d.ts +1 -2
- package/dist/internal.js +1 -2
- package/dist/keep-fallback.d.ts +0 -2
- package/dist/keep-fallback.js +4 -5
- package/dist/live-transport.js +3 -2
- package/dist/messages/build.d.ts +585 -0
- package/dist/messages/build.js +800 -0
- package/dist/messages/core.d.ts +464 -0
- package/dist/messages/core.js +843 -0
- package/dist/messages/define.d.ts +62 -0
- package/dist/messages/define.js +49 -0
- package/dist/messages/index.d.ts +6 -0
- package/dist/messages/index.js +6 -0
- package/dist/messages/island.d.ts +4 -0
- package/dist/messages/island.js +34 -0
- package/dist/messages/lazy.d.ts +3 -0
- package/dist/messages/lazy.js +22 -0
- package/dist/messages/prose.d.ts +15 -0
- package/dist/messages/prose.js +43 -0
- package/dist/messages/server.d.ts +142 -0
- package/dist/messages/server.js +246 -0
- package/dist/nested.d.ts +3 -0
- package/dist/nested.js +8 -1
- package/dist/plugin/css.js +14 -13
- package/dist/plugin/facts.d.ts +2 -0
- package/dist/plugin/facts.js +10 -6
- package/dist/plugin/hooks.d.ts +7 -17
- package/dist/plugin/hooks.js +32 -4
- package/dist/plugin/index.d.ts +9 -2
- package/dist/plugin/request-scope.d.ts +20 -0
- package/dist/plugin/request-scope.js +149 -0
- package/dist/plugin/server.d.ts +20 -2
- package/dist/plugin/server.js +98 -28
- package/dist/plugin/testing.d.ts +6 -0
- package/dist/plugin/testing.js +14 -2
- package/dist/policies.d.ts +21 -0
- package/dist/policies.js +66 -0
- package/dist/preference.js +6 -1
- package/dist/preload.js +6 -9
- package/dist/ref-watch.d.ts +0 -10
- package/dist/ref-watch.js +14 -1
- package/dist/ref.js +17 -5
- package/dist/region-snippet.js +9 -3
- package/dist/region.js +2 -1
- package/dist/report-thrown.d.ts +9 -0
- package/dist/report-thrown.js +15 -0
- package/dist/rules.d.ts +17 -0
- package/dist/rules.js +57 -0
- package/dist/runtime/beacon.d.ts +0 -4
- package/dist/runtime/beacon.js +58 -43
- package/dist/runtime/boot-link.js +19 -2
- package/dist/runtime/client-hooks.js +2 -1
- package/dist/runtime/concurrency.d.ts +16 -5
- package/dist/runtime/concurrency.js +123 -7
- package/dist/runtime/core.d.ts +3 -1
- package/dist/runtime/core.js +718 -384
- package/dist/runtime/devalue-lane.d.ts +13 -0
- package/dist/runtime/devalue-lane.js +49 -0
- package/dist/runtime/disclose-version.d.ts +1 -0
- package/dist/runtime/disclose-version.js +14 -0
- package/dist/runtime/early-holes.d.ts +4 -0
- package/dist/runtime/early-holes.js +40 -0
- package/dist/runtime/entry-locations.js +40 -49
- package/dist/runtime/foreign-props.d.ts +0 -7
- package/dist/runtime/foreign-props.js +2 -1
- package/dist/runtime/form-continuity.d.ts +2 -2
- package/dist/runtime/form-continuity.js +30 -8
- package/dist/runtime/frame-nav.d.ts +7 -1
- package/dist/runtime/frame-nav.js +84 -18
- package/dist/runtime/frame-store.d.ts +25 -16
- package/dist/runtime/frame-store.js +124 -98
- package/dist/runtime/frames.js +8 -4
- package/dist/runtime/hole-facts.js +6 -7
- package/dist/runtime/hole-fetch.d.ts +17 -0
- package/dist/runtime/hole-fetch.js +47 -0
- package/dist/runtime/hydrate-core.d.ts +4 -4
- package/dist/runtime/hydrate-core.js +80 -64
- package/dist/runtime/import-again.d.ts +33 -0
- package/dist/runtime/import-again.js +217 -0
- package/dist/runtime/interaction-replay.d.ts +5 -3
- package/dist/runtime/interaction-replay.js +6 -6
- package/dist/runtime/interaction.d.ts +14 -3
- package/dist/runtime/interaction.js +46 -23
- package/dist/runtime/kit-page-thread.svelte.d.ts +2 -1
- package/dist/runtime/kit-page-thread.svelte.js +44 -20
- package/dist/runtime/lakes.d.ts +1 -1
- package/dist/runtime/lakes.js +18 -5
- package/dist/runtime/live.d.ts +1 -2
- package/dist/runtime/live.js +6 -8
- package/dist/runtime/load-scheduler.d.ts +29 -18
- package/dist/runtime/load-scheduler.js +236 -136
- package/dist/runtime/nav-handle.js +25 -6
- package/dist/runtime/navigation.d.ts +24 -0
- package/dist/runtime/navigation.js +24 -0
- package/dist/runtime/ownership.d.ts +1 -1
- package/dist/runtime/page-defer.js +3 -3
- package/dist/runtime/parse-html.d.ts +15 -4
- package/dist/runtime/parse-html.js +24 -5
- package/dist/runtime/pushable-props.svelte.d.ts +21 -0
- package/dist/runtime/pushable-props.svelte.js +37 -0
- package/dist/runtime/region-endpoint-url.js +4 -6
- package/dist/runtime/region-machines.d.ts +111 -0
- package/dist/runtime/region-machines.js +97 -0
- package/dist/runtime/region-sheets.d.ts +31 -0
- package/dist/runtime/region-sheets.js +49 -0
- package/dist/runtime/restore-report.d.ts +1 -0
- package/dist/runtime/restore-report.js +36 -0
- package/dist/runtime/restore.d.ts +5 -2
- package/dist/runtime/restore.js +33 -14
- package/dist/runtime/router-nav.d.ts +32 -12
- package/dist/runtime/router-nav.js +320 -228
- package/dist/runtime/router.d.ts +83 -19
- package/dist/runtime/router.js +297 -129
- package/dist/runtime/rules.d.ts +35 -0
- package/dist/runtime/rules.js +137 -0
- package/dist/runtime/schedule.d.ts +6 -3
- package/dist/runtime/schedule.js +172 -57
- package/dist/runtime/seeds.d.ts +5 -1
- package/dist/runtime/seeds.js +11 -5
- package/dist/runtime/session.d.ts +46 -7
- package/dist/runtime/session.js +78 -10
- package/dist/runtime/slots.d.ts +53 -20
- package/dist/runtime/slots.js +2 -3
- package/dist/runtime/soft-refresh.d.ts +16 -0
- package/dist/runtime/soft-refresh.js +14 -0
- package/dist/runtime/svelte-warnings.d.ts +4 -10
- package/dist/runtime/svelte-warnings.js +7 -4
- package/dist/runtime/wire-classes.js +21 -8
- package/dist/runtime/wire-format.js +6 -3
- package/dist/script.js +8 -7
- package/dist/seed-ref-reviver.d.ts +0 -9
- package/dist/seed-ref-reviver.js +2 -1
- package/dist/seed-refs.d.ts +46 -36
- package/dist/seed-refs.js +131 -92
- package/dist/server/document-tail.d.ts +6 -0
- package/dist/server/document-tail.js +7 -1
- package/dist/server/early-holes-csp.d.ts +14 -0
- package/dist/server/early-holes-csp.js +73 -0
- package/dist/server/page-facts.d.ts +4 -1
- package/dist/server/page-facts.js +4 -1
- package/dist/server/page-seed.d.ts +2 -2
- package/dist/server/page-seed.js +104 -11
- package/dist/server/page-stream.d.ts +14 -1
- package/dist/server/page-stream.js +119 -2
- package/dist/server/personal-response.d.ts +0 -5
- package/dist/server/personal-response.js +6 -2
- package/dist/server/props-wire.js +5 -1
- package/dist/server/region-endpoint.js +10 -2
- package/dist/server/render-cache.d.ts +24 -20
- package/dist/server/render-cache.js +99 -40
- package/dist/server/render-page.js +46 -15
- package/dist/server/request-stats.d.ts +10 -8
- package/dist/server/request-stats.js +20 -12
- package/dist/server/rules.d.ts +11 -0
- package/dist/server/rules.js +64 -0
- package/dist/server/signing-key.js +31 -18
- package/dist/server/stream-regions.d.ts +14 -1
- package/dist/server/stream-regions.js +38 -2
- package/dist/shared-state.js +5 -1
- package/dist/shims/app-navigation.js +3 -2
- package/dist/shims/page-store.svelte.js +16 -2
- package/dist/source.js +7 -2
- package/dist/store-transport.js +12 -5
- package/dist/transport.js +3 -2
- package/dist/vite/index.js +17 -10
- package/dist/vite/kit-dirs.js +7 -1
- package/dist/vite/options.d.ts +2 -1
- package/dist/vite/options.js +11 -20
- package/dist/vite/package-files.js +13 -3
- package/dist/vite/paths.d.ts +2 -0
- package/dist/vite/paths.js +4 -4
- package/dist/vite/plugin-api.js +42 -14
- package/dist/vite/style-preprocess.js +10 -3
- package/package.json +8 -1
- package/dist/KeepHost.svelte +0 -19
- package/dist/KeepHost.svelte.d.ts +0 -10
- package/dist/LiveHost.svelte +0 -19
- package/dist/LiveHost.svelte.d.ts +0 -10
- package/dist/NestedProvider.svelte +0 -13
- package/dist/NestedProvider.svelte.d.ts +0 -8
- package/dist/runtime/page-cache.d.ts +0 -23
- package/dist/runtime/page-cache.js +0 -74
|
@@ -1,14 +1,49 @@
|
|
|
1
1
|
import { notify, watch, watcher_count } from "../ref-watch.js";
|
|
2
|
+
import { STALE, flights, scope } from "@ogygia/machine";
|
|
2
3
|
// src/runtime/frame-store.ts
|
|
3
4
|
/** Frame addresses live in the hub's watcher space under a namespace, so region binders subscribe
|
|
4
5
|
* through the ONE hub `watch`/`notify` primitive (hub v2 phase W) — the frame store no longer owns
|
|
5
|
-
* a parallel subscriber set.
|
|
6
|
+
* a parallel subscriber set. */
|
|
6
7
|
const frame_id = (a) => "frame:" + a;
|
|
8
|
+
/** The name of a refused answer's error (./region-endpoint-url.ts): by name, since the store is one
|
|
9
|
+
* per document across bundle copies, and a copy's class is not another's. */
|
|
10
|
+
const REFUSED = "RegionAnswerRefused";
|
|
11
|
+
/** A refused answer nobody asked for yet (the page's early start: ./early-holes.ts), held at its
|
|
12
|
+
* address for the first caller that asks there (see `Entry.refused`). Any other failure is not. */
|
|
13
|
+
function hold_refusal(a, err) {
|
|
14
|
+
if (err?.name === REFUSED) entry(a).refused = err;
|
|
15
|
+
}
|
|
7
16
|
/** Entries with no subscribers linger this long (ms) so quick remounts / SPA swaps reuse content. */
|
|
8
17
|
const EVICT_TTL = 6e4;
|
|
9
18
|
const GLOBAL_KEY = "__ogygia_frames__";
|
|
19
|
+
const FLIGHTS_KEY = "__ogygia_frame_flights__";
|
|
10
20
|
const store_host = typeof globalThis !== "undefined" ? globalThis : {};
|
|
11
|
-
const entries = store_host[GLOBAL_KEY]
|
|
21
|
+
const entries = store_host[GLOBAL_KEY] ??= /* @__PURE__ */ new Map();
|
|
22
|
+
/** The fetches in flight, one per address, shared by every caller of it. Nothing is kept: the
|
|
23
|
+
* versioned store holds what landed. A flight's work aborts once its last caller left. */
|
|
24
|
+
function frame_flights() {
|
|
25
|
+
return store_host[FLIGHTS_KEY] ??= flights.keyed()({
|
|
26
|
+
name: "frames",
|
|
27
|
+
key: ["a"],
|
|
28
|
+
ignore: { work: "one flight per address: a fetch or a batch part out there is joined" },
|
|
29
|
+
keep: 0,
|
|
30
|
+
run: ({ a, work }, signal) => typeof work === "function" ? fetch_frame(a, work, signal) : work
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** A fetch at an address takes its ticket at its start, and writes what it got (a newer write may
|
|
34
|
+
* have raced past it: the answer is whatever the store then holds). */
|
|
35
|
+
async function fetch_frame(a, fetcher, signal) {
|
|
36
|
+
const t = ticket(a);
|
|
37
|
+
const html = await fetcher(signal);
|
|
38
|
+
if (!signal.aborted) write({
|
|
39
|
+
a,
|
|
40
|
+
v: t,
|
|
41
|
+
html
|
|
42
|
+
});
|
|
43
|
+
return entries.get(a)?.html ?? html;
|
|
44
|
+
}
|
|
45
|
+
/** A fetch or a batch's part is out at this address. */
|
|
46
|
+
const flying = (a) => store_host[FLIGHTS_KEY]?.peek({ a }) !== void 0;
|
|
12
47
|
function entry(a) {
|
|
13
48
|
let e = entries.get(a);
|
|
14
49
|
if (!e) {
|
|
@@ -16,28 +51,29 @@ function entry(a) {
|
|
|
16
51
|
v: 0,
|
|
17
52
|
html: null,
|
|
18
53
|
seq: 0,
|
|
19
|
-
|
|
20
|
-
evict: null
|
|
54
|
+
batch: null,
|
|
55
|
+
evict: null,
|
|
56
|
+
renewed: null,
|
|
57
|
+
refused: null
|
|
21
58
|
};
|
|
22
59
|
entries.set(a, e);
|
|
23
60
|
}
|
|
24
61
|
return e;
|
|
25
62
|
}
|
|
26
63
|
/**
|
|
27
|
-
* Let an idle entry (no subscribers,
|
|
64
|
+
* Let an idle entry (no subscribers, nothing in flight) linger EVICT_TTL, then drop it. A late binder
|
|
28
65
|
* that mounts inside the window still catches its content (prefetch / batch-stream ahead of the DOM);
|
|
29
|
-
* nothing bound means nothing to keep. Idempotent — resets the timer.
|
|
30
|
-
* entry idle
|
|
31
|
-
* (a single-flight mutation for an unmounted region, or a live tick whose
|
|
32
|
-
* Without the write path, those orphan entries would accumulate forever —
|
|
33
|
-
* to trigger eviction.
|
|
66
|
+
* nothing bound means nothing to keep. Idempotent — resets the timer. Every channel that leaves an
|
|
67
|
+
* entry idle calls this: a subscriber unbinding, a flight that settled, AND a `write()` that lands
|
|
68
|
+
* where nothing is listening (a single-flight mutation for an unmounted region, or a live tick whose
|
|
69
|
+
* address keeps changing). Without the write path, those orphan entries would accumulate forever —
|
|
70
|
+
* they never had a subscriber to trigger eviction.
|
|
34
71
|
*/
|
|
35
72
|
function schedule_evict(a, e) {
|
|
36
|
-
if (watcher_count(frame_id(a)) > 0 ||
|
|
37
|
-
|
|
38
|
-
e.evict =
|
|
39
|
-
|
|
40
|
-
if (cur === e && watcher_count(frame_id(a)) === 0 && !cur.inflight) entries.delete(a);
|
|
73
|
+
if (watcher_count(frame_id(a)) > 0 || flying(a)) return;
|
|
74
|
+
e.evict?.close();
|
|
75
|
+
(e.evict = scope()).timeout(() => {
|
|
76
|
+
if (entries.get(a) === e && watcher_count(frame_id(a)) === 0 && !flying(a)) entries.delete(a);
|
|
41
77
|
}, EVICT_TTL);
|
|
42
78
|
}
|
|
43
79
|
/** Applied content for an address, if any. */
|
|
@@ -48,10 +84,18 @@ function peek(a) {
|
|
|
48
84
|
html: e.html
|
|
49
85
|
} : null;
|
|
50
86
|
}
|
|
87
|
+
/** A renewal at this address answered `fresh`, the capability its elements fetch with from now on. */
|
|
88
|
+
function renew(a, fresh) {
|
|
89
|
+
entry(a).renewed = fresh;
|
|
90
|
+
}
|
|
91
|
+
/** The fresh capability a renewal at this address answered, if any. */
|
|
92
|
+
function renewal(a) {
|
|
93
|
+
return entries.get(a)?.renewed ?? null;
|
|
94
|
+
}
|
|
51
95
|
/** Nothing applied and nothing in flight at this address (a batch may take it on). */
|
|
52
96
|
function is_idle(a) {
|
|
53
97
|
const e = entries.get(a);
|
|
54
|
-
return !e || e.html == null && !
|
|
98
|
+
return !e || e.html == null && !flying(a);
|
|
55
99
|
}
|
|
56
100
|
/** Next request ticket for an address. External channels (streaming, mutations) use this too. */
|
|
57
101
|
function ticket(a) {
|
|
@@ -67,9 +111,10 @@ function write(f) {
|
|
|
67
111
|
if (f.v > e.seq) e.seq = f.v;
|
|
68
112
|
e.v = f.v;
|
|
69
113
|
e.html = f.html;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
114
|
+
e.refused = null;
|
|
115
|
+
const held = e.batch;
|
|
116
|
+
if (held) {
|
|
117
|
+
e.batch = null;
|
|
73
118
|
held.settle(f.html);
|
|
74
119
|
}
|
|
75
120
|
notify(frame_id(f.a), f);
|
|
@@ -78,48 +123,43 @@ function write(f) {
|
|
|
78
123
|
}
|
|
79
124
|
/**
|
|
80
125
|
* Reserve an address whose content is arriving via an EXTERNAL batch stream (single-flight navigation). A region
|
|
81
|
-
* binder that mounts before its frame lands calls `ensure()`,
|
|
126
|
+
* binder that mounts before its frame lands calls `ensure()`, finds the batch's flight there, and JOINS it
|
|
82
127
|
* instead of starting its own fetch — so a navigation that pulls N regions makes ONE batch request,
|
|
83
|
-
* not N. `write()` fulfils
|
|
84
|
-
*
|
|
85
|
-
* already present.
|
|
128
|
+
* not N. `write()` fulfils it when the frame arrives; `release()` fails it if the batch ends without
|
|
129
|
+
* one (the binder then falls back to its own fetch). The flight is the batch's: a binder leaving never
|
|
130
|
+
* ends it. No-op if content or a fetch is already present.
|
|
86
131
|
*/
|
|
87
132
|
function reserve(a) {
|
|
88
133
|
const e = entry(a);
|
|
89
|
-
if (e.html != null ||
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
settle = res;
|
|
98
|
-
fail = rej;
|
|
134
|
+
if (e.html != null || flying(a)) return;
|
|
135
|
+
e.evict?.close();
|
|
136
|
+
e.evict = null;
|
|
137
|
+
const batch = new Promise((settle, fail) => {
|
|
138
|
+
e.batch = {
|
|
139
|
+
settle,
|
|
140
|
+
fail
|
|
141
|
+
};
|
|
99
142
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
settle,
|
|
107
|
-
fail
|
|
108
|
-
};
|
|
143
|
+
batch.catch(() => {});
|
|
144
|
+
const settled = () => schedule_evict(a, e);
|
|
145
|
+
frame_flights().get({
|
|
146
|
+
a,
|
|
147
|
+
work: batch
|
|
148
|
+
}).then(settled, settled);
|
|
109
149
|
}
|
|
110
150
|
/**
|
|
111
|
-
* Fail an unfulfilled
|
|
112
|
-
* reject out of `ensure()` and retry with their own fetch. No-op once
|
|
151
|
+
* Fail an unfulfilled batch part (the batch ended and no frame was written at this address, by it or
|
|
152
|
+
* by anyone). Joined binders reject out of `ensure()` and retry with their own fetch. No-op once a
|
|
153
|
+
* frame has landed (it settled the part).
|
|
113
154
|
*/
|
|
114
155
|
function release(a) {
|
|
115
156
|
const e = entries.get(a);
|
|
116
|
-
const held = e?.
|
|
117
|
-
if (!e || !held
|
|
118
|
-
e.
|
|
157
|
+
const held = e?.batch;
|
|
158
|
+
if (!e || !held) return;
|
|
159
|
+
e.batch = null;
|
|
119
160
|
const miss = /* @__PURE__ */ new Error("ogygia batch: no frame for " + a);
|
|
120
161
|
miss.name = BATCH_MISS;
|
|
121
162
|
held.fail(miss);
|
|
122
|
-
schedule_evict(a, e);
|
|
123
163
|
}
|
|
124
164
|
/** The error a joined reservation rejects with when its batch ended without its frame: the binder's
|
|
125
165
|
* own fetch runs at once (nothing failed — the batch just did not carry it). */
|
|
@@ -132,10 +172,8 @@ const BATCH_MISS = "OgygiaBatchMiss";
|
|
|
132
172
|
*/
|
|
133
173
|
function subscribe(a, cb) {
|
|
134
174
|
const e = entry(a);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
e.evict = null;
|
|
138
|
-
}
|
|
175
|
+
e.evict?.close();
|
|
176
|
+
e.evict = null;
|
|
139
177
|
const off = watch(frame_id(a), cb);
|
|
140
178
|
if (e.html != null) cb({
|
|
141
179
|
a,
|
|
@@ -151,64 +189,52 @@ function subscribe(a, cb) {
|
|
|
151
189
|
* Ensure an address has content, deduping concurrent callers onto one fetch.
|
|
152
190
|
*
|
|
153
191
|
* - Content already applied (and not `force`): resolves immediately with it.
|
|
154
|
-
* - A fetch is in flight (and not `force`): joins it — N regions, ONE request.
|
|
192
|
+
* - A fetch (or a batch's part) is in flight (and not `force`): joins it — N regions, ONE request.
|
|
155
193
|
* - Otherwise starts `fetcher` with a fresh ticket; on success the response is WRITTEN (subject to
|
|
156
194
|
* the staleness check) and the resolved value is whatever the store now holds.
|
|
157
|
-
* - `force` (SWR revalidate) always starts a new fetch
|
|
158
|
-
*
|
|
195
|
+
* - `force` (SWR revalidate) always starts a new fetch, and ends the one out before it: whoever
|
|
196
|
+
* waited on that one joins the new one. The ticket ordering still guarantees an older response
|
|
197
|
+
* can't overwrite a newer one.
|
|
159
198
|
*
|
|
160
|
-
*
|
|
199
|
+
* `signal` is the caller's: when it aborts, this caller alone gets `STALE` at once, and the shared
|
|
200
|
+
* fetch is aborted only once its LAST caller left — one unmounting element must not kill a fetch
|
|
201
|
+
* its twin still awaits. Rejections propagate to every joined caller; retry policy stays with the
|
|
202
|
+
* caller (the element).
|
|
161
203
|
*/
|
|
162
|
-
function ensure(a, fetcher, opts = {}) {
|
|
204
|
+
async function ensure(a, fetcher, opts = {}) {
|
|
205
|
+
const { signal } = opts;
|
|
206
|
+
const f = frame_flights();
|
|
163
207
|
const e = entry(a);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
e.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const t = ticket(a);
|
|
173
|
-
const inflight = {
|
|
174
|
-
ticket: t,
|
|
175
|
-
controller,
|
|
176
|
-
waiters: 1,
|
|
177
|
-
promise: fetcher(controller.signal).then((html) => {
|
|
178
|
-
if (e.inflight === inflight) e.inflight = null;
|
|
179
|
-
write({
|
|
208
|
+
let fresh = opts.force === true;
|
|
209
|
+
try {
|
|
210
|
+
for (;;) {
|
|
211
|
+
if (!fresh && e.html != null) return e.html;
|
|
212
|
+
const refused = e.refused;
|
|
213
|
+
e.refused = null;
|
|
214
|
+
if (!fresh && refused !== null) throw refused;
|
|
215
|
+
const html = await f.get({
|
|
180
216
|
a,
|
|
181
|
-
|
|
182
|
-
|
|
217
|
+
work: fetcher
|
|
218
|
+
}, {
|
|
219
|
+
signal,
|
|
220
|
+
fresh
|
|
183
221
|
});
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
};
|
|
190
|
-
e.inflight = inflight;
|
|
191
|
-
return inflight.promise;
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* A waiter is leaving (its element disconnected). Aborts the shared fetch only when the LAST
|
|
195
|
-
* waiter leaves — one unmounting element must not kill a fetch its twin still awaits.
|
|
196
|
-
*/
|
|
197
|
-
function abandon(a) {
|
|
198
|
-
const e = entries.get(a);
|
|
199
|
-
if (!e?.inflight) return;
|
|
200
|
-
if (--e.inflight.waiters <= 0) {
|
|
201
|
-
e.inflight.controller.abort();
|
|
202
|
-
e.inflight = null;
|
|
222
|
+
if (html !== STALE || signal?.aborted || frame_flights() !== f) return html;
|
|
223
|
+
fresh = false;
|
|
224
|
+
}
|
|
225
|
+
} finally {
|
|
226
|
+
schedule_evict(a, e);
|
|
203
227
|
}
|
|
204
228
|
}
|
|
205
229
|
/** Test/HMR hook — drop everything. */
|
|
206
230
|
function _reset() {
|
|
207
231
|
for (const e of entries.values()) {
|
|
208
|
-
|
|
209
|
-
e.
|
|
210
|
-
e.inflight?.controller.abort();
|
|
232
|
+
e.evict?.close();
|
|
233
|
+
e.batch?.fail(/* @__PURE__ */ new Error("reset"));
|
|
211
234
|
}
|
|
235
|
+
const old = store_host[FLIGHTS_KEY];
|
|
236
|
+
store_host[FLIGHTS_KEY] = void 0;
|
|
237
|
+
old?.drop(() => true);
|
|
212
238
|
entries.clear();
|
|
213
239
|
}
|
|
214
|
-
export { BATCH_MISS, _reset,
|
|
240
|
+
export { BATCH_MISS, _reset, ensure, hold_refusal, is_idle, peek, release, renew, renewal, reserve, subscribe, ticket, write };
|
package/dist/runtime/frames.js
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import { slots } from "./slots.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ensure, renew, renewal, subscribe } from "./frame-store.js";
|
|
3
3
|
import { batch_times, join_batch, streamFrames } from "./frame-nav.js";
|
|
4
|
+
import { adopt_early_holes } from "./early-holes.js";
|
|
5
|
+
import { runtime_session } from "./session.js";
|
|
4
6
|
// src/runtime/frames.ts
|
|
5
7
|
/**
|
|
6
8
|
* frames feature: hand the client frame store to core through the {@link ./slots.js slots} seam.
|
|
7
9
|
*
|
|
8
10
|
* Core drives deferred / live / SWR-lake regions off this store (subscribe → apply, ensure →
|
|
9
|
-
* single-flight fetch,
|
|
11
|
+
* single-flight fetch, a caller's signal → its leaving), and the router single-flights a route's deferred holes through
|
|
10
12
|
* `stream` — but NEITHER may statically import the store, or the ~10 kB store + nav-stream graph
|
|
11
13
|
* lands in every build, including a plain router app with no such region. The generated runtime entry
|
|
12
14
|
* installs this feature only when the app actually ships a deferred / live / lake region, so a
|
|
13
15
|
* static-islands app (and a router app without deferred regions) tree-shakes the whole graph away.
|
|
14
16
|
*/
|
|
15
17
|
function install() {
|
|
18
|
+
adopt_early_holes(runtime_session.visit.scope);
|
|
16
19
|
slots.frames = {
|
|
17
20
|
subscribe,
|
|
18
21
|
ensure,
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
renew,
|
|
23
|
+
renewal,
|
|
24
|
+
stream: (endpoints, visit) => streamFrames(endpoints, "nav", visit),
|
|
21
25
|
join: join_batch,
|
|
22
26
|
batch_times
|
|
23
27
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HOLES_SCRIPT_TYPE } from "../holes-record.js";
|
|
2
|
+
import { parse_region_html } from "./parse-html.js";
|
|
2
3
|
// src/runtime/hole-facts.ts
|
|
3
4
|
/**
|
|
4
5
|
* THE HOLES RECORD — what the server minted for each deferred hole on a KIT-HYDRATED document,
|
|
@@ -32,17 +33,15 @@ function parse_record(doc) {
|
|
|
32
33
|
if (!entry || typeof entry.endpoint !== "string" || !entry.endpoint) continue;
|
|
33
34
|
map.set(identity, {
|
|
34
35
|
endpoint: entry.endpoint,
|
|
35
|
-
sidecar: typeof entry.sidecar === "string" && entry.sidecar ? sidecar_element(
|
|
36
|
+
sidecar: typeof entry.sidecar === "string" && entry.sidecar ? sidecar_element(entry.sidecar) : null
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
39
|
return map;
|
|
39
40
|
}
|
|
40
|
-
/** The recorded sidecar HTML back into a script element
|
|
41
|
-
*
|
|
42
|
-
function sidecar_element(
|
|
43
|
-
const
|
|
44
|
-
holder.innerHTML = html;
|
|
45
|
-
const el = holder.content.firstElementChild;
|
|
41
|
+
/** The recorded sidecar HTML back into a script element, through the one parser (./parse-html.ts:
|
|
42
|
+
* inert, nothing runs — the sidecar is a data script, never executable). */
|
|
43
|
+
function sidecar_element(html) {
|
|
44
|
+
const el = parse_region_html(html).firstElementChild;
|
|
46
45
|
return el && el.tagName === "SCRIPT" ? el : null;
|
|
47
46
|
}
|
|
48
47
|
/** The server-minted facts of the hole with this identity on `doc`, or `null`. */
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type HoleFetch = {
|
|
2
|
+
/** Past the browser cache: the endpoint may answer `private, max-age`, and stale is the whole
|
|
3
|
+
* point of revalidating. */
|
|
4
|
+
revalidate?: boolean;
|
|
5
|
+
priority?: "high" | "auto" | "low";
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
/** A renewal answered this fresh capability for the hole. */
|
|
8
|
+
renewed?: (fresh: string) => void;
|
|
9
|
+
};
|
|
10
|
+
/** A hole's fetch: its request started ({@link start_hole}), its answer read ({@link read_hole}). */
|
|
11
|
+
export declare function fetch_hole(endpoint: string, opts?: HoleFetch): Promise<string>;
|
|
12
|
+
/** The request for a hole's HTML (the page's head starts a `load` hole's the same way:
|
|
13
|
+
* ../early-holes.ts). */
|
|
14
|
+
export declare function start_hole(endpoint: string, { revalidate, priority, signal }?: HoleFetch): Promise<Response>;
|
|
15
|
+
/** A hole's answer, read: whatever lands at an address is read this one way, whoever started its
|
|
16
|
+
* request. */
|
|
17
|
+
export declare function read_hole(endpoint: string, response: Promise<Response>, opts?: Pick<HoleFetch, "signal" | "renewed">): Promise<string>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { KEEP_FALLBACK_HTML } from "../keep-fallback-marker.js";
|
|
2
|
+
import { RegionAnswerRefused, capability_expired, deny_of, is_allowed_region_endpoint, is_document_answer, is_redirected_answer, is_same_origin_response, renewal_url } from "./region-endpoint-url.js";
|
|
3
|
+
// src/runtime/hole-fetch.ts
|
|
4
|
+
/**
|
|
5
|
+
* THE ONE NETWORK FETCH OF A HOLE'S HTML. The element's own wake, its `prefetch` warm and `preload()`
|
|
6
|
+
* all fetch a hole through here, so whatever lands in the frame store at its address was read one
|
|
7
|
+
* way: the endpoint's text, the keep-fallback marker on a 204 (`keepFallback()` on the server), or a
|
|
8
|
+
* refusal when the answer is not the region's. An expired capability renews once, and the fresh one
|
|
9
|
+
* is handed to `renewed` (the frame store keeps it for the address). No DOM, no store, no scheduler:
|
|
10
|
+
* the caller decides when it may go out.
|
|
11
|
+
*/
|
|
12
|
+
/** A hole's fetch: its request started ({@link start_hole}), its answer read ({@link read_hole}). */
|
|
13
|
+
async function fetch_hole(endpoint, opts = {}) {
|
|
14
|
+
return read_hole(endpoint, start_hole(endpoint, opts), opts);
|
|
15
|
+
}
|
|
16
|
+
/** The request for a hole's HTML (the page's head starts a `load` hole's the same way:
|
|
17
|
+
* ../early-holes.ts). */
|
|
18
|
+
function start_hole(endpoint, { revalidate = false, priority = "auto", signal } = {}) {
|
|
19
|
+
return fetch(endpoint, {
|
|
20
|
+
credentials: "same-origin",
|
|
21
|
+
cache: revalidate ? "no-store" : "default",
|
|
22
|
+
priority,
|
|
23
|
+
signal
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** A hole's answer, read: whatever lands at an address is read this one way, whoever started its
|
|
27
|
+
* request. */
|
|
28
|
+
async function read_hole(endpoint, response, opts = {}) {
|
|
29
|
+
let res = await response;
|
|
30
|
+
if (res.status === 403 && capability_expired(endpoint)) {
|
|
31
|
+
res = await start_hole(renewal_url(endpoint), {
|
|
32
|
+
revalidate: true,
|
|
33
|
+
signal: opts.signal
|
|
34
|
+
});
|
|
35
|
+
const fresh = res.ok ? res.headers.get("x-ogygia-capability") : null;
|
|
36
|
+
if (fresh && is_allowed_region_endpoint(fresh)) opts.renewed?.(fresh);
|
|
37
|
+
}
|
|
38
|
+
if (!is_same_origin_response(res)) throw new Error("cross-origin redirect");
|
|
39
|
+
if (is_redirected_answer(res)) throw new RegionAnswerRefused("redirected", res.url);
|
|
40
|
+
if (res.status === 403) throw new RegionAnswerRefused("forbidden", res.url || endpoint, deny_of(res, endpoint));
|
|
41
|
+
if (!res.ok) throw new Error("status " + res.status);
|
|
42
|
+
if (res.status === 204) return KEEP_FALLBACK_HTML;
|
|
43
|
+
const text = await res.text();
|
|
44
|
+
if (is_document_answer(text)) throw new RegionAnswerRefused("document", res.url);
|
|
45
|
+
return text;
|
|
46
|
+
}
|
|
47
|
+
export { fetch_hole, read_hole, start_hole };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Component } from "svelte";
|
|
2
2
|
export declare const seed_page_once: () => void;
|
|
3
3
|
export declare const seed_remote_once: () => void;
|
|
4
|
-
export declare const kit_page_thread: () => Promise<
|
|
4
|
+
export declare const kit_page_thread: (signal: AbortSignal) => Promise<unknown> | null;
|
|
5
5
|
/** A hydrated island as the element holds it: how to tear it down, and (keep / live hosts) how to
|
|
6
6
|
* push the next page's props into it. */
|
|
7
7
|
export type IslandHandle = {
|
|
@@ -94,8 +94,8 @@ export declare function read_region_props(region: Element, foreign?: boolean): R
|
|
|
94
94
|
*/
|
|
95
95
|
export declare function hydrate_island(region: HTMLElement, entry: string, mod: IslandModule, ssr_html?: string | null, touched?: boolean): IslandHandle | null;
|
|
96
96
|
/**
|
|
97
|
-
* Hydrate a live region's swapped-in HTML
|
|
98
|
-
* HTML
|
|
99
|
-
*
|
|
97
|
+
* Hydrate a live region's swapped-in HTML with pushable props, so a later tick reaches the mounted
|
|
98
|
+
* island in place. Its HTML is svelte `render()`'s (the root anchor pair already there) — the
|
|
99
|
+
* deferred swap's shape, so it is never wrapped again. `null` when the live feature is absent.
|
|
100
100
|
*/
|
|
101
101
|
export declare function hydrate_live(region: HTMLElement, entry: string, mod: IslandModule, props: Record<string, unknown>): IslandHandle | null;
|