@vmz/core 0.1.12 → 0.1.14

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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Deployment graph → component registry bootstrap (shared by all SSR/DOM hosts).
3
- * Replaces ad-hoc registerComponents calls with an explicit preload contract.
3
+ * Parse/validate and graph queries delegate to Rust vmz-artifacts via N-API.
4
4
  */
5
5
  export declare const DEPLOYMENT_SCHEMA = "vmz.deployment.v0";
6
6
  /**
@@ -13,7 +13,7 @@ export declare function readDeploymentDocument(distDir: any, opts?: {}): any;
13
13
  * @param {any} deployment
14
14
  * @returns {Array<{ chunkId: string, name: string, entry: string, source: string }>}
15
15
  */
16
- export declare function componentEntriesFromDeployment(deployment: any): any[];
16
+ export declare function componentEntriesFromDeployment(deployment: any): any;
17
17
  /**
18
18
  * @param {any} deployment
19
19
  * @param {string[]} rootChunkIds
@@ -1,13 +1,22 @@
1
1
  // @ts-nocheck
2
2
  /**
3
3
  * Deployment graph → component registry bootstrap (shared by all SSR/DOM hosts).
4
- * Replaces ad-hoc registerComponents calls with an explicit preload contract.
4
+ * Parse/validate and graph queries delegate to Rust vmz-artifacts via N-API.
5
5
  */
6
6
  import fs from 'node:fs';
7
7
  import { readdir } from 'node:fs/promises';
8
8
  import path from 'node:path';
9
9
  import { pathToFileURL } from 'node:url';
10
+ import { requireNativeFn } from './native-addon.js';
10
11
  export const DEPLOYMENT_SCHEMA = 'vmz.deployment.v0';
12
+ /**
13
+ * @param {string} jsonText
14
+ * @returns {any}
15
+ */
16
+ function parseDeploymentJson(jsonText) {
17
+ requireNativeFn('deploymentValidate')(jsonText);
18
+ return JSON.parse(jsonText);
19
+ }
11
20
  /**
12
21
  * @param {string} distDir
13
22
  * @param {{ strict?: boolean }} [opts]
@@ -31,45 +40,22 @@ export function readDeploymentDocument(distDir, opts = {}) {
31
40
  throw new Error(`vmz: cannot read vmz-deployment.json: ${e instanceof Error ? e.message : e}`);
32
41
  return null;
33
42
  }
34
- let doc;
35
43
  try {
36
- doc = JSON.parse(raw);
44
+ return parseDeploymentJson(raw);
37
45
  }
38
46
  catch (e) {
39
47
  if (strict)
40
48
  throw new Error(`vmz: invalid vmz-deployment.json: ${e instanceof Error ? e.message : e}`);
41
49
  return null;
42
50
  }
43
- if (doc.schema !== DEPLOYMENT_SCHEMA) {
44
- if (strict)
45
- throw new Error(`vmz: unsupported deployment schema ${doc.schema}`);
46
- return null;
47
- }
48
- return doc;
49
51
  }
50
52
  /**
51
53
  * @param {any} deployment
52
54
  * @returns {Array<{ chunkId: string, name: string, entry: string, source: string }>}
53
55
  */
54
56
  export function componentEntriesFromDeployment(deployment) {
55
- /** @type {Array<{ chunkId: string, name: string, entry: string, source: string }>} */
56
- const out = [];
57
- for (const unit of deployment.units || []) {
58
- if (unit?.kind !== 'component')
59
- continue;
60
- const chunkId = String(unit.chunkId || '').replace(/\\/g, '/');
61
- const name = chunkId.split('/').pop();
62
- if (!name)
63
- continue;
64
- const entry = String(unit.clientEntry || `${chunkId}.client.js`).replace(/\\/g, '/');
65
- out.push({
66
- chunkId,
67
- name,
68
- entry,
69
- source: String(unit.source || ''),
70
- });
71
- }
72
- return out.sort((a, b) => a.chunkId.localeCompare(b.chunkId));
57
+ const json = JSON.stringify(deployment);
58
+ return requireNativeFn('deploymentComponentEntries')(json);
73
59
  }
74
60
  /**
75
61
  * @param {any} deployment
@@ -77,30 +63,9 @@ export function componentEntriesFromDeployment(deployment) {
77
63
  * @returns {Set<string>}
78
64
  */
