@instruments/materialgraph 0.10.0 → 0.12.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/browse-types.d.ts +147 -0
- package/browse-types.js +1 -0
- package/by-colour-types.d.ts +56 -0
- 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 +28 -22
- package/client.js +33 -24
- 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/exploration-types.d.ts +110 -0
- package/exploration-types.js +1 -0
- package/fan-deck-types.d.ts +4 -1
- package/index.d.ts +4 -1
- package/match-stream-types.d.ts +16 -0
- package/operations.d.ts +1 -1
- package/operations.js +25 -0
- package/package.json +1 -1
- package/palette-search-types.d.ts +31 -0
- package/run-types.d.ts +132 -0
- package/runs.d.ts +35 -1
- package/runs.js +61 -1
- package/schedule-types.d.ts +128 -0
- package/schema-resource-types.d.ts +1 -1
- package/search-refinement-types.d.ts +28 -1
- package/search-request-types.d.ts +172 -0
- package/search-request-types.js +1 -0
- 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 +111 -4
- package/types.d.ts +149 -199
- 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
|
*
|
|
@@ -154,6 +135,38 @@ export interface SearchResultItem {
|
|
|
154
135
|
hasImage: boolean;
|
|
155
136
|
palette: PaletteSwatch[];
|
|
156
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
|
+
};
|
|
157
170
|
/** Fused score — range [0, sum of active weights]; see `maxScore`. */
|
|
158
171
|
score: number;
|
|
159
172
|
/**
|
|
@@ -238,11 +251,17 @@ export interface SearchCardItem {
|
|
|
238
251
|
* A feed total with its honesty basis — THE CLAIM ABOUT THE MATCH SET, which is
|
|
239
252
|
* a different number from `results.length` (this page).
|
|
240
253
|
*
|
|
241
|
-
* `exact` — the true count
|
|
242
|
-
* materials match".
|
|
254
|
+
* `exact` — the true count. Say "473 materials match".
|
|
243
255
|
* `lowerBound` — a floor (cap hit, ANN-pool bound, or a degraded count). Say
|
|
244
256
|
* "at least 1,000", never "1,000". Never a silent approximation.
|
|
245
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
|
+
*
|
|
246
265
|
* A response with no `counts` at all states no total: `count: true` is opt-in,
|
|
247
266
|
* and the absence of a total is not a total of zero.
|
|
248
267
|
*/
|
|
@@ -259,6 +278,54 @@ export interface SearchCounts {
|
|
|
259
278
|
variants: CountedTotal;
|
|
260
279
|
products?: CountedTotal;
|
|
261
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
|
+
}
|
|
262
329
|
/** The single candidate source a query resolved to. */
|
|
263
330
|
export type SearchMode = OpenEnum<"image" | "colour" | "text" | "lexical">;
|
|
264
331
|
/** Rerank-term report (absent in colour mode — there colour IS the source). */
|
|
@@ -290,8 +357,42 @@ export interface SearchResponse {
|
|
|
290
357
|
cursor?: Cursor;
|
|
291
358
|
rerank?: SearchRerankReport;
|
|
292
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;
|
|
293
376
|
note?: string;
|
|
294
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
|
+
};
|
|
295
396
|
/** Echo of the resolved query input (raw image bytes/vector are never echoed). */
|
|
296
397
|
query?: Record<string, unknown>;
|
|
297
398
|
/** A `getVariantBrief` echo of the seed variant, present only in `variantId` seed mode. */
|
|
@@ -302,47 +403,6 @@ export interface SearchCardResponse extends Omit<SearchResponse, "view" | "resul
|
|
|
302
403
|
view: OpenEnum<"card">;
|
|
303
404
|
results: SearchCardItem[];
|
|
304
405
|
}
|
|
305
|
-
/** One item from the pure colour-scalar browse. */
|
|
306
|
-
export interface BrowseResultItem {
|
|
307
|
-
/** Opaque — round-trip verbatim. */
|
|
308
|
-
variantId: VariantId;
|
|
309
|
-
brand: string;
|
|
310
|
-
product: string;
|
|
311
|
-
brandSlug: string | null;
|
|
312
|
-
productId: ProductId | null;
|
|
313
|
-
color: string | null;
|
|
314
|
-
variantName: string | null;
|
|
315
|
-
imageUrl: string | null;
|
|
316
|
-
scalars: {
|
|
317
|
-
l: number | null;
|
|
318
|
-
chroma: number | null;
|
|
319
|
-
vibrancy: number | null;
|
|
320
|
-
};
|
|
321
|
-
retired?: boolean;
|
|
322
|
-
/**
|
|
323
|
-
* The row's own image palette — the FULL stored palette of the SAME image
|
|
324
|
-
* `imageUrl` serves, uncapped and in stored order, each swatch with `oklab`
|
|
325
|
-
* (see {@link ByColourSwatch}), so a card can render, sort or group colours
|
|
326
|
-
* without a second call. A one-swatch palette means the extractor found one
|
|
327
|
-
* colour (solid-colour materials are genuinely monochrome), not that the list
|
|
328
|
-
* was trimmed. ABSENT when no palette is on record for that image: absent
|
|
329
|
-
* means unknown, NOT that the material has no colours. These swatches come
|
|
330
|
-
* from product imagery, not from the manufacturer.
|
|
331
|
-
*/
|
|
332
|
-
palette?: ByColourSwatch[];
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* A single page of the colour browse — the SDK's one genuinely cursor-paginated
|
|
336
|
-
* surface. `results.length` is this page and the browse states no total at all,
|
|
337
|
-
* so a corpus claim can only come from walking every page
|
|
338
|
-
* (`client.browse.pages(...)` does the loop).
|
|
339
|
-
*/
|
|
340
|
-
export interface BrowseResponse {
|
|
341
|
-
/** THIS PAGE. */
|
|
342
|
-
results: BrowseResultItem[];
|
|
343
|
-
/** Opaque keyset for the next page. Absent = the walk is finished. */
|
|
344
|
-
cursor?: Cursor;
|
|
345
|
-
}
|
|
346
406
|
export type ColourDictionaryId = OpenEnum<"colour_family" | "colour_mood" | "colour_undertone">;
|
|
347
407
|
export interface ColourFacetTerm {
|
|
348
408
|
id: string;
|
|
@@ -415,6 +475,22 @@ export interface CospecPairing {
|
|
|
415
475
|
imageUrl: string | null;
|
|
416
476
|
brandSlug: string;
|
|
417
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";
|
|
418
494
|
category: string | null;
|
|
419
495
|
projectCount: number;
|
|
420
496
|
customerCount: number;
|
|
@@ -558,133 +634,7 @@ export type SearchImageInput = {
|
|
|
558
634
|
type: "base64";
|
|
559
635
|
data: string;
|
|
560
636
|
};
|
|
561
|
-
|
|
562
|
-
* The POST `/search` JSON body — the `searchMaterials` contract. Every field is
|
|
563
|
-
* optional; supply at least one signal (`text`, `colours`, `mood`, `similarTo`,
|
|
564
|
-
* or `image`). Passed through to the server verbatim.
|
|
565
|
-
*/
|
|
566
|
-
export interface SearchInput {
|
|
567
|
-
text?: string;
|
|
568
|
-
textModes?: Array<OpenEnum<"visual" | "lexical">>;
|
|
569
|
-
colours?: ColourInput[];
|
|
570
|
-
colourLogic?: OpenEnum<"all" | "any">;
|
|
571
|
-
colourTolerance?: OpenEnum<"tight" | "normal" | "loose">;
|
|
572
|
-
colourAppearance?: {
|
|
573
|
-
lightness?: {
|
|
574
|
-
min: number;
|
|
575
|
-
max: number;
|
|
576
|
-
};
|
|
577
|
-
warmth?: {
|
|
578
|
-
min: number;
|
|
579
|
-
max: number;
|
|
580
|
-
};
|
|
581
|
-
};
|
|
582
|
-
mood?: string;
|
|
583
|
-
sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
584
|
-
/**
|
|
585
|
-
* Seed a visual search from an existing variant's image (`variantId`), from a
|
|
586
|
-
* Material Bank SKU (`materialBankId`), or from a pre-computed image vector
|
|
587
|
-
* (`vector`).
|
|
588
|
-
*
|
|
589
|
-
* `materialBankId` exists so a caller already holding a SKU does not have to
|
|
590
|
-
* ask us to translate it before it can ask us anything useful — one call, not
|
|
591
|
-
* two. It resolves to the variant server-side and then behaves identically to
|
|
592
|
-
* the `{ variantId }` form, seed verdicts included. A SKU that names no
|
|
593
|
-
* variant is a 400 carrying the offending value: never an empty result set,
|
|
594
|
-
* never a silent fall back to unseeded search.
|
|
595
|
-
*
|
|
596
|
-
* The `{ vector }` form carries no seed variant, so it is the only one
|
|
597
|
-
* `verdicts` cannot diff a candidate against.
|
|
598
|
-
*/
|
|
599
|
-
similarTo?: {
|
|
600
|
-
variantId: VariantId;
|
|
601
|
-
} | {
|
|
602
|
-
materialBankId: string;
|
|
603
|
-
} | {
|
|
604
|
-
vector: string;
|
|
605
|
-
};
|
|
606
|
-
image?: SearchImageInput;
|
|
607
|
-
category?: {
|
|
608
|
-
boost?: string;
|
|
609
|
-
filter?: string[];
|
|
610
|
-
};
|
|
611
|
-
weights?: {
|
|
612
|
-
image?: number;
|
|
613
|
-
category?: number;
|
|
614
|
-
colour?: number;
|
|
615
|
-
lexical?: number;
|
|
616
|
-
/** Text rerank multiplier applied when a seeded search also carries `text` (default 0.4). */
|
|
617
|
-
text?: number;
|
|
618
|
-
};
|
|
619
|
-
filters?: SearchFilter[];
|
|
620
|
-
/**
|
|
621
|
-
* SOFT spec criteria (rank-never-exclude). Same shape as `filters`; a preference
|
|
622
|
-
* never removes a result — it adds a `preferred` cell to each hit's `verdict` and
|
|
623
|
-
* demotes a failed/missing preferred hit WITHIN its page only.
|
|
624
|
-
*/
|
|
625
|
-
preferences?: SearchFilter[];
|
|
626
|
-
/** Page size — the size of ONE page, never a bound on the match set. */
|
|
627
|
-
limit?: number;
|
|
628
|
-
/** The previous response's `cursor`, passed back verbatim. Never synthesise one. */
|
|
629
|
-
cursor?: Cursor;
|
|
630
|
-
explain?: boolean;
|
|
631
|
-
collapse?: OpenEnum<"none" | "product">;
|
|
632
|
-
/**
|
|
633
|
-
* Opt-in feed total: EXACT up to 1,000, `lowerBound` beyond / for ANN pools.
|
|
634
|
-
* Without it the response states NO total — and a page size is not one.
|
|
635
|
-
*/
|
|
636
|
-
count?: boolean;
|
|
637
|
-
/** Only return materials with at least one catalog image. Default false (full catalog). */
|
|
638
|
-
hasImages?: boolean;
|
|
639
|
-
/**
|
|
640
|
-
* Opt-in seed-relative verdict cells (MG-629). Only meaningful with
|
|
641
|
-
* `similarTo: { variantId }`: each hit's `verdict` gains `preferred` cells
|
|
642
|
-
* diffing its comparable attributes (numeric + a `colour` axis) against the
|
|
643
|
-
* seed's, with directional deviations. Inert on every other seed form.
|
|
644
|
-
*/
|
|
645
|
-
verdicts?: boolean;
|
|
646
|
-
/** Result projection — `"full"` (default) or the lean `"card"` grid form. */
|
|
647
|
-
view?: OpenEnum<"full" | "card">;
|
|
648
|
-
}
|
|
649
|
-
/** Query-string params for the GET `/search` form. */
|
|
650
|
-
export interface SearchGetParams {
|
|
651
|
-
q?: string;
|
|
652
|
-
colours?: string;
|
|
653
|
-
logic?: OpenEnum<"all" | "any">;
|
|
654
|
-
mood?: string;
|
|
655
|
-
sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
656
|
-
tol?: OpenEnum<"tight" | "normal" | "loose">;
|
|
657
|
-
/** Seed the search from this variant's image. Opaque — pass it through verbatim. */
|
|
658
|
-
variantId?: VariantId;
|
|
659
|
-
limit?: number;
|
|
660
|
-
/** "0" or "1" — include per-signal `signals` on each result. */
|
|
661
|
-
explain?: OpenEnum<"0" | "1">;
|
|
662
|
-
/** "true" — opt in to a `counts.variants` feed total. */
|
|
663
|
-
count?: OpenEnum<"true">;
|
|
664
|
-
/** "true" — only return materials with at least one catalog image. */
|
|
665
|
-
hasImages?: OpenEnum<"true">;
|
|
666
|
-
/** Result projection — `"full"` (default) or the lean `"card"` grid form. */
|
|
667
|
-
view?: OpenEnum<"full" | "card">;
|
|
668
|
-
}
|
|
669
|
-
/** Params for the pure colour `search/browse` surface. */
|
|
670
|
-
export interface BrowseParams {
|
|
671
|
-
sort: OpenEnum<"light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
672
|
-
/**
|
|
673
|
-
* The previous page's `cursor`, verbatim. Opaque keyset state — not an offset.
|
|
674
|
-
* Changing `sort`, `limit` or `types` invalidates it.
|
|
675
|
-
*/
|
|
676
|
-
cursor?: Cursor;
|
|
677
|
-
/** Page size. */
|
|
678
|
-
limit?: number;
|
|
679
|
-
/**
|
|
680
|
-
* Comma-separated taxonomy keys to restrict the browse to, at EITHER tier of
|
|
681
|
-
* `/api/v1/taxonomy/categories`. A GROUP key (`tile_surfaces`) expands to
|
|
682
|
-
* every product type under it, a TYPE key (`ceramic_porcelain_tile`) stands
|
|
683
|
-
* for itself, and a mixed list is their deduplicated union. A key in neither
|
|
684
|
-
* tier is a 400 listing the valid keys — never an empty page.
|
|
685
|
-
*/
|
|
686
|
-
types?: string;
|
|
687
|
-
}
|
|
637
|
+
export type { SearchGetParams, SearchInput } from "./search-request-types.js";
|
|
688
638
|
/** Options for a batch material read. */
|
|
689
639
|
export interface MaterialsBatchOptions {
|
|
690
640
|
/** Projection depth. `tile` omits fields rather than reporting them absent. */
|
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")
|