@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.
- package/brands-client.d.ts +48 -0
- package/brands-client.js +45 -0
- package/browse-types.d.ts +147 -0
- package/browse-types.js +1 -0
- package/by-colour-types.d.ts +105 -5
- package/client-browse.d.ts +13 -3
- package/client-browse.js +12 -2
- package/client-exploration.d.ts +9 -0
- package/client-exploration.js +7 -0
- package/client-source-material-comments.d.ts +51 -0
- package/client-source-material-comments.js +40 -0
- package/client.d.ts +64 -71
- package/client.js +90 -78
- package/colour-aggregate-types.d.ts +58 -0
- package/colours-client.d.ts +8 -2
- package/colours-client.js +15 -2
- package/composition-client.d.ts +33 -0
- package/composition-client.js +27 -0
- package/composition-types.d.ts +46 -0
- package/coordinate-client.d.ts +23 -0
- package/coordinate-client.js +25 -0
- package/errors.js +24 -8
- package/exploration-types.d.ts +110 -0
- package/exploration-types.js +1 -0
- package/fan-deck-types.d.ts +42 -0
- package/fan-deck-types.js +9 -0
- package/http-transport.js +24 -5
- package/index.d.ts +7 -3
- package/index.js +2 -2
- package/match-stream-types.d.ts +1 -1
- package/match-stream-types.js +16 -3
- package/match-stream.js +14 -4
- package/material-types.d.ts +1 -1
- package/operations.d.ts +1 -1
- package/operations.js +187 -26
- package/package.json +1 -1
- package/palette-search-types.d.ts +31 -0
- package/resolve-client.d.ts +22 -3
- package/resolve-client.js +31 -4
- package/resolve-types.d.ts +70 -1
- package/run-events.d.ts +1 -1
- package/run-events.js +4 -1
- package/run-types.d.ts +132 -0
- package/runs.d.ts +35 -1
- package/runs.js +69 -5
- package/schedule-types.d.ts +128 -0
- package/schema-resource-types.d.ts +1 -1
- package/search-refinement-types.d.ts +39 -4
- package/search-request-types.d.ts +172 -0
- package/search-request-types.js +1 -0
- package/search-result-validation.js +21 -4
- package/slot-pool-types.d.ts +370 -0
- package/slot-pool-types.js +1 -0
- package/source-material-validation.d.ts +5 -1
- package/source-material-validation.js +114 -5
- package/spec-facet-types.d.ts +53 -0
- package/spec-facet-types.js +7 -0
- package/specifications.d.ts +26 -0
- package/specifications.js +32 -0
- package/sse.js +3 -1
- package/suggest-types.js +3 -1
- package/types.d.ts +176 -216
- package/validation.d.ts +12 -0
- 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
|
+
}
|
package/composition-types.d.ts
CHANGED
|
@@ -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
|
|
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) ||
|
|
71
|
+
if (!Array.isArray(value) ||
|
|
72
|
+
value.some((suggestion) => typeof suggestion !== "string")) {
|
|
70
73
|
return undefined;
|
|
71
74
|
}
|
|
72
|
-
return value
|
|
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
|
|
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"
|
|
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)
|
|
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, {
|
|
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 ??
|
|
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 &&
|
|
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 {
|
|
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({
|
|
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({
|
|
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({
|
|
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";
|
package/match-stream-types.d.ts
CHANGED
|
@@ -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];
|
package/match-stream-types.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/** How the workflow classified the uploaded image. */
|
|
2
|
-
export const MATCH_IMAGE_TYPES = [
|
|
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 = [
|
|
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 = [
|
|
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([
|
|
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
|
|
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 {
|
|
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;
|
package/material-types.d.ts
CHANGED
|
@@ -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
|
*
|