@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SearchData } from "./types.js";
|
|
2
2
|
/** Canonical Search wire response. Raw SDK methods preserve this envelope. */
|
|
3
|
-
export
|
|
3
|
+
export type SearchResponse = {
|
|
4
4
|
data: SearchData;
|
|
5
5
|
meta: {
|
|
6
6
|
operationId: "search_materials_post" | "search_materials_get";
|
|
@@ -8,4 +8,4 @@ export interface SearchResponse {
|
|
|
8
8
|
};
|
|
9
9
|
links: Record<string, never>;
|
|
10
10
|
warnings: never[];
|
|
11
|
-
}
|
|
11
|
+
};
|
|
@@ -1,20 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/** `verdictStateSchema` — the six live cell states. */
|
|
3
|
-
export declare const VERDICT_STATES: readonly ["matched", "deviated", "failed", "unverified", "missing", "not_evaluated"];
|
|
4
|
-
/** `attributeVerdictSchema.strength`. */
|
|
5
|
-
export declare const VERDICT_STRENGTHS: readonly ["required", "preferred"];
|
|
6
|
-
/** `verdictBaselineSchema` discriminant — the four arms of what a cell was judged against. */
|
|
7
|
-
export declare const VERDICT_BASELINE_KINDS: readonly ["term", "presence", "seed", "image"];
|
|
8
|
-
/** The `image` baseline arm's own `source` enum. */
|
|
9
|
-
export declare const VERDICT_IMAGE_SOURCES: readonly ["variant", "vector"];
|
|
10
|
-
/** `verdictDeviationSchema.direction`. */
|
|
11
|
-
export declare const VERDICT_DEVIATION_DIRECTIONS: readonly ["above", "below", "other"];
|
|
12
|
-
/** `verdictTrustGapSchema` — why a real value did not clear the trust bar. */
|
|
13
|
-
export declare const VERDICT_TRUST_GAPS: readonly ["below_confidence", "basis_unmet", "proposed"];
|
|
14
|
-
/** `verdictGradeSchema` — the rolled-up card grade. */
|
|
15
|
-
export declare const VERDICT_GRADES: readonly ["meets_all", "close", "hard_no"];
|
|
16
|
-
/** `visualMeaningSchema.basis` — where a meaning came from. */
|
|
17
|
-
export declare const VISUAL_MEANING_BASES: readonly ["canonical_attribute", "image_colour_analysis"];
|
|
1
|
+
import type { ValidationFailure } from "./validation.js";
|
|
18
2
|
/**
|
|
19
3
|
* Prove one wire value is a {@link SearchResultItem}, raising through `fail` at
|
|
20
4
|
* the first field that does not match `searchResultItemSchema`. Checks the shape
|
|
@@ -22,6 +6,6 @@ export declare const VISUAL_MEANING_BASES: readonly ["canonical_attribute", "ima
|
|
|
22
6
|
* contract (nothing on the emit path parses, so stripping would invent a
|
|
23
7
|
* normalisation the wire never had).
|
|
24
8
|
*/
|
|
25
|
-
export declare
|
|
9
|
+
export declare const checkSearchResultItem: (value: unknown, what: string, fail: ValidationFailure) => void;
|
|
26
10
|
/** Prove every entry of a `results[]` array is a {@link SearchResultItem}. */
|
|
27
|
-
export declare
|
|
11
|
+
export declare const checkSearchResultItems: (items: readonly unknown[], what: string, fail: ValidationFailure) => void;
|
|
@@ -1,178 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/** The `image` baseline arm's own `source` enum. */
|
|
22
|
-
export const VERDICT_IMAGE_SOURCES = ["variant", "vector"];
|
|
23
|
-
/** `verdictDeviationSchema.direction`. */
|
|
24
|
-
export const VERDICT_DEVIATION_DIRECTIONS = [
|
|
25
|
-
"above",
|
|
26
|
-
"below",
|
|
27
|
-
"other",
|
|
28
|
-
];
|
|
29
|
-
/** `verdictTrustGapSchema` — why a real value did not clear the trust bar. */
|
|
30
|
-
export const VERDICT_TRUST_GAPS = [
|
|
31
|
-
"below_confidence",
|
|
32
|
-
"basis_unmet",
|
|
33
|
-
"proposed",
|
|
34
|
-
];
|
|
35
|
-
/** `verdictGradeSchema` — the rolled-up card grade. */
|
|
36
|
-
export const VERDICT_GRADES = ["meets_all", "close", "hard_no"];
|
|
37
|
-
/** `visualMeaningSchema.basis` — where a meaning came from. */
|
|
38
|
-
export const VISUAL_MEANING_BASES = [
|
|
39
|
-
"canonical_attribute",
|
|
40
|
-
"image_colour_analysis",
|
|
41
|
-
];
|
|
1
|
+
/**
|
|
2
|
+
* The one runtime validator for a {@link SearchResultItem} on the wire — the
|
|
3
|
+
* hand-rolled, zero-runtime-dependency mirror of `searchResultItemSchema`
|
|
4
|
+
* (`packages/products/src/search/search-result-schema.ts`) and its full
|
|
5
|
+
* transitive closure, including `searchResultSignalsSchema`.
|
|
6
|
+
*
|
|
7
|
+
* WHY THIS EXISTS SEPARATELY FROM THE TYPE MIRROR. `contract-parity.test.ts`
|
|
8
|
+
* proves the SDK's STATIC types match the canonical schemas. That is a
|
|
9
|
+
* compile-time proof about shapes; it validates nothing at run time. A malformed
|
|
10
|
+
* result item arriving over a stream was caught by neither, so the Match reader
|
|
11
|
+
* now checks every item through here.
|
|
12
|
+
*
|
|
13
|
+
* ONE HOME. This module is the single implementation — no copy lives inside
|
|
14
|
+
* `match-stream.ts`. It is package-internal (not re-exported from `index.ts`)
|
|
15
|
+
* because a published symbol is a permanent contract, and nothing outside the
|
|
16
|
+
* SDK needs to call it yet; promoting it later is additive.
|
|
17
|
+
*
|
|
18
|
+
* ENUMS ARE CLOSED, matching the canonical `z.enum(…)` members.
|
|
19
|
+
*/
|
|
20
|
+
import { requireArray, requireBoolean, requireEnum, requireNullableNumber, requireNullableString, requireNumber, requireOptionalBoolean, requireOptionalString, requireRecord, requireString, } from "./validation.js";
|
|
42
21
|
// ── Leaf checks ──────────────────────────────────────────────────────────────
|
|
43
22
|
/** `palette[]` — `{ hex, proportion, name: string.nullable().optional() }`. */
|
|
44
|
-
|
|
23
|
+
const checkPaletteSwatch = (value, what, fail) => {
|
|
45
24
|
const record = requireRecord(value, what, fail);
|
|
46
25
|
requireString(record.hex, `${what}.hex`, fail);
|
|
47
26
|
requireNumber(record.proportion, `${what}.proportion`, fail);
|
|
48
27
|
// `.nullable().optional()` — absent OR null OR a string.
|
|
49
|
-
if (record.name !== undefined)
|
|
28
|
+
if (record.name !== undefined) {
|
|
50
29
|
requireNullableString(record.name, `${what}.name`, fail);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
* `variants[]` — one entry of a product-collapsed result's ranked variant list
|
|
54
|
-
* (`collapsedVariantSchema`). A SLIM projection, not a nested result item: the
|
|
55
|
-
* checks here are the whole shape.
|
|
56
|
-
*/
|
|
57
|
-
function checkCollapsedVariant(value, what, fail) {
|
|
58
|
-
const record = requireRecord(value, what, fail);
|
|
59
|
-
requireString(record.variantId, `${what}.variantId`, fail);
|
|
60
|
-
requireNullableString(record.variantName, `${what}.variantName`, fail);
|
|
61
|
-
requireNullableString(record.color, `${what}.color`, fail);
|
|
62
|
-
// `.optional()`, NOT nullable: the emit path omits the key when no palette is
|
|
63
|
-
// on record rather than sending null.
|
|
64
|
-
requireOptionalString(record.hex, `${what}.hex`, fail);
|
|
65
|
-
requireNullableString(record.imageUrl, `${what}.imageUrl`, fail);
|
|
66
|
-
requireOptionalString(record.materialBankId, `${what}.materialBankId`, fail);
|
|
67
|
-
requireNumber(record.score, `${what}.score`, fail);
|
|
68
|
-
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
69
32
|
/** `colourMeta` — three independently optional strings. */
|
|
70
|
-
|
|
33
|
+
const checkColourMeta = (value, what, fail) => {
|
|
71
34
|
const record = requireRecord(value, what, fail);
|
|
72
35
|
requireOptionalString(record.mood, `${what}.mood`, fail);
|
|
73
36
|
requireOptionalString(record.dominantFamily, `${what}.dominantFamily`, fail);
|
|
74
37
|
requireOptionalString(record.dominantHex, `${what}.dominantHex`, fail);
|
|
75
|
-
}
|
|
76
|
-
/** `searchResultSignalsSchema` —
|
|
77
|
-
|
|
78
|
-
const record = requireRecord(value, what, fail);
|
|
79
|
-
if (record.portrait !== undefined) {
|
|
80
|
-
requireNullableNumber(record.portrait, `${what}.portrait`, fail);
|
|
81
|
-
}
|
|
82
|
-
for (const signal of ["image", "category", "colour", "lexical", "text"]) {
|
|
83
|
-
requireNullableNumber(record[signal], `${what}.${signal}`, fail);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/** `visualMeaningSchema` — note the NON-EMPTY `values` array and the closed `basis`. */
|
|
87
|
-
function checkVisualMeaning(value, what, fail) {
|
|
88
|
-
const record = requireRecord(value, what, fail);
|
|
89
|
-
requireString(record.id, `${what}.id`, fail);
|
|
90
|
-
requireString(record.label, `${what}.label`, fail);
|
|
91
|
-
requireNonEmptyStringArray(record.values, `${what}.values`, fail);
|
|
92
|
-
requireEnum(record.basis, VISUAL_MEANING_BASES, `${what}.basis`, fail);
|
|
93
|
-
requireOptionalNumber(record.confidence, `${what}.confidence`, fail);
|
|
94
|
-
requireOptionalString(record.sourceBasis, `${what}.sourceBasis`, fail);
|
|
95
|
-
requireOptionalString(record.version, `${what}.version`, fail);
|
|
96
|
-
requireOptionalString(record.appliedAt, `${what}.appliedAt`, fail);
|
|
97
|
-
}
|
|
98
|
-
// ── Verdict subtree ──────────────────────────────────────────────────────────
|
|
99
|
-
/**
|
|
100
|
-
* `verdictBaselineSchema` — a discriminated union on `kind`. An unknown
|
|
101
|
-
* discriminant is rejected, exactly as `z.discriminatedUnion` rejects it, and
|
|
102
|
-
* each arm carries only the fields it needs.
|
|
103
|
-
*/
|
|
104
|
-
function checkVerdictBaseline(value, what, fail) {
|
|
105
|
-
const record = requireRecord(value, what, fail);
|
|
106
|
-
const kind = requireEnum(record.kind, VERDICT_BASELINE_KINDS, `${what}.kind`, fail);
|
|
107
|
-
switch (kind) {
|
|
108
|
-
case "term":
|
|
109
|
-
requireNonEmptyString(record.term, `${what}.term`, fail);
|
|
110
|
-
return;
|
|
111
|
-
case "presence":
|
|
112
|
-
return;
|
|
113
|
-
case "seed":
|
|
114
|
-
requireNonEmptyString(record.variantId, `${what}.variantId`, fail);
|
|
115
|
-
return;
|
|
116
|
-
default:
|
|
117
|
-
// `image` — the like-this-picture arm.
|
|
118
|
-
requireEnum(record.source, VERDICT_IMAGE_SOURCES, `${what}.source`, fail);
|
|
119
|
-
requireNullableNonEmptyString(record.variantId, `${what}.variantId`, fail);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/** `verdictDeviationSchema` — a named near-miss. */
|
|
123
|
-
function checkVerdictDeviation(value, what, fail) {
|
|
124
|
-
const record = requireRecord(value, what, fail);
|
|
125
|
-
requireEnum(record.direction, VERDICT_DEVIATION_DIRECTIONS, `${what}.direction`, fail);
|
|
126
|
-
requireNullableNumber(record.delta, `${what}.delta`, fail);
|
|
127
|
-
requireNullableString(record.unit, `${what}.unit`, fail);
|
|
128
|
-
requireNonEmptyString(record.label, `${what}.label`, fail);
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* `attributeVerdictSchema` — nine fields plus the TWO cross-field invariants its
|
|
132
|
-
* `.superRefine()` enforces. Those are relational rather than field-wise, which
|
|
133
|
-
* is no obstacle to a hand-rolled validator: they are two conditionals, and the
|
|
134
|
-
* schema's own doc calls them out as the reason a malformed cell can never reach
|
|
135
|
-
* a consumer.
|
|
136
|
-
*/
|
|
137
|
-
function checkAttributeVerdict(value, what, fail) {
|
|
138
|
-
const record = requireRecord(value, what, fail);
|
|
139
|
-
requireNonEmptyString(record.attributeId, `${what}.attributeId`, fail);
|
|
140
|
-
const state = requireEnum(record.state, VERDICT_STATES, `${what}.state`, fail);
|
|
141
|
-
requireEnum(record.strength, VERDICT_STRENGTHS, `${what}.strength`, fail);
|
|
142
|
-
checkVerdictBaseline(record.baseline, `${what}.baseline`, fail);
|
|
143
|
-
requireNullableString(record.value, `${what}.value`, fail);
|
|
144
|
-
requireNullableBoundedNumber(record.confidence, `${what}.confidence`, fail, 0, 1);
|
|
145
|
-
requireNullableString(record.sourceBasis, `${what}.sourceBasis`, fail);
|
|
146
|
-
if (record.citation !== undefined) {
|
|
147
|
-
const citation = requireRecord(record.citation, `${what}.citation`, fail);
|
|
148
|
-
requireNonEmptyString(citation.citationId, `${what}.citation.citationId`, fail);
|
|
149
|
-
requireNonEmptyString(citation.sourceType, `${what}.citation.sourceType`, fail);
|
|
150
|
-
const count = requireNumber(citation.count, `${what}.citation.count`, fail);
|
|
151
|
-
if (!Number.isInteger(count) || count <= 0) {
|
|
152
|
-
throw fail(`${what}.citation.count must be a positive integer`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
const deviation = record.deviation;
|
|
156
|
-
if (deviation !== null)
|
|
157
|
-
checkVerdictDeviation(deviation, `${what}.deviation`, fail);
|
|
158
|
-
const trustGap = requireNullableEnum(record.trustGap, VERDICT_TRUST_GAPS, `${what}.trustGap`, fail);
|
|
159
|
-
// superRefine invariant 1 — a deviation only describes a `deviated` cell.
|
|
160
|
-
if (deviation !== null && state !== "deviated") {
|
|
161
|
-
throw fail(`${what}.deviation is set on a cell whose state is "${state}", not "deviated"`);
|
|
162
|
-
}
|
|
163
|
-
// superRefine invariant 2 — a trust gap only explains an `unverified` cell.
|
|
164
|
-
if (trustGap !== null && state !== "unverified") {
|
|
165
|
-
throw fail(`${what}.trustGap is set on a cell whose state is "${state}", not "unverified"`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
/** `candidateVerdictSchema` — the whole report card. */
|
|
169
|
-
function checkCandidateVerdict(value, what, fail) {
|
|
38
|
+
};
|
|
39
|
+
/** `searchResultSignalsSchema` — the Match image cosine, nullable. */
|
|
40
|
+
const checkSignals = (value, what, fail) => {
|
|
170
41
|
const record = requireRecord(value, what, fail);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
requireEnum(record.grade, VERDICT_GRADES, `${what}.grade`, fail);
|
|
174
|
-
requireNullableBoundedNumber(record.minConfidence, `${what}.minConfidence`, fail, 0, 1);
|
|
175
|
-
}
|
|
42
|
+
requireNullableNumber(record.image, `${what}.image`, fail);
|
|
43
|
+
};
|
|
176
44
|
// ── The item ─────────────────────────────────────────────────────────────────
|
|
177
45
|
/**
|
|
178
46
|
* Prove one wire value is a {@link SearchResultItem}, raising through `fail` at
|
|
@@ -181,7 +49,7 @@ function checkCandidateVerdict(value, what, fail) {
|
|
|
181
49
|
* contract (nothing on the emit path parses, so stripping would invent a
|
|
182
50
|
* normalisation the wire never had).
|
|
183
51
|
*/
|
|
184
|
-
export
|
|
52
|
+
export const checkSearchResultItem = (value, what, fail) => {
|
|
185
53
|
const record = requireRecord(value, what, fail);
|
|
186
54
|
requireString(record.variantId, `${what}.variantId`, fail);
|
|
187
55
|
requireString(record.brand, `${what}.brand`, fail);
|
|
@@ -209,32 +77,23 @@ export function checkSearchResultItem(value, what, fail) {
|
|
|
209
77
|
requireBoolean(record.hasImage, `${what}.hasImage`, fail);
|
|
210
78
|
requireNumber(record.score, `${what}.score`, fail);
|
|
211
79
|
const palette = requireArray(record.palette, `${what}.palette`, fail);
|
|
212
|
-
palette.forEach((swatch, index) =>
|
|
80
|
+
palette.forEach((swatch, index) => {
|
|
81
|
+
checkPaletteSwatch(swatch, `${what}.palette[${index}]`, fail);
|
|
82
|
+
});
|
|
213
83
|
if (record.colourMeta !== undefined) {
|
|
214
84
|
checkColourMeta(record.colourMeta, `${what}.colourMeta`, fail);
|
|
215
85
|
}
|
|
216
|
-
if (record.signals !== undefined)
|
|
86
|
+
if (record.signals !== undefined) {
|
|
217
87
|
checkSignals(record.signals, `${what}.signals`, fail);
|
|
88
|
+
}
|
|
218
89
|
requireOptionalBoolean(record.retired, `${what}.retired`, fail);
|
|
219
90
|
requireOptionalBoolean(record.sampleable, `${what}.sampleable`, fail);
|
|
220
91
|
requireOptionalBoolean(record.belowFloor, `${what}.belowFloor`, fail);
|
|
221
92
|
requireOptionalBoolean(record.admittedOnColour, `${what}.admittedOnColour`, fail);
|
|
222
|
-
|
|
223
|
-
requireIntegerAtLeast(record.siblingCount, `${what}.siblingCount`, fail, 1);
|
|
224
|
-
}
|
|
225
|
-
if (record.variants !== undefined) {
|
|
226
|
-
const variants = requireArray(record.variants, `${what}.variants`, fail);
|
|
227
|
-
variants.forEach((variant, index) => checkCollapsedVariant(variant, `${what}.variants[${index}]`, fail));
|
|
228
|
-
}
|
|
229
|
-
if (record.visualMeanings !== undefined) {
|
|
230
|
-
const meanings = requireArray(record.visualMeanings, `${what}.visualMeanings`, fail);
|
|
231
|
-
meanings.forEach((meaning, index) => checkVisualMeaning(meaning, `${what}.visualMeanings[${index}]`, fail));
|
|
232
|
-
}
|
|
233
|
-
if (record.verdict !== undefined) {
|
|
234
|
-
checkCandidateVerdict(record.verdict, `${what}.verdict`, fail);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
93
|
+
};
|
|
237
94
|
/** Prove every entry of a `results[]` array is a {@link SearchResultItem}. */
|
|
238
|
-
export
|
|
239
|
-
items.forEach((item, index) =>
|
|
240
|
-
}
|
|
95
|
+
export const checkSearchResultItems = (items, what, fail) => {
|
|
96
|
+
items.forEach((item, index) => {
|
|
97
|
+
checkSearchResultItem(item, `${what}[${index}]`, fail);
|
|
98
|
+
});
|
|
99
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PostSourceMaterialCommentResult, SourceMaterialBatchDetail, SourceMaterialCommentThread } from "./run-types.js";
|
|
2
2
|
/** Validate a batch-detail body, returning it typed, or throw explaining why. */
|
|
3
|
-
export declare
|
|
3
|
+
export declare const checkSourceMaterialBatchDetail: (value: unknown, batchId: string) => SourceMaterialBatchDetail;
|
|
4
4
|
/** Validate a comment-thread body, returning it typed, or throw explaining why. */
|
|
5
|
-
export declare
|
|
5
|
+
export declare const checkSourceMaterialCommentThread: (value: unknown, sourceId: string) => SourceMaterialCommentThread;
|
|
6
6
|
/** Validate a posted-comment body, returning it typed, or throw explaining why. */
|
|
7
|
-
export declare
|
|
7
|
+
export declare const checkPostSourceMaterialCommentResult: (value: unknown, sourceId: string) => PostSourceMaterialCommentResult;
|
|
@@ -48,23 +48,21 @@ const SOURCE_ROLES = [
|
|
|
48
48
|
"inspiration_image",
|
|
49
49
|
"unclassified",
|
|
50
50
|
];
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
function checkAnalysis(value, what, fail) {
|
|
51
|
+
const detailFailure = (batchId) => (reason) => MaterialGraphError.unexpected(`GET /api/v1/source-material/${batchId} returned a malformed body: ${reason}`);
|
|
52
|
+
const checkAnalysis = (value, what, fail) => {
|
|
55
53
|
const record = requireRecord(value, what, fail);
|
|
56
54
|
// `model` and `analysedAt` ARE `.min(1)` canonically — fields this endpoint
|
|
57
55
|
// introduces, where an empty value is meaningless and `model` already cannot
|
|
58
56
|
// be written empty. Parity here is achieved by the schema being strict, never
|
|
59
57
|
// by this mirror freelancing past it.
|
|
60
58
|
return {
|
|
59
|
+
analysedAt: requireNonEmptyString(record.analysedAt, `${what}.analysedAt`, fail),
|
|
61
60
|
kind: requireEnum(record.kind, ANALYSIS_KINDS, `${what}.kind`, fail),
|
|
62
61
|
model: requireNonEmptyString(record.model, `${what}.model`, fail),
|
|
63
|
-
status: requireEnum(record.status, ANALYSIS_STATUSES, `${what}.status`, fail),
|
|
64
62
|
result: requireJsonRecord(record.result, `${what}.result`, fail),
|
|
65
|
-
|
|
63
|
+
status: requireEnum(record.status, ANALYSIS_STATUSES, `${what}.status`, fail),
|
|
66
64
|
};
|
|
67
|
-
}
|
|
65
|
+
};
|
|
68
66
|
/** `sourceMaterialCommentAuthorSchema`'s `attribution` members, verbatim. */
|
|
69
67
|
const COMMENT_ATTRIBUTIONS = ["reported"];
|
|
70
68
|
/** The canonical cap on both author fields: `.trim().min(1).max(160)`. */
|
|
@@ -84,27 +82,27 @@ const AUTHOR_FIELD_MAX = 160;
|
|
|
84
82
|
* schema caps. A validator that is looser than the contract is a validator that
|
|
85
83
|
* lets the two drift without anything failing.
|
|
86
84
|
*/
|
|
87
|
-
|
|
85
|
+
const checkCommentBase = (record, what, fail) => {
|
|
88
86
|
const author = requireRecord(record.author, `${what}.author`, fail);
|
|
89
87
|
const recordedBy = requireRecord(record.recordedBy, `${what}.recordedBy`, fail);
|
|
90
88
|
return {
|
|
91
|
-
commentId: requireNonEmptyString(record.commentId, `${what}.commentId`, fail),
|
|
92
89
|
author: {
|
|
93
|
-
name: requireTrimmedBoundedString(author.name, `${what}.author.name`, AUTHOR_FIELD_MAX, fail),
|
|
94
|
-
organisation: requireNullableTrimmedBoundedString(author.organisation, `${what}.author.organisation`, AUTHOR_FIELD_MAX, fail),
|
|
95
90
|
// Closed here, matching the canonical `z.literal("reported")`, while the
|
|
96
91
|
// published type is `OpenEnum` so a newer server value keeps
|
|
97
92
|
// type-checking — the same split the analysis enums make.
|
|
98
93
|
attribution: requireEnum(author.attribution, COMMENT_ATTRIBUTIONS, `${what}.author.attribution`, fail),
|
|
94
|
+
name: requireTrimmedBoundedString(author.name, `${what}.author.name`, AUTHOR_FIELD_MAX, fail),
|
|
95
|
+
organisation: requireNullableTrimmedBoundedString(author.organisation, `${what}.author.organisation`, AUTHOR_FIELD_MAX, fail),
|
|
99
96
|
},
|
|
97
|
+
body: requireNonEmptyString(record.body, `${what}.body`, fail),
|
|
98
|
+
commentId: requireNonEmptyString(record.commentId, `${what}.commentId`, fail),
|
|
99
|
+
createdAt: requireNonEmptyString(record.createdAt, `${what}.createdAt`, fail),
|
|
100
100
|
recordedBy: {
|
|
101
101
|
principalId: requireNonEmptyString(recordedBy.principalId, `${what}.recordedBy.principalId`, fail),
|
|
102
102
|
},
|
|
103
|
-
body: requireNonEmptyString(record.body, `${what}.body`, fail),
|
|
104
|
-
createdAt: requireNonEmptyString(record.createdAt, `${what}.createdAt`, fail),
|
|
105
103
|
};
|
|
106
|
-
}
|
|
107
|
-
|
|
104
|
+
};
|
|
105
|
+
const checkComment = (value, what, fail) => {
|
|
108
106
|
const record = requireRecord(value, what, fail);
|
|
109
107
|
// Same reasoning as `analyses`: no `.default([])` canonically, so an absent
|
|
110
108
|
// `replies` is malformed rather than empty.
|
|
@@ -113,8 +111,8 @@ function checkComment(value, what, fail) {
|
|
|
113
111
|
...checkCommentBase(record, what, fail),
|
|
114
112
|
replies: replies.map((reply, index) => checkCommentBase(requireRecord(reply, `${what}.replies[${index}]`, fail), `${what}.replies[${index}]`, fail)),
|
|
115
113
|
};
|
|
116
|
-
}
|
|
117
|
-
|
|
114
|
+
};
|
|
115
|
+
const checkFile = (value, what, fail) => {
|
|
118
116
|
const record = requireRecord(value, what, fail);
|
|
119
117
|
// NOT `requireDefaultedArray`: the schema has no `.default([])`, so an absent
|
|
120
118
|
// `analyses` is a malformed body rather than an empty one. Keeping that
|
|
@@ -133,11 +131,7 @@ function checkFile(value, what, fail) {
|
|
|
133
131
|
// refuse a response the server can legitimately produce, and tightening them
|
|
134
132
|
// is a change to a contract this endpoint does not own.
|
|
135
133
|
return {
|
|
136
|
-
|
|
137
|
-
role: requireEnum(record.role, SOURCE_ROLES, `${what}.role`, fail),
|
|
138
|
-
filename: requireString(record.filename, `${what}.filename`, fail),
|
|
139
|
-
mediaType: requireString(record.mediaType, `${what}.mediaType`, fail),
|
|
140
|
-
sha256: requireString(record.sha256, `${what}.sha256`, fail),
|
|
134
|
+
analyses: analyses.map((analysis, index) => checkAnalysis(analysis, `${what}.analyses[${index}]`, fail)),
|
|
141
135
|
// `requireSafeInteger`, NOT `requireIntegerAtLeast(…, 0)`. Canonically these
|
|
142
136
|
// are a bare `z.number().int()` on the INHERITED
|
|
143
137
|
// `sourceMaterialFileReceiptSchema`, which carries no lower bound — so a
|
|
@@ -150,17 +144,21 @@ function checkFile(value, what, fail) {
|
|
|
150
144
|
// fields is a canonical change to a schema shared with the upload path, and
|
|
151
145
|
// belongs in its own ticket with this mirror following after.
|
|
152
146
|
bytes: requireSafeInteger(record.bytes, `${what}.bytes`, fail),
|
|
147
|
+
comments: comments.map((comment, index) => checkComment(comment, `${what}.comments[${index}]`, fail)),
|
|
148
|
+
commentsTruncated,
|
|
149
|
+
filename: requireString(record.filename, `${what}.filename`, fail),
|
|
150
|
+
mediaType: requireString(record.mediaType, `${what}.mediaType`, fail),
|
|
153
151
|
pages: record.pages === null
|
|
154
152
|
? null
|
|
155
153
|
: requireSafeInteger(record.pages, `${what}.pages`, fail),
|
|
154
|
+
role: requireEnum(record.role, SOURCE_ROLES, `${what}.role`, fail),
|
|
155
|
+
sha256: requireString(record.sha256, `${what}.sha256`, fail),
|
|
156
|
+
sourceId: requireString(record.sourceId, `${what}.sourceId`, fail),
|
|
156
157
|
url: requireString(record.url, `${what}.url`, fail),
|
|
157
|
-
analyses: analyses.map((analysis, index) => checkAnalysis(analysis, `${what}.analyses[${index}]`, fail)),
|
|
158
|
-
comments: comments.map((comment, index) => checkComment(comment, `${what}.comments[${index}]`, fail)),
|
|
159
|
-
commentsTruncated,
|
|
160
158
|
};
|
|
161
|
-
}
|
|
159
|
+
};
|
|
162
160
|
/** Validate a batch-detail body, returning it typed, or throw explaining why. */
|
|
163
|
-
export
|
|
161
|
+
export const checkSourceMaterialBatchDetail = (value, batchId) => {
|
|
164
162
|
const fail = detailFailure(batchId);
|
|
165
163
|
const record = requireRecord(value, "body", fail);
|
|
166
164
|
const sources = requireArray(record.sources, "body.sources", fail);
|
|
@@ -169,21 +167,19 @@ export function checkSourceMaterialBatchDetail(value, batchId) {
|
|
|
169
167
|
createdAt: requireString(record.createdAt, "body.createdAt", fail),
|
|
170
168
|
sources: sources.map((file, index) => checkFile(file, `body.sources[${index}]`, fail)),
|
|
171
169
|
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return (reason) => MaterialGraphError.unexpected(`${verb} .../sources/${sourceId}/comments returned a malformed body: ${reason}`);
|
|
175
|
-
}
|
|
170
|
+
};
|
|
171
|
+
const commentsFailure = (verb, sourceId) => (reason) => MaterialGraphError.unexpected(`${verb} .../sources/${sourceId}/comments returned a malformed body: ${reason}`);
|
|
176
172
|
/** Validate a comment-thread body, returning it typed, or throw explaining why. */
|
|
177
|
-
export
|
|
173
|
+
export const checkSourceMaterialCommentThread = (value, sourceId) => {
|
|
178
174
|
const fail = commentsFailure("GET", sourceId);
|
|
179
175
|
const record = requireRecord(value, "body", fail);
|
|
180
176
|
const comments = requireArray(record.comments, "body.comments", fail);
|
|
181
177
|
return {
|
|
182
|
-
sourceId: requireString(record.sourceId, "body.sourceId", fail),
|
|
183
178
|
comments: comments.map((comment, index) => checkComment(comment, `body.comments[${index}]`, fail)),
|
|
179
|
+
sourceId: requireString(record.sourceId, "body.sourceId", fail),
|
|
184
180
|
truncated: requireBoolean(record.truncated, "body.truncated", fail),
|
|
185
181
|
};
|
|
186
|
-
}
|
|
182
|
+
};
|
|
187
183
|
/** `storedSourceMaterialCommentSchema`'s discriminator members, verbatim. */
|
|
188
184
|
const STORED_COMMENT_KINDS = ["root", "reply"];
|
|
189
185
|
/**
|
|
@@ -195,7 +191,7 @@ const STORED_COMMENT_KINDS = ["root", "reply"];
|
|
|
195
191
|
* defect this discriminated union was added to fix, and a mirror that did it
|
|
196
192
|
* would put the lie back on the client side.
|
|
197
193
|
*/
|
|
198
|
-
|
|
194
|
+
const checkStoredComment = (value, what, fail) => {
|
|
199
195
|
const record = requireRecord(value, what, fail);
|
|
200
196
|
const kind = requireEnum(record.kind, STORED_COMMENT_KINDS, `${what}.kind`, fail);
|
|
201
197
|
if (kind === "reply") {
|
|
@@ -206,13 +202,13 @@ function checkStoredComment(value, what, fail) {
|
|
|
206
202
|
};
|
|
207
203
|
}
|
|
208
204
|
return { kind: "root", ...checkComment(record, what, fail) };
|
|
209
|
-
}
|
|
205
|
+
};
|
|
210
206
|
/** Validate a posted-comment body, returning it typed, or throw explaining why. */
|
|
211
|
-
export
|
|
207
|
+
export const checkPostSourceMaterialCommentResult = (value, sourceId) => {
|
|
212
208
|
const fail = commentsFailure("POST", sourceId);
|
|
213
209
|
const record = requireRecord(value, "body", fail);
|
|
214
210
|
return {
|
|
215
|
-
sourceId: requireString(record.sourceId, "body.sourceId", fail),
|
|
216
211
|
comment: checkStoredComment(record.comment, "body.comment", fail),
|
|
212
|
+
sourceId: requireString(record.sourceId, "body.sourceId", fail),
|
|
217
213
|
};
|
|
218
|
-
}
|
|
214
|
+
};
|
package/spec-facet-types.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type SpecFacetValue = {
|
|
2
2
|
value: string;
|
|
3
3
|
variants: number;
|
|
4
4
|
brands: number;
|
|
5
5
|
label?: string;
|
|
6
|
-
}
|
|
6
|
+
};
|
|
7
7
|
/**
|
|
8
8
|
* A NUMERIC facet's observed span and the coverage behind it.
|
|
9
9
|
*
|
|
@@ -17,20 +17,21 @@ export interface SpecFacetValue {
|
|
|
17
17
|
* `unparseableVariants` is the honesty companion: values recorded here that are
|
|
18
18
|
* not numbers, which a range EXCLUDES rather than reading as zero.
|
|
19
19
|
*/
|
|
20
|
-
export
|
|
20
|
+
export type SpecFacetRange = {
|
|
21
21
|
min: number;
|
|
22
22
|
max: number;
|
|
23
23
|
variants: number;
|
|
24
24
|
unparseableVariants: number;
|
|
25
|
-
}
|
|
26
|
-
export
|
|
25
|
+
};
|
|
26
|
+
export type SpecFacet = {
|
|
27
27
|
attributeId: string;
|
|
28
28
|
name: string;
|
|
29
29
|
isArray: boolean;
|
|
30
30
|
/**
|
|
31
31
|
* How this facet is queried. `token` — pick a value from `values` and send it
|
|
32
|
-
* as `
|
|
33
|
-
*
|
|
32
|
+
* as a `{field: attributeId, op: "in", values: [...]}` filter leaf.
|
|
33
|
+
* `numeric` — `values` is EMPTY and `range` carries the span; send inclusive
|
|
34
|
+
* bounds as `{field: attributeId, op: "gte"/"lte", value}` leaves instead.
|
|
34
35
|
*/
|
|
35
36
|
kind: "token" | "numeric" | (string & {});
|
|
36
37
|
brands: number;
|
|
@@ -39,15 +40,9 @@ export interface SpecFacet {
|
|
|
39
40
|
/** Present ONLY on a `numeric` facet. */
|
|
40
41
|
range?: SpecFacetRange;
|
|
41
42
|
description?: string;
|
|
42
|
-
}
|
|
43
|
-
export
|
|
43
|
+
};
|
|
44
|
+
export type SpecFacetsListResponse = {
|
|
44
45
|
mode: "facets";
|
|
45
46
|
count: number;
|
|
46
47
|
facets: SpecFacet[];
|
|
47
|
-
}
|
|
48
|
-
export interface SpecFacetsDistributionResponse {
|
|
49
|
-
mode: "distribution";
|
|
50
|
-
attributeId: string;
|
|
51
|
-
count: number;
|
|
52
|
-
values: SpecFacetValue[];
|
|
53
|
-
}
|
|
48
|
+
};
|
package/spec-types.d.ts
CHANGED
|
@@ -16,16 +16,17 @@
|
|
|
16
16
|
* - `client.search({ filters: [...] })` — spec gates layered onto a RANKED
|
|
17
17
|
* (colour / visual / lexical) search. Reach for it when relevance still
|
|
18
18
|
* matters: "something like this seed, but Class A".
|
|
19
|
-
* - `client.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* - `client.listVariants({ filters })` — the value-anchored, SPEC-ONLY
|
|
20
|
+
* primitive, which takes a `{field: attributeId, op, ...}` filter leaf
|
|
21
|
+
* (build one term with the helpers in `./specifications`, or a plain
|
|
22
|
+
* object literal for anything else). Reach for it when the spec is the
|
|
23
|
+
* whole question and ranking would be noise.
|
|
23
24
|
*
|
|
24
25
|
* Discover the tokens before you spend them: `client.specificationFacets()` for
|
|
25
26
|
* what is facetable at all, `client.attributeValues()` for one attribute's
|
|
26
|
-
* values, `client.
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* values, `client.listAttributes({ query })` to turn a phrase into a canonical
|
|
28
|
+
* attribute id. A hand-guessed token returns an honest empty answer, which is
|
|
29
|
+
* the hardest kind of bug to see.
|
|
29
30
|
*/
|
|
30
31
|
/**
|
|
31
32
|
* The virtual attribute id that matches a certification PROGRAMME wherever it
|
|
@@ -57,18 +58,24 @@ export type CertificationProgrammeAttributeId = typeof CERTIFICATION_PROGRAMME_A
|
|
|
57
58
|
* A term-equality spec criterion: the attribute must carry this canonical value,
|
|
58
59
|
* e.g. `{ attributeId: "fire_classifications", term: "astm_e84_class_a" }`.
|
|
59
60
|
* Discover valid `attributeId`/`term` pairs via `client.specificationFacets()`
|
|
60
|
-
* and `client.attributeValues()`, or
|
|
61
|
-
* `client.
|
|
61
|
+
* and `client.attributeValues()`, or find an attribute from free text with
|
|
62
|
+
* `client.listAttributes({ query })`.
|
|
62
63
|
*/
|
|
63
|
-
export
|
|
64
|
+
export type SpecTermFilter = {
|
|
64
65
|
attributeId: string;
|
|
65
66
|
term: string;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* A negative spec criterion. `whenUnstated` (absent = `"drop"`) controls what an
|
|
70
|
+
* UNKNOWN effective value does: `"drop"` requires a known value, so a variant
|
|
71
|
+
* that never states the attribute does not pass; `"pass"` admits it — a variant
|
|
72
|
+
* only fails by STATING the excluded term.
|
|
73
|
+
*/
|
|
74
|
+
export type SpecExclusionFilter = {
|
|
69
75
|
attributeId: string;
|
|
70
76
|
excludesTerm: string;
|
|
71
|
-
|
|
77
|
+
whenUnstated?: "drop" | "pass";
|
|
78
|
+
};
|
|
72
79
|
/**
|
|
73
80
|
* A presence spec criterion: the attribute must have ANY accepted value —
|
|
74
81
|
* "has an EPD on record", "has a measured slip figure at all". Not supported for
|
|
@@ -90,10 +97,10 @@ export type SpecPresenceFilter = {
|
|
|
90
97
|
* virtual {@link CERTIFICATION_PROGRAMME_ATTRIBUTE_ID}. Structurally assignable
|
|
91
98
|
* to {@link SpecFilter}, so it drops straight into `search({ filters })`.
|
|
92
99
|
*/
|
|
93
|
-
export
|
|
100
|
+
export type CertificationProgrammeFilter = {
|
|
94
101
|
attributeId: CertificationProgrammeAttributeId;
|
|
95
102
|
term: string;
|
|
96
|
-
}
|
|
103
|
+
};
|
|
97
104
|
/**
|
|
98
105
|
* A hard, canonical-attribute spec criterion — either a term match or a
|
|
99
106
|
* presence check. The union the search engine applies as a restriction BEFORE
|
package/spec-types.js
CHANGED
|
@@ -16,16 +16,17 @@
|
|
|
16
16
|
* - `client.search({ filters: [...] })` — spec gates layered onto a RANKED
|
|
17
17
|
* (colour / visual / lexical) search. Reach for it when relevance still
|
|
18
18
|
* matters: "something like this seed, but Class A".
|
|
19
|
-
* - `client.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* - `client.listVariants({ filters })` — the value-anchored, SPEC-ONLY
|
|
20
|
+
* primitive, which takes a `{field: attributeId, op, ...}` filter leaf
|
|
21
|
+
* (build one term with the helpers in `./specifications`, or a plain
|
|
22
|
+
* object literal for anything else). Reach for it when the spec is the
|
|
23
|
+
* whole question and ranking would be noise.
|
|
23
24
|
*
|
|
24
25
|
* Discover the tokens before you spend them: `client.specificationFacets()` for
|
|
25
26
|
* what is facetable at all, `client.attributeValues()` for one attribute's
|
|
26
|
-
* values, `client.
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* values, `client.listAttributes({ query })` to turn a phrase into a canonical
|
|
28
|
+
* attribute id. A hand-guessed token returns an honest empty answer, which is
|
|
29
|
+
* the hardest kind of bug to see.
|
|
29
30
|
*/
|
|
30
31
|
/**
|
|
31
32
|
* The virtual attribute id that matches a certification PROGRAMME wherever it
|