@vesk/vesk-cli 0.2.59 → 0.2.60

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.
Files changed (2) hide show
  1. package/dist/cli.js +308 -35
  2. package/package.json +5 -5
package/dist/cli.js CHANGED
@@ -889,6 +889,9 @@ function block(flags, fn, state = null, co) {
889
889
  }
890
890
  if (active_block !== null) {
891
891
  push_block(newBlock, active_block);
892
+ } else {
893
+ newBlock.p = get_orphan_root();
894
+ push_block(newBlock, newBlock.p);
892
895
  }
893
896
  if ((flags & EFFECT_BLOCK) !== 0) {
894
897
  schedule_update(newBlock);
@@ -978,6 +981,7 @@ function is_destroyed(target_block) {
978
981
  }
979
982
  function destroy_block(block2, remove_dom = true) {
980
983
  block2.f ^= DESTROYED;
984
+ unsubscribe_reaction(block2);
981
985
  let removed = false;
982
986
  const f = block2.f;
983
987
  if (remove_dom && (f & (BRANCH_BLOCK | ROOT_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0 || (f & HEAD_BLOCK) !== 0) {
@@ -1013,6 +1017,28 @@ var init_ripple_utils = __esm({
1013
1017
  });
1014
1018
 
1015
1019
  // ../runtime/src/ripple-runtime.ts
1020
+ function get_orphan_root() {
1021
+ if (orphan_root === null) {
1022
+ orphan_root = {
1023
+ co: null,
1024
+ d: null,
1025
+ first: null,
1026
+ f: ROOT_BLOCK,
1027
+ fn: null,
1028
+ last: null,
1029
+ next: null,
1030
+ p: null,
1031
+ prev: null,
1032
+ s: null,
1033
+ t: null,
1034
+ tc: null
1035
+ };
1036
+ }
1037
+ return orphan_root;
1038
+ }
1039
+ function is_orphan(block2) {
1040
+ return block2 === null || block2 === void 0 || block2 === orphan_root;
1041
+ }
1016
1042
  function increment_clock() {
1017
1043
  return ++clock;
1018
1044
  }
@@ -1138,6 +1164,7 @@ function run_block(block2) {
1138
1164
  try {
1139
1165
  active_block = block2;
1140
1166
  active_reaction = block2;
1167
+ unsubscribe_reaction(block2);
1141
1168
  active_component = block2.co;
1142
1169
  destroy_non_branch_children(block2);
1143
1170
  run_teardown(block2);
@@ -1167,10 +1194,10 @@ function run_block(block2) {
1167
1194
  }
1168
1195
  }
1169
1196
  function tracked(v, block2, get2, set2) {
1170
- return new TrackedValue(v, block2 || active_block, get2 || set2 ? { get: get2, set: set2 } : empty_get_set);
1197
+ return new TrackedValue(v, block2 || active_block || get_orphan_root(), get2 || set2 ? { get: get2, set: set2 } : empty_get_set);
1171
1198
  }
1172
1199
  function derived(fn, block2, get2, set2) {
1173
- return new DerivedValue(fn, block2 || active_block, get2 || set2 ? { get: get2, set: set2 } : empty_get_set);
1200
+ return new DerivedValue(fn, block2 || active_block || get_orphan_root(), get2 || set2 ? { get: get2, set: set2 } : empty_get_set);
1174
1201
  }
1175
1202
  function track(v, b, get2, set2) {
1176
1203
  if (is_ripple_object(v)) {
@@ -1193,6 +1220,9 @@ function track(v, b, get2, set2) {
1193
1220
  }
1194
1221
  function create_dependency(tracked2) {
1195
1222
  const reaction = active_reaction;
1223
+ if (is_orphan(tracked2.b)) {
1224
+ (tracked2.r ??= /* @__PURE__ */ new Set()).add(reaction);
1225
+ }
1196
1226
  const existing = reaction.d;
1197
1227
  if (existing !== null) {
1198
1228
  reaction.d = existing.n;
@@ -1367,6 +1397,7 @@ function queue_microtask(fn) {
1367
1397
  }
1368
1398
  function schedule_update(block2) {
1369
1399
  if (block2 === null) return;
1400
+ if (block2.p === get_orphan_root() && globalThis.__vsk_ssr) return;
1370
1401
  if (scheduler_mode === FLUSH_MICROTASK) {
1371
1402
  queue_microtask();
1372
1403
  }
@@ -1426,6 +1457,36 @@ function register_dependency(tracked2) {
1426
1457
  current2.n = dependency;
1427
1458
  }
1428
1459
  }
1460
+ function unsubscribe_reaction(reaction) {
1461
+ if (reaction === null) return;
1462
+ let dep = reaction.d;
1463
+ while (dep !== null) {
1464
+ const t = dep.t;
1465
+ if (is_orphan(t.b) && t.r !== null) {
1466
+ t.r.delete(reaction);
1467
+ }
1468
+ dep = dep.n;
1469
+ }
1470
+ }
1471
+ function schedule_ownerless_update(tracked2, visited) {
1472
+ const subscribers = tracked2.r;
1473
+ if (subscribers === null) return;
1474
+ for (const reaction of Array.from(subscribers)) {
1475
+ if (visited !== null) {
1476
+ if (visited.has(reaction)) continue;
1477
+ visited.add(reaction);
1478
+ }
1479
+ if (reaction.f & DESTROYED) {
1480
+ subscribers.delete(reaction);
1481
+ continue;
1482
+ }
1483
+ if ((reaction.f & DERIVED) !== 0) {
1484
+ schedule_ownerless_update(reaction, visited ?? /* @__PURE__ */ new Set());
1485
+ continue;
1486
+ }
1487
+ schedule_update(reaction);
1488
+ }
1489
+ }
1429
1490
  function get_derived(computed) {
1430
1491
  update_derived(computed);
1431
1492
  if (tracking) {
@@ -1493,7 +1554,11 @@ function set(tracked2, value) {
1493
1554
  }
1494
1555
  tracked2.__v = value;
1495
1556
  tracked2.c = increment_clock();
1496
- schedule_update(tracked_block);
1557
+ if (!is_orphan(tracked_block)) {
1558
+ schedule_update(tracked_block);
1559
+ } else {
1560
+ schedule_ownerless_update(tracked2, null);
1561
+ }
1497
1562
  }
1498
1563
  }
1499
1564
  function untrack2(fn) {
@@ -1590,7 +1655,7 @@ function pop_component() {
1590
1655
  }
1591
1656
  active_component = component.p;
1592
1657
  }
1593
- var FLUSH_MICROTASK, FLUSH_SYNC, active_block, active_reaction, active_scope, active_component, is_mutating_allowed, old_values, scheduler_mode, is_micro_task_queued, clock, queued_root_blocks, disable_scoped_flush, queued_microtasks, flush_count, queued_post_block_flush, active_dependency, tracking, teardown, empty_get_set, TrackedValue, DerivedValue;
1658
+ var FLUSH_MICROTASK, FLUSH_SYNC, active_block, active_reaction, active_scope, active_component, is_mutating_allowed, old_values, orphan_root, scheduler_mode, is_micro_task_queued, clock, queued_root_blocks, disable_scoped_flush, queued_microtasks, flush_count, queued_post_block_flush, active_dependency, tracking, teardown, empty_get_set, TrackedValue, DerivedValue;
1594
1659
  var init_ripple_runtime = __esm({
1595
1660
  "../runtime/src/ripple-runtime.ts"() {
1596
1661
  "use strict";
@@ -1605,6 +1670,7 @@ var init_ripple_runtime = __esm({
1605
1670
  active_component = null;
1606
1671
  is_mutating_allowed = true;
1607
1672
  old_values = /* @__PURE__ */ new Map();
1673
+ orphan_root = null;
1608
1674
  scheduler_mode = FLUSH_MICROTASK;
1609
1675
  is_micro_task_queued = false;
1610
1676
  clock = 0;
@@ -1623,6 +1689,7 @@ var init_ripple_runtime = __esm({
1623
1689
  c;
1624
1690
  d;
1625
1691
  f;
1692
+ r;
1626
1693
  __v;
1627
1694
  constructor(v, block2, a) {
1628
1695
  this.a = a;
@@ -1630,6 +1697,7 @@ var init_ripple_runtime = __esm({
1630
1697
  this.c = 0;
1631
1698
  this.d = null;
1632
1699
  this.f = TRACKED;
1700
+ this.r = null;
1633
1701
  this.__v = v;
1634
1702
  }
1635
1703
  get() {
@@ -1670,6 +1738,7 @@ var init_ripple_runtime = __esm({
1670
1738
  d;
1671
1739
  f;
1672
1740
  fn;
1741
+ r;
1673
1742
  __v;
1674
1743
  constructor(fn, block2, a) {
1675
1744
  this.a = a;
@@ -1680,6 +1749,7 @@ var init_ripple_runtime = __esm({
1680
1749
  this.d = null;
1681
1750
  this.f = DERIVED;
1682
1751
  this.fn = fn;
1752
+ this.r = null;
1683
1753
  this.__v = UNINITIALIZED;
1684
1754
  }
1685
1755
  get() {
@@ -18320,6 +18390,20 @@ function tokenizeCode(code) {
18320
18390
  }
18321
18391
  });
18322
18392
  }
18393
+ function collectStringDelimiters(code, tokens) {
18394
+ const t = tokens == null ? tokenizeCode(code) : tokens;
18395
+ if (t === null) return null;
18396
+ const starts = /* @__PURE__ */ new Set();
18397
+ let ti = 0;
18398
+ for (let i = 0; i < code.length; i++) {
18399
+ const c = code.charCodeAt(i);
18400
+ if (c !== 34 && c !== 39 && c !== 96) continue;
18401
+ while (ti < t.length && t[ti].end <= i) ti++;
18402
+ const covered = ti < t.length && t[ti].start <= i;
18403
+ if (!covered || QUOTED_LABELS.has(t[ti].label)) starts.add(i);
18404
+ }
18405
+ return starts;
18406
+ }
18323
18407
  function containsIdentifier(code, name) {
18324
18408
  const tokens = tokenizeCode(code);
18325
18409
  if (tokens !== null) {
@@ -18511,33 +18595,33 @@ function manualExtractImportNames(importText) {
18511
18595
  }
18512
18596
  return names;
18513
18597
  }
18514
- function hasTopLevelValueDeclaration(code, name) {
18515
- const tokens = tokenizeCode(code);
18516
- if (tokens === null) return false;
18598
+ function hasTopLevelValueDeclaration(code, name, tokens) {
18599
+ const t = tokens == null ? tokenizeCode(code) : tokens;
18600
+ if (t === null) return false;
18517
18601
  const declWords = ["const", "let", "var", "function", "class"];
18518
- for (let i = 1; i < tokens.length; i++) {
18519
- if (tokens[i].label !== "name" || tokens[i].value !== name) continue;
18520
- const prev = tokens[i - 1];
18602
+ for (let i = 1; i < t.length; i++) {
18603
+ if (t[i].label !== "name" || t[i].value !== name) continue;
18604
+ const prev = t[i - 1];
18521
18605
  if (!prev || !declWords.includes(prev.value)) continue;
18522
18606
  if (i >= 2) {
18523
- const pp2 = tokens[i - 2];
18607
+ const pp2 = t[i - 2];
18524
18608
  if (pp2 && pp2.label === "name" && pp2.value === "export") continue;
18525
18609
  }
18526
18610
  return true;
18527
18611
  }
18528
18612
  return false;
18529
18613
  }
18530
- function findSpecifierExports(code) {
18531
- const tokens = tokenizeCode(code);
18532
- if (tokens === null) return [];
18614
+ function findSpecifierExports(code, tokens) {
18615
+ const t = tokens == null ? tokenizeCode(code) : tokens;
18616
+ if (t === null) return [];
18533
18617
  const out = [];
18534
- for (let i = 0; i < tokens.length; i++) {
18535
- if (tokens[i].label !== "export") continue;
18536
- if (!tokens[i + 1] || tokens[i + 1].label !== "{") continue;
18618
+ for (let i = 0; i < t.length; i++) {
18619
+ if (t[i].label !== "export") continue;
18620
+ if (!t[i + 1] || t[i + 1].label !== "{") continue;
18537
18621
  let k = i + 2;
18538
18622
  let depth = 1;
18539
- for (; k < tokens.length; k++) {
18540
- const lbl = tokens[k].label;
18623
+ for (; k < t.length; k++) {
18624
+ const lbl = t[k].label;
18541
18625
  if (lbl === "string") {
18542
18626
  continue;
18543
18627
  }
@@ -18548,8 +18632,8 @@ function findSpecifierExports(code) {
18548
18632
  }
18549
18633
  }
18550
18634
  let isReexport = false;
18551
- for (let j = k + 1; j < tokens.length; j++) {
18552
- const lbl = tokens[j].label;
18635
+ for (let j = k + 1; j < t.length; j++) {
18636
+ const lbl = t[j].label;
18553
18637
  if (lbl === "string") {
18554
18638
  isReexport = true;
18555
18639
  break;
@@ -18557,10 +18641,10 @@ function findSpecifierExports(code) {
18557
18641
  if (lbl === ";" || lbl === "export" || lbl === "import" || lbl === "}") break;
18558
18642
  }
18559
18643
  if (depth !== 0 || isReexport) continue;
18560
- const pairs = readSpecifierPairs(code, tokens[i + 1].end, tokens[k].end);
18644
+ const pairs = readSpecifierPairs(code, t[i + 1].end, t[k].end);
18561
18645
  if (pairs === null) continue;
18562
- const stmtEnd = tokens[k].end;
18563
- for (const [local, exported] of pairs) out.push({ local, exported, start: tokens[i].start, end: stmtEnd });
18646
+ const stmtEnd = t[k].end;
18647
+ for (const [local, exported] of pairs) out.push({ local, exported, start: t[i].start, end: stmtEnd });
18564
18648
  i = k;
18565
18649
  }
18566
18650
  return out;
@@ -18620,12 +18704,14 @@ function stripTrackDeclMarkers(text, base, node) {
18620
18704
  }
18621
18705
  return out + text.slice(prev);
18622
18706
  }
18707
+ var QUOTED_LABELS;
18623
18708
  var init_tokens = __esm({
18624
18709
  "../compiler/src/tokens.ts"() {
18625
18710
  "use strict";
18626
18711
  init_parser();
18627
18712
  init_vesk_plugin();
18628
18713
  init_scan();
18714
+ QUOTED_LABELS = /* @__PURE__ */ new Set(["string", "template", "`", "backQuote"]);
18629
18715
  }
18630
18716
  });
18631
18717
 
@@ -18649,7 +18735,7 @@ function offsetToLineCol(source, offset2) {
18649
18735
  }
18650
18736
  return { line, column };
18651
18737
  }
18652
- function preprocessForClauses(source) {
18738
+ function preprocessForClauses(source, delimiters) {
18653
18739
  const annotations = [];
18654
18740
  const chars = source.split("");
18655
18741
  const isString = (ch) => ch === '"' || ch === "'" || ch === "`";
@@ -18657,6 +18743,10 @@ function preprocessForClauses(source) {
18657
18743
  while (i < source.length) {
18658
18744
  const ch = source[i];
18659
18745
  if (isString(ch)) {
18746
+ if (delimiters && !delimiters.has(i)) {
18747
+ i++;
18748
+ continue;
18749
+ }
18660
18750
  const quote = ch;
18661
18751
  let j = i + 1;
18662
18752
  let tplDepth = 0;
@@ -19011,9 +19101,11 @@ function parseGeneratedJs(source) {
19011
19101
  }
19012
19102
  function parse4(source, options = {}) {
19013
19103
  const ParserClass = createBaseParser();
19014
- const { code, annotations } = preprocessForClauses(blankGenericArrowTypeParams(blankComments(source)));
19104
+ const sourceTokens = tokenizeCode(source);
19105
+ const delimiters = sourceTokens === null ? null : collectStringDelimiters(source, sourceTokens);
19106
+ const { code, annotations } = preprocessForClauses(blankGenericArrowTypeParams(blankComments(source)), delimiters);
19015
19107
  const vskSource = !options.filename || String(options.filename).endsWith(".vsk");
19016
- const specifierExports = vskSource ? findSpecifierExports(code).filter((e) => !hasTopLevelValueDeclaration(code, e.local)) : [];
19108
+ const specifierExports = vskSource && sourceTokens !== null ? findSpecifierExports(code, sourceTokens).filter((e) => !hasTopLevelValueDeclaration(code, e.local, sourceTokens)) : [];
19017
19109
  let parseable = code;
19018
19110
  if (specifierExports.length > 0) {
19019
19111
  const edits = specifierExports.map((e) => code.slice(e.start, e.end)).map((text) => text.split("").map((ch) => ch === "\n" ? "\n" : " ").join(""));
@@ -30399,8 +30491,13 @@ function emitNode(ctx2, node, tracked2, effectsVar, parentVar, compPrefix = "__c
30399
30491
  if (node instanceof SlotNode) {
30400
30492
  if (!parentVar) {
30401
30493
  if (ctx2.hydrate) {
30494
+ const promptExpr = ctx2.markerless && (regionBudget || skipK > 0) ? markerlessSkipExpr(regionBudget, skipK) : null;
30402
30495
  ctx2.push(`if (props.children !== undefined && props.children !== null) {`);
30403
30496
  ctx2.push(` if (typeof props.children === 'function') {`);
30497
+ if (promptExpr) {
30498
+ ctx2.push(` ${ctx2.walker}.injectSkipK(${promptExpr});`);
30499
+ if (regionBudget && ctx2.markerless) ctx2.push(` ${regionBudget} = 0;`);
30500
+ }
30404
30501
  ctx2.push(` __pendingChild = props.children(__hydrate);`);
30405
30502
  ctx2.push(` } else if (__hydrate && __hydrate.root) {`);
30406
30503
  ctx2.push(` __hydrate.root.appendChild(toDomNode(props.children));`);
@@ -35794,6 +35891,20 @@ ${serverStoreLine}
35794
35891
  });
35795
35892
  }
35796
35893
 
35894
+ // Persisted-store pre-paint payload (buildStorePrePaintPayload) \u2014 same
35895
+ // token-stash/consume contract as /ssr-data.js, own store so neither handoff
35896
+ // can eat the other.
35897
+ if (pathname === '/store-init.js') {
35898
+ const initToken = url.searchParams.get('t') || '';
35899
+ const initStore = (globalThis.__vsk_store_init_payload_store || {});
35900
+ const initPayload = initStore[initToken];
35901
+ if (initPayload !== undefined) delete initStore[initToken];
35902
+ return new Response(initPayload ?? '// no store init', {
35903
+ status: 200,
35904
+ headers: { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-store' },
35905
+ });
35906
+ }
35907
+
35797
35908
  ${eventsStartLine}
35798
35909
  let mwCtx = { params: {}, url, locals: ${localsSeed}, cookies: {}, request, resolveUrl(u) { return new URL(u, request.url).href; }, set() {}, get() { return undefined; } };
35799
35910
  if (${hasMwLiteral}) {
@@ -46693,7 +46804,7 @@ function offsetToLineCol3(source, offset2) {
46693
46804
  }
46694
46805
  return { line, column };
46695
46806
  }
46696
- function preprocessForClauses2(source) {
46807
+ function preprocessForClauses2(source, delimiters) {
46697
46808
  const annotations = [];
46698
46809
  const chars = source.split("");
46699
46810
  const isString = (ch) => ch === '"' || ch === "'" || ch === "`";
@@ -46701,6 +46812,10 @@ function preprocessForClauses2(source) {
46701
46812
  while (i < source.length) {
46702
46813
  const ch = source[i];
46703
46814
  if (isString(ch)) {
46815
+ if (delimiters && !delimiters.has(i)) {
46816
+ i++;
46817
+ continue;
46818
+ }
46704
46819
  const quote = ch;
46705
46820
  let j = i + 1;
46706
46821
  let tplDepth = 0;
@@ -47077,9 +47192,11 @@ function createBaseParser2() {
47077
47192
  }
47078
47193
  function parse5(source, options = {}) {
47079
47194
  const ParserClass = createBaseParser2();
47080
- const { code, annotations } = preprocessForClauses2(blankGenericArrowTypeParams2(blankComments(source)));
47195
+ const sourceTokens = tokenizeCode(source);
47196
+ const delimiters = sourceTokens === null ? null : collectStringDelimiters(source, sourceTokens);
47197
+ const { code, annotations } = preprocessForClauses2(blankGenericArrowTypeParams2(blankComments(source)), delimiters);
47081
47198
  const vskSource = !options.filename || String(options.filename).endsWith(".vsk");
47082
- const specifierExports = vskSource ? findSpecifierExports(code).filter((e) => !hasTopLevelValueDeclaration(code, e.local)) : [];
47199
+ const specifierExports = vskSource && sourceTokens !== null ? findSpecifierExports(code, sourceTokens).filter((e) => !hasTopLevelValueDeclaration(code, e.local, sourceTokens)) : [];
47083
47200
  let parseable = code;
47084
47201
  if (specifierExports.length > 0) {
47085
47202
  const edits = specifierExports.map((e) => code.slice(e.start, e.end)).map((text) => text.split("").map((ch) => ch === "\n" ? "\n" : " ").join(""));
@@ -48034,6 +48151,7 @@ async function bundleRuntime(appDir, outDir) {
48034
48151
  `import { renderPage, renderFullPage, renderPageStream, compileFile, setRuntimeModule, setVskHydrate, hydratePrecompile, assertSameOrigin, withSsrStore } from ${JSON.stringify(resolve3(compilerRoot, "server-codegen.js"))};`,
48035
48152
  `import { parseCookies } from ${JSON.stringify(resolve3(compilerRoot, "server-cookies.js"))};`,
48036
48153
  `import * as __veskRuntime from ${JSON.stringify(resolve3(runtimeRoot, "index-server.js"))};`,
48154
+ `import * as __persistence from ${JSON.stringify(resolve3(runtimeRoot, "store-persistence.js"))};`,
48037
48155
  "",
48038
48156
  "// Inject runtime module so server-codegen can find components like NavLink, Link, etc.",
48039
48157
  "setRuntimeModule(__veskRuntime);",
@@ -48099,6 +48217,40 @@ async function bundleRuntime(appDir, outDir) {
48099
48217
  " return '/ssr-data.js?t=' + token;",
48100
48218
  "}",
48101
48219
  "",
48220
+ "// Persisted-store SSR continuity. `installPersistedStoreServerHooks` installs the",
48221
+ "// adapter-side read hook; `get/getReset` handle the per-request key registry",
48222
+ "// the server drains into the pre-paint payload. The init payload is delivered",
48223
+ "// as an origin-served script (/store-init.js?t=...) exactly like /ssr-data.js",
48224
+ "// so strict CSP (no unsafe-inline) cannot block it; it is never inlined.",
48225
+ "export const installPersistedStoreServerHooks = __persistence.installPersistedStoreServerHooks;",
48226
+ "export const persistedStoreCookieName = __persistence.persistedStoreCookieName;",
48227
+ "export const getPersistedStoreKeys = __persistence.getPersistedStoreKeys;",
48228
+ "export const resetPersistedStoreKeys = __persistence.resetPersistedStoreKeys;",
48229
+ "export const buildStorePrePaintPayload = __persistence.buildStorePrePaintPayload;",
48230
+ "export const storeInitScriptTag = __persistence.storeInitScriptTag;",
48231
+ "",
48232
+ "// Stash the pre-paint payload under a CSPRNG token and return the origin URL",
48233
+ "// the blocking <head> script points at. Own token store (NOT __vsk_ssr_payload_store)",
48234
+ "// so consuming the page hydration handoff never eats the store-init payload",
48235
+ "// and vice versa. Same 100-entry bound as /ssr-data.js.",
48236
+ "export function storeInitScriptGlobal(payload) {",
48237
+ " if (!payload) return null;",
48238
+ " const bytes = new Uint8Array(12);",
48239
+ " crypto.getRandomValues(bytes);",
48240
+ " let tk = '';",
48241
+ " for (const b of bytes) tk += b.toString(16).padStart(2, '0');",
48242
+ " const token = tk;",
48243
+ " const store = (globalThis.__vsk_store_init_payload_store ||= {});",
48244
+ " store[token] = payload;",
48245
+ " const keys = Object.keys(store);",
48246
+ " if (keys.length > 100) {",
48247
+ " for (let i = 0; i < keys.length - 100; i++) {",
48248
+ " delete store[keys[i]];",
48249
+ " }",
48250
+ " }",
48251
+ " return '/store-init.js?t=' + token;",
48252
+ "}",
48253
+ "",
48102
48254
  "// Re-export runtime classes used by API routes",
48103
48255
  "export const VeskRequest = __veskRuntime.VeskRequest;",
48104
48256
  "export const PayloadTooLargeError = __veskRuntime.PayloadTooLargeError;",
@@ -48409,6 +48561,51 @@ function buildParamExtraction(node, urlParts) {
48409
48561
  walk9(node);
48410
48562
  return parts;
48411
48563
  }
48564
+ function persistedStoreSsrCode() {
48565
+ return [
48566
+ "// \u2500\u2500 Persisted-store SSR continuity (createStore / createPathStore persist) \u2500\u2500",
48567
+ "// Reads a persisted store's cookie value through the adapter request context.",
48568
+ "// Client writes mirror URL-encoded; decode on the way back (best effort).",
48569
+ "function __persistRead(key) {",
48570
+ " const cookieName = persistedStoreCookieName(key);",
48571
+ " const req = globalThis.__vesk_request;",
48572
+ " if (!req) return null;",
48573
+ " const c = req.cookies || {};",
48574
+ " const v = typeof c.get === 'function' ? c.get(cookieName) : c[cookieName];",
48575
+ " if (v == null) return null;",
48576
+ " try { return decodeURIComponent(String(v)); } catch { return String(v); }",
48577
+ "}",
48578
+ "",
48579
+ "// Drain this request's registered persisted keys into a served pre-paint",
48580
+ "// script (/store-init.js?t=...) then forget them. `token` selects the",
48581
+ "// render's bucket for callers outside the ALS scope (the no-layout stream",
48582
+ "// path captures its token at the renderPageStream call site); the layout",
48583
+ "// path runs this inside the scope and relies on the ambient token.",
48584
+ "function __storeInitTag(token) {",
48585
+ " const keys = getPersistedStoreKeys(token);",
48586
+ " if (keys.length === 0) { resetPersistedStoreKeys(token); return ''; }",
48587
+ " const values = {};",
48588
+ " for (let __i = 0; __i < keys.length; __i++) values[keys[__i]] = __persistRead(keys[__i]);",
48589
+ " resetPersistedStoreKeys(token);",
48590
+ " const payload = buildStorePrePaintPayload(values);",
48591
+ " const url = storeInitScriptGlobal(payload);",
48592
+ " return url ? storeInitScriptTag(url) : '';",
48593
+ "}",
48594
+ "",
48595
+ "// Inject the blocking store-init <script src> into a full HTML string",
48596
+ "// (before </head>; append if the document has no head).",
48597
+ "function __injectStoreInit(html) {",
48598
+ " const tag = __storeInitTag();",
48599
+ " if (tag === '') return html;",
48600
+ " const idx = html.lastIndexOf('</head>');",
48601
+ " if (idx === -1) return html + tag;",
48602
+ " return html.slice(0, idx) + tag + html.slice(idx);",
48603
+ "}",
48604
+ "",
48605
+ "installPersistedStoreServerHooks({ read: __persistRead });",
48606
+ ""
48607
+ ].join("\n");
48608
+ }
48412
48609
  function generateSsrFunction(routeNode, appDir, outDir, componentMap, options) {
48413
48610
  const ancestorLayouts = options?.ancestorLayouts || [];
48414
48611
  const middlewareCode = options?.middlewareCode || null;
@@ -48510,6 +48707,7 @@ ${layoutDecls.map((d) => " " + d.compiled + ",").join("\n")}
48510
48707
  "",
48511
48708
  "async function __renderHtml(params, requestUrl) {",
48512
48709
  " return withSsrStore(async () => {",
48710
+ " resetPersistedStoreKeys();",
48513
48711
  " let page;",
48514
48712
  " let caughtError = null;",
48515
48713
  " const __expose = process.env.NODE_ENV !== 'production';",
@@ -48530,7 +48728,7 @@ ${layoutDecls.map((d) => " " + d.compiled + ",").join("\n")}
48530
48728
  " if (_inner.head) _head = _inner.head + _head;",
48531
48729
  " }",
48532
48730
  " const html = await renderFullPage('', _layoutCompList[0], { params, children: _body }, __componentRegistry, { hydrate: true, cached: _layoutCompiledList[0]" + bakedOptions + clientScriptOption + dataScriptOption + ", pageHead: _head });",
48533
- " return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: caughtError ? 500 : 200 });",
48731
+ " return new Response(__injectStoreInit(html), { headers: { 'Content-Type': 'text/html' }, status: caughtError ? 500 : 200 });",
48534
48732
  " });",
48535
48733
  "}",
48536
48734
  ""
@@ -48549,25 +48747,48 @@ ${layoutDecls.map((d) => " " + d.compiled + ",").join("\n")}
48549
48747
  "async function __renderHtml(params, requestUrl) {",
48550
48748
  " return withSsrStore(async () => {",
48551
48749
  " let stream;",
48750
+ // The streamed no-layout page emits its <head> before the component body
48751
+ // renders, so its persisted stores don't exist yet when the head is
48752
+ // flushed — the pre-paint tag therefore goes into the final chunk, right
48753
+ // before </body> (still a blocking, pre-paint script). Capture the render
48754
+ // token now (renderPageStream adopts this exact ambient store) so the
48755
+ // registry drain in the consumer's start() can hit the right bucket even
48756
+ // though it runs outside the ALS scope.
48757
+ " const __streamToken = globalThis.__vsk_ssr_token || '';",
48552
48758
  " try {",
48553
48759
  " stream = renderPageStream('', _comp, { params }, __componentRegistry, { hydrate: true, cached: _compiled" + bakedOptions + clientScriptOption + dataScriptOption + " });",
48554
48760
  " } catch (err) {",
48555
48761
  " if (err && (err.name === 'NotFoundError' || err.name === 'Redirect')) throw err;",
48556
48762
  " const html = await __renderErrorFullPage(params, requestUrl, err);",
48557
- " return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: 500 });",
48763
+ " return new Response(__injectStoreInit(html), { headers: { 'Content-Type': 'text/html' }, status: 500 });",
48558
48764
  " }",
48559
48765
  " return new Response(new ReadableStream({",
48560
48766
  " async start(controller) {",
48561
48767
  " const enc = new TextEncoder();",
48768
+ " let __tail = null;",
48769
+ " resetPersistedStoreKeys(__streamToken);",
48562
48770
  " try {",
48563
48771
  " for await (const chunk of stream) {",
48564
- " controller.enqueue(enc.encode(chunk));",
48772
+ " if (__tail !== null) controller.enqueue(enc.encode(__tail));",
48773
+ " __tail = chunk;",
48774
+ " }",
48775
+ " if (__tail !== null) {",
48776
+ " const __tag = __storeInitTag(__streamToken);",
48777
+ " if (__tag !== '') {",
48778
+ " const __bi = __tail.lastIndexOf('</body>');",
48779
+ " __tail = __bi === -1 ? __tail + __tag : __tail.slice(0, __bi) + __tag + __tail.slice(__bi);",
48780
+ " }",
48781
+ " controller.enqueue(enc.encode(__tail));",
48782
+ " } else {",
48783
+ " // Empty stream (no chunks) \u2014 nothing to attach the tag to; just",
48784
+ " // clear the registry so a later render can't inherit these keys.",
48785
+ " resetPersistedStoreKeys(__streamToken);",
48565
48786
  " }",
48566
48787
  " } catch (err) {",
48567
48788
  " if (err && (err.name === 'NotFoundError' || err.name === 'Redirect')) throw err;",
48568
48789
  " try {",
48569
48790
  " const html = await __renderErrorFullPage(params, requestUrl, err);",
48570
- " controller.enqueue(enc.encode(html));",
48791
+ " controller.enqueue(enc.encode(__injectStoreInit(html)));",
48571
48792
  " } catch {}",
48572
48793
  " }",
48573
48794
  " controller.close();",
@@ -48578,6 +48799,7 @@ ${layoutDecls.map((d) => " " + d.compiled + ",").join("\n")}
48578
48799
  ""
48579
48800
  ].join("\n");
48580
48801
  }
48802
+ const persistCode = persistedStoreSsrCode();
48581
48803
  let dataCode;
48582
48804
  const exposeErr = "process.env.NODE_ENV !== 'production'";
48583
48805
  const dataNavCleanup = [
@@ -48591,6 +48813,9 @@ ${layoutDecls.map((d) => " " + d.compiled + ",").join("\n")}
48591
48813
  // process-global token — the cross-request data-slot race, for good. The
48592
48814
  // token dies with the withSsrStore scope wrapped around this block instead.
48593
48815
  " delete globalThis.__vsk_ssr_data;",
48816
+ // SPA data-nav only runs renderPage, so the persisted-store registration
48817
+ // bucket would otherwise keep this request's keys forever.
48818
+ " resetPersistedStoreKeys();",
48594
48819
  " }"
48595
48820
  ];
48596
48821
  const dataNavWrap = (body) => [
@@ -48775,7 +49000,7 @@ ${layoutDecls.map((d) => " " + d.compiled + ",").join("\n")}
48775
49000
  ""
48776
49001
  ].join("\n");
48777
49002
  const funcCode = [
48778
- "import { renderFullPage, renderPageStream, renderPage, hydratePrecompile, parseCookies, getAction, validateActionInput, issuesToFieldMap, storeDataScriptGlobal, withSsrStore, assertSameOrigin, VeskRequest } from '../runtime.js';",
49003
+ "import { renderFullPage, renderPageStream, renderPage, hydratePrecompile, parseCookies, getAction, validateActionInput, issuesToFieldMap, storeDataScriptGlobal, withSsrStore, assertSameOrigin, VeskRequest, installPersistedStoreServerHooks, persistedStoreCookieName, getPersistedStoreKeys, resetPersistedStoreKeys, buildStorePrePaintPayload, storeInitScriptGlobal, storeInitScriptTag } from '../runtime.js';",
48779
49004
  "",
48780
49005
  middlewareCode || "",
48781
49006
  registryCode,
@@ -48784,6 +49009,8 @@ ${layoutDecls.map((d) => " " + d.compiled + ",").join("\n")}
48784
49009
  paramsCode,
48785
49010
  htmlFnCode,
48786
49011
  "",
49012
+ persistCode,
49013
+ "",
48787
49014
  "export async function handle(request) {",
48788
49015
  " const url = new URL(request.url);",
48789
49016
  " const params = __paramsFor(url.pathname);",
@@ -50918,6 +51145,15 @@ async function startProdServer(outDir, options) {
50918
51145
  res.end(lines.join("\n") || "// no ssr data");
50919
51146
  return;
50920
51147
  }
51148
+ if (url.pathname === "/store-init.js") {
51149
+ const token = url.searchParams.get("t") || "";
51150
+ const store = globalThis.__vsk_store_init_payload_store;
51151
+ const payload = store?.[token];
51152
+ if (payload) delete store[token];
51153
+ res.writeHead(200, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
51154
+ res.end(payload ?? "// no store init");
51155
+ return;
51156
+ }
50921
51157
  if (url.pathname === "/_vesk/runtime.js") {
50922
51158
  const clientFile = buildConfig?.assets?.client?.file || "client.js";
50923
51159
  const clientPath = resolve13(staticDir, clientFile);
@@ -51224,6 +51460,29 @@ async function resolveCompilerApi(name) {
51224
51460
  }
51225
51461
  return import(`@vesk/compiler/src/${name.replace(/\.js$/, "")}`);
51226
51462
  }
51463
+ function findRouteConflicts(routeTree, appDir) {
51464
+ const projectRoot = resolve21(appDir, "..");
51465
+ const byPath = /* @__PURE__ */ new Map();
51466
+ const walk9 = (nodes) => {
51467
+ for (const node of nodes) {
51468
+ if (node.page) {
51469
+ const pageFile = join15(node.sourceDir, "page.vsk");
51470
+ const files = byPath.get(node.fullPath) ?? [];
51471
+ files.push(relative4(projectRoot, pageFile));
51472
+ byPath.set(node.fullPath, files);
51473
+ }
51474
+ if (node.children && node.children.length > 0)
51475
+ walk9(node.children);
51476
+ }
51477
+ };
51478
+ walk9(routeTree);
51479
+ const conflicts = [];
51480
+ for (const [fullPath, files] of byPath) {
51481
+ if (files.length > 1)
51482
+ conflicts.push({ fullPath, files });
51483
+ }
51484
+ return conflicts;
51485
+ }
51227
51486
  function filterActivePlugins2(plugins, records) {
51228
51487
  if (!records || records.length === 0)
51229
51488
  return plugins;
@@ -51357,6 +51616,14 @@ async function build2(appDir, options) {
51357
51616
  console.error("vesk build: no routes found in", appDir);
51358
51617
  return;
51359
51618
  }
51619
+ const routeConflicts = findRouteConflicts(routeTree, appDir);
51620
+ if (routeConflicts.length > 0) {
51621
+ for (const conflict of routeConflicts) {
51622
+ console.error(`vesk build: conflicting routes for ${conflict.fullPath}: ${conflict.files.join(", ")}`);
51623
+ }
51624
+ const first = routeConflicts[0];
51625
+ throw new Error(`conflicting routes: ${first.fullPath} is claimed by both ${first.files.join(" and ")}`);
51626
+ }
51360
51627
  const projectRoot = resolve21(appDir, "..");
51361
51628
  const componentsDir = resolve21(projectRoot, "components");
51362
51629
  const componentMap = scanComponents2(componentsDir);
@@ -52367,8 +52634,14 @@ function emitNode2(ctx2, node, tracked2, effectsVar, parentVar, compPrefix = "__
52367
52634
  if (node instanceof SlotNode) {
52368
52635
  if (!parentVar) {
52369
52636
  if (ctx2.hydrate) {
52637
+ const promptExpr = ctx2.markerless && (regionBudget || skipK > 0) ? markerlessSkipExpr2(regionBudget, skipK) : null;
52370
52638
  ctx2.push(`if (props.children !== undefined && props.children !== null) {`);
52371
52639
  ctx2.push(` if (typeof props.children === 'function') {`);
52640
+ if (promptExpr) {
52641
+ ctx2.push(` ${ctx2.walker}.injectSkipK(${promptExpr});`);
52642
+ if (regionBudget && ctx2.markerless)
52643
+ ctx2.push(` ${regionBudget} = 0;`);
52644
+ }
52372
52645
  ctx2.push(` __pendingChild = props.children(__hydrate);`);
52373
52646
  ctx2.push(` } else if (__hydrate && __hydrate.root) {`);
52374
52647
  ctx2.push(` __hydrate.root.appendChild(toDomNode(props.children));`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/vesk-cli",
3
- "version": "0.2.59",
3
+ "version": "0.2.60",
4
4
  "type": "module",
5
5
  "description": "Vesk — compiler-first framework for the post-VDOM web. vesk dev, vesk build.",
6
6
  "bin": {
@@ -8,10 +8,10 @@
8
8
  },
9
9
  "main": "./dist/cli.js",
10
10
  "dependencies": {
11
- "@vesk/adapter": "^0.2.59",
12
- "@vesk/compiler": "^0.2.59",
13
- "@vesk/plugin-tailwind": "^0.2.59",
14
- "@vesk/runtime": "^0.2.59",
11
+ "@vesk/adapter": "^0.2.60",
12
+ "@vesk/compiler": "^0.2.60",
13
+ "@vesk/plugin-tailwind": "^0.2.60",
14
+ "@vesk/runtime": "^0.2.60",
15
15
  "esrap": "^2.3.0",
16
16
  "typescript": "^5.8.0",
17
17
  "ws": "^8.18.0",