@instruments/materialgraph 3.1.0 → 3.3.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.
Files changed (65) hide show
  1. package/association-types.d.ts +20 -20
  2. package/baseline-types.d.ts +112 -0
  3. package/baseline-types.js +1 -0
  4. package/brand-types.d.ts +6 -6
  5. package/by-spec-types.d.ts +6 -6
  6. package/catalog-discovery-types.d.ts +4 -4
  7. package/categories-client.d.ts +3 -3
  8. package/category-types.d.ts +18 -18
  9. package/client-config.d.ts +2 -2
  10. package/client-response-schema.d.ts +2 -2
  11. package/client-response-schema.js +5 -5
  12. package/client-vocabulary.d.ts +2 -0
  13. package/client-vocabulary.js +9 -1
  14. package/client.d.ts +32 -3
  15. package/client.js +54 -14
  16. package/color-profiles-types.d.ts +8 -8
  17. package/color-wire-types.d.ts +6 -6
  18. package/colors-client.d.ts +3 -3
  19. package/discovery-context-types.d.ts +8 -8
  20. package/errors.d.ts +12 -13
  21. package/errors.js +84 -74
  22. package/find-sampled-together-types.d.ts +6 -6
  23. package/find-variants-by-color-types.d.ts +31 -30
  24. package/find-variants-by-vector-types.d.ts +13 -13
  25. package/get-variant-demand-types.d.ts +69 -0
  26. package/get-variant-demand-types.js +1 -0
  27. package/get-variant-types.d.ts +7 -7
  28. package/http-transport.d.ts +14 -10
  29. package/http-transport.js +14 -12
  30. package/image-reference-types.d.ts +2 -2
  31. package/index.d.ts +3 -1
  32. package/json.d.ts +18 -0
  33. package/json.js +36 -0
  34. package/list-enum-values-types.d.ts +8 -8
  35. package/list-sampled-together-pairs-types.d.ts +11 -11
  36. package/list-variant-facets-types.d.ts +8 -8
  37. package/list-variants-types.d.ts +42 -32
  38. package/material-asset-types.d.ts +26 -26
  39. package/material-block-types.d.ts +7 -7
  40. package/material-types.d.ts +6 -6
  41. package/nearest-paints-types.d.ts +8 -8
  42. package/operation-responses.d.ts +8 -2
  43. package/operations.d.ts +1 -1
  44. package/operations.generated.d.ts +1 -1
  45. package/operations.generated.js +9 -0
  46. package/operations.js +1 -1
  47. package/package.json +1 -1
  48. package/parse-operation-response.d.ts +4 -3
  49. package/parse-operation-response.js +112 -135
  50. package/resolve-color-types.d.ts +6 -6
  51. package/resolve-variant-types.d.ts +10 -10
  52. package/response-schema-catalog.d.ts +8 -0
  53. package/response-schema-catalog.js +82 -0
  54. package/response-schemas.d.ts +32 -3
  55. package/response-schemas.generated.d.ts +1 -1
  56. package/response-schemas.generated.js +34 -28
  57. package/response-schemas.js +5 -77
  58. package/schema-resource-types.d.ts +12 -12
  59. package/search-catalog-subset-types.d.ts +2 -2
  60. package/search-variants-types.d.ts +12 -12
  61. package/source-material-types.d.ts +6 -6
  62. package/source-material.js +2 -32
  63. package/types.d.ts +43 -45
  64. package/webmcp.d.ts +10 -4
  65. package/webmcp.generated.js +5 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materialgraph",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,5 +1,6 @@
1
+ import type { JsonValue } from "./json-value-types.js";
2
+ import type { JsonObject } from "./json.js";
1
3
  import type { JsonOperationId, OperationResponses } from "./operation-responses.js";
2
- import type { JsonSchema } from "./response-schemas.js";
3
4
  /** Evaluate the JSON Schema keywords used by the published response contracts. */
4
- export declare const matchesResponseSchema: (value: unknown, schema: unknown, root: JsonSchema) => boolean;
5
- export declare const parseOperationResponse: <K extends JsonOperationId>(operationId: K, value: unknown) => Promise<OperationResponses[K]>;
5
+ export declare const matchesResponseSchema: (value: JsonValue | undefined, schema: JsonValue | undefined, root: JsonObject) => boolean;
6
+ export declare const parseOperationResponse: <K extends JsonOperationId>(operationId: K, value: JsonValue | undefined) => Promise<OperationResponses[K]>;
@@ -1,185 +1,162 @@
1
1
  import { openClientResponseEnums } from "./client-response-schema.js";
