@fungi.computer/forage 0.0.0-stage → 0.0.2
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 +24 -0
- package/README.md +223 -2
- package/THIRD_PARTY_NOTICES +13 -0
- package/dist/app-api.d.ts +62 -0
- package/dist/app-api.js +121 -0
- package/dist/catalog.d.ts +16 -0
- package/dist/catalog.js +108 -0
- package/dist/definitions.d.ts +16 -0
- package/dist/definitions.js +25 -0
- package/dist/describe.d.ts +4 -0
- package/dist/describe.js +66 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +12 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +16 -0
- package/dist/json-schema-boundary.d.ts +10 -0
- package/dist/json-schema-boundary.js +56 -0
- package/dist/json-schema-subset.d.ts +6 -0
- package/dist/json-schema-subset.js +77 -0
- package/dist/list.d.ts +8 -0
- package/dist/list.js +53 -0
- package/dist/normalization.d.ts +22 -0
- package/dist/normalization.js +109 -0
- package/dist/openapi.d.ts +106 -0
- package/dist/openapi.js +519 -0
- package/dist/rendering.d.ts +26 -0
- package/dist/rendering.js +341 -0
- package/dist/schemas.d.ts +231 -0
- package/dist/schemas.js +156 -0
- package/dist/search.d.ts +17 -0
- package/dist/search.js +258 -0
- package/docs/api/README.md +15 -0
- package/docs/api/index.md +859 -0
- package/docs/api/json-schema-subset.md +42 -0
- package/docs/api/openapi.md +308 -0
- package/package.json +71 -3
package/dist/describe.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describeOutputSchema, maximumDescriptionTypes, } from "./schemas.js";
|
|
2
|
+
import { compareCodeUnits } from "./normalization.js";
|
|
3
|
+
import { renderNamespaceIndex, renderOperationTypes } from "./rendering.js";
|
|
4
|
+
import { ForageError } from "./errors.js";
|
|
5
|
+
function boundedDescription(value) {
|
|
6
|
+
return value && value.length > 0 ? value : undefined;
|
|
7
|
+
}
|
|
8
|
+
function checkedDescription(output) {
|
|
9
|
+
const parsed = describeOutputSchema.safeParse(output);
|
|
10
|
+
if (!parsed.success) {
|
|
11
|
+
throw new ForageError("invalid_input", "Description output exceeded its bounds");
|
|
12
|
+
}
|
|
13
|
+
return parsed.data;
|
|
14
|
+
}
|
|
15
|
+
/** Pure deterministic description of an operation or its namespace. */
|
|
16
|
+
export function describeCatalog(operations, rawInput) {
|
|
17
|
+
const operation = operations.find((candidate) => candidate.path === rawInput.path);
|
|
18
|
+
if (operation) {
|
|
19
|
+
const callPath = `env.${operation.path}`;
|
|
20
|
+
const output = {
|
|
21
|
+
path: operation.path,
|
|
22
|
+
description: boundedDescription(operation.description),
|
|
23
|
+
types: renderOperationTypes([operation.rendered]),
|
|
24
|
+
kind: "operation",
|
|
25
|
+
inputSchema: operation.inputSchema,
|
|
26
|
+
usage: `await ${callPath}({ ... })`,
|
|
27
|
+
};
|
|
28
|
+
if (output.types.length > maximumDescriptionTypes) {
|
|
29
|
+
throw new ForageError("invalid_input", "Description types exceed the bounded output");
|
|
30
|
+
}
|
|
31
|
+
return checkedDescription(output);
|
|
32
|
+
}
|
|
33
|
+
const prefix = `${rawInput.path}.`;
|
|
34
|
+
const namespaceOperations = operations
|
|
35
|
+
.filter((candidate) => candidate.path.startsWith(prefix))
|
|
36
|
+
.sort((left, right) => compareCodeUnits(left.path, right.path));
|
|
37
|
+
if (namespaceOperations.length === 0) {
|
|
38
|
+
throw new ForageError("unknown_target", `Unknown target: ${rawInput.path}`);
|
|
39
|
+
}
|
|
40
|
+
const first = namespaceOperations[0];
|
|
41
|
+
if (!first) {
|
|
42
|
+
throw new ForageError("unknown_target", `Unknown target: ${rawInput.path}`);
|
|
43
|
+
}
|
|
44
|
+
const selected = rawInput.methods?.length
|
|
45
|
+
? namespaceOperations.filter((candidate) => rawInput.methods?.includes(candidate.path.slice(prefix.length)))
|
|
46
|
+
: namespaceOperations;
|
|
47
|
+
let types;
|
|
48
|
+
if (rawInput.detail === "index") {
|
|
49
|
+
types = renderNamespaceIndex(namespaceOperations.map((operation) => operation.rendered));
|
|
50
|
+
}
|
|
51
|
+
else if (rawInput.methods?.length && selected.length === 0) {
|
|
52
|
+
types = renderNamespaceIndex(namespaceOperations.map((operation) => operation.rendered));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
types = renderOperationTypes((selected.length > 0 ? selected : namespaceOperations).map((operation) => operation.rendered));
|
|
56
|
+
}
|
|
57
|
+
if (types.length > maximumDescriptionTypes) {
|
|
58
|
+
throw new ForageError("invalid_input", "Description types exceed the bounded output");
|
|
59
|
+
}
|
|
60
|
+
return checkedDescription({
|
|
61
|
+
path: rawInput.path,
|
|
62
|
+
description: boundedDescription(first.instructions),
|
|
63
|
+
types,
|
|
64
|
+
kind: "module",
|
|
65
|
+
});
|
|
66
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ForageFailure } from "./schemas.js";
|
|
2
|
+
/** A bounded, machine-readable failure at a Forage public seam. */
|
|
3
|
+
export declare class ForageError extends Error {
|
|
4
|
+
readonly code: ForageFailure["code"];
|
|
5
|
+
readonly failure: ForageFailure;
|
|
6
|
+
constructor(code: ForageFailure["code"], message: string, options?: ErrorOptions);
|
|
7
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** A bounded, machine-readable failure at a Forage public seam. */
|
|
2
|
+
export class ForageError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
failure;
|
|
5
|
+
constructor(code, message, options) {
|
|
6
|
+
const boundedMessage = message.slice(0, 1000);
|
|
7
|
+
super(boundedMessage, options);
|
|
8
|
+
this.name = "ForageError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.failure = { code, message: boundedMessage };
|
|
11
|
+
}
|
|
12
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { catalogInputSchema, describeOutputSchema, forageDescribeInput, forageFailureSchema, forageSearchInput, operationSchema, searchOutputSchema, searchResultSchema, forageListInput, listOutputSchema, } from "./schemas.js";
|
|
2
|
+
export type { CatalogInput, DescribeOutput, ForageDescribeInput, ForageFailure, ForageSearchInput, JsonSchemaSource, JsonSchemaTarget, Operation, SearchOutput, SearchResult, ForageListInput, ListOutput, } from "./schemas.js";
|
|
3
|
+
export type { ForageDefinition } from "./definitions.js";
|
|
4
|
+
export { ForageError } from "./errors.js";
|
|
5
|
+
export { createCatalog } from "./catalog.js";
|
|
6
|
+
export { appApiOperations, appMethodSchemas, appToolSchemas, appToolFamilySchemas, jsonSchemaToZod, } from "./app-api.js";
|
|
7
|
+
export { listOperations } from "./list.js";
|
|
8
|
+
export type { ForageCatalog } from "./catalog.js";
|
|
9
|
+
export { forageDescribeDefinition, forageSearchDefinition, forageListDefinition, } from "./definitions.js";
|
|
10
|
+
import { createCatalog } from "./catalog.js";
|
|
11
|
+
import { forageDescribeDefinition, forageSearchDefinition, forageListDefinition } from "./definitions.js";
|
|
12
|
+
/** Public convenience namespace contract. */
|
|
13
|
+
export type ForageApi = {
|
|
14
|
+
/** Construct one immutable catalog revision. */
|
|
15
|
+
createCatalog: typeof createCatalog;
|
|
16
|
+
/** Structural search operation definition. */
|
|
17
|
+
forageSearchDefinition: typeof forageSearchDefinition;
|
|
18
|
+
/** Structural description operation definition. */
|
|
19
|
+
forageDescribeDefinition: typeof forageDescribeDefinition;
|
|
20
|
+
/** Structural exhaustive listing definition. */
|
|
21
|
+
forageListDefinition: typeof forageListDefinition;
|
|
22
|
+
};
|
|
23
|
+
/** Narrow convenience namespace for the package's public seams. */
|
|
24
|
+
export declare const Forage: ForageApi;
|
|
25
|
+
export default Forage;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { catalogInputSchema, describeOutputSchema, forageDescribeInput, forageFailureSchema, forageSearchInput, operationSchema, searchOutputSchema, searchResultSchema, forageListInput, listOutputSchema, } from "./schemas.js";
|
|
2
|
+
export { ForageError } from "./errors.js";
|
|
3
|
+
export { createCatalog } from "./catalog.js";
|
|
4
|
+
export { appApiOperations, appMethodSchemas, appToolSchemas, appToolFamilySchemas, jsonSchemaToZod, } from "./app-api.js";
|
|
5
|
+
export { listOperations } from "./list.js";
|
|
6
|
+
export { forageDescribeDefinition, forageSearchDefinition, forageListDefinition, } from "./definitions.js";
|
|
7
|
+
import { createCatalog } from "./catalog.js";
|
|
8
|
+
import { forageDescribeDefinition, forageSearchDefinition, forageListDefinition, } from "./definitions.js";
|
|
9
|
+
/** Narrow convenience namespace for the package's public seams. */
|
|
10
|
+
export const Forage = Object.freeze({
|
|
11
|
+
createCatalog,
|
|
12
|
+
forageSearchDefinition,
|
|
13
|
+
forageDescribeDefinition,
|
|
14
|
+
forageListDefinition,
|
|
15
|
+
});
|
|
16
|
+
export default Forage;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const schemaDepthLimit = 32;
|
|
3
|
+
export declare const schemaNodeLimit = 50000;
|
|
4
|
+
export declare const isUnsafeSchemaPropertyName: (name: string) => boolean;
|
|
5
|
+
export declare const parameterName: z.ZodString;
|
|
6
|
+
export declare const thirdPartyText: (limit: number) => z.core.$ZodBranded<z.ZodString, "ThirdPartyText", "out">;
|
|
7
|
+
export declare const jsonSchemaContainer: z.ZodUnion<readonly [z.ZodArray<z.ZodUnknown>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
8
|
+
export declare function jsonSchemaValueInspection(budget?: {
|
|
9
|
+
nodes: number;
|
|
10
|
+
}): z.ZodPipe<z.ZodUnknown, z.ZodTransform<"nodes_limit" | "depth_limit" | "unsafe_name" | null, unknown>>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export const schemaDepthLimit = 32;
|
|
3
|
+
export const schemaNodeLimit = 50_000;
|
|
4
|
+
const unsafePropertyNames = new Set(["__proto__", "constructor", "prototype"]);
|
|
5
|
+
export const isUnsafeSchemaPropertyName = (name) => unsafePropertyNames.has(name);
|
|
6
|
+
export const parameterName = z
|
|
7
|
+
.string()
|
|
8
|
+
.min(1)
|
|
9
|
+
.max(128)
|
|
10
|
+
.refine((name) => !unsafePropertyNames.has(name));
|
|
11
|
+
export const thirdPartyText = (limit) => z.string().max(limit).brand();
|
|
12
|
+
// Maintained decoding of one container level avoids expanding ref-parser's DAG
|
|
13
|
+
// or recursively decoding remote JSON before the domain budget is checked.
|
|
14
|
+
export const jsonSchemaContainer = z.union([
|
|
15
|
+
z.array(z.unknown()),
|
|
16
|
+
z.record(z.string(), z.unknown()),
|
|
17
|
+
]);
|
|
18
|
+
export function jsonSchemaValueInspection(budget = { nodes: 0 }) {
|
|
19
|
+
return z
|
|
20
|
+
.unknown()
|
|
21
|
+
.transform((source) => {
|
|
22
|
+
const pending = [
|
|
23
|
+
{ value: source, depth: 0, field: "" },
|
|
24
|
+
];
|
|
25
|
+
while (pending.length) {
|
|
26
|
+
const next = pending.pop();
|
|
27
|
+
if (!next)
|
|
28
|
+
break;
|
|
29
|
+
if (++budget.nodes > schemaNodeLimit)
|
|
30
|
+
return "nodes_limit";
|
|
31
|
+
if (next.depth > schemaDepthLimit)
|
|
32
|
+
return "depth_limit";
|
|
33
|
+
const decoded = jsonSchemaContainer.safeParse(next.value);
|
|
34
|
+
if (!decoded.success) {
|
|
35
|
+
if (["name", "required"].includes(next.field) &&
|
|
36
|
+
isUnsafeSchemaPropertyName(z.string().safeParse(next.value).data ?? ""))
|
|
37
|
+
return "unsafe_name";
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// Zod records discard __proto__: inspect the original keys before consuming
|
|
41
|
+
// their decoded children, including keys in const/enum/default/example data.
|
|
42
|
+
if (Object.getOwnPropertyNames(next.value).some(isUnsafeSchemaPropertyName))
|
|
43
|
+
return "unsafe_name";
|
|
44
|
+
for (const [field, child] of Object.entries(decoded.data)) {
|
|
45
|
+
if (pending.length + budget.nodes >= schemaNodeLimit)
|
|
46
|
+
return "nodes_limit";
|
|
47
|
+
pending.push({
|
|
48
|
+
value: child,
|
|
49
|
+
depth: next.depth + 1,
|
|
50
|
+
field: Array.isArray(decoded.data) ? next.field : field,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
/** Decode the supported schema subset before using the shared Zod compiler.
|
|
3
|
+
* Refuse depth over 32, more than 50,000 expanded nodes and reserved keys
|
|
4
|
+
* before recursive decoding. Callers bound transport bytes separately.
|
|
5
|
+
*/
|
|
6
|
+
export declare function jsonSchemaSubset(allowNullable?: boolean): z.ZodPipe<z.ZodUnknown, z.ZodType<boolean | Record<string, z.core.util.JSONType>, unknown, z.core.$ZodTypeInternals<boolean | Record<string, z.core.util.JSONType>, unknown>>>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { jsonSchemaValueInspection, parameterName, thirdPartyText, } from "./json-schema-boundary.js";
|
|
3
|
+
const jsonSchema = z.union([z.record(z.string(), z.json()), z.boolean()]);
|
|
4
|
+
/** Decode the supported schema subset before using the shared Zod compiler.
|
|
5
|
+
* Refuse depth over 32, more than 50,000 expanded nodes and reserved keys
|
|
6
|
+
* before recursive decoding. Callers bound transport bytes separately.
|
|
7
|
+
*/
|
|
8
|
+
export function jsonSchemaSubset(allowNullable = false) {
|
|
9
|
+
const schema = z.lazy(() => {
|
|
10
|
+
const objectSchema = z.strictObject({
|
|
11
|
+
$schema: z.string().optional(),
|
|
12
|
+
title: thirdPartyText(200).optional(),
|
|
13
|
+
description: thirdPartyText(2048).optional(),
|
|
14
|
+
type: z
|
|
15
|
+
.union([
|
|
16
|
+
z.enum([
|
|
17
|
+
"null",
|
|
18
|
+
"boolean",
|
|
19
|
+
"object",
|
|
20
|
+
"array",
|
|
21
|
+
"number",
|
|
22
|
+
"integer",
|
|
23
|
+
"string",
|
|
24
|
+
]),
|
|
25
|
+
z.array(z.enum([
|
|
26
|
+
"null",
|
|
27
|
+
"boolean",
|
|
28
|
+
"object",
|
|
29
|
+
"array",
|
|
30
|
+
"number",
|
|
31
|
+
"integer",
|
|
32
|
+
"string",
|
|
33
|
+
])),
|
|
34
|
+
])
|
|
35
|
+
.optional(),
|
|
36
|
+
enum: z.array(z.json()).min(1).optional(),
|
|
37
|
+
const: z.json().optional(),
|
|
38
|
+
properties: z.record(parameterName, schema).optional(),
|
|
39
|
+
required: z.array(parameterName).optional(),
|
|
40
|
+
additionalProperties: schema.optional(),
|
|
41
|
+
propertyNames: schema.optional(),
|
|
42
|
+
items: schema.optional(),
|
|
43
|
+
prefixItems: z.array(schema).optional(),
|
|
44
|
+
minItems: z.number().int().nonnegative().optional(),
|
|
45
|
+
maxItems: z.number().int().nonnegative().optional(),
|
|
46
|
+
uniqueItems: z.literal(false).optional(),
|
|
47
|
+
minLength: z.number().int().nonnegative().optional(),
|
|
48
|
+
maxLength: z.number().int().nonnegative().optional(),
|
|
49
|
+
minimum: z.number().optional(),
|
|
50
|
+
maximum: z.number().optional(),
|
|
51
|
+
exclusiveMinimum: z.union([z.number(), z.boolean()]).optional(),
|
|
52
|
+
exclusiveMaximum: z.union([z.number(), z.boolean()]).optional(),
|
|
53
|
+
multipleOf: z.number().positive().optional(),
|
|
54
|
+
allOf: z.array(schema).optional(),
|
|
55
|
+
anyOf: z.array(schema).optional(),
|
|
56
|
+
oneOf: z.array(schema).optional(),
|
|
57
|
+
readOnly: z.boolean().optional(),
|
|
58
|
+
writeOnly: z.boolean().optional(),
|
|
59
|
+
default: z.json().optional(),
|
|
60
|
+
example: z.json().optional(),
|
|
61
|
+
examples: z.array(z.json()).optional(),
|
|
62
|
+
deprecated: z.boolean().optional(),
|
|
63
|
+
});
|
|
64
|
+
const versioned = allowNullable
|
|
65
|
+
? objectSchema.extend({ nullable: z.boolean().optional() })
|
|
66
|
+
: objectSchema;
|
|
67
|
+
return z.union([z.boolean(), versioned]).pipe(jsonSchema);
|
|
68
|
+
});
|
|
69
|
+
return z
|
|
70
|
+
.unknown()
|
|
71
|
+
.superRefine((value, context) => {
|
|
72
|
+
const refusal = jsonSchemaValueInspection().parse(value);
|
|
73
|
+
if (refusal)
|
|
74
|
+
context.addIssue({ code: "custom", message: refusal });
|
|
75
|
+
})
|
|
76
|
+
.pipe(schema);
|
|
77
|
+
}
|
package/dist/list.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ForageListInput, type ListOutput, type SearchResult } from "./schemas.js";
|
|
2
|
+
/**
|
|
3
|
+
* Page a trusted immutable operation-summary view. Hosts may filter the existing
|
|
4
|
+
* catalog to their bound operations before paging; this is the one cursor owner.
|
|
5
|
+
* Order is namespace then full path, compared by UTF-16 code units, never locale.
|
|
6
|
+
* Unlike search, no equivalent operations are merged into peers.
|
|
7
|
+
*/
|
|
8
|
+
export declare function listOperations(operations: readonly SearchResult[], catalogStamp: string, rawInput: ForageListInput): ListOutput;
|
package/dist/list.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ForageError } from "./errors.js";
|
|
3
|
+
import { compareCodeUnits, operationNamespace } from "./normalization.js";
|
|
4
|
+
import { forageListInput, listOutputSchema, searchResultSchema, } from "./schemas.js";
|
|
5
|
+
const cursorSchema = z.strictObject({
|
|
6
|
+
catalogStamp: listOutputSchema.shape.catalogStamp,
|
|
7
|
+
namespace: forageListInput.shape.namespace.unwrap().nullable(),
|
|
8
|
+
after: searchResultSchema.shape.path,
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Page a trusted immutable operation-summary view. Hosts may filter the existing
|
|
12
|
+
* catalog to their bound operations before paging; this is the one cursor owner.
|
|
13
|
+
* Order is namespace then full path, compared by UTF-16 code units, never locale.
|
|
14
|
+
* Unlike search, no equivalent operations are merged into peers.
|
|
15
|
+
*/
|
|
16
|
+
export function listOperations(operations, catalogStamp, rawInput) {
|
|
17
|
+
const parsed = forageListInput.safeParse(rawInput);
|
|
18
|
+
if (!parsed.success)
|
|
19
|
+
throw new ForageError("invalid_input", parsed.error.message);
|
|
20
|
+
const input = parsed.data;
|
|
21
|
+
const namespace = input.namespace ?? null;
|
|
22
|
+
const sorted = operations
|
|
23
|
+
.filter((operation) => namespace === null || operation.path.startsWith(`${namespace}.`))
|
|
24
|
+
.sort((a, b) => compareCodeUnits(operationNamespace(a.path), operationNamespace(b.path)) || compareCodeUnits(a.path, b.path));
|
|
25
|
+
let start = 0;
|
|
26
|
+
if (input.cursor !== undefined) {
|
|
27
|
+
let cursor;
|
|
28
|
+
try {
|
|
29
|
+
cursor = cursorSchema.parse(JSON.parse(input.cursor));
|
|
30
|
+
}
|
|
31
|
+
catch (cause) {
|
|
32
|
+
throw new ForageError("invalid_input", "Invalid listing cursor", {
|
|
33
|
+
cause,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (cursor.catalogStamp !== catalogStamp || cursor.namespace !== namespace)
|
|
37
|
+
throw new ForageError("invalid_input", "Listing cursor belongs to a different catalog or namespace");
|
|
38
|
+
const index = sorted.findIndex((operation) => operation.path === cursor.after);
|
|
39
|
+
if (index < 0)
|
|
40
|
+
throw new ForageError("invalid_input", "Listing cursor operation is absent from this view");
|
|
41
|
+
start = index + 1;
|
|
42
|
+
}
|
|
43
|
+
const results = sorted.slice(start, start + input.limit);
|
|
44
|
+
const last = results.at(-1);
|
|
45
|
+
return listOutputSchema.parse({
|
|
46
|
+
results,
|
|
47
|
+
total: sorted.length,
|
|
48
|
+
catalogStamp,
|
|
49
|
+
nextCursor: start + results.length < sorted.length && last !== undefined
|
|
50
|
+
? JSON.stringify({ catalogStamp, namespace, after: last.path })
|
|
51
|
+
: null,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Operation } from "./schemas.js";
|
|
3
|
+
export declare const jsonValueSchema: z.ZodJSONSchema;
|
|
4
|
+
export type JsonValue = z.output<typeof jsonValueSchema>;
|
|
5
|
+
/** A JSON object level within an already-parsed {@link JsonValue}. */
|
|
6
|
+
export type JsonObject = Readonly<Record<string, JsonValue>>;
|
|
7
|
+
/**
|
|
8
|
+
* Narrow an already-parsed JSON value to its object case. The value was
|
|
9
|
+
* validated once at its boundary, so each nested level is told apart by its
|
|
10
|
+
* shape here instead of re-validating the whole subtree at every level, which
|
|
11
|
+
* made walking a large schema quadratic.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isJsonObject(value: JsonValue): value is JsonObject;
|
|
14
|
+
export declare function compareCodeUnits(left: string, right: string): number;
|
|
15
|
+
export declare function normalizeQuery(query: string): string;
|
|
16
|
+
export declare function tokenize(text: string): string[];
|
|
17
|
+
export declare function isStopword(token: string): boolean;
|
|
18
|
+
export declare function stemVariants(token: string): string[];
|
|
19
|
+
export declare function canonicalStringify(value: JsonValue): string;
|
|
20
|
+
export declare function hashString64(value: string): string;
|
|
21
|
+
export declare function operationMethod(path: Operation["path"]): string;
|
|
22
|
+
export declare function operationNamespace(path: Operation["path"]): string;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const jsonValueSchema = z.json();
|
|
3
|
+
const jsonPrimitiveSchema = z.union([z.string(), z.number(), z.boolean()]);
|
|
4
|
+
/**
|
|
5
|
+
* Narrow an already-parsed JSON value to its object case. The value was
|
|
6
|
+
* validated once at its boundary, so each nested level is told apart by its
|
|
7
|
+
* shape here instead of re-validating the whole subtree at every level, which
|
|
8
|
+
* made walking a large schema quadratic.
|
|
9
|
+
*/
|
|
10
|
+
export function isJsonObject(value) {
|
|
11
|
+
return (value !== null &&
|
|
12
|
+
!Array.isArray(value) &&
|
|
13
|
+
!jsonPrimitiveSchema.safeParse(value).success);
|
|
14
|
+
}
|
|
15
|
+
export function compareCodeUnits(left, right) {
|
|
16
|
+
if (left === right)
|
|
17
|
+
return 0;
|
|
18
|
+
const length = Math.min(left.length, right.length);
|
|
19
|
+
for (let index = 0; index < length; index += 1) {
|
|
20
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
21
|
+
if (difference !== 0)
|
|
22
|
+
return difference;
|
|
23
|
+
}
|
|
24
|
+
return left.length - right.length;
|
|
25
|
+
}
|
|
26
|
+
export function normalizeQuery(query) {
|
|
27
|
+
return query
|
|
28
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
29
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
30
|
+
.replace(/[_./:-]/g, " ")
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.trim();
|
|
33
|
+
}
|
|
34
|
+
export function tokenize(text) {
|
|
35
|
+
return text.split(/[^a-zA-Z0-9]+/).filter((token) => token.length > 0);
|
|
36
|
+
}
|
|
37
|
+
const stopwords = new Set([
|
|
38
|
+
"the",
|
|
39
|
+
"a",
|
|
40
|
+
"an",
|
|
41
|
+
"for",
|
|
42
|
+
"to",
|
|
43
|
+
"of",
|
|
44
|
+
"that",
|
|
45
|
+
"this",
|
|
46
|
+
"with",
|
|
47
|
+
"in",
|
|
48
|
+
"on",
|
|
49
|
+
]);
|
|
50
|
+
export function isStopword(token) {
|
|
51
|
+
return stopwords.has(token);
|
|
52
|
+
}
|
|
53
|
+
export function stemVariants(token) {
|
|
54
|
+
const variants = [];
|
|
55
|
+
const add = (variant) => {
|
|
56
|
+
if (variant !== token &&
|
|
57
|
+
variant.length >= 3 &&
|
|
58
|
+
!variants.includes(variant)) {
|
|
59
|
+
variants.push(variant);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
if (token.endsWith("ing") && token.length > 4)
|
|
63
|
+
add(token.slice(0, -3));
|
|
64
|
+
if (token.endsWith("ed") && token.length > 3)
|
|
65
|
+
add(token.slice(0, -2));
|
|
66
|
+
if (token.endsWith("ies") && token.length > 4) {
|
|
67
|
+
add(token.slice(0, -3));
|
|
68
|
+
add(`${token.slice(0, -3)}y`);
|
|
69
|
+
}
|
|
70
|
+
if (token.endsWith("es") && token.length > 3)
|
|
71
|
+
add(token.slice(0, -2));
|
|
72
|
+
if (token.endsWith("s") && !token.endsWith("ss") && token.length > 3) {
|
|
73
|
+
add(token.slice(0, -1));
|
|
74
|
+
}
|
|
75
|
+
return variants;
|
|
76
|
+
}
|
|
77
|
+
export function canonicalStringify(value) {
|
|
78
|
+
if (value === null)
|
|
79
|
+
return "null";
|
|
80
|
+
const primitive = jsonPrimitiveSchema.safeParse(value);
|
|
81
|
+
if (primitive.success) {
|
|
82
|
+
return JSON.stringify(primitive.data);
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return `[${value.map((item) => canonicalStringify(item)).join(",")}]`;
|
|
86
|
+
}
|
|
87
|
+
if (!isJsonObject(value))
|
|
88
|
+
return "null";
|
|
89
|
+
const entries = Object.entries(value).sort(([left], [right]) => compareCodeUnits(left, right));
|
|
90
|
+
return `{${entries
|
|
91
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalStringify(entry)}`)
|
|
92
|
+
.join(",")}}`;
|
|
93
|
+
}
|
|
94
|
+
export function hashString64(value) {
|
|
95
|
+
let hash = 0xcbf29ce484222325n;
|
|
96
|
+
const prime = 0x100000001b3n;
|
|
97
|
+
const mask = (1n << 64n) - 1n;
|
|
98
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
99
|
+
hash ^= BigInt(value.charCodeAt(index));
|
|
100
|
+
hash = (hash * prime) & mask;
|
|
101
|
+
}
|
|
102
|
+
return hash.toString(16).padStart(16, "0");
|
|
103
|
+
}
|
|
104
|
+
export function operationMethod(path) {
|
|
105
|
+
return path.slice(path.indexOf(".") + 1);
|
|
106
|
+
}
|
|
107
|
+
export function operationNamespace(path) {
|
|
108
|
+
return path.slice(0, path.indexOf("."));
|
|
109
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
/** Bounded provider-authored text. Hosts must frame it as data, never instructions. */
|
|
3
|
+
export type OpenApiThirdPartyText = string & z.BRAND<"ThirdPartyText">;
|
|
4
|
+
/** Optional owner-declared credential parameter excluded from guest input. */
|
|
5
|
+
export type OpenApiIntakeOptions = {
|
|
6
|
+
authParameter?: {
|
|
7
|
+
in: "header" | "query";
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
/** Stable intake and per-operation refusal codes. */
|
|
12
|
+
export type OpenApiRefusalCode = "too_large" | "invalid_spec" | "not_openapi3" | "nodes_limit" | "depth_limit" | "alias_limit" | "external_reference" | "recursive_schema" | "operations_limit" | "invalid_operation" | "unsupported_schema" | "parameter_collision" | "unsafe_auth_parameter" | "unsafe_header" | "unsupported_parameter" | "unsupported_media" | "unsupported_path_item" | "unsupported_method" | "table_too_large" | "invalid_table" | "unsafe_name" | "unsafe_path";
|
|
13
|
+
/** A bounded intake refusal, distinct from a listed unsupported operation. */
|
|
14
|
+
export declare class OpenApiError extends Error {
|
|
15
|
+
readonly code: OpenApiRefusalCode;
|
|
16
|
+
readonly name = "OpenApiError";
|
|
17
|
+
constructor(code: OpenApiRefusalCode, message: string, cause?: unknown);
|
|
18
|
+
}
|
|
19
|
+
/** One stored operation, independent of spec-host URLs or credentials. */
|
|
20
|
+
export declare const openApiOperationSchema: z.ZodObject<{
|
|
21
|
+
status: z.ZodLiteral<"supported">;
|
|
22
|
+
name: z.ZodString;
|
|
23
|
+
method: z.ZodEnum<{
|
|
24
|
+
GET: "GET";
|
|
25
|
+
HEAD: "HEAD";
|
|
26
|
+
OPTIONS: "OPTIONS";
|
|
27
|
+
POST: "POST";
|
|
28
|
+
PUT: "PUT";
|
|
29
|
+
PATCH: "PATCH";
|
|
30
|
+
DELETE: "DELETE";
|
|
31
|
+
}>;
|
|
32
|
+
path: z.ZodString;
|
|
33
|
+
effect: z.ZodEnum<{
|
|
34
|
+
write: "write";
|
|
35
|
+
read: "read";
|
|
36
|
+
}>;
|
|
37
|
+
description: z.core.$ZodBranded<z.ZodString, "ThirdPartyText", "out">;
|
|
38
|
+
textSource: z.ZodLiteral<"third_party">;
|
|
39
|
+
tags: z.ZodArray<z.core.$ZodBranded<z.ZodString, "ThirdPartyText", "out">>;
|
|
40
|
+
parameters: z.ZodArray<z.ZodObject<{
|
|
41
|
+
name: z.ZodString;
|
|
42
|
+
in: z.ZodEnum<{
|
|
43
|
+
header: "header";
|
|
44
|
+
path: "path";
|
|
45
|
+
query: "query";
|
|
46
|
+
}>;
|
|
47
|
+
required: z.ZodDefault<z.ZodBoolean>;
|
|
48
|
+
schema: z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodJSONSchema>, z.ZodBoolean]>;
|
|
49
|
+
style: z.ZodOptional<z.ZodString>;
|
|
50
|
+
explode: z.ZodOptional<z.ZodBoolean>;
|
|
51
|
+
allowReserved: z.ZodOptional<z.ZodBoolean>;
|
|
52
|
+
}, z.core.$strip>>;
|
|
53
|
+
input: z.ZodPipe<z.ZodUnknown, z.ZodType<boolean | Record<string, z.core.util.JSONType>, unknown, z.core.$ZodTypeInternals<boolean | Record<string, z.core.util.JSONType>, unknown>>>;
|
|
54
|
+
output: z.ZodPipe<z.ZodUnknown, z.ZodType<boolean | Record<string, z.core.util.JSONType>, unknown, z.core.$ZodTypeInternals<boolean | Record<string, z.core.util.JSONType>, unknown>>>;
|
|
55
|
+
}, z.core.$strict>;
|
|
56
|
+
/** Decoded normalized operation ready for selection, not an execution grant. */
|
|
57
|
+
export type OpenApiOperation = z.output<typeof openApiOperationSchema>;
|
|
58
|
+
/** An operation's supported call shape or bounded unsupported reason. */
|
|
59
|
+
export type OpenApiPreviewOperation = OpenApiOperation | {
|
|
60
|
+
status: "unsupported";
|
|
61
|
+
name: string;
|
|
62
|
+
method: string;
|
|
63
|
+
path: string;
|
|
64
|
+
reason: OpenApiRefusalCode;
|
|
65
|
+
};
|
|
66
|
+
/** Pure intake: the host supplies bounded spec text, never a fetch locator. */
|
|
67
|
+
export declare function normalizeOpenApi(source: string, options?: OpenApiIntakeOptions): Promise<{
|
|
68
|
+
title: string & z.core.$brand<"ThirdPartyText">;
|
|
69
|
+
version: string;
|
|
70
|
+
servers: string[];
|
|
71
|
+
authHints: ({
|
|
72
|
+
scheme: string;
|
|
73
|
+
kind: "api_key_header" | "api_key_query";
|
|
74
|
+
name: string;
|
|
75
|
+
} | {
|
|
76
|
+
scheme: string;
|
|
77
|
+
kind: "bearer" | "oauth" | "unsupported";
|
|
78
|
+
})[];
|
|
79
|
+
operations: OpenApiPreviewOperation[];
|
|
80
|
+
tableDigest: string;
|
|
81
|
+
}>;
|
|
82
|
+
/** Decode selected operations before persistence or execution; no authority is granted. */
|
|
83
|
+
export declare function parseOpenApiTable(source: unknown): Promise<{
|
|
84
|
+
operations: {
|
|
85
|
+
status: "supported";
|
|
86
|
+
name: string;
|
|
87
|
+
method: "GET" | "HEAD" | "OPTIONS" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
88
|
+
path: string;
|
|
89
|
+
effect: "write" | "read";
|
|
90
|
+
description: string & z.core.$brand<"ThirdPartyText">;
|
|
91
|
+
textSource: "third_party";
|
|
92
|
+
tags: (string & z.core.$brand<"ThirdPartyText">)[];
|
|
93
|
+
parameters: {
|
|
94
|
+
name: string;
|
|
95
|
+
in: "header" | "path" | "query";
|
|
96
|
+
required: boolean;
|
|
97
|
+
schema: boolean | Record<string, z.core.util.JSONType>;
|
|
98
|
+
style?: string | undefined;
|
|
99
|
+
explode?: boolean | undefined;
|
|
100
|
+
allowReserved?: boolean | undefined;
|
|
101
|
+
}[];
|
|
102
|
+
input: boolean | Record<string, z.core.util.JSONType>;
|
|
103
|
+
output: boolean | Record<string, z.core.util.JSONType>;
|
|
104
|
+
}[];
|
|
105
|
+
digest: string;
|
|
106
|
+
}>;
|