@vmz/core 0.1.32 → 0.2.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.
Files changed (62) hide show
  1. package/dist/{client-nav.d.ts → browser/client-nav.d.ts} +4 -13
  2. package/dist/{client-nav.js → browser/client-nav.js} +16 -74
  3. package/dist/browser/direct-api.types.d.ts +58 -0
  4. package/dist/browser/direct-api.types.js +4 -0
  5. package/dist/browser/direct-host-box.d.ts +11 -0
  6. package/dist/{direct-host-box.js → browser/direct-host-box.js} +2 -18
  7. package/dist/{dom-core.d.ts → browser/dom-core.d.ts} +64 -220
  8. package/dist/browser/dom-core.js +2453 -0
  9. package/dist/browser/dom-core.types.d.ts +138 -0
  10. package/dist/browser/dom-core.types.js +4 -0
  11. package/dist/{position-context.js → browser/position-context.js} +1 -1
  12. package/dist/browser/unknown-component.d.ts +14 -0
  13. package/dist/{unknown-component.js → browser/unknown-component.js} +4 -28
  14. package/dist/{dom.browser.d.ts → faces/dom.browser.d.ts} +2 -2
  15. package/dist/{dom.browser.js → faces/dom.browser.js} +2 -3
  16. package/dist/faces/dom.client.d.ts +4 -0
  17. package/dist/faces/dom.client.js +4 -0
  18. package/dist/faces/dom.d.ts +6 -0
  19. package/dist/faces/dom.js +6 -0
  20. package/dist/{http.d.ts → faces/http.d.ts} +1 -1
  21. package/dist/{http.js → faces/http.js} +1 -2
  22. package/dist/{server.d.ts → faces/server.d.ts} +8 -25
  23. package/dist/{server.js → faces/server.js} +25 -155
  24. package/dist/{vmz-dom.d.ts → faces/vmz-dom.d.ts} +1 -1
  25. package/dist/{vmz-dom.js → faces/vmz-dom.js} +1 -2
  26. package/dist/{vmz-runtime.d.ts → faces/vmz-runtime.d.ts} +1 -1
  27. package/dist/{vmz-runtime.js → faces/vmz-runtime.js} +1 -2
  28. package/dist/host/deployment-registry.d.ts +16 -0
  29. package/dist/{deployment-registry.js → host/deployment-registry.js} +1 -68
  30. package/dist/host/list-client-components.d.ts +14 -0
  31. package/dist/{list-client-components.js → host/list-client-components.js} +0 -37
  32. package/dist/{localize-body-links.js → host/localize-body-links.js} +0 -1
  33. package/dist/host/native-addon.d.ts +6 -0
  34. package/dist/{native-addon.js → host/native-addon.js} +0 -20
  35. package/dist/host/render-host.d.ts +20 -0
  36. package/dist/{render-host.js → host/render-host.js} +0 -16
  37. package/dist/{serve-host.d.ts → host/serve-host.d.ts} +3 -3
  38. package/dist/{serve-host.mjs → host/serve-host.mjs} +45 -168
  39. package/dist/shared/host.types.d.ts +67 -0
  40. package/dist/shared/host.types.js +4 -0
  41. package/dist/shared/server.types.d.ts +39 -0
  42. package/dist/shared/server.types.js +4 -0
  43. package/dist/{dom-ssr.d.ts → ssr/dom-ssr.d.ts} +8 -37
  44. package/dist/{dom-ssr.js → ssr/dom-ssr.js} +159 -217
  45. package/package.json +34 -34
  46. package/dist/deployment-registry.d.ts +0 -70
  47. package/dist/direct-host-box.d.ts +0 -26
  48. package/dist/dom-core.js +0 -4815
  49. package/dist/dom.client.d.ts +0 -4
  50. package/dist/dom.client.js +0 -5
  51. package/dist/dom.d.ts +0 -6
  52. package/dist/dom.js +0 -7
  53. package/dist/list-client-components.d.ts +0 -62
  54. package/dist/native-addon.d.ts +0 -12
  55. package/dist/render-host.d.ts +0 -32
  56. package/dist/unknown-component.d.ts +0 -40
  57. /package/dist/{position-context.d.ts → browser/position-context.d.ts} +0 -0
  58. /package/dist/{index.d.ts → faces/index.d.ts} +0 -0
  59. /package/dist/{index.js → faces/index.js} +0 -0
  60. /package/dist/{localize-body-links.d.ts → host/localize-body-links.d.ts} +0 -0
  61. /package/dist/{route-layout-chain.d.ts → host/route-layout-chain.d.ts} +0 -0
  62. /package/dist/{route-layout-chain.js → host/route-layout-chain.js} +0 -0
