relaxnative 0.1.0-beta.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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +592 -0
  3. package/dist/chunk-22W76CYR.js +607 -0
  4. package/dist/chunk-24NXCU65.js +254 -0
  5. package/dist/chunk-2APMRURB.js +65 -0
  6. package/dist/chunk-2CHBHJPT.js +607 -0
  7. package/dist/chunk-2I4JHZI7.js +287 -0
  8. package/dist/chunk-2JOHYYQO.js +607 -0
  9. package/dist/chunk-3GW77EWF.js +505 -0
  10. package/dist/chunk-5J5CAKCD.js +266 -0
  11. package/dist/chunk-5NTDZ7YZ.js +377 -0
  12. package/dist/chunk-5TA6MROS.js +529 -0
  13. package/dist/chunk-5WVEBKMJ.js +1019 -0
  14. package/dist/chunk-6O5TIEEI.js +545 -0
  15. package/dist/chunk-6XU5DETO.js +896 -0
  16. package/dist/chunk-7BIZ6P3B.js +176 -0
  17. package/dist/chunk-7DKO777J.js +285 -0
  18. package/dist/chunk-7JYWUH4Y.js +268 -0
  19. package/dist/chunk-7NMCEP2V.js +756 -0
  20. package/dist/chunk-A7N4YBP2.js +379 -0
  21. package/dist/chunk-AZTCDV6R.js +572 -0
  22. package/dist/chunk-B34XEGM6.js +559 -0
  23. package/dist/chunk-BFHBLVXW.js +607 -0
  24. package/dist/chunk-BLOJ33LO.js +65 -0
  25. package/dist/chunk-BYPXCWTI.js +375 -0
  26. package/dist/chunk-C4KJD2AN.js +1044 -0
  27. package/dist/chunk-CJALJTRQ.js +814 -0
  28. package/dist/chunk-D4PK367Z.js +627 -0
  29. package/dist/chunk-DCWBZPEV.js +287 -0
  30. package/dist/chunk-DI7KSUEC.js +676 -0
  31. package/dist/chunk-DQ2KXIOO.js +665 -0
  32. package/dist/chunk-DV5STE3W.js +986 -0
  33. package/dist/chunk-EG5KNEKP.js +1027 -0
  34. package/dist/chunk-EOA2OWFA.js +1020 -0
  35. package/dist/chunk-ES3B6EZJ.js +56 -0
  36. package/dist/chunk-ETIXNPU5.js +741 -0
  37. package/dist/chunk-EUZBU2H7.js +824 -0
  38. package/dist/chunk-F6V7XDEB.js +150 -0
  39. package/dist/chunk-FNJKUFNF.js +1019 -0
  40. package/dist/chunk-FZB37DWL.js +453 -0
  41. package/dist/chunk-G3NDHZNZ.js +453 -0
  42. package/dist/chunk-G4YR34LE.js +410 -0
  43. package/dist/chunk-GU4XXISM.js +264 -0
  44. package/dist/chunk-GVPSQXGJ.js +1027 -0
  45. package/dist/chunk-HD5C4RNU.js +676 -0
  46. package/dist/chunk-HDIVY47T.js +287 -0
  47. package/dist/chunk-HFLRTDNK.js +985 -0
  48. package/dist/chunk-HGWRCVQ5.js +287 -0
  49. package/dist/chunk-HRG3SVKK.js +995 -0
  50. package/dist/chunk-HUGFULJ3.js +1027 -0
  51. package/dist/chunk-IDYSBXYS.js +344 -0
  52. package/dist/chunk-ISDDUQVI.js +1019 -0
  53. package/dist/chunk-IZ632ZCJ.js +286 -0
  54. package/dist/chunk-J5XI4L52.js +218 -0
  55. package/dist/chunk-JTIO7BUH.js +582 -0
  56. package/dist/chunk-JTWSFMF2.js +1020 -0
  57. package/dist/chunk-K5TV62T4.js +736 -0
  58. package/dist/chunk-K7MTG53V.js +985 -0
  59. package/dist/chunk-KGLZB3H2.js +676 -0
  60. package/dist/chunk-KYAB35P5.js +741 -0
  61. package/dist/chunk-KYDW3YVX.js +453 -0
  62. package/dist/chunk-L3MEMPRH.js +361 -0
  63. package/dist/chunk-LFTO3Z7N.js +757 -0
  64. package/dist/chunk-LLZ4I4OR.js +405 -0
  65. package/dist/chunk-LMRUM4U4.js +207 -0
  66. package/dist/chunk-LT5OGU6T.js +559 -0
  67. package/dist/chunk-LZQQOC3M.js +741 -0
  68. package/dist/chunk-LZYUNF6Q.js +1017 -0
  69. package/dist/chunk-MCTPVW4G.js +453 -0
  70. package/dist/chunk-MGLWXBIB.js +65 -0
  71. package/dist/chunk-MLKGABMK.js +9 -0
  72. package/dist/chunk-MTE6XDGC.js +541 -0
  73. package/dist/chunk-NDJUNDAE.js +676 -0
  74. package/dist/chunk-NG7SNFUD.js +1027 -0
  75. package/dist/chunk-ONVWKYK7.js +739 -0
  76. package/dist/chunk-OVMTFGE7.js +1042 -0
  77. package/dist/chunk-P5RQPRJ4.js +741 -0
  78. package/dist/chunk-PFABSW6Y.js +401 -0
  79. package/dist/chunk-PVVUJA2M.js +65 -0
  80. package/dist/chunk-Q3CDTGTX.js +676 -0
  81. package/dist/chunk-QKAKWDOQ.js +967 -0
  82. package/dist/chunk-QMPRDU6I.js +598 -0
  83. package/dist/chunk-R5U7XKVJ.js +16 -0
  84. package/dist/chunk-RB6RHB6C.js +254 -0
  85. package/dist/chunk-RD2K7ODW.js +175 -0
  86. package/dist/chunk-RHBHJND2.js +582 -0
  87. package/dist/chunk-RUP6POSE.js +453 -0
  88. package/dist/chunk-RYNSM23L.js +582 -0
  89. package/dist/chunk-S72S7XXS.js +255 -0
  90. package/dist/chunk-SDV5AAPW.js +213 -0
  91. package/dist/chunk-STXQPXUY.js +242 -0
  92. package/dist/chunk-TUSF5AEG.js +140 -0
  93. package/dist/chunk-V74SNTE6.js +736 -0
  94. package/dist/chunk-VHM5XETC.js +453 -0
  95. package/dist/chunk-VKQIXLNL.js +273 -0
  96. package/dist/chunk-VPG23Z7Y.js +545 -0
  97. package/dist/chunk-VSP234PR.js +627 -0
  98. package/dist/chunk-WH4JPUWF.js +598 -0
  99. package/dist/chunk-WLAUJL3K.js +409 -0
  100. package/dist/chunk-WXCN2QJ7.js +350 -0
  101. package/dist/chunk-X3JZKLJC.js +896 -0
  102. package/dist/chunk-X7SAP7FC.js +582 -0
  103. package/dist/chunk-XEH6PRYE.js +968 -0
  104. package/dist/chunk-XI65CAQV.js +211 -0
  105. package/dist/chunk-Y7OSHR6W.js +235 -0
  106. package/dist/chunk-YN4WUMVD.js +1020 -0
  107. package/dist/chunk-YUWJ2C4Y.js +1020 -0
  108. package/dist/chunk-YXLBPWNU.js +263 -0
  109. package/dist/chunk-YYJJHO7R.js +407 -0
  110. package/dist/chunk-Z2RBHUIH.js +757 -0
  111. package/dist/chunk-Z6G3KIOM.js +1027 -0
  112. package/dist/chunk-ZPPXCDSH.js +361 -0
  113. package/dist/chunk-ZRTY24SZ.js +582 -0
  114. package/dist/chunk-ZSDFBCQG.js +741 -0
  115. package/dist/cli.d.ts +1 -0
  116. package/dist/cli.js +339 -0
  117. package/dist/esmLoader.d.ts +10 -0
  118. package/dist/esmLoader.js +112 -0
  119. package/dist/index.d.ts +407 -0
  120. package/dist/index.js +126 -0
  121. package/dist/memory/memory.selftest.d.ts +2 -0
  122. package/dist/memory/memory.selftest.js +25 -0
  123. package/dist/worker/processEntry.d.ts +2 -0
  124. package/dist/worker/processEntry.js +160 -0
  125. package/dist/worker/workerEntry.d.ts +2 -0
  126. package/dist/worker/workerEntry.js +27 -0
  127. package/native/examples/add.c +6 -0
  128. package/native/examples/add_test.c +23 -0
  129. package/native/relaxnative_test.h +36 -0
  130. package/package.json +81 -0
