@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
package/dist/hooks.js
CHANGED
|
@@ -13,32 +13,37 @@ import { region_mac_message, secret_has_min_entropy, sign, verify } from "./serv
|
|
|
13
13
|
import { expiry_refusal, note_cache_control } from "./server/shared-cache.js";
|
|
14
14
|
import { app_lifetime } from "./server/region-endpoint.js";
|
|
15
15
|
import { cached_render, render_cache_key } from "./server/render-cache.js";
|
|
16
|
+
import { render as render$1 } from "./policies.js";
|
|
17
|
+
import { install_server_rules } from "./server/rules.js";
|
|
16
18
|
import { B64Url } from "./server/payload.js";
|
|
17
19
|
import { REF_WIRE_KEY, ref_reviver } from "./ref.js";
|
|
18
20
|
import { current_facts, facts_of, mark_per_visitor, set_current_request } from "./plugin/facts.js";
|
|
19
|
-
import { around, request_contexts,
|
|
21
|
+
import { around, end_request, request_contexts, start_app_plugins } from "./plugin/server.js";
|
|
22
|
+
import { end_request_scope, own_response, request_scope } from "./plugin/request-scope.js";
|
|
20
23
|
import { register_wire_kind } from "./live-transport.js";
|
|
21
24
|
import { register_derived_kind, register_store_kind } from "./store-transport.js";
|
|
22
25
|
import { register_snippet_kind } from "./region-snippet.js";
|
|
23
26
|
import { register_fn_kind } from "./fn-transport.js";
|
|
24
27
|
import { configure_islands_endpoint, is_region_id, is_region_ttl, islands_endpoint } from "./server/endpoint.js";
|
|
25
|
-
import {
|
|
28
|
+
import { batch_stream } from "./server/stream-regions.js";
|
|
26
29
|
import { dedupe_head_links, page_declares_dev_hmr_script, page_declares_router_meta, page_declares_runtime_script, page_declares_speculation_rules, runtime_first } from "./server/head-presence.js";
|
|
27
30
|
import { assemble, locate } from "./server/document-assembly.js";
|
|
28
|
-
import { error_route_is_csr_true, route_is_csr_true } from "./context.js";
|
|
31
|
+
import { error_route_is_csr_true, route_is_csr_true, take_early_holes } from "./context.js";
|
|
32
|
+
import { early_holes_head } from "./early-holes-head.js";
|
|
33
|
+
import { add_early_holes_csp, with_early_holes_meta_csp } from "./server/early-holes-csp.js";
|
|
29
34
|
import { merge_seed_ask, shape_page_data } from "./server/seed-shape.js";
|
|
30
35
|
import { html_has_kit_bootstrap } from "./runtime/kit-boot.js";
|
|
31
36
|
import { STAND_IN_CSS, check_script, mark, settle } from "./server/reversible.js";
|
|
32
37
|
import { hole_head } from "./server/hole-graph.js";
|
|
33
38
|
import { restore, restore_adopt } from "./runtime/restore.js";
|
|
34
39
|
import { RateLimiter } from "./server/rate-limit.js";
|
|
35
|
-
import { KEEP_FALLBACK_HTML } from "./keep-fallback-marker.js";
|
|
36
40
|
import { PageSeed } from "./server/page-seed.js";
|
|
37
|
-
import {
|
|
41
|
+
import { SeedIndex, analyze, json_culprit, set_measure_memo_reader } from "./seed-refs.js";
|
|
38
42
|
import { WIRE_FORMAT_ATTR, WIRE_FORMAT_JSON } from "./server/props-wire.js";
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
43
|
+
import { KEEP_FALLBACK_HTML } from "./keep-fallback-marker.js";
|
|
44
|
+
import { page_seed_reducers, settle_deferred, stage_deferred, stream_page_deferred } from "./server/page-stream.js";
|
|
45
|
+
import { PAGE_DEFER_BOOTSTRAP } from "./page-defer.js";
|
|
46
|
+
import { ConcurrencyGate, is_run_timeout } from "./runtime/concurrency.js";
|
|
42
47
|
import { serialize_provided_context } from "./context-bridge.js";
|
|
43
48
|
import { escape_script_text } from "./escape.js";
|
|
44
49
|
import { hole_copy_key } from "./runtime/hash.js";
|
|
@@ -55,6 +60,9 @@ import { drop_dead_kit_resolves } from "./server/dead-kit-resolves.js";
|
|
|
55
60
|
import "./events/schema.js";
|
|
56
61
|
import { DEVTOOLS_COOKIE, SWITCH_COOKIE_OPTIONS, switch_in_url } from "./insights/switch.js";
|
|
57
62
|
import { mark_personal } from "./server/personal-response.js";
|
|
63
|
+
import { OgygiaError, warn } from "./messages/server.js";
|
|
64
|
+
import { ERRORS, WARNINGS } from "./messages/core.js";
|
|
65
|
+
import { define_messages, dev_or_prerender } from "./messages/define.js";
|
|
58
66
|
import { ogygiaTransport } from "./transport.js";
|
|
59
67
|
import { document } from "./document.js";
|
|
60
68
|
import { liftRegions, restoreRegions, scanRegions } from "./server/split-regions.js";
|
|
@@ -95,6 +103,8 @@ import { plugins } from "virtual:ogygia/server-plugins";
|
|
|
95
103
|
*
|
|
96
104
|
* @packageDocumentation
|
|
97
105
|
*/
|
|
106
|
+
globalThis[Symbol.for("ogygia.prerendering")] = building;
|
|
107
|
+
globalThis[Symbol.for("ogygia.messages")] ??= define_messages(import.meta.env?.DEV || import.meta.env?.SSR ? ERRORS : null, import.meta.env?.DEV || import.meta.env?.SSR ? WARNINGS : null, dev_or_prerender);
|
|
98
108
|
/** Is devtools on for this request's browser (a devtools BUILD): the URL's `?og-devtools=on|off`
|
|
99
109
|
* wins and is written to the response's cookie, else the cookie (insights/switch.ts). */
|
|
100
110
|
function devtools_switch(event) {
|
|
@@ -109,6 +119,7 @@ function devtools_switch(event) {
|
|
|
109
119
|
const MAX_REGION_BODY = 2e6;
|
|
110
120
|
const DEVTOOLS = typeof __OGYGIA_DEVTOOLS__ !== "undefined" ? __OGYGIA_DEVTOOLS__ : false;
|
|
111
121
|
const DEVTOOLS_LAZY = typeof __OGYGIA_DEVTOOLS_LAZY__ !== "undefined" ? __OGYGIA_DEVTOOLS_LAZY__ : false;
|
|
122
|
+
if (import.meta.env?.DEV || (typeof __OGYGIA_RULES__ !== "undefined" ? __OGYGIA_RULES__ : false)) install_server_rules();
|
|
112
123
|
/** High-res clock for server-realm devtools timestamps. */
|
|
113
124
|
const dt_now = () => typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
114
125
|
const bags = /* @__PURE__ */ new WeakMap();
|
|
@@ -220,7 +231,7 @@ set_source_recorder((tag) => {
|
|
|
220
231
|
current_facts()?.sources?.push(tag);
|
|
221
232
|
});
|
|
222
233
|
set_page_route_config(route_config);
|
|
223
|
-
const plugins$1 =
|
|
234
|
+
const plugins$1 = start_app_plugins(plugins, {
|
|
224
235
|
sign: (message) => sign(signing_key().secret, message),
|
|
225
236
|
verify: (message, signature) => verify(signing_key().secret, message, signature),
|
|
226
237
|
lifetime: app_lifetime,
|
|
@@ -242,10 +253,13 @@ if (DEVTOOLS) set_server_devtools_recorder((input) => dt_record(bag_of(), input)
|
|
|
242
253
|
/** Cap on a batch POST body before `request.json()` buffers it. 32 endpoints × ~8.5kB (props cap
|
|
243
254
|
* 8192 + URL overhead) ≈ 270kB; 512kB leaves margin. Rejected up front via `content-length`. */
|
|
244
255
|
const MAX_BATCH_BODY = 524288;
|
|
245
|
-
/** Abort waiting on slow region SSR (work may continue — see INVARIANTS · RENDER-TIMEOUT)
|
|
246
|
-
|
|
256
|
+
/** Abort waiting on slow region SSR (work may continue — see INVARIANTS · RENDER-TIMEOUT): the
|
|
257
|
+
* `render` policy's deadline (./policies.ts). */
|
|
258
|
+
const RENDER_TIMEOUT_MS = render$1.deadline;
|
|
247
259
|
/** Process-local cap on concurrent region SSR (M1 CPU amp under valid MAC). */
|
|
248
260
|
const render_gate = new ConcurrencyGate(4);
|
|
261
|
+
/** The abandoned renders already told (one render's timeout reaches every call that joined it). */
|
|
262
|
+
const told_abandoned = /* @__PURE__ */ new WeakSet();
|
|
249
263
|
/** The per-plugin contexts of an app with no plugins (shared: never written to). */
|
|
250
264
|
const NO_CTXS = [];
|
|
251
265
|
/**
|
|
@@ -385,31 +399,6 @@ function seed_codecs(transport) {
|
|
|
385
399
|
}
|
|
386
400
|
return hit;
|
|
387
401
|
}
|
|
388
|
-
/** `</body>` as bytes (ASCII: the same in every UTF-8 document). */
|
|
389
|
-
const BODY_CLOSE_BYTES = [
|
|
390
|
-
60,
|
|
391
|
-
47,
|
|
392
|
-
98,
|
|
393
|
-
111,
|
|
394
|
-
100,
|
|
395
|
-
121,
|
|
396
|
-
62
|
|
397
|
-
];
|
|
398
|
-
/** Does `</body>` end in `chunk` — searched backwards from its end, where it sits — or straddle the
|
|
399
|
-
* boundary with the previous chunk's last bytes (`carry`, at most 6)? No decode. */
|
|
400
|
-
function has_body_close(carry, chunk) {
|
|
401
|
-
const at_bytes = (buf, i) => {
|
|
402
|
-
for (let k = 0; k < 7; k++) if (buf[i + k] !== BODY_CLOSE_BYTES[k]) return false;
|
|
403
|
-
return true;
|
|
404
|
-
};
|
|
405
|
-
for (let i = chunk.lastIndexOf(60); i !== -1; i = i === 0 ? -1 : chunk.lastIndexOf(60, i - 1)) if (at_bytes(chunk, i)) return true;
|
|
406
|
-
if (!carry.length) return false;
|
|
407
|
-
const seam = new Uint8Array(carry.length + Math.min(6, chunk.length));
|
|
408
|
-
seam.set(carry);
|
|
409
|
-
seam.set(chunk.subarray(0, seam.length - carry.length), carry.length);
|
|
410
|
-
for (let i = 0; i + 7 <= seam.length; i++) if (at_bytes(seam, i)) return true;
|
|
411
|
-
return false;
|
|
412
|
-
}
|
|
413
402
|
function csr_page_of(html, body_end, event, bag) {
|
|
414
403
|
return event?.route?.id != null ? bag?.page?.error != null ? error_route_is_csr_true(event.route.id) : route_is_csr_true(event.route.id) : body_end !== -1 && html_has_kit_bootstrap(html, body_end);
|
|
415
404
|
}
|
|
@@ -424,6 +413,21 @@ function page_ctx_script(bag) {
|
|
|
424
413
|
/** The restorer, inline (runtime/restore.ts — self-contained, so its source runs as is): restores the
|
|
425
414
|
* document now, and stays as `__og_restore` / `__og_restore_adopt` for hole answers and router swaps. */
|
|
426
415
|
const RESTORER_SCRIPT = `<script data-og-restore>(function(){var r=${restore.toString()};window.__og_restore=r;window.__og_restore_adopt=${restore_adopt.toString()};r(document)})()<\/script>`;
|
|
416
|
+
/** `inner` with the request's owner around it: made before `inner` runs, ended with its response
|
|
417
|
+
* (or at once when it throws). */
|
|
418
|
+
function owned(inner) {
|
|
419
|
+
return async (input) => {
|
|
420
|
+
const owner = request_scope(input.event.request);
|
|
421
|
+
let response;
|
|
422
|
+
try {
|
|
423
|
+
response = await inner(input);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
end_request_scope(owner);
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
return own_response(owner, response, input.event.request);
|
|
429
|
+
};
|
|
430
|
+
}
|
|
427
431
|
var OgygiaHandle = class {
|
|
428
432
|
#endpoint;
|
|
429
433
|
#speculation_rules;
|
|
@@ -435,7 +439,7 @@ var OgygiaHandle = class {
|
|
|
435
439
|
this.#transform = options.transform;
|
|
436
440
|
configure_islands_endpoint(options.endpoint);
|
|
437
441
|
this.#endpoint = islands_endpoint();
|
|
438
|
-
if (options.secret?.trim() && !import.meta.env.DEV && !secret_has_min_entropy(options.secret)) throw new
|
|
442
|
+
if (options.secret?.trim() && !import.meta.env.DEV && !secret_has_min_entropy(options.secret)) throw new OgygiaError("handle_secret_too_short", { min: 16 });
|
|
439
443
|
configure_signing_secret(options.secret);
|
|
440
444
|
this.#speculation_rules = enabled ? "" : mpaSpeculationRules(this.#endpoint);
|
|
441
445
|
this.render_rate = new RateLimiter({
|
|
@@ -448,7 +452,7 @@ var OgygiaHandle = class {
|
|
|
448
452
|
windowMs: rateLimit.windowMs
|
|
449
453
|
});
|
|
450
454
|
}
|
|
451
|
-
handle = around(plugins$1, (input) => this.#core(input));
|
|
455
|
+
handle = owned(around(plugins$1, (input) => this.#core(input)));
|
|
452
456
|
#core = async ({ event, resolve }) => {
|
|
453
457
|
const path = decode_pathname(event.url.pathname);
|
|
454
458
|
if (path === null) return new Response("Bad Request", { status: 400 });
|
|
@@ -462,9 +466,7 @@ var OgygiaHandle = class {
|
|
|
462
466
|
}
|
|
463
467
|
return await this.#route(event, resolve, path, ctxs, facts);
|
|
464
468
|
} finally {
|
|
465
|
-
|
|
466
|
-
plugins$1[i].def.done?.(ctxs[i]);
|
|
467
|
-
} catch {}
|
|
469
|
+
end_request(plugins$1, ctxs, event.platform);
|
|
468
470
|
}
|
|
469
471
|
};
|
|
470
472
|
/** One signed hole rendered in place with THIS request's event (a plugin's `ctx.renderHole`:
|
|
@@ -525,6 +527,9 @@ var OgygiaHandle = class {
|
|
|
525
527
|
};
|
|
526
528
|
const response = await resolve(event, { transformPageChunk: async ({ html }) => this.inject_client_seeds(this.#transform ? await this.#transform_document(html, event, bag) : html, store?.state, event, bag) });
|
|
527
529
|
if (devtools_personal) mark_personal(response);
|
|
530
|
+
if (bag.early_holes?.length) try {
|
|
531
|
+
add_early_holes_csp(response.headers);
|
|
532
|
+
} catch {}
|
|
528
533
|
if (this.#transform && (response.headers.get("content-type") ?? "").includes("text/html")) try {
|
|
529
534
|
response.headers.append("vary", "x-ogygia-spa");
|
|
530
535
|
} catch {}
|
|
@@ -536,7 +541,7 @@ var OgygiaHandle = class {
|
|
|
536
541
|
let out = response;
|
|
537
542
|
for (let i = 0; i < ctxs.length; i++) out = await plugins$1[i].def.response?.(ctxs[i], out) ?? out;
|
|
538
543
|
if (out !== response) return out;
|
|
539
|
-
if (bag.deferred && bag.deferred.length) return
|
|
544
|
+
if (bag.deferred && bag.deferred.length) return stream_page_deferred(request_scope(event.request), response, bag.deferred, bag.defer_next_id, bag.seed_reducers ?? void 0);
|
|
540
545
|
if (!bag.kit_client && !response.headers.has("content-length") && (response.headers.get("content-type") ?? "").includes("text/html")) return drop_dead_kit_resolves(response);
|
|
541
546
|
return response;
|
|
542
547
|
}
|
|
@@ -566,17 +571,7 @@ var OgygiaHandle = class {
|
|
|
566
571
|
}
|
|
567
572
|
if (!Array.isArray(parsed) || parsed.length === 0) return region_response("Bad Request", { status: 400 });
|
|
568
573
|
const calls = parsed.filter((e) => typeof e === "string").slice(0, MAX_BATCH);
|
|
569
|
-
|
|
570
|
-
const encoder = new TextEncoder();
|
|
571
|
-
return region_response(new ReadableStream({ async start(controller) {
|
|
572
|
-
await Promise.all(calls.map(async (endpoint) => {
|
|
573
|
-
const out = await render(endpoint).catch(() => null);
|
|
574
|
-
const parcel = out ? build_parcel(out.slot, out.html) : null;
|
|
575
|
-
if (parcel) controller.enqueue(encoder.encode(parcel));
|
|
576
|
-
}));
|
|
577
|
-
controller.enqueue(encoder.encode(done_parcel()));
|
|
578
|
-
controller.close();
|
|
579
|
-
} }), {
|
|
574
|
+
return region_response(batch_stream(calls, (endpoint, signal) => this.#render_capability(endpoint, event, true, signal), request_scope(event.request)), {
|
|
580
575
|
status: 200,
|
|
581
576
|
headers: {
|
|
582
577
|
"content-type": "text/html; charset=utf-8",
|
|
@@ -633,23 +628,27 @@ var OgygiaHandle = class {
|
|
|
633
628
|
const t_start = performance.now();
|
|
634
629
|
const spans = locate(html);
|
|
635
630
|
const head = spans.head_end === -1 ? null : html.slice(0, spans.head_end);
|
|
631
|
+
const early = head === null || !event ? [] : take_early_holes(event.request);
|
|
632
|
+
if (early.length && bag) bag.early_holes = early;
|
|
633
|
+
const early_head = early_holes_head(early);
|
|
634
|
+
const with_early_csp = (h) => early.length ? with_early_holes_meta_csp(h) : h;
|
|
636
635
|
const csr_page = csr_page_of(html, spans.body_end, event, bag);
|
|
637
636
|
if (csr_page && bag) bag.kit_client = true;
|
|
638
637
|
if (csr_page) {
|
|
639
638
|
let head_inject = "";
|
|
640
639
|
if (head !== null) {
|
|
641
|
-
head_inject = CSR_META;
|
|
640
|
+
head_inject = CSR_META + early_head;
|
|
642
641
|
if (devtools_boot_url && !head.includes("data-ogygia-devtools-boot")) head_inject += `<script type="module" data-ogygia-devtools-boot src="${devtools_boot_url}"><\/script>`;
|
|
643
642
|
if (bag) bag.kit_with_runtime = page_declares_runtime_script(head);
|
|
644
643
|
}
|
|
645
644
|
if (head !== null && bag?.transform_head) head_inject = bag.transform_head + head_inject;
|
|
646
645
|
const tail = spans.body_end !== -1 && bag ? (bag.transform_body ?? "") + bag.tail.render(null) + (bag.kit_with_runtime ? page_ctx_script(bag) : "") : "";
|
|
647
646
|
if (head === null && !tail) return html;
|
|
648
|
-
const ordered = head === null ? null : runtime_first(head, null);
|
|
647
|
+
const ordered = head === null ? null : with_early_csp(runtime_first(head, null));
|
|
649
648
|
return assemble(html, spans, ordered === head ? null : ordered, head_inject, tail);
|
|
650
649
|
}
|
|
651
650
|
let head_out = null;
|
|
652
|
-
let head_inject = head !== null ? CSR_FALSE_META : "";
|
|
651
|
+
let head_inject = head !== null ? CSR_FALSE_META + early_head : "";
|
|
653
652
|
if (head !== null) {
|
|
654
653
|
const deduped = dedupe_head_links(head);
|
|
655
654
|
if (deduped !== head) head_out = deduped;
|
|
@@ -657,7 +656,7 @@ var OgygiaHandle = class {
|
|
|
657
656
|
if (!enabled && !page_declares_speculation_rules(probe)) head_inject += `<script type="speculationrules" data-ogygia-speculate>${this.#speculation_rules}<\/script>`;
|
|
658
657
|
if (enabled && !page_declares_router_meta(probe)) head_inject += `<meta name="ogygia-router" content="${viewTransitions ? "vt" : "plain"}">`;
|
|
659
658
|
const runtime_tag = enabled && runtime_url && !page_declares_runtime_script(probe) ? runtime_bootstrap(kit_asset) : null;
|
|
660
|
-
const ordered = runtime_first(probe, runtime_tag);
|
|
659
|
+
const ordered = with_early_csp(runtime_first(probe, runtime_tag));
|
|
661
660
|
if (ordered !== probe) head_out = ordered;
|
|
662
661
|
if (dev_hmr_url && !page_declares_dev_hmr_script(probe)) {
|
|
663
662
|
head_inject += `<script type="module" data-ogygia-dev-hmr src="${kit_asset(dev_hmr_url)}"><\/script>`;
|
|
@@ -676,9 +675,9 @@ var OgygiaHandle = class {
|
|
|
676
675
|
const page_snap = bag.page;
|
|
677
676
|
const seed_wanted = bag.seed_wanted;
|
|
678
677
|
const detail = request_stats_detailed();
|
|
679
|
-
const tail_html = bag.tail.render(seed_wanted ?
|
|
678
|
+
const tail_html = bag.tail.render(seed_wanted ? SeedIndex.of(page_snap.data) : null, detail);
|
|
680
679
|
let seed_keys = bag.seed_keys;
|
|
681
|
-
if (seed_wanted && seed_keys !== "all" && seed_keys !== null) for (const k of
|
|
680
|
+
if (seed_wanted && seed_keys !== "all" && seed_keys !== null) for (const k of SeedIndex.of(page_snap.data).touched) seed_keys.add(k);
|
|
682
681
|
const shaped_data = shape_page_data(page_snap.data, seed_keys);
|
|
683
682
|
let seed_data = shaped_data;
|
|
684
683
|
let seed_form = page_snap.form;
|
|
@@ -822,88 +821,6 @@ var OgygiaHandle = class {
|
|
|
822
821
|
if (DEVTOOLS && dt_buf && dt_buf.events.length) scripts.push(emit_ogygia_script("devtools", escape_script_text(JSON.stringify(dt_buf.events))));
|
|
823
822
|
}
|
|
824
823
|
/**
|
|
825
|
-
* Stream the captured `$page.data` promises into islands (csr=false, real browser load). The
|
|
826
|
-
* document — carrying the pending seed + resolve-global bootstrap — is already built by `resolve`;
|
|
827
|
-
* here we (1) forward it, (2) DRAIN Kit's own dead csr=false resolve tail so its
|
|
828
|
-
* `__sveltekit_<hash> is not defined` never reaches the browser, and (3) emit one
|
|
829
|
-
* `<script>__ogygia_page_resolve(id, ok, value)<\/script>` per promise AS IT SETTLES — completion
|
|
830
|
-
* order, non-blocking. Each island's `{#await page.data.x}` flips pending → resolved live.
|
|
831
|
-
*/
|
|
832
|
-
stream_page_deferred(response, deferred, initial_next_id, reducers) {
|
|
833
|
-
const source = response.body;
|
|
834
|
-
if (!source) return response;
|
|
835
|
-
const reader = source.getReader();
|
|
836
|
-
const encoder = new TextEncoder();
|
|
837
|
-
const stream = new ReadableStream({
|
|
838
|
-
async start(controller) {
|
|
839
|
-
let carry = /* @__PURE__ */ new Uint8Array(0);
|
|
840
|
-
try {
|
|
841
|
-
for (;;) {
|
|
842
|
-
const { value, done } = await reader.read();
|
|
843
|
-
if (done) break;
|
|
844
|
-
controller.enqueue(value);
|
|
845
|
-
if (has_body_close(carry, value)) break;
|
|
846
|
-
carry = value.length >= 6 ? value.subarray(value.length - 6) : value;
|
|
847
|
-
}
|
|
848
|
-
} catch {}
|
|
849
|
-
(async () => {
|
|
850
|
-
try {
|
|
851
|
-
for (;;) {
|
|
852
|
-
const { done } = await reader.read();
|
|
853
|
-
if (done) break;
|
|
854
|
-
}
|
|
855
|
-
} catch {}
|
|
856
|
-
})();
|
|
857
|
-
let pending = deferred.length;
|
|
858
|
-
let next_id = initial_next_id;
|
|
859
|
-
let closed = false;
|
|
860
|
-
const maybe_close = () => {
|
|
861
|
-
if (pending === 0 && !closed) {
|
|
862
|
-
closed = true;
|
|
863
|
-
try {
|
|
864
|
-
controller.close();
|
|
865
|
-
} catch {}
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
const push = (id, ok, value) => {
|
|
869
|
-
try {
|
|
870
|
-
controller.enqueue(encoder.encode(resolve_script(PAGE_DEFER_GLOBAL, id, {
|
|
871
|
-
ok,
|
|
872
|
-
value
|
|
873
|
-
}, reducers)));
|
|
874
|
-
} catch {}
|
|
875
|
-
};
|
|
876
|
-
const stream_one = (id, promise) => {
|
|
877
|
-
Promise.resolve(promise).then((value) => {
|
|
878
|
-
const { staged, deferred: nested, next_id: after } = stage_deferred(value, next_id);
|
|
879
|
-
next_id = after;
|
|
880
|
-
push(id, true, staged);
|
|
881
|
-
pending += nested.length;
|
|
882
|
-
for (const n of nested) stream_one(n.id, n.promise);
|
|
883
|
-
pending -= 1;
|
|
884
|
-
maybe_close();
|
|
885
|
-
}, (error) => {
|
|
886
|
-
push(id, false, error);
|
|
887
|
-
pending -= 1;
|
|
888
|
-
maybe_close();
|
|
889
|
-
});
|
|
890
|
-
};
|
|
891
|
-
for (const { id, promise } of deferred) stream_one(id, promise);
|
|
892
|
-
maybe_close();
|
|
893
|
-
},
|
|
894
|
-
cancel() {
|
|
895
|
-
reader.cancel().catch(() => {});
|
|
896
|
-
}
|
|
897
|
-
});
|
|
898
|
-
const headers = new Headers(response.headers);
|
|
899
|
-
headers.delete("content-length");
|
|
900
|
-
return new Response(stream, {
|
|
901
|
-
status: response.status,
|
|
902
|
-
statusText: response.statusText,
|
|
903
|
-
headers
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
/**
|
|
907
824
|
* True when a remote's resolved value (deep) contains a region carrying baked SSR HTML — a
|
|
908
825
|
* page-sized render, not seed data (see the skip in {@link build_remote_seed_script}). Depth-capped:
|
|
909
826
|
* a region ticket sits shallow in any sane payload (a DocView's `entry.body` is 2 levels deep).
|
|
@@ -934,23 +851,25 @@ var OgygiaHandle = class {
|
|
|
934
851
|
* if the render threw or timed out. Shared by the endpoint ({@link render_region}) and the
|
|
935
852
|
* batch path ({@link #render_capability}).
|
|
936
853
|
*/
|
|
937
|
-
async #render_component(load, props, cache, report, timing, base, reads_page_data = false) {
|
|
854
|
+
async #render_component(load, props, cache, report, timing, base, reads_page_data = false, signal) {
|
|
938
855
|
const render_body = async () => {
|
|
939
856
|
const mod = await load();
|
|
940
|
-
const
|
|
857
|
+
const out = await render(mod.default, {
|
|
941
858
|
props,
|
|
942
859
|
context: kit_render_context()
|
|
943
860
|
});
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
return hole_head(out.head, base) + body + kit_page_facts_tail();
|
|
948
|
-
}), new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("region render timeout")), RENDER_TIMEOUT_MS))]);
|
|
861
|
+
const body = out.body;
|
|
862
|
+
if (body === "<!--ogygia:keep-fallback-->") return body;
|
|
863
|
+
return hole_head(out.head, base) + body + kit_page_facts_tail();
|
|
949
864
|
};
|
|
950
865
|
try {
|
|
951
|
-
return await cached_render(async () => {
|
|
866
|
+
return await cached_render(async (render_signal) => {
|
|
867
|
+
const gated = {
|
|
868
|
+
deadline_ms: RENDER_TIMEOUT_MS,
|
|
869
|
+
signal: render_signal
|
|
870
|
+
};
|
|
952
871
|
await kit_page_ready(reads_page_data);
|
|
953
|
-
if (!timing) return render_gate.run(render_body);
|
|
872
|
+
if (!timing) return render_gate.run(render_body, gated);
|
|
954
873
|
const asked = performance.now();
|
|
955
874
|
return render_gate.run(async () => {
|
|
956
875
|
const entered = performance.now();
|
|
@@ -960,19 +879,23 @@ var OgygiaHandle = class {
|
|
|
960
879
|
} finally {
|
|
961
880
|
timing.render_ms = performance.now() - entered;
|
|
962
881
|
}
|
|
963
|
-
});
|
|
964
|
-
}, cache,
|
|
882
|
+
}, gated);
|
|
883
|
+
}, cache, report, signal);
|
|
965
884
|
} catch (e) {
|
|
966
885
|
if (is_keep_fallback(e)) return KEEP_FALLBACK_HTML;
|
|
967
|
-
if (
|
|
886
|
+
if (signal?.aborted) return null;
|
|
887
|
+
if (is_run_timeout(e) && render_gate.abandoned === 1 && !told_abandoned.has(e)) {
|
|
888
|
+
told_abandoned.add(e);
|
|
889
|
+
warn("region_render_abandoned", { ms: RENDER_TIMEOUT_MS });
|
|
890
|
+
}
|
|
891
|
+
if (import.meta.env.DEV) warn("region_render_failed", e);
|
|
968
892
|
return null;
|
|
969
893
|
}
|
|
970
894
|
}
|
|
971
895
|
/** A cached hole's memo key: the call and the visitor's session, and — when its tree reads
|
|
972
896
|
* `page.data` — the page it renders for (one page's facts never answer another page's hole). */
|
|
973
897
|
#hole_cache_key(id, payload, event, reads_page_data) {
|
|
974
|
-
|
|
975
|
-
return render_cache_key(id, payload, reads_page_data ? session + "\n" + page_url_of(event).href : session);
|
|
898
|
+
return render_cache_key(id, payload, this.#region_session(event), reads_page_data ? page_url_of(event).href : null);
|
|
976
899
|
}
|
|
977
900
|
/** The session cookie value sealed into a region capability (empty when no `sessionCookie` is
|
|
978
901
|
* configured) — the same read the MAC verify uses, and the per-user part of the render-cache key. */
|
|
@@ -1031,7 +954,7 @@ var OgygiaHandle = class {
|
|
|
1031
954
|
register_derived_kind();
|
|
1032
955
|
props = devalue.parse(B64Url.decode(payload), { [REF_WIRE_KEY]: ref_reviver(false) });
|
|
1033
956
|
} catch (e) {
|
|
1034
|
-
if (import.meta.env.DEV)
|
|
957
|
+
if (import.meta.env.DEV) warn("endpoint_props_decode_failed", e);
|
|
1035
958
|
return null;
|
|
1036
959
|
}
|
|
1037
960
|
if (props === null || typeof props !== "object" || Array.isArray(props)) return null;
|
|
@@ -1043,9 +966,9 @@ var OgygiaHandle = class {
|
|
|
1043
966
|
* bad/expired MAC, unknown id, non-serializable props, render error, oversize — resolves to
|
|
1044
967
|
* `null` so the hole silently falls back to a client fetch. Never throws.
|
|
1045
968
|
*/
|
|
1046
|
-
async #render_capability(endpoint, event, batch = false) {
|
|
969
|
+
async #render_capability(endpoint, event, batch = false, signal) {
|
|
1047
970
|
const q = endpoint.indexOf("?");
|
|
1048
|
-
if (q === -1) return null;
|
|
971
|
+
if (q === -1 || signal?.aborted) return null;
|
|
1049
972
|
const cap = capability_of(new URLSearchParams(endpoint.slice(q + 1)));
|
|
1050
973
|
const { id, payload, sig } = cap;
|
|
1051
974
|
if (this.#capability_precheck(cap)) return null;
|
|
@@ -1066,7 +989,7 @@ var OgygiaHandle = class {
|
|
|
1066
989
|
const t0 = stats ? performance.now() : 0;
|
|
1067
990
|
const timing = {};
|
|
1068
991
|
let outcome = "none";
|
|
1069
|
-
const body = await this.#render_component(load, props, cache, stats ? (o) => outcome = o : void 0, stats ? timing : void 0, event.url, reads_page_data);
|
|
992
|
+
const body = await this.#render_component(load, props, cache, stats ? (o) => outcome = o : void 0, stats ? timing : void 0, event.url, reads_page_data, signal);
|
|
1070
993
|
if (stats) record_batch_hole_stats(event.request, {
|
|
1071
994
|
kind: "hole",
|
|
1072
995
|
id,
|
|
@@ -1078,7 +1001,7 @@ var OgygiaHandle = class {
|
|
|
1078
1001
|
ms: performance.now() - t0,
|
|
1079
1002
|
status: body === null ? 500 : 200
|
|
1080
1003
|
});
|
|
1081
|
-
if (body === null || body.length > MAX_REGION_BODY) return null;
|
|
1004
|
+
if (body === null || body.length > MAX_REGION_BODY || signal?.aborted) return null;
|
|
1082
1005
|
if (body === "<!--ogygia:keep-fallback-->") return {
|
|
1083
1006
|
slot: sig,
|
|
1084
1007
|
html: KEEP_FALLBACK_HTML
|
|
@@ -1116,7 +1039,11 @@ var OgygiaHandle = class {
|
|
|
1116
1039
|
const renewal = url.searchParams.get("renew") === "1";
|
|
1117
1040
|
const refused = this.#capability_precheck(cap, renewal);
|
|
1118
1041
|
if (refused) {
|
|
1119
|
-
if (import.meta.env.DEV && refused === "foreign-key")
|
|
1042
|
+
if (import.meta.env.DEV && refused === "foreign-key") warn("endpoint_foreign_key", {
|
|
1043
|
+
id,
|
|
1044
|
+
key: String(cap.k),
|
|
1045
|
+
own: signing_key().fp
|
|
1046
|
+
});
|
|
1120
1047
|
return region_denied(refused);
|
|
1121
1048
|
}
|
|
1122
1049
|
if (this.probe_rate.limited(ip)) return region_response("Too Many Requests", { status: 429 });
|
|
@@ -1126,7 +1053,7 @@ var OgygiaHandle = class {
|
|
|
1126
1053
|
cap = fresh;
|
|
1127
1054
|
renewed = `${url.pathname}?id=${encodeURIComponent(id)}${cap.render ? `&r=${cap.render}` : ""}&props=${payload}&exp=${cap.exp}${ttl_raw ? `&ttl=${ttl_raw}` : ""}&sig=${cap.sig}&k=${cap.k}`;
|
|
1128
1055
|
} else if (!this.#verify_region_mac(cap, event)) {
|
|
1129
|
-
if (import.meta.env.DEV)
|
|
1056
|
+
if (import.meta.env.DEV) warn("endpoint_bad_signature", { id });
|
|
1130
1057
|
return region_denied("sig");
|
|
1131
1058
|
}
|
|
1132
1059
|
const cache_control = ttl_raw && !renewed ? `private, max-age=${Number(ttl_raw)}` : "no-store";
|
|
@@ -1135,7 +1062,11 @@ var OgygiaHandle = class {
|
|
|
1135
1062
|
if (this.render_rate.limited(ip)) return region_response("Too Many Requests", { status: 429 });
|
|
1136
1063
|
const target = capability_target(cap);
|
|
1137
1064
|
if (target === null) {
|
|
1138
|
-
if (import.meta.env.DEV)
|
|
1065
|
+
if (import.meta.env.DEV) warn("endpoint_unknown_id", {
|
|
1066
|
+
id,
|
|
1067
|
+
component: cap.render || void 0,
|
|
1068
|
+
known: Object.keys(island_url).length
|
|
1069
|
+
});
|
|
1139
1070
|
return region_denied("unknown-id");
|
|
1140
1071
|
}
|
|
1141
1072
|
const load = (await load_islands()).islands[target];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { find_unscoped } from "../unscoped-css.js";
|
|
2
|
+
import { scope } from "@ogygia/machine";
|
|
2
3
|
// src/insights/page-assets.ts
|
|
3
4
|
/**
|
|
4
5
|
* THE PAGE'S WEIGHT — every file the rendered document makes the browser fetch at start, found in
|
|
@@ -315,7 +316,7 @@ async function weigh_assets(opts) {
|
|
|
315
316
|
if (w === void 0) {
|
|
316
317
|
const got = await weigh_one(r, opts, deadline, origin);
|
|
317
318
|
if ("miss" in got) {
|
|
318
|
-
if (got.
|
|
319
|
+
if (got.gone) opts.cache.set(r.url, null);
|
|
319
320
|
miss(r.url, got.miss);
|
|
320
321
|
continue;
|
|
321
322
|
}
|
|
@@ -564,13 +565,20 @@ function byte_length(s) {
|
|
|
564
565
|
}
|
|
565
566
|
return n;
|
|
566
567
|
}
|
|
568
|
+
/** A status that says the file is not there for good: a client error, but not a timeout or a
|
|
569
|
+
* rate limit (408, 429), which say "not now", as a 5xx does. */
|
|
570
|
+
const gone_for_good = (status) => status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
567
571
|
async function weigh_one(r, opts, deadline, origin) {
|
|
568
572
|
const left = deadline - Date.now();
|
|
569
573
|
if (left < 100) return { miss: "budget" };
|
|
570
574
|
try {
|
|
571
|
-
const
|
|
575
|
+
const wait = scope();
|
|
576
|
+
const res = await Promise.race([opts.fetch_url(r.url.startsWith(origin) ? r.url.slice(origin.length) || "/" : r.url), new Promise((ok) => wait.timeout(() => ok(null), left))]).finally(() => wait.close());
|
|
572
577
|
if (!res) return { miss: "timeout" };
|
|
573
|
-
if (!res.ok) return
|
|
578
|
+
if (!res.ok) return gone_for_good(res.status) ? {
|
|
579
|
+
miss: `status ${res.status}`,
|
|
580
|
+
gone: true
|
|
581
|
+
} : { miss: `status ${res.status}` };
|
|
574
582
|
const enc = res.headers.get("content-encoding");
|
|
575
583
|
const len = Number(res.headers.get("content-length"));
|
|
576
584
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
@@ -425,8 +425,6 @@ export interface PreloadMiss {
|
|
|
425
425
|
crossorigin: string | null;
|
|
426
426
|
/** the answer's `Vary` header, when the devtools read it (a font downloaded again) */
|
|
427
427
|
vary?: string;
|
|
428
|
-
/** a hole the runtime asked for: the preload is ogygia's own (on any endpoint path) */
|
|
429
|
-
hole?: true;
|
|
430
428
|
}
|
|
431
429
|
export interface HoleWait {
|
|
432
430
|
name: string;
|
|
@@ -1197,21 +1197,20 @@ function analyze_page(page, regions, failures = [], now = Infinity, cpu = null)
|
|
|
1197
1197
|
};
|
|
1198
1198
|
const kb = (n) => n >= 1024 ? `${(n / 1024).toFixed(1)} KB` : `${n} B`;
|
|
1199
1199
|
const safari = !!page.browser?.startsWith("Safari");
|
|
1200
|
-
const ours = (m) => m.type === "fetch" && m.crossorigin === "anonymous" && m.hole === true;
|
|
1201
1200
|
const safari_fetch = (m) => safari && m.type === "fetch" && m.crossorigin !== null;
|
|
1202
1201
|
const varies = (m) => !!m.vary && m.vary.toLowerCase().split(",").some((v) => v.trim() === "origin" || v.trim() === "*");
|
|
1203
1202
|
const safari_font = (m) => safari && m.type === "font" && m.crossorigin !== null && varies(m);
|
|
1204
|
-
const why = (m) => safari_font(m) ? `its answer says \`Vary: ${m.vary}\`: ${page.browser} does not reuse a font preload that varies by Origin once the @font-face asking for it comes from a stylesheet added after the page parsed${page.dev ? " (on a dev server both are common: it answers every file with Vary: Origin and adds the page's CSS by script)" : ""}` :
|
|
1203
|
+
const why = (m) => safari_font(m) ? `its answer says \`Vary: ${m.vary}\`: ${page.browser} does not reuse a font preload that varies by Origin once the @font-face asking for it comes from a stylesheet added after the page parsed${page.dev ? " (on a dev server both are common: it answers every file with Vary: Origin and adds the page's CSS by script)" : ""}` : safari_fetch(m) ? `${page.browser} never hands a fetch preload to a later fetch(), whatever its crossorigin, credentials or Cache-Control: in Safari it is always a second download (Chromium uses it)` : m.type === "font" && m.crossorigin === null ? "a font always loads in CORS mode: its preload needs `crossorigin`" : m.type === "fetch" && m.crossorigin === null ? "a fetch preload needs `crossorigin` (`anonymous` for a same-origin `fetch()`, `use-credentials` for `credentials: \"include\"`)" : m.type === "fetch" || m.type === "font" ? `its \`crossorigin="${m.crossorigin}"\`${m.crossorigin ? "" : " (anonymous)"} does not match the request's credentials` : m.type === "script" ? "a module script is preloaded with `<link rel=\"modulepreload\">`, not `rel=\"preload\" as=\"script\"`" : "its `as` or `crossorigin` does not match how the page requests the file";
|
|
1205
1204
|
const wasted = misses.reduce((a, m) => a + m.bytes, 0);
|
|
1206
1205
|
findings.push({
|
|
1207
1206
|
code: "preload-unused",
|
|
1208
1207
|
severity: "warn",
|
|
1209
1208
|
message: `${list(misses.map((m) => `${file(m.url)} (${kb(m.bytes)})`))} ${misses.length === 1 ? "was" : "were"} preloaded, then downloaded again: the browser could not use the preload${misses.length === 1 ? "" : "s"}, and the page paid ${kb(wasted)} twice.`,
|
|
1210
|
-
fix: `${[...new Set(misses.map(why))].map((w) => w[0].toUpperCase() + w.slice(1)).join(". ")}.` +
|
|
1211
|
-
misses.some(
|
|
1209
|
+
fix: `${[...new Set(misses.map(why))].map((w) => w[0].toUpperCase() + w.slice(1)).join(". ")}.` + [
|
|
1210
|
+
misses.some(safari_fetch) ? " No attribute changes a fetch preload in Safari: to start the request early there too, begin it in an inline script and hand its promise to the code that reads it — or drop the preload where Safari is most of the traffic." : "",
|
|
1212
1211
|
misses.some(safari_font) ? page.dev ? " For the fonts: a build drops the dev server's habits (its CSS linked, its files answered by the host), so check whether the host also answers fonts with Vary: Origin and whether a stylesheet naming them is added by script." : " For the fonts: answer them without `Vary: Origin` (a font is the same whoever asks; a CORS header alone does not need it), or name them in a stylesheet the HTML links." : "",
|
|
1213
|
-
misses.some((m) => !
|
|
1214
|
-
].join("")
|
|
1212
|
+
misses.some((m) => !safari_fetch(m) && !safari_font(m)) ? " The preload and the request must match exactly, or the browser fetches the file again." : ""
|
|
1213
|
+
].join(""),
|
|
1215
1214
|
fps: []
|
|
1216
1215
|
});
|
|
1217
1216
|
}
|
|
@@ -1458,18 +1457,23 @@ function lcp_rivals(visit) {
|
|
|
1458
1457
|
const below = visit?.images_eager_below;
|
|
1459
1458
|
const p = visit?.paints;
|
|
1460
1459
|
if (!visit || !below?.length || typeof p?.lcp !== "number") return null;
|
|
1461
|
-
const
|
|
1462
|
-
const
|
|
1460
|
+
const by_url = /* @__PURE__ */ new Map();
|
|
1461
|
+
for (const r of visit.resources ?? []) if (!by_url.has(r.url)) by_url.set(r.url, r);
|
|
1462
|
+
const res = p.lcp_url ? by_url.get(p.lcp_url) : void 0;
|
|
1463
1463
|
const from = res ? res.req_start ?? res.start : visit.nav?.res_start ?? visit.nav?.res_end ?? 0;
|
|
1464
1464
|
const to = res ? res.end : p.lcp;
|
|
1465
1465
|
let bytes = 0;
|
|
1466
1466
|
const files = [];
|
|
1467
|
+
const named = /* @__PURE__ */ new Set();
|
|
1467
1468
|
for (const i of below) {
|
|
1468
|
-
const r =
|
|
1469
|
+
const r = by_url.get(i.url);
|
|
1469
1470
|
if (!r || r.start >= to || r.end <= from) continue;
|
|
1470
1471
|
bytes += i.bytes;
|
|
1471
1472
|
const f = i.url.slice(i.url.lastIndexOf("/") + 1).split("?")[0];
|
|
1472
|
-
if (!
|
|
1473
|
+
if (!named.has(f)) {
|
|
1474
|
+
named.add(f);
|
|
1475
|
+
files.push(f);
|
|
1476
|
+
}
|
|
1473
1477
|
}
|
|
1474
1478
|
return bytes ? {
|
|
1475
1479
|
bytes,
|
|
@@ -422,7 +422,7 @@ function analyze_session(s, names, page_url) {
|
|
|
422
422
|
else if (e.name === "region.hydrate.conflict") findings.push({
|
|
423
423
|
code: "hydrate-conflict",
|
|
424
424
|
severity: "warn",
|
|
425
|
-
message: `${name_of(e.fp ?? null)}: ${e.reason ?? "a web component
|
|
425
|
+
message: `${name_of(e.fp ?? null)}: ${e.reason ?? "a web component"} changed its own children where Svelte's hydration reads, so the repair removed what it added (render it with {@html}, put it in a lake, or mark it data-ogygia-keep).`,
|
|
426
426
|
refs: e.fp ? [{
|
|
427
427
|
kind: "island",
|
|
428
428
|
fp: e.fp
|
|
@@ -444,7 +444,7 @@ function analyze_session(s, names, page_url) {
|
|
|
444
444
|
refs: []
|
|
445
445
|
});
|
|
446
446
|
}
|
|
447
|
-
const errors = s.events.some((e) => e.name === "region.hydrate.failed") ? s.errors.filter((e) => !e.message.startsWith("console.error: [ogygia] hydration failed")) : s.errors;
|
|
447
|
+
const errors = s.events.some((e) => e.name === "region.hydrate.failed") ? s.errors.filter((e) => !(e.message.startsWith("console.error: OgygiaError: [ogygia] hydration failed") || e.message.includes("ogygiajs.dev/e/island_hydrate_failed"))) : s.errors;
|
|
448
448
|
if (errors.length) findings.push({
|
|
449
449
|
code: "errors",
|
|
450
450
|
severity: "error",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AttrChange, BeaconPage, beacon_page, beacon_record_cpu, hole_request_times, is_headless } from "./runtime/beacon.js";
|
|
2
2
|
import { props_sidecar_of } from "./runtime/sidecar.js";
|
|
3
|
+
import { import_again } from "./runtime/import-again.js";
|
|
4
|
+
import { download } from "./policies.js";
|
|
3
5
|
import { PAGE_SEED_SELECTOR } from "./runtime/seeds.js";
|
|
4
6
|
import { paint_blocker } from "./runtime/render-blocking.js";
|
|
5
7
|
import { RegionRefusal } from "./runtime/region-endpoint-url.js";
|
|
@@ -10,4 +12,4 @@ import { tool_fetch, tool_made } from "./tool-fetches.js";
|
|
|
10
12
|
import { PAGE_DEFER_GLOBAL, PAGE_DEFER_KEY, PAGE_DEFER_REGISTRY_KEY } from "./page-defer.js";
|
|
11
13
|
import { ISLAND_GRAPH_ATTR, decode_island_graph } from "./island-graph.js";
|
|
12
14
|
import { UnscopedStyles, find_unscoped, unscoped_finding, unscoped_marker } from "./unscoped-css.js";
|
|
13
|
-
export { type AttrChange, type BeaconPage, ISLAND_GRAPH_ATTR, PAGE_DEFER_GLOBAL, PAGE_DEFER_KEY, PAGE_DEFER_REGISTRY_KEY, PAGE_SEED_SELECTOR, REGION_RENDER_CONCURRENCY, type RegionRefusal, type UnscopedStyles, beacon_page, beacon_record_cpu, charge, decode_island_graph, find_unscoped, hole_request_times, is_headless, nearest_attr, own_cost, paint_blocker, props_sidecar_of, tool_fetch, tool_made, unscoped_finding, unscoped_marker };
|
|
15
|
+
export { type AttrChange, type BeaconPage, ISLAND_GRAPH_ATTR, PAGE_DEFER_GLOBAL, PAGE_DEFER_KEY, PAGE_DEFER_REGISTRY_KEY, PAGE_SEED_SELECTOR, REGION_RENDER_CONCURRENCY, type RegionRefusal, type UnscopedStyles, beacon_page, beacon_record_cpu, charge, decode_island_graph, download, find_unscoped, hole_request_times, import_again, is_headless, nearest_attr, own_cost, paint_blocker, props_sidecar_of, tool_fetch, tool_made, unscoped_finding, unscoped_marker };
|