@instruments/materialgraph 0.9.1 → 0.11.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 (64) hide show
  1. package/brands-client.d.ts +48 -0
  2. package/brands-client.js +45 -0
  3. package/browse-types.d.ts +147 -0
  4. package/browse-types.js +1 -0
  5. package/by-colour-types.d.ts +105 -5
  6. package/client-browse.d.ts +13 -3
  7. package/client-browse.js +12 -2
  8. package/client-exploration.d.ts +9 -0
  9. package/client-exploration.js +7 -0
  10. package/client-source-material-comments.d.ts +51 -0
  11. package/client-source-material-comments.js +40 -0
  12. package/client.d.ts +64 -71
  13. package/client.js +90 -78
  14. package/colour-aggregate-types.d.ts +58 -0
  15. package/colours-client.d.ts +8 -2
  16. package/colours-client.js +15 -2
  17. package/composition-client.d.ts +33 -0
  18. package/composition-client.js +27 -0
  19. package/composition-types.d.ts +46 -0
  20. package/coordinate-client.d.ts +23 -0
  21. package/coordinate-client.js +25 -0
  22. package/errors.js +24 -8
  23. package/exploration-types.d.ts +110 -0
  24. package/exploration-types.js +1 -0
  25. package/fan-deck-types.d.ts +42 -0
  26. package/fan-deck-types.js +9 -0
  27. package/http-transport.js +24 -5
  28. package/index.d.ts +7 -3
  29. package/index.js +2 -2
  30. package/match-stream-types.d.ts +1 -1
  31. package/match-stream-types.js +16 -3
  32. package/match-stream.js +14 -4
  33. package/material-types.d.ts +1 -1
  34. package/operations.d.ts +1 -1
  35. package/operations.js +187 -26
  36. package/package.json +1 -1
  37. package/palette-search-types.d.ts +31 -0
  38. package/resolve-client.d.ts +22 -3
  39. package/resolve-client.js +31 -4
  40. package/resolve-types.d.ts +70 -1
  41. package/run-events.d.ts +1 -1
  42. package/run-events.js +4 -1
  43. package/run-types.d.ts +132 -0
  44. package/runs.d.ts +35 -1
  45. package/runs.js +69 -5
  46. package/schedule-types.d.ts +128 -0
  47. package/schema-resource-types.d.ts +1 -1
  48. package/search-refinement-types.d.ts +39 -4
  49. package/search-request-types.d.ts +172 -0
  50. package/search-request-types.js +1 -0
  51. package/search-result-validation.js +21 -4
  52. package/slot-pool-types.d.ts +370 -0
  53. package/slot-pool-types.js +1 -0
  54. package/source-material-validation.d.ts +5 -1
  55. package/source-material-validation.js +114 -5
  56. package/spec-facet-types.d.ts +53 -0
  57. package/spec-facet-types.js +7 -0
  58. package/specifications.d.ts +26 -0
  59. package/specifications.js +32 -0
  60. package/sse.js +3 -1
  61. package/suggest-types.js +3 -1
  62. package/types.d.ts +176 -216
  63. package/validation.d.ts +12 -0
  64. package/validation.js +23 -0
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The COMPOSITION lens: the two synchronous scheme surfaces, as thin wrappers
3
+ * over the shared {@link HttpTransport}. `MaterialGraphClient.varyScheme` and
4
+ * `.slotPools` delegate here; the contract lives in this file.
5
+ *
6
+ * They are two altitudes of the same capability. `varyScheme` re-derives a
7
+ * scheme MaterialGraph itself composed; `slotPools` hands the ranked candidates
8
+ * to a composer that is not MaterialGraph, and keeps none of the ownership.
9
+ */
10
+ import type { VarySchemesRequest, VarySchemesResponse } from "./composition-types.js";
11
+ import { HttpTransport } from "./http-transport.js";
12
+ import type { SlotPoolsRequest, SlotPoolsResponse } from "./slot-pool-types.js";
13
+ /**
14
+ * THE QUESTION: "show me the same brief, decided differently." Re-derives an
15
+ * option B from a succeeded compose Run along ONE constraint axis. Deterministic
16
+ * and synchronous — the response IS the hydrated scheme (no Run envelope),
17
+ * stamped with axis + source provenance whose `notes` are the receipts.
18
+ * Requires the `mg:read` scope.
19
+ */
20
+ export declare function varyScheme(transport: HttpTransport, body: VarySchemesRequest, signal?: AbortSignal): Promise<VarySchemesResponse>;
21
+ /**
22
+ * THE QUESTION: "for each of these slots, what should I be choosing between?"
23
+ * Up to 24 self-contained tasks in, one ranked and gated candidate pool each
24
+ * out, correlated by YOUR `taskKey`. Synchronous, uncached and project-free —
25
+ * no Run, no programme, no state kept on the MaterialGraph side.
26
+ *
27
+ * READ THE POOLS, NOT JUST THE CANDIDATES. A task whose search failed comes back
28
+ * with a `search-failed` shortfall while the rest answer normally; a candidate
29
+ * with `eligible: false` is LABELLED rather than dropped (read `flags`); and a
30
+ * criterion that graded `missing` or `not_evaluated` was never a failure.
31
+ * Requires the `mg:read` scope.
32
+ */
33
+ export declare function slotPools(transport: HttpTransport, body: SlotPoolsRequest, signal?: AbortSignal): Promise<SlotPoolsResponse>;
@@ -0,0 +1,27 @@
1
+ import { HttpTransport } from "./http-transport.js";
2
+ import { resolveOperation } from "./operations.js";
3
+ /**
4
+ * THE QUESTION: "show me the same brief, decided differently." Re-derives an
5
+ * option B from a succeeded compose Run along ONE constraint axis. Deterministic
6
+ * and synchronous — the response IS the hydrated scheme (no Run envelope),
7
+ * stamped with axis + source provenance whose `notes` are the receipts.
8
+ * Requires the `mg:read` scope.
9
+ */
10
+ export function varyScheme(transport, body, signal) {
11
+ return transport.requestJson(resolveOperation("vary_schemes"), { body, signal });
12
+ }
13
+ /**
14
+ * THE QUESTION: "for each of these slots, what should I be choosing between?"
15
+ * Up to 24 self-contained tasks in, one ranked and gated candidate pool each
16
+ * out, correlated by YOUR `taskKey`. Synchronous, uncached and project-free —
17
+ * no Run, no programme, no state kept on the MaterialGraph side.
18
+ *
19
+ * READ THE POOLS, NOT JUST THE CANDIDATES. A task whose search failed comes back
20
+ * with a `search-failed` shortfall while the rest answer normally; a candidate
21
+ * with `eligible: false` is LABELLED rather than dropped (read `flags`); and a
22
+ * criterion that graded `missing` or `not_evaluated` was never a failure.
23
+ * Requires the `mg:read` scope.
24
+ */
25
+ export function slotPools(transport, body, signal) {
26
+ return transport.requestJson(resolveOperation("slot_pools"), { body, signal });
27
+ }
@@ -284,6 +284,34 @@ export interface ReviewProgrammeSuspendPayload {
284
284
  categorySource?: SlotCategorySource;
285
285
  provenance: ProgrammeProvenance;
286
286
  }>;
