@instruments/materialgraph 3.0.2 → 3.2.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 (57) hide show
  1. package/association-types.d.ts +20 -20
  2. package/brand-types.d.ts +6 -6
  3. package/by-spec-types.d.ts +6 -6
  4. package/catalog-discovery-types.d.ts +4 -4
  5. package/categories-client.d.ts +3 -3
  6. package/category-types.d.ts +18 -18
  7. package/client-config.d.ts +2 -2
  8. package/client-response-schema.d.ts +2 -2
  9. package/client-response-schema.js +4 -4
  10. package/client-vocabulary.js +1 -1
  11. package/client.d.ts +1 -1
  12. package/client.js +14 -10
  13. package/color-profiles-types.d.ts +8 -8
  14. package/color-wire-types.d.ts +6 -6
  15. package/colors-client.d.ts +3 -3
  16. package/discovery-context-types.d.ts +8 -8
  17. package/errors.d.ts +12 -13
  18. package/errors.js +84 -74
  19. package/find-sampled-together-types.d.ts +6 -6
  20. package/find-variants-by-color-types.d.ts +30 -30
  21. package/find-variants-by-vector-types.d.ts +13 -13
  22. package/get-variant-types.d.ts +11 -7
  23. package/http-transport.d.ts +14 -10
  24. package/http-transport.js +14 -12
  25. package/image-reference-types.d.ts +2 -2
  26. package/index.d.ts +2 -2
  27. package/json.d.ts +18 -0
  28. package/json.js +36 -0
  29. package/list-enum-values-types.d.ts +8 -8
  30. package/list-sampled-together-pairs-types.d.ts +6 -6
  31. package/list-variant-facets-types.d.ts +8 -8
  32. package/list-variants-types.d.ts +59 -29
  33. package/material-asset-types.d.ts +37 -24
  34. package/material-block-types.d.ts +6 -6
  35. package/material-types.d.ts +6 -6
  36. package/nearest-paints-types.d.ts +8 -8
  37. package/operation-responses.d.ts +2 -2
  38. package/operations.d.ts +1 -1
  39. package/operations.js +1 -1
  40. package/package.json +1 -1
  41. package/parse-operation-response.d.ts +4 -3
  42. package/parse-operation-response.js +112 -135
  43. package/resolve-color-types.d.ts +6 -6
  44. package/resolve-variant-types.d.ts +10 -10
  45. package/response-schema-catalog.d.ts +8 -0
  46. package/response-schema-catalog.js +82 -0
  47. package/response-schemas.d.ts +32 -3
  48. package/response-schemas.generated.js +26 -25
  49. package/response-schemas.js +5 -77
  50. package/schema-resource-types.d.ts +12 -12
  51. package/search-catalog-subset-types.d.ts +2 -2
  52. package/search-variants-types.d.ts +12 -12
  53. package/source-material-types.d.ts +6 -6
  54. package/source-material.js +2 -32
  55. package/types.d.ts +38 -38
  56. package/webmcp.d.ts +10 -4
  57. package/webmcp.generated.js +5 -4
package/errors.d.ts CHANGED
@@ -1,12 +1,11 @@
1
- /** Error and rate-limit contracts for the retry-free MaterialGraph SDK. */
2
1
  export type MaterialGraphErrorKind = "problem" | "simple" | "network" | "timeout" | "aborted" | "rate_limited" | "unexpected";
3
2
  export type SanitizedErrorValue = null | boolean | number | string | SanitizedErrorValue[] | SanitizedIssue;
4
3
  /** A bounded, recursively sanitized validation-issue record. */
5
- export type SanitizedIssue = {
4
+ export interface SanitizedIssue {
6
5
  [key: string]: SanitizedErrorValue;
7
- };
6
+ }
8
7
  /** Normalized RFC 7807 body. Unknown response fields are intentionally discarded. */