79
65
  export function collectDependsOnClosure(deployment, rootChunkIds) {
80
- /** @type {Map<string, any>} */
81
- const byId = new Map();
82
- for (const unit of deployment.units || []) {
83
- byId.set(String(unit.chunkId || '').replace(/\\/g, '/'), unit);
84
- }
85
- /** @type {Set<string>} */
86
- const out = new Set();
87
- /** @type {string[]} */
88
- const stack = rootChunkIds.map((id) => String(id).replace(/\\/g, '/')).filter(Boolean);
89
- while (stack.length) {
90
- const id = stack.pop();
91
- if (!id || out.has(id))
92
- continue;
93
- out.add(id);
94
- const unit = byId.get(id);
95
- if (!unit)
96
- continue;
97
- for (const dep of unit.dependsOn || []) {
98
- const d = String(dep).replace(/\\/g, '/');
99
- if (!out.has(d))
100
- stack.push(d);
101
- }
102
- }
103
- return out;
66
+ const json = JSON.stringify(deployment);
67
+ const ids = requireNativeFn('deploymentDependsOnClosure')(json, rootChunkIds);
68
+ return new Set(ids);
104
69
  }
105
70
  /**
106
71
  * Resolve tag conflicts; strict mode throws, dev mode warns and keeps last chunkId.
package/dist/dom-core.js CHANGED
@@ -498,6 +498,14 @@ export const directApi = {
498
498
  if (typeof propName !== 'string' || !propName || propName.startsWith('#'))
499
499
  return;
500
500
  child[propName] = raw;
501
+ if (typeof child.__vmzOnParentProp === 'function') {
502
+ try {
503
+ child.__vmzOnParentProp(propName, raw);
504
+ }
505
+ catch (err) {
506
+ console.error('vmz:dom __vmzOnParentProp', err);
507
+ }
508
+ }
501
509
  scheduleRefresh(child, { type: 'replace', root: propName });
502
510
  });
503
511
  },
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Discover and load the vmz N-API addon (same contract as `@vmz/vmz` loadNative).
3
+ */
4
+ /**
5
+ * @returns {any}
6
+ */
7
+ export declare function loadNativeAddon(): any;
8
+ /**
9
+ * @param {string} fnName
10
+ * @returns {any}
11
+ */
12
+ export declare function requireNativeFn(fnName: any): any;
@@ -0,0 +1,116 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Discover and load the vmz N-API addon (same contract as `@vmz/vmz` loadNative).
4
+ */
5
+ import { existsSync } from 'node:fs';
6
+ import { createRequire } from 'node:module';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ const require = createRequire(import.meta.url);
10
+ /** @type {any | null | undefined} */
11
+ let _nativeAddon;
12
+ /**
13
+ * @returns {string}
14
+ */
15
+ function platformTriple() {
16
+ const { platform, arch } = process;
17
+ if (platform === 'win32' && arch === 'x64')
18
+ return 'win32-x64-msvc';
19
+ if (platform === 'win32' && arch === 'arm64')
20
+ return 'win32-arm64-msvc';
21
+ if (platform === 'darwin' && arch === 'arm64')
22
+ return 'darwin-arm64';
23
+ if (platform === 'darwin' && arch === 'x64')
24
+ return 'darwin-x64';
25
+ if (platform === 'linux' && arch === 'x64')
26
+ return 'linux-x64-gnu';
27
+ if (platform === 'linux' && arch === 'arm64')
28
+ return 'linux-arm64-gnu';
29
+ return `${platform}-${arch}`;
30
+ }
31
+ /**
32
+ * @param {string} triple
33
+ * @returns {string}
34
+ */
35
+ function platformShort(triple) {
36
+ if (triple === 'win32-x64-msvc')
37
+ return 'win32-x64';
38
+ if (triple === 'win32-arm64-msvc')
39
+ return 'win32-arm64';
40
+ if (triple === 'linux-x64-gnu')
41
+ return 'linux-x64';
42
+ if (triple === 'linux-arm64-gnu')
43
+ return 'linux-arm64';
44
+ return triple;
45
+ }
46
+ /**
47
+ * @returns {string[]}
48
+ */
49
+ function nativeCandidatePaths() {
50
+ const triple = platformTriple();
51
+ const short = platformShort(triple);
52
+ const name = `@vmz/vmz-${short}`;
53
+ /** @type {string[]} */
54
+ const candidates = [];
55
+ try {
56
+ const resolved = require.resolve(`${name}/package.json`);
57
+ const dir = path.dirname(resolved);
58
+ candidates.push(path.join(dir, `vmz.${triple}.node`), path.join(dir, 'vmz.node'));
59
+ }
60
+ catch {
61
+ /* optional dep not installed */
62
+ }
63
+ let dir = path.dirname(fileURLToPath(import.meta.url));
64
+ for (let depth = 0; depth < 12; depth++) {
65
+ candidates.push(path.join(dir, 'node_modules', name, `vmz.${triple}.node`), path.join(dir, 'node_modules', name, 'vmz.node'));
66
+ const parent = path.dirname(dir);
67
+ if (parent === dir)
68
+ break;
69
+ dir = parent;
70
+ }
71
+ return candidates;
72
+ }
73
+ /**
74
+ * @returns {any}
75
+ */
76
+ export function loadNativeAddon() {
77
+ if (_nativeAddon !== undefined) {
78
+ if (!_nativeAddon) {
79
+ throw new Error('vmz native addon missing — run `pnpm napi:build`');
80
+ }
81
+ return _nativeAddon;
82
+ }
83
+ try {
84
+ const envPath = (typeof process.env.VMZ_NATIVE_NODE === 'string' && process.env.VMZ_NATIVE_NODE.trim()) || '';
85
+ if (envPath) {
86
+ _nativeAddon = require(path.resolve(envPath));
87
+ return _nativeAddon;
88
+ }
89
+ for (const p of nativeCandidatePaths()) {
90
+ if (existsSync(p)) {
91
+ _nativeAddon = require(p);
92
+ return _nativeAddon;
93
+ }
94
+ }
95
+ _nativeAddon = null;
96
+ }
97
+ catch {
98
+ _nativeAddon = null;
99
+ }
100
+ if (!_nativeAddon) {
101
+ throw new Error('vmz native addon missing — run `pnpm napi:build`');
102
+ }
103
+ return _nativeAddon;
104
+ }
105
+ /**
106
+ * @param {string} fnName
107
+ * @returns {any}
108
+ */
109
+ export function requireNativeFn(fnName) {
110
+ const native = loadNativeAddon();
111
+ const fn = native[fnName];
112
+ if (typeof fn !== 'function') {
113
+ throw new Error(`vmz native addon missing ${fnName} — run \`pnpm napi:build\``);
114
+ }
115
+ return fn;
116
+ }
@@ -24,10 +24,70 @@ import path from 'node:path';
24
24
  import { fileURLToPath, pathToFileURL } from 'node:url';
