@ogygia/core 0.12.2 → 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 +83 -74
- 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.d.ts +6 -5
- 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
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Stale } from "@ogygia/machine";
|
|
2
|
+
type Devalue = typeof import("devalue");
|
|
3
|
+
/** Start (once) and await devalue's import. (Never STALE: nothing drops it.) */
|
|
4
|
+
export declare const load_devalue: () => Promise<Devalue | Stale>;
|
|
5
|
+
/** devalue, already loaded: a devalue-lane read only runs after its load ({@link load_devalue}). */
|
|
6
|
+
export declare function devalue(): Devalue;
|
|
7
|
+
/** Run `fn` with devalue now when it is loaded, else once it is. Nothing else asks again for its
|
|
8
|
+
* caller (a streamed value's resolver runs once), so a failed download is asked for again later,
|
|
9
|
+
* as `devalue_download` says (`tried`: the loads that failed so far). */
|
|
10
|
+
export declare function with_devalue(fn: (d: Devalue) => void, tried?: number): void;
|
|
11
|
+
/** Does this document (or fragment) hold a script only devalue can read? */
|
|
12
|
+
export declare function needs_devalue(root: ParentNode): boolean;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { import_again } from "./import-again.js";
|
|
2
|
+
import { devalue_download, download } from "../policies.js";
|
|
3
|
+
import { STALE, backoffAfter, flights } from "@ogygia/machine";
|
|
4
|
+
// src/runtime/devalue-lane.ts
|
|
5
|
+
/**
|
|
6
|
+
* DEVALUE, LOADED WHEN A PAGE NEEDS IT. A side-channel script crosses in one of two lanes
|
|
7
|
+
* (server/props-wire.ts): plain JSON under `data-og-format="json"` (the common props and seed: a
|
|
8
|
+
* payload with nothing devalue exists for), devalue otherwise. A page whose scripts are all JSON never
|
|
9
|
+
* reads devalue, so it never downloads it: the first read that needs it starts its import beside the
|
|
10
|
+
* load that leads there (an island's wake starts it with the hydrate core; a navigation, before its
|
|
11
|
+
* preflight), never after it. The parse itself stays synchronous: by then the module is in.
|
|
12
|
+
*
|
|
13
|
+
* A leaf: island-side code (the page-defer resolver) imports it, so it imports nothing of the boot.
|
|
14
|
+
*
|
|
15
|
+
* A download that fails is tried again at once, and a failure is never kept: the next read loads it
|
|
16
|
+
* afresh (one dropped request must not leave every later wake, and every streamed value, unreadable).
|
|
17
|
+
*/
|
|
18
|
+
let lib = null;
|
|
19
|
+
const import_devalue = import_again(() => import("devalue"));
|
|
20
|
+
const devalue_load = flights({
|
|
21
|
+
name: "devalue",
|
|
22
|
+
run: () => import_devalue().then((m) => lib = m),
|
|
23
|
+
retry: download
|
|
24
|
+
});
|
|
25
|
+
/** Start (once) and await devalue's import. (Never STALE: nothing drops it.) */
|
|
26
|
+
const load_devalue = () => devalue_load.get();
|
|
27
|
+
/** devalue, already loaded: a devalue-lane read only runs after its load ({@link load_devalue}). */
|
|
28
|
+
function devalue() {
|
|
29
|
+
if (!lib) throw new Error("[ogygia] https://ogygiajs.dev/e/devalue_not_loaded");
|
|
30
|
+
return lib;
|
|
31
|
+
}
|
|
32
|
+
/** Run `fn` with devalue now when it is loaded, else once it is. Nothing else asks again for its
|
|
33
|
+
* caller (a streamed value's resolver runs once), so a failed download is asked for again later,
|
|
34
|
+
* as `devalue_download` says (`tried`: the loads that failed so far). */
|
|
35
|
+
function with_devalue(fn, tried = 0) {
|
|
36
|
+
if (lib) fn(lib);
|
|
37
|
+
else load_devalue().then((d) => d !== STALE && fn(d), () => {
|
|
38
|
+
const failed = tried + 1;
|
|
39
|
+
if (failed >= devalue_download.attempts) return;
|
|
40
|
+
setTimeout(() => with_devalue(fn, failed), backoffAfter(devalue_download, failed));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Every side-channel a read may parse with devalue: props sidecars, the page seed, page facts. */
|
|
44
|
+
const DEVALUE_LANE_SELECTOR = "script[data-ogygia-props]:not([data-og-format=\"json\"]),script[type=\"application/ogygia-page\"]:not([data-og-format=\"json\"]),script[type=\"application/ogygia-page-facts\"]";
|
|
45
|
+
/** Does this document (or fragment) hold a script only devalue can read? */
|
|
46
|
+
function needs_devalue(root) {
|
|
47
|
+
return root.querySelector(DEVALUE_LANE_SELECTOR) !== null;
|
|
48
|
+
}
|
|
49
|
+
export { devalue, load_devalue, needs_devalue, with_devalue };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function disclose_version(): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/runtime/disclose-version.ts
|
|
2
|
+
/**
|
|
3
|
+
* WHICH OGYGIA IS THIS PAGE ON: the runtime adds its version to `window.__ogygia.v`, a Set, as
|
|
4
|
+
* Svelte does with `window.__svelte.v`. A Set because a page can run more than one ogygia (a
|
|
5
|
+
* federated fragment brings its own app's runtime). The full version, not just the major: telling
|
|
6
|
+
* one release line from another is the point. The plugin defines it from the installed package.
|
|
7
|
+
*/
|
|
8
|
+
const VERSION = typeof __OGYGIA_VERSION__ !== "undefined" ? __OGYGIA_VERSION__ : null;
|
|
9
|
+
function disclose_version() {
|
|
10
|
+
if (typeof window === "undefined" || !VERSION) return;
|
|
11
|
+
const own = window;
|
|
12
|
+
((own.__ogygia ??= {}).v ??= /* @__PURE__ */ new Set()).add(VERSION);
|
|
13
|
+
}
|
|
14
|
+
export { disclose_version };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { EARLY_HOLES_KEY } from "../early-holes.js";
|
|
2
|
+
import { frameAddress } from "../frame.js";
|
|
3
|
+
import { ensure, hold_refusal, renew } from "./frame-store.js";
|
|
4
|
+
import { read_hole } from "./hole-fetch.js";
|
|
5
|
+
import { scope } from "@ogygia/machine";
|
|
6
|
+
// src/runtime/early-holes.ts
|
|
7
|
+
/**
|
|
8
|
+
* THE PAGE'S EARLY HOLE REQUESTS, TAKEN OVER (../early-holes.ts). The head started each `load`
|
|
9
|
+
* hole's request during the parse and parked it on one global; at boot each becomes the flight at
|
|
10
|
+
* its frame-store address, its response read the one way every hole answer is (./hole-fetch.ts
|
|
11
|
+
* `read_hole`). The hole's own wake joins that flight, a batch finds the address in flight and
|
|
12
|
+
* leaves it out, and the response is read once. The flight's first caller is the page visit, and
|
|
13
|
+
* the flight's signal cancels the parked request: one nobody joined is cancelled with the visit.
|
|
14
|
+
* Part of the frames feature (./frames.ts): no hole, no code.
|
|
15
|
+
*/
|
|
16
|
+
const PARKED = Symbol.for(EARLY_HOLES_KEY);
|
|
17
|
+
/** Take the parked requests over, once (the global is emptied): each the flight at its address,
|
|
18
|
+
* with `visit` its first caller. */
|
|
19
|
+
function adopt_early_holes(visit) {
|
|
20
|
+
const host = globalThis;
|
|
21
|
+
const parked = host[PARKED];
|
|
22
|
+
if (!parked) return;
|
|
23
|
+
delete host[PARKED];
|
|
24
|
+
for (const { url, response, controller } of parked) {
|
|
25
|
+
const address = frameAddress(url);
|
|
26
|
+
ensure(address, async (signal) => {
|
|
27
|
+
const tie = scope();
|
|
28
|
+
tie.on(signal, "abort", () => controller.abort(signal.reason));
|
|
29
|
+
try {
|
|
30
|
+
return await read_hole(url, response, {
|
|
31
|
+
signal,
|
|
32
|
+
renewed: (fresh) => renew(address, fresh)
|
|
33
|
+
});
|
|
34
|
+
} finally {
|
|
35
|
+
tie.close();
|
|
36
|
+
}
|
|
37
|
+
}, { signal: visit.signal }).catch((err) => hold_refusal(address, err));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export { adopt_early_holes };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { import_file } from "./import-again.js";
|
|
2
|
+
import { rollover } from "../policies.js";
|
|
3
|
+
import { STALE, backoffAfter, flights, scope } from "@ogygia/machine";
|
|
1
4
|
// src/runtime/entry-locations.ts
|
|
2
5
|
/**
|
|
3
6
|
* ENTRY IDENTITY → LOCATION, the browser's half (server/entry-location.ts is the server's).
|
|
@@ -54,7 +57,19 @@ function entry_location(entry, base) {
|
|
|
54
57
|
* is not kept: the next identity-only load asks again (a blip must not cost the page every later one).
|
|
55
58
|
*/
|
|
56
59
|
const BUILD_MAP_ATTR = "data-og-locations";
|
|
57
|
-
|
|
60
|
+
/** The map's read: whether it filled the locations. One read at a time, kept once it did. */
|
|
61
|
+
const build_map = flights({
|
|
62
|
+
name: "build location map",
|
|
63
|
+
run: (url) => fetch(url).then((res) => res.ok ? res.json() : null).catch(() => null).then((map) => {
|
|
64
|
+
if (!map) return false;
|
|
65
|
+
for (const stable in map) {
|
|
66
|
+
const identity = absolute(stable, url);
|
|
67
|
+
if (!locations.has(identity)) locations.set(identity, absolute(map[stable], url));
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}),
|
|
71
|
+
keep: (filled) => filled ? Infinity : 0
|
|
72
|
+
});
|
|
58
73
|
/**
|
|
59
74
|
* Pin the build map's address to the page that wrote it, as `pin_region_urls` pins a region's: with
|
|
60
75
|
* Kit's default `paths.relative` it is written relative to that page, and it is read lazily — after a
|
|
@@ -68,22 +83,10 @@ function pin_build_map() {
|
|
|
68
83
|
if (script && href) script.setAttribute(BUILD_MAP_ATTR, absolute(href));
|
|
69
84
|
}
|
|
70
85
|
function read_build_map() {
|
|
71
|
-
if (build_map) return build_map;
|
|
72
86
|
if (typeof document === "undefined") return null;
|
|
73
87
|
const href = document.querySelector(`script[${BUILD_MAP_ATTR}]`)?.getAttribute(BUILD_MAP_ATTR);
|
|
74
88
|
if (!href) return null;
|
|
75
|
-
|
|
76
|
-
const read = fetch(url).then((res) => res.ok ? res.json() : null).catch(() => null).then((map) => {
|
|
77
|
-
if (!map) {
|
|
78
|
-
if (build_map === read) build_map = null;
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
for (const stable in map) {
|
|
82
|
-
const identity = absolute(stable, url);
|
|
83
|
-
if (!locations.has(identity)) locations.set(identity, absolute(map[stable], url));
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
return build_map = read;
|
|
89
|
+
return build_map.get(absolute(href));
|
|
87
90
|
}
|
|
88
91
|
/** The region attributes that hold a URL: its identity, its location, its hole's endpoint. */
|
|
89
92
|
const REGION_URL_ATTRS = [
|
|
@@ -129,7 +132,11 @@ function island_entry_of(el) {
|
|
|
129
132
|
* that kept it anyway), read once per document per app. Only an ogygia identity has one: the dev
|
|
130
133
|
* server's module URLs never ask.
|
|
131
134
|
*/
|
|
132
|
-
const current_maps =
|
|
135
|
+
const current_maps = flights({
|
|
136
|
+
name: "current build map",
|
|
137
|
+
run: (file) => fetch(file, { cache: "no-cache" }).then((res) => res.ok ? res.json() : null).catch(() => null),
|
|
138
|
+
keep: (map) => map ? Infinity : 0
|
|
139
|
+
});
|
|
133
140
|
/** A build's island identity (`…/<appDir>/ogygia/og-region.<id>.js`): a name, no file. */
|
|
134
141
|
function is_build_identity(entry) {
|
|
135
142
|
return absolute(entry).includes("/ogygia/og-region.");
|
|
@@ -145,22 +152,11 @@ async function current_location(entry) {
|
|
|
145
152
|
const identity = absolute(entry);
|
|
146
153
|
const file = current_map_file(identity);
|
|
147
154
|
if (!file) return null;
|
|
148
|
-
|
|
149
|
-
if (!
|
|
150
|
-
map = fetch(file, { cache: "no-cache" }).then((res) => res.ok ? res.json() : null).catch(() => null);
|
|
151
|
-
current_maps.set(file, map);
|
|
152
|
-
}
|
|
153
|
-
const pairs = await map;
|
|
154
|
-
if (!pairs) {
|
|
155
|
-
forget_current_map(file, map);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
155
|
+
const pairs = await current_maps.get(file);
|
|
156
|
+
if (!pairs || pairs === STALE) return void 0;
|
|
158
157
|
for (const name in pairs) if (absolute(name, file) === identity) return absolute(pairs[name], file);
|
|
159
158
|
return null;
|
|
160
159
|
}
|
|
161
|
-
function forget_current_map(file, map = current_maps.get(file)) {
|
|
162
|
-
if (current_maps.get(file) === map) current_maps.delete(file);
|
|
163
|
-
}
|
|
164
160
|
/** The host says this file is not there (it does not say so of a file that failed another way). */
|
|
165
161
|
function is_gone(url) {
|
|
166
162
|
return fetch(url, {
|
|
@@ -168,13 +164,6 @@ function is_gone(url) {
|
|
|
168
164
|
cache: "no-store"
|
|
169
165
|
}).then((res) => res.status === 404 || res.status === 410, () => false);
|
|
170
166
|
}
|
|
171
|
-
/** How long a recovery waits before each ask of the current map: a rollover settles in seconds. */
|
|
172
|
-
const RECOVERY_WAITS = [
|
|
173
|
-
0,
|
|
174
|
-
1e3,
|
|
175
|
-
3e3,
|
|
176
|
-
9e3
|
|
177
|
-
];
|
|
178
167
|
/**
|
|
179
168
|
* The current build's file for a build's identity whose file just failed to load. A deploy's rollover
|
|
180
169
|
* is exactly when this is asked, so an answer that cannot be the current one is asked again while it
|
|
@@ -184,12 +173,20 @@ const RECOVERY_WAITS = [
|
|
|
184
173
|
*/
|
|
185
174
|
async function recover_location(entry, failed) {
|
|
186
175
|
let current;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
176
|
+
const waits = scope();
|
|
177
|
+
try {
|
|
178
|
+
for (let n = 1; n <= rollover.attempts; n++) {
|
|
179
|
+
if (n > 1) {
|
|
180
|
+
const wait = backoffAfter(rollover, n - 1);
|
|
181
|
+
await waits.task(() => new Promise((resolve) => waits.timeout(resolve, wait)));
|
|
182
|
+
}
|
|
183
|
+
current = await current_location(entry);
|
|
184
|
+
if (current === void 0) continue;
|
|
185
|
+
if (current !== failed || !await is_gone(failed)) break;
|
|
186
|
+
current_maps.drop(current_map_file(absolute(entry)));
|
|
187
|
+
}
|
|
188
|
+
} finally {
|
|
189
|
+
waits.close();
|
|
193
190
|
}
|
|
194
191
|
return current ?? null;
|
|
195
192
|
}
|
|
@@ -207,17 +204,11 @@ function import_entry(entry, url = entry_location(entry) ?? entry) {
|
|
|
207
204
|
});
|
|
208
205
|
}
|
|
209
206
|
function load_entry(entry, url) {
|
|
210
|
-
return
|
|
211
|
-
/* @vite-ignore */
|
|
212
|
-
url
|
|
213
|
-
).catch(async (error) => {
|
|
207
|
+
return import_file(url).catch(async (error) => {
|
|
214
208
|
const failed = absolute(url);
|
|
215
209
|
const current = is_build_identity(entry) ? await recover_location(entry, failed) : absolute(entry);
|
|
216
210
|
if (!current || current === failed) throw error;
|
|
217
|
-
const retry =
|
|
218
|
-
/* @vite-ignore */
|
|
219
|
-
current
|
|
220
|
-
);
|
|
211
|
+
const retry = import_file(current);
|
|
221
212
|
const report = (recovered) => fallback_reporter?.({
|
|
222
213
|
entry,
|
|
223
214
|
src: url,
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* THE MEMBRANE for foreign islands (fragment federation) — a pure module (no DOM, no virtual
|
|
3
|
-
* imports) so it is unit-testable and safe in any bundle. A FOREIGN-origin island's props parse
|
|
4
|
-
* with wire revivers OFF: reviving a wired ref would construct a class instance from THIS
|
|
5
|
-
* build's codec and hand it to ANOTHER build's svelte — the exact live-value crossing the
|
|
6
|
-
* boundary laws forbid. A ref in a foreign sidecar fails HERE, loudly, at the boundary.
|
|
7
|
-
*/
|
|
8
1
|
export declare function foreign_region_prop_revivers(): Record<string, (d: never) => unknown>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OgygiaError } from "../messages/lazy.js";
|
|
1
2
|
// src/runtime/foreign-props.ts
|
|
2
3
|
/**
|
|
3
4
|
* THE MEMBRANE for foreign islands (fragment federation) — a pure module (no DOM, no virtual
|
|
@@ -8,7 +9,7 @@
|
|
|
8
9
|
*/
|
|
9
10
|
function foreign_region_prop_revivers() {
|
|
10
11
|
return { OgygiaRef: () => {
|
|
11
|
-
throw new
|
|
12
|
+
throw new OgygiaError("wired_class_foreign");
|
|
12
13
|
} };
|
|
13
14
|
}
|
|
14
15
|
export { foreign_region_prop_revivers };
|
|
@@ -4,7 +4,7 @@ export declare function install(): void;
|
|
|
4
4
|
export declare function snapshot_forms(root: ParentNode, pathKey: string): void;
|
|
5
5
|
/**
|
|
6
6
|
* Arm restoration for `pathKey` on the freshly-connected page. Restores already-hydrated islands
|
|
7
|
-
* immediately and hooks `ogygia:hydrated` for those still waking
|
|
8
|
-
*
|
|
7
|
+
* immediately and hooks `ogygia:hydrated` for those still waking, for this page's restore window
|
|
8
|
+
* (see `restore_window`).
|
|
9
9
|
*/
|
|
10
10
|
export declare function arm_form_restore(pathKey: string): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { element_address, resolve_address } from "./interaction.js";
|
|
2
2
|
import { slots } from "./slots.js";
|
|
3
|
+
import { machine } from "@ogygia/machine";
|
|
3
4
|
// src/runtime/form-continuity.ts
|
|
4
5
|
/**
|
|
5
6
|
* CONTINUITY — ambient island form-field survival across SPA navigation.
|
|
@@ -25,9 +26,32 @@ const VALUE_SELECTOR = "input, textarea, select";
|
|
|
25
26
|
const MAX_PAGES = 20;
|
|
26
27
|
/** pathKey → the fields a visitor changed on that page (in this tab session). */
|
|
27
28
|
const store = /* @__PURE__ */ new Map();
|
|
29
|
+
/**
|
|
30
|
+
* THE RESTORE WINDOW, one per page visit: `open` while a page's saved fields wait for its islands
|
|
31
|
+
* to hydrate. It closes on its own after RESTORE_WINDOW_MS, when the page is left (the next
|
|
32
|
+
* navigation's snapshot), or when another page's restore opens; leaving `open` removes its
|
|
33
|
+
* listeners, so a page's values never reach the next page's islands.
|
|
34
|
+
*/
|
|
35
|
+
const restore_window = machine()({
|
|
36
|
+
name: "form-restore",
|
|
37
|
+
initial: "idle",
|
|
38
|
+
steps: {
|
|
39
|
+
idle: ["open"],
|
|
40
|
+
open: ["idle"]
|
|
41
|
+
},
|
|
42
|
+
after: { open: {
|
|
43
|
+
ms: 5e3,
|
|
44
|
+
to: "idle"
|
|
45
|
+
} }
|
|
46
|
+
}).create();
|
|
47
|
+
/** The page being left (or another page's restore opening) ends the open window. */
|
|
48
|
+
function close_restore() {
|
|
49
|
+
if (restore_window.now.kind === "open") restore_window.go("idle");
|
|
50
|
+
}
|
|
28
51
|
const is_checkable = (f) => f.type === "checkbox" || f.type === "radio";
|
|
29
52
|
/** Snapshot changed island fields in `root` under `pathKey`. Call BEFORE the body is swapped out. */
|
|
30
53
|
function snapshot_forms(root, pathKey) {
|
|
54
|
+
close_restore();
|
|
31
55
|
const regions = Array.from(root.querySelectorAll("ogygia-region"));
|
|
32
56
|
const recs = [];
|
|
33
57
|
regions.forEach((region, ri) => {
|
|
@@ -76,10 +100,11 @@ function apply_region(region, ri, recs) {
|
|
|
76
100
|
}
|
|
77
101
|
/**
|
|
78
102
|
* Arm restoration for `pathKey` on the freshly-connected page. Restores already-hydrated islands
|
|
79
|
-
* immediately and hooks `ogygia:hydrated` for those still waking
|
|
80
|
-
*
|
|
103
|
+
* immediately and hooks `ogygia:hydrated` for those still waking, for this page's restore window
|
|
104
|
+
* (see `restore_window`).
|
|
81
105
|
*/
|
|
82
106
|
function arm_form_restore(pathKey) {
|
|
107
|
+
close_restore();
|
|
83
108
|
const recs = store.get(pathKey);
|
|
84
109
|
if (!recs) return;
|
|
85
110
|
let regions = Array.from(document.querySelectorAll("ogygia-region"));
|
|
@@ -97,11 +122,8 @@ function arm_form_restore(pathKey) {
|
|
|
97
122
|
if (ri >= 0) apply_region(region, ri, recs);
|
|
98
123
|
};
|
|
99
124
|
const on_answer = () => regions = Array.from(document.querySelectorAll("ogygia-region"));
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
document.removeEventListener("ogygia:hydrated", on_hydrated, true);
|
|
104
|
-
document.removeEventListener("ogygia:server", on_answer, true);
|
|
105
|
-
}, 5e3);
|
|
125
|
+
restore_window.go("open");
|
|
126
|
+
restore_window.scope.on(document, "ogygia:hydrated", on_hydrated, true);
|
|
127
|
+
restore_window.scope.on(document, "ogygia:server", on_answer, true);
|
|
106
128
|
}
|
|
107
129
|
export { arm_form_restore, install, snapshot_forms };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Scope } from "@ogygia/machine";
|
|
1
2
|
/** How the batch that carried `endpoint` timed it, or undefined (it rode no batch). */
|
|
2
3
|
export declare function batch_times(endpoint: string): {
|
|
3
4
|
left: number;
|
|
@@ -20,8 +21,13 @@ export declare function read_parcel(buf: string, from: number): Parcel | null;
|
|
|
20
21
|
* do — all are minted against the app's one islands endpoint, `handle({ endpoint })`, read here off
|
|
21
22
|
* the first URL). Frames apply as they land; the promise resolves when
|
|
22
23
|
* the response ends.
|
|
24
|
+
*
|
|
25
|
+
* `visit`: the scope of the page whose holes these are (the page on screen by default; a navigation
|
|
26
|
+
* hands the next page's, runtime/session.ts `PageVisit`). The batch is its: the visit ending stops
|
|
27
|
+
* the request and lets go of its body. Every other end lets go of the body too (the end mark, a
|
|
28
|
+
* stall, a refusal), and the holes it did not carry fetch on their own.
|
|
23
29
|
*/
|
|
24
|
-
export declare function streamFrames(endpoints: string[], kind?: "page" | "nav"): Promise<void>;
|
|
30
|
+
export declare function streamFrames(endpoints: string[], kind?: "page" | "nav", visit?: Scope): Promise<void>;
|
|
25
31
|
/**
|
|
26
32
|
* THE PAGE'S HOLES, ONE REQUEST. Holes that start fetching together (the above-the-fold `visible`
|
|
27
33
|
* ones fire in one observer callback; `idle` and media ones in one task) join here
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { frameAddress } from "../frame.js";
|
|
2
|
+
import { stream } from "../policies.js";
|
|
2
3
|
import { is_idle, release, reserve, ticket, write } from "./frame-store.js";
|
|
4
|
+
import { runtime_session } from "./session.js";
|
|
3
5
|
import { emit } from "../events/bus.js";
|
|
4
6
|
import { beacon_hole_batch_missed } from "./beacon.js";
|
|
7
|
+
import { STALE, machine } from "@ogygia/machine";
|
|
5
8
|
// src/runtime/frame-nav.ts
|
|
6
9
|
const DEVTOOLS = typeof __OGYGIA_DEVTOOLS__ !== "undefined" ? __OGYGIA_DEVTOOLS__ : false;
|
|
7
10
|
const BEACON = typeof __OGYGIA_BEACON__ !== "undefined" ? __OGYGIA_BEACON__ : true;
|
|
@@ -55,6 +58,43 @@ function read_parcel(buf, from) {
|
|
|
55
58
|
end
|
|
56
59
|
};
|
|
57
60
|
}
|
|
61
|
+
/** A batch stream quiet this long is given up, and the holes still waiting on it fetch on their own:
|
|
62
|
+
* the `stream` policy's deadline (../policies.ts, where its reason lives). */
|
|
63
|
+
const STALL_MS = stream.deadline;
|
|
64
|
+
/**
|
|
65
|
+
* A BATCH STREAM'S LIFE, one machine: `sending` (the POST, until its answer starts), `reading` (each
|
|
66
|
+
* read that brings bytes is a new visit, so the quiet time starts again), `ended`. Quiet for
|
|
67
|
+
* STALL_MS in either, it steps to `stalled` on its own: the request is aborted and every reservation
|
|
68
|
+
* it did not fill is released, as at any other end.
|
|
69
|
+
*/
|
|
70
|
+
const batch_stream = machine()({
|
|
71
|
+
name: "batch-stream",
|
|
72
|
+
initial: "sending",
|
|
73
|
+
steps: {
|
|
74
|
+
sending: [
|
|
75
|
+
"reading",
|
|
76
|
+
"stalled",
|
|
77
|
+
"ended"
|
|
78
|
+
],
|
|
79
|
+
reading: [
|
|
80
|
+
"reading",
|
|
81
|
+
"stalled",
|
|
82
|
+
"ended"
|
|
83
|
+
],
|
|
84
|
+
stalled: [],
|
|
85
|
+
ended: []
|
|
86
|
+
},
|
|
87
|
+
after: {
|
|
88
|
+
sending: {
|
|
89
|
+
ms: STALL_MS,
|
|
90
|
+
to: "stalled"
|
|
91
|
+
},
|
|
92
|
+
reading: {
|
|
93
|
+
ms: STALL_MS,
|
|
94
|
+
to: "stalled"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
58
98
|
function sigOf(endpoint) {
|
|
59
99
|
const q = endpoint.indexOf("?");
|
|
60
100
|
return q === -1 ? null : new URLSearchParams(endpoint.slice(q + 1)).get("sig");
|
|
@@ -64,8 +104,13 @@ function sigOf(endpoint) {
|
|
|
64
104
|
* do — all are minted against the app's one islands endpoint, `handle({ endpoint })`, read here off
|
|
65
105
|
* the first URL). Frames apply as they land; the promise resolves when
|
|
66
106
|
* the response ends.
|
|
107
|
+
*
|
|
108
|
+
* `visit`: the scope of the page whose holes these are (the page on screen by default; a navigation
|
|
109
|
+
* hands the next page's, runtime/session.ts `PageVisit`). The batch is its: the visit ending stops
|
|
110
|
+
* the request and lets go of its body. Every other end lets go of the body too (the end mark, a
|
|
111
|
+
* stall, a refusal), and the holes it did not carry fetch on their own.
|
|
67
112
|
*/
|
|
68
|
-
async function streamFrames(endpoints, kind = "nav") {
|
|
113
|
+
async function streamFrames(endpoints, kind = "nav", visit = runtime_session.visit.scope) {
|
|
69
114
|
if (!endpoints.length) return;
|
|
70
115
|
const first = endpoints[0];
|
|
71
116
|
const q = first.indexOf("?");
|
|
@@ -96,31 +141,45 @@ async function streamFrames(endpoints, kind = "nav") {
|
|
|
96
141
|
let status = 0;
|
|
97
142
|
let refused;
|
|
98
143
|
let final_url;
|
|
144
|
+
const stream = batch_stream.create();
|
|
145
|
+
const batch_scope = visit.child();
|
|
146
|
+
stream.wait("stalled").then((s) => s !== STALE && batch_scope.close());
|
|
99
147
|
try {
|
|
100
148
|
let res;
|
|
101
149
|
try {
|
|
102
|
-
res = await fetch(path, {
|
|
150
|
+
res = await stream.scope.task(() => fetch(path, {
|
|
103
151
|
method: "POST",
|
|
104
152
|
credentials: "same-origin",
|
|
105
153
|
headers: { "content-type": "application/json" },
|
|
106
|
-
body: JSON.stringify(endpoints)
|
|
107
|
-
|
|
154
|
+
body: JSON.stringify(endpoints),
|
|
155
|
+
signal: batch_scope.signal
|
|
156
|
+
}));
|
|
108
157
|
} catch {
|
|
109
158
|
return;
|
|
110
159
|
}
|
|
160
|
+
if (res === STALE) return;
|
|
161
|
+
stream.go("reading");
|
|
111
162
|
status = res.status;
|
|
112
163
|
if ((DEVTOOLS || BEACON) && res.redirected) {
|
|
113
164
|
refused = "redirected";
|
|
114
165
|
final_url = res.url;
|
|
115
166
|
}
|
|
116
|
-
if (!res.ok || !res.body)
|
|
167
|
+
if (!res.ok || !res.body) {
|
|
168
|
+
res.body?.cancel().catch(() => {});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
117
171
|
const reader = res.body.getReader();
|
|
172
|
+
batch_scope.defer(() => void reader.cancel().catch(() => {}));
|
|
118
173
|
const decoder = new TextDecoder();
|
|
119
174
|
let buffer = "";
|
|
120
175
|
let parcels = false;
|
|
121
176
|
for (;;) {
|
|
122
|
-
const
|
|
177
|
+
const read = await stream.scope.task(() => reader.read());
|
|
178
|
+
if (read === STALE) return;
|
|
179
|
+
const { done, value } = read;
|
|
180
|
+
if (batch_scope.closed) return;
|
|
123
181
|
if (done) break;
|
|
182
|
+
stream.go("reading");
|
|
124
183
|
buffer += decoder.decode(value, { stream: true });
|
|
125
184
|
let consumed = 0;
|
|
126
185
|
for (let p = read_parcel(buffer, 0); p; p = read_parcel(buffer, consumed)) {
|
|
@@ -160,8 +219,10 @@ async function streamFrames(endpoints, kind = "nav") {
|
|
|
160
219
|
final_url = res.url;
|
|
161
220
|
}
|
|
162
221
|
} finally {
|
|
222
|
+
stream.stop();
|
|
223
|
+
batch_scope.close();
|
|
163
224
|
for (const a of reserved) release(a);
|
|
164
|
-
if (BEACON && delivered < reserved.length) beacon_hole_batch_missed({
|
|
225
|
+
if (BEACON && delivered < reserved.length && !visit.closed) beacon_hole_batch_missed({
|
|
165
226
|
sent: reserved.length,
|
|
166
227
|
delivered,
|
|
167
228
|
status,
|
|
@@ -205,21 +266,26 @@ function join_batch(endpoint) {
|
|
|
205
266
|
flushed = new Promise((resolve) => queueMicrotask(() => {
|
|
206
267
|
const list = queued;
|
|
207
268
|
queued = null;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
for (const e of list) {
|
|
213
|
-
const a = frameAddress(e);
|
|
214
|
-
if (seen.has(a) || hinted.size && hinted.has(new URL(e, document.baseURI).href) || !is_idle(a)) continue;
|
|
215
|
-
seen.add(a);
|
|
216
|
-
fresh.push(e);
|
|
269
|
+
try {
|
|
270
|
+
gather(list);
|
|
271
|
+
} finally {
|
|
272
|
+
resolve();
|
|
217
273
|
}
|
|
218
|
-
if (fresh.length >= 2) for (let i = 0; i < fresh.length; i += MAX_BATCH) streamFrames(fresh.slice(i, i + MAX_BATCH), "page");
|
|
219
|
-
resolve();
|
|
220
274
|
}));
|
|
221
275
|
}
|
|
222
276
|
queued.push(endpoint);
|
|
223
277
|
return flushed;
|
|
224
278
|
}
|
|
279
|
+
/** The holes that joined in one task: the ones a batch can carry go out in batches of MAX_BATCH. */
|
|
280
|
+
function gather(list) {
|
|
281
|
+
const fresh = [];
|
|
282
|
+
const seen = /* @__PURE__ */ new Set();
|
|
283
|
+
for (const e of list) {
|
|
284
|
+
const a = frameAddress(e);
|
|
285
|
+
if (seen.has(a) || !is_idle(a)) continue;
|
|
286
|
+
seen.add(a);
|
|
287
|
+
fresh.push(e);
|
|
288
|
+
}
|
|
289
|
+
if (fresh.length >= 2) for (let i = 0; i < fresh.length; i += MAX_BATCH) streamFrames(fresh.slice(i, i + MAX_BATCH), "page");
|
|
290
|
+
}
|
|
225
291
|
export { batch_times, join_batch, read_parcel, streamFrames };
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { Frame } from "../frame.js";
|
|
2
|
+
import { Stale } from "@ogygia/machine";
|
|
2
3
|
export type FrameFetcher = (signal: AbortSignal) => Promise<string>;
|
|
4
|
+
/** A refused answer nobody asked for yet (the page's early start: ./early-holes.ts), held at its
|
|
5
|
+
* address for the first caller that asks there (see `Entry.refused`). Any other failure is not. */
|
|
6
|
+
export declare function hold_refusal(a: string, err: unknown): void;
|
|
3
7
|
/** Applied content for an address, if any. */
|
|
4
8
|
export declare function peek(a: string): {
|
|
5
9
|
v: number;
|
|
6
10
|
html: string;
|
|
7
11
|
} | null;
|
|
12
|
+
/** A renewal at this address answered `fresh`, the capability its elements fetch with from now on. */
|
|
13
|
+
export declare function renew(a: string, fresh: string): void;
|
|
14
|
+
/** The fresh capability a renewal at this address answered, if any. */
|
|
15
|
+
export declare function renewal(a: string): string | null;
|
|
8
16
|
/** Nothing applied and nothing in flight at this address (a batch may take it on). */
|
|
9
17
|
export declare function is_idle(a: string): boolean;
|
|
10
18
|
/** Next request ticket for an address. External channels (streaming, mutations) use this too. */
|
|
@@ -16,16 +24,17 @@ export declare function ticket(a: string): number;
|
|
|
16
24
|
export declare function write(f: Frame): boolean;
|
|
17
25
|
/**
|
|
18
26
|
* Reserve an address whose content is arriving via an EXTERNAL batch stream (single-flight navigation). A region
|
|
19
|
-
* binder that mounts before its frame lands calls `ensure()`,
|
|
27
|
+
* binder that mounts before its frame lands calls `ensure()`, finds the batch's flight there, and JOINS it
|
|
20
28
|
* instead of starting its own fetch — so a navigation that pulls N regions makes ONE batch request,
|
|
21
|
-
* not N. `write()` fulfils
|
|
22
|
-
*
|
|
23
|
-
* already present.
|
|
29
|
+
* not N. `write()` fulfils it when the frame arrives; `release()` fails it if the batch ends without
|
|
30
|
+
* one (the binder then falls back to its own fetch). The flight is the batch's: a binder leaving never
|
|
31
|
+
* ends it. No-op if content or a fetch is already present.
|
|
24
32
|
*/
|
|
25
33
|
export declare function reserve(a: string): void;
|
|
26
34
|
/**
|
|
27
|
-
* Fail an unfulfilled
|
|
28
|
-
* reject out of `ensure()` and retry with their own fetch. No-op once
|
|
35
|
+
* Fail an unfulfilled batch part (the batch ended and no frame was written at this address, by it or
|
|
36
|
+
* by anyone). Joined binders reject out of `ensure()` and retry with their own fetch. No-op once a
|
|
37
|
+
* frame has landed (it settled the part).
|
|
29
38
|
*/
|
|
30
39
|
export declare function release(a: string): void;
|
|
31
40
|
/** The error a joined reservation rejects with when its batch ended without its frame: the binder's
|
|
@@ -42,21 +51,21 @@ export declare function subscribe(a: string, cb: (f: Frame) => void): () => void
|
|
|
42
51
|
* Ensure an address has content, deduping concurrent callers onto one fetch.
|
|
43
52
|
*
|
|
44
53
|
* - Content already applied (and not `force`): resolves immediately with it.
|
|
45
|
-
* - A fetch is in flight (and not `force`): joins it — N regions, ONE request.
|
|
54
|
+
* - A fetch (or a batch's part) is in flight (and not `force`): joins it — N regions, ONE request.
|
|
46
55
|
* - Otherwise starts `fetcher` with a fresh ticket; on success the response is WRITTEN (subject to
|
|
47
56
|
* the staleness check) and the resolved value is whatever the store now holds.
|
|
48
|
-
* - `force` (SWR revalidate) always starts a new fetch
|
|
49
|
-
*
|
|
57
|
+
* - `force` (SWR revalidate) always starts a new fetch, and ends the one out before it: whoever
|
|
58
|
+
* waited on that one joins the new one. The ticket ordering still guarantees an older response
|
|
59
|
+
* can't overwrite a newer one.
|
|
50
60
|
*
|
|
51
|
-
*
|
|
61
|
+
* `signal` is the caller's: when it aborts, this caller alone gets `STALE` at once, and the shared
|
|
62
|
+
* fetch is aborted only once its LAST caller left — one unmounting element must not kill a fetch
|
|
63
|
+
* its twin still awaits. Rejections propagate to every joined caller; retry policy stays with the
|
|
64
|
+
* caller (the element).
|
|
52
65
|
*/
|
|
53
66
|
export declare function ensure(a: string, fetcher: FrameFetcher, opts?: {
|
|
54
67
|
force?: boolean;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
* A waiter is leaving (its element disconnected). Aborts the shared fetch only when the LAST
|
|
58
|
-
* waiter leaves — one unmounting element must not kill a fetch its twin still awaits.
|
|
59
|
-
*/
|
|
60
|
-
export declare function abandon(a: string): void;
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
}): Promise<string | Stale>;
|
|
61
70
|
/** Test/HMR hook — drop everything. */
|
|
62
71
|
export declare function _reset(): void;
|