@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
@@ -1,37 +1,38 @@
1
1
  import type { RateLimit } from "./errors.js";
2
+ import type { JsonValue } from "./json-value-types.js";
2
3
  import type { JsonOperationId, OperationResponses } from "./operation-responses.js";
3
4
  import type { SdkOperation } from "./operations.js";
4
5
  type QueryValue = string | number | boolean | undefined | null | (string | number | boolean)[];
5
6
  export type QueryParams = Record<string, QueryValue>;
6
- export type RequestOptions = {
7
+ export interface RequestOptions {
7
8
  /** Required by state-changing operations that support safe retries. */
8
9
  idempotencyKey?: string;
9
10
  query?: QueryParams;
10
11
  body?: unknown;
11
12
  signal?: AbortSignal;
12
13
  pathParams?: Readonly<Record<string, string>>;
13
- };
14
+ }
14
15
  /** A read, status-checked HTTP response with its parsed body. */
15
- export type SentResponse = {
16
+ export interface SentResponse {
16
17
  response: Response;
17
18
  text: string;
18
- parsed: unknown;
19
- };
20
- type SendInput = {
19
+ parsed: JsonValue | undefined;
20
+ }
21
+ interface SendInput {
21
22
  method: string;
22
23
  url: string;
23
24
  headers: Record<string, string>;
24
25
  /** The only bodies the SDK sends: a JSON string or a multipart `FormData`. */
25
26
  body?: string | FormData;
26
27
  signal?: AbortSignal;
27
- };
28
- export type HttpTransportOptions = {
28
+ }
29
+ export interface HttpTransportOptions {
29
30
  apiKey: string;
30
31
  baseUrl: string;
31
32
  fetch: typeof fetch;
32
33
  /** Deadline in milliseconds for each JSON or upload request. Not applied to streams. */
33
34
  timeoutMs?: number;
34
- };
35
+ }
35
36
  export declare class HttpTransport {
36
37
  #private;
37
38
  readonly baseUrl: string;
@@ -40,7 +41,10 @@ export declare class HttpTransport {
40
41
  constructor(options: HttpTransportOptions);
41
42
  get lastRateLimit(): RateLimit | null;
42
43
  /** The bearer + JSON `Accept` headers, plus any per-request additions. */
43
- authHeaders(extra?: Record<string, string>): Record<string, string>;
44
+ authHeaders(extra?: Record<string, string>): {
45
+ Accept: string;
46
+ Authorization: string;
47
+ };
44
48
  buildUrl(path: string, query?: QueryParams): string;
45
49
  /**
46
50
  * Send one request, capture rate limits, read the body, and map a non-2xx or
package/http-transport.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * failure surfaces as one {@link MaterialGraphError}.
7
7
  */
8
8
  import { MaterialGraphError } from "./errors.js";
9
+ import { parseJson } from "./json.js";
9
10
  import { operationPath } from "./operations.js";
10
11
  import { parseOperationResponse } from "./parse-operation-response.js";
11
12
  const parseNumberOrNull = (value) => {
@@ -106,11 +107,11 @@ export class HttpTransport {
106
107
  signal,
107
108
  });
108
109
  }
109
- catch (cause) {
110
+ catch (error) {
110
111
  throw MaterialGraphError.fromTransport({
111
112
  ...metadata,
112
113
  callerSignal: signal,
113
- cause,
114
+ cause: error,
114
115
  });
115
116
  }
116
117
  const rateLimit = parseRateLimit(response.headers);
@@ -119,17 +120,17 @@ export class HttpTransport {
119
120
  try {
120
121
  text = await response.text();
121
122
  }
122
- catch (cause) {
123
+ catch (error) {
123
124
  throw MaterialGraphError.fromTransport({
124
125
  ...metadata,
125
126
  callerSignal: signal,
126
- cause,
127
+ cause: error,
127
128
  });
128
129
  }
129
130
  let parsed;
130
131
  if (text.length > 0) {
131
132
  try {
132
- parsed = JSON.parse(text);
133
+ parsed = parseJson(text);
133
134
  }
134
135
  catch {
135
136
  parsed = text;
@@ -152,15 +153,16 @@ export class HttpTransport {
152
153
  async requestJson(sdkOperation, options = {}) {
153
154
  const url = this.buildUrl(operationPath(sdkOperation, options.pathParams), options.query);
154
155
  const hasBody = options.body !== undefined;
156
+ const extraHeaders = {};
157
+ if (hasBody) {
158
+ extraHeaders["Content-Type"] = "application/json";
159
+ }
160
+ if (options.idempotencyKey !== undefined && options.idempotencyKey !== "") {
161
+ extraHeaders["Idempotency-Key"] = options.idempotencyKey;
162
+ }
155
163
  const { parsed } = await this.send({
156
164
  body: hasBody ? JSON.stringify(options.body) : undefined,
157
- headers: this.authHeaders({
158
- ...(hasBody ? { "Content-Type": "application/json" } : {}),
159
- ...(options.idempotencyKey !== undefined &&
160
- options.idempotencyKey !== ""
161
- ? { "Idempotency-Key": options.idempotencyKey }
162
- : {}),
163
- }),
165
+ headers: this.authHeaders(extraHeaders),
164
166
  method: sdkOperation.method,
165
167
  signal: options.signal,
166
168
  url,
@@ -6,7 +6,7 @@
6
6
  * or an existing variant's own image. Exactly one field is set per call; the
7
7
  * server refuses more than one.
8
8
  */
9
- export type ImageReferenceInput = {
9
+ export interface ImageReferenceInput {
10
10
  /** Catalog asset id. */
11
11
  assetId?: string;
12
12
  /** Upload id (`smf_...`) from source-material. */
@@ -15,4 +15,4 @@ export type ImageReferenceInput = {
15
15
  url?: string;
16
16
  /** Variant whose image to use. */
17
17
  variantId?: string;
18
- };
18
+ }
package/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type { DiscoveryReference, DiscoveryTerms, InterpretDiscoveryTermsInput, ColorlessVariantsResult, VariantSibling, } from "./discovery-context-types.js";
2
2
  export { MaterialGraphClient, DEFAULT_BASE_URL } from "./client.js";
3
3
  export type { MaterialGraphClientOptions } from "./client.js";
4
- export type { FilterOp, FilterTermCondition, FilterNumericCondition, FilterPresenceCondition, FilterAnyFieldCondition, FilterCondition, FilterGroups, OptionalFilterGroups, OrderByEntry, ListVariantsInclude, ListVariantsCollapse, ColorRegion, ColorRangeCondition, ListVariantsInput, ListVariantsPreference, ListVariantsSpecifications, ListVariantsNotApplicable, SampleSubject, ListVariantsItem, ListVariantsOutput, VariantCard, } from "./list-variants-types.js";
4
+ export type { FilterOp, FilterTermCondition, FilterNumericCondition, FilterPresenceCondition, FilterAnyFieldCondition, FilterCondition, FilterGroups, OptionalFilterGroups, OrderByEntry, ListVariantsInclude, ListVariantsCollapse, ColorRegion, ColorRangeCondition, ListVariantsInput, ListVariantsPreference, ListVariantsSpecifications, ListVariantsNotApplicable, SampleSizeObservation, VariantObservations, SampleSubject, ListVariantsItem, ListVariantsOutput, VariantCard, } from "./list-variants-types.js";
5
5
  export type { ListVariantFacetsInput, VariantFacetValue, CategoricalVariantFacet, BooleanVariantFacet, NumericVariantFacet, VariantFacet, ListVariantFacetsOutput, } from "./list-variant-facets-types.js";
6
6
  export type { SearchVariantsSelection, SearchVariantsInput, SearchVariantsUnderstood, SearchVariantsReading, SearchVariantsSuggestion, SearchVariantsChoice, SearchVariantsOutput, } from "./search-variants-types.js";
7
7
  export type { ListEnumValuesOptions, EnumValueMatch, EnumValueItem, ListEnumValuesResult, } from "./list-enum-values-types.js";
@@ -27,7 +27,7 @@ export type { ColorsTransport } from "./colors-client.js";
27
27
  export type { CategoryTier, CategoryColor, CategoryColorProfile, CategoryColorProfilesResponse, ColorProfilesParams, } from "./color-profiles-types.js";
28
28
  export type { DeclaredProvenance, DeclaredAssociationSubject, DeclaredAssociation, DeclaredAssociationsResponse, AssociatedOptions, ApplicabilitySubject, ApplicabilityCoverageState, ApplicabilityCoverage, ApplicabilityFact, ApplicabilitySourceEntry, UnresolvedApplicabilityEndpoint, ApplicabilityResponse, ApplicabilityOptions, } from "./association-types.js";
29
29
  export type { BrandsOptions, BrandItem, ListBrandsPage, } from "./brand-types.js";
30
- export type { VariantAssetAvailability, VariantImageRole, VariantImagePresentation, VariantDocumentType, VariantAssetKind, VariantAssetItem, VariantDocumentCoverageState, VariantAssetPageInfo, ListVariantAssetsOptions, VariantAssetPage, MaterialDocumentStatus, MaterialDocumentCoverageState, MaterialDocumentScope, MaterialDocumentSource, MaterialDocumentProjectionItem, MaterialDocumentCoverageSource, MaterialDocumentCoverage, MaterialDocumentCoverageSummary, MaterialDocumentPageOptions, MaterialDocumentPage, } from "./material-asset-types.js";
30
+ export type { VariantAssetAvailability, VariantImageRole, VariantImagePresentation, VariantImageShows, VariantImageSurfaceMm, VariantDocumentType, VariantAssetKind, VariantAssetItem, VariantDocumentCoverageState, VariantAssetPageInfo, ListVariantAssetsOptions, VariantAssetPage, MaterialDocumentStatus, MaterialDocumentCoverageState, MaterialDocumentScope, MaterialDocumentSource, MaterialDocumentProjectionItem, MaterialDocumentCoverageSource, MaterialDocumentCoverage, MaterialDocumentCoverageSummary, MaterialDocumentPageOptions, MaterialDocumentPage, } from "./material-asset-types.js";
31
31
  export type { MaterialCoverageStatus, MaterialBlockStatus, MaterialBlockFieldStatus, MaterialBlockFieldValue, MaterialBlockProjectionField, MaterialBlockProjectionItem, MaterialBlockProjection, VariantColorPalette, } from "./material-block-types.js";
32
32
  export type { SchemaResource, SCHEMA_RESOURCE_OPERATION_IDS, SchemaResourceRequest, SchemaLifecycle, SchemaExternalMapping, SchemaMatchStrength, SchemaValueDictionaryTerm, SchemaValueDictionaryItem, SchemaResourceItemMap, SchemaResourceResponse, } from "./schema-resource-types.js";
33
33
  export type { NearestPaintsParams, NearestPaintCard, NearestPaintsHexResult, NearestPaintsOutput, } from "./nearest-paints-types.js";
package/json.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { JsonValue } from "./json-value-types.js";
2
+ /** A JSON object: string keys, JSON values. */
3
+ export interface JsonObject {
4
+ [key: string]: JsonValue;
5
+ }
6
+ export type JsonKind = "array" | "boolean" | "null" | "number" | "object" | "string" | "undefined";
7
+ /** What a parsed JSON value is, or `undefined` for a key an object lacks. */
8
+ export declare const jsonKind: (value: JsonValue | undefined) => JsonKind;
9
+ export declare const isJsonObject: (value: JsonValue | undefined) => value is JsonObject;
10
+ export declare const isJsonString: (value: JsonValue | undefined) => value is string;
11
+ export declare const isJsonNumber: (value: JsonValue | undefined) => value is number;
12
+ /** Parses JSON text; throws on text that is not JSON. */
13
+ export declare const parseJson: (text: string) => JsonValue;
14
+ /**
15
+ * Reads a value of unknown origin as the JSON it would serialize to. A value
16
+ * JSON cannot represent, such as a `bigint`, reads as absent.
17
+ */
18
+ export declare const toJsonValue: (value: unknown) => JsonValue | undefined;
package/json.js ADDED
@@ -0,0 +1,36 @@
1
+ /** What a parsed JSON value is, or `undefined` for a key an object lacks. */
2
+ export const jsonKind = (value) => {
3
+ if (value === null) {
4
+ return "null";
5
+ }
6
+ if (Array.isArray(value)) {
7
+ return "array";
8
+ }
9
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- This is the one place a parsed JSON value is classified; every other check calls it.
10
+ const kind = typeof value;
11
+ if (kind === "boolean" || kind === "number" || kind === "string") {
12
+ return kind;
13
+ }
14
+ return kind === "undefined" ? "undefined" : "object";
15
+ };
16
+ export const isJsonObject = (value) => jsonKind(value) === "object";
17
+ export const isJsonString = (value) => jsonKind(value) === "string";
18
+ export const isJsonNumber = (value) => jsonKind(value) === "number";
19
+ /** Parses JSON text; throws on text that is not JSON. */
20
+ export const parseJson = (text) =>
21
+ // oxlint-disable-next-line typescript/no-unsafe-return -- `JSON.parse` returns exactly the values `JsonValue` names.
22
+ JSON.parse(text);
23
+ /**
24
+ * Reads a value of unknown origin as the JSON it would serialize to. A value
25
+ * JSON cannot represent, such as a `bigint`, reads as absent.
26
+ */
27
+ // oxlint-disable-next-line anti-slop/no-unknown-parameters -- A published caller may pass any body; this is where it becomes JSON.
28
+ export const toJsonValue = (value) => {
29
+ try {
30
+ const text = JSON.stringify(value);
31
+ return text === undefined ? undefined : parseJson(text);
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ };
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import type { OpenEnum } from "./types.js";
10
10
  /** Options for `listEnumValues()` (GET `/api/v1/catalog/enum-values`). */
11
- export type ListEnumValuesOptions = {
11
+ export interface ListEnumValuesOptions {
12
12
  /**
13
13
  * A value dictionary id, from a `listAttributes()` item's `dictionaryId`.
14
14
  * Required without a `query`; with a `query`, omit it to search every
@@ -21,9 +21,9 @@ export type ListEnumValuesOptions = {
21
21
  limit?: number;
22
22
  /** `pageInfo.endCursor` from the previous page, with the same query and dictionary. */
23
23
  cursor?: string;
24
- };
24
+ }
25
25
  /** How an item was found. A distance or similarity compares only within one method. */
26
- export type EnumValueMatch = {
26
+ export interface EnumValueMatch {
27
27
  method: OpenEnum<"exact" | "alias" | "fuzzy" | "vector">;
28
28
  /** The id, label or alias that matched, or null for a match by meaning. */
29
29
  matchedText: string | null;
@@ -33,9 +33,9 @@ export type EnumValueMatch = {
33
33
  resolvedFrom?: string;
34
34
  /** Cosine similarity, 1 best. */
35
35
  similarity?: number;
36
- };
36
+ }
37
37
  /** One allowed value of a dictionary. */
38
- export type EnumValueItem = {
38
+ export interface EnumValueItem {
39
39
  /** The attributes this value can filter `listVariants()` on; empty when it cannot be filtered on. */
40
40
  attributeIds: string[];
41
41
  /** The taxonomy's definition when the value resolves to a canonical concept, otherwise null. */
@@ -46,8 +46,8 @@ export type EnumValueItem = {
46
46
  match?: EnumValueMatch;
47
47
  /** The bare term id `listVariants()` accepts for any of `attributeIds`. */
48
48
  valueId: string;
49
- };
50
- export type ListEnumValuesResult = {
49
+ }
50
+ export interface ListEnumValuesResult {
51
51
  /** The dictionary the page was read from; null when a query searched every dictionary. */
52
52
  dictionaryId: string | null;
53
53
  items: EnumValueItem[];
@@ -55,4 +55,4 @@ export type ListEnumValuesResult = {
55
55
  endCursor: string | null;
56
56
  hasNextPage: boolean;
57
57
  };
58
- };
58
+ }
@@ -5,15 +5,15 @@
5
5
  * `packages/sampling/src/sampled-together-pairs.ts`; this SDK has no runtime
6
6
  * dependency on `@materialgraph/sampling`, so the shapes are copied here.
7
7
  */
8
- export type ListSampledTogetherPairsInput = {
8
+ export interface ListSampledTogetherPairsInput {
9
9
  /** `nextCursor` from the previous page. */
10
10
  cursor?: string;
11
11
  /** Pairs per page, at most 5,000. Default 2,000. */
12
12
  limit?: number;
13
13
  /** Smallest project count a pair needs. Default 3. */
14
14
  minProjects?: number;
15
- };
16
- export type SampledTogetherPair = {
15
+ }
16
+ export interface SampledTogetherPair {
17
17
  a: string;
18
18
  b: string;
19
19
  /** Projects that sampled both. */
@@ -25,8 +25,8 @@ export type SampledTogetherPair = {
25
25
  /** Each side's own sampled project total; null when unknown. */
26
26
  aProjects: number | null;
27
27
  bProjects: number | null;
28
- };
29
- export type ListSampledTogetherPairsOutput = {
28
+ }
29
+ export interface ListSampledTogetherPairsOutput {
30
30
  window: "trailing_36mo";
31
31
  signal: "sampled";
32
32
  windowStart: string | null;
@@ -36,4 +36,4 @@ export type ListSampledTogetherPairsOutput = {
36
36
  projectTotalsWindowEnd: string | null;
37
37
  pairs: SampledTogetherPair[];
38
38
  nextCursor: string | null;
39
- };
39
+ }
@@ -9,7 +9,7 @@ import type { FilterGroups } from "./list-variants-types.js";
9
9
  * `contract-parity.test.ts` keeps them honest.
10
10
  */
11
11
  import type { SearchCatalogSubset } from "./search-catalog-subset-types.js";
12
- export type ListVariantFacetsInput = {
12
+ export interface ListVariantFacetsInput {
13
13
  /** 1–10 facetable attribute ids, or `brand`. Find attribute ids with `listAttributes()`. */
14
14
  attributeIds: string[];
15
15
  /** The same kind-grouped filter tree `listVariants()` takes. */
@@ -18,20 +18,20 @@ export type ListVariantFacetsInput = {
18
18
  filters?: FilterGroups;
19
19
  /** Top values per categorical or boolean facet, 1–50. Defaults to 10. */
20
20
  valuesPerFacet?: number;
21
- };
22
- export type VariantFacetValue = {
21
+ }
22
+ export interface VariantFacetValue {
23
23
  /** The canonical value `listVariants()`' `filters` accepts for this attribute. */
24
24
  value: string;
25
25
  label: string;
26
26
  /** Distinct eligible variants under the supplied filters carrying this value. */
27
27
  variantCount: number;
28
- };
29
- type VariantFacetBase = {
28
+ }
29
+ interface VariantFacetBase {
30
30
  attributeId: string;
31
31
  name: string;
32
32
  /** Eligible variants recording no accepted value: unknown, never false or zero. */
33
33
  missingVariantCount: number;
34
- };
34
+ }
35
35
  export type CategoricalVariantFacet = VariantFacetBase & {
36
36
  kind: "categorical";
37
37
  values: VariantFacetValue[];
@@ -51,10 +51,10 @@ export type NumericVariantFacet = VariantFacetBase & {
51
51
  unitId: string | null;
52
52
  };
53
53
  export type VariantFacet = CategoricalVariantFacet | BooleanVariantFacet | NumericVariantFacet;
54
- export type ListVariantFacetsOutput = {
54
+ export interface ListVariantFacetsOutput {
55
55
  /** Eligible variants matching every hard condition: the denominator of every count. */
56
56
  matchedVariantCount: number;
57
57
  /** One per requested attribute, in request order. */
58
58
  facets: VariantFacet[];
59
- };
59
+ }
60
60
  export {};
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import type { SearchCatalogSubset } from "./search-catalog-subset-types.js";
13
13
  export type FilterOp = "in" | "not_in" | "all" | "exists" | "missing" | "eq" | "gt" | "gte" | "lt" | "lte";
14
- export type FilterTermCondition = {
14
+ export interface FilterTermCondition {
15
15
  field: string;
16
16
  op: "in" | "all" | "not_in";
17
17
  /**
@@ -26,23 +26,23 @@ export type FilterTermCondition = {
26
26
  */
27
27
  unstated?: "pass";
28
28
  values: string[];
29
- };
30
- export type FilterNumericCondition = {
29
+ }
30
+ export interface FilterNumericCondition {
31
31
  field: string;
32
32
  op: "eq" | "gt" | "gte" | "lt" | "lte";
33
33
  value: number;
34
- };
35
- export type FilterPresenceCondition = {
34
+ }
35
+ export interface FilterPresenceCondition {
36
36
  field: string;
37
37
  op: "exists" | "missing";
38
- };
38
+ }
39
39
  /**
40
40
  * A term stated under ANY of `fields` (2–8 distinct term attributes), for a
41
41
  * concept stored under several of them. `in`: at least one of `values` under
42
42
  * at least one field. `not_in`: at least one field stated at all, and none of
43
43
  * `values` under any field.
44
44
  */
45
- export type FilterAnyFieldCondition = {
45
+ export interface FilterAnyFieldCondition {
46
46
  fields: string[];
47
47
  op: "in" | "not_in";
48
48
  /**
@@ -57,7 +57,7 @@ export type FilterAnyFieldCondition = {
57
57
  */
58
58
  unstated?: "pass";
59
59
  values: string[];
60
- };
60
+ }
61
61
  /**
62
62
  * One filter condition, whichever of the four fully-required shapes. No
63
63
  * `unit` key (a numeric value is in the attribute's own canonical unit).
@@ -70,14 +70,14 @@ export type FilterCondition = FilterTermCondition | FilterNumericCondition | Fil
70
70
  * fields. Send only the arrays you need; an omitted or empty array carries no
71
71
  * conditions.
72
72
  */
73
- export type FilterGroups = {
73
+ export interface FilterGroups {
74
74
  terms?: FilterTermCondition[];
75
75
  numbers?: FilterNumericCondition[];
76
76
  presence?: FilterPresenceCondition[];
77
77
  anyField?: FilterAnyFieldCondition[];
78
- };
78
+ }
79
79
  /** `optionalFilters`: the same kind-grouped shape as `filters` but for `unstated`, with every entry also carrying a required `weight` (1–10). Ranks matched items; never excludes. */
80
- export type OptionalFilterGroups = {
80
+ export interface OptionalFilterGroups {
81
81
  terms?: (Omit<FilterTermCondition, "unstated"> & {
82
82
  weight: number;
83
83
  })[];
@@ -90,33 +90,33 @@ export type OptionalFilterGroups = {
90
90
  anyField?: (Omit<FilterAnyFieldCondition, "unstated"> & {
91
91
  weight: number;
92
92
  })[];
93
- };
94
- export type OrderByEntry = {
93
+ }
94
+ export interface OrderByEntry {
95
95
  direction: "asc" | "desc";
96
96
  /** A numeric attribute id. */
97
97
  field: string;
98
- };
98
+ }
99
99
  export type ListVariantsInclude = "image" | "url";
100
100
  export type ListVariantsCollapse = "product" | "colorway";
101
101
  /** A region in OKLCh: lightness and chroma as bounds, hue an arc in degrees that wraps when its start is past its end, null for any hue. */
102
- export type ColorRegion = {
102
+ export interface ColorRegion {
103
103
  chroma: [number, number];
104
104
  hue: [number, number] | null;
105
105
  lightness: [number, number];
106
- };
106
+ }
107
107
  /**
108
108
  * A measured-color condition. `in` keeps a variant with a large swatch inside
109
109
  * `range`; `not_in` leaves those out and lets a variant with no measured
110
110
  * color pass. `preferred` only orders and never narrows.
111
111
  */
112
- export type ColorRangeCondition = {
112
+ export interface ColorRangeCondition {
113
113
  /** The color's own point in the region, `#rrggbb`; it orders matches, nearest first. */
114
114
  hex: string;
115
115
  intent?: "required" | "preferred";
116
116
  op: "in" | "not_in";
117
117
  range: ColorRegion;
118
- };
119
- export type ListVariantsInput = {
118
+ }
119
+ export interface ListVariantsInput {
120
120
  /** Attribute ids to project under each item's `specifications`. */
121
121
  attributes?: string[];
122
122
  /**
@@ -145,27 +145,55 @@ export type ListVariantsInput = {
145
145
  * ambiguous matches are all returned rather than guessed between.
146
146
  */
147
147
  name?: string;
148
+ /** Observation attribute ids to add under each item's `observations`, e.g. `["sample_size"]`. */
149
+ observations?: string[];
148
150
  /** Rank only — never excludes. */
149
151
  optionalFilters?: OptionalFilterGroups;
150
152
  orderBy?: OrderByEntry[];
151
153
  /** Words each variant must name in its text, inside the filters. */
152
154
  words?: string[];
153
- };
155
+ }
154
156
  /**
155
157
  * `score` sums the weights of the matched `optionalFilters`; `matched` lists
156
158
  * their positions in the flattened terms-then-numbers-then-presence-then-anyField order
157
159
  * the server reads `optionalFilters` in, not the caller's own array shape.
160
+ * `looks` says how the row met each look it matched (named for it, its value
161
+ * stated, its words in the description) and by which words; `photo`, that its
162
+ * photograph looks like the look.
158
163
  */
159
- export type ListVariantsPreference = {
164
+ export interface ListVariantsPreference {
165
+ looks?: {
166
+ by: ("named" | "stated" | "described")[];
167
+ index: number;
168
+ words?: string;
169
+ }[];
160
170
  matched: number[];
171
+ photo?: true;
161
172
  score: number;
162
- };
173
+ }
163
174
  /** `{ [blockId]: { [attributeId]: value } }`. Null means the value is unknown. */
164
175
  export type ListVariantsSpecifications = Record<string, Record<string, unknown>>;
165
- export type ListVariantsNotApplicable = {
176
+ /** Material Bank's printed sample size. Any field may be absent; absent is unknown, never 0. */
177
+ export interface SampleSizeObservation {
178
+ /** The sample size as the channel printed it, verbatim. */
179
+ wording?: string;
180
+ /** The shorter side in millimeters. */
181
+ width_mm?: number;
182
+ /** The longer side in millimeters. */
183
+ length_mm?: number;
184
+ }
185
+ /**
186
+ * `{ [channel]: { [attributeId]: value } }`: the latest observation of each
187
+ * selected attribute for each channel (`no_channel` when none is named). An
188
+ * attribute with no observation is absent.
189
+ */
190
+ export type VariantObservations = Record<string, {
191
+ sample_size?: SampleSizeObservation;
192
+ } & Record<string, unknown>>;
193
+ export interface ListVariantsNotApplicable {
166
194
  attributes?: string[];
167
195
  blocks?: string[];
168
- };
196
+ }
169
197
  /** What a sample represents, independently of its specification type. */
170
198
  export type SampleSubject = "material" | "paint_color_sample" | "unknown" | "conflict";
171
199
  /**
@@ -173,7 +201,7 @@ export type SampleSubject = "material" | "paint_color_sample" | "unknown" | "con
173
201
  * and brand page only when requested (`include`) and selected attributes
174
202
  * under `specifications`. Leaner than `get_variant`'s item on purpose.
175
203
  */
176
- export type ListVariantsItem = {
204
+ export interface ListVariantsItem {
177
205
  category: {
178
206
  id: string;
179
207
  name: string;
@@ -210,6 +238,8 @@ export type ListVariantsItem = {
210
238
  /** The trimmed source variant label; null when unknown or for a paint color sample, whose own name identifies its color. Separate from surface finish, color family and derived color names. */
211
239
  variantLabel: string | null;
212
240
  notApplicable?: ListVariantsNotApplicable;
241
+ /** Present when `observations` was asked for and the variant has any. */
242
+ observations?: VariantObservations;
213
243
  preference?: ListVariantsPreference;
214
244
  product: {
215
245
  id: string;
@@ -223,7 +253,7 @@ export type ListVariantsItem = {
223
253
  */
224
254
  variantCount?: number;
225
255
  variantId: string;
226
- };
256
+ }
227
257
  /**
228
258
  * The variant card both `listVariants` rows and `findVariantsByVector` rows
229
259
  * satisfy. The only difference between the two is the Material Bank SKU:
@@ -233,7 +263,7 @@ export type VariantCard = Omit<ListVariantsItem, "materialBankId"> & {
233
263
  /** The variant's Material Bank SKU. Absent or null means none is recorded. */
234
264
  materialBankId?: string | null;
235
265
  };
236
- export type ListVariantsOutput = {
266
+ export interface ListVariantsOutput {
237
267
  /** Explicit `{field: "variant", op: "in"}` ids that name a real variant outside the materials catalog. Present only when at least one did. */
238
268
  excludedBySearchCatalogSubset?: string[];
239
269
  items: ListVariantsItem[];
@@ -252,11 +282,11 @@ export type ListVariantsOutput = {
252
282
  unavailableVariantIds?: string[];
253
283
  /** Explicit `{field: "variant", op: "in"}` ids anywhere in `filters.terms` that name no variant at all. */
254
284
  unresolvedVariantIds?: string[];
255
- /** Filtered attributes no variant in scope states at all. Present only on an empty first page, so its absence means the empty page is a genuine no-match. */
285
+ /** Filter fields no variant in scope states at all. Present only on an empty first page, so its absence means the empty page is a genuine no-match. */
256
286
  unstated?: string[];
257
287
  /** Each requested word and whether any variant in scope names it. */
258
288
  words?: {
259
289
  matched: boolean;
260
290
  word: string;
261
291
  }[];
262
- };
292
+ }