@@ -0,0 +1,453 @@
1
+ import {
2
+ compileWithCache,
3
+ detectCompilers,
4
+ detectLanguage,
5
+ loadNative,
6
+ loadNativeWithBindings,
7
+ parseNativeSource
8
+ } from "./chunk-QMPRDU6I.js";
9
+ import {
10
+ loadFfi
11
+ } from "./chunk-ZPPXCDSH.js";
12
+
13
+ // src/registry/registryPaths.ts
14
+ import { join, resolve } from "path";
15
+ function getProjectRoot(cwd = process.cwd()) {
16
+ return cwd;
17
+ }
18
+ function getRegistryRoot(projectRoot = getProjectRoot()) {
19
+ return join(projectRoot, "native", "registry");
20
+ }
21
+ function getInstalledPackageDir(pkg, projectRoot = getProjectRoot()) {
22
+ return join(getRegistryRoot(projectRoot), pkg);
23
+ }
24
+ function resolveRegistryImport(specifier, projectRoot = getProjectRoot()) {
25
+ const prefix = "relaxnative/";
26
+ if (!specifier.startsWith(prefix)) return null;
27
+ const pkg = specifier.slice(prefix.length);
28
+ return resolve(getInstalledPackageDir(pkg, projectRoot));
29
+ }
30
+
31
+ // src/registry/relaxJson.ts
32
+ import { readFileSync } from "fs";
33
+ import { join as join2 } from "path";
34
+ function readRelaxJson(pkgDir) {
35
+ const jsonPath = join2(pkgDir, "relax.json");
36
+ const raw = readFileSync(jsonPath, "utf8");
37
+ const parsed = JSON.parse(raw);
38
+ if (!parsed?.name || !parsed?.version || !Array.isArray(parsed?.exports)) {
39
+ throw new Error(`Invalid relax.json at ${jsonPath}`);
40
+ }
41
+ return parsed;
42
+ }
43
+
44
+ // src/registry/installer.ts
45
+ import {
46
+ mkdirSync,
47
+ readFileSync as readFileSync3,
48
+ rmSync,
49
+ writeFileSync,
50
+ existsSync,
51
+ readdirSync
52
+ } from "fs";
53
+ import { join as join3 } from "path";
54
+
55
+ // src/registry/staticScan.ts
56
+ import { readFileSync as readFileSync2 } from "fs";
57
+ function staticScanNativeSource(sourcePath) {
58
+ const src = readFileSync2(sourcePath, "utf8");
59
+ const lines = src.split(/\r?\n/);
60
+ const rules = [
61
+ {
62
+ rule: "process-spawn",
63
+ re: /\b(system|popen|fork|execv|execve|execvp|execvpe|execl|execlp|CreateProcessW|WinExec)\b/,
64
+ message: "Potential process execution API found"
65
+ },
66
+ {
67
+ rule: "raw-syscall",
68
+ re: /\b(syscall|__syscall)\b/,
69
+ message: "Raw syscall usage found"
70
+ },
71
+ {
72
+ rule: "file-io",
73
+ re: /\b(fopen|open|CreateFileW|unlink|remove|rename)\b/,
74
+ message: "Potential filesystem API found"
75
+ },
76
+ {
77
+ rule: "network",
78
+ re: /\b(socket|connect|bind|listen|accept|recv|send|getaddrinfo)\b/,
79
+ message: "Potential network API found"
80
+ },
81
+ {
82
+ rule: "w-x-memory",
83
+ re: /\b(mprotect|VirtualProtect|PROT_EXEC|PAGE_EXECUTE_READWRITE)\b/,
84
+ message: "Writable/executable memory pattern found"
85
+ },
86
+ {
87
+ rule: "dynamic-loader",
88
+ re: /\b(dlopen|LoadLibraryA|LoadLibraryW)\b/,
89
+ message: "Dynamic loader API found"
90
+ }
91
+ ];
92
+ const findings = [];
93
+ for (let i = 0; i < lines.length; i++) {
94
+ const line = lines[i];
95
+ for (const r of rules) {
96
+ if (r.re.test(line)) {
97
+ findings.push({ rule: r.rule, message: r.message, line: i + 1 });
98
+ }
99
+ }
100
+ }
101
+ return findings;
102
+ }
103
+
104
+ // src/registry/installer.ts
105
+ function installPackage(pkgSpecifier, projectRoot = process.cwd()) {
106
+ const warnings = [];
107
+ const registryRoot = getRegistryRoot(projectRoot);
108
+ mkdirSync(registryRoot, { recursive: true });
109
+ const isFile = pkgSpecifier.startsWith("file:");
110
+ if (!isFile) {
111
+ throw new Error(
112
+ `Only file: installs are supported right now (deterministic, offline). Got: ${pkgSpecifier}`
113
+ );
114
+ }
115
+ const srcDir = pkgSpecifier.slice("file:".length);
116
+ const manifest = readRelaxJson(srcDir);
117
+ const destDir = getInstalledPackageDir(manifest.name, projectRoot);
118
+ rmSync(destDir, { recursive: true, force: true });
119
+ mkdirSync(destDir, { recursive: true });
120
+ writeFileSync(join3(destDir, "relax.json"), readFileSync3(join3(srcDir, "relax.json")));
121
+ for (const exp of manifest.exports) {
122
+ const srcPath = join3(srcDir, exp.source);
123
+ const dstPath = join3(destDir, exp.source);
124
+ mkdirSync(join3(dstPath, ".."), { recursive: true });
125
+ if (!existsSync(srcPath)) {
126
+ throw new Error(`Missing export source: ${srcPath}`);
127
+ }
128
+ const findings = staticScanNativeSource(srcPath);
129
+ for (const f of findings) {
130
+ warnings.push(`${exp.source}:${f.line ?? "?"} ${f.rule}: ${f.message}`);
131
+ }
132
+ writeFileSync(dstPath, readFileSync3(srcPath));
133
+ }
134
+ return { pkg: manifest.name, dir: destDir, warnings };
135
+ }
136
+ function removePackage(pkg, projectRoot = process.cwd()) {
137
+ const destDir = getInstalledPackageDir(pkg, projectRoot);
138
+ rmSync(destDir, { recursive: true, force: true });
139
+ }
140
+ function listPackages(projectRoot = process.cwd()) {
141
+ const root = getRegistryRoot(projectRoot);
142
+ if (!existsSync(root)) return [];
143
+ return readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
144
+ }
145
+ function updateIndex(projectRoot = process.cwd()) {
146
+ const root = getRegistryRoot(projectRoot);
147
+ if (!existsSync(root)) return;
148
+ const pkgs = listPackages(projectRoot);
149
+ writeFileSync(join3(root, ".index"), pkgs.join("\n") + (pkgs.length ? "\n" : ""));
150
+ }
151
+
152
+ // src/nativeTestHarness.ts
153
+ import { readdirSync as readdirSync2, existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
154
+ import { extname, join as join4 } from "path";
155
+ var NATIVE_EXTS = /* @__PURE__ */ new Set([".c", ".cpp", ".cc", ".cxx", ".rs"]);
156
+ function isNativeFile(p) {
157
+ return NATIVE_EXTS.has(extname(p));
158
+ }
159
+ function discoverNativeTestFiles(rootDir) {
160
+ const out = [];
161
+ function walk(dir) {
162
+ for (const ent of readdirSync2(dir, { withFileTypes: true })) {
163
+ const p = join4(dir, ent.name);
164
+ if (ent.isDirectory()) walk(p);
165
+ else if (ent.isFile() && isNativeFile(p)) out.push(p);
166
+ }
167
+ }
168
+ if (!existsSync2(rootDir)) return [];
169
+ walk(rootDir);
170
+ out.sort((a, b) => {
171
+ const at = /_test\./.test(a) ? 0 : 1;
172
+ const bt = /_test\./.test(b) ? 0 : 1;
173
+ return at - bt || a.localeCompare(b);
174
+ });
175
+ return out;
176
+ }
177
+ function discoverNativeTestsFromFile(sourcePath) {
178
+ const src = readFileSync4(sourcePath, "utf8");
179
+ const lines = src.split(/\r?\n/);
180
+ const out = [];
181
+ const re = /^(?:\s*)(const\s+char\s*\*|int)\s+(test_[A-Za-z0-9_]+)\s*\(/;
182
+ for (let i = 0; i < lines.length; i++) {
183
+ const m = lines[i].match(re);
184
+ if (!m) continue;
185
+ const ret = m[1];
186
+ const name = m[2];
187
+ const kind = /^int\b/.test(ret) ? "int" : "cstring";
188
+ out.push({ name, sourcePath, line: i + 1, kind });
189
+ }
190
+ if (out.length) return out;
191
+ const language = detectLanguage(sourcePath);
192
+ const bindings = parseNativeSource(sourcePath, language);
193
+ const tests = [];
194
+ for (const [name, fn] of Object.entries(bindings.functions ?? {})) {
195
+ if (!name.startsWith("test_")) continue;
196
+ const ret = fn.returns;
197
+ const kind = ret === "int" || ret === "uint" || ret === "long" || ret === "size_t" ? "int" : "cstring";
198
+ tests.push({
199
+ name,
200
+ sourcePath,
201
+ line: fn.sourceLine,
202
+ kind
203
+ });
204
+ }
205
+ return tests;
206
+ }
207
+ function discoverNativeTests(rootDir) {
208
+ const files = discoverNativeTestFiles(rootDir);
209
+ const all = [];
210
+ for (const f of files) {
211
+ all.push(...discoverNativeTestsFromFile(f));
212
+ }
213
+ return all;
214
+ }
215
+ function formatLocation(sourcePath, line) {
216
+ if (!line) return sourcePath;
217
+ return `${sourcePath}:${line}`;
218
+ }
219
+ function formatNativeTestResults(results) {
220
+ const lines = [];
221
+ for (const r of results) {
222
+ if (r.ok) {
223
+ lines.push(`\u2713 ${r.name}`);
224
+ } else {
225
+ const loc = formatLocation(r.sourcePath, r.line);
226
+ const msg = r.message ? ` (${r.message})` : "";
227
+ lines.push(`\u2717 ${r.name}${msg}`);
228
+ lines.push(` at ${loc}`);
229
+ }
230
+ }
231
+ const passed = results.filter((r) => r.ok).length;
232
+ const failed = results.length - passed;
233
+ lines.push("");
234
+ lines.push(`${passed} passed, ${failed} failed`);
235
+ return lines.join("\n");
236
+ }
237
+ async function runNativeTests(rootDir, opts) {
238
+ const isolation = opts?.isolation ?? "in-process";
239
+ const tests = discoverNativeTests(rootDir);
240
+ if (!tests.length) {
241
+ return { results: [], exitCode: 0 };
242
+ }
243
+ const { c, rust, platform } = detectCompilers();
244
+ const byFile = /* @__PURE__ */ new Map();
245
+ for (const t of tests) {
246
+ const arr = byFile.get(t.sourcePath) ?? [];
247
+ arr.push(t);
248
+ byFile.set(t.sourcePath, arr);
249
+ }
250
+ const results = [];
251
+ for (const [sourcePath, cases] of byFile) {
252
+ const language = detectLanguage(sourcePath);
253
+ const compiler = language === "rust" ? rust : c;
254
+ if (!compiler) throw new Error(`No compiler for ${language}`);
255
+ const compileRes = compileWithCache(compiler, platform, {
256
+ sourcePath,
257
+ outDir: ".cache/native"
258
+ });
259
+ let api;
260
+ if (isolation === "in-process") {
261
+ const bindings = parseNativeSource(sourcePath, language);
262
+ api = loadFfi(compileRes.outputPath, bindings);
263
+ } else {
264
+ const synth = {
265
+ functions: Object.fromEntries(
266
+ cases.map((tc) => [
267
+ tc.name,
268
+ {
269
+ name: tc.name,
270
+ // Note: mapType supports "char*" (and "cstring" used to), but
271
+ // some environments/version combos reject "cstring" when sent
272
+ // across worker/process boundaries. Use a stable alias.
273
+ returns: tc.kind === "int" ? "int" : "char*",
274
+ args: [],
275
+ mode: "sync",
276
+ cost: "low"
277
+ }
278
+ ])
279
+ )
280
+ };
281
+ const { mod } = await loadNativeWithBindings(sourcePath, {
282
+ isolation,
283
+ mutateBindings(bindings) {
284
+ bindings.functions = synth.functions;
285
+ }
286
+ });
287
+ api = mod;
288
+ }
289
+ for (const tc of cases) {
290
+ const fn = api[tc.name];
291
+ if (typeof fn !== "function") {
292
+ results.push({
293
+ name: tc.name,
294
+ ok: false,
295
+ message: `Function not found: ${tc.name}`,
296
+ sourcePath: tc.sourcePath,
297
+ line: tc.line,
298
+ durationMs: 0
299
+ });
300
+ continue;
301
+ }
302
+ const start = process.hrtime.bigint();
303
+ let ok = false;
304
+ let message;
305
+ try {
306
+ const out = fn();
307
+ const awaited = out instanceof Promise ? await out : out;
308
+ if (tc.kind === "int") {
309
+ ok = Number(awaited) === 0;
310
+ if (!ok) message = `returned ${String(awaited)}`;
311
+ } else {
312
+ ok = awaited == null || String(awaited).length === 0;
313
+ if (!ok) message = String(awaited);
314
+ }
315
+ } catch (err) {
316
+ ok = false;
317
+ message = err?.message ?? String(err);
318
+ }
319
+ const end = process.hrtime.bigint();
320
+ const durationMs = Number(end - start) / 1e6;
321
+ results.push({
322
+ name: tc.name,
323
+ ok,
324
+ message,
325
+ sourcePath: tc.sourcePath,
326
+ line: tc.line,
327
+ durationMs
328
+ });
329
+ }
330
+ }
331
+ const failed = results.some((r) => !r.ok);
332
+ return { results, exitCode: failed ? 1 : 0 };
333
+ }
334
+ function getSourceLine(sourcePath, line) {
335
+ if (!line) return null;
336
+ try {
337
+ const src = readFileSync4(sourcePath, "utf8");
338
+ const lines = src.split(/\r?\n/);
339
+ return lines[line - 1] ?? null;
340
+ } catch {
341
+ return null;
342
+ }
343
+ }
344
+
345
+ // src/benchmark.ts
346
+ function nowNs() {
347
+ return process.hrtime.bigint();
348
+ }
349
+ function nsToMs(ns) {
350
+ return Number(ns) / 1e6;
351
+ }
352
+ function assertSafeRun(iterations) {
353
+ if (iterations > 2e6) {
354
+ throw new Error(
355
+ `Refusing to run benchmark with iterations=${iterations} (cap=2,000,000). Pass a smaller value.`
356
+ );
357
+ }
358
+ }
359
+ async function benchmarkNativeFunction(nativePath, fnName, opts) {
360
+ const iterations = opts?.iterations ?? 1e5;
361
+ const warmup = opts?.warmup ?? Math.min(1e4, Math.max(100, Math.floor(iterations * 0.05)));
362
+ const mode = opts?.mode ?? "sync";
363
+ assertSafeRun(iterations);
364
+ const isolation = mode === "worker" ? "worker" : "in-process";
365
+ const mod = await loadNative(nativePath, { isolation });
366
+ const fn = mod?.[fnName];
367
+ if (typeof fn !== "function") {
368
+ throw new Error(`Function not found: ${fnName}`);
369
+ }
370
+ const args = Array.isArray(opts?.args) ? opts.args : fn.length === 0 ? [] : [1, 2];
371
+ for (let i = 0; i < warmup; i++) {
372
+ const out = fn(...args);
373
+ if (out instanceof Promise) await out;
374
+ }
375
+ const latencies = [];
376
+ const startAll = nowNs();
377
+ for (let i = 0; i < iterations; i++) {
378
+ const t0 = nowNs();
379
+ const out = fn(...args);
380
+ if (out instanceof Promise) await out;
381
+ const t1 = nowNs();
382
+ latencies.push(nsToMs(t1 - t0));
383
+ }
384
+ const endAll = nowNs();
385
+ const totalMs = nsToMs(endAll - startAll);
386
+ let min = Infinity;
387
+ let max = -Infinity;
388
+ let sum = 0;
389
+ for (const l of latencies) {
390
+ if (l < min) min = l;
391
+ if (l > max) max = l;
392
+ sum += l;
393
+ }
394
+ const avg = sum / latencies.length;
395
+ const callsPerSec = iterations / (totalMs / 1e3);
396
+ return {
397
+ fnName,
398
+ mode,
399
+ iterations,
400
+ warmup,
401
+ callsPerSec,
402
+ avgLatencyMs: avg,
403
+ minLatencyMs: min,
404
+ maxLatencyMs: max
405
+ };
406
+ }
407
+ async function benchmarkCompareSyncVsWorker(nativePath, fnName, opts) {
408
+ const sync = await benchmarkNativeFunction(nativePath, fnName, {
409
+ ...opts,
410
+ mode: "sync"
411
+ });
412
+ const worker = await benchmarkNativeFunction(nativePath, fnName, {
413
+ ...opts,
414
+ mode: "worker"
415
+ });
416
+ return { sync, worker };
417
+ }
418
+ function formatBenchmarkResult(r) {
419
+ const f = (n) => Number.isFinite(n) ? n.toFixed(3) : String(n);
420
+ return [
421
+ `${r.fnName} (${r.mode})`,
422
+ ` iterations: ${r.iterations} (warmup ${r.warmup})`,
423
+ ` calls/sec: ${f(r.callsPerSec)}`,
424
+ ` avg ms: ${f(r.avgLatencyMs)}`,
425
+ ` min ms: ${f(r.minLatencyMs)}`,
426
+ ` max ms: ${f(r.maxLatencyMs)}`
427
+ ].join("\n");
428
+ }
429
+ function formatBenchmarkCompare(res) {
430
+ return [formatBenchmarkResult(res.sync), "", formatBenchmarkResult(res.worker)].join("\n");
431
+ }
432
+
433
+ export {
434
+ getProjectRoot,
435
+ getRegistryRoot,
436
+ getInstalledPackageDir,
437
+ resolveRegistryImport,
438
+ readRelaxJson,
439
+ installPackage,
440
+ removePackage,
441
+ listPackages,
442
+ updateIndex,
443
+ discoverNativeTestFiles,
444
+ discoverNativeTestsFromFile,
445
+ discoverNativeTests,
446
+ formatNativeTestResults,
447
+ runNativeTests,
448
+ getSourceLine,
449
+ benchmarkNativeFunction,
450
+ benchmarkCompareSyncVsWorker,
451
+ formatBenchmarkResult,
452
+ formatBenchmarkCompare
453
+ };