@vmz/core 0.1.32 → 0.2.1

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 (66) 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 +141 -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} +67 -220
  8. package/dist/browser/dom-core.js +2484 -0
  9. package/dist/browser/dom-core.types.d.ts +127 -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} +5 -30
  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/faces/http.d.ts +6 -0
  21. package/dist/{http.js → faces/http.js} +0 -1
  22. package/dist/faces/server.d.ts +30 -0
  23. package/dist/{server.js → faces/server.js} +27 -149
  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} +1 -17
  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/dom-attr-normalize.d.ts +8 -0
  40. package/dist/shared/dom-attr-normalize.js +71 -0
  41. package/dist/shared/host.types.d.ts +113 -0
  42. package/dist/shared/host.types.js +4 -0
  43. package/dist/shared/server.types.d.ts +67 -0
  44. package/dist/shared/server.types.js +4 -0
  45. package/dist/{dom-ssr.d.ts → ssr/dom-ssr.d.ts} +8 -37
  46. package/dist/{dom-ssr.js → ssr/dom-ssr.js} +189 -217
  47. package/package.json +34 -34
  48. package/dist/deployment-registry.d.ts +0 -70
  49. package/dist/direct-host-box.d.ts +0 -26
  50. package/dist/dom-core.js +0 -4815
  51. package/dist/dom.client.d.ts +0 -4
  52. package/dist/dom.client.js +0 -5
  53. package/dist/dom.d.ts +0 -6
  54. package/dist/dom.js +0 -7
  55. package/dist/http.d.ts +0 -6
  56. package/dist/list-client-components.d.ts +0 -62
  57. package/dist/native-addon.d.ts +0 -12
  58. package/dist/render-host.d.ts +0 -32
  59. package/dist/server.d.ts +0 -46
  60. package/dist/unknown-component.d.ts +0 -40
  61. /package/dist/{position-context.d.ts → browser/position-context.d.ts} +0 -0
  62. /package/dist/{index.d.ts → faces/index.d.ts} +0 -0
  63. /package/dist/{index.js → faces/index.js} +0 -0
  64. /package/dist/{localize-body-links.d.ts → host/localize-body-links.d.ts} +0 -0
  65. /package/dist/{route-layout-chain.d.ts → host/route-layout-chain.d.ts} +0 -0
  66. /package/dist/{route-layout-chain.js → host/route-layout-chain.js} +0 -0
@@ -1,44 +1,22 @@
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
11
  * 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.
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
22
  // Node SSR / smokes set a resolver and leave __VMZ_USE_HTTP_RPC unset → local.
