@vmz/core 0.1.31 → 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 (63) 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/browser/dom-core.d.ts +246 -0
  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} +47 -185
  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} +162 -219
  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.d.ts +0 -402
  49. package/dist/dom-core.js +0 -4813
  50. package/dist/dom.client.d.ts +0 -4
  51. package/dist/dom.client.js +0 -5
  52. package/dist/dom.d.ts +0 -6
  53. package/dist/dom.js +0 -7
  54. package/dist/list-client-components.d.ts +0 -62
  55. package/dist/native-addon.d.ts +0 -12
  56. package/dist/render-host.d.ts +0 -32
  57. package/dist/unknown-component.d.ts +0 -40
  58. /package/dist/{position-context.d.ts → browser/position-context.d.ts} +0 -0
  59. /package/dist/{index.d.ts → faces/index.d.ts} +0 -0
  60. /package/dist/{index.js → faces/index.js} +0 -0
  61. /package/dist/{localize-body-links.d.ts → host/localize-body-links.d.ts} +0 -0
  62. /package/dist/{route-layout-chain.d.ts → host/route-layout-chain.d.ts} +0 -0
  63. /package/dist/{route-layout-chain.js → host/route-layout-chain.js} +0 -0
@@ -1,63 +1,36 @@
1
- // @ts-nocheck
2
1
  /**
3
2
  * Minimal VMZ runtime — `#server` invoke + RPC/REST HTTP + optional static/SSR.
4
3
  *
5
- *
6
4
  * Browser-safe: no static `node:*` imports. Node builtins are loaded only inside
7
5
  * Node/SSR request handlers so client bundles can `import { callServer }`.
8
6
  */
9
- /** @typedef {{ moduleId: string, method: string, args?: unknown[] }} RpcRequest */
10
- /** @typedef {{ verb: string, path: string, moduleId: string, method: string, className?: string }} Route */
11
- /**
12
- * @typedef {{
13
- * distDir?: string,
14
- * renderIndex?: => Promise<string> | string,
15
- * renderIndexStream?: (opts?: { signal?: AbortSignal }) => AsyncIterable<string>,
16
- * renderPage?: (pathname: string) => Promise<string | null> | string | null,
17
- * renderPageStream?: (pathname: string, opts?: { signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string, method?: string, body?: unknown }) => Promise<AsyncIterable<string> | { status?: number, stream?: AsyncIterable<string>, redirect?: string, headers?: Record<string, string> } | null> | AsyncIterable<string> | null,
18
- * req?: import('node:http').IncomingMessage,
19
- * }} NodeRequestOptions
20
- */
21
7
  const DEFAULT_RPC_PATH = '/__vmz/rpc';
22
- /** @type {((id: string) => string | URL) | null} */
23
8
  let resolveServerModule = null;
24
- /** @type {Route[]} */
25
9
  let routes = [];
26
10
  /**
27
- * Map `#server/foo` → filesystem / URL the host can `import`.
28
- * Only set this in Node/SSR hosts — browser bundles must omit it so RPC goes HTTP.
11
+ * Map `#server/foo` �?filesystem / URL the host can `import`.
12
+ * Only set this in Node/SSR hosts �?browser bundles must omit it so RPC goes HTTP.
29
13
  */
30
14
  export function setServerModuleResolver(fn) {
31
15
  resolveServerModule = fn;
32
16
  }
33
- /** @param {Route[]} next */
34
17
  export function setRoutes(next) {
35
18
  routes = Array.isArray(next) ? next : [];
36
19
  }
37
- /**
38
- * @param {string} moduleId
39
- * @param {string} method
40
- * @param {unknown[]} args
41
- */
42
20
  export async function callServer(moduleId, method, args = []) {
43
21
  // Browser (or tests forcing HTTP): never touch server modules in-process.
44
- // Node SSR / smokes set a resolver and leave __VMZ_USE_HTTP_RPC unset → local.
22
+ // Node SSR / smokes set a resolver and leave __VMZ_USE_HTTP_RPC unset �?local.
45
23
  if (globalThis.__VMZ_USE_HTTP_RPC || !resolveServerModule) {
46
24
  return callServerHttp(moduleId, method, args);
47
25
  }
48
26
  return callServerLocal(moduleId, method, args);
49
27
  }
