@knitli/openapi-mcp 0.0.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/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +362 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +249 -0
- package/dist/compile.d.ts +14 -0
- package/dist/compile.js +132 -0
- package/dist/compiler.d.ts +13 -0
- package/dist/compiler.js +7 -0
- package/dist/conformance/fixtures.d.ts +24 -0
- package/dist/conformance/fixtures.js +119 -0
- package/dist/conformance/index.d.ts +2 -0
- package/dist/conformance/index.js +2 -0
- package/dist/conformance/runtime-suite.d.ts +31 -0
- package/dist/conformance/runtime-suite.js +343 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/load.d.ts +31 -0
- package/dist/load.js +20 -0
- package/dist/operations.d.ts +27 -0
- package/dist/operations.js +231 -0
- package/dist/permissions.d.ts +31 -0
- package/dist/permissions.js +89 -0
- package/dist/release/compile-release.d.ts +33 -0
- package/dist/release/compile-release.js +1620 -0
- package/dist/release/load-v4.d.ts +29 -0
- package/dist/release/load-v4.js +297 -0
- package/dist/release/manifest-builder.d.ts +19 -0
- package/dist/release/manifest-builder.js +28 -0
- package/dist/release/publish.d.ts +45 -0
- package/dist/release/publish.js +346 -0
- package/dist/release/reference-map.d.ts +53 -0
- package/dist/release/reference-map.js +347 -0
- package/dist/release/schema-v4.d.ts +5 -0
- package/dist/release/schema-v4.js +91 -0
- package/dist/runtime/action-permit.d.ts +11 -0
- package/dist/runtime/action-permit.js +1 -0
- package/dist/runtime/classify.d.ts +16 -0
- package/dist/runtime/classify.js +279 -0
- package/dist/runtime/credential-binding.d.ts +3 -0
- package/dist/runtime/credential-binding.js +101 -0
- package/dist/runtime/digest.d.ts +8 -0
- package/dist/runtime/digest.js +80 -0
- package/dist/runtime/errors.d.ts +14 -0
- package/dist/runtime/errors.js +15 -0
- package/dist/runtime/index.d.ts +20 -0
- package/dist/runtime/index.js +14 -0
- package/dist/runtime/manifest.d.ts +33 -0
- package/dist/runtime/manifest.js +489 -0
- package/dist/runtime/prepared-call.d.ts +39 -0
- package/dist/runtime/prepared-call.js +566 -0
- package/dist/runtime/references.d.ts +7 -0
- package/dist/runtime/references.js +197 -0
- package/dist/runtime/runtime.d.ts +25 -0
- package/dist/runtime/runtime.js +1272 -0
- package/dist/runtime/schema-keywords.d.ts +2 -0
- package/dist/runtime/schema-keywords.js +30 -0
- package/dist/runtime/schema-resolver.d.ts +10 -0
- package/dist/runtime/schema-resolver.js +237 -0
- package/dist/runtime/serialize.d.ts +12 -0
- package/dist/runtime/serialize.js +877 -0
- package/dist/runtime/store.d.ts +52 -0
- package/dist/runtime/store.js +582 -0
- package/dist/runtime/strict-json.d.ts +19 -0
- package/dist/runtime/strict-json.js +539 -0
- package/dist/runtime/types.d.ts +457 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/verify-record.d.ts +6 -0
- package/dist/runtime/verify-record.js +492 -0
- package/dist/runtime/versions.d.ts +63 -0
- package/dist/runtime/versions.js +86 -0
- package/dist/safety.d.ts +28 -0
- package/dist/safety.js +60 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.js +52 -0
- package/dist/schemas.d.ts +9 -0
- package/dist/schemas.js +16 -0
- package/dist/sign.d.ts +20 -0
- package/dist/sign.js +55 -0
- package/dist/sqlite/auth.d.ts +33 -0
- package/dist/sqlite/auth.js +973 -0
- package/dist/sqlite/catalog-store.d.ts +20 -0
- package/dist/sqlite/catalog-store.js +370 -0
- package/dist/sqlite/destination-guard.d.ts +24 -0
- package/dist/sqlite/destination-guard.js +140 -0
- package/dist/sqlite/generation-store.d.ts +12 -0
- package/dist/sqlite/generation-store.js +721 -0
- package/dist/sqlite/guarded-fetch.d.ts +24 -0
- package/dist/sqlite/guarded-fetch.js +626 -0
- package/dist/sqlite/index.d.ts +5 -0
- package/dist/sqlite/index.js +5 -0
- package/dist/sqlite/oauth-resource.d.ts +2 -0
- package/dist/sqlite/oauth-resource.js +15 -0
- package/dist/sqlite/release-completion.d.ts +5 -0
- package/dist/sqlite/release-completion.js +80 -0
- package/dist/stdio/action-broker.d.ts +15 -0
- package/dist/stdio/action-broker.js +136 -0
- package/dist/stdio/authorizer.d.ts +30 -0
- package/dist/stdio/authorizer.js +484 -0
- package/dist/stdio/config.d.ts +77 -0
- package/dist/stdio/config.js +144 -0
- package/dist/stdio/exact-policy.d.ts +41 -0
- package/dist/stdio/exact-policy.js +361 -0
- package/dist/stdio/index.d.ts +6 -0
- package/dist/stdio/index.js +148 -0
- package/dist/stdio/render.d.ts +8 -0
- package/dist/stdio/render.js +253 -0
- package/dist/stdio/request-state.d.ts +18 -0
- package/dist/stdio/request-state.js +79 -0
- package/dist/stdio/search.d.ts +10 -0
- package/dist/stdio/search.js +76 -0
- package/dist/stdio/server.d.ts +29 -0
- package/dist/stdio/server.js +309 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +1 -0
- package/package.json +131 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { compile } from "./compile.js";
|
|
6
|
+
import { compileRelease } from "./release/compile-release.js";
|
|
7
|
+
import { publishRelease } from "./release/publish.js";
|
|
8
|
+
import { generateKeypair, signArtifact, verifyArtifact } from "./sign.js";
|
|
9
|
+
const USAGE = `openapi-mcp — compile OpenAPI documents into signed MCP artifacts
|
|
10
|
+
|
|
11
|
+
compile --spec <path> --api <name> --out <path> [--append] [--permissions <path>] [--sign-key <path>] (legacy v3)
|
|
12
|
+
compile-release --spec <path> (--source-uri <https-uri> | --source-label <label>) --source-revision <revision>
|
|
13
|
+
--catalog <id> --release <id> --generation <n> --issuer <id> --key-id <id>
|
|
14
|
+
--policy-id <id> --allowed-origin <https-origin> --out <directory> --sign-key <path>
|
|
15
|
+
[--permissions <path>] [--reference-root <path> --reference-map <path>]
|
|
16
|
+
verify --artifact <path> --sig <path> --pub <path> (legacy v3 exact-file signature)
|
|
17
|
+
keygen [--out <dir>]
|
|
18
|
+
serve --config <absolute-config-path>
|
|
19
|
+
`;
|
|
20
|
+
const DIAGNOSTIC_UNSAFE_CHARACTERS =
|
|
21
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: diagnostics must visibly encode terminal controls.
|
|
22
|
+
/[\u0000-\u001F\u007F-\u009F\u2028\u2029\p{Cf}]/gu;
|
|
23
|
+
function renderDiagnostic(message) {
|
|
24
|
+
return message.replace(DIAGNOSTIC_UNSAFE_CHARACTERS, (character) => {
|
|
25
|
+
const codePoint = character.codePointAt(0);
|
|
26
|
+
if (codePoint === undefined)
|
|
27
|
+
return "�";
|
|
28
|
+
return `\\u${codePoint.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function fail(message, opts = {}) {
|
|
32
|
+
console.error(opts.usage === false
|
|
33
|
+
? `error: ${renderDiagnostic(message)}`
|
|
34
|
+
: `error: ${renderDiagnostic(message)}\n\n${USAGE}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
/** parseArgs throws on unknown flags and missing values; route that through fail. */
|
|
38
|
+
function parseOrFail(config) {
|
|
39
|
+
try {
|
|
40
|
+
return parseArgs(config);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
47
|
+
if (command === "serve") {
|
|
48
|
+
const { values } = parseOrFail({
|
|
49
|
+
args: rest,
|
|
50
|
+
options: { config: { type: "string" } },
|
|
51
|
+
strict: true,
|
|
52
|
+
});
|
|
53
|
+
if (!values.config)
|
|
54
|
+
fail("--config is required");
|
|
55
|
+
try {
|
|
56
|
+
const { parseOpenApiStdioConfig, serveOpenApiStdio } = await import("./stdio/index.js");
|
|
57
|
+
await serveOpenApiStdio(parseOpenApiStdioConfig(await readFile(values.config, "utf8")));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
fail("OpenAPI stdio startup failed", { usage: false });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (command === "keygen") {
|
|
64
|
+
const { values } = parseOrFail({
|
|
65
|
+
args: rest,
|
|
66
|
+
options: { out: { type: "string" } },
|
|
67
|
+
strict: true,
|
|
68
|
+
});
|
|
69
|
+
const dir = values.out ?? ".";
|
|
70
|
+
const pubPath = join(dir, "openapi-mcp.pub");
|
|
71
|
+
const keyPath = join(dir, "openapi-mcp.key");
|
|
72
|
+
try {
|
|
73
|
+
const { publicKeyPem, privateKeyPem } = generateKeypair();
|
|
74
|
+
// "wx" fails with EEXIST on any existing file *or* symlink (dangling or
|
|
75
|
+
// not) instead of following it — existsSync + a separate write would
|
|
76
|
+
// both race and let a pre-planted symlink redirect these bytes.
|
|
77
|
+
await writeFile(pubPath, publicKeyPem, { flag: "wx" });
|
|
78
|
+
try {
|
|
79
|
+
await writeFile(keyPath, privateKeyPem, { flag: "wx", mode: 0o600 });
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
// All-or-nothing: never leave a public key with no matching private key.
|
|
83
|
+
await unlink(pubPath).catch(() => { });
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const e = err;
|
|
89
|
+
if (e.code === "EEXIST")
|
|
90
|
+
fail(`${e.path} exists`, { usage: false });
|
|
91
|
+
fail(e instanceof Error ? e.message : String(e), { usage: false });
|
|
92
|
+
}
|
|
93
|
+
console.log(pubPath);
|
|
94
|
+
console.log(keyPath);
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
if (command === "verify") {
|
|
98
|
+
const { values } = parseOrFail({
|
|
99
|
+
args: rest,
|
|
100
|
+
options: {
|
|
101
|
+
artifact: { type: "string" },
|
|
102
|
+
sig: { type: "string" },
|
|
103
|
+
pub: { type: "string" },
|
|
104
|
+
},
|
|
105
|
+
strict: true,
|
|
106
|
+
});
|
|
107
|
+
if (!values.artifact)
|
|
108
|
+
fail("--artifact is required");
|
|
109
|
+
if (!values.sig)
|
|
110
|
+
fail("--sig is required");
|
|
111
|
+
if (!values.pub)
|
|
112
|
+
fail("--pub is required");
|
|
113
|
+
let ok;
|
|
114
|
+
try {
|
|
115
|
+
const sigB64 = (await readFile(values.sig, "utf8")).trim();
|
|
116
|
+
const pubPem = await readFile(values.pub, "utf8");
|
|
117
|
+
ok = await verifyArtifact(values.artifact, sigB64, pubPem);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
fail(err instanceof Error ? err.message : String(err), { usage: false });
|
|
121
|
+
}
|
|
122
|
+
console.log(ok ? "valid" : "invalid");
|
|
123
|
+
process.exit(ok ? 0 : 1);
|
|
124
|
+
}
|
|
125
|
+
if (command === "compile-release") {
|
|
126
|
+
const { values } = parseOrFail({
|
|
127
|
+
args: rest,
|
|
128
|
+
options: {
|
|
129
|
+
spec: { type: "string" },
|
|
130
|
+
"source-uri": { type: "string" },
|
|
131
|
+
"source-label": { type: "string" },
|
|
132
|
+
"source-revision": { type: "string" },
|
|
133
|
+
catalog: { type: "string" },
|
|
134
|
+
release: { type: "string" },
|
|
135
|
+
generation: { type: "string" },
|
|
136
|
+
issuer: { type: "string" },
|
|
137
|
+
"key-id": { type: "string" },
|
|
138
|
+
"policy-id": { type: "string" },
|
|
139
|
+
"allowed-origin": { type: "string", multiple: true },
|
|
140
|
+
out: { type: "string" },
|
|
141
|
+
"sign-key": { type: "string" },
|
|
142
|
+
permissions: { type: "string" },
|
|
143
|
+
"reference-root": { type: "string" },
|
|
144
|
+
"reference-map": { type: "string" },
|
|
145
|
+
},
|
|
146
|
+
strict: true,
|
|
147
|
+
});
|
|
148
|
+
for (const [flag, value] of [
|
|
149
|
+
["--spec", values.spec],
|
|
150
|
+
["--source-revision", values["source-revision"]],
|
|
151
|
+
["--catalog", values.catalog],
|
|
152
|
+
["--release", values.release],
|
|
153
|
+
["--generation", values.generation],
|
|
154
|
+
["--issuer", values.issuer],
|
|
155
|
+
["--key-id", values["key-id"]],
|
|
156
|
+
["--policy-id", values["policy-id"]],
|
|
157
|
+
["--allowed-origin", values["allowed-origin"]?.[0]],
|
|
158
|
+
["--out", values.out],
|
|
159
|
+
["--sign-key", values["sign-key"]],
|
|
160
|
+
])
|
|
161
|
+
if (!value)
|
|
162
|
+
fail(`${flag} is required`);
|
|
163
|
+
if ((values["source-uri"] === undefined) ===
|
|
164
|
+
(values["source-label"] === undefined)) {
|
|
165
|
+
fail("exactly one of --source-uri or --source-label is required");
|
|
166
|
+
}
|
|
167
|
+
if ((values["reference-root"] === undefined) !==
|
|
168
|
+
(values["reference-map"] === undefined)) {
|
|
169
|
+
fail("--reference-root and --reference-map must be supplied together");
|
|
170
|
+
}
|
|
171
|
+
const generation = Number(values.generation);
|
|
172
|
+
if (!Number.isSafeInteger(generation) || generation < 0)
|
|
173
|
+
fail("--generation must be a non-negative safe integer");
|
|
174
|
+
try {
|
|
175
|
+
const privateKeyPem = await readFile(values["sign-key"], "utf8").catch((error) => {
|
|
176
|
+
throw new Error("signing key could not be read", { cause: error });
|
|
177
|
+
});
|
|
178
|
+
const provenance = values["source-uri"] !== undefined
|
|
179
|
+
? { sourceUri: values["source-uri"] }
|
|
180
|
+
: { sourceLabel: values["source-label"] };
|
|
181
|
+
const compiled = await compileRelease({
|
|
182
|
+
...provenance,
|
|
183
|
+
specPath: values.spec,
|
|
184
|
+
sourceRevision: values["source-revision"],
|
|
185
|
+
catalogId: values.catalog,
|
|
186
|
+
releaseId: values.release,
|
|
187
|
+
generation,
|
|
188
|
+
issuer: values.issuer,
|
|
189
|
+
keyId: values["key-id"],
|
|
190
|
+
policyId: values["policy-id"],
|
|
191
|
+
allowedOrigins: values["allowed-origin"],
|
|
192
|
+
outDir: values.out,
|
|
193
|
+
privateKeyPem,
|
|
194
|
+
permissionsPath: values.permissions,
|
|
195
|
+
referenceRoot: values["reference-root"],
|
|
196
|
+
referenceMapPath: values["reference-map"],
|
|
197
|
+
});
|
|
198
|
+
await publishRelease(compiled, { directory: values.out });
|
|
199
|
+
console.log(`compiled immutable v5 release ${values.release}`);
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
fail(err instanceof Error ? err.message : String(err), { usage: false });
|
|
203
|
+
}
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
206
|
+
if (command !== "serve") {
|
|
207
|
+
if (command !== "compile") {
|
|
208
|
+
fail(command ? `unknown command "${command}"` : "no command given");
|
|
209
|
+
}
|
|
210
|
+
const { values } = parseOrFail({
|
|
211
|
+
args: rest,
|
|
212
|
+
options: {
|
|
213
|
+
spec: { type: "string" },
|
|
214
|
+
api: { type: "string" },
|
|
215
|
+
out: { type: "string" },
|
|
216
|
+
append: { type: "boolean" },
|
|
217
|
+
permissions: { type: "string" },
|
|
218
|
+
"sign-key": { type: "string" },
|
|
219
|
+
},
|
|
220
|
+
strict: true,
|
|
221
|
+
});
|
|
222
|
+
if (!values.spec)
|
|
223
|
+
fail("--spec is required");
|
|
224
|
+
if (!values.api)
|
|
225
|
+
fail("--api is required");
|
|
226
|
+
if (!values.out)
|
|
227
|
+
fail("--out is required");
|
|
228
|
+
const started = Date.now();
|
|
229
|
+
try {
|
|
230
|
+
const result = await compile({
|
|
231
|
+
specPath: values.spec,
|
|
232
|
+
api: values.api,
|
|
233
|
+
outPath: values.out,
|
|
234
|
+
append: values.append === true,
|
|
235
|
+
permissionsPath: values.permissions,
|
|
236
|
+
});
|
|
237
|
+
console.log(`compiled ${result.operations} operations, ${result.schemas} schemas ` +
|
|
238
|
+
`(${result.mapped} permission-mapped) in ${Date.now() - started} ms -> ${values.out}`);
|
|
239
|
+
if (values["sign-key"]) {
|
|
240
|
+
const privateKeyPem = await readFile(values["sign-key"], "utf8");
|
|
241
|
+
const sig = await signArtifact(values.out, privateKeyPem);
|
|
242
|
+
await writeFile(`${values.out}.sig`, sig);
|
|
243
|
+
console.log(`signed -> ${values.out}.sig`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
fail(err instanceof Error ? err.message : String(err), { usage: false });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CompileOptions {
|
|
2
|
+
specPath: string;
|
|
3
|
+
api: string;
|
|
4
|
+
outPath: string;
|
|
5
|
+
permissionsPath?: string;
|
|
6
|
+
/** Mount into an existing artifact instead of replacing it. */
|
|
7
|
+
append?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface CompileResult {
|
|
10
|
+
operations: number;
|
|
11
|
+
schemas: number;
|
|
12
|
+
mapped: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function compile(options: CompileOptions): Promise<CompileResult>;
|
package/dist/compile.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { existsSync, renameSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
import { loadSpec } from "./load.js";
|
|
5
|
+
import { extractOperations } from "./operations.js";
|
|
6
|
+
import { applyPermissions, buildPermissionIndex, } from "./permissions.js";
|
|
7
|
+
import { createSchema, LEGACY_FORMAT_VERSION } from "./schema.js";
|
|
8
|
+
import { extractSchemas } from "./schemas.js";
|
|
9
|
+
const COMPILER_VERSION = "0.1.0";
|
|
10
|
+
export async function compile(options) {
|
|
11
|
+
const doc = await loadSpec(options.specPath);
|
|
12
|
+
const operations = extractOperations(doc, options.api);
|
|
13
|
+
const schemas = extractSchemas(doc, options.api);
|
|
14
|
+
if (options.permissionsPath) {
|
|
15
|
+
const dataset = JSON.parse(await readFile(options.permissionsPath, "utf8"));
|
|
16
|
+
applyPermissions(operations, buildPermissionIndex(dataset));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
// Still recompute risk so unmapped writes land on `high`.
|
|
20
|
+
applyPermissions(operations, { byMethod: new Map(), privilege: new Map() });
|
|
21
|
+
}
|
|
22
|
+
const exists = existsSync(options.outPath);
|
|
23
|
+
if (options.append && !exists) {
|
|
24
|
+
throw new Error(`${options.outPath} does not exist; cannot append`);
|
|
25
|
+
}
|
|
26
|
+
// A fresh compile builds into a sibling and renames over the target only
|
|
27
|
+
// after a clean close. Replacing in place would destroy a valid artifact the
|
|
28
|
+
// instant anything downstream failed — rollback can only empty the new
|
|
29
|
+
// database, it cannot bring the old one back. Append has nothing to protect:
|
|
30
|
+
// it mutates the existing artifact under its own transaction.
|
|
31
|
+
const buildPath = options.append
|
|
32
|
+
? options.outPath
|
|
33
|
+
: `${options.outPath}.building`;
|
|
34
|
+
if (!options.append) {
|
|
35
|
+
try {
|
|
36
|
+
unlinkSync(buildPath);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// No leftover build file from an interrupted run.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
let existingApis = [];
|
|
43
|
+
let committed = false;
|
|
44
|
+
const db = new DatabaseSync(buildPath);
|
|
45
|
+
try {
|
|
46
|
+
db.exec("BEGIN");
|
|
47
|
+
if (options.append) {
|
|
48
|
+
const version = db
|
|
49
|
+
.prepare("SELECT value FROM meta WHERE key = ?")
|
|
50
|
+
.get("format_version")?.value;
|
|
51
|
+
if (version !== String(LEGACY_FORMAT_VERSION)) {
|
|
52
|
+
throw new Error(`format_version mismatch: artifact is ${version}, compiler is ${LEGACY_FORMAT_VERSION}`);
|
|
53
|
+
}
|
|
54
|
+
const mounted = JSON.parse(db.prepare("SELECT value FROM meta WHERE key = ?").get("apis")?.value ?? "[]");
|
|
55
|
+
if (mounted.includes(options.api)) {
|
|
56
|
+
throw new Error(`api "${options.api}" is already mounted`);
|
|
57
|
+
}
|
|
58
|
+
existingApis = mounted;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
createSchema(db);
|
|
62
|
+
}
|
|
63
|
+
const insertOp = db.prepare(`INSERT INTO operations
|
|
64
|
+
(qualified_id, api, operation_id, method, path, safety, risk,
|
|
65
|
+
operation_type, pageable, deprecated, permissions, perm_confidence,
|
|
66
|
+
privilege_level, summary, tags, params_json, search_text, body_ref,
|
|
67
|
+
body_schema, body_media_type, server_url)
|
|
68
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
|
|
69
|
+
for (const op of operations) {
|
|
70
|
+
insertOp.run(op.qualifiedId, op.api, op.operationId, op.method, op.path, op.safety, op.risk, op.operationType, op.pageable ? 1 : 0, op.deprecated ? 1 : 0, op.permissions ? JSON.stringify(op.permissions) : null, op.permConfidence, op.privilegeLevel, op.summary, op.tags, op.paramsJson, op.searchText, op.bodyRef, op.bodySchemaJson, op.bodyMediaType, op.serverUrl);
|
|
71
|
+
}
|
|
72
|
+
const insertSchema = db.prepare("INSERT INTO schemas (api, name, json) VALUES (?,?,?)");
|
|
73
|
+
for (const s of schemas)
|
|
74
|
+
insertSchema.run(s.api, s.name, s.json);
|
|
75
|
+
// External-content fts5: populate from the base table after loading it.
|
|
76
|
+
// Appending only indexes the newly mounted api's rows — the first
|
|
77
|
+
// mount's rows are already indexed and must not be touched again.
|
|
78
|
+
if (options.append) {
|
|
79
|
+
db.prepare(`INSERT INTO operations_fts (rowid, qualified_id, operation_id, summary, path, tags, api, search_text)
|
|
80
|
+
SELECT rowid, qualified_id, operation_id, summary, path, tags, api, search_text
|
|
81
|
+
FROM operations WHERE api = ?`).run(options.api);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
db.exec(`INSERT INTO operations_fts (rowid, qualified_id, operation_id, summary, path, tags, api, search_text)
|
|
85
|
+
SELECT rowid, qualified_id, operation_id, summary, path, tags, api, search_text FROM operations`);
|
|
86
|
+
}
|
|
87
|
+
// Global keys plus per-api namespaced provenance. `apis` is a JSON array so
|
|
88
|
+
// a second mount can append to it without rewriting anything.
|
|
89
|
+
const insertMeta = db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?,?)");
|
|
90
|
+
for (const [key, value] of Object.entries({
|
|
91
|
+
format_version: String(LEGACY_FORMAT_VERSION),
|
|
92
|
+
compiler_version: COMPILER_VERSION,
|
|
93
|
+
apis: JSON.stringify([...existingApis, options.api]),
|
|
94
|
+
[`${options.api}.source_path`]: options.specPath,
|
|
95
|
+
[`${options.api}.compiled_at`]: new Date().toISOString(),
|
|
96
|
+
})) {
|
|
97
|
+
insertMeta.run(key, value);
|
|
98
|
+
}
|
|
99
|
+
db.exec("COMMIT");
|
|
100
|
+
committed = true;
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
try {
|
|
104
|
+
db.exec("ROLLBACK");
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// No active transaction (e.g. BEGIN itself failed) — the original
|
|
108
|
+
// error is what matters; don't let a rollback failure mask it.
|
|
109
|
+
}
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
db.close();
|
|
114
|
+
// Cleared only after the handle is closed, so this is safe on platforms
|
|
115
|
+
// that refuse to unlink an open file.
|
|
116
|
+
if (!committed && buildPath !== options.outPath) {
|
|
117
|
+
try {
|
|
118
|
+
unlinkSync(buildPath);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Never created, or already gone.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (buildPath !== options.outPath)
|
|
126
|
+
renameSync(buildPath, options.outPath);
|
|
127
|
+
return {
|
|
128
|
+
operations: operations.length,
|
|
129
|
+
schemas: schemas.length,
|
|
130
|
+
mapped: operations.filter((o) => o.permissions !== null).length,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type { CompileOptions, CompileResult } from "./compile.ts";
|
|
2
|
+
export { compile } from "./compile.ts";
|
|
3
|
+
export { resolveLocalPointer } from "./operations.ts";
|
|
4
|
+
export type { CompileReleaseOptions } from "./release/compile-release.ts";
|
|
5
|
+
export { compileRelease } from "./release/compile-release.ts";
|
|
6
|
+
export type { CompilerLimits } from "./release/load-v4.ts";
|
|
7
|
+
export { DEFAULT_COMPILER_LIMITS, loadSpecV4, parseDocumentBytesV4, parseSpecBytesV4, redactSourcePath, } from "./release/load-v4.ts";
|
|
8
|
+
export type { CompiledRelease, CompiledReleasePaths, } from "./release/publish.ts";
|
|
9
|
+
export { discardCompiledRelease, publishRelease, } from "./release/publish.ts";
|
|
10
|
+
export type { ReferenceMapEntryV1, ReferenceMapV1, ReferenceResolver, ResolvedReferenceV1, } from "./release/reference-map.ts";
|
|
11
|
+
export { normalizeGraphUri, normalizeHttpsUri, referenceGraphDigest, sourceUriFromLabel, } from "./release/reference-map.ts";
|
|
12
|
+
export { deriveReleasePublicKeyV4, generateKeypair, signArtifact, signReleaseManifestV4, verifyArtifact, } from "./sign.ts";
|
|
13
|
+
export type { OperationRecord, PermConfidence, Risk, Safety, SchemaRecord, } from "./types.ts";
|
package/dist/compiler.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { compile } from "./compile.js";
|
|
2
|
+
export { resolveLocalPointer } from "./operations.js";
|
|
3
|
+
export { compileRelease } from "./release/compile-release.js";
|
|
4
|
+
export { DEFAULT_COMPILER_LIMITS, loadSpecV4, parseDocumentBytesV4, parseSpecBytesV4, redactSourcePath, } from "./release/load-v4.js";
|
|
5
|
+
export { discardCompiledRelease, publishRelease, } from "./release/publish.js";
|
|
6
|
+
export { normalizeGraphUri, normalizeHttpsUri, referenceGraphDigest, sourceUriFromLabel, } from "./release/reference-map.js";
|
|
7
|
+
export { deriveReleasePublicKeyV4, generateKeypair, signArtifact, signReleaseManifestV4, verifyArtifact, } from "./sign.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CandidateRef, CatalogId, ManifestEnvelope, ManifestTrust, OperationRecordV4, ReleaseId, SchemaRecordV4, StoredRecord, TypedOperationId, TypedSchemaId } from "../runtime/index.ts";
|
|
2
|
+
export interface RuntimeConformanceFixture {
|
|
3
|
+
readonly catalogId: CatalogId;
|
|
4
|
+
readonly releaseA: ReleaseId;
|
|
5
|
+
readonly releaseB: ReleaseId;
|
|
6
|
+
readonly operationId: TypedOperationId;
|
|
7
|
+
readonly missingOperationId: TypedOperationId;
|
|
8
|
+
readonly schemaIds: readonly TypedSchemaId[];
|
|
9
|
+
readonly searchQuery: string;
|
|
10
|
+
readonly api: string;
|
|
11
|
+
readonly expectedCandidates: readonly CandidateRef[];
|
|
12
|
+
readonly envelopeA: ManifestEnvelope;
|
|
13
|
+
readonly envelopeB: ManifestEnvelope;
|
|
14
|
+
readonly trust: ManifestTrust;
|
|
15
|
+
readonly operationA: StoredRecord<OperationRecordV4>;
|
|
16
|
+
readonly operationB: StoredRecord<OperationRecordV4>;
|
|
17
|
+
readonly schemasA: readonly StoredRecord<SchemaRecordV4>[];
|
|
18
|
+
readonly schemasB: readonly StoredRecord<SchemaRecordV4>[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Stable, cryptographically valid public fixture for every store adapter.
|
|
22
|
+
* The matching private key is deliberately not included.
|
|
23
|
+
*/
|
|
24
|
+
export declare const RUNTIME_CONFORMANCE_FIXTURE: RuntimeConformanceFixture;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const catalogId = "conformance";
|
|
2
|
+
const releaseA = "release-a";
|
|
3
|
+
const releaseB = "release-b";
|
|
4
|
+
const operationId = "operation:conformance:get-item";
|
|
5
|
+
const itemSchemaId = "schema:conformance:#/components/schemas/Item";
|
|
6
|
+
const tagSchemaId = "schema:conformance:#/components/schemas/Tag";
|
|
7
|
+
const envelopeA = {
|
|
8
|
+
manifestJson: '{"allowedOrigins":["https://api.example.invalid"],"catalogId":"conformance","compiledAt":"2026-09-04T00:00:00.000Z","compilerVersion":"fixture-v1","contract":1,"format":4,"generation":1,"issuer":"conformance.example","keyId":"fixture-key","policyId":"fixture-policy","records":{"operation:conformance:get-item":"8eeb54ce7802f5e33d8cdfd0c2c59965f9867a18b3c5dbc81082fd3b8b9d5753","schema:conformance:#/components/schemas/Item":"e10267890d908b2db1fac5cb17b139c08f9cd8bf887600d8aec9e80156f42d66","schema:conformance:#/components/schemas/Tag":"5c28908d43c68938ba560687c098e8339f93564471043f7aefe4bdc2fb42a1dd"},"releaseId":"release-a","source":{"contentSha256":"0000000000000000000000000000000000000000000000000000000000000000","referenceGraphDigest":"1111111111111111111111111111111111111111111111111111111111111111","revision":"release-a","uri":"https://specs.example.invalid/conformance.json"}}',
|
|
9
|
+
signature: {
|
|
10
|
+
algorithm: "Ed25519",
|
|
11
|
+
keyId: "fixture-key",
|
|
12
|
+
signature: "ol9usPjzs-FujMZnOP8uSwCrSxnQc1LLuaMZp2oJXMJU0vFLX3tlLEfFNr6mzaF83ZU2xUlyUpoAzMQPlo5YBw",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
const envelopeB = {
|
|
16
|
+
manifestJson: '{"allowedOrigins":["https://api.example.invalid"],"catalogId":"conformance","compiledAt":"2026-09-04T00:00:00.000Z","compilerVersion":"fixture-v1","contract":1,"format":4,"generation":2,"issuer":"conformance.example","keyId":"fixture-key","policyId":"fixture-policy","records":{"operation:conformance:get-item":"ab223e9204df8dbb2dd94982f10080db4517efc7b181fa02b3999b6657b893cd","schema:conformance:#/components/schemas/Item":"8416a81dbe1c8fe3ef459701d787384cc63f55953103a28bf1b384760cf59273","schema:conformance:#/components/schemas/Tag":"5c28908d43c68938ba560687c098e8339f93564471043f7aefe4bdc2fb42a1dd"},"releaseId":"release-b","source":{"contentSha256":"0000000000000000000000000000000000000000000000000000000000000000","referenceGraphDigest":"1111111111111111111111111111111111111111111111111111111111111111","revision":"release-b","uri":"https://specs.example.invalid/conformance.json"}}',
|
|
17
|
+
signature: {
|
|
18
|
+
algorithm: "Ed25519",
|
|
19
|
+
keyId: "fixture-key",
|
|
20
|
+
signature: "t2vi58Ars0_amzBA2NfBlb7NzEM6Bl7v5n5tFu437b1hxDSx6YxnuV7TFEDcvXJY06Fj_GEWE0cjExU6fJE_Cg",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
function operation(summary, logicalDigest) {
|
|
24
|
+
return {
|
|
25
|
+
id: operationId,
|
|
26
|
+
logicalDigest: logicalDigest,
|
|
27
|
+
record: {
|
|
28
|
+
id: operationId,
|
|
29
|
+
api: "conformance",
|
|
30
|
+
operationId: "get-item",
|
|
31
|
+
method: "POST",
|
|
32
|
+
path: "/items",
|
|
33
|
+
origin: "https://api.example.invalid",
|
|
34
|
+
summary,
|
|
35
|
+
deprecated: false,
|
|
36
|
+
parameters: [],
|
|
37
|
+
requestBody: {
|
|
38
|
+
required: true,
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
mediaType: "application/json",
|
|
42
|
+
schemaId: itemSchemaId,
|
|
43
|
+
encoding: [],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
schemaIds: [itemSchemaId],
|
|
48
|
+
advisory: {},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function schemas(title, itemDigest) {
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
id: itemSchemaId,
|
|
56
|
+
logicalDigest: itemDigest,
|
|
57
|
+
record: {
|
|
58
|
+
id: itemSchemaId,
|
|
59
|
+
schema: {
|
|
60
|
+
properties: { tag: { $ref: tagSchemaId } },
|
|
61
|
+
title,
|
|
62
|
+
type: "object",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: tagSchemaId,
|
|
68
|
+
logicalDigest: "5c28908d43c68938ba560687c098e8339f93564471043f7aefe4bdc2fb42a1dd",
|
|
69
|
+
record: {
|
|
70
|
+
id: tagSchemaId,
|
|
71
|
+
schema: { type: "string" },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
function deepFreeze(value) {
|
|
77
|
+
if (typeof value !== "object" || value === null)
|
|
78
|
+
return value;
|
|
79
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
80
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
81
|
+
if (descriptor && "value" in descriptor)
|
|
82
|
+
deepFreeze(descriptor.value);
|
|
83
|
+
}
|
|
84
|
+
return Object.freeze(value);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Stable, cryptographically valid public fixture for every store adapter.
|
|
88
|
+
* The matching private key is deliberately not included.
|
|
89
|
+
*/
|
|
90
|
+
export const RUNTIME_CONFORMANCE_FIXTURE = deepFreeze({
|
|
91
|
+
catalogId,
|
|
92
|
+
releaseA,
|
|
93
|
+
releaseB,
|
|
94
|
+
operationId,
|
|
95
|
+
missingOperationId: "operation:conformance:missing",
|
|
96
|
+
schemaIds: Object.freeze([itemSchemaId, tagSchemaId]),
|
|
97
|
+
searchQuery: "item",
|
|
98
|
+
api: "conformance",
|
|
99
|
+
expectedCandidates: Object.freeze([
|
|
100
|
+
{ catalogId, releaseId: releaseA, operationId },
|
|
101
|
+
{ catalogId, releaseId: releaseB, operationId },
|
|
102
|
+
]),
|
|
103
|
+
envelopeA: Object.freeze(envelopeA),
|
|
104
|
+
envelopeB: Object.freeze(envelopeB),
|
|
105
|
+
trust: Object.freeze({
|
|
106
|
+
releaseKeys: Object.freeze([
|
|
107
|
+
Object.freeze({
|
|
108
|
+
issuer: "conformance.example",
|
|
109
|
+
keyId: "fixture-key",
|
|
110
|
+
publicKey: "MCowBQYDK2VwAyEAQujfB1nZVWKU0z212f9rLQ8SanSmrjKOv7Ah40_oMQU",
|
|
111
|
+
}),
|
|
112
|
+
]),
|
|
113
|
+
rollbackKeys: Object.freeze([]),
|
|
114
|
+
}),
|
|
115
|
+
operationA: Object.freeze(operation("release a", "8eeb54ce7802f5e33d8cdfd0c2c59965f9867a18b3c5dbc81082fd3b8b9d5753")),
|
|
116
|
+
operationB: Object.freeze(operation("release b", "ab223e9204df8dbb2dd94982f10080db4517efc7b181fa02b3999b6657b893cd")),
|
|
117
|
+
schemasA: Object.freeze(schemas("release a", "e10267890d908b2db1fac5cb17b139c08f9cd8bf887600d8aec9e80156f42d66")),
|
|
118
|
+
schemasB: Object.freeze(schemas("release b", "8416a81dbe1c8fe3ef459701d787384cc63f55953103a28bf1b384760cf59273")),
|
|
119
|
+
});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { RUNTIME_CONFORMANCE_FIXTURE, type RuntimeConformanceFixture, } from "./fixtures.ts";
|
|
2
|
+
export { type CatalogStoreFactory, type CatalogStoreFactoryResult, type ConformanceTestAdapter, type RuntimeConformanceCandidateFault, type RuntimeConformanceDriverFault, type RuntimeConformanceDuplicateFault, type RuntimeConformanceOptions, type RuntimeConformanceScenario, runRuntimeConformanceSuite, } from "./runtime-suite.ts";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type CatalogStore } from "../runtime/index.ts";
|
|
2
|
+
import type { RuntimeConformanceFixture } from "./fixtures.ts";
|
|
3
|
+
export interface CatalogStoreFactoryResult {
|
|
4
|
+
readonly store: CatalogStore;
|
|
5
|
+
readonly fixture: RuntimeConformanceFixture;
|
|
6
|
+
readonly dispose?: () => void | Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export type RuntimeConformanceDuplicateFault = "duplicate-candidates" | "duplicate-manifest" | "duplicate-operation" | "duplicate-schemas";
|
|
9
|
+
export type RuntimeConformanceCandidateFault = "candidate-malformed-operation-id" | "candidate-non-operation-id" | "candidate-cross-api";
|
|
10
|
+
export type RuntimeConformanceDriverFault = "driver-candidates" | "driver-manifest" | "driver-operation" | "driver-schemas";
|
|
11
|
+
export type RuntimeConformanceScenario = {
|
|
12
|
+
readonly fault: RuntimeConformanceDuplicateFault;
|
|
13
|
+
} | {
|
|
14
|
+
readonly fault: RuntimeConformanceCandidateFault;
|
|
15
|
+
} | {
|
|
16
|
+
readonly fault: RuntimeConformanceDriverFault;
|
|
17
|
+
readonly injectedDriverError: string;
|
|
18
|
+
};
|
|
19
|
+
export type CatalogStoreFactory = (scenario?: RuntimeConformanceScenario) => CatalogStoreFactoryResult | Promise<CatalogStoreFactoryResult>;
|
|
20
|
+
export interface ConformanceTestAdapter {
|
|
21
|
+
test(name: string, run: () => void | Promise<void>): void;
|
|
22
|
+
equal(actual: unknown, expected: unknown, message?: string): void;
|
|
23
|
+
deepEqual(actual: unknown, expected: unknown, message?: string): void;
|
|
24
|
+
ok(value: unknown, message?: string): void;
|
|
25
|
+
rejects(run: () => unknown | Promise<unknown>, code: string, message?: string): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export interface RuntimeConformanceOptions {
|
|
28
|
+
readonly testAdapter: ConformanceTestAdapter;
|
|
29
|
+
}
|
|
30
|
+
/** Register the transport-independent CatalogStore contract against a factory. */
|
|
31
|
+
export declare function runRuntimeConformanceSuite(factory: CatalogStoreFactory, options: RuntimeConformanceOptions): void;
|