@@ -47,21 +25,17 @@ export async function callServer(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);
61
- const Ctor = mod.default ?? mod[exportGuess(moduleId)];
62
- if (typeof Ctor !== 'function') {
33
+ const mod = (await import(typeof spec === 'string' ? spec : spec.href));
34
+ const exported = mod.default ?? mod[exportGuess(moduleId)];
35
+ if (typeof exported !== 'function') {
63
36
  throw new Error(`vmz:runtime: no class export for ${moduleId}`);
64
37
  }
38
+ const Ctor = exported;
65
39
  const instance = new Ctor();
66
40
  const fn = instance[method];
67
41
  if (typeof fn !== 'function') {
@@ -69,11 +43,6 @@ async function callServerLocal(moduleId, method, args) {
69
43
  }
70
44
  return fn.apply(instance, args);
71
45
  }
72
- /**
73
- * @param {string} moduleId
74
- * @param {string} method
75
- * @param {unknown[]} args
76
- */
77
46
  async function callServerHttp(moduleId, method, args) {
78
47
  let rpcPath = (typeof globalThis !== 'undefined' && globalThis.__VMZ_RPC_PATH) || DEFAULT_RPC_PATH;
79
48
  // Node undici `fetch` rejects relative URLs; browsers accept path-only.
@@ -104,17 +73,11 @@ function exportGuess(moduleId) {
104
73
  const base = moduleId.split('/').pop() || 'Server';
105
74
  return `${base}Server`;
106
75
  }
107
- /**
108
- * @param {RpcRequest} body
109
- */
110
76
  export async function handleRpc(body) {
111
77
  return callServerLocal(body.moduleId, body.method, body.args ?? []);
112
78
  }
113
79
  /**
114
80
  * Match REST route from `vmz-routes.json`.
115
- * @param {string} verb
116
- * @param {string} pathname
117
- * @returns {Route | null}
118
81
  */
119
82
  export function matchRoute(verb, pathname) {
120
83
  const v = verb.toUpperCase();
@@ -126,9 +89,6 @@ export function matchRoute(verb, pathname) {
126
89
  * Multipart → `[record]` where File/Blob parts stay as File/Blob (tool-site binary upload).
127
90
  * Octet-stream PUT also forwards Upload resumable chunk headers (upload-id / chunk-index / chunk-total).
128
91
  * Extra args are ignored by zero-parameter server methods (JS).
129
- * @param {Request} request
130
- * @param {string} verb
131
- * @returns {Promise<unknown[]>}
132
92
  */
133
93
  async function routeArgsFromRequest(request, verb) {
134
94
  const v = String(verb || 'GET').toUpperCase();
@@ -147,12 +107,12 @@ async function routeArgsFromRequest(request, verb) {
147
107
  }
148
108
  }
149
109
  if (ctype.includes('multipart/form-data')) {
150
- // Parse from raw bytes — undici Request.formData() can UTF-8-mangle high bytes in file parts
110
+ // Parse from raw bytes → undici Request.formData() can UTF-8-mangle high bytes in file parts
151
111
  // (0xFF/0xFE → U+FFFD), which breaks Upload binary / tool-site intakes.
152
112
  const buf = Buffer.from(await request.arrayBuffer());
153
113
  return [parseMultipartBuffer(buf, ctype)];
154
114
  }
155
- // Object-store PUT / resumable chunk PUT — keep bytes (never request.text()).
115
+ // Object-store PUT / resumable chunk PUT → keep bytes (never request.text()).
156
116
  if (ctype.includes('application/octet-stream') ||
157
117
  ((v === 'PUT' || v === 'PATCH') && !ctype.includes('json') && !ctype.includes('x-www-form-urlencoded'))) {
158
118
  const buf = Buffer.from(await request.arrayBuffer());
@@ -180,15 +140,13 @@ async function routeArgsFromRequest(request, verb) {
180
140
  /**
181
141
  * Web Standards Fetch entry for ServerArtifact hosts (Node adapter, worker/edge parity).
182
142
  * Handles RPC + public ServerRoute only; static/SSR stay on Node host options.
183
- * @param {Request} request
184
- * @returns {Promise<Response>}
185
143
  */
186
144
  export async function handleFetchRequest(request) {
187
145
  const url = new URL(request.url);
188
146
  const verb = (request.method || 'GET').toUpperCase();
189
147
  try {
190
148
  if (verb === 'POST' && url.pathname === DEFAULT_RPC_PATH) {
191
- const body = await request.json();
149
+ const body = (await request.json());
192
150
  const result = await handleRpc(body);
193
151
  return Response.json(result);
194
152
  }
@@ -209,9 +167,6 @@ export async function handleFetchRequest(request) {
209
167
  /**
210
168
  * Node `http.createServer` listener: RPC + REST + optional static / SSR index.
211
169
  * 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
170
  */
216
171
  export async function handleNodeRequest(req, res, opts = {}) {
217
172
  const host = req.headers.host || '127.0.0.1';
@@ -227,9 +182,9 @@ export async function handleNodeRequest(req, res, opts = {}) {
227
182
  }
228
183
  // Static first for assets + DocumentMount (`/d/…`) so docs aren't swallowed by SSR 404 shells.
229
184
  // 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).
185
+ // when Server Host SSR is active, those files must not shadow live render (local/dev → SSR truth).
231
186
  if (verb === 'GET' && opts.distDir) {
232
- const nodePath = await import('node:path');
187
+ const nodePath = (await import('node:path'));
233
188
  const { readFile, stat } = await import('node:fs/promises');
234
189
  const file = await resolveDistStatic(opts.distDir, url.pathname, nodePath, stat, {
235
190
  cookieHeader: String(req.headers.cookie || ''),
@@ -244,7 +199,8 @@ export async function handleNodeRequest(req, res, opts = {}) {
244
199
  return sendBytes(res, 200, body, contentType(file, nodePath));
245
200
  }
246
201
  catch (err) {
247
- if (err && (err.code === 'ENOENT' || err.code === 'EISDIR')) {
202
+ const code = err && typeof err === 'object' && 'code' in err ? err.code : undefined;
203
+ if (code === 'ENOENT' || code === 'EISDIR') {
248
204
  /* fall through */
249
205
  }
250
206
  else {
@@ -272,7 +228,6 @@ export async function handleNodeRequest(req, res, opts = {}) {
272
228
  });
273
229
  try {
274
230
  if (typeof opts.renderPageStream === 'function') {
275
- /** @type {unknown} */
276
231
  let body;
277
232
  if (verb === 'POST') {
278
233
  const ctype = String(req.headers['content-type'] || '');
@@ -292,7 +247,7 @@ export async function handleNodeRequest(req, res, opts = {}) {
292
247
  body,
293
248
  });
294
249
  if (rendered) {
295
- if (typeof rendered === 'object' && rendered.redirect) {
250
+ if (typeof rendered === 'object' && 'redirect' in rendered && rendered.redirect) {
296
251
  const status = Number(rendered.status) || 302;
297
252
  const headers = {
298
253
  Location: String(rendered.redirect),
@@ -303,7 +258,7 @@ export async function handleNodeRequest(req, res, opts = {}) {
303
258
  return;
304
259
  }
305
260
  const status = rendered && typeof rendered === 'object' && 'status' in rendered ? Number(rendered.status) || 200 : 200;
306
- const stream = rendered && typeof rendered === 'object' && rendered.stream ? rendered.stream : rendered;
261
+ const stream = rendered && typeof rendered === 'object' && 'stream' in rendered && rendered.stream ? rendered.stream : rendered;
307
262
  return await sendHtmlStream(res, status, stream, ac.signal);
308
263
  }
309
264
  }
@@ -329,7 +284,7 @@ export async function handleNodeRequest(req, res, opts = {}) {
329
284
  }
330
285
  if (!res.headersSent) {
331
286
  if (wantsBrowserHtml(req)) {
332
- sendHtml(res, 404, browserErrorHtml(404, 'Not Found', `No page matched ${url.pathname}`));
287
+ sendHtml(res, 404, browserErrorHtml(404, 'Not Found', `No page matched ${url.pathname}`, undefined));
333
288
  }
334
289
  else {
335
290
  sendJson(res, 404, { error: 'not found', path: url.pathname });
@@ -357,14 +312,8 @@ export async function handleNodeRequest(req, res, opts = {}) {
357
312
  }
358
313
  }
359
314
  }
360
- /**
361
- * @param {import('node:http').IncomingMessage} req
362
- * @param {URL} url
363
- * @returns {Promise<Request>}
364
- */
365
315
  async function incomingToRequest(req, url) {
366
316
  const method = (req.method || 'GET').toUpperCase();
367
- /** @type {HeadersInit} */
368
317
  const headers = {};
369
318
  for (const [k, v] of Object.entries(req.headers)) {
370
319
  if (v == null)
@@ -378,10 +327,6 @@ async function incomingToRequest(req, url) {
378
327
  // Node undici requires duplex when constructing Request with a body.
379
328
  return new Request(url, { method, headers, body: raw, duplex: 'half' });
380
329
  }
381
- /**
382
- * @param {import('node:http').ServerResponse} res
383
- * @param {Response} response
384
- */
385
330
  async function writeFetchResponse(res, response) {
386
331
  const headers = {};
387
332
  response.headers.forEach((value, key) => {
@@ -396,10 +341,6 @@ async function writeFetchResponse(res, response) {
396
341
  }
397
342
  /**
398
343
  * 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
344
  */
404
345
  function safeDistFile(distDir, pathname, nodePath) {
405
346
  let rel = decodeURIComponent(pathname.split('?')[0] || '/');
@@ -418,9 +359,6 @@ function safeDistFile(distDir, pathname, nodePath) {
418
359
  /**
419
360
  * static profile emits per-route HTML beside client assets. That HTML is for CDN / local-static
420
361
  * 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
424
362
  */
425
363
  function isWebStaticHtmlShadow(file, pathname, nodePath) {
426
364
  const ext = nodePath.extname(file).toLowerCase();
@@ -437,13 +375,8 @@ function isWebStaticHtmlShadow(file, pathname, nodePath) {
437
375
  /**
438
376
  * Static resolve for app assets + integrated document mounts:
439
377
  * `/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>}
445
378
  */
446
- async function resolveDistStatic(distDir, pathname, nodePath, stat) {
379
+ async function resolveDistStatic(distDir, pathname, nodePath, stat, _opts = {}) {
447
380
  const candidates = staticPathCandidates(pathname);
448
381
  for (const candidate of candidates) {
449
382
  const full = safeDistFile(distDir, candidate, nodePath);
@@ -466,15 +399,10 @@ async function resolveDistStatic(distDir, pathname, nodePath, stat) {
466
399
  }
467
400
  return null;
468
401
  }
469
- /**
470
- * @param {string} pathname
471
- * @returns {string[]}
472
- */
473
402
  function staticPathCandidates(pathname) {
474
403
  let rel = decodeURIComponent(pathname.split('?')[0] || '/');
475
404
  if (!rel.startsWith('/'))
476
405
  rel = `/${rel}`;
477
- /** @type {string[]} */
478
406
  const out = [];
479
407
  const push = (p) => {
480
408
  if (p && !out.includes(p))
@@ -498,16 +426,11 @@ function staticPathCandidates(pathname) {
498
426
  }
499
427
  return out;
500
428
  }
501
- /** @param {string} p */
502
429
  function nodePathExt(p) {
503
430
  const base = p.split('/').pop() || '';
504
431
  const i = base.lastIndexOf('.');
505
432
  return i > 0 ? base.slice(i) : '';
506
433
  }
507
- /**
508
- * @param {string} filePath
509
- * @param {typeof import('node:path')} nodePath
510
- */
511
434
  function contentType(filePath, nodePath) {
512
435
  const ext = nodePath.extname(filePath).toLowerCase();
513
436
  switch (ext) {
@@ -528,13 +451,12 @@ function contentType(filePath, nodePath) {
528
451
  return 'application/octet-stream';
529
452
  }
530
453
  }
531
- /**
532
- * @param {import('node:http').IncomingMessage} req
533
- */
534
454
  function readJson(req) {
535
455
  return new Promise((resolve, reject) => {
536
456
  const chunks = [];
537
- req.on('data', (c) => chunks.push(c));
457
+ req.on('data', (c) => {
458
+ chunks.push(typeof c === 'string' ? Buffer.from(c) : c);
459
+ });
538
460
  req.on('end', () => {
539
461
  try {
540
462
  const raw = Buffer.concat(chunks).toString('utf8') || '{}';
@@ -547,14 +469,12 @@ function readJson(req) {
547
469
  req.on('error', reject);
548
470
  });
549
471
  }
550
- /**
551
- * @param {import('node:http').IncomingMessage} req
552
- * @returns {Promise<Buffer>}
553
- */
554
472
  function readRawBody(req) {
555
473
  return new Promise((resolve, reject) => {
556
474
  const chunks = [];
557
- req.on('data', (c) => chunks.push(c));
475
+ req.on('data', (c) => {
476
+ chunks.push(typeof c === 'string' ? Buffer.from(c) : c);
477
+ });
558
478
  // Buffer — never utf8-string: multipart File bytes must survive (Upload binary gate).
559
479
  req.on('end', () => resolve(Buffer.concat(chunks)));
560
480
  req.on('error', reject);
@@ -562,9 +482,6 @@ function readRawBody(req) {
562
482
  }
563
483
  /**
564
484
  * Buffer-safe multipart/form-data parser (file parts stay binary).
565
- * @param {Buffer} buf
566
- * @param {string} contentType
567
- * @returns {Record<string, unknown>}
568
485
  */
569
486
  function parseMultipartBuffer(buf, contentType) {
570
487
  const bm = /boundary=(?:"([^"]+)"|([^;\s]+))/i.exec(String(contentType || ''));
@@ -573,7 +490,6 @@ function parseMultipartBuffer(buf, contentType) {
573
490
  throw new Error('multipart: missing boundary');
574
491
  }
575
492
  const sep = Buffer.from(`--${boundary}`);
576
- /** @type {Record<string, unknown>} */
577
493
  const out = {};
578
494
  let start = indexOfBuffer(buf, sep, 0);
579
495
  if (start < 0)
@@ -629,11 +545,6 @@ function parseMultipartBuffer(buf, contentType) {
629
545
  }
630
546
  return out;
631
547
  }
632
- /**
633
- * @param {Buffer} hay
634
- * @param {Buffer} needle
635
- * @param {number} from
636
- */
637
548
  function indexOfBuffer(hay, needle, from) {
638
549
  if (!needle.length)
639
550
  return from;
@@ -646,31 +557,23 @@ function indexOfBuffer(hay, needle, from) {
646
557
  }
647
558
  return -1;
648
559
  }
649
- /**
650
- * @param {string} raw
651
- * @param {string} contentType
652
- * @returns {Record<string, string> | string}
653
- */
654
560
  function parseFormBody(raw, contentType) {
655
- if (!raw)
561
+ const text = typeof raw === 'string' ? raw : raw.toString('utf8');
562
+ if (!text)
656
563
  return {};
657
564
  if (contentType.includes('application/x-www-form-urlencoded')) {
658
565
  const out = {};
659
- for (const [k, v] of new URLSearchParams(raw))
566
+ for (const [k, v] of new URLSearchParams(text))
660
567
  out[k] = v;
661
568
  return out;
662
569
  }
663
570
  try {
664
- return JSON.parse(raw);
571
+ return JSON.parse(text);
665
572
  }
666
573
  catch {
667
- return { raw };
574
+ return { raw: text };
668
575
  }
669
576
  }
670
- /**
671
- * @param {import('node:http').IncomingMessage} req
672
- * @returns {boolean}
673
- */
674
577
  function wantsBrowserHtml(req) {
675
578
  const accept = String(req.headers?.accept || '');
676
579
  const method = String(req.method || 'GET').toUpperCase();
@@ -698,10 +601,6 @@ function wantsBrowserHtml(req) {
698
601
  /**
699
602
  * Browser-facing error document (no N-API). Used when SSR returns null or throws
700
603
  * 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]
705
604
  */
706
605
  function browserErrorHtml(status, title, message, stack) {
707
606
  const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
@@ -734,11 +633,6 @@ function browserErrorHtml(status, title, message, stack) {
734
633
  </html>
735
634
  `;
736
635
  }
737
- /**
738
- * @param {import('node:http').ServerResponse} res
739
- * @param {number} status
740
- * @param {unknown} body
741
- */
742
636
  function sendJson(res, status, body) {
743
637
  const payload = JSON.stringify(body);
744
638
  res.writeHead(status, {
@@ -747,11 +641,6 @@ function sendJson(res, status, body) {
747
641
  });
748
642
  res.end(payload);
749
643
  }
750
- /**
751
- * @param {import('node:http').ServerResponse} res
752
- * @param {number} status
753
- * @param {string} html
754
- */
755
644
  function sendHtml(res, status, html) {
756
645
  const payload = typeof html === 'string' ? html : String(html);
757
646
  res.writeHead(status, {
@@ -763,10 +652,6 @@ function sendHtml(res, status, html) {
763
652
  /**
764
653
  * Stream HTML without buffering the full document (event-flow / stream SSR host).
765
654
  * 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
655
  */
771
656
  async function sendHtmlStream(res, status, source, signal) {
772
657
  const aborted = () => Boolean(signal?.aborted || res.destroyed || res.writableEnded || !res.writable);
@@ -821,14 +706,7 @@ async function sendHtmlStream(res, status, source, signal) {
821
706
  res.end();
822
707
  }
823
708
  }
824
- /**
825
- * @param {import('node:http').ServerResponse} res
826
- * @param {number} status
827
- * @param {Buffer} body
828
- * @param {string} type
829
- */
830
709
  function sendBytes(res, status, body, type) {
831
- /** @type {Record<string, string | number>} */
832
710
  const headers = {
833
711
  'content-type': type,
834
712
  'content-length': body.byteLength,
@@ -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>;