@nudojs/service 3.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.
@@ -0,0 +1,364 @@
1
+ // src/case-emitter.ts
2
+ import { parse, extractDirectives } from "@nudojs/parser";
3
+ var GENERATED_PREFIX = "call@";
4
+ var GENERATED_CASE_LINE_REGEX = /^\s*\*?\s*@nudo:case\s+"(call@[^"]*)"/;
5
+ var IDENT_KEY_REGEX = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
6
+ var NUMBER_LITERAL_REGEX = /^-?\d+(\.\d+)?$/;
7
+ var UNSAFE_STRING_CHARS = /[,()[\]{}]/;
8
+ var UNSAFE_KEY_CHARS = /[,()[\]{}:]/;
9
+ var CONTROL_CHARS = /[\x00-\x1f\x7f]/;
10
+ function serializeStringLiteral(value) {
11
+ if (CONTROL_CHARS.test(value)) return null;
12
+ if (value.includes("*/")) return null;
13
+ if (UNSAFE_STRING_CHARS.test(value)) return null;
14
+ const hasDbl = value.includes('"');
15
+ const hasSgl = value.includes("'");
16
+ if (hasDbl && hasSgl) return null;
17
+ return hasDbl ? `'${value}'` : `"${value}"`;
18
+ }
19
+ function serializeObjectKey(key) {
20
+ if (IDENT_KEY_REGEX.test(key)) return key;
21
+ if (CONTROL_CHARS.test(key)) return null;
22
+ if (key.includes('"')) return null;
23
+ if (UNSAFE_KEY_CHARS.test(key) || key.includes("*/")) return null;
24
+ if (/^['"]|['"]$/.test(key)) return null;
25
+ return `"${key}"`;
26
+ }
27
+ function serializeCaseArg(a) {
28
+ const s = a.shape;
29
+ if (a.term?.op === "lit") {
30
+ const v = a.term.value;
31
+ if (typeof v === "number") {
32
+ if (!Number.isFinite(v)) return null;
33
+ const n = String(v);
34
+ return NUMBER_LITERAL_REGEX.test(n) ? n : null;
35
+ }
36
+ if (typeof v === "boolean") return v ? "true" : "false";
37
+ if (v === null) return "null";
38
+ if (v === void 0) return "undefined";
39
+ if (typeof v === "bigint") return null;
40
+ if (typeof v === "string") return serializeStringLiteral(v);
41
+ return null;
42
+ }
43
+ switch (s.k) {
44
+ case "prim":
45
+ if (s.type === "number") return "number()";
46
+ if (s.type === "string") return "string()";
47
+ if (s.type === "boolean") return "boolean()";
48
+ return null;
49
+ case "unknown":
50
+ case "any":
51
+ return "any()";
52
+ case "never":
53
+ return "never";
54
+ case "sum": {
55
+ const parts = [];
56
+ for (const member of s.members) {
57
+ const ser = serializeCaseArg(member);
58
+ if (ser === null) return null;
59
+ parts.push(ser);
60
+ }
61
+ return `union(${parts.join(", ")})`;
62
+ }
63
+ case "arr": {
64
+ const el = serializeCaseArg(s.element);
65
+ return el === null ? null : `array(${el})`;
66
+ }
67
+ case "tuple": {
68
+ const parts = [];
69
+ for (const el of s.elements) {
70
+ const ser = serializeCaseArg(el);
71
+ if (ser === null) return null;
72
+ parts.push(ser);
73
+ }
74
+ return `[${parts.join(", ")}]`;
75
+ }
76
+ case "obj": {
77
+ const parts = [];
78
+ for (const [key, slot] of Object.entries(s.slots)) {
79
+ const vs = serializeCaseArg(slot.value);
80
+ if (vs === null) return null;
81
+ const ks = serializeObjectKey(key);
82
+ if (ks === null) return null;
83
+ parts.push(`${ks}: ${vs}`);
84
+ }
85
+ return parts.length === 0 ? "{}" : `{ ${parts.join(", ")} }`;
86
+ }
87
+ default:
88
+ return null;
89
+ }
90
+ }
91
+ function buildCaseDirective(name, argsAbs) {
92
+ if (name.includes('"') || /[\r\n]/.test(name)) return null;
93
+ const parts = [];
94
+ for (const arg of argsAbs) {
95
+ const s = serializeCaseArg(arg);
96
+ if (s === null) return null;
97
+ parts.push(s);
98
+ }
99
+ return ` * @nudo:case "${name}" (${parts.join(", ")})`;
100
+ }
101
+ function stripGeneratedCaseDirectives(source) {
102
+ const lines = source.split("\n");
103
+ const removed = [];
104
+ const drop = new Array(lines.length).fill(false);
105
+ for (let i = 0; i < lines.length; i++) {
106
+ const m = lines[i].match(GENERATED_CASE_LINE_REGEX);
107
+ if (m) {
108
+ drop[i] = true;
109
+ removed.push(m[1]);
110
+ }
111
+ }
112
+ for (let i = 0; i < lines.length; i++) {
113
+ if (!/^\s*\/\*\*\s*$/.test(lines[i])) continue;
114
+ let j = i + 1;
115
+ let clean = true;
116
+ while (j < lines.length) {
117
+ if (/\*\//.test(lines[j])) break;
118
+ if (/\/\*/.test(lines[j])) {
119
+ clean = false;
120
+ break;
121
+ }
122
+ j++;
123
+ }
124
+ if (!clean || j >= lines.length) continue;
125
+ if (!/^\s*\*\/\s*$/.test(lines[j])) continue;
126
+ let onlyFiller = true;
127
+ for (let k = i + 1; k < j; k++) {
128
+ if (drop[k]) continue;
129
+ const t = lines[k].trim();
130
+ if (t !== "" && !/^\*{1,2}$/.test(t)) {
131
+ onlyFiller = false;
132
+ break;
133
+ }
134
+ }
135
+ if (onlyFiller) {
136
+ for (let k = i; k <= j; k++) drop[k] = true;
137
+ }
138
+ }
139
+ return {
140
+ source: lines.filter((_, i) => !drop[i]).join("\n"),
141
+ removed
142
+ };
143
+ }
144
+ function collectExistingCases(source) {
145
+ const byName = /* @__PURE__ */ new Map();
146
+ let ast;
147
+ try {
148
+ ast = parse(source, { errorRecovery: true });
149
+ } catch {
150
+ return byName;
151
+ }
152
+ for (const { name, directives } of extractDirectives(ast)) {
153
+ for (const d of directives) {
154
+ if (d.kind !== "case") continue;
155
+ const list = byName.get(name);
156
+ if (list) list.push(d);
157
+ else byName.set(name, [d]);
158
+ }
159
+ }
160
+ return byName;
161
+ }
162
+ function insertGeneratedCaseDirectives(source, analysis) {
163
+ const written = [];
164
+ const skipped = [];
165
+ const existing = collectExistingCases(source);
166
+ const edits = [];
167
+ for (const fn of analysis.functions) {
168
+ const existingCases = existing.get(fn.name) ?? [];
169
+ if (existingCases.some((c) => !c.name.startsWith(GENERATED_PREFIX))) {
170
+ skipped.push({ fn: fn.name, reason: "hand-written" });
171
+ continue;
172
+ }
173
+ if (existingCases.length > 0) {
174
+ skipped.push({ fn: fn.name, reason: "already-generated" });
175
+ continue;
176
+ }
177
+ if (fn.skipped) {
178
+ skipped.push({ fn: fn.name, reason: "skipped" });
179
+ continue;
180
+ }
181
+ if (fn.noDeclaration) {
182
+ skipped.push({ fn: fn.name, reason: "no-declaration" });
183
+ continue;
184
+ }
185
+ if (fn.entryOnly) {
186
+ skipped.push({ fn: fn.name, reason: "entry-only" });
187
+ continue;
188
+ }
189
+ const callsiteCases = fn.cases.filter((c) => c.source === "callsite");
190
+ if (callsiteCases.length === 0) {
191
+ skipped.push({ fn: fn.name, reason: "no-serializable-cases", detail: "no callsite cases" });
192
+ continue;
193
+ }
194
+ const built = [];
195
+ const names = [];
196
+ for (const c of callsiteCases) {
197
+ const directive = buildCaseDirective(c.name, c.argAbs ?? []);
198
+ if (directive === null) {
199
+ skipped.push({ fn: fn.name, reason: "no-serializable-cases", detail: `case ${c.name} not serializable` });
200
+ } else {
201
+ built.push(directive);
202
+ names.push(c.name);
203
+ }
204
+ }
205
+ if (built.length === 0) continue;
206
+ edits.push({ line: fn.loc.start.line, column: fn.loc.start.column, directives: built, fn: fn.name });
207
+ written.push({ fn: fn.name, cases: names });
208
+ }
209
+ edits.sort((a, b) => b.line - a.line);
210
+ const lines = source.split("\n");
211
+ for (const edit of edits) {
212
+ const declIdx = edit.line - 1;
213
+ const indent = " ".repeat(edit.column);
214
+ let insertIdx = -1;
215
+ let blockIndent = "";
216
+ const aboveIdx = declIdx - 1;
217
+ if (aboveIdx >= 0 && /^\s*\*\/\s*$/.test(lines[aboveIdx])) {
218
+ let s = aboveIdx - 1;
219
+ while (s >= 0) {
220
+ const t = lines[s];
221
+ if (/^\s*\/\*\*/.test(t)) {
222
+ insertIdx = s + 1;
223
+ blockIndent = t.match(/^\s*/)?.[0] ?? "";
224
+ break;
225
+ }
226
+ if (/^\s*\*/.test(t) || t.trim() === "") {
227
+ s--;
228
+ continue;
229
+ }
230
+ break;
231
+ }
232
+ }
233
+ if (insertIdx >= 0) {
234
+ lines.splice(insertIdx, 0, ...edit.directives.map((d) => blockIndent + d));
235
+ } else {
236
+ lines.splice(declIdx, 0, `${indent}/**`, ...edit.directives.map((d) => indent + d), `${indent} */`);
237
+ }
238
+ }
239
+ return {
240
+ source: lines.join("\n"),
241
+ changed: written.length > 0,
242
+ written,
243
+ skipped
244
+ };
245
+ }
246
+ function diffOps(a, b) {
247
+ let start = 0;
248
+ while (start < a.length && start < b.length && a[start] === b[start]) start++;
249
+ let endA = a.length;
250
+ let endB = b.length;
251
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
252
+ endA--;
253
+ endB--;
254
+ }
255
+ const midA = a.slice(start, endA);
256
+ const midB = b.slice(start, endB);
257
+ const n = midA.length;
258
+ const m = midB.length;
259
+ const dp = new Int32Array((n + 1) * (m + 1));
260
+ for (let i2 = n - 1; i2 >= 0; i2--) {
261
+ for (let j2 = m - 1; j2 >= 0; j2--) {
262
+ dp[i2 * (m + 1) + j2] = midA[i2] === midB[j2] ? dp[(i2 + 1) * (m + 1) + (j2 + 1)] + 1 : Math.max(dp[(i2 + 1) * (m + 1) + j2], dp[i2 * (m + 1) + (j2 + 1)]);
263
+ }
264
+ }
265
+ const ops = [];
266
+ for (let k = 0; k < start; k++) ops.push({ type: "equal", text: a[k] });
267
+ let i = 0;
268
+ let j = 0;
269
+ while (i < n && j < m) {
270
+ if (midA[i] === midB[j]) {
271
+ ops.push({ type: "equal", text: midA[i] });
272
+ i++;
273
+ j++;
274
+ } else if (dp[(i + 1) * (m + 1) + j] >= dp[i * (m + 1) + (j + 1)]) {
275
+ ops.push({ type: "delete", text: midA[i] });
276
+ i++;
277
+ } else {
278
+ ops.push({ type: "insert", text: midB[j] });
279
+ j++;
280
+ }
281
+ }
282
+ while (i < n) ops.push({ type: "delete", text: midA[i++] });
283
+ while (j < m) ops.push({ type: "insert", text: midB[j++] });
284
+ for (let k = endA; k < a.length; k++) ops.push({ type: "equal", text: a[k] });
285
+ return ops;
286
+ }
287
+ var DIFF_CONTEXT = 3;
288
+ function formatRange(start, count) {
289
+ const s = count === 0 ? start - 1 : start;
290
+ return count === 1 ? `${s}` : `${s},${count}`;
291
+ }
292
+ function unifiedDiff(a, b, path) {
293
+ if (a === b) return "";
294
+ const ops = diffOps(a.split("\n"), b.split("\n"));
295
+ const hunks = [];
296
+ let idx = 0;
297
+ while (idx < ops.length) {
298
+ if (ops[idx].type === "equal") {
299
+ idx++;
300
+ continue;
301
+ }
302
+ let start = idx;
303
+ let ctx = 0;
304
+ while (start > 0 && ops[start - 1].type === "equal" && ctx < DIFF_CONTEXT) {
305
+ start--;
306
+ ctx++;
307
+ }
308
+ let j = idx;
309
+ let equalRun = 0;
310
+ let lastChange = idx;
311
+ while (j < ops.length) {
312
+ if (ops[j].type === "equal") {
313
+ equalRun++;
314
+ if (equalRun > DIFF_CONTEXT * 2) break;
315
+ } else {
316
+ equalRun = 0;
317
+ lastChange = j;
318
+ }
319
+ j++;
320
+ }
321
+ let end = lastChange + 1;
322
+ ctx = 0;
323
+ while (end < ops.length && ops[end].type === "equal" && ctx < DIFF_CONTEXT) {
324
+ end++;
325
+ ctx++;
326
+ }
327
+ hunks.push({ start, ops: ops.slice(start, end) });
328
+ idx = end;
329
+ }
330
+ const out = [`--- a/${path}`, `+++ b/${path}`];
331
+ for (let h = 0; h < hunks.length; h++) {
332
+ const { start, ops: hunkOps } = hunks[h];
333
+ let oldCount = 0;
334
+ let newCount = 0;
335
+ for (let k = 0; k < start; k++) {
336
+ if (ops[k].type !== "insert") oldCount++;
337
+ if (ops[k].type !== "delete") newCount++;
338
+ }
339
+ let dels = 0;
340
+ let ins = 0;
341
+ for (const op of hunkOps) {
342
+ if (op.type === "delete") dels++;
343
+ else if (op.type === "insert") ins++;
344
+ else {
345
+ dels++;
346
+ ins++;
347
+ }
348
+ }
349
+ out.push(`@@ -${formatRange(oldCount + 1, dels)} +${formatRange(newCount + 1, ins)} @@`);
350
+ for (const op of hunkOps) {
351
+ const prefix = op.type === "equal" ? " " : op.type === "delete" ? "-" : "+";
352
+ out.push(prefix + op.text);
353
+ }
354
+ }
355
+ return out.join("\n") + "\n";
356
+ }
357
+
358
+ export {
359
+ serializeCaseArg,
360
+ buildCaseDirective,
361
+ stripGeneratedCaseDirectives,
362
+ insertGeneratedCaseDirectives,
363
+ unifiedDiff
364
+ };
@@ -1,51 +1,8 @@
1
- import { Abs, Environment } from '@nudojs/core';
2
-
3
- /**
4
- * 调用点记录(CallRecord)。Abs 唯一真理源。
5
- * argAbs/resultAbs/throwsAbs 必填;展示/外延在 CaseResult 边界再桥。
6
- */
7
-
8
- type CallRecord = {
9
- fnName: string;
10
- /** 无损参数 Abs */
11
- argAbs: Abs[];
12
- /** 无损结果 Abs(threw 时为 never) */
13
- resultAbs: Abs;
14
- /** 无损抛出值 Abs(未抛为 never) */
15
- throwsAbs: Abs;
16
- /** Line-relative. Per-fn cache replay shifts this by lineDelta — if you
17
- * add another position field (callee loc, arg loc), extend
18
- * shiftCallRecordLines in analyzer.ts in the same change. */
19
- callLoc?: {
20
- line: number;
21
- column: number;
22
- };
23
- targetModule?: string;
24
- targetExport?: string;
25
- /** export names the same function value was re-exported under after its
26
- * defining module (barrel `index.js`, CJS forwarding shims); usage-site
27
- * records stay name-matchable against them */
28
- targetAliases?: string[];
29
- /** module whose evaluation created the function value (definition site). */
30
- fnModule?: string;
31
- };
32
-
33
- type LoadedEnv = {
34
- /** Abs 原生模块导出(B 路径 / Abs 模块图) */
35
- modules: Record<string, Record<string, Abs>>;
36
- globals: Record<string, Abs>;
37
- };
38
- /** Host cache-clear hooks (CLI watch / vite / tests) must drop path-env modules too */
39
- declare function clearPathEnvCaches(): void;
40
- declare function preloadPathEnvs(envNames: string[], baseDir: string): Promise<void>;
41
- declare function loadEnvsAsync(envNames: string[], globalEnv: Environment, baseDir?: string): Promise<LoadedEnv>;
42
- declare function loadEnvs(envNames: string[], globalEnv: Environment): LoadedEnv;
43
-
44
1
  type NudoConfig = {
45
2
  env?: string[];
46
3
  mocks?: Record<string, string>;
47
- interface?: {
48
- /** 侧车 ambient 绑定总开关(check/LSP 执法与 interface 打印共用) */
4
+ contract?: {
5
+ /** 侧车 ambient 绑定总开关(check/LSP 执法与 contract 打印共用) */
49
6
  autoBind?: boolean;
50
7
  /**
51
8
  * emit 白名单(Phase 3,§7.3):glob 数组,相对 projectDir。
@@ -53,7 +10,7 @@ type NudoConfig = {
53
10
  */
54
11
  emit?: string[] | string;
55
12
  };
56
- /** 分析范围与噪声档(design-analysis-scope.md / A2) */
13
+ /** 分析范围与噪声档(design-cli-semantics.md §7) */
57
14
  analysis?: {
58
15
  include?: string[] | string;
59
16
  exclude?: string[] | string;
@@ -65,9 +22,30 @@ type NudoConfig = {
65
22
  callSiteBudget?: number;
66
23
  /** C0.5:求值命中闭对象缺字段 → nudo:missing-slot;默认 off */
67
24
  evalMissingSlot?: "off" | "warning";
25
+ /**
26
+ * B $fork 总次数上限(默认 5000)。env `NUDO_MAX_FORKS` 优先。
27
+ * n≥1 有限整数;非法值回默认。启动时 set 进 core(setBForkBudgetLimit)。
28
+ */
29
+ maxForks?: number;
68
30
  };
69
31
  /** 磁盘缓存(B3):true → `.nudo/cache`;字符串 → 自定义根;false/省略 → 关 */
70
32
  cache?: boolean | string;
33
+ /**
34
+ * 进程内会话 LRU 上限(内存/速度权衡)。多项目开 IDE 时调低封顶;
35
+ * 单大仓 warm 命中可调高。0 = 关闭该层。env `NUDO_CACHE_MAX_*` 优先。
36
+ */
37
+ sessionCache?: {
38
+ maxFiles?: number;
39
+ maxFns?: number;
40
+ maxBRuns?: number;
41
+ };
42
+ /** check 门禁(design-cli-semantics §3) */
43
+ check?: {
44
+ /** L2 入口 may-throw:error | warning | off(默认 error) */
45
+ entryThrows?: "error" | "warning" | "off";
46
+ /** L2 --ignore-throws 类型名列表 */
47
+ ignoreThrows?: string[];
48
+ };
71
49
  };
72
50
  type InterfaceConfig = {
73
51
  autoBind: boolean;
@@ -85,7 +63,17 @@ type AnalysisConfig = {
85
63
  callSiteBudget: number;
86
64
  /** C0.5 evaluation-driven missing-slot;默认 off */
87
65
  evalMissingSlot: "off" | "warning";
66
+ /** B $fork 总次数上限(已归一化;非法值回 core 默认) */
67
+ maxForks: number;
68
+ };
69
+ type CheckConfig = {
70
+ /** L2 入口 may-throw 执法档;默认 error */
71
+ entryThrows: "error" | "warning" | "off";
72
+ /** L2 ignoreThrows 类型名;默认空 */
73
+ ignoreThrows: string[];
88
74
  };
75
+ /** package.json#nudo.check → 执法选项 */
76
+ declare function checkConfig(config: NudoConfig | null | undefined): CheckConfig;
89
77
  /** A1 产品默认:exports — 普通带导出的 .js 进 IDE;directives/all 需显式 */
90
78
  declare const DEFAULT_ANALYSIS_MODE: AnalysisMode;
91
79
  /**
@@ -95,10 +83,19 @@ declare const DEFAULT_ANALYSIS_MODE: AnalysisMode;
95
83
  * include 空 = 不按路径过滤(isNudoTargetPath 已管扩展名)。
96
84
  */
97
85
  declare function analysisConfig(config: NudoConfig | null | undefined): AnalysisConfig;
86
+ /**
87
+ * 把 fork 预算写进 core(core 保持无 IO)。
88
+ * 优先级:env `NUDO_MAX_FORKS` > `package.json#nudo.analysis.maxForks` > 默认 5000。
89
+ * 约定:n≥1 有限整数;非法值回默认。返回实际生效值。
90
+ * 由 findProjectConfig / 宿主启动时调用(与 setSessionCacheFromProject 同时机)。
91
+ */
92
+ declare function applyBForkBudgetFromConfig(config: NudoConfig | null | undefined, env?: NodeJS.ProcessEnv): number;
93
+ /** 当前生效 fork 上限(调试/测试;与 core getBForkBudgetLimit 同源) */
94
+ declare function currentBForkBudgetLimit(): number;
98
95
  /** 磁盘缓存根(B3):config.cache / NUDO_CACHE_DIR / 默认关 */
99
96
  declare function diskCacheRoot(config: NudoConfig | null | undefined, projectDir: string | undefined): string | undefined;
100
97
  /**
101
- * 归一化 `nudo.interface` 配置段。
98
+ * 归一化 `nudo.contract` 配置段。
102
99
  * - autoBind 默认 true
103
100
  * - emit:string | string[] → string[](空 = 不限制路径)
104
101
  */
@@ -114,4 +111,4 @@ declare function findProjectConfig(startDir: string): {
114
111
  projectDir: string;
115
112
  } | null;
116
113
 
117
- export { type AnalysisConfig as A, type CallRecord as C, type DiagnosticsLevel as D, type InterfaceConfig as I, type LoadedEnv as L, type NudoConfig as N, type AnalysisMode as a, DEFAULT_ANALYSIS_MODE as b, analysisConfig as c, clearPathEnvCaches as d, diskCacheRoot as e, findProjectConfig as f, loadEnvsAsync as g, interfaceConfig as i, loadEnvs as l, matchesEmitAllowlist as m, preloadPathEnvs as p };
114
+ export { type AnalysisConfig as A, type CheckConfig as C, type DiagnosticsLevel as D, type InterfaceConfig as I, type NudoConfig as N, type AnalysisMode as a, analysisConfig as b, checkConfig as c, diskCacheRoot as d, DEFAULT_ANALYSIS_MODE as e, findProjectConfig as f, applyBForkBudgetFromConfig as g, currentBForkBudgetLimit as h, interfaceConfig as i, matchesEmitAllowlist as m };
package/dist/dts.d.ts ADDED
@@ -0,0 +1,142 @@
1
+ import { Abs, NudoConstraint } from '@nudojs/core';
2
+ import { a as AnalysisResult, F as FunctionAnalysis } from './analyzer-types-JyJiCt8w.js';
3
+ import '@babel/types';
4
+
5
+ /**
6
+ * Abs → TS 类型串。有损:pred / 非 lit term 落到 shape 基类型。
7
+ * `typeVars`:term var id → TS 类型参数名(HOF 泛型投影时传入;
8
+ * `any`+var 在此映射下渲染为该参数名,否则 `unknown`)。
9
+ */
10
+ declare function absToTSType(a: Abs, typeVars?: Map<string, string>): string;
11
+ /**
12
+ * 为单个函数生成 .d.ts 声明行(JSDoc + 单一 widen 主签名)。
13
+ * 有 hof 关系时优先泛型投影(C3.3);否则 case-widen。
14
+ * service 的 generateDts 与 CLI `--dts`(infer/watch)共用本函数,
15
+ * 两条路径输出保持一致。
16
+ */
17
+ declare function generateFunctionDtsLines(fn: FunctionAnalysis): string[];
18
+ declare function generateDts(result: AnalysisResult): string;
19
+
20
+ /**
21
+ * Abs → 生态 schema 投影(单向有损)。
22
+ *
23
+ * 优先走 core `absToConstraint`(与 interface/check 同一投影语义),
24
+ * 再转成中间层 SchemaNode;投影失败时退回 shape 尽力提取并记 dropped。
25
+ * Dialect 只负责 Node → 源码字符串;Abs 才是真理源。
26
+ */
27
+
28
+ type SchemaDialect = "zod";
29
+ type SchemaRefinement = {
30
+ kind: "numBound";
31
+ op: "gt" | "ge" | "lt" | "le";
32
+ n: number;
33
+ } | {
34
+ kind: "int";
35
+ } | {
36
+ kind: "strMin";
37
+ n: number;
38
+ } | {
39
+ kind: "strMax";
40
+ n: number;
41
+ };
42
+ type SchemaNode = {
43
+ k: "lit";
44
+ value: string | number | boolean | null | undefined;
45
+ } | {
46
+ k: "prim";
47
+ type: "number" | "string" | "boolean" | "bigint" | "symbol";
48
+ refinements: SchemaRefinement[];
49
+ } | {
50
+ k: "obj";
51
+ slots: Array<{
52
+ key: string;
53
+ node: SchemaNode;
54
+ optional?: boolean;
55
+ }>;
56
+ } | {
57
+ k: "arr";
58
+ element: SchemaNode;
59
+ } | {
60
+ k: "tuple";
61
+ elements: SchemaNode[];
62
+ } | {
63
+ k: "union";
64
+ members: SchemaNode[];
65
+ } | {
66
+ k: "fn";
67
+ } | {
68
+ k: "promise";
69
+ inner: SchemaNode;
70
+ } | {
71
+ k: "brand";
72
+ name: string;
73
+ } | {
74
+ k: "never";
75
+ } | {
76
+ k: "unknown";
77
+ };
78
+ type SchemaProjection = {
79
+ source: string;
80
+ dialect: SchemaDialect;
81
+ dropped: string[];
82
+ };
83
+ /** NudoConstraint → SchemaNode(与 absToConstraint 投影语义对齐) */
84
+ declare function constraintToSchemaNode(c: NudoConstraint): SchemaNode;
85
+ /** Abs → SchemaNode + dropped(优先 core absToConstraint;失败则 shape 尽力) */
86
+ declare function absToSchemaNode(a: Abs): {
87
+ node: SchemaNode;
88
+ dropped: string[];
89
+ };
90
+ declare function schemaNodeToZod(node: SchemaNode): string;
91
+ declare function projectAbsToSchema(a: Abs, opts?: {
92
+ dialect?: SchemaDialect;
93
+ }): SchemaProjection;
94
+ declare function absToSchemaSource(a: Abs, opts?: {
95
+ dialect?: SchemaDialect;
96
+ }): string;
97
+
98
+ /**
99
+ * Abs / SchemaNode → Standard Schema v1 运行时模块(单向有损投影)。
100
+ *
101
+ * 产物是零第三方依赖的 TS/JS 模块:每个导出实现 `~standard` Props
102
+ * (version=1, vendor="nudo", validate)。validate 对 SchemaNode 做
103
+ * 结构 + 可表达 refinement 执法;落不了的 pred 不在此假装精确。
104
+ *
105
+ * 协议:https://standardschema.dev —— 运行时互操作出口,不替代 nudo check。
106
+ */
107
+
108
+ type StandardSchemaIssue = {
109
+ message: string;
110
+ path?: ReadonlyArray<PropertyKey>;
111
+ };
112
+ type StandardSchemaResult = {
113
+ value: unknown;
114
+ issues?: undefined;
115
+ } | {
116
+ issues: ReadonlyArray<StandardSchemaIssue>;
117
+ value?: undefined;
118
+ };
119
+ /** SchemaNode 同步校验(生成模块与测试共用语义)。 */
120
+ declare function validateSchemaNode(node: SchemaNode, value: unknown): StandardSchemaResult;
121
+ type StandardSchemaModuleProjection = {
122
+ source: string;
123
+ dropped: string[];
124
+ };
125
+ /**
126
+ * Abs → Standard Schema v1 模块源码。
127
+ * `exports`:导出名 → Abs(通常是 output / 各参数)。
128
+ */
129
+ declare function absToStandardSchemaModule(exports: Record<string, Abs>, opts?: {
130
+ banner?: string;
131
+ }): StandardSchemaModuleProjection;
132
+ /** 便捷:单个 Abs → 单导出模块(默认导出名 `schema`)。 */
133
+ declare function absToStandardSchema(a: Abs, opts?: {
134
+ name?: string;
135
+ }): StandardSchemaModuleProjection;
136
+
137
+ /** Abs 指称守卫(设计 §2.7):保留 pred */
138
+ declare function generateGuardFunctionFromAbs(name: string, abs: Abs): string;
139
+ /** 兼容别名:Abs 路径唯一 */
140
+ declare function generateGuardFunction(name: string, abs: Abs): string;
141
+
142
+ export { type SchemaDialect, type SchemaNode, type SchemaProjection, type SchemaRefinement, type StandardSchemaIssue, type StandardSchemaModuleProjection, type StandardSchemaResult, absToSchemaNode, absToSchemaSource, absToStandardSchema, absToStandardSchemaModule, absToTSType, constraintToSchemaNode, generateDts, generateFunctionDtsLines, generateGuardFunction, generateGuardFunctionFromAbs, projectAbsToSchema, schemaNodeToZod, validateSchemaNode };
package/dist/dts.js ADDED
@@ -0,0 +1,30 @@
1
+ import {
2
+ absToSchemaNode,
3
+ absToSchemaSource,
4
+ absToStandardSchema,
5
+ absToStandardSchemaModule,
6
+ absToTSType,
7
+ constraintToSchemaNode,
8
+ generateDts,
9
+ generateFunctionDtsLines,
10
+ generateGuardFunction,
11
+ generateGuardFunctionFromAbs,
12
+ projectAbsToSchema,
13
+ schemaNodeToZod,
14
+ validateSchemaNode
15
+ } from "./chunk-6IZR4ZYJ.js";
16
+ export {
17
+ absToSchemaNode,
18
+ absToSchemaSource,
19
+ absToStandardSchema,
20
+ absToStandardSchemaModule,
21
+ absToTSType,
22
+ constraintToSchemaNode,
23
+ generateDts,
24
+ generateFunctionDtsLines,
25
+ generateGuardFunction,
26
+ generateGuardFunctionFromAbs,
27
+ projectAbsToSchema,
28
+ schemaNodeToZod,
29
+ validateSchemaNode
30
+ };
@@ -0,0 +1,20 @@
1
+ import { Abs, Environment } from '@nudojs/core';
2
+
3
+ type LoadedEnv = {
4
+ /** Abs 原生模块导出(B 路径 / Abs 模块图) */
5
+ modules: Record<string, Record<string, Abs>>;
6
+ globals: Record<string, Abs>;
7
+ };
8
+ /** 测试/诊断:path-env 驻留规模(均 ≤ 对应上限) */
9
+ declare function getPathEnvCacheSizes(): {
10
+ byKey: number;
11
+ byPath: number;
12
+ baseDirs: number;
13
+ };
14
+ /** Host cache-clear hooks (CLI watch / vite / tests) must drop path-env modules too */
15
+ declare function clearPathEnvCaches(): void;
16
+ declare function preloadPathEnvs(envNames: string[], baseDir: string): Promise<void>;
17
+ declare function loadEnvsAsync(envNames: string[], globalEnv: Environment, baseDir?: string): Promise<LoadedEnv>;
18
+ declare function loadEnvs(envNames: string[], globalEnv: Environment): LoadedEnv;
19
+
20
+ export { type LoadedEnv as L, loadEnvsAsync as a, clearPathEnvCaches as c, getPathEnvCacheSizes as g, loadEnvs as l, preloadPathEnvs as p };