@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
package/types.d.ts
CHANGED
|
@@ -1,35 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hand-mirrored wire types for the MaterialGraph public API (spec 0.5.0).
|
|
3
|
-
*
|
|
4
|
-
* These interfaces intentionally have NO runtime dependency on any
|
|
5
|
-
* `@materialgraph/*` package — they are a standalone, publishable mirror of the
|
|
6
|
-
* shapes in `apps/web/public/openapi.yaml` and the captured fixtures under
|
|
7
|
-
* `docs/surface/fixtures/v0.5.0/`. The `contract-parity` test re-checks them
|
|
8
|
-
* against the real Zod schemas so the mirror cannot silently drift.
|
|
9
|
-
* TOLERANT-PARSING CONTRACT: the public surface declares open response enums —
|
|
10
|
-
* "clients must tolerate unknown enum values and unknown fields". Every union
|
|
11
|
-
* that mirrors a server enum is therefore widened with `(string & {})` so a new
|
|
12
|
-
* server value still type-checks, and objects that may grow fields are read
|
|
13
|
-
* structurally (unknown keys pass through untouched — the client never strips).
|
|
14
|
-
*
|
|
15
|
-
* READING CONTRACT — the three things callers get wrong, stated once here and
|
|
16
|
-
* repeated on the types where each one bites:
|
|
17
|
-
* 1. **Ids are opaque.** Round-trip them verbatim; never parse or construct
|
|
18
|
-
* one. See `./identifiers.ts` for which strings are opaque tokens and which
|
|
19
|
-
* are canonical vocabulary you discover.
|
|
20
|
-
* 2. **Absent is not false.** An optional field that is missing means UNKNOWN,
|
|
21
|
-
* not "known to be absent in reality" — most dangerously for booleans.
|
|
22
|
-
* Values carry `confidence` and `sourceBasis`; report them as claims.
|
|
23
|
-
* 3. **A page is not a corpus.** `results.length` is this page.
|
|
24
|
-
* {@link CountedTotal} — with the `basis` it states — is the claim about the
|
|
25
|
-
* match set. Follow the cursor for the rest, and never report a page size as
|
|
26
|
-
* a total.
|
|
27
|
-
*/
|
|
28
|
-
import type { ByColourSwatch } from "./by-colour-types.js";
|
|
29
1
|
import type { BySpecVerdict } from "./by-spec-types.js";
|
|
30
2
|
import type { Cursor, ProductId, VariantId } from "./identifiers.js";
|
|
31
3
|
import type { MaterialDetail, MaterialTile } from "./material-types.js";
|
|
32
|
-
import type {
|
|
4
|
+
import type { SearchDisambiguation, SearchSuggestion } from "./search-refinement-types.js";
|
|
33
5
|
/** A widened string-literal union: known members autocomplete, unknown values still type-check. */
|
|
34
6
|
export type OpenEnum<T extends string> = T | (string & {});
|
|
35
7
|
/**
|
|
@@ -104,6 +76,15 @@ export interface VisualMeaning {
|
|
|
104
76
|
version?: string;
|
|
105
77
|
appliedAt?: string;
|
|
106
78
|
}
|
|
79
|
+
/** One member of a collapsed result's {@link SearchResultItem.anatomy}. */
|
|
80
|
+
export interface SearchAnatomyMember {
|
|
81
|
+
/** Opaque — round-trip verbatim. */
|
|
82
|
+
variantId: VariantId;
|
|
83
|
+
variantName: string | null;
|
|
84
|
+
color: string | null;
|
|
85
|
+
score: number;
|
|
86
|
+
signals: SearchResultSignals;
|
|
87
|
+
}
|
|
107
88
|
/**
|
|
108
89
|
* One material in a search / match result list.
|
|
109
90
|
*
|
|
@@ -121,10 +102,14 @@ export interface SearchResultItem {
|
|
|
121
102
|
brandSlug: string | null;
|
|
122
103
|
productId: ProductId | null;
|
|
123
104
|
/**
|
|
124
|
-
* The variant's Material Bank SKU — the
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
105
|
+
* The variant's Material Bank SKU — the ONE EXTERNAL identifier on a result,
|
|
106
|
+
* in deliberate contrast to the opaque `variantId` above. `variantId` is ours
|
|
107
|
+
* and means nothing anywhere else; this is the id you are expected to match
|
|
108
|
+
* against Material Bank's own catalogue, so a result list can be joined to a
|
|
109
|
+
* cart, an order or an analytics event without a per-result lookup.
|
|
110
|
+
*
|
|
111
|
+
* Populated on EVERY lane: `search()` (all four arms), `match()`, and
|
|
112
|
+
* `matchStream()` frames.
|
|
128
113
|
*
|
|
129
114
|
* ABSENT means no MB identity is known for the variant — never that the SKU
|
|
130
115
|
* cannot be ordered. Presence is the orderability tell; absence is silence.
|
|
@@ -150,6 +135,38 @@ export interface SearchResultItem {
|
|
|
150
135
|
hasImage: boolean;
|
|
151
136
|
palette: PaletteSwatch[];
|
|
152
137
|
colourMeta?: ColourMeta;
|
|
138
|
+
/**
|
|
139
|
+
* Present when this material's imagery carries nothing beyond ONE colour, so
|
|
140
|
+
* a surface should render that colour rather than the photograph — a paint's
|
|
141
|
+
* photo is a flat fill, and the colour we hold structured beats a JPEG of it.
|
|
142
|
+
*
|
|
143
|
+
* The hex is EXTRACTED FROM the product image (a visual likeness), never a
|
|
144
|
+
* manufacturer-declared colour specification; do not present it as one.
|
|
145
|
+
* ABSENT means the question was not established (unknown product type,
|
|
146
|
+
* unextracted or multi-swatch palette) and the image should be rendered — it
|
|
147
|
+
* NEVER asserts "this is not a coating".
|
|
148
|
+
*/
|
|
149
|
+
colourChip?: {
|
|
150
|
+
hex: string;
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* EXPLAIN-only: the two identities a COLLAPSED result holds, which are not the
|
|
154
|
+
* same thing and are routinely confused.
|
|
155
|
+
*
|
|
156
|
+
* - `representative` — the RANKING identity. Its score is the group's cursor
|
|
157
|
+
* key and the item's `signals` describe IT, not the photograph on the card.
|
|
158
|
+
* Omitted when the face IS the representative.
|
|
159
|
+
* - the item's own `variantId` — the DISPLAY identity (the face).
|
|
160
|
+
*
|
|
161
|
+
* `siblingsTruncated` says the sibling list was capped: a product can hold
|
|
162
|
+
* hundreds of colourways, so absence of a sibling is never evidence it does
|
|
163
|
+
* not exist.
|
|
164
|
+
*/
|
|
165
|
+
anatomy?: {
|
|
166
|
+
representative?: SearchAnatomyMember;
|
|
167
|
+
siblings?: SearchAnatomyMember[];
|
|
168
|
+
siblingsTruncated?: boolean;
|
|
169
|
+
};
|
|
153
170
|
/** Fused score — range [0, sum of active weights]; see `maxScore`. */
|
|
154
171
|
score: number;
|
|
155
172
|
/**
|
|
@@ -189,6 +206,17 @@ export interface SearchResultItem {
|
|
|
189
206
|
* dropped by default. Appended after the kept results.
|
|
190
207
|
*/
|
|
191
208
|
belowFloor?: boolean;
|
|
209
|
+
/**
|
|
210
|
+
* Present (true) only on Match rows the colour probe admitted (match only):
|
|
211
|
+
* this material is as close in colour as the catalogue gets, and its image
|
|
212
|
+
* similarity is BELOW the match quality floor by design. It is on the page
|
|
213
|
+
* because nothing image-retrieved is nearer in colour, not because it cleared
|
|
214
|
+
* the floor — present it as a colour answer or suppress it knowingly. Absent
|
|
215
|
+
* (never `false`) on rows that came through image retrieval. Distinct from
|
|
216
|
+
* {@link SearchResultItem.belowFloor}, which marks rejects returned only under
|
|
217
|
+
* `includeBelowFloor`.
|
|
218
|
+
*/
|
|
219
|
+
admittedOnColour?: boolean;
|
|
192
220
|
siblingCount?: number;
|
|
193
221
|
visualMeanings?: VisualMeaning[];
|
|
194
222
|
/**
|
|
@@ -223,11 +251,17 @@ export interface SearchCardItem {
|
|
|
223
251
|
* A feed total with its honesty basis — THE CLAIM ABOUT THE MATCH SET, which is
|
|
224
252
|
* a different number from `results.length` (this page).
|
|
225
253
|
*
|
|
226
|
-
* `exact` — the true count
|
|
227
|
-
* materials match".
|
|
254
|
+
* `exact` — the true count. Say "473 materials match".
|
|
228
255
|
* `lowerBound` — a floor (cap hit, ANN-pool bound, or a degraded count). Say
|
|
229
256
|
* "at least 1,000", never "1,000". Never a silent approximation.
|
|
230
257
|
*
|
|
258
|
+
* The CAP differs by lane, and only `lowerBound` ever tells you one was hit. The
|
|
259
|
+
* SEARCH feed counts exactly to 1,000 and reports a floor beyond it, because it
|
|
260
|
+
* counts over attribute and tsquery universes where an unbounded count is a
|
|
261
|
+
* timeout. The BROWSE feed ({@link BrowseCounts}) is uncapped — a plain count
|
|
262
|
+
* over the same predicate its page scan ran, so a category of 66,000 reports
|
|
263
|
+
* 66,000 exactly. Read `basis`; never assume a ceiling.
|
|
264
|
+
*
|
|
231
265
|
* A response with no `counts` at all states no total: `count: true` is opt-in,
|
|
232
266
|
* and the absence of a total is not a total of zero.
|
|
233
267
|
*/
|
|
@@ -244,6 +278,54 @@ export interface SearchCounts {
|
|
|
244
278
|
variants: CountedTotal;
|
|
245
279
|
products?: CountedTotal;
|
|
246
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* One value within a facet's counts: the CANONICAL token (send it back verbatim
|
|
283
|
+
* as a filter term), its display label, and how many variants in the counted
|
|
284
|
+
* set carry it.
|
|
285
|
+
*/
|
|
286
|
+
export interface SearchFacetValue {
|
|
287
|
+
value: string;
|
|
288
|
+
label: string;
|
|
289
|
+
variants: number;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* One facet's counts. `basis` is the honesty label, and — per the negotiated
|
|
293
|
+
* MG-1010 contract — its union is CLOSED: exactly `"exact"` or `"lowerBound"`,
|
|
294
|
+
* never widened, which is why it is not an {@link OpenEnum}. `exact` — each
|
|
295
|
+
* `variants` figure is a true count under the query's whole predicate
|
|
296
|
+
* (filter-driven queries only). `lowerBound` — counted over the ranked
|
|
297
|
+
* candidate pool (`SearchFacets.poolSize`), so each figure is a floor; a
|
|
298
|
+
* pool-counted value is NEVER `exact`. `truncated` is ALWAYS present: true
|
|
299
|
+
* when more distinct values existed beyond the reported list.
|
|
300
|
+
*/
|
|
301
|
+
export interface SearchFacetCount {
|
|
302
|
+
values: SearchFacetValue[];
|
|
303
|
+
truncated: boolean;
|
|
304
|
+
basis: "exact" | "lowerBound";
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* A requested facet that was not counted, with its reason: `"unsupported"` —
|
|
308
|
+
* the id is known but facet counting does not cover it; `"timeout"` — the
|
|
309
|
+
* facet stage's own time budget expired, and the RESULTS are unaffected (the
|
|
310
|
+
* rail could not count; the page is intact). Render it as "couldn't count",
|
|
311
|
+
* never as zero.
|
|
312
|
+
*/
|
|
313
|
+
export interface SearchAbsentFacet {
|
|
314
|
+
facetId: string;
|
|
315
|
+
reason: OpenEnum<"unsupported" | "timeout">;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Per-query facet counts (MG-1010) — present ONLY when the request sent
|
|
319
|
+
* `facetCounts`. `counts` is keyed by requested facet id; every requested
|
|
320
|
+
* facet not in `absent` has an entry, including empty ones. `poolSize` is
|
|
321
|
+
* present exactly when the counts are pool-derived.
|
|
322
|
+
*/
|
|
323
|
+
export interface SearchFacets {
|
|
324
|
+
poolSize?: number;
|
|
325
|
+
requested: string[];
|
|
326
|
+
absent: SearchAbsentFacet[];
|
|
327
|
+
counts: Record<string, SearchFacetCount>;
|
|
328
|
+
}
|
|
247
329
|
/** The single candidate source a query resolved to. */
|
|
248
330
|
export type SearchMode = OpenEnum<"image" | "colour" | "text" | "lexical">;
|
|
249
331
|
/** Rerank-term report (absent in colour mode — there colour IS the source). */
|
|
@@ -275,8 +357,42 @@ export interface SearchResponse {
|
|
|
275
357
|
cursor?: Cursor;
|
|
276
358
|
rerank?: SearchRerankReport;
|
|
277
359
|
counts?: SearchCounts;
|
|
360
|
+
/**
|
|
361
|
+
* The `#rrggbb` colours the colour signal ACTUALLY ran on, after
|
|
362
|
+
* minor-proportion accents were dropped. Present whenever the request carried
|
|
363
|
+
* explicit `colours`. Compare against your request: a difference means the
|
|
364
|
+
* search that ran is not the search that was asked for.
|
|
365
|
+
*/
|
|
366
|
+
effectiveColours?: string[];
|
|
367
|
+
/**
|
|
368
|
+
* Colours removed from the query for carrying a `weight` below the 5% accent
|
|
369
|
+
* threshold. EMPTY is the normal case. Non-empty and the dropped colours are
|
|
370
|
+
* absent from colour retrieval and ranking entirely — they did not merely
|
|
371
|
+
* rank low. To keep a small accent in the query, give it `role: "accent"`.
|
|
372
|
+
*/
|
|
373
|
+
droppedColours?: string[];
|
|
374
|
+
/** Per-query facet counts — present ONLY when the request sent `facetCounts`. */
|
|
375
|
+
facets?: SearchFacets;
|
|
278
376
|
note?: string;
|
|
279
377
|
timings?: Record<string, number>;
|
|
378
|
+
/** A detected mood term overridden by a stronger colour reading. */
|
|
379
|
+
supersededMoodId?: string;
|
|
380
|
+
/**
|
|
381
|
+
* Present when the query had TWO defensible readings. Says which one ran and
|
|
382
|
+
* offers the other as a complete executable request.
|
|
383
|
+
*/
|
|
384
|
+
disambiguation?: SearchDisambiguation;
|
|
385
|
+
/**
|
|
386
|
+
* The corpus this page AND its `counts` were drawn from — present ONLY when
|
|
387
|
+
* the request narrowed it. ABSENT means the whole graph: every material
|
|
388
|
+
* MaterialGraph knows, orderable or not. Read it alongside `counts`: a short
|
|
389
|
+
* page under a scope is a small SCOPED corpus, not a truncated one.
|
|
390
|
+
*/
|
|
391
|
+
corpusScope?: {
|
|
392
|
+
scope: OpenEnum<"all" | "materialBankOrderable">;
|
|
393
|
+
/** What the scope admits, in one sentence, for a human reader. */
|
|
394
|
+
description: string;
|
|
395
|
+
};
|
|
280
396
|
/** Echo of the resolved query input (raw image bytes/vector are never echoed). */
|
|
281
397
|
query?: Record<string, unknown>;
|
|
282
398
|
/** A `getVariantBrief` echo of the seed variant, present only in `variantId` seed mode. */
|
|
@@ -287,47 +403,6 @@ export interface SearchCardResponse extends Omit<SearchResponse, "view" | "resul
|
|
|
287
403
|
view: OpenEnum<"card">;
|
|
288
404
|
results: SearchCardItem[];
|
|
289
405
|
}
|
|
290
|
-
/** One item from the pure colour-scalar browse. */
|
|
291
|
-
export interface BrowseResultItem {
|
|
292
|
-
/** Opaque — round-trip verbatim. */
|
|
293
|
-
variantId: VariantId;
|
|
294
|
-
brand: string;
|
|
295
|
-
product: string;
|
|
296
|
-
brandSlug: string | null;
|
|
297
|
-
productId: ProductId | null;
|
|
298
|
-
color: string | null;
|
|
299
|
-
variantName: string | null;
|
|
300
|
-
imageUrl: string | null;
|
|
301
|
-
scalars: {
|
|
302
|
-
l: number | null;
|
|
303
|
-
chroma: number | null;
|
|
304
|
-
vibrancy: number | null;
|
|
305
|
-
};
|
|
306
|
-
retired?: boolean;
|
|
307
|
-
/**
|
|
308
|
-
* The row's own image palette — the FULL stored palette of the SAME image
|
|
309
|
-
* `imageUrl` serves, uncapped and in stored order, each swatch with `oklab`
|
|
310
|
-
* (see {@link ByColourSwatch}), so a card can render, sort or group colours
|
|
311
|
-
* without a second call. A one-swatch palette means the extractor found one
|
|
312
|
-
* colour (solid-colour materials are genuinely monochrome), not that the list
|
|
313
|
-
* was trimmed. ABSENT when no palette is on record for that image: absent
|
|
314
|
-
* means unknown, NOT that the material has no colours. These swatches come
|
|
315
|
-
* from product imagery, not from the manufacturer.
|
|
316
|
-
*/
|
|
317
|
-
palette?: ByColourSwatch[];
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* A single page of the colour browse — the SDK's one genuinely cursor-paginated
|
|
321
|
-
* surface. `results.length` is this page and the browse states no total at all,
|
|
322
|
-
* so a corpus claim can only come from walking every page
|
|
323
|
-
* (`client.browse.pages(...)` does the loop).
|
|
324
|
-
*/
|
|
325
|
-
export interface BrowseResponse {
|
|
326
|
-
/** THIS PAGE. */
|
|
327
|
-
results: BrowseResultItem[];
|
|
328
|
-
/** Opaque keyset for the next page. Absent = the walk is finished. */
|
|
329
|
-
cursor?: Cursor;
|
|
330
|
-
}
|
|
331
406
|
export type ColourDictionaryId = OpenEnum<"colour_family" | "colour_mood" | "colour_undertone">;
|
|
332
407
|
export interface ColourFacetTerm {
|
|
333
408
|
id: string;
|
|
@@ -400,6 +475,22 @@ export interface CospecPairing {
|
|
|
400
475
|
imageUrl: string | null;
|
|
401
476
|
brandSlug: string;
|
|
402
477
|
productId: ProductId;
|
|
478
|
+
/**
|
|
479
|
+
* How this companion relates to the seed — the distinction raw order evidence
|
|
480
|
+
* does not carry, and the one that decides what you may claim.
|
|
481
|
+
*
|
|
482
|
+
* - `companion` — a DIFFERENT BRAND entirely. This is the cross-brand claim
|
|
483
|
+
* the rail exists to make.
|
|
484
|
+
* - `brand_sibling` — the seed's own brand, a different product.
|
|
485
|
+
* - `colourway` — the seed's own product, restyled.
|
|
486
|
+
*
|
|
487
|
+
* **70.6% of edges are same-brand**, so a consumer that ignores this reports
|
|
488
|
+
* the seed's own family as cross-brand intelligence (MG-1007).
|
|
489
|
+
*
|
|
490
|
+
* Optional because responses captured before the field existed are genuinely
|
|
491
|
+
* in the wild; absent means "from an older version", never "unrelated".
|
|
492
|
+
*/
|
|
493
|
+
relation?: "companion" | "brand_sibling" | "colourway";
|
|
403
494
|
category: string | null;
|
|
404
495
|
projectCount: number;
|
|
405
496
|
customerCount: number;
|
|
@@ -543,121 +634,7 @@ export type SearchImageInput = {
|
|
|
543
634
|
type: "base64";
|
|
544
635
|
data: string;
|
|
545
636
|
};
|
|
546
|
-
|
|
547
|
-
* The POST `/search` JSON body — the `searchMaterials` contract. Every field is
|
|
548
|
-
* optional; supply at least one signal (`text`, `colours`, `mood`, `similarTo`,
|
|
549
|
-
* or `image`). Passed through to the server verbatim.
|
|
550
|
-
*/
|
|
551
|
-
export interface SearchInput {
|
|
552
|
-
text?: string;
|
|
553
|
-
textModes?: Array<OpenEnum<"visual" | "lexical">>;
|
|
554
|
-
colours?: ColourInput[];
|
|
555
|
-
colourLogic?: OpenEnum<"all" | "any">;
|
|
556
|
-
colourTolerance?: OpenEnum<"tight" | "normal" | "loose">;
|
|
557
|
-
colourAppearance?: {
|
|
558
|
-
lightness?: {
|
|
559
|
-
min: number;
|
|
560
|
-
max: number;
|
|
561
|
-
};
|
|
562
|
-
warmth?: {
|
|
563
|
-
min: number;
|
|
564
|
-
max: number;
|
|
565
|
-
};
|
|
566
|
-
};
|
|
567
|
-
mood?: string;
|
|
568
|
-
sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
569
|
-
/**
|
|
570
|
-
* Seed a visual search from an existing variant's image (`variantId`) or a
|
|
571
|
-
* pre-computed image vector (`vector`). Only the `{ variantId }` form carries a
|
|
572
|
-
* seed variant, so it is the only form `verdicts` can diff a candidate against.
|
|
573
|
-
*/
|
|
574
|
-
similarTo?: {
|
|
575
|
-
variantId: VariantId;
|
|
576
|
-
} | {
|
|
577
|
-
vector: string;
|
|
578
|
-
};
|
|
579
|
-
image?: SearchImageInput;
|
|
580
|
-
category?: {
|
|
581
|
-
boost?: string;
|
|
582
|
-
filter?: string[];
|
|
583
|
-
};
|
|
584
|
-
weights?: {
|
|
585
|
-
image?: number;
|
|
586
|
-
category?: number;
|
|
587
|
-
colour?: number;
|
|
588
|
-
lexical?: number;
|
|
589
|
-
/** Text rerank multiplier applied when a seeded search also carries `text` (default 0.4). */
|
|
590
|
-
text?: number;
|
|
591
|
-
};
|
|
592
|
-
filters?: SearchFilter[];
|
|
593
|
-
/**
|
|
594
|
-
* SOFT spec criteria (rank-never-exclude). Same shape as `filters`; a preference
|
|
595
|
-
* never removes a result — it adds a `preferred` cell to each hit's `verdict` and
|
|
596
|
-
* demotes a failed/missing preferred hit WITHIN its page only.
|
|
597
|
-
*/
|
|
598
|
-
preferences?: SearchFilter[];
|
|
599
|
-
/** Page size — the size of ONE page, never a bound on the match set. */
|
|
600
|
-
limit?: number;
|
|
601
|
-
/** The previous response's `cursor`, passed back verbatim. Never synthesise one. */
|
|
602
|
-
cursor?: Cursor;
|
|
603
|
-
explain?: boolean;
|
|
604
|
-
collapse?: OpenEnum<"none" | "product">;
|
|
605
|
-
/**
|
|
606
|
-
* Opt-in feed total: EXACT up to 1,000, `lowerBound` beyond / for ANN pools.
|
|
607
|
-
* Without it the response states NO total — and a page size is not one.
|
|
608
|
-
*/
|
|
609
|
-
count?: boolean;
|
|
610
|
-
/** Only return materials with at least one catalog image. Default false (full catalog). */
|
|
611
|
-
hasImages?: boolean;
|
|
612
|
-
/**
|
|
613
|
-
* Opt-in seed-relative verdict cells (MG-629). Only meaningful with
|
|
614
|
-
* `similarTo: { variantId }`: each hit's `verdict` gains `preferred` cells
|
|
615
|
-
* diffing its comparable attributes (numeric + a `colour` axis) against the
|
|
616
|
-
* seed's, with directional deviations. Inert on every other seed form.
|
|
617
|
-
*/
|
|
618
|
-
verdicts?: boolean;
|
|
619
|
-
/** Result projection — `"full"` (default) or the lean `"card"` grid form. */
|
|
620
|
-
view?: OpenEnum<"full" | "card">;
|
|
621
|
-
}
|
|
622
|
-
/** Query-string params for the GET `/search` form. */
|
|
623
|
-
export interface SearchGetParams {
|
|
624
|
-
q?: string;
|
|
625
|
-
colours?: string;
|
|
626
|
-
logic?: OpenEnum<"all" | "any">;
|
|
627
|
-
mood?: string;
|
|
628
|
-
sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
629
|
-
tol?: OpenEnum<"tight" | "normal" | "loose">;
|
|
630
|
-
/** Seed the search from this variant's image. Opaque — pass it through verbatim. */
|
|
631
|
-
variantId?: VariantId;
|
|
632
|
-
limit?: number;
|
|
633
|
-
/** "0" or "1" — include per-signal `signals` on each result. */
|
|
634
|
-
explain?: OpenEnum<"0" | "1">;
|
|
635
|
-
/** "true" — opt in to a `counts.variants` feed total. */
|
|
636
|
-
count?: OpenEnum<"true">;
|
|
637
|
-
/** "true" — only return materials with at least one catalog image. */
|
|
638
|
-
hasImages?: OpenEnum<"true">;
|
|
639
|
-
/** Result projection — `"full"` (default) or the lean `"card"` grid form. */
|
|
640
|
-
view?: OpenEnum<"full" | "card">;
|
|
641
|
-
}
|
|
642
|
-
/** Params for the pure colour `search/browse` surface. */
|
|
643
|
-
export interface BrowseParams {
|
|
644
|
-
sort: OpenEnum<"light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
645
|
-
/**
|
|
646
|
-
* The previous page's `cursor`, verbatim. Opaque keyset state — not an offset.
|
|
647
|
-
* Changing `sort`, `limit` or `types` invalidates it.
|
|
648
|
-
*/
|
|
649
|
-
cursor?: Cursor;
|
|
650
|
-
/** Page size. */
|
|
651
|
-
limit?: number;
|
|
652
|
-
/**
|
|
653
|
-
* Comma-separated taxonomy keys to restrict the browse to, at EITHER tier of
|
|
654
|
-
* `/api/v1/taxonomy/categories`. A GROUP key (`tile_surfaces`) expands to
|
|
655
|
-
* every product type under it, a TYPE key (`ceramic_porcelain_tile`) stands
|
|
656
|
-
* for itself, and a mixed list is their deduplicated union. A key in neither
|
|
657
|
-
* tier is a 400 listing the valid keys — never an empty page.
|
|
658
|
-
*/
|
|
659
|
-
types?: string;
|
|
660
|
-
}
|
|
637
|
+
export type { SearchGetParams, SearchInput } from "./search-request-types.js";
|
|
661
638
|
/** Options for a batch material read. */
|
|
662
639
|
export interface MaterialsBatchOptions {
|
|
663
640
|
/** Projection depth. `tile` omits fields rather than reporting them absent. */
|
|
@@ -691,6 +668,13 @@ export interface ResolveAttributeOptions {
|
|
|
691
668
|
/** Options for `search/specifications`. */
|
|
692
669
|
export interface BySpecOptions {
|
|
693
670
|
limit?: number;
|
|
671
|
+
/**
|
|
672
|
+
* Numeric range criteria as `attribute:min..max` strings, ANDed with the
|
|
673
|
+
* `spec` criteria. Build them with `specRangeCriterion` rather than by hand —
|
|
674
|
+
* both bounds are inclusive and at least one is required, and a hand-built
|
|
675
|
+
* token with neither is refused by the server rather than matching everything.
|
|
676
|
+
*/
|
|
677
|
+
ranges?: string[];
|
|
694
678
|
}
|
|
695
679
|
export type * from "./demand-types.js";
|
|
696
680
|
/** One quarter's tally. Sparse across a series — an absent quarter is not a zero. */
|
|
@@ -731,31 +715,7 @@ export interface ExtractedPalette {
|
|
|
731
715
|
export interface ExtractPaletteResponse {
|
|
732
716
|
palette: ExtractedPalette | null;
|
|
733
717
|
}
|
|
734
|
-
export
|
|
735
|
-
value: string;
|
|
736
|
-
variants: number;
|
|
737
|
-
brands: number;
|
|
738
|
-
label?: string;
|
|
739
|
-
}
|
|
740
|
-
export interface SpecFacet {
|
|
741
|
-
attributeId: string;
|
|
742
|
-
name: string;
|
|
743
|
-
isArray: boolean;
|
|
744
|
-
brands: number;
|
|
745
|
-
values: SpecFacetValue[];
|
|
746
|
-
description?: string;
|
|
747
|
-
}
|
|
748
|
-
export interface SpecFacetsListResponse {
|
|
749
|
-
mode: "facets";
|
|
750
|
-
count: number;
|
|
751
|
-
facets: SpecFacet[];
|
|
752
|
-
}
|
|
753
|
-
export interface SpecFacetsDistributionResponse {
|
|
754
|
-
mode: "distribution";
|
|
755
|
-
attributeId: string;
|
|
756
|
-
count: number;
|
|
757
|
-
values: SpecFacetValue[];
|
|
758
|
-
}
|
|
718
|
+
export type * from "./spec-facet-types.js";
|
|
759
719
|
export interface ApplicableSpecAttribute {
|
|
760
720
|
id: string;
|
|
761
721
|
name: string;
|
package/validation.d.ts
CHANGED
|
@@ -34,6 +34,18 @@ export declare function requireOptionalString(value: unknown, what: string, fail
|
|
|
34
34
|
export declare function requireNullableString(value: unknown, what: string, fail: ValidationFailure): string | null;
|
|
35
35
|
/** A `z.string().min(1).nullable()`. */
|
|
36
36
|
export declare function requireNullableNonEmptyString(value: unknown, what: string, fail: ValidationFailure): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* A `z.string().trim().min(1).max(max)`.
|
|
39
|
+
*
|
|
40
|
+
* The `.trim()` is a TRANSFORM, so the schema's parsed output is the trimmed
|
|
41
|
+
* string and its length bounds apply AFTER trimming. A mirror that checked the
|
|
42
|
+
* raw length would disagree with the schema twice over: it would accept a
|
|
43
|
+
* whitespace-only value the schema rejects, and reject a padded value the schema
|
|
44
|
+
* accepts. Returns the trimmed string, which is what the schema returns.
|
|
45
|
+
*/
|
|
46
|
+
export declare function requireTrimmedBoundedString(value: unknown, what: string, max: number, fail: ValidationFailure): string;
|
|
47
|
+
/** A `z.string().trim().min(1).max(max).nullable()`. */
|
|
48
|
+
export declare function requireNullableTrimmedBoundedString(value: unknown, what: string, max: number, fail: ValidationFailure): string | null;
|
|
37
49
|
/** A `z.boolean()`. */
|
|
38
50
|
export declare function requireBoolean(value: unknown, what: string, fail: ValidationFailure): boolean;
|
|
39
51
|
/** A `z.boolean().optional()`. */
|
package/validation.js
CHANGED
|
@@ -55,6 +55,29 @@ export function requireNullableNonEmptyString(value, what, fail) {
|
|
|
55
55
|
return null;
|
|
56
56
|
return requireNonEmptyString(value, what, fail);
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* A `z.string().trim().min(1).max(max)`.
|
|
60
|
+
*
|
|
61
|
+
* The `.trim()` is a TRANSFORM, so the schema's parsed output is the trimmed
|
|
62
|
+
* string and its length bounds apply AFTER trimming. A mirror that checked the
|
|
63
|
+
* raw length would disagree with the schema twice over: it would accept a
|
|
64
|
+
* whitespace-only value the schema rejects, and reject a padded value the schema
|
|
65
|
+
* accepts. Returns the trimmed string, which is what the schema returns.
|
|
66
|
+
*/
|
|
67
|
+
export function requireTrimmedBoundedString(value, what, max, fail) {
|
|
68
|
+
const text = requireString(value, what, fail).trim();
|
|
69
|
+
if (text.length === 0)
|
|
70
|
+
throw fail(`${what} is empty once trimmed`);
|
|
71
|
+
if (text.length > max)
|
|
72
|
+
throw fail(`${what} is longer than ${max} characters (${text.length})`);
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
75
|
+
/** A `z.string().trim().min(1).max(max).nullable()`. */
|
|
76
|
+
export function requireNullableTrimmedBoundedString(value, what, max, fail) {
|
|
77
|
+
if (value === null)
|
|
78
|
+
return null;
|
|
79
|
+
return requireTrimmedBoundedString(value, what, max, fail);
|
|
80
|
+
}
|
|
58
81
|
/** A `z.boolean()`. */
|
|
59
82
|
export function requireBoolean(value, what, fail) {
|
|
60
83
|
if (typeof value !== "boolean")
|