@fungi.computer/forage 0.1.0 → 0.2.1

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 CHANGED
@@ -59,7 +59,13 @@ has a unique dotted path, an input schema, and optional description, capability
59
59
  notes, instructions, and output schema. Forage validates the catalog, orders its
60
60
  operations deterministically, and prepares descriptions once when the catalog is
61
61
  created. To change the catalog, create a new revision instead of mutating the
62
- existing one.
62
+ existing one. Every operation must fit one complete description, and the
63
+ catalog's aggregate metadata remains bounded. A whole namespace can exceed a
64
+ description response without making its individually describable operations
65
+ unavailable. Use paged `list({ namespace })` and describe one operation or
66
+ select `methods` when a broad namespace description exceeds the response bound.
67
+ This applies equally to flat namespaces and broad parents of nested namespaces;
68
+ responses are never truncated and the response bound is unchanged.
63
69
 
64
70
  `search` ranks matching operations. `describe` returns a bounded TypeScript
65
71
  description for one operation or a namespace. Operation descriptions also
package/dist/app-api.d.ts CHANGED
@@ -3,7 +3,16 @@ import type { AppApi } from "@fungi.computer/puffball";
3
3
  import type { Operation } from "./schemas.js";
4
4
  /** The shared JSON-Schema compiler for App methods and OpenAPI operations. */
5
5
  export declare function jsonSchemaToZod(source: unknown, defaultTarget?: "draft-2020-12" | "openapi-3.0"): z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
6
- /** Compile App validators and their Standard JSON Schema descriptions.
6
+ /**
7
+ * One decoded, immutable App declaration as a JSON-only Standard Schema.
8
+ * Release and family-listing decoders own its JSON boundary. A catalog
9
+ * describes every installed method on each capture but a turn validates only
10
+ * the few it calls. Compiling every method eagerly exhausted an Agent Durable
11
+ * Object's isolate. A draft 2020-12 description is the declaration itself;
12
+ * validators compile for the call in hand and are never kept on the surface.
13
+ */
14
+ export declare function appJsonSchema(declaration: AppApi["methods"][string]["input"] | boolean): Pick<z.ZodType<z.output<z.ZodJSONSchema>>, "parse" | "safeParse" | "~standard">;
15
+ /** Describe App methods with call-scoped JSON validators.
7
16
  * Validation enforces both the declaration and JSON-only values; discovery
8
17
  * describes the declaration without adding JSON guard nesting.
9
18
  * Agar's emitted output already includes Pending for opted-in methods. */
@@ -11,18 +20,18 @@ export declare function appMethodSchemas(api: AppApi): {
11
20
  [k: string]: {
12
21
  readonly description: string;
13
22
  readonly effect: "write" | "external" | "read";
14
- readonly requires: readonly ("agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage")[];
23
+ readonly requires: readonly ("approvals" | "agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage")[];
15
24
  readonly pending: true | undefined;
16
25
  readonly input: Pick<z.ZodType<z.core.util.JSONType, unknown, z.core.$ZodTypeInternals<z.core.util.JSONType, unknown>>, "~standard" | "parse" | "safeParse">;
17
26
  readonly output: Pick<z.ZodType<z.core.util.JSONType, unknown, z.core.$ZodTypeInternals<z.core.util.JSONType, unknown>>, "~standard" | "parse" | "safeParse">;
18
27
  };
19
28
  };
