@instruments/materialgraph 0.25.0 → 0.27.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/association-types.d.ts +38 -27
- package/associations-client.d.ts +4 -4
- package/associations-client.js +3 -10
- package/attribution-types.d.ts +4 -4
- package/brand-types.d.ts +14 -6
- package/brands-client.d.ts +7 -38
- package/brands-client.js +10 -40
- package/by-colour-types.d.ts +58 -31
- package/by-spec-types.d.ts +6 -73
- package/by-spec-types.js +7 -5
- package/catalog-discovery-types.d.ts +11 -26
- package/catalogue-client.d.ts +16 -35
- package/catalogue-client.js +32 -67
- package/catalogue-types.d.ts +44 -4
- package/categories-client.d.ts +63 -0
- package/categories-client.js +48 -0
- package/category-types.d.ts +114 -0
- package/citation-types.d.ts +4 -4
- package/client-config.d.ts +4 -6
- package/client-config.js +2 -4
- package/client-source-material-comments.d.ts +4 -4
- package/client-source-material-comments.js +4 -38
- package/client-vocabulary.d.ts +7 -9
- package/client-vocabulary.js +22 -30
- package/client.d.ts +110 -244
- package/client.js +217 -387
- package/codes-client.d.ts +2 -2
- package/codes-client.js +8 -8
- package/codes-types.d.ts +55 -34
- package/colour-aggregate-types.d.ts +24 -24
- package/colours-client.d.ts +6 -11
- package/colours-client.js +11 -19
- package/coordinate-client.d.ts +2 -2
- package/coordinate-client.js +1 -13
- package/corpus-scope-types.d.ts +3 -3
- package/corpus-scope-types.js +1 -25
- package/demand-batch-types.d.ts +18 -18
- package/demand-types.d.ts +24 -24
- package/errors.d.ts +13 -13
- package/errors.js +54 -52
- package/http-transport.d.ts +13 -21
- package/http-transport.js +31 -28
- package/identify-types.d.ts +54 -44
- package/identify-types.js +5 -3
- package/index.d.ts +11 -27
- package/index.js +4 -6
- package/list-variant-facets-types.d.ts +60 -0
- package/list-variants-types.d.ts +142 -0
- package/list-variants-types.js +12 -0
- package/match-stream-types.d.ts +45 -32
- package/match-stream-types.js +2 -0
- package/match-stream.d.ts +2 -2
- package/match-stream.js +67 -49
- package/match-types.d.ts +32 -8
- package/material-asset-types.d.ts +89 -69
- package/material-block-types.d.ts +8 -6
- package/material-composition-client.d.ts +21 -0
- package/material-composition-client.js +13 -0
- package/material-composition-types.d.ts +403 -0
- package/material-query-plan-types.d.ts +61 -145
- package/material-resources-client.d.ts +12 -10
- package/material-resources-client.js +27 -22
- package/material-types.d.ts +16 -24
- package/ndjson.js +16 -8
- package/operations.d.ts +6 -6
- package/operations.js +29 -156
- package/package.json +5 -1
- package/palette-search-types.d.ts +87 -52
- package/query-understanding-types.d.ts +65 -40
- package/resolve-client.d.ts +8 -12
- package/resolve-client.js +9 -13
- package/resolve-colour-types.d.ts +8 -8
- package/resolve-types.d.ts +10 -10
- package/response-schemas.d.ts +20 -0
- package/response-schemas.generated.d.ts +5 -0
- package/response-schemas.generated.js +160 -0
- package/response-schemas.js +113 -0
- package/run-types.d.ts +64 -145
- package/runs.d.ts +9 -58
- package/runs.js +60 -149
- package/samplize-catalogue-types.d.ts +18 -18
- package/samplize-colorvec.d.ts +20 -20
- package/samplize-colorvec.js +66 -42
- package/schema-resource-types.d.ts +52 -39
- package/schema-resource-types.js +5 -5
- package/search-card-types.d.ts +26 -4
- package/search-index-plan-types.d.ts +70 -0
- package/search-refinement-types.d.ts +10 -40
- package/search-refinement-types.js +1 -0
- package/search-request-types.d.ts +121 -204
- package/search-response-receipt-types.d.ts +13 -253
- package/search-response-types.d.ts +2 -2
- package/search-result-validation.d.ts +3 -19
- package/search-result-validation.js +42 -183
- package/source-material-validation.d.ts +3 -3
- package/source-material-validation.js +35 -39
- package/spec-facet-types.d.ts +11 -16
- package/spec-types.d.ts +23 -16
- package/spec-types.js +8 -7
- package/specifications.d.ts +14 -72
- package/specifications.js +39 -89
- package/sse.d.ts +2 -2
- package/sse.js +32 -16
- package/substitute-types.d.ts +22 -22
- package/suggest-types.d.ts +11 -17
- package/suggest-types.js +3 -3
- package/taxonomy-coverage-types.d.ts +8 -8
- package/types.d.ts +202 -405
- package/validation.d.ts +26 -26
- package/validation.js +97 -92
- package/webmcp.d.ts +3 -3
- package/webmcp.generated.js +10 -93
- package/brand-most-sampled-types.d.ts +0 -33
- package/browse-types.d.ts +0 -236
- package/client-browse.d.ts +0 -78
- package/client-browse.js +0 -72
- package/client-exploration.d.ts +0 -9
- package/client-exploration.js +0 -7
- package/compare-types.d.ts +0 -89
- package/composition-client.d.ts +0 -33
- package/composition-client.js +0 -27
- package/composition-types.d.ts +0 -455
- package/composition-types.js +0 -10
- package/correction-types.d.ts +0 -88
- package/exploration-types.d.ts +0 -118
- package/exploration-types.js +0 -1
- package/fan-deck-types.d.ts +0 -42
- package/fan-deck-types.js +0 -9
- package/match-rejection-types.d.ts +0 -43
- package/match-rejection-types.js +0 -7
- package/palette-completion-types.d.ts +0 -115
- package/palette-completion-types.js +0 -1
- package/project-types.d.ts +0 -79
- package/project-types.js +0 -9
- package/projects.d.ts +0 -13
- package/projects.js +0 -46
- package/render-assets-types.d.ts +0 -65
- package/render-assets-types.js +0 -1
- package/run-events.d.ts +0 -106
- package/run-events.js +0 -32
- package/schedule-reference-types.d.ts +0 -46
- package/schedule-reference-types.js +0 -1
- package/schedule-references-client.d.ts +0 -14
- package/schedule-references-client.js +0 -26
- package/schedule-types.d.ts +0 -261
- package/schedule-types.js +0 -1
- package/search-collapse-types.d.ts +0 -69
- package/search-collapse-types.js +0 -1
- package/search-mood-types.d.ts +0 -6
- package/search-mood-types.js +0 -1
- package/search-window-types.d.ts +0 -25
- package/search-window-types.js +0 -1
- package/slot-pool-types.d.ts +0 -372
- package/slot-pool-types.js +0 -1
- /package/{brand-most-sampled-types.js → category-types.js} +0 -0
- /package/{browse-types.js → list-variant-facets-types.js} +0 -0
- /package/{compare-types.js → material-composition-types.js} +0 -0
- /package/{correction-types.js → search-index-plan-types.js} +0 -0
package/match-stream-types.d.ts
CHANGED
|
@@ -21,6 +21,9 @@ import type { SearchResultItem } from "./types.js";
|
|
|
21
21
|
/** How the workflow classified the uploaded image. */
|
|
22
22
|
export declare const MATCH_IMAGE_TYPES: readonly ["swatch", "installation", "flatplan"];
|
|
23
23
|
export type MatchImageType = (typeof MATCH_IMAGE_TYPES)[number];
|
|
24
|
+
/** Why a Match run ended on an `error` frame (MG-730). */
|
|
25
|
+
export declare const MATCH_ERROR_REASONS: readonly ["upstream_unavailable", "failed"];
|
|
26
|
+
export type MatchErrorReason = (typeof MATCH_ERROR_REASONS)[number];
|
|
24
27
|
/** What a detected surface is FOR — the element the material would serve. */
|
|
25
28
|
export declare const MATCH_MATERIAL_FUNCTIONS: readonly ["flooring", "wall_surface", "ceiling", "partition", "upholstery", "millwork", "countertop", "accent", "sample"];
|
|
26
29
|
export type MatchMaterialFunction = (typeof MATCH_MATERIAL_FUNCTIONS)[number];
|
|
@@ -42,14 +45,14 @@ export type MatchSurfaceStatus = (typeof MATCH_SURFACE_STATUSES)[number];
|
|
|
42
45
|
export declare const MATCH_STREAM_EVENT_TYPES: readonly ["step", "classified", "surfaces", "surface", "heartbeat", "complete", "error"];
|
|
43
46
|
export type MatchStreamEventType = (typeof MATCH_STREAM_EVENT_TYPES)[number];
|
|
44
47
|
/** Where a surface sits in the image, in Gemini-style 0–1000 image space. */
|
|
45
|
-
export
|
|
48
|
+
export type MatchBoundingBox = {
|
|
46
49
|
x0: number;
|
|
47
50
|
y0: number;
|
|
48
51
|
x1: number;
|
|
49
52
|
y1: number;
|
|
50
|
-
}
|
|
53
|
+
};
|
|
51
54
|
/** One surface the decompose step found in the image. */
|
|
52
|
-
export
|
|
55
|
+
export type MatchDetectedSurface = {
|
|
53
56
|
id: string;
|
|
54
57
|
label: string;
|
|
55
58
|
materialFunction: MatchMaterialFunction;
|
|
@@ -61,7 +64,9 @@ export interface MatchDetectedSurface {
|
|
|
61
64
|
boundingBox: MatchBoundingBox;
|
|
62
65
|
/** JPEG data-URI crop used for Match retrieval — the thumbnail to show. */
|
|
63
66
|
cropDataUri?: string;
|
|
64
|
-
|
|
67
|
+
/** The `construction_methods` dictionary term the detector's finish names. */
|
|
68
|
+
construction?: string;
|
|
69
|
+
};
|
|
65
70
|
/**
|
|
66
71
|
* One surface's matching state and its ranked results. The same `surface.id`
|
|
67
72
|
* arrives repeatedly as the run progresses (`matching` → `complete` / `error`);
|
|
@@ -86,7 +91,7 @@ export interface MatchDetectedSurface {
|
|
|
86
91
|
* [0,1] — the same unit as each result's `confidence` — so they belong on one
|
|
87
92
|
* axis. `poolSize` and `droppedBelowFloor` are counts and belong on neither.
|
|
88
93
|
*/
|
|
89
|
-
export
|
|
94
|
+
export type MatchComparison = {
|
|
90
95
|
/** Candidates retrieved before the quality floor and product dedup. */
|
|
91
96
|
poolSize: number;
|
|
92
97
|
/** How many of those the quality floor rejected. */
|
|
@@ -98,8 +103,8 @@ export interface MatchComparison {
|
|
|
98
103
|
* nothing was rejected, or when the rejects carried no readable signal.
|
|
99
104
|
*/
|
|
100
105
|
bestRejected?: number;
|
|
101
|
-
}
|
|
102
|
-
export
|
|
106
|
+
};
|
|
107
|
+
export type MatchSurfaceResult = {
|
|
103
108
|
surface: MatchDetectedSurface;
|
|
104
109
|
status: MatchSurfaceStatus;
|
|
105
110
|
results: SearchResultItem[];
|
|
@@ -150,10 +155,10 @@ export interface MatchSurfaceResult {
|
|
|
150
155
|
*/
|
|
151
156
|
colour?: MatchColourReading[];
|
|
152
157
|
/** The segment's own extracted palette — what the colour stage matched against. */
|
|
153
|
-
segmentPalette?:
|
|
158
|
+
segmentPalette?: {
|
|
154
159
|
hex: string;
|
|
155
160
|
proportion: number;
|
|
156
|
-
}
|
|
161
|
+
}[];
|
|
157
162
|
/**
|
|
158
163
|
* One sentence of provenance a consumer may render verbatim, when the server
|
|
159
164
|
* sent one — e.g. the `colour_fallback` explanation carrying the refused
|
|
@@ -162,13 +167,13 @@ export interface MatchSurfaceResult {
|
|
|
162
167
|
note?: string;
|
|
163
168
|
/** Present only on `status: "error"` — why this one surface failed. */
|
|
164
169
|
error?: string;
|
|
165
|
-
}
|
|
170
|
+
};
|
|
166
171
|
/** The verifier's reading of one candidate, keyed back to its variant. */
|
|
167
|
-
export
|
|
172
|
+
export type MatchSenseCheckCell = {
|
|
168
173
|
variantId: string;
|
|
169
174
|
verdict: MatchSenseCheckVerdict;
|
|
170
175
|
reason: string;
|
|
171
|
-
}
|
|
176
|
+
};
|
|
172
177
|
export declare const MATCH_SENSE_CHECK_VERDICTS: readonly ["confirmed", "rejected", "abstained"];
|
|
173
178
|
export type MatchSenseCheckVerdict = (typeof MATCH_SENSE_CHECK_VERDICTS)[number];
|
|
174
179
|
/** The terminal Match workflow result — every surface in its final state. */
|
|
@@ -179,7 +184,7 @@ export type MatchSenseCheckVerdict = (typeof MATCH_SENSE_CHECK_VERDICTS)[number]
|
|
|
179
184
|
*/
|
|
180
185
|
export declare const MATCH_OUTCOMES: readonly ["matched", "no_surfaces"];
|
|
181
186
|
export type MatchOutcome = (typeof MATCH_OUTCOMES)[number];
|
|
182
|
-
export
|
|
187
|
+
export type MatchWorkflowResult = {
|
|
183
188
|
/**
|
|
184
189
|
* Result attribution (MG-1131) — `responseId` (the same value as the run's
|
|
185
190
|
* `X-Request-Id` header), `basis: "match"`, and `issuedAt`. Every item in
|
|
@@ -214,10 +219,10 @@ export interface MatchWorkflowResult {
|
|
|
214
219
|
*/
|
|
215
220
|
paletteMatches?: {
|
|
216
221
|
results: SearchResultItem[];
|
|
217
|
-
palette:
|
|
222
|
+
palette: {
|
|
218
223
|
hex: string;
|
|
219
224
|
proportion: number;
|
|
220
|
-
}
|
|
225
|
+
}[];
|
|
221
226
|
note: string;
|
|
222
227
|
};
|
|
223
228
|
/**
|
|
@@ -225,23 +230,23 @@ export interface MatchWorkflowResult {
|
|
|
225
230
|
* nowhere else to put this. A scene run reports per surface instead.
|
|
226
231
|
*/
|
|
227
232
|
comparison?: MatchComparison;
|
|
228
|
-
}
|
|
233
|
+
};
|
|
229
234
|
/** Progress narration for the phase the workflow just entered. */
|
|
230
|
-
export
|
|
235
|
+
export type MatchStreamStepEvent = {
|
|
231
236
|
type: "step";
|
|
232
237
|
step: MatchWorkflowStep;
|
|
233
238
|
message: string;
|
|
234
|
-
}
|
|
239
|
+
};
|
|
235
240
|
/** The image type the classify step settled on. */
|
|
236
|
-
export
|
|
241
|
+
export type MatchStreamClassifiedEvent = {
|
|
237
242
|
type: "classified";
|
|
238
243
|
imageType: MatchImageType;
|
|
239
|
-
}
|
|
244
|
+
};
|
|
240
245
|
/** Every surface the decompose step found, before any of them has matched. */
|
|
241
|
-
export
|
|
246
|
+
export type MatchStreamSurfacesEvent = {
|
|
242
247
|
type: "surfaces";
|
|
243
248
|
surfaces: MatchDetectedSurface[];
|
|
244
|
-
}
|
|
249
|
+
};
|
|
245
250
|
/**
|
|
246
251
|
* The run's tally at the moment a `surface` event was emitted, so a consumer
|
|
247
252
|
* reads the server's arithmetic rather than reimplementing it.
|
|
@@ -252,7 +257,7 @@ export interface MatchStreamSurfacesEvent {
|
|
|
252
257
|
* `matchesSoFar`. `surfacesResolved === surfacesDetected` is therefore the
|
|
253
258
|
* correct "the run is done" test, and never stalls at 3/4 on a failure.
|
|
254
259
|
*/
|
|
255
|
-
export
|
|
260
|
+
export type MatchRunTotals = {
|
|
256
261
|
/** Surfaces the decompose step found. The denominator; fixed for the run. */
|
|
257
262
|
surfacesDetected: number;
|
|
258
263
|
/** Surfaces that reached a terminal state — `complete` OR `error`. */
|
|
@@ -261,14 +266,14 @@ export interface MatchRunTotals {
|
|
|
261
266
|
surfacesFailed: number;
|
|
262
267
|
/** Result items accumulated across every surface resolved so far. */
|
|
263
268
|
matchesSoFar: number;
|
|
264
|
-
}
|
|
269
|
+
};
|
|
265
270
|
/** One surface's state changed — the frame that makes Match progressive. */
|
|
266
|
-
export
|
|
271
|
+
export type MatchStreamSurfaceEvent = {
|
|
267
272
|
type: "surface";
|
|
268
273
|
surface: MatchSurfaceResult;
|
|
269
274
|
/** The run's tally as of this frame. Always present on a `surface` event. */
|
|
270
275
|
totals: MatchRunTotals;
|
|
271
|
-
}
|
|
276
|
+
};
|
|
272
277
|
/**
|
|
273
278
|
* CONNECTION LIVENESS ONLY — never progress. Proxies and load balancers kill
|
|
274
279
|
* idle connections, so the server sends this when the stream has been quiet, and
|
|
@@ -276,18 +281,26 @@ export interface MatchStreamSurfaceEvent {
|
|
|
276
281
|
* consumer should treat it as a state change: do not advance a step, reset a
|
|
277
282
|
* no-progress watchdog, or count it towards anything.
|
|
278
283
|
*/
|
|
279
|
-
export
|
|
284
|
+
export type MatchStreamHeartbeatEvent = {
|
|
280
285
|
type: "heartbeat";
|
|
281
|
-
}
|
|
286
|
+
};
|
|
282
287
|
/** Terminal: the whole run finished and carries its assembled result. */
|
|
283
|
-
export
|
|
288
|
+
export type MatchStreamCompleteEvent = {
|
|
284
289
|
type: "complete";
|
|
285
290
|
result: MatchWorkflowResult;
|
|
286
|
-
}
|
|
291
|
+
};
|
|
287
292
|
/** Terminal: the run failed as a whole (a single surface failing is not this). */
|
|
288
|
-
export
|
|
293
|
+
export type MatchStreamErrorEvent = {
|
|
289
294
|
type: "error";
|
|
290
295
|
message: string;
|
|
291
|
-
|
|
296
|
+
/**
|
|
297
|
+
* `upstream_unavailable`: a provider (vision, embedding, vector index) could
|
|
298
|
+
* not answer, so nothing was looked at and a retry may succeed. `failed`: the
|
|
299
|
+
* run broke on something else. Absent from servers older than MG-730.
|
|
300
|
+
*/
|
|
301
|
+
reason?: MatchErrorReason;
|
|
302
|
+
/** True only for `upstream_unavailable`. Absent from servers older than MG-730. */
|
|
303
|
+
retriable?: boolean;
|
|
304
|
+
};
|
|
292
305
|
/** One frame of the Match progress stream, discriminated by `type`. */
|
|
293
306
|
export type MatchStreamEvent = MatchStreamStepEvent | MatchStreamClassifiedEvent | MatchStreamSurfacesEvent | MatchStreamSurfaceEvent | MatchStreamHeartbeatEvent | MatchStreamCompleteEvent | MatchStreamErrorEvent;
|
package/match-stream-types.js
CHANGED
|
@@ -4,6 +4,8 @@ export const MATCH_IMAGE_TYPES = [
|
|
|
4
4
|
"installation",
|
|
5
5
|
"flatplan",
|
|
6
6
|
];
|
|
7
|
+
/** Why a Match run ended on an `error` frame (MG-730). */
|
|
8
|
+
export const MATCH_ERROR_REASONS = ["upstream_unavailable", "failed"];
|
|
7
9
|
/** What a detected surface is FOR — the element the material would serve. */
|
|
8
10
|
export const MATCH_MATERIAL_FUNCTIONS = [
|
|
9
11
|
"flooring",
|
package/match-stream.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpTransport } from "./http-transport.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { MatchStreamEvent } from "./match-stream-types.js";
|
|
3
3
|
import type { MatchInput } from "./match-types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Decode one NDJSON line into a validated {@link MatchStreamEvent}, raising a
|
|
@@ -7,7 +7,7 @@ import type { MatchInput } from "./match-types.js";
|
|
|
7
7
|
* for `contract-parity.test.ts`, which re-checks it against the real Zod schema;
|
|
8
8
|
* it is not part of the published surface.
|
|
9
9
|
*/
|
|
10
|
-
export declare
|
|
10
|
+
export declare const decodeMatchStreamEvent: (line: string) => MatchStreamEvent;
|
|
11
11
|
/**
|
|
12
12
|
* Stream Match progress as an async iterator. Iterates until the run reaches a
|
|
13
13
|
* `complete` or an `error` event, and raises if the connection ends before one
|
package/match-stream.js
CHANGED
|
@@ -35,10 +35,9 @@
|
|
|
35
35
|
* objects (`createMatchNdjsonStream`), so `parse` output is not what the wire
|
|
36
36
|
* carries; reshaping to match it would invent a normalisation that never
|
|
37
37
|
* happened.
|
|
38
|
-
* 2. Stripping needs the COMPLETE key inventory of `searchResultItemSchema
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* wider surface than the validation it would serve.
|
|
38
|
+
* 2. Stripping needs the COMPLETE key inventory of `searchResultItemSchema`
|
|
39
|
+
* — a hand-mirror that would have to stay in step forever, and a wider
|
|
40
|
+
* surface than the validation it would serve.
|
|
42
41
|
* 3. An additive server field then reaches consumers without an SDK release,
|
|
43
42
|
* which matters while new EVENT types still require one (MG-658).
|
|
44
43
|
* The ONE exception is `.default([])`: an omitted `results` / `inferredCategories`
|
|
@@ -50,14 +49,12 @@
|
|
|
50
49
|
* optional string, `MatchCategoryInference` items, and every
|
|
51
50
|
* `SearchResultItem` in `results[]` are checked exhaustively. Item validation is
|
|
52
51
|
* the shared `checkSearchResultItem` (`search-result-validation.ts`), which
|
|
53
|
-
* mirrors `searchResultItemSchema` and its whole closure
|
|
54
|
-
* `candidateVerdictSchema` — including both of its `.superRefine()` cross-field
|
|
55
|
-
* invariants.
|
|
52
|
+
* mirrors `searchResultItemSchema` and its whole closure.
|
|
56
53
|
*/
|
|
57
54
|
import { MaterialGraphError } from "./errors.js";
|
|
58
|
-
import { MATCH_IMAGE_TYPES, MATCH_MATERIAL_FUNCTIONS, MATCH_STREAM_EVENT_TYPES, MATCH_SURFACE_STATUSES, MATCH_WORKFLOW_STEPS, } from "./match-stream-types.js";
|
|
55
|
+
import { MATCH_ERROR_REASONS, MATCH_IMAGE_TYPES, MATCH_MATERIAL_FUNCTIONS, MATCH_STREAM_EVENT_TYPES, MATCH_SURFACE_STATUSES, MATCH_WORKFLOW_STEPS, } from "./match-stream-types.js";
|
|
59
56
|
import { readNdjsonLines } from "./ndjson.js";
|
|
60
|
-
import { findSdkOperation
|
|
57
|
+
import { findSdkOperation } from "./operations.js";
|
|
61
58
|
import { checkSearchResultItems } from "./search-result-validation.js";
|
|
62
59
|
import { requireArray, requireBoundedNumber, requireDefaultedArray, requireEnum, requireIntegerAtLeast, requireNumber, requireOptionalString, requireRecord, requireString, } from "./validation.js";
|
|
63
60
|
/** The NDJSON media type the Match route negotiates the scene workflow on. */
|
|
@@ -70,34 +67,37 @@ const BOUNDING_BOX_CORNERS = Object.freeze([
|
|
|
70
67
|
"x1",
|
|
71
68
|
"y1",
|
|
72
69
|
]);
|
|
73
|
-
|
|
70
|
+
const operation = (operationId) => {
|
|
74
71
|
const found = findSdkOperation(operationId);
|
|
75
|
-
if (!found)
|
|
72
|
+
if (!found) {
|
|
76
73
|
throw MaterialGraphError.unexpected(`Unknown SDK operation: ${operationId}`);
|
|
74
|
+
}
|
|
77
75
|
return found;
|
|
78
|
-
}
|
|
76
|
+
};
|
|
79
77
|
/**
|
|
80
78
|
* The failure shape every check in this module raises: one message naming the
|
|
81
79
|
* offending path and quoting a bounded slice of the frame.
|
|
82
80
|
*/
|
|
83
|
-
|
|
81
|
+
const frameFailure = (line) => {
|
|
84
82
|
const preview = line.length > MAX_FRAME_PREVIEW
|
|
85
83
|
? `${line.slice(0, MAX_FRAME_PREVIEW)}…`
|
|
86
84
|
: line;
|
|
87
85
|
return (reason) => MaterialGraphError.unexpected(`MaterialGraph match stream sent a malformed NDJSON frame: ${reason}. Frame: ${preview}`);
|
|
88
|
-
}
|
|
86
|
+
};
|
|
89
87
|
/** Check one `MatchCategoryInference` item. */
|
|
90
|
-
|
|
88
|
+
const checkCategoryInference = (value, what, fail) => {
|
|
91
89
|
const record = requireRecord(value, what, fail);
|
|
92
90
|
requireString(record.productTypeId, `${what}.productTypeId`, fail);
|
|
93
91
|
requireString(record.displayName, `${what}.displayName`, fail);
|
|
94
92
|
requireNumber(record.boost, `${what}.boost`, fail);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
items.forEach((item, index) =>
|
|
98
|
-
}
|
|
93
|
+
};
|
|
94
|
+
const checkCategoryInferences = (items, what, fail) => {
|
|
95
|
+
items.forEach((item, index) => {
|
|
96
|
+
checkCategoryInference(item, `${what}[${index}]`, fail);
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
99
|
/** Check one detected surface — identity, function, confidence, and its box. */
|
|
100
|
-
|
|
100
|
+
const checkDetectedSurface = (value, what, fail) => {
|
|
101
101
|
const record = requireRecord(value, what, fail);
|
|
102
102
|
requireString(record.id, `${what}.id`, fail);
|
|
103
103
|
requireString(record.label, `${what}.label`, fail);
|
|
@@ -110,12 +110,12 @@ function checkDetectedSurface(value, what, fail) {
|
|
|
110
110
|
for (const corner of BOUNDING_BOX_CORNERS) {
|
|
111
111
|
requireNumber(box[corner], `${what}.boundingBox.${corner}`, fail);
|
|
112
112
|
}
|
|
113
|
-
}
|
|
113
|
+
};
|
|
114
114
|
/**
|
|
115
115
|
* Check one surface's matching state and fill the canonical `.default([])`
|
|
116
116
|
* arrays, so a consumer never meets `undefined` where the type promises an array.
|
|
117
117
|
*/
|
|
118
|
-
|
|
118
|
+
const readSurfaceResult = (value, what, fail) => {
|
|
119
119
|
const record = requireRecord(value, what, fail);
|
|
120
120
|
checkDetectedSurface(record.surface, `${what}.surface`, fail);
|
|
121
121
|
requireEnum(record.status, MATCH_SURFACE_STATUSES, `${what}.status`, fail);
|
|
@@ -124,8 +124,8 @@ function readSurfaceResult(value, what, fail) {
|
|
|
124
124
|
checkSearchResultItems(results, `${what}.results`, fail);
|
|
125
125
|
const inferredCategories = requireDefaultedArray(record.inferredCategories, `${what}.inferredCategories`, fail);
|
|
126
126
|
checkCategoryInferences(inferredCategories, `${what}.inferredCategories`, fail);
|
|
127
|
-
return { ...record,
|
|
128
|
-
}
|
|
127
|
+
return { ...record, inferredCategories, results };
|
|
128
|
+
};
|
|
129
129
|
/**
|
|
130
130
|
* `matchRunTotalsSchema` — four non-negative integers plus the TWO cross-field
|
|
131
131
|
* invariants its `.superRefine()` enforces. Relational rather than field-wise,
|
|
@@ -135,7 +135,7 @@ function readSurfaceResult(value, what, fail) {
|
|
|
135
135
|
* `matchesSoFar` is deliberately unbounded against the surface counts — one
|
|
136
136
|
* surface can carry many results and another none, so no ratio is principled.
|
|
137
137
|
*/
|
|
138
|
-
|
|
138
|
+
const checkRunTotals = (value, what, fail) => {
|
|
139
139
|
const record = requireRecord(value, what, fail);
|
|
140
140
|
const detected = requireIntegerAtLeast(record.surfacesDetected, `${what}.surfacesDetected`, fail, 0);
|
|
141
141
|
const resolved = requireIntegerAtLeast(record.surfacesResolved, `${what}.surfacesResolved`, fail, 0);
|
|
@@ -149,9 +149,9 @@ function checkRunTotals(value, what, fail) {
|
|
|
149
149
|
if (resolved > detected) {
|
|
150
150
|
throw fail(`${what}.surfacesResolved (${resolved}) exceeds ${what}.surfacesDetected (${detected}) — a run cannot resolve more surfaces than it found`);
|
|
151
151
|
}
|
|
152
|
-
}
|
|
152
|
+
};
|
|
153
153
|
/** Check the terminal workflow result — image type, surfaces, optional roll-ups. */
|
|
154
|
-
|
|
154
|
+
const readWorkflowResult = (value, what, fail) => {
|
|
155
155
|
const record = requireRecord(value, what, fail);
|
|
156
156
|
requireEnum(record.imageType, MATCH_IMAGE_TYPES, `${what}.imageType`, fail);
|
|
157
157
|
const surfaces = requireArray(record.surfaces, `${what}.surfaces`, fail).map((surface, index) => readSurfaceResult(surface, `${what}.surfaces[${index}]`, fail));
|
|
@@ -164,26 +164,30 @@ function readWorkflowResult(value, what, fail) {
|
|
|
164
164
|
checkCategoryInferences(requireArray(record.inferredCategories, `${what}.inferredCategories`, fail), `${what}.inferredCategories`, fail);
|
|
165
165
|
}
|
|
166
166
|
return { ...record, surfaces };
|
|
167
|
-
}
|
|
167
|
+
};
|
|
168
168
|
/**
|
|
169
169
|
* Validate one frame against the canonical contract and apply the schema's
|
|
170
170
|
* `.default([])` fills, returning the event as a plain record. Unknown keys ride
|
|
171
171
|
* through untouched — see the pass-through note in the module header.
|
|
172
172
|
*/
|
|
173
|
-
|
|
173
|
+
const readFrame = (value, fail) => {
|
|
174
174
|
const record = requireRecord(value, "the frame", fail);
|
|
175
175
|
const type = requireString(record.type, "`type`", fail);
|
|
176
176
|
switch (type) {
|
|
177
|
-
case "step":
|
|
177
|
+
case "step": {
|
|
178
178
|
requireEnum(record.step, MATCH_WORKFLOW_STEPS, "`step`", fail);
|
|
179
179
|
requireString(record.message, "`message`", fail);
|
|
180
180
|
return record;
|
|
181
|
-
|
|
181
|
+
}
|
|
182
|
+
case "classified": {
|
|
182
183
|
requireEnum(record.imageType, MATCH_IMAGE_TYPES, "`imageType`", fail);
|
|
183
184
|
return record;
|
|
185
|
+
}
|
|
184
186
|
case "surfaces": {
|
|
185
187
|
const surfaces = requireArray(record.surfaces, "`surfaces`", fail);
|
|
186
|
-
surfaces.forEach((surface, index) =>
|
|
188
|
+
surfaces.forEach((surface, index) => {
|
|
189
|
+
checkDetectedSurface(surface, `surfaces[${index}]`, fail);
|
|
190
|
+
});
|
|
187
191
|
return record;
|
|
188
192
|
}
|
|
189
193
|
case "surface": {
|
|
@@ -193,37 +197,48 @@ function readFrame(value, fail) {
|
|
|
193
197
|
checkRunTotals(record.totals, "`totals`", fail);
|
|
194
198
|
return { ...record, surface };
|
|
195
199
|
}
|
|
196
|
-
case "heartbeat":
|
|
200
|
+
case "heartbeat": {
|
|
197
201
|
// Payload-free by contract — there is nothing else to check.
|
|
198
202
|
return record;
|
|
199
|
-
|
|
203
|
+
}
|
|
204
|
+
case "complete": {
|
|
200
205
|
return {
|
|
201
206
|
...record,
|
|
202
207
|
result: readWorkflowResult(record.result, "`result`", fail),
|
|
203
208
|
};
|
|
204
|
-
|
|
209
|
+
}
|
|
210
|
+
case "error": {
|
|
205
211
|
requireString(record.message, "`message`", fail);
|
|
212
|
+
if (record.reason !== undefined) {
|
|
213
|
+
requireEnum(record.reason, MATCH_ERROR_REASONS, "`reason`", fail);
|
|
214
|
+
}
|
|
215
|
+
if (record.retriable !== undefined &&
|
|
216
|
+
typeof record.retriable !== "boolean") {
|
|
217
|
+
throw fail("`retriable` must be a boolean");
|
|
218
|
+
}
|
|
206
219
|
return record;
|
|
207
|
-
|
|
220
|
+
}
|
|
221
|
+
default: {
|
|
208
222
|
throw fail(`\`type\` is not a known Match stream event (${type})`);
|
|
223
|
+
}
|
|
209
224
|
}
|
|
210
|
-
}
|
|
225
|
+
};
|
|
211
226
|
/**
|
|
212
227
|
* The single narrowing boundary of this module. {@link readFrame} has already
|
|
213
228
|
* proven every field the union's shape depends on; re-checking the discriminant
|
|
214
229
|
* here keeps the assertion from being vacuous.
|
|
215
230
|
*/
|
|
216
|
-
|
|
231
|
+
const assertMatchStreamEvent = (frame, fail) => {
|
|
217
232
|
const record = requireRecord(frame, "the frame", fail);
|
|
218
233
|
requireEnum(record.type, MATCH_STREAM_EVENT_TYPES, "`type`", fail);
|
|
219
|
-
}
|
|
234
|
+
};
|
|
220
235
|
/**
|
|
221
236
|
* Decode one NDJSON line into a validated {@link MatchStreamEvent}, raising a
|
|
222
237
|
* {@link MaterialGraphError} on anything the contract does not allow. Exported
|
|
223
238
|
* for `contract-parity.test.ts`, which re-checks it against the real Zod schema;
|
|
224
239
|
* it is not part of the published surface.
|
|
225
240
|
*/
|
|
226
|
-
export
|
|
241
|
+
export const decodeMatchStreamEvent = (line) => {
|
|
227
242
|
const fail = frameFailure(line);
|
|
228
243
|
let parsed;
|
|
229
244
|
try {
|
|
@@ -235,10 +250,8 @@ export function decodeMatchStreamEvent(line) {
|
|
|
235
250
|
const frame = readFrame(parsed, fail);
|
|
236
251
|
assertMatchStreamEvent(frame, fail);
|
|
237
252
|
return frame;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return event.type === "complete" || event.type === "error";
|
|
241
|
-
}
|
|
253
|
+
};
|
|
254
|
+
const isTerminal = (event) => event.type === "complete" || event.type === "error";
|
|
242
255
|
/**
|
|
243
256
|
* Stream Match progress as an async iterator. Iterates until the run reaches a
|
|
244
257
|
* `complete` or an `error` event, and raises if the connection ends before one
|
|
@@ -251,33 +264,38 @@ function isTerminal(event) {
|
|
|
251
264
|
* it cancelled, so the truncated-stream error is not raised for it.
|
|
252
265
|
*/
|
|
253
266
|
export async function* matchStream(transport, input, signal) {
|
|
254
|
-
if (signal?.aborted === true)
|
|
267
|
+
if (signal?.aborted === true) {
|
|
255
268
|
return;
|
|
269
|
+
}
|
|
256
270
|
// Our own controller, so abandoning the iterator cancels the request too.
|
|
257
271
|
const controller = new AbortController();
|
|
258
|
-
const forwardAbort = () =>
|
|
272
|
+
const forwardAbort = () => {
|
|
273
|
+
controller.abort();
|
|
274
|
+
};
|
|
259
275
|
signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
260
276
|
try {
|
|
261
277
|
let body;
|
|
262
278
|
try {
|
|
263
279
|
body = await transport.openStream(operation("match_material"), {
|
|
280
|
+
accept: MATCH_STREAM_ACCEPT,
|
|
264
281
|
body: input,
|
|
265
282
|
signal: controller.signal,
|
|
266
|
-
accept: MATCH_STREAM_ACCEPT,
|
|
267
283
|
subject: "match stream",
|
|
268
284
|
});
|
|
269
285
|
}
|
|
270
286
|
catch (error) {
|
|
271
287
|
// Cancelled before the response headers landed — the caller's own stop.
|
|
272
|
-
if (controller.signal.aborted)
|
|
288
|
+
if (controller.signal.aborted) {
|
|
273
289
|
return;
|
|
290
|
+
}
|
|
274
291
|
throw error;
|
|
275
292
|
}
|
|
276
293
|
for await (const line of readNdjsonLines(body, controller.signal)) {
|
|
277
294
|
const event = decodeMatchStreamEvent(line);
|
|
278
295
|
yield event;
|
|
279
|
-
if (isTerminal(event))
|
|
296
|
+
if (isTerminal(event)) {
|
|
280
297
|
return;
|
|
298
|
+
}
|
|
281
299
|
}
|
|
282
300
|
if (!controller.signal.aborted) {
|
|
283
301
|
throw MaterialGraphError.unexpected("MaterialGraph match stream ended without a `complete` or `error` event — the connection was truncated");
|
package/match-types.d.ts
CHANGED
|
@@ -9,14 +9,15 @@ import type { CorpusScope } from "./corpus-scope-types.js";
|
|
|
9
9
|
* along its natural seam: everything a caller of `client.match()` needs, and
|
|
10
10
|
* nothing else. The STREAMING contracts live in `match-stream-types.ts`.
|
|
11
11
|
*/
|
|
12
|
+
import type { MatchWorkflowResult } from "./match-stream-types.js";
|
|
12
13
|
import type { SearchFilter } from "./search-refinement-types.js";
|
|
13
14
|
import type { OpenEnum, PaletteSwatch, SearchResultItem } from "./types.js";
|
|
14
15
|
/** An inferred product-type category from the query image, with its boost. */
|
|
15
|
-
export
|
|
16
|
+
export type MatchCategoryInference = {
|
|
16
17
|
productTypeId: string;
|
|
17
18
|
displayName: string;
|
|
18
19
|
boost: number;
|
|
19
|
-
}
|
|
20
|
+
};
|
|
20
21
|
/**
|
|
21
22
|
* The Material Match response (non-streaming form).
|
|
22
23
|
*
|
|
@@ -31,13 +32,13 @@ export interface MatchCategoryInference {
|
|
|
31
32
|
* means the material carries no palette on record — UNKNOWN, never "no colour"
|
|
32
33
|
* and never "the wrong colour".
|
|
33
34
|
*/
|
|
34
|
-
export
|
|
35
|
+
export type MatchColourReading = {
|
|
35
36
|
variantId: string;
|
|
36
37
|
colourDistance: number | null;
|
|
37
38
|
colourBand: string | null;
|
|
38
39
|
measurable: boolean;
|
|
39
|
-
}
|
|
40
|
-
export
|
|
40
|
+
};
|
|
41
|
+
export type MatchResponse = {
|
|
41
42
|
/**
|
|
42
43
|
* Result attribution (MG-1131). `basis` is always `"match"` here — this route
|
|
43
44
|
* has no `mode`, so this is the field that lets a Match outcome be grouped
|
|
@@ -73,9 +74,32 @@ export interface MatchResponse {
|
|
|
73
74
|
/** The query crop's own extracted palette — what the colour stage compared against. */
|
|
74
75
|
segmentPalette?: PaletteSwatch[] | null;
|
|
75
76
|
note?: string;
|
|
76
|
-
}
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* What a BUFFERED `match()` actually answers with, either shape.
|
|
80
|
+
*
|
|
81
|
+
* An IMAGE input (`imageBase64` or `imageUrl`) runs the same workflow the
|
|
82
|
+
* stream lane runs — image kind first, then the swatch shortcut or the scene
|
|
83
|
+
* route — and answers in {@link MatchWorkflowResult}. A `vector` input has no
|
|
84
|
+
* bytes to read a kind from and keeps the thin {@link MatchResponse} leaf.
|
|
85
|
+
* The server declares exactly this as an `anyOf` of the two.
|
|
86
|
+
*
|
|
87
|
+
* TELL THEM APART BY A FIELD THAT ONLY ONE HAS, never by what you sent:
|
|
88
|
+
* `imageType` is required on the workflow shape and absent from the leaf, and
|
|
89
|
+
* `qualityFloor` is required on the leaf and absent from the workflow shape.
|
|
90
|
+
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* const answer = await client.match({ imageBase64 });
|
|
93
|
+
* if ("imageType" in answer) {
|
|
94
|
+
* for (const surface of answer.surfaces) render(surface);
|
|
95
|
+
* } else {
|
|
96
|
+
* report(answer.qualityFloor, answer.droppedBelowFloor);
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export type MatchAnswer = MatchResponse | MatchWorkflowResult;
|
|
77
101
|
/** The Material Match request body — exactly one of `imageBase64`, `imageUrl`, `vector`. */
|
|
78
|
-
export
|
|
102
|
+
export type MatchInput = {
|
|
79
103
|
imageBase64?: string;
|
|
80
104
|
imageUrl?: string;
|
|
81
105
|
vector?: string;
|
|
@@ -111,4 +135,4 @@ export interface MatchInput {
|
|
|
111
135
|
preferences?: SearchFilter[];
|
|
112
136
|
/** Opt in to seed-relative verdict cells; inert unless the inner search carries a variant seed. */
|
|
113
137
|
verdicts?: boolean;
|
|
114
|
-
}
|
|
138
|
+
};
|