2
2
  import { MaterialGraphError } from "./errors.js";
3
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3
+ import { isJsonNumber, isJsonObject, isJsonString, jsonKind } from "./json.js";
4
4
  const sameJson = (left, right) => JSON.stringify(left) === JSON.stringify(right);
5
+ const SIMPLE_TYPES = new Set([
6
+ "array",
7
+ "boolean",
8
+ "null",
9
+ "object",
10
+ "string",
11
+ ]);
5
12
  const matchesType = (value, type) => {
6
13
  if (Array.isArray(type)) {
7
14
  return type.some((candidate) => matchesType(value, candidate));
8
15
  }
9
- switch (type) {
10
- case undefined: {
11
- return true;
12
- }
13
- case "null": {
14
- return value === null;
15
- }
16
- case "array": {
17
- return Array.isArray(value);
18
- }
19
- case "object": {
20
- return isRecord(value);
21
- }
22
- case "integer": {
23
- return typeof value === "number" && Number.isInteger(value);
24
- }
25
- case "number": {
26
- return typeof value === "number" && Number.isFinite(value);
27
- }
28
- case "string": {
29
- return typeof value === "string";
30
- }
31
- case "boolean": {
32
- return typeof value === "boolean";
33
- }
34
- default: {
35
- return false;
36
- }
16
+ if (type === undefined) {
17
+ return true;
18
+ }
19
+ if (type === "integer") {
20
+ return isJsonNumber(value) && Number.isInteger(value);
21
+ }
22
+ if (type === "number") {
23
+ return isJsonNumber(value) && Number.isFinite(value);
37
24
  }
25
+ return (isJsonString(type) && SIMPLE_TYPES.has(type) && jsonKind(value) === type);
38
26
  };
39
27
  const matchesNumberKeywords = (value, schema) => {
40
- if (typeof value === "number") {
41
- if (typeof schema.minimum === "number" && value < schema.minimum) {
42
- return false;
28
+ if (!isJsonNumber(value)) {
29
+ return true;
30
+ }
31
+ const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = schema;
32
+ if (isJsonNumber(minimum) && value < minimum) {
33
+ return false;
34
+ }
35
+ if (isJsonNumber(maximum) && value > maximum) {
36
+ return false;
37
+ }
38
+ if (isJsonNumber(exclusiveMinimum) && value <= exclusiveMinimum) {
39
+ return false;
40
+ }
41
+ return !(isJsonNumber(exclusiveMaximum) && value >= exclusiveMaximum);
42
+ };
43
+ const matchesStringKeywords = (value, schema) => {
44
+ if (!isJsonString(value)) {
45
+ return true;
46
+ }
47
+ const { maxLength, minLength, pattern } = schema;
48
+ let length = 0;
49
+ for (const _codePoint of value) {
50
+ length += 1;
51
+ }
52
+ if (isJsonNumber(minLength) && length < minLength) {
53
+ return false;
54
+ }
55
+ if (isJsonNumber(maxLength) && length > maxLength) {
56
+ return false;
57
+ }
58
+ return !(isJsonString(pattern) && !new RegExp(pattern, "u").test(value));
59
+ };
60
+ const matchesValueKeywords = (value, schema) => {
61
+ if ("const" in schema && !sameJson(value, schema.const)) {
62
+ return false;
63
+ }
64
+ const { enum: allowed } = schema;
65
+ return !(Array.isArray(allowed) &&
66
+ !allowed.some((candidate) => sameJson(candidate, value)));
67
+ };
68
+ /** Evaluates the JSON Schema keywords the published response contracts use. */
69
+ class ResponseSchemaEvaluator {
70
+ #root;
71
+ constructor(root) {
72
+ this.#root = root;
73
+ }
74
+ matches(value, schema) {
75
+ if (schema === true || schema === false) {
76
+ return schema;
43
77
  }
44
- if (typeof schema.maximum === "number" && value > schema.maximum) {
78
+ if (!isJsonObject(schema)) {
45
79
  return false;
46
80
  }
47
- if (typeof schema.exclusiveMinimum === "number" &&
48
- value <= schema.exclusiveMinimum) {
81
+ if (!this.#matchesReference(value, schema)) {
49
82
  return false;
50
83
  }
51
- if (typeof schema.exclusiveMaximum === "number" &&
52
- value >= schema.exclusiveMaximum) {
84
+ if (!matchesType(value, schema.type)) {
53
85
  return false;
54
86
  }
55
- }
56
- return true;
57
- };
58
- const matchesStringKeywords = (value, schema) => {
59
- if (typeof value === "string") {
60
- let length = 0;
61
- for (const _codePoint of value) {
62
- length += 1;
63
- }
64
- if (typeof schema.minLength === "number" && length < schema.minLength) {
87
+ if (!matchesValueKeywords(value, schema)) {
65
88
  return false;
66
89
  }
67
- if (typeof schema.maxLength === "number" && length > schema.maxLength) {
90
+ if (Array.isArray(schema.anyOf) &&
91
+ !schema.anyOf.some((candidate) => this.matches(value, candidate))) {
68
92
  return false;
69
93
  }
70
- if (typeof schema.pattern === "string" &&
71
- !new RegExp(schema.pattern, "u").test(value)) {
94
+ if ("not" in schema && this.matches(value, schema.not)) {
72
95
  return false;
73
96
  }
97
+ return (matchesNumberKeywords(value, schema) &&
98
+ matchesStringKeywords(value, schema) &&
99
+ this.#matchesArrayKeywords(value, schema) &&
100
+ this.#matchesObjectKeywords(value, schema));
74
101
  }
75
- return true;
76
- };
77
- const matchesArrayKeywords = (value, schema, root) => {
78
- if (Array.isArray(value)) {
79
- if (typeof schema.minItems === "number" && value.length < schema.minItems) {
102
+ #matchesArrayKeywords(value, schema) {
103
+ if (!Array.isArray(value)) {
104
+ return true;
105
+ }
106
+ const { items, maxItems, minItems, prefixItems } = schema;
107
+ if (isJsonNumber(minItems) && value.length < minItems) {
80
108
  return false;
81
109
  }
82
- if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
110
+ if (isJsonNumber(maxItems) && value.length > maxItems) {
83
111
  return false;
84
112
  }
85
- const prefix = Array.isArray(schema.prefixItems) ? schema.prefixItems : [];
113
+ const prefix = Array.isArray(prefixItems) ? prefixItems : [];
86
114
  for (const [index, item] of value.entries()) {
87
- const itemSchema = index < prefix.length ? prefix[index] : schema.items;
88
- if (itemSchema !== undefined &&
89
- !matchesResponseSchema(item, itemSchema, root)) {
115
+ const itemSchema = index < prefix.length ? prefix[index] : items;
116
+ if (itemSchema !== undefined && !this.matches(item, itemSchema)) {
90
117
  return false;
91
118
  }
92
119
  }
120
+ return true;
93
121
  }
94
- return true;
95
- };
96
- const matchesObjectKeywords = (value, schema, root) => {
97
- if (isRecord(value)) {
98
- if (Array.isArray(schema.required) &&
99
- !schema.required.every((key) => typeof key === "string" && Object.hasOwn(value, key))) {
122
+ #matchesObjectKeywords(value, schema) {
123
+ if (!isJsonObject(value)) {
124
+ return true;
125
+ }
126
+ const { additionalProperties, propertyNames, required } = schema;
127
+ if (Array.isArray(required) &&
128
+ !required.every((key) => isJsonString(key) && Object.hasOwn(value, key))) {
100
129
  return false;
101
130
  }
102
- const properties = isRecord(schema.properties) ? schema.properties : {};
131
+ const properties = isJsonObject(schema.properties) ? schema.properties : {};
103
132
  for (const [key, item] of Object.entries(value)) {
104
- if (schema.propertyNames !== undefined &&
105
- !matchesResponseSchema(key, schema.propertyNames, root)) {
133
+ if (propertyNames !== undefined && !this.matches(key, propertyNames)) {
106
134
  return false;
107
135
  }
108
136
  const property = Object.hasOwn(properties, key)
109
137
  ? properties[key]
110
- : schema.additionalProperties;
111
- if (property !== undefined &&
112
- !matchesResponseSchema(item, property, root)) {
138
+ : additionalProperties;
139
+ if (property !== undefined && !this.matches(item, property)) {
113
140
  return false;
114
141
  }
115
142
  }
143
+ return true;
116
144
  }
117
- return true;
118
- };
119
- const matchesResponseReference = (value, schema, root) => {
120
- if (typeof schema.$ref === "string") {
121
- if (!schema.$ref.startsWith("#/$defs/")) {
122
- return false;
145
+ #matchesReference(value, schema) {
146
+ const reference = schema.$ref;
147
+ if (!isJsonString(reference)) {
148
+ return true;
123
149
  }
124
- const name = schema.$ref
125
- .slice(8)
126
- .replaceAll("~1", "/")
127
- .replaceAll("~0", "~");
128
- if (!isRecord(root.$defs) ||
129
- !matchesResponseSchema(value, root.$defs[name], root)) {
150
+ if (!reference.startsWith("#/$defs/")) {
130
151
  return false;
131
152
  }
153
+ const name = reference.slice(8).replaceAll("~1", "/").replaceAll("~0", "~");
154
+ const definitions = this.#root.$defs;
155
+ return isJsonObject(definitions) && this.matches(value, definitions[name]);
132
156
  }
133
- return true;
134
- };
135
- const matchesResponseValue = (value, schema) => {
136
- if ("const" in schema && !sameJson(value, schema.const)) {
137
- return false;
138
- }
139
- if (Array.isArray(schema.enum) &&
140
- !schema.enum.some((candidate) => sameJson(candidate, value))) {
141
- return false;
142
- }
143
- return true;
144
- };
157
+ }
145
158
  /** Evaluate the JSON Schema keywords used by the published response contracts. */
146
- export const matchesResponseSchema = (value, schema, root) => {
147
- if (typeof schema === "boolean") {
148
- return schema;
149
- }
150
- if (!isRecord(schema)) {
151
- return false;
152
- }
153
- if (!matchesResponseReference(value, schema, root)) {
154
- return false;
155
- }
156
- if (!matchesType(value, schema.type)) {
157
- return false;
158
- }
159
- if (!matchesResponseValue(value, schema)) {
160
- return false;
161
- }
162
- if (Array.isArray(schema.anyOf) &&
163
- !schema.anyOf.some((candidate) => matchesResponseSchema(value, candidate, root))) {
164
- return false;
165
- }
166
- if ("not" in schema && matchesResponseSchema(value, schema.not, root)) {
167
- return false;
168
- }
169
- if (!matchesNumberKeywords(value, schema)) {
170
- return false;
171
- }
172
- if (!matchesStringKeywords(value, schema)) {
173
- return false;
174
- }
175
- if (!matchesArrayKeywords(value, schema, root)) {
176
- return false;
177
- }
178
- if (!matchesObjectKeywords(value, schema, root)) {
179
- return false;
180
- }
181
- return true;
182
- };
159
+ export const matchesResponseSchema = (value, schema, root) => new ResponseSchemaEvaluator(root).matches(value, schema);
183
160
  const schemas = new Map();
184
161
  /** The operation's published schema, rather than a caller's type argument, proves its response. */
185
162
  const isOperationResponse = (operationId, value) => {
@@ -190,8 +167,8 @@ export const parseOperationResponse = async (operationId, value) => {
190
167
  let schema = schemas.get(operationId);
191
168
  if (!schema) {
192
169
  // oxlint-disable-next-line howells/no-runtime-dynamic-imports -- Load the large response schema bundle only when a response needs validation.
193
- const { responseSchema } = await import("./response-schemas.js");
194
- schema = responseSchema(operationId);
170
+ const catalog = await import("./response-schema-catalog.js");
171
+ schema = catalog.buildResponseSchema(operationId);
195
172
  openClientResponseEnums(schema);
196
173
  schemas.set(operationId, schema);
197
174
  }
@@ -7,15 +7,15 @@ import type { VariantId } from "./identifiers.js";
7
7
  import type { SearchCatalogSubset } from "./search-catalog-subset-types.js";
8
8
  import type { OpenEnum } from "./types.js";
9
9
  /** Options for `resolveColor`. */
10
- export type ResolveColorOptions = {
10
+ export interface ResolveColorOptions {
11
11
  /**
12
12
  * Sets `inScope` on each catalog variant the lookup reports. It never
13
13
  * narrows the lookup. Absent leaves every `inScope` null.
14
14
  */
15
15
  searchCatalogSubset?: SearchCatalogSubset;
16
- };
16
+ }
17
17
  /** One catalog variant carrying the named paint. */
18
- export type ResolveColorCatalogVariant = {
18
+ export interface ResolveColorCatalogVariant {
19
19
  variantId: VariantId;
20
20
  /** The brand exactly as stored, suspect or not. */
21
21
  brand: {
@@ -25,7 +25,7 @@ export type ResolveColorCatalogVariant = {
25
25
  };
26
26
  /** Whether the variant is in the requested scope; null when none was asked for. */
27
27
  inScope: boolean | null;
28
- };
28
+ }
29
29
  /**
30
30
  * Whether the named paint is a variant in the catalog. Exact, global, and
31
31
  * never narrowed by `searchCatalogSubset`, and run only for a `brand` library hit.
@@ -43,7 +43,7 @@ export type ResolveColorVariant = ({
43
43
  status: "not-looked-up";
44
44
  reason: OpenEnum<"reference-color" | "descriptive-color" | "no-paint-identity" | "generic-color" | "several-paints">;
45
45
  };
46
- export type ResolveColorResponse = {
46
+ export interface ResolveColorResponse {
47
47
  /** Examples within the governed range. They do not replace its representative or bounds. */
48
48
  rangeSamples?: string[];
49
49
  /** Explicit brand choices for an ambiguous paint name. Never selected automatically. */
@@ -73,4 +73,4 @@ export type ResolveColorResponse = {
73
73
  /** The catalog lookup for the name. Absent from servers that predate it. */
74
74
  variant?: ResolveColorVariant;
75
75
  } | null;
76
- };
76
+ }
@@ -13,15 +13,15 @@
13
13
  * product names visible on the page, sent only to tell several URL candidates
14
14
  * apart - they never widen a search or invent an identity on their own.
15
15
  */
16
- export type ResolveVariantInput = {
16
+ export interface ResolveVariantInput {
17
17
  url?: string;
18
18
  materialBankId?: string;
19
19
  code?: string;
20
20
  brandId?: string;
21
21
  labels?: string[];
22
- };
22
+ }
23
23
  /** One identified, placed or bounded-candidate subject. */
24
- export type ResolveVariantSubject = {
24
+ export interface ResolveVariantSubject {
25
25
  kind: "variant" | "brand";
26
26
  id: string;
27
27
  name: string;
@@ -30,23 +30,23 @@ export type ResolveVariantSubject = {
30
30
  id: string;
31
31
  name: string;
32
32
  };
33
- };
33
+ }
34
34
  /** What stood behind an outcome. Exact and scoreless: never a similarity guess. */
35
- export type ResolveVariantEvidence = {
35
+ export interface ResolveVariantEvidence {
36
36
  kind: "brand-url" | "brand-homepage" | "brand-code" | "material-bank-id" | "visible-label";
37
37
  url?: string;
38
38
  detail: string;
39
- };
39
+ }
40
40
  /** The submitted URL, normalized, and the steps that normalization took. Absent for a code-only call. */
41
- export type ResolveVariantReceipt = {
41
+ export interface ResolveVariantReceipt {
42
42
  submittedUrl: string;
43
43
  normalizedUrl: string;
44
44
  normalizationSteps: string[];
45
- };
46
- type ResolveVariantOutputBase = {
45
+ }
46
+ interface ResolveVariantOutputBase {
47
47
  evidence: ResolveVariantEvidence[];
48
48
  receipt?: ResolveVariantReceipt;
49
- };
49
+ }
50
50
  /**
51
51
  * Why nothing was resolved. Absent and empty differ: this names what was
52
52
  * looked for and not found, never a similarity guess.
@@ -0,0 +1,8 @@
1
+ import type { JsonObject } from "./json.js";
2
+ export declare const JSON_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema";
3
+ /**
4
+ * The success-response schema for one operation, self-contained and carrying
5
+ * only the definitions it reaches. Each call returns a fresh copy, so a caller
6
+ * that annotates or mutates it cannot change what the next caller gets.
7
+ */
8
+ export declare const buildResponseSchema: (operationId: string) => JsonObject;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The generated response-schema catalog, parsed once on first use. It is its
3
+ * own module because the catalog is large: importing the client does not load
4
+ * it.
5
+ */
6
+ import { MaterialGraphError } from "./errors.js";
7
+ import { isJsonObject, isJsonString, parseJson } from "./json.js";
8
+ import { RESPONSE_SCHEMA_CATALOG_JSON } from "./response-schemas.generated.js";
9
+ export const JSON_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema";
10
+ const DEFINITION_PREFIX = "#/$defs/";
11
+ const isJsonSchemaMap = (value) => isJsonObject(value) && Object.values(value).every(isJsonObject);
12
+ const isCatalog = (value) => isJsonObject(value) &&
13
+ isJsonSchemaMap(value.definitions) &&
14
+ isJsonSchemaMap(value.operations);
15
+ let parsedCatalog;
16
+ const loadCatalog = () => {
17
+ if (parsedCatalog === undefined) {
18
+ const parsed = parseJson(RESPONSE_SCHEMA_CATALOG_JSON);
19
+ if (!isCatalog(parsed)) {
20
+ throw MaterialGraphError.unexpected("The response schema catalog is malformed");
21
+ }
22
+ parsedCatalog = parsed;
23
+ }
24
+ return parsedCatalog;
25
+ };
26
+ /** Add every definition `value` reaches, transitively, to `into`. */
27
+ const collectReferences = (catalog, value, into) => {
28
+ if (Array.isArray(value)) {
29
+ for (const item of value) {
30
+ collectReferences(catalog, item, into);
31
+ }
32
+ return;
33
+ }
34
+ if (!isJsonObject(value)) {
35
+ return;
36
+ }
37
+ for (const [key, child] of Object.entries(value)) {
38
+ if (key === "$ref" &&
39
+ isJsonString(child) &&
40
+ child.startsWith(DEFINITION_PREFIX)) {
41
+ const name = child
42
+ .slice(DEFINITION_PREFIX.length)
43
+ .replaceAll("~1", "/")
44
+ .replaceAll("~0", "~");
45
+ if (!into.has(name)) {
46
+ into.add(name);
47
+ collectReferences(catalog, catalog.definitions[name], into);
48
+ }
49
+ }
50
+ else {
51
+ collectReferences(catalog, child, into);
52
+ }
53
+ }
54
+ };
55
+ /**
56
+ * The success-response schema for one operation, self-contained and carrying
57
+ * only the definitions it reaches. Each call returns a fresh copy, so a caller
58
+ * that annotates or mutates it cannot change what the next caller gets.
59
+ */
60
+ export const buildResponseSchema = (operationId) => {
61
+ const catalog = loadCatalog();
62
+ const root = Object.hasOwn(catalog.operations, operationId)
63
+ ? catalog.operations[operationId]
64
+ : undefined;
65
+ if (root === undefined || !isJsonObject(root)) {
66
+ throw MaterialGraphError.unexpected(`No response schema for operation: ${operationId}`);
67
+ }
68
+ const names = new Set();
69
+ collectReferences(catalog, root, names);
70
+ const definitions = {};
71
+ for (const name of [...names].toSorted()) {
72
+ const definition = catalog.definitions[name];
73
+ if (definition !== undefined) {
74
+ definitions[name] = definition;
75
+ }
76
+ }
77
+ const schema = { $schema: JSON_SCHEMA_DIALECT, ...root };
78
+ if (names.size > 0) {
79
+ schema.$defs = definitions;
80
+ }
81
+ return structuredClone(schema);
82
+ };
@@ -1,17 +1,46 @@
1
+ /**
2
+ * Runtime response contracts: the JSON Schema of each operation's success body.
3
+ *
4
+ * The schemas are the producer's own. They are projected from the served
5
+ * OpenAPI document, whose response bodies are generated from the Zod contracts
6
+ * the routes validate against, so a consumer never has to re-describe a large,
7
+ * partly open payload and watch it drift. Hand one to anything that takes a
8
+ * JSON Schema, such as a Mastra tool's `outputSchema`:
9
+ *
10
+ * ```ts
11
+ * import { responseSchema } from "@instruments/materialgraph/response-schemas";
12
+ *
13
+ * createTool({ outputSchema: responseSchema("search_variants"), … });
14
+ * ```
15
+ *
16
+ * Keyed by OpenAPI `operationId`, the same id `SDK_OPERATIONS` pairs with each
17
+ * client method. Schemas are JSON Schema 2020-12 and describe the parsed body
18
+ * the client method returns.
19
+ *
20
+ * Objects accept properties the schema does not declare, so a field the API
21
+ * adds later never makes a pinned consumer reject a live response; enums and
22
+ * constants stay closed. That is why these are more lenient than the WebMCP
23
+ * `outputSchema`s, which describe what the API sends rather than what a
24
+ * consumer should tolerate.
25
+ *
26
+ * This is its own entry point because the catalog is large; importing the
27
+ * client does not load it, and it is parsed on the first `responseSchema()`
28
+ * call rather than on import.
29
+ */
30
+ import { JSON_SCHEMA_DIALECT } from "./response-schema-catalog.js";
1
31
  import type { RESPONSE_SCHEMA_OPERATION_IDS } from "./response-schemas.generated.js";
2
32
  export { RESPONSE_SCHEMA_API_VERSION, RESPONSE_SCHEMA_OPERATION_IDS, } from "./response-schemas.generated.js";
3
33
  /** Every operation {@link responseSchema} answers for. */
4
34
  export type ResponseSchemaOperationId = (typeof RESPONSE_SCHEMA_OPERATION_IDS)[number];
5
35
  /** A JSON Schema document or subschema, as plain JSON. */
6
- export type JsonSchema = {
36
+ export interface JsonSchema {
7
37
  [keyword: string]: unknown;
8
- };
38
+ }
9
39
  /** A self-contained schema: every `$ref` resolves inside its own `$defs`. */
10
40
  export type ResponseJsonSchema = JsonSchema & {
11
41
  $schema: typeof JSON_SCHEMA_DIALECT;
12
42
  $defs?: Record<string, JsonSchema>;
13
43
  };
14
- declare const JSON_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema";
15
44
  /**
16
45
  * The success-response schema for one operation, self-contained and carrying
17
46
  * only the definitions it reaches. Each call returns a fresh copy, so a caller
@@ -1,5 +1,5 @@
1
1
  /** The API contract version the schemas were generated from. */
2
2
  export declare const RESPONSE_SCHEMA_API_VERSION = "0.8.0";
3
3
  /** Every SDK operation whose success response has a published JSON Schema. */
4
- export declare const RESPONSE_SCHEMA_OPERATION_IDS: readonly ["get_discovery_reference", "interpret_discovery_terms", "find_colorless_variants", "list_variant_siblings", "find_nearest_paints", "get_catalog_generation", "list_material_blocks", "list_variant_assets", "list_material_documents", "list_schema_value_dictionaries", "get_material_pairings", "list_associated_finishes", "list_associated_products", "get_applicability", "list_variants", "list_variant_facets", "search_variants", "find_variants_by_color", "find_variants_by_vector", "find_sampled_together", "list_sampled_together_pairs", "resolve_variant", "get_variant", "resolve_color_name", "catalog_list_attributes", "catalog_list_enum_values", "list_categories", "get_category", "whoami", "get_category_color_profiles", "extract_image_palette", "list_brands", "upload_source_material"];
4
+ export declare const RESPONSE_SCHEMA_OPERATION_IDS: readonly ["get_discovery_reference", "interpret_discovery_terms", "find_colorless_variants", "list_variant_siblings", "find_nearest_paints", "get_catalog_generation", "list_material_blocks", "list_variant_assets", "list_material_documents", "list_schema_value_dictionaries", "get_material_pairings", "list_associated_finishes", "list_associated_products", "get_applicability", "list_variants", "list_variant_facets", "search_variants", "find_variants_by_color", "find_variants_by_vector", "find_sampled_together", "list_sampled_together_pairs", "get_variant_demand", "list_rising_variants", "resolve_variant", "get_variant", "resolve_color_name", "catalog_list_attributes", "catalog_list_enum_values", "list_baselines", "check_baselines", "list_categories", "get_category", "whoami", "get_category_color_profiles", "extract_image_palette", "list_brands", "upload_source_material"];
5
5
  export declare const RESPONSE_SCHEMA_CATALOG_JSON: string;