20
- /** Compile discoverable App methods only; direct RPC validation uses appMethodSchemas. */
29
+ /** Describe discoverable App methods only; direct RPC validation uses appMethodSchemas. */
21
30
  export declare function appToolSchemas(api: AppApi): {
22
31
  [k: string]: {
23
32
  readonly description: string;
24
33
  readonly effect: "write" | "external" | "read";
25
- readonly requires: readonly ("agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage")[];
34
+ readonly requires: readonly ("approvals" | "agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage")[];
26
35
  readonly pending: true | undefined;
27
36
  readonly input: Pick<z.ZodType<z.core.util.JSONType, unknown, z.core.$ZodTypeInternals<z.core.util.JSONType, unknown>>, "~standard" | "parse" | "safeParse">;
28
37
  readonly output: Pick<z.ZodType<z.core.util.JSONType, unknown, z.core.$ZodTypeInternals<z.core.util.JSONType, unknown>>, "~standard" | "parse" | "safeParse">;
@@ -35,7 +44,7 @@ export declare function appToolFamilySchemas(api: AppApi, reply: unknown): {
35
44
  name: string;
36
45
  call: string;
37
46
  effect: "write" | "external" | "read";
38
- requires: readonly ("agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage")[];
47
+ requires: readonly ("approvals" | "agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage")[];
39
48
  kind: "ready" | "unindexed";
40
49
  tools: {
41
50
  row: {
package/dist/app-api.js CHANGED
@@ -4,25 +4,45 @@ const bindingName = z.lazy(() => z
4
4
  .min(1)
5
5
  .max(75)
6
6
  .regex(/^[A-Za-z][A-Za-z0-9_]*$/));
7
- const jsonSchema = z.lazy(() => z.union([z.record(z.string(), z.json()), z.boolean()]));
7
+ const jsonSchemaDocument = z.lazy(() => z.record(z.string(), z.json()));
8
+ const jsonSchema = z.lazy(() => z.union([jsonSchemaDocument, z.boolean()]));
8
9
  /** The shared JSON-Schema compiler for App methods and OpenAPI operations. */
9
10
  export function jsonSchemaToZod(source, defaultTarget = "draft-2020-12") {
10
11
  return z.fromJSONSchema(jsonSchema.parse(source), { defaultTarget });
11
12
  }
12
- /** Validate JSON with Zod; describe the declaration without the redundant JSON guard. */
13
- function appMethodSchema(source) {
14
- const declared = jsonSchemaToZod(source);
15
- const validator = z.intersection(declared, z.json());
13
+ /**
14
+ * One decoded, immutable App declaration as a JSON-only Standard Schema.
15
+ * Release and family-listing decoders own its JSON boundary. A catalog
16
+ * describes every installed method on each capture but a turn validates only
17
+ * the few it calls. Compiling every method eagerly exhausted an Agent Durable
18
+ * Object's isolate. A draft 2020-12 description is the declaration itself;
19
+ * validators compile for the call in hand and are never kept on the surface.
20
+ */
21
+ export function appJsonSchema(declaration) {
22
+ const build = () => jsonSchemaToZod(declaration);
23
+ const validate = () => z.intersection(build(), z.json());
24
+ const describe = (io, options) => {
25
+ if (options.target === "draft-2020-12" &&
26
+ declaration !== true &&
27
+ declaration !== false)
28
+ return declaration;
29
+ return build()["~standard"].jsonSchema[io](options);
30
+ };
16
31
  return {
17
- parse: (value, options) => validator.parse(value, options),
18
- safeParse: (value, options) => validator.safeParse(value, options),
32
+ parse: (value, options) => validate().parse(value, options),
33
+ safeParse: (value, options) => validate().safeParse(value, options),
19
34
  "~standard": {
20
- ...validator["~standard"],
21
- jsonSchema: declared["~standard"].jsonSchema,
35
+ version: 1,
36
+ vendor: "zod",
37
+ validate: (value, options) => validate()["~standard"].validate(value, options),
38
+ jsonSchema: {
39
+ input: (options) => describe("input", options),
40
+ output: (options) => describe("output", options),
41
+ },
22
42
  },
23
43
  };
24
44
  }
25
- /** Compile App validators and their Standard JSON Schema descriptions.
45
+ /** Describe App methods with call-scoped JSON validators.
26
46
  * Validation enforces both the declaration and JSON-only values; discovery
27
47
  * describes the declaration without adding JSON guard nesting.
28
48
  * Agar's emitted output already includes Pending for opted-in methods. */
@@ -34,12 +54,12 @@ export function appMethodSchemas(api) {
34
54
  effect: declaration.effect,
35
55
  requires: declaration.requires,
36
56
  pending: declaration.pending,
37
- input: appMethodSchema(declaration.input),
38
- output: appMethodSchema(declaration.output),
57
+ input: appJsonSchema(declaration.input),
58
+ output: appJsonSchema(declaration.output),
39
59
  },
40
60
  ]));
41
61
  }
42
- /** Compile discoverable App methods only; direct RPC validation uses appMethodSchemas. */
62
+ /** Describe discoverable App methods only; direct RPC validation uses appMethodSchemas. */
43
63
  export function appToolSchemas(api) {
44
64
  return appMethodSchemas({
45
65
  ...api,
@@ -83,7 +103,7 @@ export function appToolFamilySchemas(api, reply) {
83
103
  const call = api.methods[family.call];
84
104
  if (list === undefined || call === undefined)
85
105
  throw new Error("Tool family references undeclared methods");
86
- const inventory = familyInventory.parse(appMethodSchema(list.output).parse(reply));
106
+ const inventory = familyInventory.parse(appJsonSchema(list.output).parse(reply));
87
107
  return {
88
108
  name: family.name,
89
109
  call: family.call,
@@ -97,8 +117,8 @@ export function appToolFamilySchemas(api, reply) {
97
117
  description: [row.title, row.description]
98
118
  .filter((text) => text.length > 0)
99
119
  .join("\n"),
100
- input: appMethodSchema(row.input),
101
- output: appMethodSchema(row.output),
120
+ input: appJsonSchema(row.input),
121
+ output: appJsonSchema(row.output),
102
122
  })),
103
123
  };
104
124
  }
package/dist/catalog.js CHANGED
@@ -1,11 +1,34 @@
1
+ import { z } from "zod";
1
2
  import { ForageError } from "./errors.js";
2
- import { catalogInputSchema, forageDescribeInput, forageSearchInput, maximumDescriptionTypes, } from "./schemas.js";
3
+ import { catalogInputSchema, forageDescribeInput, forageSearchInput, maximumDescriptionTypes, maximumSampleBytes, maximumSampleDepth, maximumSampleEntries, jsonDepth, } from "./schemas.js";
3
4
  import { describeCatalog } from "./describe.js";
4
5
  import { catalogStamp, prepareOperation, searchCatalog, } from "./search.js";
5
6
  import { compareCodeUnits } from "./normalization.js";
6
7
  import { listOperations } from "./list.js";
7
- import { renderNamespaceIndex, renderOperationTypes, schemaProjectionSize, } from "./rendering.js";
8
+ import { rawSchema, schemaText } from "./rendering.js";
8
9
  const maximumCatalogProjectionBytes = 1_000_000;
10
+ /**
11
+ * A nesting bound on the raw producer document, not a second JSON decoder.
12
+ * The ceiling bounds recursion; sibling order cannot change the maximum.
13
+ * Indexed traversal avoids cold iterator results and heap stack entries.
14
+ */
15
+ function schemaDepth(children, depth = 0) {
16
+ if (depth >= maximumSampleDepth)
17
+ return children.length === 0 ? depth : depth + 1;
18
+ let maximum = depth;
19
+ for (let index = children.length - 1; index >= 0; index -= 1) {
20
+ const child = children[index];
21
+ let childDepth = depth + 1;
22
+ if (Array.isArray(child))
23
+ childDepth = schemaDepth(child, childDepth);
24
+ else if (z.util.isObject(child))
25
+ childDepth = schemaDepth(Object.values(child), childDepth);
26
+ maximum = Math.max(maximum, childDepth);
27
+ if (maximum > maximumSampleDepth)
28
+ return maximum;
29
+ }
30
+ return maximum;
31
+ }
9
32
  function parseCatalog(input) {
10
33
  const parsed = catalogInputSchema.safeParse(input);
11
34
  if (!parsed.success) {
@@ -16,24 +39,42 @@ function parseCatalog(input) {
16
39
  }
17
40
  const operations = [...parsed.data.operations].sort((left, right) => compareCodeUnits(left.path, right.path));
18
41
  const paths = new Set();
42
+ const preparedOperations = [];
19
43
  let projectionBytes = 0;
20
44
  for (const operation of operations) {
21
45
  if (paths.has(operation.path)) {
22
46
  throw new ForageError("duplicate_operation", `Duplicate operation path: ${operation.path}`);
23
47
  }
24
48
  paths.add(operation.path);
25
- const inputProjection = schemaProjectionSize(operation.input, "input");
26
- if (inputProjection === undefined) {
49
+ const inputProjection = schemaText(operation.input, "input").length;
50
+ if (inputProjection > maximumDescriptionTypes ||
51
+ schemaDepth(Object.values(rawSchema(operation.input, "input"))) >
52
+ maximumSampleDepth) {
27
53
  throw new ForageError("invalid_input", `Schema projection is too large: ${operation.path}`);
28
54
  }
29
55
  const outputProjection = operation.output
30
- ? schemaProjectionSize(operation.output, "output")
56
+ ? schemaText(operation.output, "output").length
31
57
  : 0;
32
- if (outputProjection === undefined) {
58
+ if (outputProjection > maximumDescriptionTypes ||
59
+ (operation.output !== undefined &&
60
+ schemaDepth(Object.values(rawSchema(operation.output, "output"))) >
61
+ maximumSampleDepth)) {
33
62
  throw new ForageError("invalid_input", `Schema output projection is too large: ${operation.path}`);
34
63
  }
64
+ const prepared = prepareOperation(operation);
65
+ const sample = prepared.sample;
66
+ const sampleBytes = sample === undefined ? 0 : JSON.stringify(sample).length;
67
+ if (sample !== undefined &&
68
+ (Object.keys(sample).length > maximumSampleEntries ||
69
+ sampleBytes > maximumSampleBytes ||
70
+ jsonDepth(sample, 0) > maximumSampleDepth))
71
+ throw new ForageError("invalid_input", `Schema projection is too large: ${operation.path}`);
72
+ preparedOperations.push(prepared);
35
73
  projectionBytes +=
36
- inputProjection + outputProjection + operation.path.length;
74
+ prepared.inputBytes +
75
+ prepared.outputBytes +
76
+ sampleBytes +
77
+ operation.path.length;
37
78
  projectionBytes += operation.description?.length ?? 0;
38
79
  projectionBytes += operation.capabilityNotes?.length ?? 0;
39
80
  projectionBytes += operation.instructions?.length ?? 0;
@@ -41,23 +82,6 @@ function parseCatalog(input) {
41
82
  throw new ForageError("invalid_input", "Catalog metadata exceeds the bounded projection");
42
83
  }
43
84
  }
44
- const preparedOperations = operations.map((operation) => prepareOperation(operation));
45
- const namespaces = new Map();
46
- for (const operation of preparedOperations) {
47
- const namespace = operation.path.slice(0, operation.path.lastIndexOf("."));
48
- const group = namespaces.get(namespace) ?? [];
49
- group.push(operation);
50
- namespaces.set(namespace, group);
51
- }
52
- for (const [namespace, group] of namespaces) {
53
- const rendered = group.map((operation) => operation.rendered);
54
- const index = renderNamespaceIndex(rendered);
55
- const full = renderOperationTypes(rendered);
56
- if (index.length > maximumDescriptionTypes ||
57
- full.length > maximumDescriptionTypes) {
58
- throw new ForageError("invalid_input", `Namespace description exceeds the bounded output: ${namespace}`);
59
- }
60
- }
61
85
  return {
62
86
  revision: parsed.data.revision,
63
87
  operations: preparedOperations,
package/dist/describe.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { describeOutputSchema, maximumDescriptionTypes, } from "./schemas.js";
2
- import { compareCodeUnits } from "./normalization.js";
3
- import { renderNamespaceIndex, renderOperationTypes } from "./rendering.js";
2
+ import { compareCodeUnits, jsonValueSchema } from "./normalization.js";
3
+ import { rawSchema, renderOperation, renderNamespaceIndex, renderOperationTypes, } from "./rendering.js";
4
4
  import { ForageError } from "./errors.js";
5
5
  function boundedDescription(value) {
6
6
  return value && value.length > 0 ? value : undefined;
@@ -17,16 +17,19 @@ export function describeCatalog(operations, rawInput) {
17
17
  const operation = operations.find((candidate) => candidate.path === rawInput.path);
18
18
  if (operation) {
19
19
  const callPath = `env.${operation.path}`;
20
+ const inputSchema = jsonValueSchema.safeParse(rawSchema(operation.input, "input"));
21
+ if (!inputSchema.success)
22
+ throw new ForageError("invalid_input", "Schema cannot be projected to JSON Schema");
20
23
  const output = {
21
24
  path: operation.path,
22
25
  description: boundedDescription(operation.description),
23
- types: renderOperationTypes([operation.rendered]),
26
+ types: renderOperationTypes([renderOperation(operation)]),
24
27
  kind: "operation",
25
- inputSchema: operation.inputSchema,
28
+ inputSchema: inputSchema.data,
26
29
  usage: `await ${callPath}({ ... })`,
27
30
  };
28
31
  if (output.types.length > maximumDescriptionTypes) {
29
- throw new ForageError("invalid_input", "Description types exceed the bounded output");
32
+ throw new ForageError("invalid_input", "Operation description exceeds the bounded output");
30
33
  }
31
34
  return checkedDescription(output);
32
35
  }
@@ -46,13 +49,13 @@ export function describeCatalog(operations, rawInput) {
46
49
  : namespaceOperations;
47
50
  let types;
48
51
  if (rawInput.detail === "index") {
49
- types = renderNamespaceIndex(namespaceOperations.map((operation) => operation.rendered));
52
+ types = renderNamespaceIndex(namespaceOperations.map(renderOperation));
50
53
  }
51
54
  else if (rawInput.methods?.length && selected.length === 0) {
52
- types = renderNamespaceIndex(namespaceOperations.map((operation) => operation.rendered));
55
+ types = renderNamespaceIndex(namespaceOperations.map(renderOperation));
53
56
  }
54
57
  else {
55
- types = renderOperationTypes((selected.length > 0 ? selected : namespaceOperations).map((operation) => operation.rendered));
58
+ types = renderOperationTypes((selected.length > 0 ? selected : namespaceOperations).map(renderOperation));
56
59
  }
57
60
  if (types.length > maximumDescriptionTypes) {
58
61
  throw new ForageError("invalid_input", "Description types exceed the bounded output");
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { CatalogInput, DescribeOutput, ForageDescribeInput, ForageFailure,
3
3
  export type { ForageDefinition } from "./definitions.js";
4
4
  export { ForageError } from "./errors.js";
5
5
  export { createCatalog } from "./catalog.js";
6
- export { appApiOperations, appMethodSchemas, appToolSchemas, appToolFamilySchemas, jsonSchemaToZod, } from "./app-api.js";
6
+ export { appApiOperations, appJsonSchema, appMethodSchemas, appToolSchemas, appToolFamilySchemas, jsonSchemaToZod, } from "./app-api.js";
7
7
  export { listOperations } from "./list.js";
8
8
  export type { ForageCatalog } from "./catalog.js";
9
9
  export { forageDescribeDefinition, forageSearchDefinition, forageListDefinition, } from "./definitions.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { catalogInputSchema, describeOutputSchema, forageDescribeInput, forageFailureSchema, forageSearchInput, operationSchema, searchOutputSchema, searchResultSchema, forageListInput, listOutputSchema, } from "./schemas.js";
2
2
  export { ForageError } from "./errors.js";
3
3
  export { createCatalog } from "./catalog.js";
4
- export { appApiOperations, appMethodSchemas, appToolSchemas, appToolFamilySchemas, jsonSchemaToZod, } from "./app-api.js";
4
+ export { appApiOperations, appJsonSchema, appMethodSchemas, appToolSchemas, appToolFamilySchemas, jsonSchemaToZod, } from "./app-api.js";
5
5
  export { listOperations } from "./list.js";
6
6
  export { forageDescribeDefinition, forageSearchDefinition, forageListDefinition, } from "./definitions.js";
7
7
  import { createCatalog } from "./catalog.js";
@@ -16,7 +16,6 @@ export declare function normalizeQuery(query: string): string;
16
16
  export declare function tokenize(text: string): string[];
17
17
  export declare function isStopword(token: string): boolean;
18
18
  export declare function stemVariants(token: string): string[];
19
- export declare function canonicalStringify(value: JsonValue): string;
20
19
  export declare function hashString64(value: string): string;
21
20
  export declare function operationMethod(path: Operation["path"]): string;
22
21
  export declare function operationNamespace(path: Operation["path"]): string;
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
2
  export const jsonValueSchema = z.json();
3
- const jsonPrimitiveSchema = z.lazy(() => z.union([z.string(), z.number(), z.boolean()]));
4
3
  /**
5
4
  * Narrow an already-parsed JSON value to its object case. The value was
6
5
  * validated once at its boundary, so each nested level is told apart by its
@@ -8,9 +7,7 @@ const jsonPrimitiveSchema = z.lazy(() => z.union([z.string(), z.number(), z.bool
8
7
  * made walking a large schema quadratic.
9
8
  */
10
9
  export function isJsonObject(value) {
11
- return (value !== null &&
12
- !Array.isArray(value) &&
13
- !jsonPrimitiveSchema.safeParse(value).success);
10
+ return value !== null && !Array.isArray(value) && typeof value === "object";
14
11
  }
15
12
  export function compareCodeUnits(left, right) {
16
13
  if (left === right)
@@ -63,6 +60,9 @@ export function stemVariants(token) {
63
60
  add(token.slice(0, -3));
64
61
  if (token.endsWith("ed") && token.length > 3)
65
62
  add(token.slice(0, -2));
63
+ if (token.endsWith("ation") && token.length > 6) {
64
+ add(`${token.slice(0, -3)}e`);
65
+ }
66
66
  if (token.endsWith("ies") && token.length > 4) {
67
67
  add(token.slice(0, -3));
68
68
  add(`${token.slice(0, -3)}y`);
@@ -74,32 +74,29 @@ export function stemVariants(token) {
74
74
  }
75
75
  return variants;
76
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
77
  export function hashString64(value) {
95
- let hash = 0xcbf29ce484222325n;
96
- const prime = 0x100000001b3n;
97
- const mask = (1n << 64n) - 1n;
78
+ let word0 = 0x2325;
79
+ let word1 = 0x8422;
80
+ let word2 = 0x9ce4;
81
+ let word3 = 0xcbf2;
98
82
  for (let index = 0; index < value.length; index += 1) {
99
- hash ^= BigInt(value.charCodeAt(index));
100
- hash = (hash * prime) & mask;
83
+ const low = word0 ^ value.charCodeAt(index);
84
+ // The prime is 2^40 + 435. Four 16-bit limbs preserve modulo 2^64;
85
+ // every intermediate stays below 2^26, including before V8 optimizes
86
+ // this loop. Unsigned 32-bit limbs allocated boxed numbers when cold.
87
+ const product0 = low * 435;
88
+ const product1 = word1 * 435 + (product0 >>> 16);
89
+ const product2 = word2 * 435 + (product1 >>> 16) + (low << 8);
90
+ const product3 = word3 * 435 + (product2 >>> 16) + (word1 << 8);
91
+ word0 = product0 & 0xffff;
92
+ word1 = product1 & 0xffff;
93
+ word2 = product2 & 0xffff;
94
+ word3 = product3 & 0xffff;
101
95
  }
102
- return hash.toString(16).padStart(16, "0");
96
+ return (word3.toString(16).padStart(4, "0") +
97
+ word2.toString(16).padStart(4, "0") +
98
+ word1.toString(16).padStart(4, "0") +
99
+ word0.toString(16).padStart(4, "0"));
103
100
  }
104
101
  export function operationMethod(path) {
105
102
  return path.slice(path.indexOf(".") + 1);
package/dist/openapi.js CHANGED
@@ -11,6 +11,16 @@ const reservedNames = new Set([
11
11
  "prototype",
12
12
  ...Object.getOwnPropertyNames(Object.prototype),
13
13
  ]);
14
+ const operationMethods = [
15
+ "get",
16
+ "head",
17
+ "options",
18
+ "post",
19
+ "put",
20
+ "patch",
21
+ "delete",
22
+ "trace",
23
+ ];
14
24
  const pathTemplate = z
15
25
  .string()
16
26
  .max(2048)
@@ -304,6 +314,18 @@ function inspectExpansion(source, budget) {
304
314
  throw new OpenApiError(refusal, messages[refusal]);
305
315
  }
306
316
  }
317
+ function inspectOperationCount(paths) {
318
+ let count = 0;
319
+ for (const item of Object.values(paths)) {
320
+ count +=
321
+ item.$ref !== undefined
322
+ ? 1
323
+ : operationMethods.filter((method) => item[method] !== undefined)
324
+ .length;
325
+ if (count > 500)
326
+ throw new OpenApiError("operations_limit", "The spec exceeds 500 operations");
327
+ }
328
+ }
307
329
  /** Pure intake: the host supplies bounded spec text, never a fetch locator. */
308
330
  export async function normalizeOpenApi(source, options = {}) {
309
331
  const authParameter = intakeOptions.parse(options).authParameter;
@@ -349,9 +371,9 @@ export async function normalizeOpenApi(source, options = {}) {
349
371
  const budget = { nodes: 0 };
350
372
  const names = new Set(reservedNames);
351
373
  const operations = [];
374
+ // Refuse oversized catalogs before cloning or compiling individual operations.
375
+ inspectOperationCount(parsed.paths);
352
376
  for (const [path, item] of Object.entries(parsed.paths).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
353
- if (operations.length >= 500)
354
- throw new OpenApiError("operations_limit", "The spec exceeds 500 operations");
355
377
  if (item.$ref !== undefined) {
356
378
  operations.push({
357
379
  status: "unsupported",
@@ -362,21 +384,10 @@ export async function normalizeOpenApi(source, options = {}) {
362
384
  });
363
385
  continue;
364
386
  }
365
- for (const method of [
366
- "get",
367
- "head",
368
- "options",
369
- "post",
370
- "put",
371
- "patch",
372
- "delete",
373
- "trace",
374
- ]) {
387
+ for (const method of operationMethods) {
375
388
  const raw = item[method];
376
389
  if (raw === undefined)
377
390
  continue;
378
- if (operations.length >= 500)
379
- throw new OpenApiError("operations_limit", "The spec exceeds 500 operations");
380
391
  const label = z
381
392
  .object({ operationId: z.string().max(128).optional() })
382
393
  .safeParse(raw);
@@ -1,26 +1,20 @@
1
1
  import type { JsonValue } from "./normalization.js";
2
- import { type JsonSchemaSource } from "./schemas.js";
3
- /** Installation-time TypeScript projections for one catalog operation. */
2
+ import type { JsonSchemaSource, Operation } from "./schemas.js";
3
+ /** Requested TypeScript projections for one catalog operation. */
4
4
  export type RenderedOperation = Readonly<{
5
5
  types: string;
6
6
  environment: string;
7
7
  indexEnvironment: string;
8
8
  }>;
9
+ /** The producer's document, without a recursive projection decode or copy. */
10
+ export declare function rawSchema(schema: JsonSchemaSource, io: "input" | "output"): object;
11
+ /** Native serialization owns the document's text representation and failures. */
12
+ export declare function schemaText(schema: JsonSchemaSource, io: "input" | "output"): string;
9
13
  /** Render a namespace index as a callable nested environment. */
10
14
  export declare function renderNamespaceIndex(operations: readonly RenderedOperation[]): string;
11
15
  /** Render whole-operation schemas and their callable nested environment. */
12
16
  export declare function renderOperationTypes(operations: readonly RenderedOperation[]): string;
13
- /** Prepare all TypeScript descriptions for one operation at installation. */
14
- export declare function renderOperation(operation: {
15
- path: string;
16
- input: JsonSchemaSource;
17
- output?: JsonSchemaSource | undefined;
18
- }): RenderedOperation;
17
+ /** Render only requested operations, once per immutable prepared operation. */
18
+ export declare function renderOperation(operation: Operation): RenderedOperation;
19
19
  /** Derive sample arguments solely from input-schema examples and defaults. */
20
20
  export declare function deriveSample(schema: JsonSchemaSource): Record<string, JsonValue> | undefined;
21
- /** Preserve validator constraints in the operation's Standard JSON Schema. */
22
- export declare function inputSchemaProjection(schema: JsonSchemaSource): JsonValue;
23
- /** Produce a canonical schema representation used in peer identity and stamps. */
24
- export declare function schemaFingerprint(schema: JsonSchemaSource, io?: "input" | "output"): string;
25
- /** Validate the bounded JSON projection before a catalog is installed. */
26
- export declare function schemaProjectionSize(schema: JsonSchemaSource, io: "input" | "output"): number | undefined;