@@ -1,6 +1,5 @@
1
- // @ts-nocheck
2
1
  /**
3
- * Generic VMZ Node host — SSR Route Graph pages + dist static + RPC/REST.
2
+ * Generic VMZ Node host �?SSR Route Graph pages + dist static + RPC/REST.
4
3
  *
5
4
  * Invoked by `vmz serve` / `vmz dev` (or: node dist/vmz-serve-host.mjs).
6
5
  *
@@ -10,8 +9,8 @@
10
9
  *
11
10
  * `VMZ_DEV=1`: POST `/__vmz/reload` soft-reloads modules (cache-bust import);
12
11
  * GET `/__vmz/events` SSE notifies the browser:
13
- * - island HMR → re-import `entry-client.js` (no full document reload)
14
- * - otherwise → `location.reload`
12
+ * - island HMR �?re-import `entry-client.js` (no full document reload)
13
+ * - otherwise �?`location.reload`
15
14
  *
16
15
  * Dev resolve hook propagates `?t=` onto nested relative `file:` imports under
17
16
  * dist so soft reload does not keep a stale `lib/*.js` ESM cache entry.
@@ -27,12 +26,11 @@ import { LOCALE_LINK_PLAN_SCHEMA, linkRouteAliasesFromUnits, localeHrefTableFrom
27
26
  import { loadNativeAddon } from './native-addon.js';
28
27
  import { createRenderHost } from './render-host.js';
29
28
  import { resolveRouteLayoutChain } from './route-layout-chain.js';
30
- import { handleNodeRequest, setRoutes, setServerModuleResolver } from './vmz-runtime.js';
29
+ import { handleNodeRequest, setRoutes, setServerModuleResolver } from '../faces/vmz-runtime.js';
31
30
  const ROUTE_CATALOG_SCHEMA = 'vmz.route.catalog.v0';
32
31
  const ROUTE_CATALOG_REL = '_vmz/route-catalog.json';
33
32
  const LOCALE_LINK_PLAN_REL = '_vmz/locale-link-plan.json';
34
33
  const require = createRequire(import.meta.url);
35
- /** App delivery root. Host companions may live under `_vmz/host/` (0.1.31). */
36
34
  function resolveServeDistDir() {
37
35
  if (process.env.VMZ_DIST)
38
36
  return path.resolve(process.env.VMZ_DIST);
@@ -45,14 +43,12 @@ function resolveServeDistDir() {
45
43
  return here;
46
44
  }
47
45
  const distDir = resolveServeDistDir();
48
- /** App project root for bare import / JSON resolve (dev SSR ≡ build node_modules). */
49
46
  function projectRootForResolve() {
50
47
  const fromEnv = typeof process.env.VMZ_PROJECT_ROOT === 'string' ? process.env.VMZ_PROJECT_ROOT.trim() : '';
51
48
  if (fromEnv)
52
49
  return path.resolve(fromEnv);
53
50
  return process.cwd();
54
51
  }
55
- /** @type {ReturnType<typeof createRequire> | null} */
56
52
  let appPackageRequire = null;
57
53
  function appPackageRequireResolve() {
58
54
  if (!appPackageRequire) {
@@ -112,7 +108,7 @@ const isDev = process.env.VMZ_DEV === '1' || process.env.VMZ_DEV === 'true';
112
108
  globalThis.__VMZ_RPC_ORIGIN = `http://${host}:${port}`;
113
109
  /**
114
110
  * Soft reload only busts the top-level `import(page?t=token)`. Nested relative
115
- * imports (`../../lib/units.js`) keep the first-loaded ESM cache entry — so a
111
+ * imports (`../../lib/units.js`) keep the first-loaded ESM cache entry �?so a
116
112
  * page can demand exports that the stale dep never had (or vice versa).
117
113
  * Propagate `t` from parentURL onto file: children under this dist.
118
114
  */
@@ -151,34 +147,20 @@ if (isDev) {
151
147
  },
152
148
  });
153
149
  }
154
- /** @type {number} */
155
150
  let reloadToken = Date.now();
156
- /** @type {Awaited<ReturnType<typeof createRenderHost>> | null} */
157
151
  let ssrRenderHost = null;
158
- /** @type {string | null} Correlatable build id from vmz dev (Living §12.8). */
159
152
  let lastDevBuildId = null;
160
- /** @type {Array<{ chunkId: string, pageRel: string, segs: ReturnType<typeof parsePathPattern> }>} */
161
153
  let pageCatalog = [];
162
- /** @type {Map<string, any>} */
163
154
  const pageCtors = new Map();
164
- /** Stylesheet from deployment `cssEntry` (e.g. vmz.css). */
165
155
  let cssEntry = null;
166
- /** Fingerprint of style inputs — busts `@import` siblings when tokens change (VMZ-8). */
167
156
  let styleBundleHash = null;
168
- /** @type {{ defaultThemeId: string, themeIds: string[], activationAttr: string, contentHash: string|null } | null} */
169
157
  let styleTheme = null;
170
- /** Locale route realization artifact from `_vmz/locale-route-realization.json` (optional). */
171
158
  let localeArtifact = null;
172
- /** Frozen locale link plan from `_vmz/locale-link-plan.json` (0.1.30 authority). */
173
159
  let localeLinkPlan = null;
174
- /** @type {Set<import('node:http').ServerResponse>} */
175
160
  const sseClients = new Set();
176
- /** In-flight HTTP requests (graceful shutdown drain). */
177
161
  let inFlight = 0;
178
- /** When true, refuse new work except health. */
179
162
  let shuttingDown = false;
180
163
  let ready = false;
181
- /** @type {{ message: string, stack?: string, at: number } | null} */
182
164
  let lastDevError = null;