50
- /**
51
- * @param {string} moduleId
52
- * @param {string} method
53
- * @param {unknown[]} args
54
- */
55
28
  async function callServerLocal(moduleId, method, args) {
56
29
  if (!resolveServerModule) {
57
30
  throw new Error(`vmz:runtime callServer(${moduleId}): setServerModuleResolver() required in Node`);
58
31
  }
59
32
  const spec = resolveServerModule(moduleId);
60
- const mod = await import(spec);
33
+ const mod = await import(typeof spec === 'string' ? spec : spec.href);
61
34
  const Ctor = mod.default ?? mod[exportGuess(moduleId)];
62
35
  if (typeof Ctor !== 'function') {
63
36
  throw new Error(`vmz:runtime: no class export for ${moduleId}`);
@@ -69,11 +42,6 @@ async function callServerLocal(moduleId, method, args) {
69
42
  }
70
43
  return fn.apply(instance, args);
71
44
  }
72
- /**
73
- * @param {string} moduleId
74
- * @param {string} method
75
- * @param {unknown[]} args
76
- */
77
45
  async function callServerHttp(moduleId, method, args) {
78
46
  let rpcPath = (typeof globalThis !== 'undefined' && globalThis.__VMZ_RPC_PATH) || DEFAULT_RPC_PATH;
79
47
  // Node undici `fetch` rejects relative URLs; browsers accept path-only.
@@ -104,17 +72,11 @@ function exportGuess(moduleId) {
104
72
  const base = moduleId.split('/').pop() || 'Server';
105
73
  return `${base}Server`;
106
74
  }
107
- /**
108
- * @param {RpcRequest} body
109
- */
110
75
  export async function handleRpc(body) {
111
76
  return callServerLocal(body.moduleId, body.method, body.args ?? []);
112
77
  }
113
78
  /**
114
79
  * Match REST route from `vmz-routes.json`.
115
- * @param {string} verb
116
- * @param {string} pathname
117
- * @returns {Route | null}
118
80
  */
119
81
  export function matchRoute(verb, pathname) {
120
82
  const v = verb.toUpperCase();
@@ -122,13 +84,10 @@ export function matchRoute(verb, pathname) {
122
84
  }
123
85
  /**
124
86
  * Build `callServerLocal` args from a Fetch Request for REST routes.
125
- * GET/HEAD → `[]`. JSON POST/PUT/PATCH → `[body]`. form-urlencoded → `[record]`.
126
- * Multipart → `[record]` where File/Blob parts stay as File/Blob (tool-site binary upload).
87
+ * GET/HEAD �?`[]`. JSON POST/PUT/PATCH �?`[body]`. form-urlencoded �?`[record]`.
88
+ * Multipart �?`[record]` where File/Blob parts stay as File/Blob (tool-site binary upload).
127
89
  * Octet-stream PUT also forwards Upload resumable chunk headers (upload-id / chunk-index / chunk-total).
128
90
  * Extra args are ignored by zero-parameter server methods (JS).
129
- * @param {Request} request
130
- * @param {string} verb
131
- * @returns {Promise<unknown[]>}
132
91
  */
133
92
  async function routeArgsFromRequest(request, verb) {
134
93
  const v = String(verb || 'GET').toUpperCase();
@@ -147,12 +106,12 @@ async function routeArgsFromRequest(request, verb) {
147
106
  }
148
107
  }
149
108
  if (ctype.includes('multipart/form-data')) {
150
- // Parse from raw bytes — undici Request.formData() can UTF-8-mangle high bytes in file parts
151
- // (0xFF/0xFE → U+FFFD), which breaks Upload binary / tool-site intakes.
109
+ // Parse from raw bytes �?undici Request.formData() can UTF-8-mangle high bytes in file parts
110
+ // (0xFF/0xFE �?U+FFFD), which breaks Upload binary / tool-site intakes.
152
111
  const buf = Buffer.from(await request.arrayBuffer());
153
112
  return [parseMultipartBuffer(buf, ctype)];
154
113
  }
155
- // Object-store PUT / resumable chunk PUT — keep bytes (never request.text()).
114
+ // Object-store PUT / resumable chunk PUT �?keep bytes (never request.text()).
156
115
  if (ctype.includes('application/octet-stream') ||
157
116
  ((v === 'PUT' || v === 'PATCH') && !ctype.includes('json') && !ctype.includes('x-www-form-urlencoded'))) {
158
117
  const buf = Buffer.from(await request.arrayBuffer());
@@ -180,8 +139,6 @@ async function routeArgsFromRequest(request, verb) {
180
139
  /**
181
140
  * Web Standards Fetch entry for ServerArtifact hosts (Node adapter, worker/edge parity).
182
141
  * Handles RPC + public ServerRoute only; static/SSR stay on Node host options.
183
- * @param {Request} request
184
- * @returns {Promise<Response>}
185
142
  */
186
143
  export async function handleFetchRequest(request) {
187
144
  const url = new URL(request.url);
@@ -209,9 +166,6 @@ export async function handleFetchRequest(request) {
209
166
  /**
210
167
  * Node `http.createServer` listener: RPC + REST + optional static / SSR index.
211
168
  * RPC/REST go through {@link handleFetchRequest} so Node and Fetch hosts share one core.
212
- * @param {import('node:http').IncomingMessage} req
213
- * @param {import('node:http').ServerResponse} res
214
- * @param {NodeRequestOptions} [opts]
215
169
  */
216
170
  export async function handleNodeRequest(req, res, opts = {}) {
217
171
  const host = req.headers.host || '127.0.0.1';
@@ -225,9 +179,8 @@ export async function handleNodeRequest(req, res, opts = {}) {
225
179
  const response = await handleFetchRequest(request);
226
180
  return await writeFetchResponse(res, response);
227
181
  }
228
- // Static first for assets + DocumentMount (`/d/…`) so docs aren't swallowed by SSR 404 shells.
229
- // static route HTML (`index.html`, `about/index.html`, …) is a CDN/deploy projection only —
230
- // when Server Host SSR is active, those files must not shadow live render (local/dev ≡ SSR truth).
182
+ // Static first for assets + DocumentMount (`/d/�`) so docs aren't swallowed by SSR 404 shells.
183
+ // static route HTML (`index.html`, `about/index.html`, �? is a CDN/deploy projection only �? // when Server Host SSR is active, those files must not shadow live render (local/dev �?SSR truth).
231
184
  if (verb === 'GET' && opts.distDir) {
232
185
  const nodePath = await import('node:path');
233
186
  const { readFile, stat } = await import('node:fs/promises');
@@ -263,7 +216,7 @@ export async function handleNodeRequest(req, res, opts = {}) {
263
216
  /* ignore */
264
217
  }
265
218
  };
266
- // Do not abort on IncomingMessage `close` — that fires after a POST body is
219
+ // Do not abort on IncomingMessage `close` �?that fires after a POST body is
267
220
  // fully read (normal), which would cancel SSR before the first chunk.
268
221
  req.on('aborted', onClientGone);
269
222
  res.on('close', () => {
@@ -272,7 +225,6 @@ export async function handleNodeRequest(req, res, opts = {}) {
272
225
  });
273
226
  try {
274
227
  if (typeof opts.renderPageStream === 'function') {
275
- /** @type {unknown} */
276
228
  let body;
277
229
  if (verb === 'POST') {
278
230
  const ctype = String(req.headers['content-type'] || '');
@@ -329,7 +281,7 @@ export async function handleNodeRequest(req, res, opts = {}) {
329
281
  }
330
282
  if (!res.headersSent) {
331
283
  if (wantsBrowserHtml(req)) {
332
- sendHtml(res, 404, browserErrorHtml(404, 'Not Found', `No page matched ${url.pathname}`));
284
+ sendHtml(res, 404, browserErrorHtml(404, 'Not Found', `No page matched ${url.pathname}`, undefined));
333
285
  }
334
286
  else {
335
287
  sendJson(res, 404, { error: 'not found', path: url.pathname });
@@ -357,14 +309,8 @@ export async function handleNodeRequest(req, res, opts = {}) {
357
309
  }
358
310
  }
359
311
  }
360
- /**
361
- * @param {import('node:http').IncomingMessage} req
362
- * @param {URL} url
363
- * @returns {Promise<Request>}
364
- */
365
312
  async function incomingToRequest(req, url) {
366
313
  const method = (req.method || 'GET').toUpperCase();
367
- /** @type {HeadersInit} */
368
314
  const headers = {};
369
315
  for (const [k, v] of Object.entries(req.headers)) {
370
316
  if (v == null)
@@ -378,10 +324,6 @@ async function incomingToRequest(req, url) {
378
324
  // Node undici requires duplex when constructing Request with a body.
379
325
  return new Request(url, { method, headers, body: raw, duplex: 'half' });
380
326
  }
381
- /**
382
- * @param {import('node:http').ServerResponse} res
383
- * @param {Response} response
384
- */
385
327
  async function writeFetchResponse(res, response) {
386
328
  const headers = {};
387
329
  response.headers.forEach((value, key) => {
@@ -396,10 +338,6 @@ async function writeFetchResponse(res, response) {
396
338
  }
397
339
  /**
398
340
  * Resolve a path under distDir; reject `..` escapes.
399
- * @param {string} distDir
400
- * @param {string} pathname
401
- * @param {typeof import('node:path')} nodePath
402
- * @returns {string | null}
403
341
  */
404
342
  function safeDistFile(distDir, pathname, nodePath) {
405
343
  let rel = decodeURIComponent(pathname.split('?')[0] || '/');
@@ -417,10 +355,7 @@ function safeDistFile(distDir, pathname, nodePath) {
417
355
  }
418
356
  /**
419
357
  * static profile emits per-route HTML beside client assets. That HTML is for CDN / local-static
420
- * delivery hosts — not for Server Host when SSR is available. DocumentMount stays static.
421
- * @param {string} file
422
- * @param {string} pathname
423
- * @param {typeof import('node:path')} nodePath
358
+ * delivery hosts �?not for Server Host when SSR is available. DocumentMount stays static.
424
359
  */
425
360
  function isWebStaticHtmlShadow(file, pathname, nodePath) {
426
361
  const ext = nodePath.extname(file).toLowerCase();
@@ -429,21 +364,16 @@ function isWebStaticHtmlShadow(file, pathname, nodePath) {
429
364
  let rel = decodeURIComponent(String(pathname || '').split('?')[0] || '/');
430
365
  if (!rel.startsWith('/'))
431
366
  rel = `/${rel}`;
432
- // Integrated DocumentMount — keep static-first (see resolveDistStatic comment).
367
+ // Integrated DocumentMount �?keep static-first (see resolveDistStatic comment).
433
368
  if (rel === '/d' || rel.startsWith('/d/'))
434
369
  return false;
435
370
  return true;
436
371
  }
437
372
  /**
438
373
  * Static resolve for app assets + integrated document mounts:
439
- * `/d/` → `d/index.html`, `/d/zh-hans/guide` → `d/zh-hans/guide.html`.
440
- * @param {string} distDir
441
- * @param {string} pathname
442
- * @param {typeof import('node:path')} nodePath
443
- * @param {typeof import('node:fs/promises').stat} stat
444
- * @returns {Promise<string | null>}
374
+ * `/d/` �?`d/index.html`, `/d/zh-hans/guide` �?`d/zh-hans/guide.html`.
445
375
  */
446
- async function resolveDistStatic(distDir, pathname, nodePath, stat) {
376
+ async function resolveDistStatic(distDir, pathname, nodePath, stat, _opts = {}) {
447
377
  const candidates = staticPathCandidates(pathname);
448
378
  for (const candidate of candidates) {
449
379
  const full = safeDistFile(distDir, candidate, nodePath);
@@ -466,15 +396,10 @@ async function resolveDistStatic(distDir, pathname, nodePath, stat) {
466
396
  }
467
397
  return null;
468
398
  }
469
- /**
470
- * @param {string} pathname
471
- * @returns {string[]}
472
- */
473
399
  function staticPathCandidates(pathname) {
474
400
  let rel = decodeURIComponent(pathname.split('?')[0] || '/');
475
401
  if (!rel.startsWith('/'))
476
402
  rel = `/${rel}`;
477
- /** @type {string[]} */
478
403
  const out = [];
479
404
  const push = (p) => {
480
405
  if (p && !out.includes(p))
@@ -498,16 +423,11 @@ function staticPathCandidates(pathname) {
498
423
  }
499
424
  return out;
500
425
  }
501
- /** @param {string} p */
502
426
  function nodePathExt(p) {
503
427
  const base = p.split('/').pop() || '';
504
428
  const i = base.lastIndexOf('.');
505
429
  return i > 0 ? base.slice(i) : '';
506
430
  }
507
- /**
508
- * @param {string} filePath
509
- * @param {typeof import('node:path')} nodePath
510
- */
511
431
  function contentType(filePath, nodePath) {
512
432
  const ext = nodePath.extname(filePath).toLowerCase();
513
433
  switch (ext) {
@@ -528,9 +448,6 @@ function contentType(filePath, nodePath) {
528
448
  return 'application/octet-stream';
529
449
  }
530
450
  }
531
- /**
532
- * @param {import('node:http').IncomingMessage} req
533
- */
534
451
  function readJson(req) {
535
452
  return new Promise((resolve, reject) => {
536
453
  const chunks = [];
@@ -547,24 +464,17 @@ function readJson(req) {
547
464
  req.on('error', reject);
548
465
  });
549
466
  }
550
- /**
551
- * @param {import('node:http').IncomingMessage} req
552
- * @returns {Promise<Buffer>}
553
- */
554
467
  function readRawBody(req) {
555
468
  return new Promise((resolve, reject) => {
556
469
  const chunks = [];
557
470
  req.on('data', (c) => chunks.push(c));
558
- // Buffer — never utf8-string: multipart File bytes must survive (Upload binary gate).
471
+ // Buffer �?never utf8-string: multipart File bytes must survive (Upload binary gate).
559
472
  req.on('end', () => resolve(Buffer.concat(chunks)));
560
473
  req.on('error', reject);
561
474
  });
562
475
  }
563
476
  /**
564
477
  * Buffer-safe multipart/form-data parser (file parts stay binary).
565
- * @param {Buffer} buf
566
- * @param {string} contentType
567
- * @returns {Record<string, unknown>}
568
478
  */
569
479
  function parseMultipartBuffer(buf, contentType) {
570
480
  const bm = /boundary=(?:"([^"]+)"|([^;\s]+))/i.exec(String(contentType || ''));
@@ -573,7 +483,6 @@ function parseMultipartBuffer(buf, contentType) {
573
483
  throw new Error('multipart: missing boundary');
574
484
  }
575
485
  const sep = Buffer.from(`--${boundary}`);
576
- /** @type {Record<string, unknown>} */
577
486
  const out = {};
578
487
  let start = indexOfBuffer(buf, sep, 0);
579
488
  if (start < 0)
@@ -604,7 +513,7 @@ function parseMultipartBuffer(buf, contentType) {
604
513
  if (fileM) {
605
514
  const filename = fileM[1] || 'upload.bin';
606
515
  const type = typeM ? String(typeM[1]).trim() : 'application/octet-stream';
607
- // Copy body — File may outlive the request buffer.
516
+ // Copy body �?File may outlive the request buffer.
608
517
  const copy = Buffer.from(body);
609
518
  const file = new File([copy], filename, { type });
610
519
  const prev = out[key];
@@ -629,11 +538,6 @@ function parseMultipartBuffer(buf, contentType) {
629
538
  }
630
539
  return out;
631
540
  }
632
- /**
633
- * @param {Buffer} hay
634
- * @param {Buffer} needle
635
- * @param {number} from
636
- */
637
541
  function indexOfBuffer(hay, needle, from) {
638
542
  if (!needle.length)
639
543
  return from;
@@ -646,11 +550,6 @@ function indexOfBuffer(hay, needle, from) {
646
550
  }
647
551
  return -1;
648
552
  }
649
- /**
650
- * @param {string} raw
651
- * @param {string} contentType
652
- * @returns {Record<string, string> | string}
653
- */
654
553
  function parseFormBody(raw, contentType) {
655
554
  if (!raw)
656
555
  return {};
@@ -667,10 +566,6 @@ function parseFormBody(raw, contentType) {
667
566
  return { raw };
668
567
  }
669
568
  }
670
- /**
671
- * @param {import('node:http').IncomingMessage} req
672
- * @returns {boolean}
673
- */
674
569
  function wantsBrowserHtml(req) {
675
570
  const accept = String(req.headers?.accept || '');
676
571
  const method = String(req.method || 'GET').toUpperCase();
@@ -697,11 +592,7 @@ function wantsBrowserHtml(req) {
697
592
  }
698
593
  /**
699
594
  * Browser-facing error document (no N-API). Used when SSR returns null or throws
700
- * on a document navigation — must not fall back to API-shaped JSON.
701
- * @param {number} status
702
- * @param {string} title
703
- * @param {string} message
704
- * @param {string} [stack]
595
+ * on a document navigation �?must not fall back to API-shaped JSON.
705
596
  */
706
597
  function browserErrorHtml(status, title, message, stack) {
707
598
  const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
@@ -728,17 +619,12 @@ function browserErrorHtml(status, title, message, stack) {
728
619
  <h1>${esc(title)}</h1>
729
620
  <pre>${esc(message)}</pre>
730
621
  ${stackBlock}
731
- <p class="hint">Document navigation returns HTML errors — not API JSON.</p>
622
+ <p class="hint">Document navigation returns HTML errors �?not API JSON.</p>
732
623
  </main>
733
624
  </body>
734
625
  </html>
735
626
  `;
736
627
  }
737
- /**
738
- * @param {import('node:http').ServerResponse} res
739
- * @param {number} status
740
- * @param {unknown} body
741
- */
742
628
  function sendJson(res, status, body) {
743
629
  const payload = JSON.stringify(body);
744
630
  res.writeHead(status, {
@@ -747,11 +633,6 @@ function sendJson(res, status, body) {
747
633
  });
748
634
  res.end(payload);
749
635
  }
750
- /**
751
- * @param {import('node:http').ServerResponse} res
752
- * @param {number} status
753
- * @param {string} html
754
- */
755
636
  function sendHtml(res, status, html) {
756
637
  const payload = typeof html === 'string' ? html : String(html);
757
638
  res.writeHead(status, {
@@ -763,10 +644,6 @@ function sendHtml(res, status, html) {
763
644
  /**
764
645
  * Stream HTML without buffering the full document (event-flow / stream SSR host).
765
646
  * Honors AbortSignal + response destroy for cancel; awaits drain for backpressure.
766
- * @param {import('node:http').ServerResponse} res
767
- * @param {number} status
768
- * @param {AsyncIterable<string> | Iterable<string> | AsyncGenerator<string, any, any>} source
769
- * @param {AbortSignal} [signal]
770
647
  */
771
648
  async function sendHtmlStream(res, status, source, signal) {
772
649
  const aborted = () => Boolean(signal?.aborted || res.destroyed || res.writableEnded || !res.writable);
@@ -775,8 +652,8 @@ async function sendHtmlStream(res, status, source, signal) {
775
652
  'transfer-encoding': 'chunked',
776
653
  'cache-control': 'no-cache',
777
654
  };
778
- // Defer writeHead until the first successful chunk so SSR throw → clean 500
779
- // (not headersSent + destroy → ERR_EMPTY_RESPONSE).
655
+ // Defer writeHead until the first successful chunk so SSR throw �?clean 500
656
+ // (not headersSent + destroy �?ERR_EMPTY_RESPONSE).
780
657
  let started = false;
781
658
  try {
782
659
  for await (const chunk of source) {
@@ -814,26 +691,19 @@ async function sendHtmlStream(res, status, source, signal) {
814
691
  catch (err) {
815
692
  if (aborted())
816
693
  return;
817
- // Before headers: let outer handler sendJson(500). After headers: rethrow → destroy.
694
+ // Before headers: let outer handler sendJson(500). After headers: rethrow �?destroy.
818
695
  throw err;
819
696
  }
820
697
  if (started && !res.writableEnded && !res.destroyed) {
821
698
  res.end();
822
699
  }
823
700
  }
824
- /**
825
- * @param {import('node:http').ServerResponse} res
826
- * @param {number} status
827
- * @param {Buffer} body
828
- * @param {string} type
829
- */
830
701
  function sendBytes(res, status, body, type) {
831
- /** @type {Record<string, string | number>} */
832
702
  const headers = {
833
703
  'content-type': type,
834
704
  'content-length': body.byteLength,
835
705
  };
836
- // Dev: stylesheets are rebuilt in-place — never cache @import siblings (VMZ-8).
706
+ // Dev: stylesheets are rebuilt in-place �?never cache @import siblings (VMZ-8).
837
707
  if (process.env.VMZ_DEV === '1' && typeof type === 'string' && type.startsWith('text/css')) {
838
708
  headers['cache-control'] = 'no-store';
839
709
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * App-dist alias: compiler copies dom.js → vmz-dom.js.
2
+ * App-dist alias: compiler copies dom.js �?vmz-dom.js.
3
3
  * Package-local serve-host imports this path during `@vmz/core` build.
4
4
  */
5
5
  export * from './dom.js';
@@ -1,6 +1,5 @@
1
- // @ts-nocheck
2
1
  /**
3
- * App-dist alias: compiler copies dom.js → vmz-dom.js.
2
+ * App-dist alias: compiler copies dom.js �?vmz-dom.js.
4
3
  * Package-local serve-host imports this path during `@vmz/core` build.
5
4
  */
6
5
  export * from './dom.js';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * App-dist alias: compiler copies server.js → vmz-runtime.js.
2
+ * App-dist alias: compiler copies server.js �?vmz-runtime.js.
3
3
  * Package-local serve-host imports this path during `@vmz/core` build.
4
4
  */
5
5
  export * from './server.js';
@@ -1,6 +1,5 @@
1
- // @ts-nocheck
2
1
  /**
3
- * App-dist alias: compiler copies server.js → vmz-runtime.js.
2
+ * App-dist alias: compiler copies server.js �?vmz-runtime.js.
4
3
  * Package-local serve-host imports this path during `@vmz/core` build.
5
4
  */
6
5
  export * from './server.js';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Deployment graph — component registry bootstrap (shared by all SSR/DOM hosts).
3
+ * Parse/validate and graph queries delegate to Rust vmz-artifacts via N-API.
4
+ */
5
+ import type { BootstrapComponentRegistryOpts, ComponentEntry, ComponentRegistryMap, DedupeComponentEntriesOpts, DeploymentDocument, ImportComponentEntriesOpts, LoadComponentEntriesOpts } from '../shared/host.types.js';
6
+ export declare const DEPLOYMENT_SCHEMA = "vmz.deployment.v0";
7
+ export declare function readDeploymentDocument(distDir: string, opts?: {
8
+ strict?: boolean;
9
+ }): DeploymentDocument | null;
10
+ export declare function componentEntriesFromDeployment(deployment: DeploymentDocument): ComponentEntry[];
11
+ export declare function collectDependsOnClosure(deployment: DeploymentDocument, rootChunkIds: string[]): Set<string>;
12
+ export declare function dedupeComponentEntriesByTag(entries: ComponentEntry[], opts?: DedupeComponentEntriesOpts): ComponentEntry[];
13
+ export declare function mergeExplicitComponentEntries(entries: ComponentEntry[], explicit?: Record<string, string>): ComponentEntry[];
14
+ export declare function loadComponentEntries(distDir: string, opts?: LoadComponentEntriesOpts): Promise<ComponentEntry[]>;
15
+ export declare function importAndRegisterComponentEntries(distDir: string, entries: ComponentEntry[], registerComponents: (map: ComponentRegistryMap) => void, opts?: ImportComponentEntriesOpts): Promise<ComponentRegistryMap>;
16
+ export declare function bootstrapComponentRegistry(distDir: string, registerComponents: (map: ComponentRegistryMap) => void, opts?: BootstrapComponentRegistryOpts): Promise<ComponentRegistryMap>;
@@ -1,6 +1,5 @@
1
- // @ts-nocheck
2
1
  /**
3
- * Deployment graph → component registry bootstrap (shared by all SSR/DOM hosts).
2
+ * Deployment graph — component registry bootstrap (shared by all SSR/DOM hosts).
4
3
  * Parse/validate and graph queries delegate to Rust vmz-artifacts via N-API.
5
4
  */
6
5
  import fs from 'node:fs';
@@ -8,19 +7,10 @@ import path from 'node:path';
8
7
  import { pathToFileURL } from 'node:url';
9
8
  import { requireNativeFn } from './native-addon.js';
10
9
  export const DEPLOYMENT_SCHEMA = 'vmz.deployment.v0';
11
- /**
12
- * @param {string} jsonText
13
- * @returns {any}
14
- */
15
10
  function parseDeploymentJson(jsonText) {
16
11
  requireNativeFn('deploymentValidate')(jsonText);
17
12
  return JSON.parse(jsonText);
18
13
  }
19
- /**
20
- * @param {string} distDir
21
- * @param {{ strict?: boolean }} [opts]
22
- * @returns {any | null}
23
- */
24
14
  export function readDeploymentDocument(distDir, opts = {}) {
25
15
  const strict = opts.strict === true;
26
16
  const filePath = path.join(distDir, 'vmz-deployment.json');
@@ -48,33 +38,17 @@ export function readDeploymentDocument(distDir, opts = {}) {
48
38
  return null;
49
39
  }
50
40
  }
51
- /**
52
- * @param {any} deployment
53
- * @returns {Array<{ chunkId: string, name: string, entry: string, source: string }>}
54
- */
55
41
  export function componentEntriesFromDeployment(deployment) {
56
42
  const json = JSON.stringify(deployment);
57
43
  return requireNativeFn('deploymentComponentEntries')(json);
58
44
  }
59
- /**
60
- * @param {any} deployment
61
- * @param {string[]} rootChunkIds
62
- * @returns {Set<string>}
63
- */
64
45
  export function collectDependsOnClosure(deployment, rootChunkIds) {
65
46
  const json = JSON.stringify(deployment);
66
47
  const ids = requireNativeFn('deploymentDependsOnClosure')(json, rootChunkIds);
67
48
  return new Set(ids);
68
49
  }
69
- /**
70
- * Resolve tag conflicts; strict mode throws, dev mode warns and keeps last chunkId.
71
- * @param {Array<{ chunkId: string, name: string, entry: string, source?: string }>} entries
72
- * @param {{ strict?: boolean }} [opts]
73
- * @returns {Array<{ chunkId: string, name: string, entry: string, source?: string }>}
74
- */
75
50
  export function dedupeComponentEntriesByTag(entries, opts = {}) {
76
51
  const strict = opts.strict === true;
77
- /** @type {Map<string, { chunkId: string, name: string, entry: string, source?: string }>} */
78
52
  const byTag = new Map();
79
53
  for (const e of entries) {
80
54
  const prev = byTag.get(e.name);
@@ -88,13 +62,7 @@ export function dedupeComponentEntriesByTag(entries, opts = {}) {
88
62
  }
89
63
  return [...byTag.values()].sort((a, b) => a.name.localeCompare(b.name));
90
64
  }
91
- /**
92
- * @param {Array<{ chunkId: string, name: string, entry: string, source?: string }>} entries
93
- * @param {Record<string, string> | undefined} explicit name → chunkId (no .client.js)
94
- * @returns {Array<{ chunkId: string, name: string, entry: string, source?: string }>}
95
- */
96
65
  export function mergeExplicitComponentEntries(entries, explicit) {
97
- /** @type {Map<string, { chunkId: string, name: string, entry: string, source?: string }>} */
98
66
  const byTag = new Map(entries.map((e) => [e.name, e]));
99
67
  if (explicit) {
100
68
  for (const [name, chunk] of Object.entries(explicit)) {
@@ -109,19 +77,9 @@ export function mergeExplicitComponentEntries(entries, explicit) {
109
77
  }
110
78
  return [...byTag.values()].sort((a, b) => a.name.localeCompare(b.name));
111
79
  }
112
- /**
113
- * @param {string} distDir
114
- * @param {{
115
- * strict?: boolean,
116
- * closureRoots?: string[],
117
- * explicit?: Record<string, string>,
118
- * }} [opts]
119
- * @returns {Promise<Array<{ chunkId: string, name: string, entry: string, source?: string }>>}
120
- */
121
80
  export async function loadComponentEntries(distDir, opts = {}) {
122
81
  const strict = opts.strict === true;
123
82
  const deployment = readDeploymentDocument(distDir, { strict });
124
- /** @type {Array<{ chunkId: string, name: string, entry: string, source?: string }>} */
125
83
  let entries = [];
126
84
  if (deployment) {
127
85
  entries = componentEntriesFromDeployment(deployment);
@@ -133,22 +91,10 @@ export async function loadComponentEntries(distDir, opts = {}) {
133
91
  else if (strict) {
134
92
  throw new Error(`vmz: missing vmz-deployment.json under ${distDir} (plan-only host)`);
135
93
  }
136
- // No directory scan fallback — component closure comes from Deployment Plan only.
137
94
  entries = mergeExplicitComponentEntries(entries, opts.explicit);
138
95
  return dedupeComponentEntriesByTag(entries, { strict });
139
96
  }
140
- /**
141
- * @param {string} distDir
142
- * @param {Array<{ chunkId: string, name: string, entry: string }>} entries
143
- * @param {(map: Record<string, unknown>) => void} registerComponents
144
- * @param {{
145
- * cacheBust?: string | number,
146
- * loaded?: Set<string>,
147
- * }} [opts]
148
- * @returns {Promise<Record<string, unknown>>}
149
- */
150
97
  export async function importAndRegisterComponentEntries(distDir, entries, registerComponents, opts = {}) {
151
- /** @type {Record<string, unknown>} */
152
98
  const map = {};
153
99
  const loaded = opts.loaded ?? null;
154
100
  for (const entry of entries) {
@@ -168,19 +114,6 @@ export async function importAndRegisterComponentEntries(distDir, entries, regist
168
114
  registerComponents(map);
169
115
  return map;
170
116
  }
171
- /**
172
- * Bootstrap component registry from deployment (all or closure — no directory fallback).
173
- * @param {string} distDir
174
- * @param {(map: Record<string, unknown>) => void} registerComponents
175
- * @param {{
176
- * strict?: boolean,
177
- * closureRoots?: string[],
178
- * explicit?: Record<string, string>,
179
- * cacheBust?: string | number,
180
- * loaded?: Set<string>,
181
- * preload?: 'all' | 'closure' | 'none',
182
- * }} [opts]
183
- */
184
117
  export async function bootstrapComponentRegistry(distDir, registerComponents, opts = {}) {
185
118
  const preload = opts.preload ?? (opts.closureRoots?.length ? 'closure' : 'all');
186
119
  if (preload === 'none')
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Discover compiled client component modules from Deployment Plan only.
3
+ * Shared by serve-host SSR, static emit, and test hosts (plan-only host).
4
+ */
5
+ import type { ClientComponentListEntry, ComponentRegistryMap, ListClientComponentsOpts, PreloadComponentRegistryOpts } from '../shared/host.types.js';
6
+ export { DEPLOYMENT_SCHEMA, readDeploymentDocument, componentEntriesFromDeployment, collectDependsOnClosure, dedupeComponentEntriesByTag, mergeExplicitComponentEntries, loadComponentEntries, importAndRegisterComponentEntries, bootstrapComponentRegistry, } from './deployment-registry.js';
7
+ export { createRenderHost } from './render-host.js';
8
+ export declare function listClientComponents(dir: string, opts?: ListClientComponentsOpts): Promise<ClientComponentListEntry[]>;
9
+ export declare function listClientComponentsSync(dir: string, opts?: ListClientComponentsOpts): ClientComponentListEntry[];
10
+ export declare function mergeComponentEntries(entries: ClientComponentListEntry[], explicit?: Record<string, string>): ClientComponentListEntry[];
11
+ export declare function resolveComponentEntries(distDir: string, explicit?: Record<string, string>, opts?: ListClientComponentsOpts & {
12
+ closureRoots?: string[];
13
+ }): Promise<ClientComponentListEntry[]>;
14
+ export declare function preloadComponentRegistry(distDir: string, registerComponents: (map: ComponentRegistryMap) => void, opts?: PreloadComponentRegistryOpts): Promise<ComponentRegistryMap>;