@instruments/materialgraph 0.44.0 → 0.46.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.
@@ -12,4 +12,11 @@ export type MaterialGraphClientOptions = {
12
12
  baseUrl?: string;
13
13
  /** Inject a custom `fetch` (tests, proxies). Defaults to the global `fetch`. */
14
14
  fetch?: typeof fetch;
15
+ /**
16
+ * Deadline in milliseconds for each request, composed with any signal the
17
+ * caller passes. An expired deadline throws a `MaterialGraphError` of kind
18
+ * `timeout`. It does not apply to run-event streams, which stay open until
19
+ * they end or the caller aborts. Absent means no deadline.
20
+ */
21
+ timeoutMs?: number;
15
22
  };
package/client.js CHANGED
@@ -97,6 +97,9 @@ export class MaterialGraphClient extends CatalogueClient {
97
97
  apiKey: options.apiKey,
98
98
  baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
99
99
  fetch: options.fetch ?? globalThis.fetch,
100
+ ...(options.timeoutMs === undefined
101
+ ? {}
102
+ : { timeoutMs: options.timeoutMs }),
100
103
  });
101
104
  super(transport);
102
105
  this.#transport = transport;
package/errors.d.ts CHANGED
@@ -26,6 +26,8 @@ export type ProblemDetail = {
26
26
  };
27
27
  /** Normalized residual `{ error }` body. Unknown response fields are discarded. */
28
28
  export type SimpleErrorBody = {
29
+ /** The vector and variant routes' machine-readable code, when the body is `{ code, message }`. */
30
+ code?: string;
29
31
  error: string;
30
32
  issues?: SanitizedIssue[];
31
33
  };
package/errors.js CHANGED
@@ -157,10 +157,23 @@ const normalizeProblem = (body, secrets) => {
157
157
  };
158
158
  };