183
165
  setServerModuleResolver((moduleId) => {
184
166
  const rel = moduleId.replace(/^#server\//, '') + '.js';
@@ -190,13 +172,9 @@ try {
190
172
  }
191
173
  catch (err) {
192
174
  lastDevError = normalizeDevError(err);
193
- ready = true; // still accept HTTP — serve error page / recover on next reload
175
+ ready = true; // still accept HTTP �?serve error page / recover on next reload
194
176
  console.error('vmz serve: initial load failed (dev host stays up)', lastDevError.message);
195
177
  }
196
- /**
197
- * @param {import('node:http').IncomingMessage} req
198
- * @returns {Promise<string>}
199
- */
200
178
  function readRequestBody(req) {
201
179
  return new Promise((resolve, reject) => {
202
180
  const chunks = [];
@@ -205,17 +183,16 @@ function readRequestBody(req) {
205
183
  req.on('error', reject);
206
184
  });
207
185
  }
208
- /**
209
- * @param {string} pathname
210
- * @returns {Promise<string | null>}
211
- */
212
186
  async function renderPage(pathname, opts = {}) {
213
187
  const rendered = await renderPageStream(pathname, opts);
214
188
  if (!rendered)
215
189
  return null;
216
- const stream = rendered.stream ?? rendered;
190
+ const raw = rendered.stream ?? rendered;
191
+ if (!raw || typeof raw !== 'object' || typeof raw[Symbol.asyncIterator] !== 'function') {
192
+ return null;
193
+ }
217
194
  let html = '';
218
- for await (const chunk of stream) {
195
+ for await (const chunk of raw) {
219
196
  html += chunk;
220
197
  }
221
198
  return html;
@@ -224,9 +201,6 @@ async function renderPage(pathname, opts = {}) {
224
201
  * Stream shell + Direct serialize body for the matched file-route page.
225
202
  * Runs Page.access (closed allow/redirect/not-found/deny) before load;
226
203
  * POST may run Page.action before re-render.
227
- * @param {string} pathname
228
- * @param {{ signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string, method?: string, body?: unknown }} [opts]
229
- * @returns {Promise<{ status: number, stream?: AsyncGenerator<string, void, void>, redirect?: string, headers?: Record<string, string> } | null>}
230
204
  */
231
205
  async function renderPageStream(pathname, opts = {}) {
232
206
  try {
@@ -239,11 +213,6 @@ async function renderPageStream(pathname, opts = {}) {
239
213
  return { status: 500, stream: emitDevErrorHtml(normalized) };
240
214
  }
241
215
  }
242
- /**
243
- * @param {string} pathname
244
- * @param {{ signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string, method?: string, body?: unknown }} [opts]
245
- * @returns {Promise<{ status: number, stream?: AsyncGenerator<string, void, void>, redirect?: string, headers?: Record<string, string> } | null>}
246
- */
247
216
  async function renderPageStreamInner(pathname, opts = {}) {
248
217
  if (isDev && lastDevError && pageCtors.size === 0) {
249
218
  return { status: 500, stream: emitDevErrorHtml(lastDevError) };
@@ -376,10 +345,6 @@ async function renderPageStreamInner(pathname, opts = {}) {
376
345
  stream: emitPageHtml(Page, match.chunkId, eventOnlyShell, props, opts, layoutChain, localeCtx),
377
346
  };
378
347
  }
379
- /**
380
- * @param {unknown} access
381
- * @returns {{ kind: 'allow' } | { kind: 'redirect', location: string } | { kind: 'deny' } | { kind: 'not-found' }}
382
- */
383
348
  function normalizeAccessResult(access) {
384
349
  if (access == null || access === true)
385
350
  return { kind: 'allow' };
@@ -392,10 +357,11 @@ function normalizeAccessResult(access) {
392
357
  if (k === 'not-found' || k === 'notfound')
393
358
  return { kind: 'not-found' };
394
359
  }
395
- if (typeof access === 'object') {
396
- const kind = String(access.kind || access.type || 'allow').toLowerCase();
360
+ if (typeof access === 'object' && access) {
361
+ const row = access;
362
+ const kind = String(row.kind || row.type || 'allow').toLowerCase();
397
363
  if (kind === 'redirect') {
398
- const location = String(access.location || access.to || access.href || '');
364
+ const location = String(row.location || row.to || row.href || '');
399
365
  if (!location)
400
366
  return { kind: 'deny' };
401
367
  return { kind: 'redirect', location };
@@ -408,30 +374,28 @@ function normalizeAccessResult(access) {
408
374
  }
409
375
  return { kind: 'allow' };
410
376
  }
411
- /**
412
- * @param {unknown} acted
413
- * @returns {{ kind: 'allow', props?: Record<string, unknown> } | { kind: 'redirect', location: string } | { kind: 'deny' } | { kind: 'not-found' }}
414
- */
415
377
  function normalizeActionResult(acted) {
416
378
  const base = normalizeAccessResult(acted);
417
379
  if (base.kind !== 'allow')
418
380
  return base;
419
- if (acted && typeof acted === 'object' && acted.props && typeof acted.props === 'object') {
420
- return { kind: 'allow', props: acted.props };
421
- }
422
- if (acted && typeof acted === 'object' && !('kind' in acted) && !('type' in acted) && !Array.isArray(acted)) {
423
- return { kind: 'allow', props: acted };
381
+ if (acted && typeof acted === 'object' && !Array.isArray(acted)) {
382
+ const row = acted;
383
+ if (row.props && typeof row.props === 'object') {
384
+ return { kind: 'allow', props: row.props };
385
+ }
386
+ if (!('kind' in row) && !('type' in row)) {
387
+ return { kind: 'allow', props: row };
388
+ }
424
389
  }
425
390
  return { kind: 'allow' };
426
391
  }
427
392
  /**
428
393
  * Minimal HTML for closed access/action results when no NotFound page exists.
429
- * @param {string} marker
430
394
  */
431
395
  async function* emitAccessShell(marker) {
432
396
  const native = loadNativeAddon();
433
397
  if (typeof native.generateHtmlShell !== 'function') {
434
- throw new Error('vmz native addon missing generateHtmlShell — rebuild with `pnpm napi:build`');
398
+ throw new Error('vmz native addon missing generateHtmlShell �?rebuild with `pnpm napi:build`');
435
399
  }
436
400
  yield native.generateHtmlShell({
437
401
  title: 'App',
@@ -442,8 +406,7 @@ async function* emitAccessShell(marker) {
442
406
  });
443
407
  }
444
408
  /**
445
- * Prefer page `static meta()` for document title/description — never brand the framework in business HTML.
446
- * @param {any} Page
409
+ * Prefer page `static meta()` for document title/description �?never brand the framework in business HTML.
447
410
  */
448
411
  function resolvePageDocumentMeta(Page) {
449
412
  try {
@@ -460,14 +423,6 @@ function resolvePageDocumentMeta(Page) {
460
423
  return { title: 'App', description: '' };
461
424
  }
462
425
  }
463
- /**
464
- * @param {any} Page
465
- * @param {string} chunkId
466
- * @param {boolean} eventOnlyShell
467
- * @param {Record<string, unknown>} props
468
- * @param {{ signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string }} [opts]
469
- * @param {string[]} [layoutChain] layout chunk ids outer→inner
470
- */
471
426
  async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {}, layoutChain = [], localeCtx = {}) {
472
427
  const signal = opts.signal;
473
428
  const live = isDev
@@ -477,7 +432,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
477
432
  let sawDisconnect = false;
478
433
  es.onerror = () => { sawDisconnect = true; };
479
434
  es.onopen = () => {
480
- // Host respawn drops SSE — reload once the new process is up (no manual restart).
435
+ // Host respawn drops SSE �?reload once the new process is up (no manual restart).
481
436
  if (sawDisconnect) location.reload();
482
437
  };
483
438
  function showOverlay(err) {
@@ -501,7 +456,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
501
456
  + "<p style=\\"margin:0 0 .5rem;color:#f87171;font-weight:700\\">Dev Error</p>"
502
457
  + "<pre style=\\"white-space:pre-wrap;margin:0 0 1rem;font-size:13px;line-height:1.45\\">" + esc(msg) + "</pre>"
503
458
  + (stack ? "<pre style=\\"white-space:pre-wrap;opacity:.7;font-size:12px\\">" + esc(stack) + "</pre>" : "")
504
- + "<p style=\\"opacity:.65;font-size:12px\\">Fix the file and save — soft reload will clear this overlay.</p>"
459
+ + "<p style=\\"opacity:.65;font-size:12px\\">Fix the file and save �?soft reload will clear this overlay.</p>"
505
460
  + "</div>";
506
461
  }
507
462
  function hideOverlay() {
@@ -568,7 +523,6 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
568
523
  const propsJson = JSON.stringify(props ?? {});
569
524
  const localeId = localeCtx.localeId || localeArtifact?.defaultLocale || 'en';
570
525
  const dir = localeCtx.dir || 'ltr';
571
- /** @type {string[]} */
572
526
  const htmlExtraAttrs = [...htmlThemeAttrPair(themeId)];
573
527
  if (localeArtifact?.routing) {
574
528
  htmlExtraAttrs.push('data-vmz-locale-routing', JSON.stringify({
@@ -602,7 +556,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
602
556
  }
603
557
  if (signal?.aborted)
604
558
  return;
605
- // Wrap page HTML in layout chain (outer → inner) via default slot injection.
559
+ // Wrap page HTML in layout chain (outer �?inner) via default slot injection.
606
560
  for (let i = layoutChain.length - 1; i >= 0; i--) {
607
561
  const Layout = await loadPageCtor(layoutChain[i]);
608
562
  if (!Layout)
@@ -611,7 +565,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
611
565
  if (signal?.aborted)
612
566
  return;
613
567
  }
614
- // Locale discipline: apply frozen link plan rows (0.1.30) — no path algebra.
568
+ // Locale discipline: apply frozen link plan rows (0.1.30) �?no path algebra.
615
569
  if (localeArtifact && localeId) {
616
570
  bodyHtml = localizeBodyLinks(bodyHtml, localeId, localeArtifact, undefined, localeLinkPlan);
617
571
  }
@@ -626,7 +580,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
626
580
  return;
627
581
  const native = loadNativeAddon();
628
582
  if (typeof native.generatePageShell !== 'function') {
629
- throw new Error('vmz native addon missing generatePageShell — rebuild with `pnpm napi:build`');
583
+ throw new Error('vmz native addon missing generatePageShell �?rebuild with `pnpm napi:build`');
630
584
  }
631
585
  const entrySrc = `/${eventOnlyShell ? 'entry-event.js' : 'entry-client.js'}?t=${reloadToken}`;
632
586
  const cssHref = cssEntryWithBust(cssEntry);
@@ -690,7 +644,7 @@ const server = http.createServer((req, res) => {
690
644
  .then((raw) => {
691
645
  let payload = {};
692
646
  try {
693
- payload = raw ? JSON.parse(raw) : {};
647
+ payload = raw ? JSON.parse(String(raw)) : {};
694
648
  }
695
649
  catch {
696
650
  payload = {};
@@ -739,7 +693,7 @@ async function gracefulShutdown(signal) {
739
693
  return;
740
694
  shuttingDown = true;
741
695
  ready = false;
742
- console.log(`vmz serve: ${signal} — draining in-flight=${inFlight} timeout=${SHUTDOWN_TIMEOUT_MS}ms`);
696
+ console.log(`vmz serve: ${signal} �?draining in-flight=${inFlight} timeout=${SHUTDOWN_TIMEOUT_MS}ms`);
743
697
  server.close();
744
698
  const start = Date.now();
745
699
  while (inFlight > 0 && Date.now() - start < SHUTDOWN_TIMEOUT_MS) {
@@ -765,8 +719,6 @@ process.on('SIGINT', () => {
765
719
  /**
766
720
  * Attach author Link RouteId aliases so localizeBodyLinks can match `data-vmz-route="IndexPage"`
767
721
  * against realization rows keyed by chunk path (`pages/index`).
768
- * @param {any} artifact
769
- * @param {string} dir
770
722
  */
771
723
  function attachLinkRouteAliases(artifact, dir) {
772
724
  if (!artifact || typeof artifact !== 'object')
@@ -786,7 +738,6 @@ function attachLinkRouteAliases(artifact, dir) {
786
738
  * Re-import routes / pages / components with a new cache-bust token.
787
739
  * Keeps the HTTP server process alive (no Node restart).
788
740
  * Failed reloads keep the previous in-memory modules (Vite-like resilience).
789
- * @param {{ quiet?: boolean, payload?: { affectedChunks?: string[], seedChunks?: string[], emitted?: string[], full?: boolean, islandHmr?: boolean, buildId?: string, sourceRevision?: string, bundleRevision?: string, changed?: string[] } }} [opts]
790
741
  */
791
742
  async function softReload(opts = {}) {
792
743
  const prevToken = reloadToken;
@@ -805,7 +756,7 @@ async function softReload(opts = {}) {
805
756
  const bundleRevision = opts.payload?.bundleRevision != null ? String(opts.payload.bundleRevision) : null;
806
757
  if (buildId)
807
758
  lastDevBuildId = buildId;
808
- // 0.1.31: payload-only scope. `reloadToken` is opaque cache-bust — never invent full/affected here.
759
+ // 0.1.31: payload-only scope. `reloadToken` is opaque cache-bust �?never invent full/affected here.
809
760
  const reloadAllPages = Boolean(full) || (!islandHmr && rerunLoaders.length === 0 && affected.length === 0);
810
761
  try {
811
762
  try {
@@ -837,7 +788,6 @@ async function softReload(opts = {}) {
837
788
  });
838
789
  const nextCatalog = await listPageClientFiles(distDir);
839
790
  // empty catalog throws inside listPageClientFiles
840
- /** @type {Map<string, any>} */
841
791
  const nextCtors = new Map();
842
792
  if (!islandHmr) {
843
793
  const reloadSet = rerunLoaders.length > 0
@@ -937,7 +887,6 @@ async function softReload(opts = {}) {
937
887
  throw err;
938
888
  }
939
889
  }
940
- /** @param {string} chunkId @param {string[]} affected */
941
890
  function pageNeedsReload(chunkId, affected) {
942
891
  if (chunkId === 'pages/Layout' || chunkId.endsWith('/Layout'))
943
892
  return true;
@@ -946,7 +895,6 @@ function pageNeedsReload(chunkId, affected) {
946
895
  return id === chunkId || chunkId.startsWith(`${id}/`) || id.startsWith(`${chunkId}/`);
947
896
  });
948
897
  }
949
- /** @param {string} event */
950
898
  function notifySse(event) {
951
899
  for (const client of [...sseClients]) {
952
900
  try {
@@ -957,10 +905,9 @@ function notifySse(event) {
957
905
  }
958
906
  }
959
907
  }
960
- /** @param {unknown} err */
961
908
  function normalizeDevError(err) {
962
909
  if (err && typeof err === 'object') {
963
- const e = /** @type {{ message?: string, stack?: string }} */ (err);
910
+ const e = err;
964
911
  return {
965
912
  message: e.message ? String(e.message) : String(err),
966
913
  stack: e.stack ? String(e.stack) : undefined,
@@ -969,7 +916,6 @@ function normalizeDevError(err) {
969
916
  }
970
917
  return { message: String(err), at: Date.now() };
971
918
  }
972
- /** @param {{ message: string, stack?: string }} err */
973
919
  async function* emitDevErrorHtml(err) {
974
920
  const msg = escapeHtml(err.message || 'Unknown error');
975
921
  const stack = err.stack ? escapeHtml(err.stack) : '';
@@ -985,7 +931,7 @@ async function* emitDevErrorHtml(err) {
985
931
  <h1>Dev Error</h1>
986
932
  <pre>${msg}</pre>
987
933
  ${stack ? `<pre style="opacity:.7;font-size:12px">${stack}</pre>` : ''}
988
- <p class="hint">Dev host stayed up. Fix the source and save — soft reload will recover.</p>
934
+ <p class="hint">Dev host stayed up. Fix the source and save �?soft reload will recover.</p>
989
935
  </main>
990
936
  <script>
991
937
  (() => {
@@ -1011,11 +957,10 @@ async function* emitDevErrorHtml(err) {
1011
957
  }
1012
958
  }
1013
959
  catch {
1014
- /* fall through to plain HTML — never throw from the error page itself */
960
+ /* fall through to plain HTML �?never throw from the error page itself */
1015
961
  }
1016
962
  yield `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><title>Dev Error</title></head><body>${body}</body></html>`;
1017
963
  }
1018
- /** @param {string} s */
1019
964
  function escapeHtml(s) {
1020
965
  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1021
966
  }
@@ -1024,8 +969,6 @@ function escapeHtml(s) {
1024
969
  * - `prefix`: LocaleId from URL path (existing).
1025
970
  * - `none`: Host preference from cookie `vmz.locale` (validated), else defaultLocale.
1026
971
  * URL never carries LocaleId.
1027
- * @param {string} pathname
1028
- * @param {string | undefined} cookieHeader
1029
972
  */
1030
973
  function resolveLocalePath(pathname, cookieHeader) {
1031
974
  const raw = String(pathname || '/');
@@ -1073,10 +1016,6 @@ function resolveLocalePath(pathname, cookieHeader) {
1073
1016
  redirectTo: null,
1074
1017
  };
1075
1018
  }
1076
- /**
1077
- * @param {string} chunkId
1078
- * @param {string} localeId
1079
- */
1080
1019
  function pageMetaAlternates(chunkId, localeId) {
1081
1020
  if (!localeArtifact?.pageMetas)
1082
1021
  return [];
@@ -1084,13 +1023,11 @@ function pageMetaAlternates(chunkId, localeId) {
1084
1023
  localeArtifact.pageMetas.find((m) => m.routeId === chunkId && m.locale === localeArtifact.defaultLocale);
1085
1024
  return Array.isArray(meta?.alternates) ? meta.alternates : [];
1086
1025
  }
1087
- /** @param {string} href */
1088
1026
  function bustUrl(href) {
1089
1027
  const u = new URL(href);
1090
1028
  u.searchParams.set('t', String(reloadToken));
1091
1029
  return u.href;
1092
1030
  }
1093
- /** @param {string} chunkId */
1094
1031
  async function loadPageCtor(chunkId) {
1095
1032
  const pageRel = `${chunkId}.client.js`;
1096
1033
  const href = bustUrl(pathToFileURL(path.join(distDir, pageRel)).href);
@@ -1101,7 +1038,6 @@ async function loadPageCtor(chunkId) {
1101
1038
  /**
1102
1039
  * Load compiled page catalog from `_vmz/route-catalog.json` (0.1.30 authority).
1103
1040
  * Hosts must not re-parse deployment pathPattern into a live catalog.
1104
- * @param {string} dir
1105
1041
  */
1106
1042
  async function listPageClientFiles(dir) {
1107
1043
  const fromCatalog = await listPagesFromRouteCatalog(dir);
@@ -1114,11 +1050,7 @@ async function listPageClientFiles(dir) {
1114
1050
  }
1115
1051
  return fromCatalog;
1116
1052
  }
1117
- /**
1118
- * @param {string} dir
1119
- */
1120
1053
  async function listPagesFromRouteCatalog(dir) {
1121
- /** @type {Array<{ chunkId: string, pageRel: string, routeId?: string, pathPattern?: string, segs: ReturnType<typeof parsePathPattern> }>} */
1122
1054
  const out = [];
1123
1055
  try {
1124
1056
  const raw = await readFile(path.join(dir, ...ROUTE_CATALOG_REL.split('/')), 'utf8');
@@ -1150,14 +1082,12 @@ async function listPagesFromRouteCatalog(dir) {
1150
1082
  }
1151
1083
  /**
1152
1084
  * Browser HTTP pattern (`/` / `/home` / `/users/:id` / `/blog/[...slug]`).
1153
- * @param {string} pattern
1154
1085
  */
1155
1086
  function parsePathPattern(pattern) {
1156
1087
  const raw = String(pattern || '').trim();
1157
1088
  if (!raw || raw === '/')
1158
1089
  return [];
1159
1090
  const parts = raw.replace(/^\/+/, '').split('/').filter(Boolean);
1160
- /** @type {Array<{ kind: 'static' | 'param' | 'catch', value?: string, name?: string }>} */
1161
1091
  const segs = [];
1162
1092
  for (const p of parts) {
1163
1093
  if (isRouteGroupDir(p))
@@ -1166,9 +1096,6 @@ function parsePathPattern(pattern) {
1166
1096
  }
1167
1097
  return segs;
1168
1098
  }
1169
- /**
1170
- * @param {string} p
1171
- */
1172
1099
  function parsePathSegment(p) {
1173
1100
  const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(p);
1174
1101
  const param = /^\[([^\]]+)\]$/.exec(p);
@@ -1187,10 +1114,6 @@ function isRouteGroupDir(seg) {
1187
1114
  function isRouteBoundaryStem(stem) {
1188
1115
  return stem === 'Layout' || stem === 'Loading' || stem === 'Error' || stem === 'NotFound';
1189
1116
  }
1190
- /**
1191
- * @param {string} pathname
1192
- * @param {typeof pageCatalog} catalog
1193
- */
1194
1117
  function matchFileRoute(pathname, catalog) {
1195
1118
  const pathParts = decodeURIComponent(pathname.split('?')[0] || '/')
1196
1119
  .replace(/\/+$/, '')
@@ -1210,17 +1133,11 @@ function matchFileRoute(pathname, catalog) {
1210
1133
  }
1211
1134
  return best;
1212
1135
  }
1213
- /**
1214
- * @param {ReturnType<typeof parsePathPattern>} segs
1215
- * @param {string} pathname
1216
- * @returns {Record<string, string>}
1217
- */
1218
1136
  function extractRouteParams(segs, pathname) {
1219
1137
  const pathParts = decodeURIComponent(pathname.split('?')[0] || '/')
1220
1138
  .replace(/\/+$/, '')
1221
1139
  .split('/')
1222
1140
  .filter(Boolean);
1223
- /** @type {Record<string, string>} */
1224
1141
  const params = {};
1225
1142
  let j = 0;
1226
1143
  for (let i = 0; i < segs.length; i++) {
@@ -1239,11 +1156,6 @@ function extractRouteParams(segs, pathname) {
1239
1156
  }
1240
1157
  return params;
1241
1158
  }
1242
- /**
1243
- * @param {ReturnType<typeof parsePathPattern>} segs
1244
- * @param {string[]} pathParts
1245
- * @returns {number | null}
1246
- */
1247
1159
  function scoreRoute(segs, pathParts) {
1248
1160
  let i = 0;
1249
1161
  let j = 0;
@@ -1251,7 +1163,7 @@ function scoreRoute(segs, pathParts) {
1251
1163
  while (i < segs.length) {
1252
1164
  const s = segs[i];
1253
1165
  if (s.kind === 'catch') {
1254
- // Required catch-all `[...slug]` needs ≥1 remaining segment (not `/`).
1166
+ // Required catch-all `[...slug]` needs �? remaining segment (not `/`).
1255
1167
  if (j >= pathParts.length)
1256
1168
  return null;
1257
1169
  score += 1;
@@ -1281,9 +1193,7 @@ function findRootCatchAll(catalog) {
1281
1193
  return catalog.find((p) => isRootCatchAll(p)) || null;
1282
1194
  }
1283
1195
  /**
1284
- * Optional app gate: `dist/vmz-route-gate.mjs` → `{ check(pathname, chunkId) => 'not_found' | null }`.
1285
- * @param {string} pathname
1286
- * @param {string | undefined} chunkId
1196
+ * Optional app gate: `dist/vmz-route-gate.mjs` �?`{ check(pathname, chunkId) => 'not_found' | null }`.
1287
1197
  */
1288
1198
  async function runRouteGate(pathname, chunkId) {
1289
1199
  try {
@@ -1297,29 +1207,23 @@ async function runRouteGate(pathname, chunkId) {
1297
1207
  return null;
1298
1208
  }
1299
1209
  }
1300
- /**
1301
- * @param {Array<{ name: string, entry: string }>} eager
1302
- * @param {Array<{ name: string, entry: string }>} lazy
1303
- * @param {number} token
1304
- */
1305
1210
  function emitEntryClient(eager, lazy, token) {
1306
1211
  const q = `?t=${token}`;
1307
1212
  const native = loadNativeAddon();
1308
1213
  if (typeof native.generateServeEntryClient !== 'function') {
1309
- throw new Error('vmz native addon missing generateServeEntryClient — rebuild with `pnpm napi:build`');
1214
+ throw new Error('vmz native addon missing generateServeEntryClient �?rebuild with `pnpm napi:build`');
1310
1215
  }
1311
1216
  return native.generateServeEntryClient(eager, lazy, q);
1312
1217
  }
1313
1218
  /**
1314
1219
  * EventEntry zero-framework bootstrap: no static import of vmz-dom / page / islands.
1315
1220
  * Framework bytes load only inside the first matching DOM event handler.
1316
- * @param {number} token
1317
1221
  */
1318
1222
  function emitEntryEvent(token) {
1319
1223
  const q = `?t=${token}`;
1320
1224
  const native = loadNativeAddon();
1321
1225
  if (typeof native.generateServeEntryEvent !== 'function') {
1322
- throw new Error('vmz native addon missing generateServeEntryEvent — rebuild with `pnpm napi:build`');
1226
+ throw new Error('vmz native addon missing generateServeEntryEvent �?rebuild with `pnpm napi:build`');
1323
1227
  }
1324
1228
  return native.generateServeEntryEvent(q);
1325
1229
  }
@@ -1327,11 +1231,9 @@ function emitEntryEvent(token) {
1327
1231
  * Style Theme cookie / localStorage key (host contract, not a second theme API).
1328
1232
  */
1329
1233
  const THEME_STORE_KEY = 'vmz-theme';
1330
- /** Host preference key for `routing.strategy: 'none'` (cookie + localStorage). */
1331
1234
  const LOCALE_STORE_KEY = 'vmz.locale';
1332
1235
  /**
1333
1236
  * Cache-bust stylesheet entry for dev reload (token + serve revision).
1334
- * @param {string | null | undefined} entry
1335
1237
  */
1336
1238
  function cssEntryWithBust(entry) {
1337
1239
  if (!entry)
@@ -1343,10 +1245,6 @@ function cssEntryWithBust(entry) {
1343
1245
  params.set('h', styleBundleHash);
1344
1246
  return `${base}?${params.toString()}`;
1345
1247
  }
1346
- /**
1347
- * @param {string} dir
1348
- * @returns {Promise<{ cssEntry: string|null, styleTheme: typeof styleTheme, styleBundleHash: string|null }>}
1349
- */
1350
1248
  async function loadDeploymentStyle(dir) {
1351
1249
  try {
1352
1250
  const raw = await readFile(path.join(dir, 'vmz-deployment.json'), 'utf8');
@@ -1374,11 +1272,8 @@ async function loadDeploymentStyle(dir) {
1374
1272
  }
1375
1273
  }
1376
1274
  /**
1377
- * Priority: `?theme=` → cookie → none (CSS `:root` + prefers-color-scheme media).
1275
+ * Priority: `?theme=` �?cookie �?none (CSS `:root` + prefers-color-scheme media).
1378
1276
  * Explicit ids (including default) always win over OS preference via activation attr.
1379
- * @param {URLSearchParams | undefined} searchParams
1380
- * @param {string | undefined} cookieHeader
1381
- * @returns {string|null}
1382
1277
  */
1383
1278
  function resolveThemeId(searchParams, cookieHeader) {
1384
1279
  if (!styleTheme)
@@ -1394,8 +1289,6 @@ function resolveThemeId(searchParams, cookieHeader) {
1394
1289
  }
1395
1290
  /**
1396
1291
  * Always emit activation attr for an explicit theme id (incl. default) so it overrides OS media.
1397
- * @param {string|null} themeId
1398
- * @returns {[string, string] | []} flattened attr pair for generatePageShell
1399
1292
  */
1400
1293
  function htmlThemeAttrPair(themeId) {
1401
1294
  if (!styleTheme || !themeId)
@@ -1407,7 +1300,7 @@ function htmlThemeAttrPair(themeId) {
1407
1300
  }
1408
1301
  /**
1409
1302
  * Inline boot when SSR had no query/cookie: apply explicit `localStorage` only.
1410
- * No stored choice → leave bare `<html>` so CSS `@media (prefers-color-scheme)` follows OS live.
1303
+ * No stored choice �?leave bare `<html>` so CSS `@media (prefers-color-scheme)` follows OS live.
1411
1304
  * Explicit ids (incl. default) always set the activation attr so they override OS media.
1412
1305
  */
1413
1306
  function themeBootstrapScript() {
@@ -1421,7 +1314,7 @@ function themeBootstrapScript() {
1421
1314
  /**
1422
1315
  * LocaleId as client state (routing.strategy = none): apply localStorage before any
1423
1316
  * page/client module runs so `#locales/*` pick the right variant. Prefix strategy
1424
- * keeps LocaleId in the URL — no boot rewrite.
1317
+ * keeps LocaleId in the URL �?no boot rewrite.
1425
1318
  * Also mirrors into cookie so the next SSR negotiate sees Host preference.
1426
1319
  */
1427
1320
  function localeBootstrapScript() {
@@ -1438,8 +1331,8 @@ function localeBootstrapScript() {
1438
1331
  return ` <script>(function(){try{var k=${key},ids=${idList};var id=localStorage.getItem(k);if(!id||ids.indexOf(id)<0)return;document.documentElement.setAttribute("data-locale",id);document.documentElement.setAttribute("lang",id);window.__vmzLocaleIdHint=id;document.cookie=k+"="+encodeURIComponent(id)+"; path=/; max-age=31536000; SameSite=Lax";}catch(e){}})();</script>\n`;
1439
1332
  }
1440
1333
  /**
1441
- * Site favicon links from build artifact `_vmz/site-favicon.json` (author SVG → PNG/ICO).
1442
- * Empty when skipped / missing — do not invent broken <link>s.
1334
+ * Site favicon links from build artifact `_vmz/site-favicon.json` (author SVG �?PNG/ICO).
1335
+ * Empty when skipped / missing �?do not invent broken <link>s.
1443
1336
  */
1444
1337
  function siteFaviconHeadHtml() {
1445
1338
  try {
@@ -1457,10 +1350,6 @@ function siteFaviconHeadHtml() {
1457
1350
  return '';
1458
1351
  }
1459
1352
  }
1460
- /**
1461
- * @param {string|undefined} header
1462
- * @param {string} name
1463
- */
1464
1353
  function readCookie(header, name) {
1465
1354
  if (!header)
1466
1355
  return null;
@@ -1481,19 +1370,10 @@ function readCookie(header, name) {
1481
1370
  }
1482
1371
  return null;
1483
1372
  }
1484
- /**
1485
- * @param {string} dir
1486
- * @returns {Promise<string|null>}
1487
- */
1488
1373
  async function loadCssEntry(dir) {
1489
1374
  const meta = await loadDeploymentStyle(dir);
1490
1375
  return meta.cssEntry;
1491
1376
  }
1492
- /**
1493
- * @param {string} dir
1494
- * @param {string} chunkId
1495
- * @returns {Promise<Array<{ component: string, strategy: string }>>}
1496
- */
1497
1377
  async function loadPageResumeEntries(dir, chunkId) {
1498
1378
  try {
1499
1379
  const raw = await readFile(path.join(dir, 'vmz-deployment.json'), 'utf8');
@@ -1510,17 +1390,14 @@ async function loadPageResumeEntries(dir, chunkId) {
1510
1390
  return [];
1511
1391
  }
1512
1392
  }
1513
- /** @param {string} strategy */
1514
1393
  function isEventStrategy(strategy) {
1515
1394
  return strategy === 'event' || strategy === 'click' || strategy.startsWith('event:');
1516
1395
  }
1517
- /** @param {string[]} strategies */
1518
1396
  function isEventOnlyShell(strategies) {
1519
1397
  if (!strategies.length)
1520
1398
  return false;
1521
1399
  return strategies.every((s) => isEventStrategy(s));
1522
1400
  }
1523
- /** @param {string} s */
1524
1401
  function escapeAttr(s) {
1525
1402
  return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
1526
1403
  }