287
+ /**
288
+ * Lines this room usually carries that the brief never named.
289
+ *
290
+ * Offered, never imposed: a programme that proceeds without them is a legal
291
+ * answer. `essential` marks the ones whose absence makes the room unbuildable
292
+ * as specified; `ffe` marks furniture and lighting the catalogue does not
293
+ * serve yet, carried because a bedroom with no bed on the schedule is a gap
294
+ * worth seeing coming. Add one with an `add-slot` correction.
295
+ */
296
+ unstated?: Array<{
297
+ spaceId: string;
298
+ key: string;
299
+ name: string;
300
+ element: SlotElement;
301
+ /**
302
+ * Absent and `null` both mean the graph has no category for this line, and a
303
+ * reader must treat them as one answer.
304
+ *
305
+ * The distinction does not survive the wire. A `null` set server-side
306
+ * arrives as an absent key on the far side of the workflow runtime, so a
307
+ * client that branched on which one it received would be reading transport
308
+ * noise as meaning. An FF&E line the catalogue does not serve — a table
309
+ * lamp, bed linen, ironmongery — has no category under either reading.
310
+ */
311
+ category?: string | null;
312
+ weight: "essential" | "optional";
313
+ ffe?: boolean;
314
+ }>;
287
315
  }
288
316
  /**
289
317
  * Resume grammar for the `review-programme` step: drop a slot, drop a space
@@ -310,6 +338,24 @@ export interface ReviewProgrammeResumeData {
310
338
  action: "set-slot-category";
311
339
  slotId: string;
312
340
  category: string;
341
+ }
342
+ /**
343
+ * ADD a line the brief never named — usually one the payload offered under
344
+ * `unstated`, though any line is legal. Every other verb here subtracts,
345
+ * which made the programme a ceiling: whatever the brief happened to
346
+ * mention was the most a schedule could ever contain.
347
+ *
348
+ * `category` may be null or absent, because some real lines — grout,
349
+ * ironmongery, sanitaryware — have no catalog category yet. Such a slot is
350
+ * skipped at retrieval with a counted `no-category` shortfall, which is the
351
+ * honest treatment of a line we can hold but not yet fill.
352
+ */
353
+ | {
354
+ action: "add-slot";
355
+ spaceId: string;
356
+ name: string;
357
+ element: SlotElement;
358
+ category?: string | null;
313
359
  }>;