159
159
  const normalizeSimple = (body, secrets) => {
160
- if (!isRecord(body) || typeof body.error !== "string") {
160
+ if (!isRecord(body)) {
161
161
  return undefined;
162
162
  }
163
163
  const issues = sanitizeIssues(body.issues, secrets);
164
+ // `{ code, message }` is the vector routes' error body; keep its code.
165
+ if (typeof body.error !== "string" &&
166
+ typeof body.code === "string" &&
167
+ typeof body.message === "string") {
168
+ return {
169
+ code: safeString(body.code, secrets),
170
+ error: safeString(body.message, secrets),
171
+ ...(issues === undefined ? {} : { issues }),
172
+ };
173
+ }
174
+ if (typeof body.error !== "string") {
175
+ return undefined;
176
+ }
164
177
  return {
165
178
  error: safeString(body.error, secrets),
166
179
  ...(issues === undefined ? {} : { issues }),
@@ -211,7 +224,12 @@ export class MaterialGraphError extends Error {
211
224
  });
212
225
  }
213
226
  static fromTransport(input) {
214
- if (input.callerSignal?.aborted === true) {
227
+ // A deadline (`AbortSignal.timeout`, or the client's `timeoutMs`) aborts
228
+ // the signal with a TimeoutError reason: that is a timeout, not a
229
+ // cancellation by the caller.
230
+ const reason = input.callerSignal?.reason;
231
+ const reasonName = reason instanceof Error ? reason.name : "";
232
+ if (input.callerSignal?.aborted === true && reasonName !== "TimeoutError") {
215
233
  return new MaterialGraphError("MaterialGraph request aborted by caller", {
216
234
  ...requestMetadata(input),
217
235
  kind: "aborted",
@@ -219,7 +237,7 @@ export class MaterialGraphError extends Error {
219
237
  });
220
238
  }
221
239
  const name = input.cause instanceof Error ? input.cause.name : "";
222
- if (name === "TimeoutError") {
240
+ if (name === "TimeoutError" || reasonName === "TimeoutError") {
223
241
  return new MaterialGraphError("MaterialGraph request timed out", {
224
242
  ...requestMetadata(input),
225
243
  kind: "timeout",
@@ -106,6 +106,8 @@ export type ColorVariantMatch = {
106
106
  coverage: number;
107
107
  imageUrl: string | null;
108
108
  hex?: string | null;
109
+ /** True for a paint color card, paint line, primer or coating: the card is the brand's declared hex, never a photo. */
110
+ hasDeclaredColor: boolean;
109
111
  product: string;
110
112
  productId: string;
111
113
  variantId: string;
@@ -8,13 +8,13 @@ import type { PaintCatalogVariant } from "./find-variants-by-color-types.js";
8
8
  * are copied here and `contract-parity.test.ts` keeps them honest.
9
9
  */
10
10
  import type { ImageReferenceInput } from "./image-reference-types.js";
11
- import type { FilterGroups, ListVariantsItem } from "./list-variants-types.js";
11
+ import type { FilterGroups, VariantCard } from "./list-variants-types.js";
12
12
  import type { SearchCatalogueSubset } from "./search-catalogue-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
15
  export type FindVariantsByVectorQuery = {
16
- /** Exactly one reference. Only for image modes. */
17
- image?: ImageReferenceInput;
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
+ images?: ImageReferenceInput[];
18
18
  mode: FindVariantsByVectorMode;
19
19
  /** Description, mood words included. Only for text modes. */
20
20
  text?: string;
@@ -31,7 +31,7 @@ export type FindVariantsByVectorInput = {
31
31
  /** Only this corpus scope. Absent or all: every variant. */
32
32
  searchCatalogueSubset?: SearchCatalogueSubset;
33
33
  };
34
- export type FindVariantsByVectorItem = Omit<ListVariantsItem, "materialBankId"> & {
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. */
@@ -40,7 +40,7 @@ export type FindVariantsByVectorItem = Omit<ListVariantsItem, "materialBankId">
40
40
  export type FindVariantsByVectorReference = {
41
41
  id: string;
42
42
  kind: "url" | "assetId" | "variantId" | "sourceId";
43
- } | null;
43
+ };
44
44
  export type FindVariantsByVectorScan = {
45
45
  /** True when no further candidates match; false means more may exist. */
46
46
  exhausted: boolean;
@@ -61,7 +61,8 @@ export type FindVariantsByVectorOutput = {
61
61
  excludedBySearchCatalogueSubset?: string[];
62
62
  items: FindVariantsByVectorItem[];
63
63
  mode: FindVariantsByVectorMode;
64
- reference: FindVariantsByVectorReference;
64
+ /** The image references the query used, in order. Empty in text mode. */
65
+ references: FindVariantsByVectorReference[];
65
66
  /** The exact brand paint named by the text query, when held in the catalogue. */
66
67
  namedPaint?: PaintCatalogVariant;
67
68
  scan: FindVariantsByVectorScan;
@@ -52,6 +52,8 @@ export type GetVariantOutput = {
52
52
  sampleSubject: SampleSubject;
53
53
  /** The represented library color; null when none is established. */
54
54
  colorOptionValueId: string | null;
55
+ /** True for a paint color card, paint line, primer or coating: the card is the brand's declared hex, never a photo. */
56
+ hasDeclaredColor: boolean;
55
57
  code: string | null;
56
58
  /** Settled card color. Paint cards show this hex and have no photo. */
57
59
  hex?: string | null;
@@ -35,6 +35,8 @@ export type HttpTransportOptions = {
35
35
  apiKey: string;
36
36
  baseUrl: string;
37
37
  fetch: typeof fetch;
38
+ /** Deadline in milliseconds for each JSON or upload request. Not applied to streams. */
39
+ timeoutMs?: number;
38
40
  };
39
41
  export declare class HttpTransport {
40
42
  #private;
package/http-transport.js CHANGED
@@ -32,11 +32,23 @@ export class HttpTransport {
32
32
  baseUrl;
33
33
  apiKey;
34
34
  fetch;
35
+ #timeoutMs;
35
36
  #lastRateLimit = null;
36
37
  constructor(options) {
37
38
  this.apiKey = options.apiKey;
38
39
  this.baseUrl = options.baseUrl.replace(/\/+$/u, "");
39
40
  this.fetch = options.fetch;
41
+ this.#timeoutMs = options.timeoutMs;
42
+ }
43
+ /** The caller's signal and the client deadline, whichever fires first. */
44
+ #withDeadline(signal) {
45
+ if (this.#timeoutMs === undefined) {
46
+ return signal;
47
+ }
48
+ const deadline = AbortSignal.timeout(this.#timeoutMs);
49
+ return signal === undefined
50
+ ? deadline
51
+ : AbortSignal.any([signal, deadline]);
40
52
  }
41
53
  get lastRateLimit() {
42
54
  return this.#lastRateLimit;
@@ -83,19 +95,21 @@ export class HttpTransport {
83
95
  origin: requestUrl.origin,
84
96
  path: requestUrl.pathname,
85
97
  };
98
+ // The deadline covers the whole request, body read included.
99
+ const signal = this.#withDeadline(input.signal);
86
100
  let response;
87
101
  try {
88
102
  response = await this.fetch(input.url, {
89
103
  body: input.body,
90
104
  headers: input.headers,
91
105
  method: input.method,
92
- signal: input.signal,
106
+ signal,
93
107
  });
94
108
  }
95
109
  catch (cause) {
96
110
  throw MaterialGraphError.fromTransport({
97
111
  ...metadata,
98
- callerSignal: input.signal,
112
+ callerSignal: signal,
99
113
  cause,
100
114
  });
101
115
  }
@@ -108,7 +122,7 @@ export class HttpTransport {
108
122
  catch (cause) {
109
123
  throw MaterialGraphError.fromTransport({
110
124
  ...metadata,
111
- callerSignal: input.signal,
125
+ callerSignal: signal,
112
126
  cause,
113
127
  });
114
128
  }
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { MaterialGraphClient, DEFAULT_BASE_URL } from "./client.js";
2
2
  export type { MaterialGraphClientOptions } from "./client.js";
3
- export type { FilterOp, FilterTermCondition, FilterNumericCondition, FilterPresenceCondition, FilterAnyFieldCondition, FilterCondition, FilterGroups, OptionalFilterGroups, OrderByEntry, ListVariantsInclude, ListVariantsCollapse, ColorRegion, ColorRangeCondition, ListVariantsInput, ListVariantsPreference, ListVariantsSpecifications, ListVariantsNotApplicable, SampleSubject, ListVariantsItem, ListVariantsOutput, } from "./list-variants-types.js";
3
+ 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
4
  export type { ListVariantFacetsInput, VariantFacetValue, CategoricalVariantFacet, BooleanVariantFacet, NumericVariantFacet, VariantFacet, ListVariantFacetsOutput, } from "./list-variant-facets-types.js";
5
5
  export type { SearchVariantsSelection, SearchVariantsInput, SearchVariantsUnderstood, SearchVariantsReading, SearchVariantsChoice, SearchVariantsOutput, } from "./search-variants-types.js";
6
6
  export type { ListEnumValuesOptions, EnumValueMatch, EnumValueItem, ListEnumValuesResult, } from "./list-enum-values-types.js";
@@ -190,6 +190,8 @@ export type ListVariantsItem = {
190
190
  sampleSubject: SampleSubject;
191
191
  /** The represented library color; null when none is established. */
192
192
  colorOptionValueId: string | null;
193
+ /** True for a paint color card, paint line, primer or coating: the card is the brand's declared hex, never a photo. */
194
+ hasDeclaredColor: boolean;
193
195
  code: string | null;
194
196
  /** Present only when `include` has `image`. */
195
197
  /** Settled card color. Paint cards show this hex and have no photo. */
@@ -220,6 +222,15 @@ export type ListVariantsItem = {
220
222
  variantCount?: number;
221
223
  variantId: string;
222
224
  };
225
+ /**
226
+ * The variant card both `listVariants` rows and `findVariantsByVector` rows
227
+ * satisfy. The only difference between the two is the Material Bank SKU:
228
+ * absent in a list row when none is recorded, `null` in a vector row.
229
+ */
230
+ export type VariantCard = Omit<ListVariantsItem, "materialBankId"> & {
231
+ /** The variant's Material Bank SKU. Absent or null means none is recorded. */
232
+ materialBankId?: string | null;
233
+ };
223
234
  export type ListVariantsOutput = {
224
235
  /** Explicit `{field: "variant", op: "in"}` ids that name a real variant outside the materials catalogue. Present only when at least one did. */
225
236
  excludedBySearchCatalogueSubset?: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materialgraph",
3
- "version": "0.44.0",
3
+ "version": "0.46.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -21,7 +21,7 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@materialinstruments/colorscope": "10.2.1"
24
+ "@materialinstruments/colorscope": "10.6.1"
25
25
  },
26
26
  "license": "Proprietary",
27
27
  "engines": {