@nifrajs/core 2.13.0 → 3.1.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.
- package/README.md +4 -0
- package/dist/assurance.d.ts +16 -8
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js +33 -0
- package/dist/assurance.js.map +1 -1
- package/dist/binary.d.ts +32 -0
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +17 -0
- package/dist/binary.js.map +1 -1
- package/dist/durable-adapters.d.ts.map +1 -1
- package/dist/durable-adapters.js +21 -50
- package/dist/durable-adapters.js.map +1 -1
- package/dist/durable-execution.d.ts.map +1 -1
- package/dist/durable-execution.js +49 -69
- package/dist/durable-execution.js.map +1 -1
- package/dist/edge-kit.d.ts +27 -0
- package/dist/edge-kit.d.ts.map +1 -0
- package/dist/edge-kit.js +24 -0
- package/dist/edge-kit.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/durable-approval-protocol.d.ts +13 -0
- package/dist/internal/durable-approval-protocol.d.ts.map +1 -0
- package/dist/internal/durable-approval-protocol.js +66 -0
- package/dist/internal/durable-approval-protocol.js.map +1 -0
- package/dist/internal/durable-effect-protocol.d.ts +14 -0
- package/dist/internal/durable-effect-protocol.d.ts.map +1 -0
- package/dist/internal/durable-effect-protocol.js +16 -0
- package/dist/internal/durable-effect-protocol.js.map +1 -0
- package/dist/internal/durable-saga-protocol.d.ts +8 -0
- package/dist/internal/durable-saga-protocol.d.ts.map +1 -0
- package/dist/internal/durable-saga-protocol.js +21 -0
- package/dist/internal/durable-saga-protocol.js.map +1 -0
- package/dist/internal/route-catalog.d.ts +3 -0
- package/dist/internal/route-catalog.d.ts.map +1 -1
- package/dist/internal/route-catalog.js +5 -0
- package/dist/internal/route-catalog.js.map +1 -1
- package/dist/internal/route-execution.d.ts +11 -5
- package/dist/internal/route-execution.d.ts.map +1 -1
- package/dist/internal/route-execution.js +2 -1
- package/dist/internal/route-execution.js.map +1 -1
- package/dist/response-observer.d.ts +25 -0
- package/dist/response-observer.d.ts.map +1 -0
- package/dist/response-observer.js +40 -0
- package/dist/response-observer.js.map +1 -0
- package/dist/security/baseline.d.ts +68 -0
- package/dist/security/baseline.d.ts.map +1 -0
- package/dist/security/baseline.js +118 -0
- package/dist/security/baseline.js.map +1 -0
- package/dist/server/bare-error-lane.d.ts +26 -0
- package/dist/server/bare-error-lane.d.ts.map +1 -0
- package/dist/server/bare-error-lane.js +60 -0
- package/dist/server/bare-error-lane.js.map +1 -0
- package/dist/server/body.d.ts +7 -0
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +7 -0
- package/dist/server/body.js.map +1 -1
- package/dist/server/bridge-markers.d.ts +25 -0
- package/dist/server/bridge-markers.d.ts.map +1 -0
- package/dist/server/bridge-markers.js +25 -0
- package/dist/server/bridge-markers.js.map +1 -0
- package/dist/server/context.d.ts +6 -1
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/header-case.d.ts.map +1 -1
- package/dist/server/header-case.js +2 -1
- package/dist/server/header-case.js.map +1 -1
- package/dist/server/http.d.ts +36 -3
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +49 -3
- package/dist/server/http.js.map +1 -1
- package/dist/server/install.d.ts +6 -8
- package/dist/server/install.d.ts.map +1 -1
- package/dist/server/install.js +7 -0
- package/dist/server/install.js.map +1 -1
- package/dist/server/node-outcome-hook.d.ts +2 -1
- package/dist/server/node-outcome-hook.d.ts.map +1 -1
- package/dist/server/node-outcome-hook.js.map +1 -1
- package/dist/server/node-outcome.d.ts +17 -2
- package/dist/server/node-outcome.d.ts.map +1 -1
- package/dist/server/node-outcome.js +37 -3
- package/dist/server/node-outcome.js.map +1 -1
- package/dist/server/plugin.d.ts +1 -1
- package/dist/server/plugin.d.ts.map +1 -1
- package/dist/server/query.d.ts +7 -1
- package/dist/server/query.d.ts.map +1 -1
- package/dist/server/query.js +8 -3
- package/dist/server/query.js.map +1 -1
- package/dist/server/registry.d.ts +11 -2
- package/dist/server/registry.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +17 -4
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +82 -45
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +19 -2
- package/dist/server/respond.js.map +1 -1
- package/dist/server/response-contract-lane.d.ts.map +1 -1
- package/dist/server/response-contract-lane.js +5 -2
- package/dist/server/response-contract-lane.js.map +1 -1
- package/dist/server/response-observer-runtime.d.ts +20 -0
- package/dist/server/response-observer-runtime.d.ts.map +1 -0
- package/dist/server/response-observer-runtime.js +95 -0
- package/dist/server/response-observer-runtime.js.map +1 -0
- package/dist/server/runtime-core.d.ts +60 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js +65 -1
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +18 -1
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +89 -48
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +397 -276
- package/dist/server/server.js.map +1 -1
- package/dist/server/websocket.d.ts +20 -4
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +81 -11
- package/dist/server/websocket.js.map +1 -1
- package/dist/server/ws-bun.d.ts +1 -1
- package/dist/server/ws-bun.d.ts.map +1 -1
- package/dist/server/ws-bun.js +12 -8
- package/dist/server/ws-bun.js.map +1 -1
- package/dist/server.d.ts +2 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/dist/single-copy-register.d.ts +2 -0
- package/dist/single-copy-register.d.ts.map +1 -0
- package/dist/single-copy-register.js +24 -0
- package/dist/single-copy-register.js.map +1 -0
- package/dist/single-copy.d.ts +156 -0
- package/dist/single-copy.d.ts.map +1 -0
- package/dist/single-copy.js +470 -0
- package/dist/single-copy.js.map +1 -0
- package/package.json +24 -2
- package/src/assurance.ts +58 -1
- package/src/binary.ts +35 -0
- package/src/durable-adapters.ts +26 -51
- package/src/durable-execution.ts +46 -88
- package/src/edge-kit.ts +27 -0
- package/src/errors.ts +1 -0
- package/src/index.ts +1 -1
- package/src/internal/durable-approval-protocol.ts +92 -0
- package/src/internal/durable-effect-protocol.ts +37 -0
- package/src/internal/durable-saga-protocol.ts +55 -0
- package/src/internal/route-catalog.ts +6 -0
- package/src/internal/route-execution.ts +23 -16
- package/src/response-observer.ts +64 -0
- package/src/security/baseline.ts +172 -0
- package/src/server/bare-error-lane.ts +76 -0
- package/src/server/body.ts +8 -0
- package/src/server/bridge-markers.ts +24 -0
- package/src/server/context.ts +6 -1
- package/src/server/header-case.ts +3 -1
- package/src/server/http.ts +53 -3
- package/src/server/install.ts +14 -0
- package/src/server/node-outcome-hook.ts +2 -1
- package/src/server/node-outcome.ts +44 -3
- package/src/server/plugin.ts +1 -1
- package/src/server/query.ts +10 -4
- package/src/server/registry.ts +19 -4
- package/src/server/request-context.ts +106 -51
- package/src/server/respond.ts +26 -1
- package/src/server/response-contract-lane.ts +6 -2
- package/src/server/response-observer-runtime.ts +135 -0
- package/src/server/runtime-core.ts +98 -1
- package/src/server/server-types.ts +18 -1
- package/src/server/server.ts +607 -376
- package/src/server/websocket.ts +127 -17
- package/src/server/ws-bun.ts +26 -7
- package/src/server.ts +2 -1
- package/src/single-copy-register.ts +24 -0
- package/src/single-copy.ts +524 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One physical copy of an identity-sensitive package, enforced at LOAD time.
|
|
3
|
+
*
|
|
4
|
+
* ## The gap this closes
|
|
5
|
+
*
|
|
6
|
+
* `@nifrajs/web`'s build already pins react/preact/svelte to the app's copy through `onResolve`
|
|
7
|
+
* (`reactDedupePlugin` and friends), and that covers every bundled phase. It cannot cover an
|
|
8
|
+
* UNBUNDLED one: Bun's runtime plugin API delivers only the entry point and RELATIVE specifiers to
|
|
9
|
+
* `onResolve`, so a bare `import "react"` inside a linked package never reaches a resolver hook. The
|
|
10
|
+
* surfaces that run app sources unbundled - `bun test`, a preloaded script, a route imported natively
|
|
11
|
+
* - therefore keep loading a second copy no matter what the build does. `onLoad` DOES fire on the
|
|
12
|
+
* resolved file, which is the one hook that can still intervene, and it is what this module uses.
|
|
13
|
+
*
|
|
14
|
+
* It also covers a package class the build plugins never did: `@nifrajs/*` itself. Two copies of
|
|
15
|
+
* `@nifrajs/core` are two `Server` classes, and `Server` carries private members, so `.merge()` stops
|
|
16
|
+
* accepting the other's app with a type error that names neither copy.
|
|
17
|
+
*
|
|
18
|
+
* ## Why a topology needs it at all
|
|
19
|
+
*
|
|
20
|
+
* A package consumed with `link:` (or `file:`, or an `npm link`) resolves ITS OWN imports from where
|
|
21
|
+
* it physically lives. A shared component library in a sibling repository therefore loads that
|
|
22
|
+
* repository's react, while the app loads its own - same version, two paths, two module registries.
|
|
23
|
+
* React reads hooks off a dispatcher the other copy never set, and SSR dies with `Invalid hook call
|
|
24
|
+
* … resolveDispatcher() is null`, naming neither react nor the package that shadowed it.
|
|
25
|
+
*
|
|
26
|
+
* The conventional fixes both cost something real: a cross-repo `workspaces` entry makes the
|
|
27
|
+
* consumer's install the owner of the other repository's tree and writes into it, and vendoring or
|
|
28
|
+
* packing copies files that exist precisely so they are not copied. This module is the third option -
|
|
29
|
+
* leave the topology alone and make the resolution answer correctly - and it is not a workaround:
|
|
30
|
+
* these packages declare react as a `peerDependency`, which means "the consumer supplies the copy".
|
|
31
|
+
* That sentence has simply never been enforceable at runtime. Now it is.
|
|
32
|
+
*
|
|
33
|
+
* ## What it will not do
|
|
34
|
+
*
|
|
35
|
+
* Redirect across a VERSION difference. Two versions is a different defect (someone's range is wrong)
|
|
36
|
+
* and silently serving 19.2.8 to a package that asked for 19.2.7 turns a loud install problem into a
|
|
37
|
+
* quiet behavioural one. A version skew is left untouched, so `nifra check` still fails it.
|
|
38
|
+
*
|
|
39
|
+
* @example Declare it in package.json, then preload the registrar in bunfig.toml.
|
|
40
|
+
* ```json
|
|
41
|
+
* { "nifra": { "singleCopy": ["react", "react-dom", "@nifrajs/*"] } }
|
|
42
|
+
* ```
|
|
43
|
+
* ```toml
|
|
44
|
+
* preload = ["@nifrajs/core/single-copy/register"]
|
|
45
|
+
* [test]
|
|
46
|
+
* preload = ["@nifrajs/core/single-copy/register"]
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
50
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
51
|
+
/**
|
|
52
|
+
* Packages whose duplication is a defect rather than a waste. Each keeps module-scoped state that
|
|
53
|
+
* every importer must share - a hook dispatcher, a renderer's options global, a class identity - so a
|
|
54
|
+
* second physical copy breaks behaviour instead of merely costing bytes. Anything scoped `@nifrajs/`
|
|
55
|
+
* qualifies for the same reason and is matched by pattern.
|
|
56
|
+
*/
|
|
57
|
+
export const IDENTITY_SENSITIVE_PACKAGES = [
|
|
58
|
+
"@nifrajs/*",
|
|
59
|
+
"react",
|
|
60
|
+
"react-dom",
|
|
61
|
+
"preact",
|
|
62
|
+
"solid-js",
|
|
63
|
+
"svelte",
|
|
64
|
+
"vue",
|
|
65
|
+
];
|
|
66
|
+
/** Files a redirect may rewrite. Everything else in a package (json, css, wasm) loads unchanged. */
|
|
67
|
+
const REDIRECTABLE = /\.(?:m|c)?[jt]sx?$/;
|
|
68
|
+
/** Runaway guards. A real `node_modules` top level is hundreds of entries, a repo walk is a few. */
|
|
69
|
+
const MAX_SCANNED_ENTRIES = 4_096;
|
|
70
|
+
const MAX_ANCESTOR_DIRS = 16;
|
|
71
|
+
const MAX_REPO_DEPTH = 8;
|
|
72
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
73
|
+
const readJsonSync = (path) => {
|
|
74
|
+
try {
|
|
75
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
76
|
+
return isRecord(value) ? value : undefined;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const realpathOrSelf = (path) => {
|
|
83
|
+
try {
|
|
84
|
+
return realpathSync(path);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return path;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const inside = (root, path) => path === root || path.startsWith(root.endsWith(sep) ? root : `${root}${sep}`);
|
|
91
|
+
/**
|
|
92
|
+
* The declaration, read from `package.json` - deliberately NOT from `nifra.config.ts`.
|
|
93
|
+
*
|
|
94
|
+
* `nifra check` holds a pre-load invariant: it never imports the app's config, because importing is
|
|
95
|
+
* executing. A dedupe claim has to be verifiable by a checker that refuses to run the app, so it lives
|
|
96
|
+
* in the one file every tool already parses. `true` means the built-in identity-sensitive set.
|
|
97
|
+
*/
|
|
98
|
+
export function readSingleCopyDeclaration(cwd) {
|
|
99
|
+
const pkg = readJsonSync(join(resolve(cwd), "package.json"));
|
|
100
|
+
const nifra = pkg?.nifra;
|
|
101
|
+
if (!isRecord(nifra))
|
|
102
|
+
return undefined;
|
|
103
|
+
const declared = nifra.singleCopy;
|
|
104
|
+
if (declared === true)
|
|
105
|
+
return IDENTITY_SENSITIVE_PACKAGES;
|
|
106
|
+
if (declared === false || declared === undefined)
|
|
107
|
+
return undefined;
|
|
108
|
+
if (!Array.isArray(declared))
|
|
109
|
+
return undefined;
|
|
110
|
+
const names = declared.filter((name) => typeof name === "string" && name.length > 0);
|
|
111
|
+
return names.length > 0 ? names : undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Match a package name against a declaration entry: an exact name, or a `@scope/*` prefix.
|
|
115
|
+
*
|
|
116
|
+
* Exported because the enforcement and the CHECK must agree on it exactly. If `nifra check` decided
|
|
117
|
+
* coverage by its own rule, a package could be reported as deduplicated while the plugin walked past
|
|
118
|
+
* it - which is worse than no check at all, because it is a green light for a broken graph.
|
|
119
|
+
*/
|
|
120
|
+
export const matchesSingleCopyDeclaration = (declared, name) => declared.some((entry) => entry.endsWith("/*") ? name.startsWith(entry.slice(0, -1)) : entry === name);
|
|
121
|
+
/** The public specifier a `bunfig.toml` preload must name to arm the runtime. */
|
|
122
|
+
export const SINGLE_COPY_REGISTER_SPECIFIER = "@nifrajs/core/single-copy/register";
|
|
123
|
+
/** Every `preload = [...]` assignment, tagged with the section it appears under. */
|
|
124
|
+
const PRELOAD_ASSIGNMENT = /^\s*(?:\[(?<section>[^\]]+)\]|preload\s*=\s*(?<value>\[[^\]]*\]|.+))\s*$/;
|
|
125
|
+
/**
|
|
126
|
+
* The exact entries of a `preload` assignment: a single-line array, or one quoted string.
|
|
127
|
+
*
|
|
128
|
+
* A substring test on the raw assignment is not proof. `preload =
|
|
129
|
+
* ["@nifrajs/core/single-copy/register-extra"]` contains the specifier and satisfies an
|
|
130
|
+
* `includes()`, while Bun loads that other module and the registrar never runs - the check then
|
|
131
|
+
* reports single-copy enforcement as armed on a process that still loads both copies. An entry only
|
|
132
|
+
* counts when it IS the specifier. Anything this cannot read as a quoted entry (a multi-line array,
|
|
133
|
+
* an interpolated value) yields nothing, so the caller reports "not registered" rather than proof.
|
|
134
|
+
*/
|
|
135
|
+
function preloadEntries(value) {
|
|
136
|
+
const closing = value.lastIndexOf("]");
|
|
137
|
+
const inner = value.startsWith("[") && closing !== -1 ? value.slice(1, closing) : value;
|
|
138
|
+
const entries = [];
|
|
139
|
+
for (const raw of inner.split(",")) {
|
|
140
|
+
const entry = raw.trim();
|
|
141
|
+
// A TOML string is quoted at both ends with the same quote; two characters is the shortest one.
|
|
142
|
+
if (entry.length < 2)
|
|
143
|
+
continue;
|
|
144
|
+
const quote = entry.charCodeAt(0);
|
|
145
|
+
if (quote !== 34 && quote !== 39)
|
|
146
|
+
continue;
|
|
147
|
+
if (entry.charCodeAt(entry.length - 1) !== quote)
|
|
148
|
+
continue;
|
|
149
|
+
entries.push(entry.slice(1, -1));
|
|
150
|
+
}
|
|
151
|
+
return entries;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Read the runtime proof out of `bunfig.toml`.
|
|
155
|
+
*
|
|
156
|
+
* Line-oriented on purpose: this runs inside `nifra check`, which must not execute or import anything
|
|
157
|
+
* from the project, and the shape being read is a literal array of strings under a known key. A full
|
|
158
|
+
* TOML parse would buy nothing here and would turn an unrelated syntax error elsewhere in the file
|
|
159
|
+
* into a failure to answer this question.
|
|
160
|
+
*/
|
|
161
|
+
export function readSingleCopyRegistration(cwd) {
|
|
162
|
+
const config = join(resolve(cwd), "bunfig.toml");
|
|
163
|
+
let text;
|
|
164
|
+
try {
|
|
165
|
+
text = readFileSync(config, "utf8");
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return { run: false, test: false };
|
|
169
|
+
}
|
|
170
|
+
let section = "";
|
|
171
|
+
let run = false;
|
|
172
|
+
let test = false;
|
|
173
|
+
for (const line of text.split(/\r?\n/)) {
|
|
174
|
+
const match = PRELOAD_ASSIGNMENT.exec(line);
|
|
175
|
+
if (match?.groups === undefined)
|
|
176
|
+
continue;
|
|
177
|
+
const { section: heading, value } = match.groups;
|
|
178
|
+
if (heading !== undefined) {
|
|
179
|
+
section = heading.trim();
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (value === undefined || !preloadEntries(value).includes(SINGLE_COPY_REGISTER_SPECIFIER))
|
|
183
|
+
continue;
|
|
184
|
+
if (section === "test")
|
|
185
|
+
test = true;
|
|
186
|
+
else if (section === "")
|
|
187
|
+
run = true;
|
|
188
|
+
}
|
|
189
|
+
return { run, test, config };
|
|
190
|
+
}
|
|
191
|
+
/** The repository a linked checkout resolves its own dependencies from: its nearest `.git` ancestor.
|
|
192
|
+
* A path that lands inside a `node_modules` is a package-manager store copy rather than a checkout,
|
|
193
|
+
* and everything above it belongs to whoever owns that store - clamp to the copy itself. */
|
|
194
|
+
const repoBoundary = (packageRoot) => {
|
|
195
|
+
if (packageRoot.split(sep).includes("node_modules"))
|
|
196
|
+
return packageRoot;
|
|
197
|
+
let dir = packageRoot;
|
|
198
|
+
for (let depth = 0; depth < MAX_REPO_DEPTH; depth++) {
|
|
199
|
+
if (existsSync(join(dir, ".git")))
|
|
200
|
+
return dir;
|
|
201
|
+
const parent = dirname(dir);
|
|
202
|
+
if (parent === dir)
|
|
203
|
+
break;
|
|
204
|
+
dir = parent;
|
|
205
|
+
}
|
|
206
|
+
return packageRoot;
|
|
207
|
+
};
|
|
208
|
+
/** Walk up from `start` for `node_modules/<name>/package.json`, stopping after `boundary`. */
|
|
209
|
+
const installedCopy = (start, name, boundary) => {
|
|
210
|
+
const parts = name.split("/");
|
|
211
|
+
for (let dir = start;; dir = dirname(dir)) {
|
|
212
|
+
const packageRoot = join(dir, "node_modules", ...parts);
|
|
213
|
+
const meta = readJsonSync(join(packageRoot, "package.json"));
|
|
214
|
+
if (meta !== undefined) {
|
|
215
|
+
return {
|
|
216
|
+
root: realpathOrSelf(packageRoot),
|
|
217
|
+
version: typeof meta.version === "string" ? meta.version : "unknown",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (dir === boundary)
|
|
221
|
+
return undefined;
|
|
222
|
+
const parent = dirname(dir);
|
|
223
|
+
if (parent === dir)
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
/** Every `node_modules` from the app root up to (and including) its repository root. */
|
|
228
|
+
const nodeModulesDirs = (root) => {
|
|
229
|
+
const boundary = repoBoundary(root);
|
|
230
|
+
const dirs = [];
|
|
231
|
+
let dir = root;
|
|
232
|
+
for (let depth = 0; depth < MAX_ANCESTOR_DIRS; depth++) {
|
|
233
|
+
const candidate = join(dir, "node_modules");
|
|
234
|
+
if (existsSync(candidate))
|
|
235
|
+
dirs.push(candidate);
|
|
236
|
+
if (dir === boundary)
|
|
237
|
+
break;
|
|
238
|
+
const parent = dirname(dir);
|
|
239
|
+
if (parent === dir)
|
|
240
|
+
break;
|
|
241
|
+
dir = parent;
|
|
242
|
+
}
|
|
243
|
+
return dirs;
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Package directories that are symlinked OUT of this repository - the `link:` / `file:` / `npm link`
|
|
247
|
+
* consumers whose own imports resolve somewhere this install does not own. Those are the only
|
|
248
|
+
* importers that can introduce a second copy; a normally-installed dependency resolves inside the
|
|
249
|
+
* tree that just installed it.
|
|
250
|
+
*/
|
|
251
|
+
const linkedPackageDirs = (root) => {
|
|
252
|
+
const repo = realpathOrSelf(repoBoundary(root));
|
|
253
|
+
const found = new Set();
|
|
254
|
+
let scanned = 0;
|
|
255
|
+
for (const nodeModules of nodeModulesDirs(root)) {
|
|
256
|
+
let entries;
|
|
257
|
+
try {
|
|
258
|
+
entries = readdirSync(nodeModules);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
for (const entry of entries) {
|
|
264
|
+
if (scanned++ >= MAX_SCANNED_ENTRIES)
|
|
265
|
+
return [...found];
|
|
266
|
+
if (entry === ".bin" || entry === ".cache")
|
|
267
|
+
continue;
|
|
268
|
+
const path = join(nodeModules, entry);
|
|
269
|
+
// A scope directory is not a package; its children are.
|
|
270
|
+
const children = entry.startsWith("@")
|
|
271
|
+
? readdirSync(path, { withFileTypes: true }).map((child) => join(path, child.name))
|
|
272
|
+
: [path];
|
|
273
|
+
for (const candidate of children) {
|
|
274
|
+
if (scanned++ >= MAX_SCANNED_ENTRIES)
|
|
275
|
+
return [...found];
|
|
276
|
+
let link;
|
|
277
|
+
try {
|
|
278
|
+
link = lstatSync(candidate);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (!link.isSymbolicLink())
|
|
284
|
+
continue;
|
|
285
|
+
const target = realpathOrSelf(candidate);
|
|
286
|
+
if (inside(repo, target))
|
|
287
|
+
continue;
|
|
288
|
+
found.add(target);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return [...found];
|
|
293
|
+
};
|
|
294
|
+
/** Concrete package names a declaration covers, taken from what is actually installed for the app. */
|
|
295
|
+
const declaredNames = (root, declared) => {
|
|
296
|
+
const names = new Set();
|
|
297
|
+
for (const entry of declared) {
|
|
298
|
+
if (!entry.endsWith("/*")) {
|
|
299
|
+
names.add(entry);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const scope = entry.slice(0, -2);
|
|
303
|
+
for (const nodeModules of nodeModulesDirs(root)) {
|
|
304
|
+
const scopeDir = join(nodeModules, ...scope.split("/"));
|
|
305
|
+
try {
|
|
306
|
+
for (const child of readdirSync(scopeDir))
|
|
307
|
+
names.add(`${scope}/${child}`);
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
// No such scope installed here - the pattern simply matches nothing in this tree.
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return [...names].sort();
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* Work out which foreign copies exist and which of them may be redirected.
|
|
318
|
+
*
|
|
319
|
+
* Pure discovery: it reads `package.json` files and symlink targets, never application source, and
|
|
320
|
+
* never mutates anything. Both the plugin and `nifra check`'s verification are built on it, so the
|
|
321
|
+
* enforcement and the report cannot drift apart.
|
|
322
|
+
*/
|
|
323
|
+
export function planSingleCopy(options = {}) {
|
|
324
|
+
const root = realpathOrSelf(resolve(options.cwd ?? process.cwd()));
|
|
325
|
+
const declared = options.packages ?? readSingleCopyDeclaration(root) ?? [];
|
|
326
|
+
if (declared.length === 0)
|
|
327
|
+
return { root, declared, redirects: [], skipped: [] };
|
|
328
|
+
const names = declaredNames(root, declared);
|
|
329
|
+
const redirects = [];
|
|
330
|
+
const skipped = [];
|
|
331
|
+
const rootBoundary = repoBoundary(root);
|
|
332
|
+
for (const linked of linkedPackageDirs(root)) {
|
|
333
|
+
const boundary = repoBoundary(linked);
|
|
334
|
+
for (const name of names) {
|
|
335
|
+
if (!matchesSingleCopyDeclaration(declared, name))
|
|
336
|
+
continue;
|
|
337
|
+
const theirs = installedCopy(linked, name, boundary);
|
|
338
|
+
if (theirs === undefined)
|
|
339
|
+
continue;
|
|
340
|
+
const ours = installedCopy(root, name, rootBoundary);
|
|
341
|
+
if (ours === undefined || ours.root === theirs.root)
|
|
342
|
+
continue;
|
|
343
|
+
if (ours.version !== theirs.version) {
|
|
344
|
+
skipped.push({
|
|
345
|
+
package: name,
|
|
346
|
+
from: theirs.root,
|
|
347
|
+
reason: "version-skew",
|
|
348
|
+
detail: `${theirs.version} there, ${ours.version} here - redirecting would serve a version that copy did not ask for`,
|
|
349
|
+
});
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (redirects.some((redirect) => redirect.from === theirs.root))
|
|
353
|
+
continue;
|
|
354
|
+
redirects.push({ package: name, from: theirs.root, to: ours.root, version: ours.version });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
redirects.sort((a, b) => a.from.localeCompare(b.from));
|
|
358
|
+
skipped.sort((a, b) => a.from.localeCompare(b.from));
|
|
359
|
+
return { root, declared, redirects, skipped };
|
|
360
|
+
}
|
|
361
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
362
|
+
/**
|
|
363
|
+
* The module a foreign file should become: the same relative file inside the winning copy.
|
|
364
|
+
*
|
|
365
|
+
* Mapping FILE to FILE rather than file to export subpath is what makes this safe on a package with a
|
|
366
|
+
* rich exports map. Guessing that `dist/server.js` is the `./server` subpath is guesswork that fails
|
|
367
|
+
* on the first package whose map is not one-to-one; the counterpart path is a fact, and the two trees
|
|
368
|
+
* hold the same version, so the layout matches by construction. When it does not, the file loads
|
|
369
|
+
* untouched.
|
|
370
|
+
*/
|
|
371
|
+
const counterpart = (redirect, path) => {
|
|
372
|
+
const rel = relative(redirect.from, path);
|
|
373
|
+
if (rel === "" || rel.startsWith(`..${sep}`))
|
|
374
|
+
return undefined;
|
|
375
|
+
const target = join(redirect.to, rel);
|
|
376
|
+
try {
|
|
377
|
+
return statSync(target).isFile() ? target : undefined;
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
/**
|
|
384
|
+
* A re-export of the winning file, written so both module systems keep working.
|
|
385
|
+
*
|
|
386
|
+
* `export *` forwards the named bindings, including the ones Bun synthesizes for a CommonJS target,
|
|
387
|
+
* and the explicit default covers `module.exports` (react is CommonJS, and its consumers `require` it
|
|
388
|
+
* as often as they import it). Static imports keep the module SYNCHRONOUS, which matters more than it
|
|
389
|
+
* looks: an `await import()` here would make the module async, and a CommonJS `require` of an async
|
|
390
|
+
* module fails outright with `require() async module … is unsupported`.
|
|
391
|
+
*/
|
|
392
|
+
const reexport = (target) => {
|
|
393
|
+
const from = JSON.stringify(target);
|
|
394
|
+
return (`export * from ${from};\n` +
|
|
395
|
+
`import * as __singleCopy from ${from};\n` +
|
|
396
|
+
`export default __singleCopy.default ?? __singleCopy;\n`);
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Pin every declared package to the app's copy.
|
|
400
|
+
*
|
|
401
|
+
* Two hooks, because the two phases resolve differently. `onResolve` is the direct statement of the
|
|
402
|
+
* rule and it is what a BUNDLER honours - it sees every bare specifier. The runtime does not deliver
|
|
403
|
+
* bare specifiers to it at all, so the `onLoad` arm intercepts the foreign file itself and hands back
|
|
404
|
+
* a re-export of the counterpart in the winning copy. The importer gets the same function objects and
|
|
405
|
+
* therefore the same module state, which is the entire requirement.
|
|
406
|
+
*/
|
|
407
|
+
export function singleCopyPlugin(options = {}) {
|
|
408
|
+
const plan = planSingleCopy(options);
|
|
409
|
+
const declared = plan.declared;
|
|
410
|
+
return {
|
|
411
|
+
name: "nifra-single-copy",
|
|
412
|
+
plan,
|
|
413
|
+
setup(build) {
|
|
414
|
+
if (declared.length > 0) {
|
|
415
|
+
const exact = declared.filter((entry) => !entry.endsWith("/*")).map(escapeRegExp);
|
|
416
|
+
const scopes = declared
|
|
417
|
+
.filter((entry) => entry.endsWith("/*"))
|
|
418
|
+
.map((entry) => `${escapeRegExp(entry.slice(0, -1))}[^/]+`);
|
|
419
|
+
const alternatives = [...exact.map((name) => `${name}(?:/.*)?`), ...scopes];
|
|
420
|
+
if (alternatives.length > 0) {
|
|
421
|
+
const filter = new RegExp(`^(?:${alternatives.join("|")})$`);
|
|
422
|
+
build.onResolve({ filter }, (args) => {
|
|
423
|
+
try {
|
|
424
|
+
return { path: Bun.resolveSync(args.path, plan.root) };
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// Not installed for the app, so there is nothing to dedupe TO. Forcing a resolution here
|
|
428
|
+
// would turn a working import into a build failure.
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (plan.redirects.length === 0)
|
|
435
|
+
return;
|
|
436
|
+
const roots = plan.redirects.map((redirect) => escapeRegExp(redirect.from)).join("|");
|
|
437
|
+
// Anchored at the foreign roots so the winning copy can never match itself. It must not: this
|
|
438
|
+
// hook has to return contents for everything it matches, and re-emitting a CommonJS entry as an
|
|
439
|
+
// ES module is how `Export named 'useEffect' not found` happens.
|
|
440
|
+
const filter = new RegExp(`^(?:${roots})[/\\\\].*${REDIRECTABLE.source}`);
|
|
441
|
+
build.onLoad({ filter }, (args) => {
|
|
442
|
+
for (const redirect of plan.redirects) {
|
|
443
|
+
const target = counterpart(redirect, args.path);
|
|
444
|
+
if (target === undefined)
|
|
445
|
+
continue;
|
|
446
|
+
return { contents: reexport(target), loader: "js" };
|
|
447
|
+
}
|
|
448
|
+
// A runtime `onLoad` must always return an object - returning nothing fails the load rather
|
|
449
|
+
// than falling through to the default. Hand back the file as it was, at its own path, so its
|
|
450
|
+
// relative imports keep resolving exactly as they would have.
|
|
451
|
+
return { contents: readFileSync(args.path, "utf8"), loader: "js" };
|
|
452
|
+
});
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
/** Set once the runtime plugin is installed, so a checker can tell "one copy" from "deduplicated". */
|
|
457
|
+
export const SINGLE_COPY_ACTIVE = Symbol.for("nifra.single-copy.active");
|
|
458
|
+
/**
|
|
459
|
+
* Install the plugin into the Bun RUNTIME. Import `@nifrajs/core/single-copy/register` from a
|
|
460
|
+
* `bunfig.toml` preload rather than calling this from application code: a resolver installed from
|
|
461
|
+
* inside a module cannot affect the imports that module already resolved.
|
|
462
|
+
*/
|
|
463
|
+
export function registerSingleCopy(options = {}) {
|
|
464
|
+
const plugin = singleCopyPlugin(options);
|
|
465
|
+
if (plugin.plan.declared.length > 0)
|
|
466
|
+
Bun.plugin(plugin);
|
|
467
|
+
globalThis[SINGLE_COPY_ACTIVE] = plugin.plan;
|
|
468
|
+
return plugin.plan;
|
|
469
|
+
}
|
|
470
|
+
//# sourceMappingURL=single-copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"single-copy.js","sourceRoot":"","sources":["../src/single-copy.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Bun-native, contract-first HTTP framework - the router, server, and route descriptor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"types": "./dist/ws.d.ts",
|
|
38
38
|
"default": "./dist/ws.js"
|
|
39
39
|
},
|
|
40
|
+
"./edge-kit": {
|
|
41
|
+
"bun": "./src/edge-kit.ts",
|
|
42
|
+
"types": "./dist/edge-kit.d.ts",
|
|
43
|
+
"default": "./dist/edge-kit.js"
|
|
44
|
+
},
|
|
40
45
|
"./wire": {
|
|
41
46
|
"bun": "./src/wire.ts",
|
|
42
47
|
"types": "./dist/wire.d.ts",
|
|
@@ -237,6 +242,11 @@
|
|
|
237
242
|
"types": "./dist/server/response-contract-lane.d.ts",
|
|
238
243
|
"default": "./dist/server/response-contract-lane.js"
|
|
239
244
|
},
|
|
245
|
+
"./response-observer": {
|
|
246
|
+
"bun": "./src/response-observer.ts",
|
|
247
|
+
"types": "./dist/response-observer.d.ts",
|
|
248
|
+
"default": "./dist/response-observer.js"
|
|
249
|
+
},
|
|
240
250
|
"./tool-contract": {
|
|
241
251
|
"bun": "./src/tool-contract.ts",
|
|
242
252
|
"types": "./dist/tool-contract.d.ts",
|
|
@@ -251,6 +261,16 @@
|
|
|
251
261
|
"bun": "./src/execution-policy.ts",
|
|
252
262
|
"types": "./dist/execution-policy.d.ts",
|
|
253
263
|
"default": "./dist/execution-policy.js"
|
|
264
|
+
},
|
|
265
|
+
"./single-copy": {
|
|
266
|
+
"bun": "./src/single-copy.ts",
|
|
267
|
+
"types": "./dist/single-copy.d.ts",
|
|
268
|
+
"default": "./dist/single-copy.js"
|
|
269
|
+
},
|
|
270
|
+
"./single-copy/register": {
|
|
271
|
+
"bun": "./src/single-copy-register.ts",
|
|
272
|
+
"types": "./dist/single-copy-register.d.ts",
|
|
273
|
+
"default": "./dist/single-copy-register.js"
|
|
254
274
|
}
|
|
255
275
|
},
|
|
256
276
|
"files": [
|
|
@@ -265,7 +285,9 @@
|
|
|
265
285
|
"./src/node-direct.ts",
|
|
266
286
|
"./dist/node-direct.js",
|
|
267
287
|
"./src/mcp-runtime.ts",
|
|
268
|
-
"./dist/mcp-runtime.js"
|
|
288
|
+
"./dist/mcp-runtime.js",
|
|
289
|
+
"./src/single-copy-register.ts",
|
|
290
|
+
"./dist/single-copy-register.js"
|
|
269
291
|
],
|
|
270
292
|
"scripts": {
|
|
271
293
|
"build": "tsc -p tsconfig.build.json && bun run ../../scripts/fix-dts.ts dist"
|
package/src/assurance.ts
CHANGED
|
@@ -36,6 +36,11 @@ import type { NifraManifestSigner } from "./manifest.ts"
|
|
|
36
36
|
import { type ReflectedRoute, reflectRoutes } from "./reflection.ts"
|
|
37
37
|
import type { Method } from "./router/router.ts"
|
|
38
38
|
|
|
39
|
+
export {
|
|
40
|
+
type SecurityBaselineLevel,
|
|
41
|
+
type SecurityBaselineOptions,
|
|
42
|
+
securityBaseline,
|
|
43
|
+
} from "./security/baseline.ts"
|
|
39
44
|
export type { AssuranceAttachment, AssuranceDeclaration, AssuranceEvidence, AssuranceScope }
|
|
40
45
|
export { withRouteAssurance }
|
|
41
46
|
|
|
@@ -135,6 +140,20 @@ export interface AssuranceRouteSelector {
|
|
|
135
140
|
readonly zone?: CapabilityZone
|
|
136
141
|
/** Match routes whose response classification is at least this sensitivity. */
|
|
137
142
|
readonly classificationAtLeast?: DataClassification
|
|
143
|
+
/**
|
|
144
|
+
* Match routes by whether they declare a request-body schema. `true` selects routes that parse a
|
|
145
|
+
* body (the buffered-read surface F-001 is about); `false` selects bodyless routes. Reflection
|
|
146
|
+
* already carries `schema.body`, so this reads what the route declared, not where it lives.
|
|
147
|
+
*/
|
|
148
|
+
readonly hasBody?: boolean
|
|
149
|
+
/**
|
|
150
|
+
* Match routes by their effective transport body policy. `"unlimited"` is the explicit
|
|
151
|
+
* streaming/upload exemption; `"bounded"` is any finite cap (an explicit number or the inherited
|
|
152
|
+
* server default); `"unset"` is a route that declared no `bodyLimit` at all. Lets a baseline say
|
|
153
|
+
* "a body-schema route may never be unlimited" as a first-class, movable invariant rather than a
|
|
154
|
+
* path list. A route with no schema reports `"unset"` unless it names a limit.
|
|
155
|
+
*/
|
|
156
|
+
readonly bodyLimit?: "bounded" | "unlimited" | "unset"
|
|
138
157
|
}
|
|
139
158
|
|
|
140
159
|
/** Extra inputs an assurance evaluation needs beyond the routes themselves. */
|
|
@@ -337,6 +356,8 @@ export function defineAssurancePolicy(policy: AssurancePolicy): AssurancePolicy
|
|
|
337
356
|
"capabilities",
|
|
338
357
|
...CLASS_SELECTOR_KEYS,
|
|
339
358
|
"classificationAtLeast",
|
|
359
|
+
"hasBody",
|
|
360
|
+
"bodyLimit",
|
|
340
361
|
].includes(key),
|
|
341
362
|
)
|
|
342
363
|
if (unknown.length > 0) {
|
|
@@ -351,6 +372,21 @@ export function defineAssurancePolicy(policy: AssurancePolicy): AssurancePolicy
|
|
|
351
372
|
`route assurance: rule ${JSON.stringify(name)} tools selector must be boolean`,
|
|
352
373
|
)
|
|
353
374
|
}
|
|
375
|
+
if (rule.match.hasBody !== undefined && typeof rule.match.hasBody !== "boolean") {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`route assurance: rule ${JSON.stringify(name)} hasBody selector must be boolean`,
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
if (
|
|
381
|
+
rule.match.bodyLimit !== undefined &&
|
|
382
|
+
rule.match.bodyLimit !== "bounded" &&
|
|
383
|
+
rule.match.bodyLimit !== "unlimited" &&
|
|
384
|
+
rule.match.bodyLimit !== "unset"
|
|
385
|
+
) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`route assurance: rule ${JSON.stringify(name)} bodyLimit selector must be "bounded", "unlimited", or "unset"`,
|
|
388
|
+
)
|
|
389
|
+
}
|
|
354
390
|
const methods = rule.match.methods?.map((method) => method.toUpperCase())
|
|
355
391
|
for (const method of methods ?? []) {
|
|
356
392
|
if (!validMethod(method)) {
|
|
@@ -431,6 +467,8 @@ export function defineAssurancePolicy(policy: AssurancePolicy): AssurancePolicy
|
|
|
431
467
|
...(rule.match.classificationAtLeast !== undefined
|
|
432
468
|
? { classificationAtLeast: rule.match.classificationAtLeast }
|
|
433
469
|
: {}),
|
|
470
|
+
...(rule.match.hasBody !== undefined ? { hasBody: rule.match.hasBody } : {}),
|
|
471
|
+
...(rule.match.bodyLimit !== undefined ? { bodyLimit: rule.match.bodyLimit } : {}),
|
|
434
472
|
}),
|
|
435
473
|
require: required,
|
|
436
474
|
forbid: forbidden,
|
|
@@ -558,16 +596,35 @@ export function defineAssuranceConfig(config: AssuranceConfig): AssuranceConfig
|
|
|
558
596
|
* reflection alone. A caller that omits it while the selector needs it gets no match, so callers that
|
|
559
597
|
* accept user policy must reject that combination up front - `defineAssuranceConfig` does.
|
|
560
598
|
*/
|
|
599
|
+
/**
|
|
600
|
+
* The effective transport body policy a `bodyLimit` selector matches on. A route that named no limit
|
|
601
|
+
* reports `"unset"` (it inherits the server default, which a policy that cares about explicit caps
|
|
602
|
+
* should treat distinctly); a finite number is `"bounded"`; the streaming/upload exemption is
|
|
603
|
+
* `"unlimited"`. Kept total so a future `bodyLimit` shape cannot fall through to a silent match.
|
|
604
|
+
*/
|
|
605
|
+
function effectiveBodyLimit(schema: ReflectedRoute["schema"]): "bounded" | "unlimited" | "unset" {
|
|
606
|
+
const limit = schema?.bodyLimit
|
|
607
|
+
if (limit === "unlimited") return "unlimited"
|
|
608
|
+
if (typeof limit === "number") return "bounded"
|
|
609
|
+
return "unset"
|
|
610
|
+
}
|
|
611
|
+
|
|
561
612
|
export function matchesAssuranceSelector(
|
|
562
|
-
route: Pick<
|
|
613
|
+
route: Pick<
|
|
614
|
+
ReflectedRoute,
|
|
615
|
+
"method" | "path" | "tool" | "capabilities" | "classification" | "schema"
|
|
616
|
+
>,
|
|
563
617
|
selector: AssuranceRouteSelector,
|
|
564
618
|
definitions?: ReadonlyMap<string, CapabilityDefinition>,
|
|
565
619
|
): boolean {
|
|
566
620
|
const { methods, paths, tools, capabilities, access, zone, classificationAtLeast } = selector
|
|
621
|
+
const { hasBody, bodyLimit } = selector
|
|
567
622
|
if (methods !== undefined && !methods.includes(route.method as Method)) return false
|
|
568
623
|
if (paths !== undefined && !paths.some((pattern) => routeGlob(pattern).test(route.path)))
|
|
569
624
|
return false
|
|
570
625
|
if (tools !== undefined && (route.tool !== undefined) !== tools) return false
|
|
626
|
+
if (hasBody !== undefined && (route.schema?.body !== undefined) !== hasBody) return false
|
|
627
|
+
if (bodyLimit !== undefined && effectiveBodyLimit(route.schema) !== bodyLimit) return false
|
|
571
628
|
if (
|
|
572
629
|
classificationAtLeast !== undefined &&
|
|
573
630
|
(route.classification === undefined ||
|