9
- export type ProblemDetail = {
8
+ export interface ProblemDetail {
10
9
  type: string;
11
10
  title: string;
12
11
  status: number;
@@ -23,27 +22,27 @@ export type ProblemDetail = {
23
22
  * not carry them. See {@link MATERIALGRAPH_PROBLEM_EXTENSIONS}.
24
23
  */
25
24
  readonly [extension: string]: unknown;
26
- };
25
+ }
27
26
  /** Normalized residual `{ error }` body. Unknown response fields are discarded. */
28
- export type SimpleErrorBody = {
27
+ export interface SimpleErrorBody {
29
28
  /** The vector and variant routes' machine-readable code, when the body is `{ code, message }`. */
30
29
  code?: string;
31
30
  error: string;
32
31
  issues?: SanitizedIssue[];
33
- };
32
+ }
34
33
  /** Parsed `X-RateLimit-*` / `Retry-After` headers. `null` means absent. */
35
- export type RateLimit = {
34
+ export interface RateLimit {
36
35
  limit: number | null;
37
36
  remaining: number | null;
38
37
  reset: number | null;
39
38
  retryAfter: number | null;
40
- };
41
- export type MaterialGraphRequestMetadata = {
39
+ }
40
+ export interface MaterialGraphRequestMetadata {
42
41
  method: string;
43
42
  origin: string;
44
43
  path: string;
45
- };
46
- export type MaterialGraphErrorInit = {
44
+ }
45
+ export interface MaterialGraphErrorInit {
47
46
  kind: MaterialGraphErrorKind;
48
47
  status?: number;
49
48
  retriable: boolean;
@@ -56,7 +55,7 @@ export type MaterialGraphErrorInit = {
56
55
  path?: string;
57
56
  /** Accepted for compatibility but deliberately never retained on the public error. */
58
57
  cause?: unknown;
59
- };
58
+ }
60
59
  type ResponseErrorInput = {
61
60
  status: number;
62
61
  body: unknown;
package/errors.js CHANGED
@@ -1,4 +1,5 @@
1
1
  /** Error and rate-limit contracts for the retry-free MaterialGraph SDK. */
2
+ import { isJsonNumber, isJsonObject, isJsonString, toJsonValue, } from "./json.js";
2
3
  const RETRIABLE_STATUS = new Set([502, 503, 504]);
3
4
  /**
4
5
  * MaterialGraph's documented problem EXTENSION members — the only non-standard
@@ -45,7 +46,6 @@ const MAX_UNEXPECTED_TEXT = 768;
45
46
  const MAX_EXTENSION_DEPTH = 8;
46
47
  const MAX_EXTENSION_ITEMS = 100;
47
48
  const MAX_EXTENSION_STRING = 1024;
48
- const isRecord = (value) => typeof value === "object" && value !== null;
49
49
  const redact = (text, secrets) => {
50
50
  let sanitized = text;
51
51
  for (const secret of secrets) {
@@ -67,35 +67,35 @@ const requestMetadata = (input) => ({
67
67
  origin: input.origin,
68
68
  path: input.path,
69
69
  });
70
- const sanitizeValue = (value, secrets, depth = 0) => {
71
- if (depth >= MAX_EXTENSION_DEPTH) {
72
- return undefined;
73
- }
74
- if (value === null || typeof value === "boolean") {
75
- return value;
76
- }
77
- if (typeof value === "number") {
78
- return Number.isFinite(value) ? value : undefined;
79
- }
80
- if (typeof value === "string") {
81
- return safeString(value, secrets);
82
- }
83
- if (Array.isArray(value)) {
84
- return value
85
- .slice(0, MAX_EXTENSION_ITEMS)
86
- .map((item) => sanitizeValue(item, secrets, depth + 1))
87
- .filter((item) => item !== undefined);
88
- }
89
- if (!isRecord(value)) {
90
- return undefined;
91
- }
92
- return sanitizeRecord(value, secrets, depth);
93
- };
94
- const sanitizeRecord = (value, secrets, depth) => {
70
+ /** Redact, cap and drop non-JSON from one object, at most {@link MAX_EXTENSION_DEPTH} levels deep. */
71
+ const sanitizeObject = (value, secrets, depth) => {
72
+ const sanitizeItem = (item, itemDepth) => {
73
+ if (itemDepth >= MAX_EXTENSION_DEPTH) {
74
+ return undefined;
75
+ }
76
+ if (item === null || item === true || item === false) {
77
+ return item;
78
+ }
79
+ if (isJsonNumber(item)) {
80
+ return Number.isFinite(item) ? item : undefined;
81
+ }
82
+ if (isJsonString(item)) {
83
+ return safeString(item, secrets);
84
+ }
85
+ if (Array.isArray(item)) {
86
+ return item
87
+ .slice(0, MAX_EXTENSION_ITEMS)
88
+ .map((child) => sanitizeItem(child, itemDepth + 1))
89
+ .filter((child) => child !== undefined);
90
+ }
91
+ return isJsonObject(item)
92
+ ? sanitizeObject(item, secrets, itemDepth)
93
+ : undefined;
94
+ };
95
95
  const entries = Object.entries(value)
96
96
  .slice(0, MAX_EXTENSION_ITEMS)
97
97
  .flatMap(([key, item]) => {
98
- const sanitized = sanitizeValue(item, secrets, depth + 1);
98
+ const sanitized = sanitizeItem(item, depth + 1);
99
99
  return sanitized === undefined
100
100
  ? []
101
101
  : [[safeString(key, secrets), sanitized]];
@@ -108,12 +108,11 @@ const sanitizeIssues = (value, secrets) => {
108
108
  }
109
109
  return value
110
110
  .slice(0, MAX_EXTENSION_ITEMS)
111
- .filter((issue) => isRecord(issue) && !Array.isArray(issue))
112
- .map((issue) => sanitizeRecord(issue, secrets, 0));
111
+ .filter(isJsonObject)
112
+ .map((issue) => sanitizeObject(issue, secrets, 0));
113
113
  };
114
114
  const sanitizeSuggestions = (value, secrets) => {
115
- if (!Array.isArray(value) ||
116
- !value.every((suggestion) => typeof suggestion === "string")) {
115
+ if (!Array.isArray(value) || !value.every(isJsonString)) {
117
116
  return undefined;
118
117
  }
119
118
  return value
@@ -121,62 +120,72 @@ const sanitizeSuggestions = (value, secrets) => {
121
120
  .map((suggestion) => safeString(suggestion, secrets));
122
121
  };
123
122
  const normalizeProblem = (body, secrets) => {
124
- if (!isRecord(body) ||
125
- typeof body.type !== "string" ||
126
- typeof body.title !== "string" ||
127
- typeof body.status !== "number" ||
128
- typeof body.detail !== "string" ||
129
- typeof body.is_retriable !== "boolean" ||
130
- typeof body.trace_id !== "string" ||
131
- (body.instance !== undefined && typeof body.instance !== "string")) {
123
+ if (!isJsonObject(body)) {
124
+ return undefined;
125
+ }
126
+ const { detail, doc_uri: docUri, instance, is_retriable: retriable, status, title, trace_id: traceId, type, } = body;
127
+ if (!isJsonString(type) ||
128
+ !isJsonString(title) ||
129
+ !isJsonNumber(status) ||
130
+ !isJsonString(detail) ||
131
+ (retriable !== true && retriable !== false) ||
132
+ !isJsonString(traceId) ||
133
+ (instance !== undefined && !isJsonString(instance))) {
132
134
  return undefined;
133
135
  }
134
- const suggestions = sanitizeSuggestions(body.suggestions, secrets);
135
- const issues = sanitizeIssues(body.issues, secrets);
136
136
  // Extensions FIRST so a malformed one can never shadow a standard member
137
- // below. `sanitizeRecord` is the existing redaction and depth/item caps —
137
+ // below. `sanitizeObject` is the existing redaction and depth/item caps —
138
138
  // this is that machinery finally being reached, not a second sanitizer.
139
- const extensions = sanitizeRecord(Object.fromEntries(Object.entries(body).filter(([key]) => PROBLEM_EXTENSION_KEYS.has(key))), secrets, 0);
140
- return {
139
+ const extensions = sanitizeObject(Object.fromEntries(Object.entries(body).filter(([key]) => PROBLEM_EXTENSION_KEYS.has(key))), secrets, 0);
140
+ const problem = {
141
141
  ...extensions,
142
- type: safeString(body.type, secrets),
143
- title: safeString(body.title, secrets),
144
- status: body.status,
145
- detail: safeString(body.detail, secrets),
146
- ...(body.instance === undefined
147
- ? {}
148
- : { instance: safeString(body.instance, secrets) }),
149
- is_retriable: body.is_retriable,
150
- trace_id: safeString(body.trace_id, secrets),
151
- ...(suggestions === undefined ? {} : { suggestions }),
152
- ...(typeof body.doc_uri === "string"
153
- ? { doc_uri: safeString(body.doc_uri, secrets) }
154
- : {}),
155
- ...(issues === undefined ? {} : { issues }),
142
+ detail: safeString(detail, secrets),
143
+ is_retriable: retriable,
144
+ status,
145
+ title: safeString(title, secrets),
146
+ trace_id: safeString(traceId, secrets),
147
+ type: safeString(type, secrets),
156
148
  };
149
+ if (instance !== undefined) {
150
+ problem.instance = safeString(instance, secrets);
151
+ }
152
+ const suggestions = sanitizeSuggestions(body.suggestions, secrets);
153
+ if (suggestions !== undefined) {
154
+ problem.suggestions = suggestions;
155
+ }
156
+ if (isJsonString(docUri)) {
157
+ problem.doc_uri = safeString(docUri, secrets);
158
+ }
159
+ const issues = sanitizeIssues(body.issues, secrets);
160
+ if (issues !== undefined) {
161
+ problem.issues = issues;
162
+ }
163
+ return problem;
157
164
  };
158
165
  const normalizeSimple = (body, secrets) => {
159
- if (!isRecord(body)) {
166
+ if (!isJsonObject(body)) {
160
167
  return undefined;
161
168
  }
162
- const issues = sanitizeIssues(body.issues, secrets);
169
+ const { code, error, message } = body;
170
+ let simple;
163
171
  // `{ code, message }` is the vector routes' error body; keep its code.
164
- if (typeof body.error !== "string" &&
165
- typeof body.code === "string" &&
166
- typeof body.message === "string") {
167
- return {
168
- code: safeString(body.code, secrets),
169
- error: safeString(body.message, secrets),
170
- ...(issues === undefined ? {} : { issues }),
172
+ if (!isJsonString(error) && isJsonString(code) && isJsonString(message)) {
173
+ simple = {
174
+ code: safeString(code, secrets),
175
+ error: safeString(message, secrets),
171
176
  };
172
177
  }
173
- if (typeof body.error !== "string") {
178
+ else if (isJsonString(error)) {
179
+ simple = { error: safeString(error, secrets) };
180
+ }
181
+ else {
174
182
  return undefined;
175
183
  }
176
- return {
177
- error: safeString(body.error, secrets),
178
- ...(issues === undefined ? {} : { issues }),
179
- };
184
+ const issues = sanitizeIssues(body.issues, secrets);
185
+ if (issues !== undefined) {
186
+ simple.issues = issues;
187
+ }
188
+ return simple;
180
189
  };
181
190
  const unexpectedMessage = (status, responseText, secrets) => {
182
191
  const sanitized = redact(responseText, secrets)
@@ -251,8 +260,9 @@ export class MaterialGraphError extends Error {
251
260
  }
252
261
  static fromResponse(input) {
253
262
  const secrets = secretVariants(input.apiKey, input.queryValues);
254
- const problem = normalizeProblem(input.body, secrets);
255
- const simple = problem === undefined ? normalizeSimple(input.body, secrets) : undefined;
263
+ const body = toJsonValue(input.body);
264
+ const problem = normalizeProblem(body, secrets);
265
+ const simple = problem === undefined ? normalizeSimple(body, secrets) : undefined;
256
266
  const retriable = RETRIABLE_STATUS.has(input.status);
257
267
  const metadata = requestMetadata(input);
258
268
  if (input.status === 429) {
@@ -12,7 +12,7 @@
12
12
  /** The closed sector vocabulary co-specification evidence is tagged with. */
13
13
  export type SampledTogetherProjectType = "Workplace" | "Residential" | "Institution Education" | "Hospitality: Hotel" | "Mixed-Use & Multi-Unit Residential" | "Healthcare" | "Hospitality: Restaurant" | "Government" | "Senior Living" | "Retail" | "Other" | "Sports Entertainment" | "Wellness" | "Medical Dental" | "Aviation" | "Industrial" | "Tech" | "Transportation" | "Single Family" | "Condominium Cooperative" | "Marine" | "Power & Energy" | "University Design Program: Interior Design" | "Non-Billable" | "University Design Program: Interior Architecture" | "University Design Program: Architecture" | "Historic Preservation";
14
14
  /** At least one of `variantIds` and `materials`, at most twelve together. */
15
- export type FindSampledTogetherInput = {
15
+ export interface FindSampledTogetherInput {
16
16
  /** Optional row fields: image adds imageUrl, color adds swatches. */
17
17
  include?: ("image" | "color")[];
18
18
  /** Materials to return, default 20, at most 100. */
@@ -23,8 +23,8 @@ export type FindSampledTogetherInput = {
23
23
  projectType?: SampledTogetherProjectType;
24
24
  /** Variants to find sampled-together materials for. */
25
25
  variantIds?: string[];
26
- };
27
- export type FindSampledTogetherRow = {
26
+ }
27
+ export interface FindSampledTogetherRow {
28
28
  brand: string;
29
29
  /** Palette likeness to the seeds. Present with `include: ["color"]`; never reorders. */
30
30
  colorFit?: number | null;
@@ -49,8 +49,8 @@ export type FindSampledTogetherRow = {
49
49
  /** The trimmed source variant label; null when unknown or for a paint color sample. Separate from surface finish, color family and derived color names. */
50
50
  variantLabel: string | null;
51
51
  variantName: string | null;
52
- };
53
- export type FindSampledTogetherOutput = {
52
+ }
53
+ export interface FindSampledTogetherOutput {
54
54
  /** Other brands first; within each, product type groups in turn, each group best first (seeds matched, then lift weighted by projects); one row per product. */
55
55
  sampledTogether: FindSampledTogetherRow[];
56
56
  /** The seeds as resolved, so a caller sees what a name became. */
@@ -71,4 +71,4 @@ export type FindSampledTogetherOutput = {
71
71
  excludedBySearchCatalogSubset?: string[];
72
72
  /** References that matched no variant. */
73
73
  unresolved: string[];
74
- };
74
+ }
@@ -15,7 +15,7 @@ import type { ImageReferenceInput } from "./image-reference-types.js";
15
15
  import type { SearchCatalogSubset, SearchCatalogSubsetReceipt } from "./search-catalog-subset-types.js";
16
16
  export type FindVariantsByColorMode = "contains" | "palette" | "rerank";
17
17
  /** One color condition. `role` defaults to `required`; every other field defaults to absent/false/0. */
18
- export type ColorConditionInput = {
18
+ export interface ColorConditionInput {
19
19
  /** Name, code or hex (e.g. "sage green"). */
20
20
  color?: string;
21
21
  /** Must be the swatch's largest color. */
@@ -25,8 +25,8 @@ export type ColorConditionInput = {
25
25
  share?: number;
26
26
  /** Maximum share, e.g. an accent. */
27
27
  shareMax?: number;
28
- };
29
- export type FindVariantsByColorInput = {
28
+ }
29
+ export interface FindVariantsByColorInput {
30
30
  /** Brand slugs or names to filter to. */
31
31
  brands?: string[];
32
32
  /** One entry per color (exactly one for rerank). */
@@ -45,8 +45,8 @@ export type FindVariantsByColorInput = {
45
45
  targetVariantId?: string;
46
46
  /** Ids to reorder by color (rerank). */
47
47
  variantIds?: string[];
48
- };
49
- export type ColorConditionReceipt = {
48
+ }
49
+ export interface ColorConditionReceipt {
50
50
  /** CIEDE2000 difference to the assigned settled swatch. Never a probability. */
51
51
  distance: number;
52
52
  hex: string;
@@ -56,8 +56,8 @@ export type ColorConditionReceipt = {
56
56
  proportion: number;
57
57
  /** Requested share used for ColorVec retrieval, palette only; null otherwise. */
58
58
  target: number | null;
59
- };
60
- export type PaintCatalogVariant = {
59
+ }
60
+ export interface PaintCatalogVariant {
61
61
  brand: string;
62
62
  color: string | null;
63
63
  imageUrl: string | null;
@@ -65,8 +65,8 @@ export type PaintCatalogVariant = {
65
65
  product: string;
66
66
  variantId: string;
67
67
  variantName: string | null;
68
- };
69
- export type ResolvedColor = {
68
+ }
69
+ export interface ResolvedColor {
70
70
  /** Other meanings of the same words, offered: ColorScope's color when a paint's exact name led. */
71
71
  alternatives?: ColorAlternative[];
72
72
  catalogVariant: PaintCatalogVariant | null;
@@ -76,21 +76,21 @@ export type ResolvedColor = {
76
76
  kind: "point" | "region";
77
77
  /** Image-extracted or stored target-variant swatch share; else null. */
78
78
  share: number | null;
79
- };
80
- export type ColorAlternative = {
79
+ }
80
+ export interface ColorAlternative {
81
81
  code?: string;
82
82
  hex: string;
83
83
  libraryId: string;
84
84
  libraryName: string;
85
85
  name: string;
86
86
  query: string;
87
- };
88
- export type UnresolvedColor = {
87
+ }
88
+ export interface UnresolvedColor {
89
89
  alternatives: ColorAlternative[];
90
90
  index: number;
91
91
  input: string | null;
92
- };
93
- export type ColorVariantMatch = {
92
+ }
93
+ export interface ColorVariantMatch {
94
94
  /** The trimmed source variant label; null when unknown or for a paint color sample. Separate from surface finish, color family and derived color names. */
95
95
  variantLabel: string | null;
96
96
  productType: {
@@ -116,8 +116,8 @@ export type ColorVariantMatch = {
116
116
  productId: string;
117
117
  variantId: string;
118
118
  variantName: string;
119
- };
120
- export type ColorPageInfo = {
119
+ }
120
+ export interface ColorPageInfo {
121
121
  endCursor: string | null;
122
122
  hasNextPage: boolean;
123
123
  /**
@@ -128,15 +128,15 @@ export type ColorPageInfo = {
128
128
  * the page where paging stops at the pool's 400 cap.
129
129
  */
130
130
  poolTruncated: boolean;
131
- };
132
- type ContainsOrPaletteResult = {
131
+ }
132
+ interface ContainsOrPaletteResult {
133
133
  /** Color-matched candidates dropped by `list_variants`' hard filters or eligibility. */
134
134
  gatedOut: number;
135
135
  matches: ColorVariantMatch[];
136
136
  pageInfo: ColorPageInfo;
137
137
  resolvedColors: ResolvedColor[];
138
138
  unresolvedColors: UnresolvedColor[];
139
- };
139
+ }
140
140
  export type FindVariantsByColorContainsResult = ContainsOrPaletteResult & {
141
141
  mode: "contains";
142
142
  };
@@ -144,7 +144,7 @@ export type FindVariantsByColorPaletteResult = ContainsOrPaletteResult & {
144
144
  mode: "palette";
145
145
  };
146
146
  /** `reference.candidates`/`reference.seed`'s shape — mirrors `compare-color-similarity.ts`'s own seed, unlike `matches[]` below. */
147
- export type ColorRankedSeed = {
147
+ export interface ColorRankedSeed {
148
148
  brand: string;
149
149
  brandSlug: string;
150
150
  color: string | null;
@@ -155,9 +155,9 @@ export type ColorRankedSeed = {
155
155
  variantId: string;
156
156
  /** The variant's own name — paint colorway names live here (label contract). */
157
157
  variantName: string | null;
158
- };
158
+ }
159
159
  /** RERANK's own card — the same shape `ColorVariantMatch` above (`brandId`, `code`), plus its perceptual distance. */
160
- export type ColorRankMatch = {
160
+ export interface ColorRankMatch {
161
161
  /** The trimmed source variant label; null when unknown or for a paint color sample. Separate from surface finish, color family and derived color names. */
162
162
  variantLabel: string | null;
163
163
  brand: string;
@@ -171,13 +171,13 @@ export type ColorRankMatch = {
171
171
  productId: string;
172
172
  variantId: string;
173
173
  variantName: string | null;
174
- };
175
- export type ResolvedColorReference = {
174
+ }
175
+ export interface ResolvedColorReference {
176
176
  hex: string;
177
177
  name: string | null;
178
178
  ref: string;
179
- };
180
- export type CompareColorSimilarityReference = {
179
+ }
180
+ export interface CompareColorSimilarityReference {
181
181
  /** Best-per-brand candidates when `resolution === "ambiguous"`; empty otherwise. */
182
182
  candidates: ColorRankedSeed[];
183
183
  /** The resolved color when the reference was a `color`, else null. */
@@ -197,8 +197,8 @@ export type CompareColorSimilarityReference = {
197
197
  resolution: "resolved" | "ambiguous";
198
198
  /** The resolved seed when the reference was a `material`/`variantId`, else null. */
199
199
  seed: ColorRankedSeed | null;
200
- };
201
- export type FindVariantsByColorRerankResult = {
200
+ }
201
+ export interface FindVariantsByColorRerankResult {
202
202
  /** Present ONLY when the request narrowed it. ABSENT means the whole graph. */
203
203
  searchCatalogSubset?: SearchCatalogSubsetReceipt;
204
204
  /** Real variant ids outside `searchCatalogSubset` or the materials catalog. Present when a scope narrowed or any was excluded. */
@@ -212,6 +212,6 @@ export type FindVariantsByColorRerankResult = {
212
212
  resolvedColors: ResolvedColor[];
213
213
  /** Candidate ids that did not resolve to any variant. */
214
214
  unresolved: string[];
215
- };
215
+ }
216
216
  export type FindVariantsByColorOutput = FindVariantsByColorContainsResult | FindVariantsByColorPaletteResult | FindVariantsByColorRerankResult;
217
217
  export {};
@@ -12,14 +12,14 @@ import type { FilterGroups, VariantCard } from "./list-variants-types.js";
12
12
  import type { SearchCatalogSubset } from "./search-catalog-subset-types.js";
13
13
  export declare const FIND_VARIANTS_BY_VECTOR_MODES: readonly ["text", "image", "text_and_image"];
14
14
  export type FindVariantsByVectorMode = (typeof FIND_VARIANTS_BY_VECTOR_MODES)[number];
15
- export type FindVariantsByVectorQuery = {
15
+ export interface FindVariantsByVectorQuery {
16
16
  /** One to six image references, each with exactly one field. Several are combined into one query, and every seed image's variant is left out. Only for image modes. Each image that has to be embedded spends one of the embed requests. */
17
17
  images?: ImageReferenceInput[];
18
18
  mode: FindVariantsByVectorMode;
19
19
  /** Description, mood words included. Only for text modes. */
20
20
  text?: string;
21
- };
22
- export type FindVariantsByVectorInput = {
21
+ }
22
+ export interface FindVariantsByVectorInput {
23
23
  /** Attribute or block ids to add. */
24
24
  attributes?: string[];
25
25
  /** Variant ids to leave out. A seed image's own variant is always left out. */
@@ -30,26 +30,26 @@ export type FindVariantsByVectorInput = {
30
30
  query: FindVariantsByVectorQuery;
31
31
  /** Only this corpus scope. Absent or all: every variant. */
32
32
  searchCatalogSubset?: SearchCatalogSubset;
33
- };
33
+ }
34
34
  export type FindVariantsByVectorItem = Omit<VariantCard, "materialBankId"> & {
35
35
  /** The variant's Material Bank SKU; null means none is recorded. Always present here, unlike `list_variants`' optional field. */
36
36
  materialBankId: string | null;
37
37
  /** Voyage cosine to the query. Ranks only; not a probability. */
38
38
  similarity: number;
39
39
  };
40
- export type FindVariantsByVectorReference = {
40
+ export interface FindVariantsByVectorReference {
41
41
  id: string;
42
42
  kind: "url" | "assetId" | "variantId" | "sourceId";
43
- };
44
- export type FindVariantsByVectorScan = {
43
+ }
44
+ export interface FindVariantsByVectorScan {
45
45
  /** True when no further candidates match; false means more may exist. */
46
46
  exhausted: boolean;
47
47
  read: number;
48
48
  rejected: number;
49
49
  /** How filters reached the vector search. Always "metadata": every filter runs inside the kNN. */
50
50
  route: "lane" | "metadata" | "allowlist";
51
- };
52
- export type FindVariantsByVectorTimings = {
51
+ }
52
+ export interface FindVariantsByVectorTimings {
53
53
  embedMs: number;
54
54
  /** Time spent filtering outside the kNN. Always 0: filters run inside it. */
55
55
  filterMs: number;
@@ -57,8 +57,8 @@ export type FindVariantsByVectorTimings = {
57
57
  totalMs: number;
58
58
  vectorMs: number;
59
59
  verifyMs: number;
60
- };
61
- export type FindVariantsByVectorOutput = {
60
+ }
61
+ export interface FindVariantsByVectorOutput {
62
62
  /** Explicit `{field: "variant", op: "in"}` filter ids that name a real variant outside the materials catalog — present only when at least one did. */
63
63
  excludedBySearchCatalogSubset?: string[];
64
64
  items: FindVariantsByVectorItem[];
@@ -71,6 +71,6 @@ export type FindVariantsByVectorOutput = {
71
71
  timings: FindVariantsByVectorTimings;
72
72
  /** Explicit `{field: "variant", op: "in"}` filter ids that named no eligible variant — present only when at least one did. */
73
73
  unresolvedVariantIds?: string[];
74
- /** Filtered attributes no variant in scope states at all. Present only on an empty page, so its absence means the empty page is a genuine no-match. */
74
+ /** Filter fields no variant in scope states at all. Present only on an empty page, so its absence means the empty page is a genuine no-match. */
75
75
  unstated?: string[];
76
- };
76
+ }
@@ -7,22 +7,24 @@
7
7
  * `__tests__/contract-parity.test.ts` keeps the mirror honest against the
8
8
  * server types.
9
9
  */
10
- import type { ListVariantsNotApplicable, ListVariantsSpecifications, SampleSubject } from "./list-variants-types.js";
10
+ import type { ListVariantsNotApplicable, ListVariantsSpecifications, SampleSubject, VariantObservations } from "./list-variants-types.js";
11
11
  import type { VariantColorPalette } from "./material-block-types.js";
12
12
  /** The detail to add under `specifications`; absent, the answer is the compact item alone. */
13
- export type GetVariantOptions = {
13
+ export interface GetVariantOptions {
14
14
  /** Canonical attribute ids, e.g. `["nrc"]`. */
15
15
  attributes?: readonly string[];
16
16
  /** Canonical block ids, returned whole, e.g. `["acoustic"]`. */
17
17
  blocks?: readonly string[];
18
+ /** Observation attribute ids, e.g. `["sample_size"]`, returned under `observations` by channel. */
19
+ observations?: readonly string[];
18
20
  /** Add `ifc`: the IFC 4.3 manufacturer property set and classification references. */
19
21
  ifc?: boolean;
20
- };
22
+ }
21
23
  /**
22
24
  * The variant as an IFC 4.3 type states it. Properties the record does not
23
25
  * hold are left out, so an absent property is unknown.
24
26
  */
25
- export type GetVariantIfc = {
27
+ export interface GetVariantIfc {
26
28
  propertySets: {
27
29
  name: "Pset_ManufacturerTypeInformation";
28
30
  properties: {
@@ -39,13 +41,13 @@ export type GetVariantIfc = {
39
41
  name?: string;
40
42
  edition?: string;
41
43
  }[];
42
- };
44
+ }
43
45
  /**
44
46
  * One variant: the compact identity, and `specifications` only for the
45
47
  * attributes and blocks that were asked for. Only stated values appear there:
46
48
  * an unknown value, and a block with none, is absent, never false or zero.
47
49
  */
48
- export type GetVariantOutput = {
50
+ export interface GetVariantOutput {
49
51
  /** Whether this variant may appear in its Brand's catalog. Material-only lists also require Search Catalog membership. Reads by id include ineligible variants. */
50
52
  eligible: boolean;
51
53
  channels: string[];
@@ -98,6 +100,8 @@ export type GetVariantOutput = {
98
100
  variantLabel: string | null;
99
101
  /** Requested blocks or attributes the variant's product type does not carry. */
100
102
  notApplicable?: ListVariantsNotApplicable;
103
+ /** Present when `observations` was asked for and the variant has any. */
104
+ observations?: VariantObservations;
101
105
  /** Present when `ifc` was asked for. */
102
106
  ifc?: GetVariantIfc;
103
107
  product: {
@@ -113,4 +117,4 @@ export type GetVariantOutput = {
113
117
  };
114
118
  url: string | null;
115
119
  variantId: string;
116
- };
120
+ }