314
360
  }
315
361
  /**
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The COORDINATION lens: given several materials already chosen, what completes
3
+ * them. A thin wrapper over the shared {@link HttpTransport};
4
+ * `MaterialGraphClient.coordinate` delegates here.
5
+ *
6
+ * Extracted from `client.ts`, which sits above the repository's source-size
7
+ * limit, in the same per-lens shape as `brands-client` / `codes-client` /
8
+ * `colours-client`. Behaviour is unchanged.
9
+ */
10
+ import { HttpTransport } from "./http-transport.js";
11
+ import type { CoordinateInput, CoordinateResponse } from "./types.js";
12
+ /**
13
+ * THE QUESTION: "here is my whole scheme so far — what completes it?" The
14
+ * multi-seed form of `pairings`: lift is SUMMED across seeds, so a companion
15
+ * suiting one pick ranks below one suiting several. Returns demand-lift-ranked
16
+ * companions with colour swatches and an annotate-only `colourFit` that never
17
+ * reorders in v1.
18
+ *
19
+ * Instead use `pairings` for a single material, and `colours.palette()` when the
20
+ * scheme is COLOURS rather than materials. Check `unresolved` before presenting:
21
+ * a reference that resolved to nothing is named there, not dropped.
22
+ */
23
+ export declare function coordinate(transport: HttpTransport, input: CoordinateInput, signal?: AbortSignal): Promise<CoordinateResponse>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The COORDINATION lens: given several materials already chosen, what completes
3
+ * them. A thin wrapper over the shared {@link HttpTransport};
4
+ * `MaterialGraphClient.coordinate` delegates here.
5
+ *
6
+ * Extracted from `client.ts`, which sits above the repository's source-size
7
+ * limit, in the same per-lens shape as `brands-client` / `codes-client` /
8
+ * `colours-client`. Behaviour is unchanged.
9
+ */
10
+ import { HttpTransport } from "./http-transport.js";
11
+ import { resolveOperation } from "./operations.js";
12
+ /**
13
+ * THE QUESTION: "here is my whole scheme so far — what completes it?" The
14
+ * multi-seed form of `pairings`: lift is SUMMED across seeds, so a companion
15
+ * suiting one pick ranks below one suiting several. Returns demand-lift-ranked
16
+ * companions with colour swatches and an annotate-only `colourFit` that never
17
+ * reorders in v1.
18
+ *
19
+ * Instead use `pairings` for a single material, and `colours.palette()` when the
20
+ * scheme is COLOURS rather than materials. Check `unresolved` before presenting:
21
+ * a reference that resolved to nothing is named there, not dropped.
22
+ */
23
+ export function coordinate(transport, input, signal) {
24
+ return transport.requestJson(resolveOperation("coordinate_materials"), { body: input, signal });
25
+ }
package/errors.js CHANGED
@@ -53,7 +53,9 @@ function sanitizeRecord(value, secrets, depth) {
53
53
  .slice(0, MAX_EXTENSION_ITEMS)
54
54
  .flatMap(([key, item]) => {
55
55
  const sanitized = sanitizeValue(item, secrets, depth + 1);
56
- return sanitized === undefined ? [] : [[safeString(key, secrets), sanitized]];
56
+ return sanitized === undefined
57
+ ? []
58
+ : [[safeString(key, secrets), sanitized]];
57
59
  });
58
60
  return Object.fromEntries(entries);
59
61
  }
@@ -66,10 +68,13 @@ function sanitizeIssues(value, secrets) {
66
68
  .map((issue) => sanitizeRecord(issue, secrets, 0));
67
69
  }
