@nudojs/service 4.0.0 → 5.0.0-beta.0

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.
@@ -225,159 +225,84 @@ function builtinProtoMember(className, member) {
225
225
  return table[member] ?? null;
226
226
  }
227
227
 
228
- // src/evaluator/env-loader.ts
229
- import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync } from "fs";
230
- import { resolve as resolvePath, join as joinPath } from "path";
231
- import { pathToFileURL } from "url";
232
- import { createRequire } from "module";
233
- import { createHash } from "crypto";
234
- import { tmpdir } from "os";
235
- import { defineEnv as defineEsEnv } from "@nudojs/env/es";
236
- import { defineEnv as defineWebEnv } from "@nudojs/env/web";
237
- import { defineEnv as defineNodeEnv } from "@nudojs/env/node";
238
- var envFactories = {
239
- es: defineEsEnv,
240
- web: defineWebEnv,
241
- node: defineNodeEnv
242
- };
243
- var impliedDeps = {
244
- web: ["es"],
245
- node: ["es"]
228
+ // src/evaluator/config.ts
229
+ import { readFileSync, existsSync } from "fs";
230
+ import { resolve, dirname, relative, sep } from "path";
231
+
232
+ // src/session-cache-limits.ts
233
+ var DEFAULT_SESSION_CACHE_LIMITS = {
234
+ maxFiles: 64,
235
+ maxFns: 1024,
236
+ maxBRuns: 32
246
237
  };
247
- function resolveEnvNames(names) {
248
- const resolved = /* @__PURE__ */ new Set();
249
- const visit = (name) => {
250
- if (resolved.has(name)) return;
251
- const deps = impliedDeps[name];
252
- if (deps) deps.forEach(visit);
253
- resolved.add(name);
238
+ var HARD_CAP = 65536;
239
+ function clampEntries(n, fallback) {
240
+ if (typeof n !== "number" || !Number.isFinite(n)) return fallback;
241
+ return Math.max(0, Math.min(HARD_CAP, Math.floor(n)));
242
+ }
243
+ function parseLimit(raw, fallback) {
244
+ if (raw === void 0 || raw === "") return fallback;
245
+ if (raw === "off" || raw === "0") return 0;
246
+ const n = Number(raw);
247
+ return clampEntries(n, fallback);
248
+ }
249
+ var explicit = {};
250
+ var fromProject = {};
251
+ var envCache = null;
252
+ function envLimits(env) {
253
+ return {
254
+ maxFiles: parseLimit(env.NUDO_CACHE_MAX_FILES, NaN),
255
+ maxFns: parseLimit(env.NUDO_CACHE_MAX_FNS, NaN),
256
+ maxBRuns: parseLimit(env.NUDO_CACHE_MAX_BRUNS, NaN)
254
257
  };
255
- names.forEach(visit);
256
- return [...resolved];
257
- }
258
- var pathEnvCache = /* @__PURE__ */ new Map();
259
- var pathEnvByPath = /* @__PURE__ */ new Map();
260
- var pathEnvBaseDirs = /* @__PURE__ */ new Set();
261
- function isPathEnvName(name, baseDir) {
262
- if (name in envFactories) return false;
263
- if (name.includes("/") || name.startsWith("./") || name.startsWith("../")) return true;
264
- const resolved = resolvePath(baseDir, name);
265
- return resolved.endsWith(".ts") && existsSync(resolved);
266
- }
267
- function rewriteBareImports(text) {
268
- const require2 = createRequire(import.meta.url);
269
- let rewrote = false;
270
- const out = text.replace(/(["'])(@nudojs\/[a-z0-9-]+)\1/g, (_m, quote, spec) => {
271
- try {
272
- const entry = require2.resolve(spec);
273
- const url = pathToFileURL(entry).href;
274
- rewrote = true;
275
- return `${quote}${url}${quote}`;
276
- } catch {
277
- return `${quote}${spec}${quote}`;
278
- }
279
- });
280
- return rewrote ? out : null;
281
258
  }
282
- async function importPathEnv(resolvedPath, mtimeMs) {
283
- const cacheKey = `${resolvedPath}:${mtimeMs}`;
284
- if (pathEnvCache.has(cacheKey)) return;
285
- let mod = null;
286
- try {
287
- const url = pathToFileURL(resolvedPath).href + `?mtime=${mtimeMs}`;
288
- mod = await import(url);
289
- } catch {
290
- mod = null;
291
- }
292
- if (!mod) {
293
- try {
294
- const text = readFileSync(resolvedPath, "utf-8");
295
- const rewritten = rewriteBareImports(text);
296
- if (rewritten === null) return;
297
- const cacheDir = joinPath(tmpdir(), "nudo-env");
298
- mkdirSync(cacheDir, { recursive: true });
299
- const hash = createHash("md5").update(`${resolvedPath}:${mtimeMs}`).digest("hex").slice(0, 16);
300
- const copyPath = joinPath(cacheDir, `${hash}.ts`);
301
- writeFileSync(copyPath, rewritten, "utf-8");
302
- mod = await import(pathToFileURL(copyPath).href);
303
- } catch {
304
- return;
305
- }
306
- }
307
- if (mod && typeof mod.defineEnv === "function") {
308
- pathEnvCache.set(cacheKey, mod.defineEnv);
309
- pathEnvByPath.set(resolvedPath, {
310
- factory: mod.defineEnv,
311
- mtimeMs
312
- });
313
- }
259
+ function pick(key, env, fallback) {
260
+ const e = env[key];
261
+ if (typeof e === "number" && Number.isFinite(e)) return e;
262
+ const x = explicit[key];
263
+ if (typeof x === "number" && Number.isFinite(x)) return clampEntries(x, fallback);
264
+ const p = fromProject[key];
265
+ if (typeof p === "number" && Number.isFinite(p)) return clampEntries(p, fallback);
266
+ return fallback;
314
267
  }
315
- function lookupPathEnv(name) {
316
- if (pathEnvByPath.size === 0) return void 0;
317
- const candidates = name.startsWith("/") ? [name] : [name, ...[...pathEnvBaseDirs].map((d) => resolvePath(d, name))];
318
- for (const candidate of candidates) {
319
- const hit = pathEnvByPath.get(candidate);
320
- if (!hit) continue;
321
- try {
322
- const { mtimeMs } = statSync(candidate);
323
- if (mtimeMs !== hit.mtimeMs) {
324
- pathEnvByPath.delete(candidate);
325
- return void 0;
326
- }
327
- } catch {
328
- pathEnvByPath.delete(candidate);
329
- return void 0;
330
- }
331
- return hit.factory;
332
- }
333
- return void 0;
268
+ function getSessionCacheLimits(env = process.env) {
269
+ if (envCache === null) envCache = envLimits(env);
270
+ return {
271
+ maxFiles: pick("maxFiles", envCache, DEFAULT_SESSION_CACHE_LIMITS.maxFiles),
272
+ maxFns: pick("maxFns", envCache, DEFAULT_SESSION_CACHE_LIMITS.maxFns),
273
+ maxBRuns: pick("maxBRuns", envCache, DEFAULT_SESSION_CACHE_LIMITS.maxBRuns)
274
+ };
334
275
  }
335
- function clearPathEnvCaches() {
336
- pathEnvCache.clear();
337
- pathEnvByPath.clear();
338
- pathEnvBaseDirs.clear();
339
- }
340
- async function preloadPathEnvs(envNames, baseDir) {
341
- pathEnvBaseDirs.add(baseDir);
342
- for (const name of envNames) {
343
- if (!isPathEnvName(name, baseDir)) continue;
344
- const resolved = resolvePath(baseDir, name);
345
- if (!existsSync(resolved)) continue;
346
- try {
347
- const { mtimeMs } = statSync(resolved);
348
- await importPathEnv(resolved, mtimeMs);
349
- } catch {
350
- }
351
- }
276
+ function setSessionCacheLimits(partial) {
277
+ explicit = partial ? { ...partial } : {};
278
+ return getSessionCacheLimits();
352
279
  }
353
- async function loadEnvsAsync(envNames, globalEnv, baseDir = process.cwd()) {
354
- await preloadPathEnvs(envNames, baseDir);
355
- return loadEnvs(envNames, globalEnv);
356
- }
357
- function loadEnvs(envNames, globalEnv) {
358
- const allModules = {};
359
- const allGlobals = {};
360
- const resolved = resolveEnvNames(envNames);
361
- for (const name of resolved) {
362
- const factory = envFactories[name] ?? lookupPathEnv(name);
363
- if (!factory) continue;
364
- const def = factory();
365
- for (const [key, value] of Object.entries(def.globals)) {
366
- allGlobals[key] = value;
367
- globalEnv.bind(key, value);
368
- }
369
- if (def.modules) {
370
- for (const [modName, exports] of Object.entries(def.modules)) {
371
- allModules[modName] = { ...allModules[modName], ...exports };
372
- }
373
- }
374
- }
375
- return { modules: allModules, globals: allGlobals };
280
+ function setSessionCacheFromProject(partial) {
281
+ fromProject = partial ? { ...partial } : {};
282
+ }
283
+ function resetSessionCacheLimitState() {
284
+ explicit = {};
285
+ fromProject = {};
286
+ envCache = null;
376
287
  }
377
288
 
378
289
  // src/evaluator/config.ts
379
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
380
- import { resolve, dirname, relative, sep } from "path";
290
+ import { setBForkBudgetLimit, getBForkBudgetLimit, MAX_B_TOTAL_FORKS } from "@nudojs/core/internal";
291
+ function checkConfig(config) {
292
+ const raw = config?.check;
293
+ const rawEntry = raw?.entryThrows;
294
+ let entryThrows = "error";
295
+ if (rawEntry === "off" || rawEntry === "warning" || rawEntry === "error") {
296
+ entryThrows = rawEntry;
297
+ } else if (rawEntry !== void 0) {
298
+ process.stderr?.write?.(
299
+ `nudo.check.entryThrows: invalid value ${JSON.stringify(rawEntry)} (expected error|warning|off); using error
300
+ `
301
+ );
302
+ }
303
+ const ignore = Array.isArray(raw?.ignoreThrows) ? raw.ignoreThrows.filter((s) => typeof s === "string" && s.length > 0) : [];
304
+ return { entryThrows, ignoreThrows: ignore };
305
+ }
381
306
  var DEFAULT_ANALYSIS_EXCLUDE = [
382
307
  "**/node_modules/**",
383
308
  "**/dist/**",
@@ -406,9 +331,24 @@ function analysisConfig(config) {
406
331
  mode,
407
332
  diagnostics,
408
333
  callSiteBudget,
409
- evalMissingSlot: raw?.evalMissingSlot === "warning" ? "warning" : "off"
334
+ evalMissingSlot: raw?.evalMissingSlot === "warning" ? "warning" : "off",
335
+ maxForks: parseMaxForks(raw?.maxForks) ?? MAX_B_TOTAL_FORKS
410
336
  };
411
337
  }
338
+ function parseMaxForks(raw) {
339
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 1) return void 0;
340
+ return Math.floor(raw);
341
+ }
342
+ function applyBForkBudgetFromConfig(config, env = process.env) {
343
+ const fromEnv = parseMaxForks(
344
+ env.NUDO_MAX_FORKS === void 0 ? void 0 : Number(env.NUDO_MAX_FORKS)
345
+ );
346
+ const fromCfg = parseMaxForks(config?.analysis?.maxForks);
347
+ return setBForkBudgetLimit(fromEnv ?? fromCfg ?? MAX_B_TOTAL_FORKS);
348
+ }
349
+ function currentBForkBudgetLimit() {
350
+ return getBForkBudgetLimit();
351
+ }
412
352
  function diskCacheRoot(config, projectDir) {
413
353
  const raw = config?.cache;
414
354
  if (raw === false) return void 0;
@@ -424,10 +364,10 @@ function diskCacheRoot(config, projectDir) {
424
364
  return void 0;
425
365
  }
426
366
  function interfaceConfig(config) {
427
- const raw = config?.interface?.emit;
367
+ const raw = config?.contract?.emit;
428
368
  const emit = raw === void 0 ? [] : Array.isArray(raw) ? raw.filter((s) => typeof s === "string" && s.length > 0) : typeof raw === "string" && raw.length > 0 ? [raw] : [];
429
369
  return {
430
- autoBind: config?.interface?.autoBind ?? true,
370
+ autoBind: config?.contract?.autoBind ?? true,
431
371
  emit
432
372
  };
433
373
  }
@@ -474,11 +414,14 @@ function findProjectConfig(startDir) {
474
414
  const root = resolve("/");
475
415
  while (dir !== root) {
476
416
  const pkgPath = resolve(dir, "package.json");
477
- if (existsSync2(pkgPath)) {
417
+ if (existsSync(pkgPath)) {
478
418
  try {
479
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
419
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
480
420
  if (pkg.nudo) {
481
- return { config: pkg.nudo, projectDir: dir };
421
+ const nudo = pkg.nudo;
422
+ setSessionCacheFromProject(nudo.sessionCache);
423
+ applyBForkBudgetFromConfig(nudo);
424
+ return { config: nudo, projectDir: dir };
482
425
  }
483
426
  } catch {
484
427
  }
@@ -487,24 +430,91 @@ function findProjectConfig(startDir) {
487
430
  if (parent === dir) break;
488
431
  dir = parent;
489
432
  }
433
+ applyBForkBudgetFromConfig(null);
490
434
  return null;
491
435
  }
492
436
 
493
437
  // src/evaluator/resolve-npm.ts
494
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
438
+ import { readFileSync as readFileSync2, existsSync as existsSync2, statSync } from "fs";
495
439
  import { resolve as resolve2, join, dirname as dirname2 } from "path";
496
440
  function findNodeModules(startDir) {
497
441
  let dir = resolve2(startDir);
498
442
  const root = resolve2("/");
499
443
  while (dir !== root) {
500
444
  const nmPath = join(dir, "node_modules");
501
- if (existsSync3(nmPath)) return nmPath;
445
+ if (existsSync2(nmPath)) return nmPath;
502
446
  const parent = dirname2(dir);
503
447
  if (parent === dir) break;
504
448
  dir = parent;
505
449
  }
506
450
  return null;
507
451
  }
452
+ function resolveNpmJsEntry(source, fromDir) {
453
+ if (!source || source.startsWith(".") || source.startsWith("/") || source.startsWith("node:")) {
454
+ return null;
455
+ }
456
+ const parts = source.startsWith("@") ? source.split("/").slice(0, 2) : source.split("/").slice(0, 1);
457
+ const pkgName = parts.join("/");
458
+ const subpath = source.slice(pkgName.length).replace(/^\//, "") || ".";
459
+ const nodeModules = findNodeModules(fromDir);
460
+ if (!nodeModules) return null;
461
+ const pkgDir = join(nodeModules, pkgName);
462
+ const pkgJsonPath = join(pkgDir, "package.json");
463
+ if (!existsSync2(pkgJsonPath)) return null;
464
+ const tryFile = (p) => {
465
+ for (const c of [p, `${p}.js`, `${p}.cjs`, `${p}.mjs`, join(p, "index.js"), join(p, "index.cjs")]) {
466
+ try {
467
+ if (existsSync2(c) && statSync(c).isFile()) return c;
468
+ } catch {
469
+ }
470
+ }
471
+ return null;
472
+ };
473
+ let pkg = {};
474
+ try {
475
+ pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf-8"));
476
+ } catch {
477
+ return null;
478
+ }
479
+ const exportsField = pkg.exports;
480
+ const pickExport = (entry) => {
481
+ if (typeof entry === "string") return entry;
482
+ if (entry && typeof entry === "object") {
483
+ const o = entry;
484
+ for (const k of ["nudo", "node", "require", "import", "default"]) {
485
+ const v = o[k];
486
+ const r = pickExport(v);
487
+ if (r) return r;
488
+ }
489
+ }
490
+ return null;
491
+ };
492
+ if (exportsField) {
493
+ let rel = null;
494
+ if (typeof exportsField === "string" && subpath === ".") rel = exportsField;
495
+ else if (exportsField && typeof exportsField === "object") {
496
+ const map = exportsField;
497
+ const key = subpath === "." ? "." : `./${subpath}`;
498
+ rel = pickExport(map[key] ?? (subpath === "." ? map : void 0));
499
+ }
500
+ if (rel) {
501
+ const hit = tryFile(resolve2(pkgDir, rel));
502
+ if (hit) return hit;
503
+ }
504
+ }
505
+ if (subpath === ".") {
506
+ const main = typeof pkg.main === "string" ? pkg.main : void 0;
507
+ const hit = tryFile(resolve2(pkgDir, main ?? "."));
508
+ if (hit) return hit;
509
+ const mod = typeof pkg.module === "string" ? pkg.module : void 0;
510
+ if (mod) {
511
+ const hitMod = tryFile(resolve2(pkgDir, mod));
512
+ if (hitMod) return hitMod;
513
+ }
514
+ return null;
515
+ }
516
+ return tryFile(resolve2(pkgDir, subpath));
517
+ }
508
518
  function resolveExportsNudo(exports, subpath) {
509
519
  if (!exports || typeof exports !== "object") return null;
510
520
  const entry = exports[subpath];
@@ -524,13 +534,13 @@ function resolveNpmNudo(source, fromDir) {
524
534
  const nodeModules = findNodeModules(fromDir);
525
535
  if (!nodeModules) return null;
526
536
  const pkgJsonPath = join(nodeModules, pkgName, "package.json");
527
- if (!existsSync3(pkgJsonPath)) return null;
537
+ if (!existsSync2(pkgJsonPath)) return null;
528
538
  try {
529
- const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
539
+ const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf-8"));
530
540
  const nudoEntry = resolveExportsNudo(pkg.exports, subpath);
531
541
  if (nudoEntry) {
532
542
  const resolved = resolve2(dirname2(pkgJsonPath), nudoEntry);
533
- if (existsSync3(resolved)) return resolved;
543
+ if (existsSync2(resolved)) return resolved;
534
544
  }
535
545
  } catch {
536
546
  }
@@ -538,19 +548,24 @@ function resolveNpmNudo(source, fromDir) {
538
548
  }
539
549
 
540
550
  export {
541
- clearPathEnvCaches,
542
- preloadPathEnvs,
543
- loadEnvsAsync,
544
- loadEnvs,
551
+ resolveNpmJsEntry,
552
+ resolveNpmNudo,
553
+ BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS,
554
+ describeAbsMember,
555
+ builtinProtoMemberNames,
556
+ builtinProtoMember,
557
+ DEFAULT_SESSION_CACHE_LIMITS,
558
+ getSessionCacheLimits,
559
+ setSessionCacheLimits,
560
+ setSessionCacheFromProject,
561
+ resetSessionCacheLimitState,
562
+ checkConfig,
545
563
  DEFAULT_ANALYSIS_MODE,
546
564
  analysisConfig,
565
+ applyBForkBudgetFromConfig,
566
+ currentBForkBudgetLimit,
547
567
  diskCacheRoot,
548
568
  interfaceConfig,
549
569
  matchesEmitAllowlist,
550
- findProjectConfig,
551
- resolveNpmNudo,
552
- BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS,
553
- describeAbsMember,
554
- builtinProtoMemberNames,
555
- builtinProtoMember
570
+ findProjectConfig
556
571
  };