@vmz/core 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dom-core.d.ts +9 -2
- package/dist/dom-core.js +61 -11
- package/dist/dom-ssr.js +15 -15
- package/dist/serve-host.mjs +33 -11
- package/dist/server.js +82 -5
- package/package.json +7 -7
package/dist/dom-core.d.ts
CHANGED
|
@@ -153,14 +153,14 @@ export declare const directApi: {
|
|
|
153
153
|
*/
|
|
154
154
|
projectDefaultSlot(hostEl: any, node: any): void;
|
|
155
155
|
/**
|
|
156
|
-
* Direct if/else — no
|
|
156
|
+
* Direct if/else — comment anchors (no empty `span[data-vmz-if]` layout box).
|
|
157
157
|
* @param {object} inst
|
|
158
158
|
* @param {number|string|null} bindingId
|
|
159
159
|
* @param {string[]} deps
|
|
160
160
|
* @param {Array<{ cond?: => any, create: (api: typeof directApi) => Node }>} branches
|
|
161
161
|
* @param {number|string|null} [regionId]
|
|
162
162
|
*/
|
|
163
|
-
ifBlock(inst: any, bindingId: any, deps: any, branches: any, regionId?: any):
|
|
163
|
+
ifBlock(inst: any, bindingId: any, deps: any, branches: any, regionId?: any): DocumentFragment;
|
|
164
164
|
/**
|
|
165
165
|
* Direct keyed each — no blueprint `kind: "each"` dispatch.
|
|
166
166
|
* /: Set/Map + Fragment batch insert; item-local binds; host field dispatch; event delegate.
|
|
@@ -172,6 +172,13 @@ export declare const directApi: {
|
|
|
172
172
|
*/
|
|
173
173
|
eachBlock(inst: any, bindingId: any, deps: any, spec: any, regionId?: any): DocumentFragment;
|
|
174
174
|
};
|
|
175
|
+
/**
|
|
176
|
+
* Vue-familiar `@click` / legacy `onClick` → canonical `onXxx` prop name.
|
|
177
|
+
* Function props that are not events are treated as getters (`v.call(host)`).
|
|
178
|
+
* @param {unknown} name
|
|
179
|
+
* @returns {string | null}
|
|
180
|
+
*/
|
|
181
|
+
export declare function eventPropHandlerName(name: any): string;
|
|
175
182
|
export declare function isEventPropName(name: any): boolean;
|
|
176
183
|
/** HTML boolean attributes: presence means true; `false`/`null` must remove the attr. */
|
|
177
184
|
export declare const BOOLEAN_HTML_ATTRS: Set<string>;
|
package/dist/dom-core.js
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
/** @type {Record<string, new (props?: object) => any>} */
|
|
11
11
|
const components = Object.create(null);
|
|
12
|
+
/**
|
|
13
|
+
* Inline chip hosts that opt into `display: contents` (`ui-direct-host-box`).
|
|
14
|
+
* Block surfaces (DataTable / shells / overlays) must keep a real layout box.
|
|
15
|
+
* @type {Set<string>}
|
|
16
|
+
*/
|
|
17
|
+
const INLINE_HOST_CONTENTS = new Set(['Button', 'Badge', 'Link', 'Tag', 'Icon']);
|
|
12
18
|
/**
|
|
13
19
|
* Precision lab counters (test / MCP / benchmarks — not a user API).
|
|
14
20
|
* Primary keys: BindingId (IR). `*ByDep` is transitional stable-string adapter.
|
|
@@ -428,8 +434,9 @@ export const directApi = {
|
|
|
428
434
|
/** @type {Record<string, any>} */
|
|
429
435
|
const resolved = {};
|
|
430
436
|
for (const [k, v] of Object.entries(props || {})) {
|
|
431
|
-
|
|
432
|
-
|
|
437
|
+
const onKey = typeof v === 'function' ? eventPropHandlerName(k) : null;
|
|
438
|
+
if (onKey)
|
|
439
|
+
resolved[onKey] = v;
|
|
433
440
|
else if (typeof v === 'function')
|
|
434
441
|
resolved[k] = v.call(hostInst);
|
|
435
442
|
else
|
|
@@ -454,6 +461,13 @@ export const directApi = {
|
|
|
454
461
|
const Ctor = components[name];
|
|
455
462
|
if (!Ctor)
|
|
456
463
|
throw new Error(`vmz:dom unknown component <${name} />`);
|
|
464
|
+
// Inline chips opt into no-box host (`ui-direct-host-box`). Block surfaces
|
|
465
|
+
// (tables/shells/overlays) keep a real box — default `contents` breaks
|
|
466
|
+
// hit-testing / slot hosts (DataTable select timed out in ui-automation).
|
|
467
|
+
const hostBox = Ctor.__vmzHostBox;
|
|
468
|
+
if (hostBox === 'contents' || (hostBox == null && INLINE_HOST_CONTENTS.has(String(name)))) {
|
|
469
|
+
host.style.display = 'contents';
|
|
470
|
+
}
|
|
457
471
|
const child = createInstance(Ctor, resolved);
|
|
458
472
|
if (!(Ctor.__vmzDirect && typeof Ctor.__vmzCreate === 'function')) {
|
|
459
473
|
throw new Error(`vmz:dom direct component <${name}> requires __vmzCreate (rebuild child with Direct)`);
|
|
@@ -539,7 +553,7 @@ export const directApi = {
|
|
|
539
553
|
hostEl.appendChild(node);
|
|
540
554
|
},
|
|
541
555
|
/**
|
|
542
|
-
* Direct if/else — no
|
|
556
|
+
* Direct if/else — comment anchors (no empty `span[data-vmz-if]` layout box).
|
|
543
557
|
* @param {object} inst
|
|
544
558
|
* @param {number|string|null} bindingId
|
|
545
559
|
* @param {string[]} deps
|
|
@@ -548,10 +562,22 @@ export const directApi = {
|
|
|
548
562
|
*/
|
|
549
563
|
ifBlock(inst, bindingId, deps, branches, regionId = null) {
|
|
550
564
|
noteDomCreate();
|
|
551
|
-
const
|
|
552
|
-
|
|
565
|
+
const start = document.createComment('vmz-if');
|
|
566
|
+
const end = document.createComment('/vmz-if');
|
|
553
567
|
if (regionId != null)
|
|
554
|
-
|
|
568
|
+
start.__vmzRegion = regionId;
|
|
569
|
+
const frag = document.createDocumentFragment();
|
|
570
|
+
frag.appendChild(start);
|
|
571
|
+
/** @type {HTMLElement | null} */
|
|
572
|
+
let regionHost = null;
|
|
573
|
+
if (regionId != null) {
|
|
574
|
+
// Queryable region marker without a layout box (`display: contents`).
|
|
575
|
+
regionHost = document.createElement('span');
|
|
576
|
+
regionHost.style.display = 'contents';
|
|
577
|
+
regionHost.setAttribute('data-vmz-region', String(regionId));
|
|
578
|
+
frag.appendChild(regionHost);
|
|
579
|
+
}
|
|
580
|
+
frag.appendChild(end);
|
|
555
581
|
/** @type {Array<Node | null>} */
|
|
556
582
|
const cached = branches.map(() => null);
|
|
557
583
|
/** @type {Array<Array<{ deps: string[], fn: => any, bindingId?: number|string|null }>>} */
|
|
@@ -634,14 +660,18 @@ export const directApi = {
|
|
|
634
660
|
if (next < 0)
|
|
635
661
|
return;
|
|
636
662
|
wireBranch(next);
|
|
637
|
-
if (cached[next])
|
|
638
|
-
|
|
663
|
+
if (cached[next] && end.parentNode) {
|
|
664
|
+
if (regionHost)
|
|
665
|
+
regionHost.appendChild(cached[next]);
|
|
666
|
+
else
|
|
667
|
+
end.parentNode.insertBefore(cached[next], end);
|
|
668
|
+
}
|
|
639
669
|
};
|
|
640
670
|
registerBind(inst, deps || [], apply, bindingId);
|
|
641
671
|
if (directApi._itemPatches)
|
|
642
672
|
directApi._itemPatches.push(apply);
|
|
643
673
|
// parent destroy disposes all cached branch trees (pause ≠ destroy on switch).
|
|
644
|
-
|
|
674
|
+
start.__vmzDispose = () => {
|
|
645
675
|
for (let i = 0; i < cached.length; i++) {
|
|
646
676
|
unwireBranch(i);
|
|
647
677
|
if (cached[i])
|
|
@@ -651,7 +681,7 @@ export const directApi = {
|
|
|
651
681
|
active = -1;
|
|
652
682
|
};
|
|
653
683
|
apply();
|
|
654
|
-
return
|
|
684
|
+
return frag;
|
|
655
685
|
},
|
|
656
686
|
/**
|
|
657
687
|
* Direct keyed each — no blueprint `kind: "each"` dispatch.
|
|
@@ -2616,8 +2646,28 @@ function wireDirectBind(inst, bindingId, deps, get, write, cf) {
|
|
|
2616
2646
|
apply();
|
|
2617
2647
|
trackDirectBind(inst, liveDeps, apply, bindingId);
|
|
2618
2648
|
}
|
|
2649
|
+
/**
|
|
2650
|
+
* Vue-familiar `@click` / legacy `onClick` → canonical `onXxx` prop name.
|
|
2651
|
+
* Function props that are not events are treated as getters (`v.call(host)`).
|
|
2652
|
+
* @param {unknown} name
|
|
2653
|
+
* @returns {string | null}
|
|
2654
|
+
*/
|
|
2655
|
+
export function eventPropHandlerName(name) {
|
|
2656
|
+
if (typeof name !== 'string' || !name)
|
|
2657
|
+
return null;
|
|
2658
|
+
if (/^on[A-Z]/.test(name))
|
|
2659
|
+
return name;
|
|
2660
|
+
// `@input` → `onInput`; reject `@update:modelValue` until that surface exists.
|
|
2661
|
+
if (name.charAt(0) === '@' && name.length > 1 && !name.includes(':')) {
|
|
2662
|
+
const ev = name.slice(1);
|
|
2663
|
+
if (!ev || !/^[A-Za-z]/.test(ev))
|
|
2664
|
+
return null;
|
|
2665
|
+
return `on${ev.charAt(0).toUpperCase()}${ev.slice(1)}`;
|
|
2666
|
+
}
|
|
2667
|
+
return null;
|
|
2668
|
+
}
|
|
2619
2669
|
export function isEventPropName(name) {
|
|
2620
|
-
return
|
|
2670
|
+
return eventPropHandlerName(name) != null;
|
|
2621
2671
|
}
|
|
2622
2672
|
/** Monotonic id for `bindComponentProp` BindingIds (per process). */
|
|
2623
2673
|
let directPropBindSeq = 0;
|
package/dist/dom-ssr.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* VMZ DOM SSR / hydrate / resume — precise patches, no VDOM diff.
|
|
4
4
|
* Imports client DOM primitives from ./dom-core.js for tree-shakeable browser entry.
|
|
5
5
|
*/
|
|
6
|
-
import { applyDomAttr, applyPreservedState, BOOLEAN_HTML_ATTRS, createInstance, destroy, directApi, getRegisteredComponent, hasMeaningfulChild, isEventEntryStrategy,
|
|
6
|
+
import { applyDomAttr, applyPreservedState, BOOLEAN_HTML_ATTRS, createInstance, destroy, directApi, eventPropHandlerName, getRegisteredComponent, hasMeaningfulChild, isEventEntryStrategy, mount, noteDomCreate, resolveComponent, runDirectCreate, scheduleClientOn, settlePendingChildMounts, snapshotInstanceState, stripFns, } from './dom-core.js';
|
|
7
7
|
/** @type {Error | null} last linkedom resolve failure (for clear SSR errors) */
|
|
8
8
|
let _ssrDocumentLastError = null;
|
|
9
9
|
/**
|
|
@@ -671,16 +671,8 @@ const serializeApi = {
|
|
|
671
671
|
el.children = [];
|
|
672
672
|
},
|
|
673
673
|
ifBlock(inst, bindingId, deps, branches) {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
tag: 'span',
|
|
677
|
-
attrs: { 'data-vmz-if': '' },
|
|
678
|
-
children: [],
|
|
679
|
-
appendChild(c) {
|
|
680
|
-
if (c != null)
|
|
681
|
-
this.children.push(c);
|
|
682
|
-
},
|
|
683
|
-
};
|
|
674
|
+
// No empty `span[data-vmz-if]` box (`ui-vif-dom`): false → empty frag.
|
|
675
|
+
const frag = serializeApi.frag();
|
|
684
676
|
let idx = -1;
|
|
685
677
|
for (let i = 0; i < branches.length; i++) {
|
|
686
678
|
const b = branches[i];
|
|
@@ -701,9 +693,9 @@ const serializeApi = {
|
|
|
701
693
|
if (idx >= 0 && branches[idx].create) {
|
|
702
694
|
const created = branches[idx].create.call(inst, serializeApi);
|
|
703
695
|
if (created)
|
|
704
|
-
|
|
696
|
+
frag.appendChild(created);
|
|
705
697
|
}
|
|
706
|
-
return
|
|
698
|
+
return frag;
|
|
707
699
|
},
|
|
708
700
|
eachBlock(inst, bindingId, deps, spec) {
|
|
709
701
|
const frag = serializeApi.frag();
|
|
@@ -755,7 +747,8 @@ const serializeApi = {
|
|
|
755
747
|
/** @type {Record<string, any>} */
|
|
756
748
|
const resolved = {};
|
|
757
749
|
for (const [k, v] of Object.entries(props || {})) {
|
|
758
|
-
|
|
750
|
+
const onKey = typeof v === 'function' ? eventPropHandlerName(k) : null;
|
|
751
|
+
if (onKey)
|
|
759
752
|
continue;
|
|
760
753
|
else if (typeof v === 'function')
|
|
761
754
|
resolved[k] = v.call(hostInst);
|
|
@@ -815,10 +808,17 @@ const serializeApi = {
|
|
|
815
808
|
serializeApi._inst = child;
|
|
816
809
|
try {
|
|
817
810
|
const node = Ctor.__vmzCreate.call(child, serializeApi);
|
|
811
|
+
/** @type {Record<string, string>} */
|
|
812
|
+
const attrs = { 'data-vmz': name };
|
|
813
|
+
const hostBox = Ctor.__vmzHostBox;
|
|
814
|
+
if (hostBox === 'contents' ||
|
|
815
|
+
(hostBox == null && (name === 'Button' || name === 'Badge' || name === 'Link' || name === 'Tag' || name === 'Icon'))) {
|
|
816
|
+
attrs.style = 'display:contents';
|
|
817
|
+
}
|
|
818
818
|
return {
|
|
819
819
|
__kind: 'el',
|
|
820
820
|
tag: 'div',
|
|
821
|
-
attrs
|
|
821
|
+
attrs,
|
|
822
822
|
children: node ? [node] : [],
|
|
823
823
|
appendChild(c) {
|
|
824
824
|
if (c != null)
|
package/dist/serve-host.mjs
CHANGED
|
@@ -211,6 +211,22 @@ async function renderPage(pathname, opts = {}) {
|
|
|
211
211
|
* @returns {Promise<{ status: number, stream?: AsyncGenerator<string, void, void>, redirect?: string, headers?: Record<string, string> } | null>}
|
|
212
212
|
*/
|
|
213
213
|
async function renderPageStream(pathname, opts = {}) {
|
|
214
|
+
try {
|
|
215
|
+
return await renderPageStreamInner(pathname, opts);
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
const normalized = normalizeDevError(err);
|
|
219
|
+
lastDevError = normalized;
|
|
220
|
+
console.error('vmz serve: renderPageStream failed', normalized.message);
|
|
221
|
+
return { status: 500, stream: emitDevErrorHtml(normalized) };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* @param {string} pathname
|
|
226
|
+
* @param {{ signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string, method?: string, body?: unknown }} [opts]
|
|
227
|
+
* @returns {Promise<{ status: number, stream?: AsyncGenerator<string, void, void>, redirect?: string, headers?: Record<string, string> } | null>}
|
|
228
|
+
*/
|
|
229
|
+
async function renderPageStreamInner(pathname, opts = {}) {
|
|
214
230
|
if (isDev && lastDevError && pageCtors.size === 0) {
|
|
215
231
|
return { status: 500, stream: emitDevErrorHtml(lastDevError) };
|
|
216
232
|
}
|
|
@@ -945,7 +961,7 @@ async function* emitDevErrorHtml(err) {
|
|
|
945
961
|
.hint{opacity:.65;font-size:12px}
|
|
946
962
|
</style>`;
|
|
947
963
|
const body = `${style}
|
|
948
|
-
<main>
|
|
964
|
+
<main data-vmz-error="500">
|
|
949
965
|
<h1>Dev Error</h1>
|
|
950
966
|
<pre>${msg}</pre>
|
|
951
967
|
${stack ? `<pre style="opacity:.7;font-size:12px">${stack}</pre>` : ''}
|
|
@@ -961,17 +977,23 @@ async function* emitDevErrorHtml(err) {
|
|
|
961
977
|
};
|
|
962
978
|
})();
|
|
963
979
|
</script>`;
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
980
|
+
try {
|
|
981
|
+
const native = loadNativeAddon();
|
|
982
|
+
if (typeof native.generateHtmlShell === 'function') {
|
|
983
|
+
yield native.generateHtmlShell({
|
|
984
|
+
title: 'Dev Error',
|
|
985
|
+
lang: 'en',
|
|
986
|
+
cssHrefs: [],
|
|
987
|
+
bodyHtml: body,
|
|
988
|
+
bodyAttrs: [],
|
|
989
|
+
});
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
967
992
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
bodyHtml: body,
|
|
973
|
-
bodyAttrs: [],
|
|
974
|
-
});
|
|
993
|
+
catch {
|
|
994
|
+
/* fall through to plain HTML — never throw from the error page itself */
|
|
995
|
+
}
|
|
996
|
+
yield `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><title>Dev Error</title></head><body>${body}</body></html>`;
|
|
975
997
|
}
|
|
976
998
|
/** @param {string} s */
|
|
977
999
|
function escapeHtml(s) {
|
package/dist/server.js
CHANGED
|
@@ -328,7 +328,12 @@ export async function handleNodeRequest(req, res, opts = {}) {
|
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
if (!res.headersSent) {
|
|
331
|
-
|
|
331
|
+
if (wantsBrowserHtml(req)) {
|
|
332
|
+
sendHtml(res, 404, browserErrorHtml(404, 'Not Found', `No page matched ${url.pathname}`));
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
sendJson(res, 404, { error: 'not found', path: url.pathname });
|
|
336
|
+
}
|
|
332
337
|
}
|
|
333
338
|
}
|
|
334
339
|
catch (err) {
|
|
@@ -342,9 +347,14 @@ export async function handleNodeRequest(req, res, opts = {}) {
|
|
|
342
347
|
}
|
|
343
348
|
return;
|
|
344
349
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
350
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
351
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
352
|
+
if (wantsBrowserHtml(req)) {
|
|
353
|
+
sendHtml(res, 500, browserErrorHtml(500, 'Dev Error', message, stack));
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
sendJson(res, 500, { error: message });
|
|
357
|
+
}
|
|
348
358
|
}
|
|
349
359
|
}
|
|
350
360
|
/**
|
|
@@ -585,7 +595,7 @@ function parseMultipartBuffer(buf, contentType) {
|
|
|
585
595
|
const splitAt = indexOfBuffer(part, Buffer.from('\r\n\r\n'), 0);
|
|
586
596
|
if (splitAt >= 0) {
|
|
587
597
|
const headerText = part.subarray(0, splitAt).toString('utf8');
|
|
588
|
-
|
|
598
|
+
const body = part.subarray(splitAt + 4);
|
|
589
599
|
const nameM = /content-disposition:[^\r\n]*;\s*name="([^"]*)"/i.exec(headerText);
|
|
590
600
|
const fileM = /content-disposition:[^\r\n]*;\s*filename="([^"]*)"/i.exec(headerText);
|
|
591
601
|
const typeM = /content-type:\s*([^\r\n]+)/i.exec(headerText);
|
|
@@ -657,6 +667,73 @@ function parseFormBody(raw, contentType) {
|
|
|
657
667
|
return { raw };
|
|
658
668
|
}
|
|
659
669
|
}
|
|
670
|
+
/**
|
|
671
|
+
* @param {import('node:http').IncomingMessage} req
|
|
672
|
+
* @returns {boolean}
|
|
673
|
+
*/
|
|
674
|
+
function wantsBrowserHtml(req) {
|
|
675
|
+
const accept = String(req.headers?.accept || '');
|
|
676
|
+
const method = String(req.method || 'GET').toUpperCase();
|
|
677
|
+
if (method !== 'GET' && method !== 'HEAD')
|
|
678
|
+
return false;
|
|
679
|
+
// Document navigations: browsers send text/html and/or Sec-Fetch-Dest: document.
|
|
680
|
+
// Bare `*/*` / empty Accept (Node fetch, many API clients) must stay JSON.
|
|
681
|
+
const dest = String(req.headers?.['sec-fetch-dest'] || '');
|
|
682
|
+
const mode = String(req.headers?.['sec-fetch-mode'] || '');
|
|
683
|
+
if (dest === 'document' || mode === 'navigate')
|
|
684
|
+
return true;
|
|
685
|
+
if (!accept || accept === '*/*')
|
|
686
|
+
return false;
|
|
687
|
+
const html = accept.includes('text/html');
|
|
688
|
+
const json = accept.includes('application/json');
|
|
689
|
+
if (html && !json)
|
|
690
|
+
return true;
|
|
691
|
+
if (html && json) {
|
|
692
|
+
const hi = accept.indexOf('text/html');
|
|
693
|
+
const ji = accept.indexOf('application/json');
|
|
694
|
+
return hi <= ji;
|
|
695
|
+
}
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* 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]
|
|
705
|
+
*/
|
|
706
|
+
function browserErrorHtml(status, title, message, stack) {
|
|
707
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
708
|
+
const stackBlock = stack ? `<pre class="stack">${esc(stack)}</pre>` : '';
|
|
709
|
+
return `<!DOCTYPE html>
|
|
710
|
+
<html lang="en">
|
|
711
|
+
<head>
|
|
712
|
+
<meta charset="utf-8" />
|
|
713
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
714
|
+
<title>${esc(title)}</title>
|
|
715
|
+
<style>
|
|
716
|
+
body{margin:0;background:#0f1115;color:#f4f4f5;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
717
|
+
main{max-width:56rem;margin:0 auto;padding:2rem 1.25rem}
|
|
718
|
+
h1{margin:0 0 .75rem;color:#f87171;font-size:1.1rem}
|
|
719
|
+
.code{opacity:.65;font-size:12px;margin-bottom:.75rem}
|
|
720
|
+
pre{white-space:pre-wrap;margin:0 0 1rem}
|
|
721
|
+
.stack{opacity:.7;font-size:12px}
|
|
722
|
+
.hint{opacity:.65;font-size:12px}
|
|
723
|
+
</style>
|
|
724
|
+
</head>
|
|
725
|
+
<body data-vmz-error="${status}">
|
|
726
|
+
<main>
|
|
727
|
+
<p class="code">HTTP ${status}</p>
|
|
728
|
+
<h1>${esc(title)}</h1>
|
|
729
|
+
<pre>${esc(message)}</pre>
|
|
730
|
+
${stackBlock}
|
|
731
|
+
<p class="hint">Document navigation returns HTML errors — not API JSON.</p>
|
|
732
|
+
</main>
|
|
733
|
+
</body>
|
|
734
|
+
</html>
|
|
735
|
+
`;
|
|
736
|
+
}
|
|
660
737
|
/**
|
|
661
738
|
* @param {import('node:http').ServerResponse} res
|
|
662
739
|
* @param {number} status
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vmz/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "VMZ production runtime core — DOM / SSR / HTTP / WriteBarrier (no compiler)",
|
|
6
6
|
"exports": {
|
|
@@ -58,12 +58,12 @@
|
|
|
58
58
|
"linkedom": "^0.18.13"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@vmz/vmz-win32-x64": "0.1.
|
|
62
|
-
"@vmz/vmz-win32-arm64": "0.1.
|
|
63
|
-
"@vmz/vmz-darwin-x64": "0.1.
|
|
64
|
-
"@vmz/vmz-darwin-arm64": "0.1.
|
|
65
|
-
"@vmz/vmz-linux-x64": "0.1.
|
|
66
|
-
"@vmz/vmz-linux-arm64": "0.1.
|
|
61
|
+
"@vmz/vmz-win32-x64": "0.1.18",
|
|
62
|
+
"@vmz/vmz-win32-arm64": "0.1.18",
|
|
63
|
+
"@vmz/vmz-darwin-x64": "0.1.18",
|
|
64
|
+
"@vmz/vmz-darwin-arm64": "0.1.18",
|
|
65
|
+
"@vmz/vmz-linux-x64": "0.1.18",
|
|
66
|
+
"@vmz/vmz-linux-arm64": "0.1.18"
|
|
67
67
|
},
|
|
68
68
|
"publishConfig": {
|
|
69
69
|
"access": "public"
|