@ogygia/core 0.11.5 → 0.12.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/Region.svelte +28 -21
- package/dist/Region.svelte.d.ts +0 -2
- package/dist/compiler/bom.js +1 -1
- package/dist/compiler/browser.d.ts +1 -1
- package/dist/compiler/browser.js +1 -1
- package/dist/compiler/ctx.d.ts +1 -1
- package/dist/compiler/ctx.js +3 -3
- package/dist/compiler/{macros/dedent.d.ts → dedent.d.ts} +12 -1
- package/dist/compiler/dedent.js +43 -0
- package/dist/compiler/driver.d.ts +9 -6
- package/dist/compiler/driver.js +16 -24
- package/dist/compiler/index.d.ts +2 -2
- package/dist/compiler/index.js +2 -2
- package/dist/compiler/link/caps.js +59 -9
- package/dist/compiler/link/island-deps.js +225 -22
- package/dist/compiler/link/location-map.d.ts +10 -0
- package/dist/compiler/link/location-map.js +53 -0
- package/dist/compiler/link/manifest.js +5 -1
- package/dist/compiler/link/server-manifest.js +43 -7
- package/dist/compiler/link/transport.js +39 -7
- package/dist/compiler/macros/source.js +2 -2
- package/dist/compiler/region/transform.d.ts +13 -4
- package/dist/compiler/region/transform.js +15 -6
- package/dist/hooks.d.ts +16 -0
- package/dist/hooks.js +4 -2
- package/dist/insights/page-insights.js +2 -2
- package/dist/runtime/beacon.js +1 -1
- package/dist/runtime/boot-link.js +4 -1
- package/dist/runtime/core.js +3 -2
- package/dist/runtime/entry-locations.d.ts +10 -3
- package/dist/runtime/entry-locations.js +132 -21
- package/dist/runtime/morph.d.ts +7 -0
- package/dist/runtime/morph.js +21 -15
- package/dist/runtime/ownership.d.ts +10 -1
- package/dist/runtime/ownership.js +19 -1
- package/dist/runtime/reconcile.d.ts +5 -2
- package/dist/runtime/reconcile.js +52 -16
- package/dist/runtime/region-endpoint-url.js +1 -0
- package/dist/runtime/router-nav.js +15 -0
- package/dist/runtime/router.js +3 -0
- package/dist/runtime/slots.d.ts +8 -2
- package/dist/server/document-tail.d.ts +3 -2
- package/dist/server/document-tail.js +4 -3
- package/dist/server/entry-location.js +11 -8
- package/dist/server/props-wire.js +2 -2
- package/dist/server/signing-key.d.ts +10 -2
- package/dist/server/signing-key.js +49 -15
- package/dist/vite/index.js +5 -4
- package/dist/vite/kit-dirs.js +1 -1
- package/package.json +1 -1
- package/dist/compiler/link/entry-shim.d.ts +0 -4
- package/dist/compiler/link/entry-shim.js +0 -29
- package/dist/compiler/macros/dedent.js +0 -36
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { path } from "../host.js";
|
|
2
|
+
import { dedent } from "../dedent.js";
|
|
2
3
|
import { merge_page_keys } from "./page-keys.js";
|
|
3
4
|
import { hydration_hazards } from "../../insights/hydration-hazards.js";
|
|
4
5
|
import { OWN_FILE_RE, OWN_PACKAGE_NAMES } from "../../own-package.js";
|
|
@@ -499,27 +500,229 @@ function collect_inline_css(bundle, hrefs, threshold) {
|
|
|
499
500
|
return out;
|
|
500
501
|
}
|
|
501
502
|
function island_deps_module(ssr, is_dev, out_dir_rel = ".svelte-kit") {
|
|
502
|
-
if (!ssr) return `
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
function
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
503
|
+
if (!ssr) return dedent`
|
|
504
|
+
export function islandDeps(_entry) { return []; }
|
|
505
|
+
export function islandCss(_entry) { return []; }
|
|
506
|
+
export function islandCssInline(_href) { return null; }
|
|
507
|
+
export function contentCss(_id) { return []; }
|
|
508
|
+
export function islandReadsPage(_entry) { return false; }
|
|
509
|
+
export function islandPageKeys(_entry) { return null; }
|
|
510
|
+
export function islandPageWhy(_entry) { return null; }
|
|
511
|
+
export function islandRemotes(_entry) { return null; }
|
|
512
|
+
export function islandInteractivity(_entry) { return null; }
|
|
513
|
+
export function islandHazards(_entry) { return null; }
|
|
514
|
+
export function chunkContents(_href) { return null; }
|
|
515
|
+
export function chunkHeavy(_href) { return null; }
|
|
516
|
+
export function chunkBarrels(_href) { return null; }
|
|
517
|
+
export function chunkDuplicates() { return null; }
|
|
518
|
+
export function fnManifest() { return null; }
|
|
519
|
+
export function entryLocation(_identity) { return null; }
|
|
520
|
+
export function locationMap() { return null; }
|
|
521
|
+
`;
|
|
522
|
+
if (is_dev) return dedent`
|
|
523
|
+
export function islandDeps(_entry) { return []; }
|
|
524
|
+
export function islandCss(entry) { return entry ? [entry] : []; }
|
|
525
|
+
export function islandCssInline(_href) { return null; }
|
|
526
|
+
export function contentCss(_id) { return []; }
|
|
527
|
+
export function islandReadsPage(_entry) { return true; }
|
|
528
|
+
export function islandPageKeys(_entry) { return null; }
|
|
529
|
+
export function islandPageWhy(_entry) { return null; }
|
|
530
|
+
export function islandRemotes(_entry) { return null; }
|
|
531
|
+
export function islandInteractivity(_entry) { return null; }
|
|
532
|
+
export function islandHazards(_entry) { return null; }
|
|
533
|
+
export function chunkContents(_href) { return null; }
|
|
534
|
+
export function chunkHeavy(_href) { return null; }
|
|
535
|
+
export function chunkBarrels(_href) { return null; }
|
|
536
|
+
export function chunkDuplicates() { return null; }
|
|
537
|
+
export function fnManifest() { return null; }
|
|
538
|
+
export function entryLocation(identity) { return identity || null; }
|
|
539
|
+
export function locationMap() { return null; }
|
|
540
|
+
`;
|
|
541
|
+
const out_dir = JSON.stringify(out_dir_rel);
|
|
542
|
+
return dedent`
|
|
543
|
+
import fs from 'node:fs';
|
|
544
|
+
import path from 'node:path';
|
|
545
|
+
import { fileURLToPath } from 'node:url';
|
|
546
|
+
|
|
547
|
+
// PRIMARY source: a string slot the client build patches in place with the manifest JSON (see
|
|
548
|
+
// writeBundle). Inlined into the server bundle, it survives serverless tracing: Vercel/Netlify
|
|
549
|
+
// (@vercel/nft) bundle only imported files, not runtime fs reads, so the co-located JSON below
|
|
550
|
+
// is dropped there. The fs walk stays the fallback for adapter-node and dev-preview (the whole
|
|
551
|
+
// server dir ships). Unpatched, the token starts with '_' (char 95), the guard is false, and
|
|
552
|
+
// the walk runs. A bad patch degrades to the walk, never crashes the server at import.
|
|
553
|
+
const __OG_INLINE = '__OGYGIA_ISLAND_DEPS_INLINE__';
|
|
554
|
+
let cache = null;
|
|
555
|
+
try { if (__OG_INLINE.charCodeAt(0) === 123) cache = JSON.parse(__OG_INLINE); } catch {}
|
|
556
|
+
|
|
557
|
+
function candidates() {
|
|
558
|
+
const out = [];
|
|
559
|
+
try {
|
|
560
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
561
|
+
for (let i = 0; i < 8; i++) {
|
|
562
|
+
out.push(path.join(dir, 'og-region-deps.json'));
|
|
563
|
+
const parent = path.dirname(dir);
|
|
564
|
+
if (parent === dir) break;
|
|
565
|
+
dir = parent;
|
|
566
|
+
}
|
|
567
|
+
} catch {}
|
|
568
|
+
if (typeof process !== 'undefined' && process.cwd) {
|
|
569
|
+
const cwd = process.cwd();
|
|
570
|
+
out.push(path.join(cwd, ${out_dir}, 'og-region-deps.json'));
|
|
571
|
+
out.push(path.join(cwd, ${out_dir}, 'output', 'server', 'og-region-deps.json'));
|
|
572
|
+
}
|
|
573
|
+
return out;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function load() {
|
|
577
|
+
if (cache) return cache;
|
|
578
|
+
for (const p of candidates()) {
|
|
579
|
+
try { cache = JSON.parse(fs.readFileSync(p, 'utf8')); return cache; } catch {}
|
|
580
|
+
}
|
|
581
|
+
cache = {};
|
|
582
|
+
return cache;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Handoff shape: { js: { entryUrl: [...] }, css: { entryUrl: [...] } }. A stale flat
|
|
586
|
+
// map (pre-css build) degrades gracefully: js falls back to the root, css to [].
|
|
587
|
+
function pick(kind, entry) {
|
|
588
|
+
const all = load();
|
|
589
|
+
const map = all && typeof all[kind] === 'object' ? all[kind] : kind === 'js' ? all : null;
|
|
590
|
+
const list = map ? map[entry] : null;
|
|
591
|
+
return Array.isArray(list) ? list : [];
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/** One entry of a handoff map (\`field\`), or null: no map, no key, or the wrong shape. */
|
|
595
|
+
function lookup(field, key, ok) {
|
|
596
|
+
const all = load();
|
|
597
|
+
const map = all && typeof all[field] === 'object' && all[field] ? all[field] : null;
|
|
598
|
+
if (!map || !key) return null;
|
|
599
|
+
const v = map[key];
|
|
600
|
+
return ok(v) ? v : null;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** A chunk href's key: root-absolute, as the build wrote it. */
|
|
604
|
+
function chunk_key(href) {
|
|
605
|
+
return href.startsWith('/') ? href : '/' + href.replace(/^\\.\\//, '');
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export function islandDeps(entry) {
|
|
609
|
+
return entry ? pick('js', entry) : [];
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export function islandCss(entry) {
|
|
613
|
+
return entry ? pick('css', entry) : [];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// INLINE REGION CSS: the text of a region CSS asset the build kept under Kit's
|
|
617
|
+
// inlineStyleThreshold (collect_inline_css), keyed by the same public href islandCss hands
|
|
618
|
+
// out. null = link it (over the threshold, no threshold, or a pre-inline handoff).
|
|
619
|
+
export function islandCssInline(href) {
|
|
620
|
+
return lookup('css_inline', href, (v) => typeof v === 'string');
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
export function contentCss(id) {
|
|
624
|
+
return id ? pick('content_css', id) : [];
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Does this island's client closure read $page? FAIL-OPEN: no map (a pre-page handoff), or an
|
|
628
|
+
// entry the build never saw (a foreign fragment's island mounted from another app) → true, so
|
|
629
|
+
// the seed ships and nothing that might read it finds it missing.
|
|
630
|
+
export function islandReadsPage(entry) {
|
|
631
|
+
const all = load();
|
|
632
|
+
const map = all && typeof all.page === 'object' && all.page ? all.page : null;
|
|
633
|
+
if (!map || !entry) return true;
|
|
634
|
+
const v = map[entry];
|
|
635
|
+
return v === undefined ? true : !!v;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// SEED SHAPING: which top-level page.data keys this island's closure reads. FAIL-OPEN as null
|
|
639
|
+
// ("ship all"): no map (a pre-shaping handoff), an entry the build never saw, or a closure
|
|
640
|
+
// whose reads could not be pinned to literal keys.
|
|
641
|
+
export function islandPageKeys(entry) {
|
|
642
|
+
return lookup('page_keys', entry, Array.isArray);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Why this island ships all of page.data: the modules in its closure whose reads the build
|
|
646
|
+
// could not pin (file, line, why). Profiler builds only; null = none recorded.
|
|
647
|
+
export function islandPageWhy(entry) {
|
|
648
|
+
return lookup('page_why', entry, Array.isArray);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Which remotes (Kit id-hashes) this island's client closure can call: REMOTE SEED ONLY WHEN
|
|
652
|
+
// REACHABLE. FAIL-OPEN as null ("may call anything"): no map (a pre-remotes handoff), or an
|
|
653
|
+
// entry the build never saw (a foreign fragment's island) → every SSR-resolved remote seeds.
|
|
654
|
+
export function islandRemotes(entry) {
|
|
655
|
+
return lookup('remotes', entry, Array.isArray);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// WAKE ADVISOR facts per entry (handlers / $state / bind: counts over the island's own
|
|
659
|
+
// components), for the profiler's Islands table. null = the handoff has none.
|
|
660
|
+
export function islandInteractivity(entry) {
|
|
661
|
+
return lookup('interactivity', entry, (v) => !!v && typeof v === 'object');
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// each island's lines that draw differently in the browser (the profiler's mismatch findings)
|
|
665
|
+
export function islandHazards(entry) {
|
|
666
|
+
return lookup('hazards', entry, Array.isArray);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// WHAT IS INSIDE a chunk (readable source list): the profiler names hashed chunks with it
|
|
670
|
+
export function chunkContents(href) {
|
|
671
|
+
const all = load();
|
|
672
|
+
const map = all && typeof all.contents === 'object' && all.contents ? all.contents : null;
|
|
673
|
+
if (!map || !href) return null;
|
|
674
|
+
const v = map[chunk_key(href)] ?? map[href];
|
|
675
|
+
return Array.isArray(v) ? v : null;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// …and its heaviest modules with their rendered bytes (the report's heavy-module finding)
|
|
679
|
+
export function chunkHeavy(href) {
|
|
680
|
+
const all = load();
|
|
681
|
+
const map = all && typeof all.heavy === 'object' && all.heavy ? all.heavy : null;
|
|
682
|
+
if (!map || !href) return null;
|
|
683
|
+
const v = map[chunk_key(href)] ?? map[href];
|
|
684
|
+
return v && typeof v === 'object' && Array.isArray(v.top) ? v : null;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// …and the re-export barrels it still holds (the report's island-barrel note)
|
|
688
|
+
export function chunkBarrels(href) {
|
|
689
|
+
const all = load();
|
|
690
|
+
const map = all && typeof all.barrels === 'object' && all.barrels ? all.barrels : null;
|
|
691
|
+
if (!map || !href) return null;
|
|
692
|
+
const v = map[chunk_key(href)] ?? map[href];
|
|
693
|
+
return Array.isArray(v) ? v : null;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// the modules the build shipped twice, each copy's chunk (the report's duplicate-module finding)
|
|
697
|
+
export function chunkDuplicates() {
|
|
698
|
+
const all = load();
|
|
699
|
+
const v = all && Array.isArray(all.dupes) ? all.dupes : null;
|
|
700
|
+
return v && v.length ? v : null;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// og.$ factories for the page-inline registration script (CSP-clean prod path): written by
|
|
704
|
+
// the CLIENT build's writeBundle, read here at SSR render time, the same ordering-safe channel
|
|
705
|
+
// islandCss uses.
|
|
706
|
+
export function fnManifest() {
|
|
707
|
+
const m = load().fn_manifest;
|
|
708
|
+
return m && typeof m === 'object' && Object.keys(m).length ? m : null;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// IDENTITY → LOCATION: the content-hashed file an entry (an island's stable URL, the runtime's)
|
|
712
|
+
// is served from. null = no location known (a handoff from before hashing, a foreign island):
|
|
713
|
+
// the identity itself is written, and the browser finds it in the build's location maps.
|
|
714
|
+
export function entryLocation(identity) {
|
|
715
|
+
const m = load().entries;
|
|
716
|
+
const v = m && typeof m === 'object' && identity ? m[identity] : null;
|
|
717
|
+
return typeof v === 'string' ? v : null;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// The build's content-hashed location map (link/location-map.ts), the file the browser reads:
|
|
721
|
+
// its served path, base-less (asset() adds it), or null (no island, a handoff from before it).
|
|
722
|
+
export function locationMap() {
|
|
723
|
+
const v = load().locations;
|
|
724
|
+
return typeof v === 'string' ? v : null;
|
|
725
|
+
}
|
|
726
|
+
`;
|
|
524
727
|
}
|
|
525
728
|
export { chunk_module_bytes, collectIslandDepModulepreloads, collect_inline_css, duplicate_modules, interactivity_facts, islandDepsHandoffPath, island_deps_module, kit_remote_hash, module_key, module_origin, remote_hash_of, summarize_chunk_contents };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The fixed-name map's file, in the app dir (beside Kit's `version.json`). */
|
|
2
|
+
export declare const CURRENT_LOCATIONS_FILE = "ogygia.json";
|
|
3
|
+
/**
|
|
4
|
+
* Both maps, as output files: the content-hashed one first. `pairs`: identity file → hashed file,
|
|
5
|
+
* output-relative. Empty when there is nothing to map.
|
|
6
|
+
*/
|
|
7
|
+
export declare function location_map_files(app_dir: string, pairs: Iterable<readonly [string, string]>, hash: (source: string) => string): {
|
|
8
|
+
fileName: string;
|
|
9
|
+
source: string;
|
|
10
|
+
}[];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { path } from "../host.js";
|
|
2
|
+
// src/compiler/link/location-map.ts
|
|
3
|
+
/**
|
|
4
|
+
* THE LOCATION MAPS. Island entries are content-hashed files under `immutable/`; an island's IDENTITY
|
|
5
|
+
* (`<appDir>/ogygia/og-region.<iid>.js`, the name every key uses) is a name, not a file. The build
|
|
6
|
+
* writes its identity → location pairs twice, as the same map:
|
|
7
|
+
*
|
|
8
|
+
* - `<appDir>/immutable/og-locations.<hash>.json`, named by its content. The runtime's script names
|
|
9
|
+
* it (`data-og-locations`), and a load that arrives with an identity alone (a region the browser
|
|
10
|
+
* rendered, a held region from the wire, a portable snippet) reads it: this build's locations, for
|
|
11
|
+
* this build's page.
|
|
12
|
+
* - `<appDir>/ogygia.json`, the ONE file at a fixed name: the current build's, beside Kit's own
|
|
13
|
+
* `<appDir>/version.json` and found from an identity alone (its app dir is the identity's, so any
|
|
14
|
+
* app's is found). A page that outlived its build reads it when its own files are gone, and wakes
|
|
15
|
+
* on the current code. Outside `immutable/`: a host revalidates it, as it does Kit's.
|
|
16
|
+
*
|
|
17
|
+
* Every other file the build writes is named by its content and lives in `immutable/`, so a host's
|
|
18
|
+
* year-long rule fits all of them; no directory of ogygia's own is written (`ogygia/` in an identity
|
|
19
|
+
* is a name). Both sides of each pair are written relative to the file holding them: the browser
|
|
20
|
+
* resolves them against wherever the build is served (a base, an assets CDN), and an identity that
|
|
21
|
+
* resolves elsewhere (another app's) never matches.
|
|
22
|
+
*/
|
|
23
|
+
/** The fixed-name map's file, in the app dir (beside Kit's `version.json`). */
|
|
24
|
+
const CURRENT_LOCATIONS_FILE = "ogygia.json";
|
|
25
|
+
/** A path from `from_file`'s directory to `to_file` (both output-relative), always dot-led. */
|
|
26
|
+
function relative_specifier(from_file, to_file) {
|
|
27
|
+
const rel = path.posix.relative(path.posix.dirname(from_file), to_file);
|
|
28
|
+
return rel.startsWith(".") ? rel : "./" + rel;
|
|
29
|
+
}
|
|
30
|
+
function map_source(file, pairs) {
|
|
31
|
+
const map = {};
|
|
32
|
+
for (const [identity, location] of pairs) map[relative_specifier(file, identity)] = relative_specifier(file, location);
|
|
33
|
+
return JSON.stringify(map);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Both maps, as output files: the content-hashed one first. `pairs`: identity file → hashed file,
|
|
37
|
+
* output-relative. Empty when there is nothing to map.
|
|
38
|
+
*/
|
|
39
|
+
function location_map_files(app_dir, pairs, hash) {
|
|
40
|
+
const list = [...pairs];
|
|
41
|
+
if (!list.length) return [];
|
|
42
|
+
const hashed_dir = `${app_dir}/immutable`;
|
|
43
|
+
const hashed_source = map_source(hashed_dir + "/og-locations.json", list);
|
|
44
|
+
const current = `${app_dir}/${CURRENT_LOCATIONS_FILE}`;
|
|
45
|
+
return [{
|
|
46
|
+
fileName: `${hashed_dir}/og-locations.${hash(hashed_source)}.json`,
|
|
47
|
+
source: hashed_source
|
|
48
|
+
}, {
|
|
49
|
+
fileName: current,
|
|
50
|
+
source: map_source(current, list)
|
|
51
|
+
}];
|
|
52
|
+
}
|
|
53
|
+
export { CURRENT_LOCATIONS_FILE, location_map_files };
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { dedent } from "../dedent.js";
|
|
1
2
|
// src/compiler/link/manifest.ts
|
|
2
3
|
/**
|
|
3
4
|
* `virtual:ogygia/manifest` emitter — a legacy stub. Hydrate modules are loaded via
|
|
4
5
|
* `<ogygia-region entry>` URLs, not this map; only the `dev` flag is still read.
|
|
5
6
|
*/
|
|
6
7
|
function manifest_module(is_dev) {
|
|
7
|
-
return `
|
|
8
|
+
return dedent`
|
|
9
|
+
export const dev = ${is_dev ? "true" : "false"};
|
|
10
|
+
export const regions = {};
|
|
11
|
+
`;
|
|
8
12
|
}
|
|
9
13
|
export { manifest_module };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { V_SERVER_ISLANDS } from "../ids.js";
|
|
2
|
+
import { dedent } from "../dedent.js";
|
|
2
3
|
// src/compiler/link/server-manifest.ts
|
|
3
4
|
/** The SERVER-island ids the manifest carries right now (deferred / server-picked regions). */
|
|
4
5
|
function server_island_ids(program) {
|
|
@@ -7,7 +8,14 @@ function server_island_ids(program) {
|
|
|
7
8
|
return ids;
|
|
8
9
|
}
|
|
9
10
|
function server_manifest_module(ssr, program, is_dev, devUrlFor, publicUrlFor) {
|
|
10
|
-
if (!ssr) return `
|
|
11
|
+
if (!ssr) return dedent`
|
|
12
|
+
export const load_islands = () => Promise.resolve({ islands: {} });
|
|
13
|
+
export const island_url = {};
|
|
14
|
+
export const island_name = {};
|
|
15
|
+
export const island_render = {};
|
|
16
|
+
export const render_island = {};
|
|
17
|
+
export const island_reads_page_data = null;
|
|
18
|
+
`;
|
|
11
19
|
const urls = [];
|
|
12
20
|
const names = [];
|
|
13
21
|
const renders = [];
|
|
@@ -17,25 +25,53 @@ function server_manifest_module(ssr, program, is_dev, devUrlFor, publicUrlFor) {
|
|
|
17
25
|
const reg = program.registry.get(virtualPath);
|
|
18
26
|
if (!reg?.server) continue;
|
|
19
27
|
if (reg.renderId) {
|
|
20
|
-
renders.push(
|
|
28
|
+
renders.push(`${JSON.stringify(iid)}: ${JSON.stringify(reg.renderId)}`);
|
|
21
29
|
const seen = by_render.get(reg.renderId);
|
|
22
30
|
if (seen === void 0 || iid < seen) by_render.set(reg.renderId, iid);
|
|
23
31
|
}
|
|
24
32
|
const file = reg.componentPath ?? "";
|
|
25
33
|
const base = file.slice(file.lastIndexOf("/") + 1);
|
|
26
|
-
if (base.endsWith(".svelte")) names.push(
|
|
27
|
-
urls.push(
|
|
34
|
+
if (base.endsWith(".svelte")) names.push(`${JSON.stringify(iid)}: ${JSON.stringify(base.slice(0, -7))}`);
|
|
35
|
+
urls.push(`${JSON.stringify(iid)}: ${JSON.stringify(is_dev ? devUrlFor(virtualPath) : publicUrlFor(iid))}`);
|
|
28
36
|
}
|
|
29
|
-
|
|
37
|
+
const entries = [...by_render].map(([r, iid]) => `${JSON.stringify(r)}: ${JSON.stringify(iid)}`);
|
|
38
|
+
return dedent`
|
|
39
|
+
export const load_islands = () => import(${JSON.stringify(V_SERVER_ISLANDS)});
|
|
40
|
+
|
|
41
|
+
export const island_url = {
|
|
42
|
+
${urls.join(",\n ")}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const island_name = {
|
|
46
|
+
${names.join(",\n ")}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const island_render = {
|
|
50
|
+
${renders.join(",\n ")}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const render_island = {
|
|
54
|
+
${entries.join(",\n ")}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// id → does the hole's SERVER tree read \`page.data\` (the page lookup a hole needs: server/
|
|
58
|
+
// render-page.ts). Known only once every module is transformed: a build patches the
|
|
59
|
+
// placeholder in renderChunk (driver \`patch_page_data_reads\`); dev leaves \`null\` (unknown → look up).
|
|
60
|
+
export const island_reads_page_data = ${is_dev ? "null" : PAGE_DATA_READS_PLACEHOLDER};
|
|
61
|
+
`;
|
|
30
62
|
}
|
|
31
63
|
/** `virtual:ogygia/server-islands`: each SERVER island's loader, `id → () => import(entry)` (empty on
|
|
32
64
|
* the client). Imported only by the manifest's `load_islands()` — never statically. */
|
|
33
65
|
function server_islands_module(ssr, program) {
|
|
34
66
|
const entries = [];
|
|
35
67
|
if (ssr) {
|
|
36
|
-
for (const [iid, virtualPath] of program.by_id) if (program.registry.get(virtualPath)?.server) entries.push(
|
|
68
|
+
for (const [iid, virtualPath] of program.by_id) if (program.registry.get(virtualPath)?.server) entries.push(`${JSON.stringify(iid)}: () => import(${JSON.stringify(virtualPath)})`);
|
|
37
69
|
}
|
|
38
|
-
return `
|
|
70
|
+
return dedent`
|
|
71
|
+
export const islands = {
|
|
72
|
+
${entries.join(",\n ")}
|
|
73
|
+
};
|
|
74
|
+
`;
|
|
39
75
|
}
|
|
40
76
|
/** The token the build replaces with its answer (driver `patch_page_data_reads`), inside a string
|
|
41
77
|
* literal the bundler cannot fold: a plain `null` placeholder was inlined into every reader as the
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OWN_SPECIFIER_SOURCE } from "../../own-package.js";
|
|
2
|
+
import { dedent } from "../dedent.js";
|
|
2
3
|
// src/compiler/link/transport.ts
|
|
3
4
|
/**
|
|
4
5
|
* `virtual:ogygia/transport` emitter — re-exports the app's universal `transport` hook (or an empty
|
|
@@ -6,10 +7,29 @@ import { OWN_SPECIFIER_SOURCE } from "../../own-package.js";
|
|
|
6
7
|
* Pure over the resolved absolute path to the app's universal hooks module (or null).
|
|
7
8
|
*/
|
|
8
9
|
function transport_module(universal_hooks, kit_codec = null) {
|
|
9
|
-
if (!universal_hooks) return `
|
|
10
|
-
|
|
10
|
+
if (!universal_hooks) return dedent`
|
|
11
|
+
export const transport = {};
|
|
12
|
+
export const encoders = {};
|
|
13
|
+
export const decoders = {};
|
|
14
|
+
`;
|
|
15
|
+
const src = dedent`
|
|
16
|
+
import * as hooks from ${JSON.stringify(universal_hooks)};
|
|
17
|
+
|
|
18
|
+
export const transport = hooks.transport || {};
|
|
19
|
+
const codecs = Object.entries(transport);
|
|
20
|
+
export const encoders = Object.fromEntries(codecs.map(([k, v]) => [k, v.encode]));
|
|
21
|
+
export const decoders = Object.fromEntries(codecs.map(([k, v]) => [k, v.decode]));
|
|
22
|
+
`;
|
|
11
23
|
if (!kit_codec) return src;
|
|
12
|
-
return `
|
|
24
|
+
return dedent`
|
|
25
|
+
import { init_transport } from ${JSON.stringify(kit_codec)};
|
|
26
|
+
${src}
|
|
27
|
+
|
|
28
|
+
// Kit 3 (client leg): Kit's own codec module is a reader too — its remote argument encoding
|
|
29
|
+
// reads it, and only Kit's client boot fills it otherwise (never on a csr=false page). Kit's
|
|
30
|
+
// boot later fills it again with the same transport.
|
|
31
|
+
init_transport(transport);
|
|
32
|
+
`;
|
|
13
33
|
}
|
|
14
34
|
/**
|
|
15
35
|
* `virtual:ogygia/kit-transport` emitter — the value behind the barrel's `ogygia.transport` (the Kit
|
|
@@ -22,8 +42,10 @@ function transport_module(universal_hooks, kit_codec = null) {
|
|
|
22
42
|
* `transport_spec` is the resolvable path to ogygia's own `transport.js` (dist).
|
|
23
43
|
*/
|
|
24
44
|
function kit_transport_module(app_crosses_wire, transport_spec) {
|
|
25
|
-
if (!app_crosses_wire) return `export const transport = {}
|
|
26
|
-
return `
|
|
45
|
+
if (!app_crosses_wire) return `export const transport = {};`;
|
|
46
|
+
return dedent`
|
|
47
|
+
export { ogygiaTransport as transport } from ${JSON.stringify(transport_spec)};
|
|
48
|
+
`;
|
|
27
49
|
}
|
|
28
50
|
const OGYGIA_WIRE_NAMED = new RegExp(`import\\s+(?:type\\s+)?\\{([\\s\\S]*?)\\}\\s*from\\s*['"]${OWN_SPECIFIER_SOURCE}['"]`, "g");
|
|
29
51
|
const WIRE_API = /\b(?:region|Region|site)\b/;
|
|
@@ -58,10 +80,20 @@ function transportables_module(modules, ssr, rel, crosses_wire = false) {
|
|
|
58
80
|
if (ssr) {
|
|
59
81
|
const imports = [];
|
|
60
82
|
for (const abs of modules) imports.push(`import ${JSON.stringify(abs)};`);
|
|
61
|
-
return
|
|
83
|
+
return dedent`
|
|
84
|
+
${imports.join("\n")}
|
|
85
|
+
|
|
86
|
+
export const wire_loaders = {};
|
|
87
|
+
export const wire_all = false;
|
|
88
|
+
`;
|
|
62
89
|
}
|
|
63
90
|
const entries = [];
|
|
64
91
|
for (const abs of modules) entries.push(`${JSON.stringify(rel(abs))}: () => import(${JSON.stringify(abs)})`);
|
|
65
|
-
return `
|
|
92
|
+
return dedent`
|
|
93
|
+
export const wire_loaders = {
|
|
94
|
+
${entries.join(",\n ")}
|
|
95
|
+
};
|
|
96
|
+
export const wire_all = ${crosses_wire && entries.length > 0};
|
|
97
|
+
`;
|
|
66
98
|
}
|
|
67
99
|
export { kit_transport_module, source_crosses_wire, transport_module, transportables_module };
|
|
@@ -6,12 +6,12 @@ import { og_member } from "./wire.js";
|
|
|
6
6
|
* `import.meta.og.source(fn)` — declared data sources, as a compile construct (the postrender
|
|
7
7
|
* reverse index; internal/notes/freeze.md §L2):
|
|
8
8
|
*
|
|
9
|
-
* export const
|
|
9
|
+
* export const loadCmsContent = import.meta.og.source(async (event, category) => { … });
|
|
10
10
|
*
|
|
11
11
|
* The macro stamps the id from THIS definition's `file#export` (root-relative; the ogygia.files
|
|
12
12
|
* identity rail applies to package paths upstream) and rewrites to
|
|
13
13
|
* `__og_source('<id>', fn, opts?)` with the runtime-wrapper import injected. Loads stay
|
|
14
|
-
* untouched plain TS forever; `postrender.invalidate(
|
|
14
|
+
* untouched plain TS forever; `postrender.invalidate(loadCmsContent, [args])` reads the stamp
|
|
15
15
|
* off the function — no strings anywhere.
|
|
16
16
|
*
|
|
17
17
|
* STRICT BY CONSTRUCTION — legal in exactly ONE position:
|
|
@@ -110,12 +110,21 @@ export declare function import_keys_hint(import_keys: ImportKeys): RegExp;
|
|
|
110
110
|
* Paths are always posix so SSR/client builds agree across OS path separators. */
|
|
111
111
|
export declare function islandId(relHostPath: string, index: string | number, salt?: string): string;
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
113
|
+
* The directory, under the app dir, of the STABLE names (an island's identity, the runtime's): not
|
|
114
|
+
* `immutable/`. A host serves `immutable/` for a year by name (every adapter, every CDN rule written
|
|
115
|
+
* for it), and a stable name keeps its name while its content follows each build: a year-old copy
|
|
116
|
+
* would load another build's code. Here a host's ordinary rule revalidates them. The content-hashed
|
|
117
|
+
* files they point at stay in `immutable/`.
|
|
118
|
+
*/
|
|
119
|
+
export declare const STABLE_NAME_DIR = "ogygia";
|
|
120
|
+
/**
|
|
121
|
+
* Deterministic client path of a hydrate island's stable name (mirrors the runtime's). SSR bakes it
|
|
122
|
+
* into `<ogygia-region entry>` as the island's identity — a name, not a file: the content-hashed
|
|
123
|
+
* entry is found through the handoff (the server) and the location maps (the browser,
|
|
124
|
+
* link/location-map.ts), since Kit builds server before client.
|
|
116
125
|
*/
|
|
117
126
|
export declare function islandChunkFileName(iid: string, appDir?: string): string;
|
|
118
|
-
/** App-internal URL for {@link islandChunkFileName}: `/<appDir>/
|
|
127
|
+
/** App-internal URL for {@link islandChunkFileName}: `/<appDir>/ogygia/…` (leading slash, appDir,
|
|
119
128
|
* NO base). It is what SSR bakes onto `<ogygia-region entry>`; `Region.svelte` runs it through Kit's
|
|
120
129
|
* `asset()` at render time, which is the sole authority on `base` / the assets CDN / relative paths —
|
|
121
130
|
* so a base is never baked here (that would double-apply it). NOT the on-disk output filename. */
|
|
@@ -200,14 +200,23 @@ function islandId(relHostPath, index, salt = "") {
|
|
|
200
200
|
return createHash("md5").update(msg).digest("hex").slice(0, 12);
|
|
201
201
|
}
|
|
202
202
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
203
|
+
* The directory, under the app dir, of the STABLE names (an island's identity, the runtime's): not
|
|
204
|
+
* `immutable/`. A host serves `immutable/` for a year by name (every adapter, every CDN rule written
|
|
205
|
+
* for it), and a stable name keeps its name while its content follows each build: a year-old copy
|
|
206
|
+
* would load another build's code. Here a host's ordinary rule revalidates them. The content-hashed
|
|
207
|
+
* files they point at stay in `immutable/`.
|
|
208
|
+
*/
|
|
209
|
+
const STABLE_NAME_DIR = "ogygia";
|
|
210
|
+
/**
|
|
211
|
+
* Deterministic client path of a hydrate island's stable name (mirrors the runtime's). SSR bakes it
|
|
212
|
+
* into `<ogygia-region entry>` as the island's identity — a name, not a file: the content-hashed
|
|
213
|
+
* entry is found through the handoff (the server) and the location maps (the browser,
|
|
214
|
+
* link/location-map.ts), since Kit builds server before client.
|
|
206
215
|
*/
|
|
207
216
|
function islandChunkFileName(iid, appDir = "_app") {
|
|
208
|
-
return `${appDir}/
|
|
217
|
+
return `${appDir}/${STABLE_NAME_DIR}/og-region.${iid}.js`;
|
|
209
218
|
}
|
|
210
|
-
/** App-internal URL for {@link islandChunkFileName}: `/<appDir>/
|
|
219
|
+
/** App-internal URL for {@link islandChunkFileName}: `/<appDir>/ogygia/…` (leading slash, appDir,
|
|
211
220
|
* NO base). It is what SSR bakes onto `<ogygia-region entry>`; `Region.svelte` runs it through Kit's
|
|
212
221
|
* `asset()` at render time, which is the sole authority on `base` / the assets CDN / relative paths —
|
|
213
222
|
* so a base is never baked here (that would double-apply it). NOT the on-disk output filename. */
|
|
@@ -1930,4 +1939,4 @@ var TsRegionCompilation = class {
|
|
|
1930
1939
|
};
|
|
1931
1940
|
}
|
|
1932
1941
|
};
|
|
1933
|
-
export { CLIENT_BINDING_STUB, DEFAULT_IMPORT_KEYS, ISLAND_DIR, asRegionLocals, foucCssVirtualId, import_keys_hint, is_island_path, islandChunkFileName, islandId, islandPublicUrl, normalize_hydrate_value, normalize_import_keys, normalize_region_value, regionBindingVirtualId, regionId, regionIdentity, strategyKey, transformHost, transformTsRegions, wrapperVirtualId };
|
|
1942
|
+
export { CLIENT_BINDING_STUB, DEFAULT_IMPORT_KEYS, ISLAND_DIR, STABLE_NAME_DIR, asRegionLocals, foucCssVirtualId, import_keys_hint, is_island_path, islandChunkFileName, islandId, islandPublicUrl, normalize_hydrate_value, normalize_import_keys, normalize_region_value, regionBindingVirtualId, regionId, regionIdentity, strategyKey, transformHost, transformTsRegions, wrapperVirtualId };
|
package/dist/hooks.d.ts
CHANGED
|
@@ -17,6 +17,22 @@ export interface OgygiaHandleOptions {
|
|
|
17
17
|
* (`/_app/…`): Kit answers those itself, before any handle runs.
|
|
18
18
|
*/
|
|
19
19
|
endpoint?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The secret that signs region capabilities (hole and live-region URLs). Takes precedence over
|
|
22
|
+
* the `OGYGIA_SECRET` environment variable, for a host whose server process never sees its
|
|
23
|
+
* runtime env. Pass a build-time private env value:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { OGYGIA_SECRET } from '$env/static/private';
|
|
27
|
+
* export const handle = ogygia.handle({ secret: OGYGIA_SECRET });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* An empty or blank value counts as not given (`OGYGIA_SECRET`, then a per-build random key, sign
|
|
31
|
+
* instead). It signs exactly as the same value in `OGYGIA_SECRET` does, so pages from either
|
|
32
|
+
* verify on the other. In production it needs ≥16 UTF-8 bytes. Every deployment that serves your
|
|
33
|
+
* pages or your islands endpoint needs the same secret.
|
|
34
|
+
*/
|
|
35
|
+
secret?: string;
|
|
20
36
|
/**
|
|
21
37
|
* Post-process the HTML ogygia sends — every document (the chunk carrying `</head>`; a streamed
|
|
22
38
|
* tail passes untouched) and every region answer (a hole, a lake remount) — e.g. a web-component
|
package/dist/hooks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { REGION_BRAND } from "./region-brand.js";
|
|
2
|
-
import { signing_key } from "./server/signing-key.js";
|
|
2
|
+
import { configure_signing_secret, signing_key } from "./server/signing-key.js";
|
|
3
3
|
import { mpaSpeculationRules } from "./compiler/link/speculation.js";
|
|
4
4
|
import { set_page_route_config } from "./plugin/routes.js";
|
|
5
5
|
import { kit_page_facts_tail, kit_page_ready, kit_render_context, set_kit_event_reader, set_kit_page_reader, set_render_page_port } from "./server/kit-context.js";
|
|
@@ -9,7 +9,7 @@ import { absolutize_hole_html } from "./server/hole-urls.js";
|
|
|
9
9
|
import { is_keep_fallback } from "./keep-fallback.js";
|
|
10
10
|
import { set_source_recorder } from "./source.js";
|
|
11
11
|
import { kit_asset } from "./kit-paths.js";
|
|
12
|
-
import { region_mac_message, sign, verify } from "./server/hmac.js";
|
|
12
|
+
import { region_mac_message, secret_has_min_entropy, sign, verify } from "./server/hmac.js";
|
|
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";
|
|
@@ -435,6 +435,8 @@ var OgygiaHandle = class {
|
|
|
435
435
|
this.#transform = options.transform;
|
|
436
436
|
configure_islands_endpoint(options.endpoint);
|
|
437
437
|
this.#endpoint = islands_endpoint();
|
|
438
|
+
if (options.secret?.trim() && !import.meta.env.DEV && !secret_has_min_entropy(options.secret)) throw new Error(`[ogygia] handle({ secret }) is too short for production (need ≥16 UTF-8 bytes).`);
|
|
439
|
+
configure_signing_secret(options.secret);
|
|
438
440
|
this.#speculation_rules = enabled ? "" : mpaSpeculationRules(this.#endpoint);
|
|
439
441
|
this.render_rate = new RateLimiter({
|
|
440
442
|
max: rateLimit.max,
|