25
25
  import { createRenderHost } from './render-host.js';
26
26
  import { listClientComponents } from './list-client-components.js';
27
+ import { loadNativeAddon } from './native-addon.js';
27
28
  import { resolveRouteLayoutChain } from './route-layout-chain.js';
28
29
  import { handleNodeRequest, setRoutes, setServerModuleResolver } from './vmz-runtime.js';
29
30
  const require = createRequire(import.meta.url);
30
31
  const distDir = process.env.VMZ_DIST ? path.resolve(process.env.VMZ_DIST) : path.dirname(fileURLToPath(import.meta.url));
32
+ /** App project root for bare import / JSON resolve (dev SSR ≡ build node_modules). */
33
+ function projectRootForResolve() {
34
+ const fromEnv = typeof process.env.VMZ_PROJECT_ROOT === 'string' ? process.env.VMZ_PROJECT_ROOT.trim() : '';
35
+ if (fromEnv)
36
+ return path.resolve(fromEnv);
37
+ return process.cwd();
38
+ }
39
+ /** @type {ReturnType<typeof createRequire> | null} */
40
+ let appPackageRequire = null;
41
+ function appPackageRequireResolve() {
42
+ if (!appPackageRequire) {
43
+ const root = projectRootForResolve();
44
+ const pkg = path.join(root, 'package.json');
45
+ appPackageRequire = existsSync(pkg) ? createRequire(pkg) : require;
46
+ }
47
+ return appPackageRequire;
48
+ }
49
+ /**
50
+ * Dev/prod serve-host: resolve workspace peers + JSON imports from the app package root.
51
+ * Dist-relative ESM cannot see app `node_modules` without this hook.
52
+ */
53
+ function installAppModuleResolveHooks() {
54
+ registerHooks({
55
+ resolve(specifier, context, nextResolve) {
56
+ if (!specifier ||
57
+ specifier.startsWith('.') ||
58
+ specifier.startsWith('node:') ||
59
+ specifier.startsWith('file:') ||
60
+ specifier.startsWith('#')) {
61
+ return nextResolve(specifier, context);
62
+ }
63
+ try {
64
+ const resolved = appPackageRequireResolve().resolve(specifier);
65
+ return { url: pathToFileURL(resolved).href, shortCircuit: true };
66
+ }
67
+ catch {
68
+ return nextResolve(specifier, context);
69
+ }
70
+ },
71
+ load(url, context, nextLoad) {
72
+ const pathOnly = url.split('?')[0].split('#')[0];
73
+ if (!pathOnly.endsWith('.json'))
74
+ return nextLoad(url, context);
75
+ try {
76
+ const filePath = fileURLToPath(pathOnly);
77
+ const raw = readFileSync(filePath, 'utf8');
78
+ return {
79
+ format: 'module',
80
+ shortCircuit: true,
81
+ source: `export default ${raw}`,
82
+ };
83
+ }
84
+ catch {
85
+ return nextLoad(url, context);
86
+ }
87
+ },
88
+ });
89
+ }
90
+ installAppModuleResolveHooks();
31
91
  const host = process.env.VMZ_HOST || '127.0.0.1';