68
70
  function sanitizeSuggestions(value, secrets) {
69
- if (!Array.isArray(value) || value.some((suggestion) => typeof suggestion !== "string")) {
71
+ if (!Array.isArray(value) ||
72
+ value.some((suggestion) => typeof suggestion !== "string")) {
70
73
  return undefined;
71
74
  }
72
- return value.slice(0, MAX_EXTENSION_ITEMS).map((suggestion) => safeString(suggestion, secrets));
75
+ return value
76
+ .slice(0, MAX_EXTENSION_ITEMS)
77
+ .map((suggestion) => safeString(suggestion, secrets));
73
78
  }
74
79
  function normalizeProblem(body, secrets) {
75
80
  if (!isRecord(body) ||
@@ -89,11 +94,15 @@ function normalizeProblem(body, secrets) {
89
94
  title: safeString(body.title, secrets),
90
95
  status: body.status,
91
96
  detail: safeString(body.detail, secrets),
92
- ...(body.instance === undefined ? {} : { instance: safeString(body.instance, secrets) }),
97
+ ...(body.instance === undefined
98
+ ? {}
99
+ : { instance: safeString(body.instance, secrets) }),
93
100
  is_retriable: body.is_retriable,
94
101
  trace_id: safeString(body.trace_id, secrets),
95
102
  ...(suggestions === undefined ? {} : { suggestions }),
96
- ...(typeof body.doc_uri === "string" ? { doc_uri: safeString(body.doc_uri, secrets) } : {}),
103
+ ...(typeof body.doc_uri === "string"
104
+ ? { doc_uri: safeString(body.doc_uri, secrets) }
105
+ : {}),
97
106
  ...(issues === undefined ? {} : { issues }),
98
107
  };
99
108
  }
@@ -107,7 +116,9 @@ function normalizeSimple(body, secrets) {
107
116
  };
108
117
  }
109
118
  function unexpectedMessage(status, responseText, secrets) {
110
- const sanitized = redact(responseText, secrets).trim().slice(0, MAX_UNEXPECTED_TEXT);
119
+ const sanitized = redact(responseText, secrets)
120
+ .trim()
121
+ .slice(0, MAX_UNEXPECTED_TEXT);
111
122
  return sanitized.length > 0
112
123
  ? `MaterialGraph request failed with HTTP ${status}: ${sanitized}`
113
124
  : `MaterialGraph request failed with HTTP ${status}`;
@@ -143,7 +154,10 @@ export class MaterialGraphError extends Error {
143
154
  return this.retriable;
144
155
  }
145
156
  static unexpected(message) {
146
- return new MaterialGraphError(message, { kind: "unexpected", retriable: false });
157
+ return new MaterialGraphError(message, {
158
+ kind: "unexpected",
159
+ retriable: false,
160
+ });
147
161
  }
148
162
  static fromTransport(input) {
149
163
  if (input.callerSignal?.aborted === true) {
@@ -174,7 +188,9 @@ export class MaterialGraphError extends Error {
174
188
  const retriable = RETRIABLE_STATUS.has(input.status);
175
189
  const metadata = requestMetadata(input);
176
190
  if (input.status === 429) {
177
- return new MaterialGraphError(problem?.detail ?? simple?.error ?? "MaterialGraph rate limit exceeded (429)", {
191
+ return new MaterialGraphError(problem?.detail ??
192
+ simple?.error ??
193
+ "MaterialGraph rate limit exceeded (429)", {
178
194
  ...metadata,
179
195
  status: input.status,
180
196
  rateLimit: input.rateLimit,
@@ -0,0 +1,110 @@
1
+ import type { VariantId } from "./identifiers.js";
2
+ export type GraphOperator = "colourways" | "same_category" | "still_listed" | "looks_like" | "same_colour" | "pairs_with" | "often_specified_with" | "substitutes_for" | "more_durable" | "outdoor_suitable" | "lower_cost" | "more_sustainable";
3
+ export interface GraphOperatorAvailability {
4
+ operator: GraphOperator;
5
+ operatorClass: "identity" | "perceptual" | "relational" | "spec";
6
+ label: string;
7
+ available: boolean;
8
+ basis: string;
9
+ count?: number;
10
+ }
11
+ export interface GraphOperatorEvidence {
12
+ variantId: VariantId;
13
+ operators: GraphOperatorAvailability[];
14
+ }
15
+ export interface CandidateReadInput {
16
+ operator: "looks_like";
17
+ corpusScope?: "all" | "materialBankOrderable";
18
+ limit?: number;
19
+ excludeVariantIds?: VariantId[];
20
+ }
21
+ export interface CandidateReadImage {
22
+ url: string;
23
+ source: "r2" | "origin";
24
+ /** Null means this image has not been analysed for a uniform light background. */
25
+ isIsolated: boolean | null;
26
+ }
27
+ export interface CandidateReadPaletteSwatch {
28
+ hex: string;
29
+ proportion: number;
30
+ name?: string | null;
31
+ }
32
+ export interface CandidateReadCard {
33
+ variantId: VariantId;
34
+ productId: string;
35
+ brandSlug: string;
36
+ materialBankId: string | null;
37
+ brand: string;
38
+ product: string;
39
+ variantName: string | null;
40
+ color: string | null;
41
+ image: CandidateReadImage | null;
42
+ palette: CandidateReadPaletteSwatch[];
43
+ }
44
+ export interface CandidateRankEvidence {
45
+ rank: number;
46
+ distance: number;
47
+ similarity: number;
48
+ basis: "active_main_image_embedding";
49
+ provenanceGrade: "measured";
50
+ displayImageMatchedEvidence: boolean | null;
51
+ }
52
+ export interface CandidateReadCandidate extends CandidateReadCard {
53
+ evidence: CandidateRankEvidence;
54
+ reason: string;
55
+ }
56
+ export interface CandidateSeedEvidence {
57
+ evidenceImageSha256: string;
58
+ displayImageSha256: string | null;
59
+ basis: "active_main_image_embedding";
60
+ provenanceGrade: "measured";
61
+ displayImageMatchedEvidence: boolean | null;
62
+ }
63
+ export interface CandidateReadReceipt {
64
+ operator: "looks_like";
65
+ corpusScope: {
66
+ scope: "all" | "materialBankOrderable";
67
+ description: string;
68
+ } | null;
69
+ requestedLimit: number;
70
+ returnedCount: number;
71
+ exclusions: {
72
+ requested: number;
73
+ unique: number;
74
+ applied: number;
75
+ };
76
+ dedupe: {
77
+ retrieved: number;
78
+ removed: number;
79
+ truncated: number;
80
+ projectionMisses: number;
81
+ };
82
+ timing: {
83
+ /** Internal seed projection completion; not a consumer response milestone. */
84
+ seedProjectionMs: number;
85
+ retrievalMs: number;
86
+ projectionMs: number;
87
+ completeMs: number;
88
+ };
89
+ }
90
+ interface CandidateReadResponseBase {
91
+ seed: CandidateReadCard;
92
+ receipt: CandidateReadReceipt;
93
+ }
94
+ export type CandidateReadResponse = (CandidateReadResponseBase & {
95
+ status: "ready";
96
+ seedEvidence: CandidateSeedEvidence;
97
+ candidates: CandidateReadCandidate[];
98
+ reason: null;
99
+ }) | (CandidateReadResponseBase & {
100
+ status: "no_candidates" | "no_unseen_candidates" | "bounded_retrieval_unavailable";
101
+ seedEvidence: CandidateSeedEvidence;
102
+ candidates: [];
103
+ reason: string;
104
+ }) | (CandidateReadResponseBase & {
105
+ status: "operator_unavailable";
106
+ seedEvidence: null;
107
+ candidates: [];
108
+ reason: string;
109
+ });
110
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Wire types for a maker's whole range — GET `/api/v1/brands/{brandSlug}/colours`.
3
+ *
4
+ * The one read in the SDK that is deliberately UNPAGINATED. Family order is only
5
+ * correct over a complete range: the greys cannot be placed against the browns
6
+ * while holding one page of each. So the response carries the brand, and
7
+ * `truncatedAt` appears only if a range ever exceeds the server's ceiling.
8
+ */
9
+ export interface FanDeckSwatch {
10
+ /** Opaque token. Never parse or construct one. */
11
+ variantId: string;
12
+ brand: string;
13
+ /** The maker's own colour name. Null when the catalogue publishes none. */
14
+ name: string | null;
15
+ /** The maker's PUBLISHED hex. This read never returns a derived colour. */
16
+ hex: string;
17
+ /** colorscope's family id, lower case (`cream`, `gray`, `blue`). */
18
+ family: string;
19
+ /** OKLab L, 0–1. The sort key within a family: light to dark. */
20
+ lightness: number;
21
+ imageUrl: string | null;
22
+ }
23
+ export interface FanDeckFamily {
24
+ family: string;
25
+ /** Count over the WHOLE range, computed before any truncation. */
26
+ count: number;
27
+ }
28
+ export interface BrandFanDeck {
29
+ /**
30
+ * The brand SLUGS the deck resolved to — round-trippable identifiers, not
31
+ * display names. The maker's name as it should be shown is on every swatch.
32
+ */
33
+ brands: string[];
34
+ /** Grouped by `families` order, light to dark within each family. */
35
+ swatches: FanDeckSwatch[];
36
+ families: FanDeckFamily[];
37
+ /**
38
+ * Present ONLY when the range exceeded the server ceiling. Absent means the
39
+ * deck is complete — absent is not zero.
40
+ */
41
+ truncatedAt?: number;
42
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Wire types for a maker's whole range — GET `/api/v1/brands/{brandSlug}/colours`.
3
+ *
4
+ * The one read in the SDK that is deliberately UNPAGINATED. Family order is only
5
+ * correct over a complete range: the greys cannot be placed against the browns
6
+ * while holding one page of each. So the response carries the brand, and
7
+ * `truncatedAt` appears only if a range ever exceeds the server's ceiling.
8
+ */
9
+ export {};
package/http-transport.js CHANGED
@@ -19,7 +19,10 @@ function parseRateLimit(headers) {
19
19
  const remaining = parseIntOrNull(headers.get("x-ratelimit-remaining"));
20
20
  const reset = parseIntOrNull(headers.get("x-ratelimit-reset"));
21
21
  const retryAfter = parseIntOrNull(headers.get("retry-after"));
22
- if (limit === null && remaining === null && reset === null && retryAfter === null) {
22
+ if (limit === null &&
23
+ remaining === null &&
24
+ reset === null &&
25
+ retryAfter === null) {
23
26
  return null;
24
27
  }
25
28
  return { limit, remaining, reset, retryAfter };
@@ -39,7 +42,11 @@ export class HttpTransport {
39
42
  }
40
43
  /** The bearer + JSON `Accept` headers, plus any per-request additions. */
41
44
  authHeaders(extra) {
42
- return { Authorization: `Bearer ${this.apiKey}`, Accept: "application/json", ...extra };
45
+ return {
46
+ Authorization: `Bearer ${this.apiKey}`,
47
+ Accept: "application/json",
48
+ ...extra,
49
+ };
43
50
  }
44
51
  buildUrl(path, query) {
45
52
  const url = new URL(`${this.baseUrl}${path}`);
@@ -83,7 +90,11 @@ export class HttpTransport {
83
90
  });
84
91
  }
85
92
  catch (cause) {
86
- throw MaterialGraphError.fromTransport({ ...metadata, cause, callerSignal: input.signal });
93
+ throw MaterialGraphError.fromTransport({
94
+ ...metadata,
95
+ cause,
96
+ callerSignal: input.signal,
97
+ });
87
98
  }
88
99
  const rateLimit = parseRateLimit(response.headers);
89
100
  this.#lastRateLimit = rateLimit;
@@ -92,7 +103,11 @@ export class HttpTransport {
92
103
  text = await response.text();
93
104
  }
94
105
  catch (cause) {
95
- throw MaterialGraphError.fromTransport({ ...metadata, cause, callerSignal: input.signal });
106
+ throw MaterialGraphError.fromTransport({
107
+ ...metadata,
108
+ cause,
109
+ callerSignal: input.signal,
110
+ });
96
111
  }
97
112
  let parsed;
98
113
  if (text.length > 0) {
@@ -147,7 +162,11 @@ export class HttpTransport {
147
162
  });
148
163
  }
149
164
  catch (cause) {
150
- throw MaterialGraphError.fromTransport({ ...metadata, cause, callerSignal: options.signal });
165
+ throw MaterialGraphError.fromTransport({
166
+ ...metadata,
167
+ cause,
168
+ callerSignal: options.signal,
169
+ });
151
170
  }
152
171
  const rateLimit = parseRateLimit(response.headers);
153
172
  this.#lastRateLimit = rateLimit;
package/index.d.ts CHANGED
@@ -1,13 +1,15 @@
1
- export { MaterialGraphClient, DEFAULT_BASE_URL, MAX_BATCH_IDS } from "./client.js";
2
- export type { MaterialGraphClientOptions, BrowseNamespace } from "./client.js";
1
+ export { MaterialGraphClient, DEFAULT_BASE_URL, MAX_BATCH_IDS, } from "./client.js";
2
+ export type { MaterialGraphClientOptions, BrowseNamespace, ExplorationNamespace, SourceMaterialCommentsNamespace, } from "./client.js";
3
3
  export { MATERIALGRAPH_API_VERSION, SDK_OPERATIONS } from "./operations.js";
4
4
  export type { SdkOperation, SdkOperationId } from "./operations.js";
5
5
  export type * from "./identifiers.js";
6
+ export type * from "./exploration-types.js";
6
7
  export { TERMINAL_RUN_EVENT_TYPES } from "./run-types.js";
7
8
  export type * from "./run-types.js";
8
9
  export type * from "./schedule-types.js";
9
10
  export { VARY_AXES } from "./composition-types.js";
10
11
  export type * from "./composition-types.js";
12
+ export type * from "./slot-pool-types.js";
11
13
  export { PROJECT_EVENT_KINDS } from "./project-types.js";
12
14
  export type * from "./project-types.js";
13
15
  export { COMPOSITION_PHASE_EVENT_TYPES, COMPOSITION_PROGRAMME_EVENT_TYPES, COMPOSITION_REVIEW_CRITERIA_EVENT_TYPES, parseRunEvent, } from "./run-events.js";
@@ -16,6 +18,7 @@ export { MaterialGraphError } from "./errors.js";
16
18
  export type { MaterialGraphErrorInit, MaterialGraphErrorKind, MaterialGraphRequestMetadata, ProblemDetail, RateLimit, SanitizedErrorValue, SanitizedIssue, SimpleErrorBody, } from "./errors.js";
17
19
  export type * from "./types.js";
18
20
  export type * from "./match-types.js";
21
+ export type * from "./browse-types.js";
19
22
  export type * from "./by-colour-types.js";
20
23
  export { MATCH_IMAGE_TYPES, MATCH_OUTCOMES, MATCH_MATERIAL_FUNCTIONS, MATCH_STREAM_EVENT_TYPES, MATCH_SURFACE_STATUSES, MATCH_WORKFLOW_STEPS, } from "./match-stream-types.js";
21
24
  export type * from "./match-stream-types.js";
@@ -27,10 +30,11 @@ export type * from "./colour-aggregate-types.js";
27
30
  export type * from "./palette-search-types.js";
28
31
  export type * from "./association-types.js";
29
32
  export type * from "./brand-types.js";
33
+ export type * from "./fan-deck-types.js";
30
34
  export type * from "./material-asset-types.js";
31
35
  export type * from "./material-block-types.js";
32
36
  export type * from "./schema-resource-types.js";
33
37
  export type * from "./search-refinement-types.js";
34
38
  export { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID } from "./spec-types.js";
35
39
  export type * from "./spec-types.js";
36
- export { certificationProgramme, certificationProgrammeCriterion, hasSpec, specCriterion, specTerm, } from "./specifications.js";
40
+ export { certificationProgramme, certificationProgrammeCriterion, hasSpec, specCriterion, specRangeCriterion, specTerm, } from "./specifications.js";
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { MaterialGraphClient, DEFAULT_BASE_URL, MAX_BATCH_IDS } from "./client.js";
1
+ export { MaterialGraphClient, DEFAULT_BASE_URL, MAX_BATCH_IDS, } from "./client.js";
2
2
  export { MATERIALGRAPH_API_VERSION, SDK_OPERATIONS } from "./operations.js";
3
3
  export { TERMINAL_RUN_EVENT_TYPES } from "./run-types.js";
4
4
  export { VARY_AXES } from "./composition-types.js";
@@ -9,4 +9,4 @@ export { MATCH_IMAGE_TYPES, MATCH_OUTCOMES, MATCH_MATERIAL_FUNCTIONS, MATCH_STRE
9
9
  export { CodesClient } from "./codes-client.js";
10
10
  export { ColoursClient } from "./colours-client.js";
11
11
  export { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID } from "./spec-types.js";
12
- export { certificationProgramme, certificationProgrammeCriterion, hasSpec, specCriterion, specTerm, } from "./specifications.js";
12
+ export { certificationProgramme, certificationProgrammeCriterion, hasSpec, specCriterion, specRangeCriterion, specTerm, } from "./specifications.js";
@@ -1,3 +1,4 @@
1
+ import type { MatchCategoryInference, MatchColourReading } from "./match-types.js";
1
2
  /**
2
3
  * The Material Match PROGRESS-STREAM contracts — the wire vocabulary of
3
4
  * {@link MaterialGraphClient.matchStream} (`POST /api/v1/search/match` under
@@ -16,7 +17,6 @@
16
17
  * UI legend from the same source the validator uses.
17
18
  */
18
19
  import type { SearchResultItem } from "./types.js";
19
- import type { MatchCategoryInference, MatchColourReading } from "./match-types.js";
20
20
  /** How the workflow classified the uploaded image. */
21
21
  export declare const MATCH_IMAGE_TYPES: readonly ["swatch", "installation", "flatplan"];
22
22
  export type MatchImageType = (typeof MATCH_IMAGE_TYPES)[number];
@@ -1,5 +1,9 @@
1
1
  /** How the workflow classified the uploaded image. */
2
- export const MATCH_IMAGE_TYPES = ["swatch", "installation", "flatplan"];
2
+ export const MATCH_IMAGE_TYPES = [
3
+ "swatch",
4
+ "installation",
5
+ "flatplan",
6
+ ];
3
7
  /** What a detected surface is FOR — the element the material would serve. */
4
8
  export const MATCH_MATERIAL_FUNCTIONS = [
5
9
  "flooring",
@@ -13,7 +17,12 @@ export const MATCH_MATERIAL_FUNCTIONS = [
13
17
  "sample",
14
18
  ];
15
19
  /** The workflow's coarse progress phase, narrated by a `step` event. */
16
- export const MATCH_WORKFLOW_STEPS = ["classifying", "decomposing", "matching", "complete"];
20
+ export const MATCH_WORKFLOW_STEPS = [
21
+ "classifying",
22
+ "decomposing",
23
+ "matching",
24
+ "complete",
25
+ ];
17
26
  /**
18
27
  * Where one surface stands in the run.
19
28
  *
@@ -40,7 +49,11 @@ export const MATCH_STREAM_EVENT_TYPES = [
40
49
  "complete",
41
50
  "error",
42
51
  ];
43
- export const MATCH_SENSE_CHECK_VERDICTS = ["confirmed", "rejected", "abstained"];
52
+ export const MATCH_SENSE_CHECK_VERDICTS = [
53
+ "confirmed",
54
+ "rejected",
55
+ "abstained",
56
+ ];
44
57
  /** The terminal Match workflow result — every surface in its final state. */
45
58
  /**
46
59
  * How a Match run ended. `no_surfaces` means the detector worked and found no
package/match-stream.js CHANGED
@@ -55,16 +55,21 @@
55
55
  * invariants.
56
56
  */
57
57
  import { MaterialGraphError } from "./errors.js";
58
- import { findSdkOperation } from "./operations.js";
59
58
  import { MATCH_IMAGE_TYPES, MATCH_MATERIAL_FUNCTIONS, MATCH_STREAM_EVENT_TYPES, MATCH_SURFACE_STATUSES, MATCH_WORKFLOW_STEPS, } from "./match-stream-types.js";
60
59
  import { readNdjsonLines } from "./ndjson.js";
60
+ import { findSdkOperation, } from "./operations.js";
61
61
  import { checkSearchResultItems } from "./search-result-validation.js";
62
62
  import { requireArray, requireBoundedNumber, requireDefaultedArray, requireEnum, requireIntegerAtLeast, requireNumber, requireOptionalString, requireRecord, requireString, } from "./validation.js";
63
63
  /** The NDJSON media type the Match route negotiates the scene workflow on. */
64
64
  const MATCH_STREAM_ACCEPT = "application/x-ndjson";
65
65
  /** How much of an offending frame a diagnostic quotes (crops can be megabytes). */
66
66
  const MAX_FRAME_PREVIEW = 200;
67
- const BOUNDING_BOX_CORNERS = Object.freeze(["x0", "y0", "x1", "y1"]);
67
+ const BOUNDING_BOX_CORNERS = Object.freeze([
68
+ "x0",
69
+ "y0",
70
+ "x1",
71
+ "y1",
72
+ ]);
68
73
  function operation(operationId) {
69
74
  const found = findSdkOperation(operationId);
70
75
  if (!found)
@@ -76,7 +81,9 @@ function operation(operationId) {
76
81
  * offending path and quoting a bounded slice of the frame.
77
82
  */
78
83
  function frameFailure(line) {
79
- const preview = line.length > MAX_FRAME_PREVIEW ? `${line.slice(0, MAX_FRAME_PREVIEW)}…` : line;
84
+ const preview = line.length > MAX_FRAME_PREVIEW
85
+ ? `${line.slice(0, MAX_FRAME_PREVIEW)}…`
86
+ : line;
80
87
  return (reason) => MaterialGraphError.unexpected(`MaterialGraph match stream sent a malformed NDJSON frame: ${reason}. Frame: ${preview}`);
81
88
  }
82
89
  /** Check one `MatchCategoryInference` item. */
@@ -190,7 +197,10 @@ function readFrame(value, fail) {
190
197
  // Payload-free by contract — there is nothing else to check.
191
198
  return record;
192
199
  case "complete":
193
- return { ...record, result: readWorkflowResult(record.result, "`result`", fail) };
200
+ return {
201
+ ...record,
202
+ result: readWorkflowResult(record.result, "`result`", fail),
203
+ };
194
204
  case "error":
195
205
  requireString(record.message, "`message`", fail);
196
206
  return record;
@@ -1,3 +1,4 @@
1
+ import type { ProductId, VariantId } from "./identifiers.js";
1
2
  /**
2
3
  * Hand-mirrored wire types for the material projections — the grid-card tile,
3
4
  * the detail record, and the availability/successor block they carry.
@@ -22,7 +23,6 @@
22
23
  * that says which.
23
24
  */
24
25
  import type { ImageSource, MaterialColour, OpenEnum, PaletteSwatch } from "./types.js";
25
- import type { ProductId, VariantId } from "./identifiers.js";
26
26
  /**
27
27
  * Whether a material can still be bought.
28
28
  *