@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
package/dist/seed-refs.js
CHANGED
|
@@ -308,100 +308,141 @@ function path_of(at) {
|
|
|
308
308
|
return path;
|
|
309
309
|
}
|
|
310
310
|
const EMPTY_NODES = [];
|
|
311
|
-
const EMPTY_INDEX = {
|
|
312
|
-
by_identity: {
|
|
313
|
-
get: () => void 0,
|
|
314
|
-
has: () => false
|
|
315
|
-
},
|
|
316
|
-
candidates: () => EMPTY_NODES,
|
|
317
|
-
hash_of: () => null,
|
|
318
|
-
size: 0,
|
|
319
|
-
touched: /* @__PURE__ */ new Set()
|
|
320
|
-
};
|
|
321
311
|
const index_cache = /* @__PURE__ */ new WeakMap();
|
|
322
312
|
/**
|
|
323
|
-
*
|
|
313
|
+
* The seed's `data` indexed for referencing: every referenceable node at or above `min_bytes`, by
|
|
324
314
|
* identity now and by structural hash on demand (first occurrence wins — the shortest path is not
|
|
325
|
-
* guaranteed, the first in key order is, which is deterministic).
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* is most of the nodes.
|
|
315
|
+
* guaranteed, the first in key order is, which is deterministic). It reads the one measuring walk
|
|
316
|
+
* (`analyze`) and prunes: a node below `min_bytes` cannot hold a child at or above it (a child is
|
|
317
|
+
* never larger than its parent), so its subtree is skipped BEFORE anything is materialised — on a
|
|
318
|
+
* CMS tree that is most of the nodes. Data that is not a plain object or array indexes nothing.
|
|
330
319
|
*/
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
320
|
+
var SeedIndex = class SeedIndex {
|
|
321
|
+
/** nodes indexed (for tests / devtools). */
|
|
322
|
+
size;
|
|
323
|
+
/**
|
|
324
|
+
* SEED SHAPING: the top-level `page.data` keys under which a props node was matched
|
|
325
|
+
* (`plan_seed_refs` records the first path segment of every reference it plans). The handle
|
|
326
|
+
* renders the tail against the FULL index, then ships every touched key — a key no island's
|
|
327
|
+
* code reads but whose node an island's props point into still ships, so a reference always
|
|
328
|
+
* has something to point at.
|
|
329
|
+
*/
|
|
330
|
+
touched = /* @__PURE__ */ new Set();
|
|
331
|
+
#memo = null;
|
|
332
|
+
#at = /* @__PURE__ */ new WeakMap();
|
|
333
|
+
#nodes = [];
|
|
334
|
+
#by_bytes = null;
|
|
335
|
+
#hash_memo = /* @__PURE__ */ new Map();
|
|
336
|
+
#by_hash = /* @__PURE__ */ new Map();
|
|
337
|
+
/** The index of `data`, cached per `data` object: one index per request, however many islands
|
|
338
|
+
* ask. */
|
|
339
|
+
static of(data, min_bytes = 96) {
|
|
340
|
+
if (!is_plain(data)) return new SeedIndex(data, min_bytes);
|
|
341
|
+
let index = index_cache.get(data);
|
|
342
|
+
if (!index) index_cache.set(data, index = new SeedIndex(data, min_bytes));
|
|
343
|
+
return index;
|
|
344
|
+
}
|
|
345
|
+
constructor(data, min_bytes) {
|
|
346
|
+
if (is_plain(data)) {
|
|
347
|
+
const memo = this.#memo = analysis(data).memo;
|
|
348
|
+
const at = this.#at;
|
|
349
|
+
const nodes = this.#nodes;
|
|
350
|
+
const seen = /* @__PURE__ */ new Set();
|
|
351
|
+
const enter = (c) => {
|
|
352
|
+
if (!is_plain(c) || seen.has(c)) return false;
|
|
353
|
+
const p = memo.get(c);
|
|
354
|
+
return p !== void 0 && bytes_of(p) >= min_bytes;
|
|
355
|
+
};
|
|
356
|
+
const visit = (v, here) => {
|
|
357
|
+
seen.add(v);
|
|
358
|
+
if (memo.get(v) & F_REF) {
|
|
359
|
+
at.set(v, here);
|
|
360
|
+
nodes.push(v);
|
|
361
|
+
}
|
|
362
|
+
if (Array.isArray(v)) for (let i = 0; i < v.length; i++) {
|
|
363
|
+
const c = v[i];
|
|
364
|
+
if (enter(c)) visit(c, {
|
|
365
|
+
parent: here,
|
|
366
|
+
key: i,
|
|
367
|
+
path: null
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
else for (const key in v) {
|
|
371
|
+
const c = v[key];
|
|
372
|
+
if (enter(c)) visit(c, {
|
|
373
|
+
parent: here,
|
|
374
|
+
key,
|
|
375
|
+
path: null
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
if (enter(data)) visit(data, {
|
|
380
|
+
parent: null,
|
|
381
|
+
key: "",
|
|
382
|
+
path: []
|
|
367
383
|
});
|
|
368
384
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
385
|
+
this.size = this.#nodes.length;
|
|
386
|
+
}
|
|
387
|
+
/** node → its path from `page.data` (identity matches: the app passed the seed's own object).
|
|
388
|
+
* The path is materialised on the first ask — an indexed node that no island references
|
|
389
|
+
* never allocates one. */
|
|
390
|
+
path(node) {
|
|
391
|
+
const here = this.#at.get(node);
|
|
392
|
+
return here === void 0 ? void 0 : path_of(here);
|
|
393
|
+
}
|
|
394
|
+
/** Whether `node` is an indexed seed node (by identity). */
|
|
395
|
+
has(node) {
|
|
396
|
+
return this.#at.has(node);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* The seed nodes with exactly this byte estimate — the only ones a props node of that size can
|
|
400
|
+
* be a clone of. Structure matching hashes just those few candidates (memoised per seed node)
|
|
401
|
+
* instead of the whole seed, so an app that passes the seed's own objects never hashes a seed
|
|
402
|
+
* node at all, and a cloning app hashes only what it has to. The buckets are built on the first
|
|
403
|
+
* ask — a page whose islands only pass seed objects by identity never builds them.
|
|
404
|
+
*/
|
|
405
|
+
candidates(bytes) {
|
|
406
|
+
if (this.#by_bytes === null) {
|
|
407
|
+
const by_bytes = this.#by_bytes = /* @__PURE__ */ new Map();
|
|
408
|
+
for (const n of this.#nodes) {
|
|
409
|
+
const b = bytes_of(this.#memo.get(n));
|
|
381
410
|
const bucket = by_bytes.get(b);
|
|
382
411
|
if (bucket) bucket.push(n);
|
|
383
412
|
else by_bytes.set(b, [n]);
|
|
384
413
|
}
|
|
385
414
|
}
|
|
386
|
-
return by_bytes;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
415
|
+
return this.#by_bytes.get(bytes) ?? EMPTY_NODES;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* The first seed node of exactly this byte estimate whose structural hash is `hash` (bucket
|
|
419
|
+
* order, as `candidates(bytes).find(...)` gives it), in one map lookup. The bucket is hashed in
|
|
420
|
+
* order only as far as an ask needs, each hash keeping its first node, so the many same-sized
|
|
421
|
+
* records of a list cost one hash each however many props nodes ask.
|
|
422
|
+
*/
|
|
423
|
+
match(bytes, hash) {
|
|
424
|
+
const bucket = this.candidates(bytes);
|
|
425
|
+
if (bucket.length === 0) return void 0;
|
|
426
|
+
let at = this.#by_hash.get(bytes);
|
|
427
|
+
if (at === void 0) this.#by_hash.set(bytes, at = {
|
|
428
|
+
hashed: 0,
|
|
429
|
+
first: /* @__PURE__ */ new Map()
|
|
430
|
+
});
|
|
431
|
+
const hit = at.first.get(hash);
|
|
432
|
+
if (hit !== void 0) return hit;
|
|
433
|
+
while (at.hashed < bucket.length) {
|
|
434
|
+
const node = bucket[at.hashed++];
|
|
435
|
+
const h = this.hash_of(node);
|
|
436
|
+
if (h === null || at.first.has(h)) continue;
|
|
437
|
+
at.first.set(h, node);
|
|
438
|
+
if (h === hash) return node;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/** Structural hash of an indexed seed node, memoised for the request. */
|
|
442
|
+
hash_of(node) {
|
|
443
|
+
return this.#memo === null ? null : hash_subtree(node, this.#hash_memo, /* @__PURE__ */ new Set())?.hash ?? null;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
405
446
|
/**
|
|
406
447
|
* Plan one island's references: walk the props once, top-down, largest matching ancestor first —
|
|
407
448
|
* identity first (a pointer lookup per node, no hashing), then structure (hash + exact comparison,
|
|
@@ -410,6 +451,7 @@ function index_seed(data, min_bytes = 96) {
|
|
|
410
451
|
* ride inside the reference), and neither is anything under a node below `min_bytes` (no child
|
|
411
452
|
* of it can be referenced). The props' own measures come from the request memo, so the plan adds
|
|
412
453
|
* no walk of its own beyond the pointer lookups.
|
|
454
|
+
* @heisenbench linear in props
|
|
413
455
|
*/
|
|
414
456
|
function plan_seed_refs(index, props, min_bytes = 96) {
|
|
415
457
|
const matched = /* @__PURE__ */ new WeakMap();
|
|
@@ -428,18 +470,15 @@ function plan_seed_refs(index, props, min_bytes = 96) {
|
|
|
428
470
|
const visit = (v) => {
|
|
429
471
|
if (!is_plain(v) || seen.has(v)) return;
|
|
430
472
|
seen.add(v);
|
|
431
|
-
const by_id = index.
|
|
473
|
+
const by_id = index.path(v);
|
|
432
474
|
if (by_id) return hit(v, by_id);
|
|
433
475
|
const p = memo.get(v) ?? measure(v, memo);
|
|
434
476
|
const bytes = bytes_of(p);
|
|
435
477
|
if (bytes < min_bytes) return;
|
|
436
|
-
if (p & F_REF) {
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const cand = h ? cands.find((c) => index.hash_of(c) === h) : void 0;
|
|
441
|
-
if (cand && deep_equal_plain(cand, v)) return hit(v, index.by_identity.get(cand));
|
|
442
|
-
}
|
|
478
|
+
if (p & F_REF && index.candidates(bytes).length) {
|
|
479
|
+
const h = hash_subtree(v, hashed, /* @__PURE__ */ new Set())?.hash ?? null;
|
|
480
|
+
const cand = h ? index.match(bytes, h) : void 0;
|
|
481
|
+
if (cand && deep_equal_plain(cand, v)) return hit(v, index.path(cand));
|
|
443
482
|
}
|
|
444
483
|
if (Array.isArray(v)) for (const item of v) visit(item);
|
|
445
484
|
else for (const key in v) visit(v[key]);
|
|
@@ -452,4 +491,4 @@ function plan_seed_refs(index, props, min_bytes = 96) {
|
|
|
452
491
|
keys
|
|
453
492
|
};
|
|
454
493
|
}
|
|
455
|
-
export { SEED_REF_KEY, analyze, deep_equal_plain,
|
|
494
|
+
export { SEED_REF_KEY, SeedIndex, analyze, deep_equal_plain, json_culprit, plan_seed_refs, resolve_seed_ref, seed_ref_reviver, set_measure_memo_reader };
|
|
@@ -43,6 +43,12 @@ export declare function modulepreload_tag(href: string): string;
|
|
|
43
43
|
* them. `data-ogygia-runtime-dep` keeps them attached to the script when the handle moves it
|
|
44
44
|
* (head-presence.ts `runtime_first`). `locations`: the build's location map, which the runtime reads
|
|
45
45
|
* for a load that arrives with an identity alone (runtime/entry-locations.ts).
|
|
46
|
+
*
|
|
47
|
+
* The runtime cannot say that it failed to load: when its file or one of its static imports fails to
|
|
48
|
+
* download, none of it runs, every island stays server markup, and nothing of ogygia's was logged
|
|
49
|
+
* (#262). The script element's own `error` event fires then (a module graph that failed to fetch),
|
|
50
|
+
* so the tag says it: the code's link, the way a production build prints every message. (A strict
|
|
51
|
+
* CSP without inline handlers blocks it, and the browser's own error stands, as before.)
|
|
46
52
|
*/
|
|
47
53
|
export declare function runtime_bootstrap_tags(src: string, deps: readonly string[], locations?: string | null): string;
|
|
48
54
|
/**
|
|
@@ -35,9 +35,15 @@ function modulepreload_tag(href) {
|
|
|
35
35
|
* them. `data-ogygia-runtime-dep` keeps them attached to the script when the handle moves it
|
|
36
36
|
* (head-presence.ts `runtime_first`). `locations`: the build's location map, which the runtime reads
|
|
37
37
|
* for a load that arrives with an identity alone (runtime/entry-locations.ts).
|
|
38
|
+
*
|
|
39
|
+
* The runtime cannot say that it failed to load: when its file or one of its static imports fails to
|
|
40
|
+
* download, none of it runs, every island stays server markup, and nothing of ogygia's was logged
|
|
41
|
+
* (#262). The script element's own `error` event fires then (a module graph that failed to fetch),
|
|
42
|
+
* so the tag says it: the code's link, the way a production build prints every message. (A strict
|
|
43
|
+
* CSP without inline handlers blocks it, and the browser's own error stands, as before.)
|
|
38
44
|
*/
|
|
39
45
|
function runtime_bootstrap_tags(src, deps, locations = null) {
|
|
40
|
-
let html = "<script type=\"module\" data-ogygia-runtime" + (locations ? " data-og-locations=\"" + locations + "\"" : "") + " src=\"" + src + "\"><\/script>";
|
|
46
|
+
let html = "<script type=\"module\" data-ogygia-runtime" + (locations ? " data-og-locations=\"" + locations + "\"" : "") + " src=\"" + src + "\" onerror=\"console.error('[ogygia] https://ogygiajs.dev/e/runtime_load_failed')\"><\/script>";
|
|
41
47
|
for (const dep of deps) html += "<link rel=\"modulepreload\" href=\"" + dep + "\" data-ogygia-runtime-dep>";
|
|
42
48
|
return html;
|
|
43
49
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** The text between a tag's `<script…>` and `<\/script>`: what a CSP hash covers. */
|
|
2
|
+
export declare function script_text(tag: string): string;
|
|
3
|
+
/** The script's hash source, as a policy lists it: of the exact text the head carries, once. */
|
|
4
|
+
export declare const EARLY_HOLES_SOURCE: string;
|
|
5
|
+
/**
|
|
6
|
+
* A `Content-Security-Policy` (or `-Report-Only`) header value with the early start's hash where its
|
|
7
|
+
* script would be blocked. A header can carry several policies, comma-separated: each is checked.
|
|
8
|
+
*/
|
|
9
|
+
export declare function with_early_holes_csp(header: string): string;
|
|
10
|
+
/** A head's `<meta http-equiv="content-security-policy">` policies, each with the hash where its
|
|
11
|
+
* script would be blocked. A head with none comes back as itself. */
|
|
12
|
+
export declare function with_early_holes_meta_csp(head: string): string;
|
|
13
|
+
/** A response's CSP headers (enforced and report-only), each with the hash where it is needed. */
|
|
14
|
+
export declare function add_early_holes_csp(headers: Headers): void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { EARLY_HOLES_TAG } from "../early-holes-head.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
// src/server/early-holes-csp.ts
|
|
4
|
+
/**
|
|
5
|
+
* THE EARLY START'S SCRIPT UNDER A CONTENT SECURITY POLICY (../early-holes.ts). Its bytes never
|
|
6
|
+
* change, so its `'sha256-…'` source is computed once, here. A policy that would block an inline
|
|
7
|
+
* script gets that one source added to the directive that rules script elements:
|
|
8
|
+
* `script-src-elem`, else `script-src`, else `default-src`. Nothing else in the policy changes:
|
|
9
|
+
*
|
|
10
|
+
* - no such directive: inline scripts are not restricted, nothing to add;
|
|
11
|
+
* - `'none'`: no script runs, ours neither;
|
|
12
|
+
* - `'unsafe-inline'` with no nonce or hash: inline scripts run already, and adding a hash would
|
|
13
|
+
* switch `'unsafe-inline'` off for every other one;
|
|
14
|
+
* - a nonce or `'strict-dynamic'` policy: a hash is a valid source beside both.
|
|
15
|
+
*
|
|
16
|
+
* Server-only (node:crypto).
|
|
17
|
+
*/
|
|
18
|
+
/** The text between a tag's `<script…>` and `<\/script>`: what a CSP hash covers. */
|
|
19
|
+
function script_text(tag) {
|
|
20
|
+
return tag.slice(tag.indexOf(">") + 1, tag.lastIndexOf("<\/script>"));
|
|
21
|
+
}
|
|
22
|
+
/** The script's hash source, as a policy lists it: of the exact text the head carries, once. */
|
|
23
|
+
const EARLY_HOLES_SOURCE = `'sha256-${createHash("sha256").update(script_text(EARLY_HOLES_TAG), "utf8").digest("base64")}'`;
|
|
24
|
+
/** The directives that rule an inline `<script>`, the most specific first. */
|
|
25
|
+
const SCRIPT_DIRECTIVES = [
|
|
26
|
+
"script-src-elem",
|
|
27
|
+
"script-src",
|
|
28
|
+
"default-src"
|
|
29
|
+
];
|
|
30
|
+
const NONCE_OR_HASH_RE = /^'(?:nonce|sha256|sha384|sha512)-/i;
|
|
31
|
+
const WHITESPACE_RE = /\s+/;
|
|
32
|
+
/** One serialized policy (`directive sources; …`) with the hash where an inline script needs it. */
|
|
33
|
+
function with_hash(policy) {
|
|
34
|
+
const parts = policy.split(";");
|
|
35
|
+
const names = parts.map((p) => p.trim().split(WHITESPACE_RE)[0]?.toLowerCase() ?? "");
|
|
36
|
+
for (const directive of SCRIPT_DIRECTIVES) {
|
|
37
|
+
const at = names.indexOf(directive);
|
|
38
|
+
if (at === -1) continue;
|
|
39
|
+
const sources = parts[at].trim().split(WHITESPACE_RE).slice(1);
|
|
40
|
+
const lower = sources.map((s) => s.toLowerCase());
|
|
41
|
+
if (sources.includes(EARLY_HOLES_SOURCE)) return policy;
|
|
42
|
+
if (lower.length === 1 && lower[0] === "'none'") return policy;
|
|
43
|
+
if (lower.includes("'unsafe-inline'") && !sources.some((s) => NONCE_OR_HASH_RE.test(s))) return policy;
|
|
44
|
+
parts[at] = parts[at].replace(/\s*$/, "") + " " + EARLY_HOLES_SOURCE;
|
|
45
|
+
return parts.join(";");
|
|
46
|
+
}
|
|
47
|
+
return policy;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A `Content-Security-Policy` (or `-Report-Only`) header value with the early start's hash where its
|
|
51
|
+
* script would be blocked. A header can carry several policies, comma-separated: each is checked.
|
|
52
|
+
*/
|
|
53
|
+
function with_early_holes_csp(header) {
|
|
54
|
+
if (!header) return header;
|
|
55
|
+
return header.split(",").map(with_hash).join(",");
|
|
56
|
+
}
|
|
57
|
+
const META_CSP_G = /(<meta\b[^>]*\bhttp-equiv\s*=\s*["']?content-security-policy["']?[^>]*\bcontent\s*=\s*")([^"]*)(")/gi;
|
|
58
|
+
/** A head's `<meta http-equiv="content-security-policy">` policies, each with the hash where its
|
|
59
|
+
* script would be blocked. A head with none comes back as itself. */
|
|
60
|
+
function with_early_holes_meta_csp(head) {
|
|
61
|
+
return head.replace(META_CSP_G, (_m, open, policy, close) => open + with_early_holes_csp(policy) + close);
|
|
62
|
+
}
|
|
63
|
+
const CSP_HEADERS = ["content-security-policy", "content-security-policy-report-only"];
|
|
64
|
+
/** A response's CSP headers (enforced and report-only), each with the hash where it is needed. */
|
|
65
|
+
function add_early_holes_csp(headers) {
|
|
66
|
+
for (const name of CSP_HEADERS) {
|
|
67
|
+
const value = headers.get(name);
|
|
68
|
+
if (!value) continue;
|
|
69
|
+
const next = with_early_holes_csp(value);
|
|
70
|
+
if (next !== value) headers.set(name, next);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export { EARLY_HOLES_SOURCE, add_early_holes_csp, script_text, with_early_holes_csp, with_early_holes_meta_csp };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Scope } from "@ogygia/machine";
|
|
1
2
|
/** Marks ogygia's own lookup: the handle answers it without setting up a page render. */
|
|
2
3
|
export declare const PAGE_FACTS_HEADER = "x-ogygia-page-facts";
|
|
3
4
|
/** The route id and params Kit matched for the looked-up page (the data alone carries neither). */
|
|
@@ -18,8 +19,10 @@ export declare function data_url_of(page: URL): string;
|
|
|
18
19
|
/**
|
|
19
20
|
* The page's facts, or a reason they are not available (a redirect, an error, a load that threw,
|
|
20
21
|
* not a Kit data answer). `decoders`: the app's `transport` decoders, as Kit's client uses them.
|
|
22
|
+
* `owner`: the request the look-up is for. When it ends, the answer's reader stops (a streamed
|
|
23
|
+
* promise still pending then stays pending).
|
|
21
24
|
*/
|
|
22
|
-
export declare function look_up_page(page: URL, fetch: Fetch, decoders: Decoders): Promise<PageFacts | {
|
|
25
|
+
export declare function look_up_page(page: URL, fetch: Fetch, decoders: Decoders, owner?: Scope): Promise<PageFacts | {
|
|
23
26
|
miss: string;
|
|
24
27
|
}>;
|
|
25
28
|
export {};
|
|
@@ -31,8 +31,10 @@ function data_url_of(page) {
|
|
|
31
31
|
/**
|
|
32
32
|
* The page's facts, or a reason they are not available (a redirect, an error, a load that threw,
|
|
33
33
|
* not a Kit data answer). `decoders`: the app's `transport` decoders, as Kit's client uses them.
|
|
34
|
+
* `owner`: the request the look-up is for. When it ends, the answer's reader stops (a streamed
|
|
35
|
+
* promise still pending then stays pending).
|
|
34
36
|
*/
|
|
35
|
-
async function look_up_page(page, fetch, decoders) {
|
|
37
|
+
async function look_up_page(page, fetch, decoders, owner) {
|
|
36
38
|
let res;
|
|
37
39
|
try {
|
|
38
40
|
res = await fetch(data_url_of(page), { headers: { [PAGE_FACTS_HEADER]: "1" } });
|
|
@@ -44,6 +46,7 @@ async function look_up_page(page, fetch, decoders) {
|
|
|
44
46
|
return { miss: `the data request answered ${res.status}` };
|
|
45
47
|
}
|
|
46
48
|
const reader = res.body.getReader();
|
|
49
|
+
owner?.defer(() => void reader.cancel().catch(() => {}));
|
|
47
50
|
const text = new TextDecoder();
|
|
48
51
|
let buf = "";
|
|
49
52
|
let nl = -1;
|
|
@@ -20,8 +20,8 @@ export interface SeedText {
|
|
|
20
20
|
export declare class PageSeed {
|
|
21
21
|
/**
|
|
22
22
|
* Public page slice for islands. url/params/route/status are always plain and MUST survive;
|
|
23
|
-
* data/form/error may carry a non-serializable leaf (function/store/class) — that
|
|
24
|
-
*
|
|
23
|
+
* data/form/error may carry a non-serializable leaf (function/store/class) — that leaf is left
|
|
24
|
+
* out where it sits (`keep_serializable`), never its field or the whole seed. Promises should already be resolved by the handle
|
|
25
25
|
* (`resolve_promises`) before this. See INVARIANTS.md · PAGE-SEED.
|
|
26
26
|
*
|
|
27
27
|
* `json`: the caller proved the whole slice JSON-exact (seed-refs.ts `analyze`) — then the seed
|
package/dist/server/page-seed.js
CHANGED
|
@@ -2,11 +2,96 @@ import { escape_script_text } from "../escape.js";
|
|
|
2
2
|
import { stringify } from "devalue";
|
|
3
3
|
// src/server/page-seed.ts
|
|
4
4
|
/** Serialize a Kit `page` snapshot for a single document-level ogygia side-channel. */
|
|
5
|
+
const DROP = Symbol("drop");
|
|
6
|
+
/**
|
|
7
|
+
* A copy of `value` without the values `stringify_fn` cannot carry (a function, a symbol, a class
|
|
8
|
+
* no reducer encodes): an object's key is left out, an array's slot reads `undefined` (later
|
|
9
|
+
* indexes still point at what they did), a Map or Set loses that entry. Plain objects, arrays,
|
|
10
|
+
* Maps and Sets are copied (one copy per source object, so shared and cyclic references keep their
|
|
11
|
+
* shape); every other value is kept or left out whole, asked of `stringify_fn` itself so the app's
|
|
12
|
+
* reducers decide. The source is not touched. Only for a slice whose whole serialization threw.
|
|
13
|
+
*/
|
|
14
|
+
function keep_serializable(value, stringify_fn) {
|
|
15
|
+
const copies = /* @__PURE__ */ new Map();
|
|
16
|
+
const carries = (v) => {
|
|
17
|
+
try {
|
|
18
|
+
stringify_fn(v);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const put = (out, key, v) => {
|
|
25
|
+
if (key === "__proto__") Object.defineProperty(out, key, {
|
|
26
|
+
value: v,
|
|
27
|
+
enumerable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
configurable: true
|
|
30
|
+
});
|
|
31
|
+
else out[key] = v;
|
|
32
|
+
};
|
|
33
|
+
const keep = (v) => {
|
|
34
|
+
if (typeof v === "function" || typeof v === "symbol") return carries(v) ? v : DROP;
|
|
35
|
+
if (v === null || typeof v !== "object") return v;
|
|
36
|
+
if (copies.has(v)) return copies.get(v);
|
|
37
|
+
const proto = Object.getPrototypeOf(v);
|
|
38
|
+
if (Array.isArray(v) && proto === Array.prototype) {
|
|
39
|
+
const out = [];
|
|
40
|
+
out.length = v.length;
|
|
41
|
+
copies.set(v, out);
|
|
42
|
+
for (let i = 0; i < v.length; i++) {
|
|
43
|
+
if (!(i in v)) continue;
|
|
44
|
+
const c = keep(v[i]);
|
|
45
|
+
out[i] = c === DROP ? void 0 : c;
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
if (proto === Object.prototype || proto === null) {
|
|
50
|
+
const out = Object.create(proto);
|
|
51
|
+
copies.set(v, out);
|
|
52
|
+
for (const key of Object.keys(v)) {
|
|
53
|
+
let e;
|
|
54
|
+
try {
|
|
55
|
+
e = v[key];
|
|
56
|
+
} catch {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const c = keep(e);
|
|
60
|
+
if (c !== DROP) put(out, key, c);
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
if (proto === Map.prototype) {
|
|
65
|
+
const out = /* @__PURE__ */ new Map();
|
|
66
|
+
copies.set(v, out);
|
|
67
|
+
for (const [k, e] of v) {
|
|
68
|
+
const ck = keep(k);
|
|
69
|
+
const ce = keep(e);
|
|
70
|
+
if (ck !== DROP && ce !== DROP) out.set(ck, ce);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
if (proto === Set.prototype) {
|
|
75
|
+
const out = /* @__PURE__ */ new Set();
|
|
76
|
+
copies.set(v, out);
|
|
77
|
+
for (const e of v) {
|
|
78
|
+
const c = keep(e);
|
|
79
|
+
if (c !== DROP) out.add(c);
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
const kept = carries(v) ? v : DROP;
|
|
84
|
+
copies.set(v, kept);
|
|
85
|
+
return kept;
|
|
86
|
+
};
|
|
87
|
+
const out = keep(value);
|
|
88
|
+
return out === DROP ? void 0 : out;
|
|
89
|
+
}
|
|
5
90
|
var PageSeed = class {
|
|
6
91
|
/**
|
|
7
92
|
* Public page slice for islands. url/params/route/status are always plain and MUST survive;
|
|
8
|
-
* data/form/error may carry a non-serializable leaf (function/store/class) — that
|
|
9
|
-
*
|
|
93
|
+
* data/form/error may carry a non-serializable leaf (function/store/class) — that leaf is left
|
|
94
|
+
* out where it sits (`keep_serializable`), never its field or the whole seed. Promises should already be resolved by the handle
|
|
10
95
|
* (`resolve_promises`) before this. See INVARIANTS.md · PAGE-SEED.
|
|
11
96
|
*
|
|
12
97
|
* `json`: the caller proved the whole slice JSON-exact (seed-refs.ts `analyze`) — then the seed
|
|
@@ -37,16 +122,24 @@ var PageSeed = class {
|
|
|
37
122
|
try {
|
|
38
123
|
raw = stringify_fn(full);
|
|
39
124
|
} catch {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"form",
|
|
44
|
-
"error"
|
|
45
|
-
]) try {
|
|
46
|
-
stringify_fn(full[key]);
|
|
47
|
-
safe[key] = full[key];
|
|
125
|
+
let kept = full;
|
|
126
|
+
try {
|
|
127
|
+
kept = keep_serializable(full, stringify_fn);
|
|
48
128
|
} catch {}
|
|
49
|
-
|
|
129
|
+
try {
|
|
130
|
+
raw = stringify_fn(kept);
|
|
131
|
+
} catch {
|
|
132
|
+
const safe = { ...base };
|
|
133
|
+
for (const key of [
|
|
134
|
+
"data",
|
|
135
|
+
"form",
|
|
136
|
+
"error"
|
|
137
|
+
]) try {
|
|
138
|
+
stringify_fn(kept[key]);
|
|
139
|
+
safe[key] = kept[key];
|
|
140
|
+
} catch {}
|
|
141
|
+
raw = stringify_fn(safe);
|
|
142
|
+
}
|
|
50
143
|
}
|
|
51
144
|
return {
|
|
52
145
|
text: raw,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Scope } from "@ogygia/machine";
|
|
1
2
|
/** A THENABLE, not just a native Promise — Kit wraps streamed load promises, so `instanceof Promise`
|
|
2
3
|
* would miss them. Anything with a callable `.then` we can await. */
|
|
3
4
|
export declare const is_thenable: (v: unknown) => v is PromiseLike<unknown>;
|
|
@@ -52,4 +53,16 @@ export declare function resolve_script(global_name: string, id: number, settled:
|
|
|
52
53
|
}, reducers?: Record<string, (v: unknown) => unknown>): string;
|
|
53
54
|
export declare function normalize_error(err: unknown): {
|
|
54
55
|
message: string;
|
|
55
|
-
};
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Stream the captured `$page.data` promises into islands (csr=false, real browser load). The
|
|
59
|
+
* document — carrying the pending seed + resolve-global bootstrap — is already built by `resolve`;
|
|
60
|
+
* here we (1) forward it, (2) DRAIN Kit's own dead csr=false resolve tail so its
|
|
61
|
+
* `__sveltekit_<hash> is not defined` never reaches the browser, and (3) emit one
|
|
62
|
+
* `<script>__ogygia_page_resolve(id, ok, value)<\/script>` per promise AS IT SETTLES — completion
|
|
63
|
+
* order, non-blocking. Each island's `{#await page.data.x}` flips pending → resolved live.
|
|
64
|
+
*
|
|
65
|
+
* The stream is the request's (`request`, its owner): the visitor leaving, or the body cancelled,
|
|
66
|
+
* stops Kit's reader and drops what settles after.
|
|
67
|
+
*/
|
|
68
|
+
export declare function stream_page_deferred(request: Scope, response: Response, deferred: Deferred[], initial_next_id: number, reducers?: Record<string, (v: unknown) => unknown>): Response;
|