32
92
  const port = Number(process.env.VMZ_PORT || process.env.PORT || 5173);
33
93
  const isDev = process.env.VMZ_DEV === '1' || process.env.VMZ_DEV === 'true';
@@ -102,12 +162,6 @@ let shuttingDown = false;
102
162
  let ready = false;
103
163
  /** @type {{ message: string, stack?: string, at: number } | null} */
104
164
  let lastDevError = null;
105
- /**
106
- * Native CodeGenerators handle — must be declared before top-level `await softReload()`
107
- * (TDZ: requireNativeGenerator may run during that await).
108
- * @type {any}
109
- */
110
- let _nativeGen;
111
165
  setServerModuleResolver((moduleId) => {
112
166
  const rel = moduleId.replace(/^#server\//, '') + '.js';
113
167
  return bustUrl(pathToFileURL(path.join(distDir, '#server', rel)).href);
@@ -341,7 +395,7 @@ function normalizeActionResult(acted) {
341
395
  * @param {string} marker
342
396
  */
343
397
  async function* emitAccessShell(marker) {
344
- const native = requireNativeGenerator();
398
+ const native = loadNativeAddon();
345
399
  if (typeof native.generateHtmlShell !== 'function') {
346
400
  throw new Error('vmz native addon missing generateHtmlShell — rebuild with `pnpm napi:build`');
347
401
  }
@@ -547,7 +601,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
547
601
  }
548
602
  if (signal?.aborted)
549
603
  return;
550
- const native = requireNativeGenerator();
604
+ const native = loadNativeAddon();
551
605
  if (typeof native.generatePageShell !== 'function') {
552
606
  throw new Error('vmz native addon missing generatePageShell — rebuild with `pnpm napi:build`');
553
607
  }
@@ -907,7 +961,7 @@ async function* emitDevErrorHtml(err) {
907
961
  };
908
962
  })();
909
963
  </script>`;
910
- const native = requireNativeGenerator();
964
+ const native = loadNativeAddon();
911
965
  if (typeof native.generateHtmlShell !== 'function') {
912
966
  throw new Error('vmz native addon missing generateHtmlShell — rebuild with `pnpm napi:build`');
913
967
  }
@@ -1323,7 +1377,7 @@ async function runRouteGate(pathname, chunkId) {
1323
1377
  */
1324
1378
  function emitEntryClient(eager, lazy, token) {
1325
1379
  const q = `?t=${token}`;
1326
- const native = requireNativeGenerator();
1380
+ const native = loadNativeAddon();
1327
1381
  if (typeof native.generateServeEntryClient !== 'function') {
1328
1382
  throw new Error('vmz native addon missing generateServeEntryClient — rebuild with `pnpm napi:build`');
1329
1383
  }
@@ -1336,81 +1390,12 @@ function emitEntryClient(eager, lazy, token) {
1336
1390
  */
1337
1391
  function emitEntryEvent(token) {
1338
1392
  const q = `?t=${token}`;
1339
- const native = requireNativeGenerator();
1393
+ const native = loadNativeAddon();
1340
1394
  if (typeof native.generateServeEntryEvent !== 'function') {
1341
1395
  throw new Error('vmz native addon missing generateServeEntryEvent — rebuild with `pnpm napi:build`');
1342
1396
  }
1343
1397
  return native.generateServeEntryEvent(q);
1344
1398
  }
1345
- /**
1346
- * Load vmz N-API CodeGenerators (same discovery as `@vmz/vmz` native-addon).
1347
- * @returns {any}
1348
- */
1349
- function requireNativeGenerator() {
1350
- if (_nativeGen !== undefined) {
1351
- if (!_nativeGen) {
1352
- throw new Error('vmz native addon missing — run `pnpm napi:build` (serve entry printers live in vmz-generator via N-API)');
1353
- }
1354
- return _nativeGen;
1355
- }
1356
- try {
1357
- const envPath = (typeof process.env.VMZ_NATIVE_NODE === 'string' && process.env.VMZ_NATIVE_NODE.trim()) || '';
1358
- if (envPath) {
1359
- _nativeGen = require(path.resolve(envPath));
1360
- return _nativeGen;
1361
- }
1362
- const { platform, arch } = process;
1363
- let triple = `${platform}-${arch}`;
1364
- if (platform === 'win32' && arch === 'x64')
1365
- triple = 'win32-x64-msvc';
1366
- else if (platform === 'win32' && arch === 'arm64')
1367
- triple = 'win32-arm64-msvc';
1368
- else if (platform === 'darwin' && arch === 'arm64')
1369
- triple = 'darwin-arm64';
1370
- else if (platform === 'darwin' && arch === 'x64')
1371
- triple = 'darwin-x64';
1372
- else if (platform === 'linux' && arch === 'x64')
1373
- triple = 'linux-x64-gnu';
1374
- else if (platform === 'linux' && arch === 'arm64')
1375
- triple = 'linux-arm64-gnu';
1376
- const short = triple === 'win32-x64-msvc'
1377
- ? 'win32-x64'
1378
- : triple === 'win32-arm64-msvc'
1379
- ? 'win32-arm64'
1380
- : triple === 'linux-x64-gnu'
1381
- ? 'linux-x64'
1382
- : triple === 'linux-arm64-gnu'
1383
- ? 'linux-arm64'
1384
- : triple;
1385
- const name = `@vmz/vmz-${short}`;
1386
- /** @type {string[]} */
1387
- const candidates = [];
1388
- try {
1389
- const resolved = require.resolve(`${name}/package.json`);
1390
- const dir = path.dirname(resolved);
1391
- candidates.push(path.join(dir, `vmz.${triple}.node`), path.join(dir, 'vmz.node'));
1392
- }
1393
- catch {
1394
- /* optional */
1395
- }
1396
- const here = path.dirname(fileURLToPath(import.meta.url));
1397
- candidates.push(path.join(here, 'node_modules', name, `vmz.${triple}.node`), path.join(here, 'node_modules', name, 'vmz.node'), path.join(here, '..', 'node_modules', name, `vmz.${triple}.node`), path.join(here, '..', 'node_modules', name, 'vmz.node'));
1398
- for (const p of candidates) {
1399
- if (existsSync(p)) {
1400
- _nativeGen = require(p);
1401
- return _nativeGen;
1402
- }
1403
- }
1404
- _nativeGen = null;
1405
- }
1406
- catch {
1407
- _nativeGen = null;
1408
- }
1409
- if (!_nativeGen) {
1410
- throw new Error('vmz native addon missing — run `pnpm napi:build` (serve entry printers live in vmz-generator via N-API)');
1411
- }
1412
- return _nativeGen;
1413
- }
1414
1399
  /**
1415
1400
  * Style Theme cookie / localStorage key (host contract, not a second theme API).
1416
1401
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vmz/core",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "description": "VMZ production runtime core — DOM / SSR / HTTP / WriteBarrier (no compiler)",
6
6
  "exports": {
@@ -57,6 +57,14 @@
57
57
  "dependencies": {
58
58
  "linkedom": "^0.18.13"
59
59
  },
60
+ "optionalDependencies": {
61
+ "@vmz/vmz-win32-x64": "0.1.14",
62
+ "@vmz/vmz-win32-arm64": "0.1.14",
63
+ "@vmz/vmz-darwin-x64": "0.1.14",
64
+ "@vmz/vmz-darwin-arm64": "0.1.14",
65
+ "@vmz/vmz-linux-x64": "0.1.14",
66
+ "@vmz/vmz-linux-arm64": "0.1.14"
67
+ },
60
68
  "publishConfig": {
61
69
  "access": "public"
62
70
  },