// index.js — Greenshades native N-API loader (lazy). // // The Zig backend's napi_register_module_v1 entry point performs a // ServiceCellPool.init that forks 4 child processes. On non-Linux, in // containers without seccomp, or in test runners that don't intend to // exercise the native code, this fork sequence can corrupt glibc's heap // or simply block for ~4 seconds (one blocked read per cell). // // To avoid loading the binary on import (which would run that init at // unpredictable times), the binary is loaded LAZILY — only when a // caller actually accesses an export. Detection at module-load time // uses a synchronous fs stat call to confirm the prebuild exists. // // Env vars: // GREENSHADES_ZIG_NATIVE=1 — prefer a freshly-built zig-out/ binary // over the bundled prebuild // GREENSHADES_ZIG_SKIP=1 — disable native loading entirely (use // synthetic JS-only stubs for tests) 'use strict'; const os = require('os'); const fs = require('fs'); const path = require('path'); function platformArch() { const p = os.platform(); const a = os.arch(); if (p === 'linux' && a === 'x64') return 'x86_64-linux'; if (p === 'linux' && a === 'arm64') return 'aarch64-linux'; return null; } const platformKey = platformArch(); const useBuilt = process.env.GREENSHADES_ZIG_NATIVE === '1'; const skipNative = process.env.GREENSHADES_ZIG_SKIP === '1'; function candidatePath() { if (useBuilt) { const built = path.join(__dirname, '..', 'zig-out', 'greenshades_backend.node'); if (fs.existsSync(built)) return built; const projRoot = path.join(__dirname, '..', 'greenshades_backend.node'); if (fs.existsSync(projRoot)) return projRoot; } if (!platformKey) return null; const prebuild = path.join(__dirname, 'prebuilds', platformKey, 'greenshades_backend.node'); return fs.existsSync(prebuild) ? prebuild : null; } function ensureLoaded() { if (module.exports.__loaded) return true; const candidate = candidatePath(); if (!candidate) return false; const { dlopen } = require('process'); const constants = process.binding('constants'); const RTLD_LAZY = constants.os.dlopen.RTLD_LAZY; const RTLD_LOCAL = constants.os.dlopen.RTLD_LOCAL; try { const mod = { exports: {} }; dlopen(mod, candidate, RTLD_LAZY | RTLD_LOCAL); if (typeof mod.exports.createBackend === 'function') { for (const k of Object.keys(mod.exports)) { module.exports[k] = mod.exports[k]; } module.exports.__loaded = true; return true; } } catch (_) {} return false; } if (!skipNative) { const candidate = candidatePath(); module.exports.__nativeCandidatePath = candidate; module.exports.__nativeCandidatePresent = !!candidate; module.exports.__nativeAvailable = false; module.exports.__loaded = false; } module.exports.__platformKey = platformKey; module.exports.__ensureLoaded = ensureLoaded; function notLoadedError() { return new Error([ 'Greenshades native addon not available for ' + (platformKey || os.platform() + '-' + os.arch()) + '.', '', 'To build from source:', ' 1. Install Zig 0.16+', ' 2. Run: cd zig-native && zig build napi', ' 3. Copy zig-out/greenshades_backend.node to:', ' zig-native/node/prebuilds/' + (platformKey || 'YOUR_PLATFORM') + '/greenshades_backend.node', ].join('\n')); } const handler = { get(target, prop) { if (typeof prop === 'string' && prop.startsWith('__')) { return target[prop]; } if (typeof prop === 'string' && target[prop] === undefined) { if (!ensureLoaded()) throw notLoadedError(); return target[prop]; } return target[prop]; }, }; module.exports = new Proxy(module.exports, handler);