@instruments/materialgraph 0.26.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 +18 -7
- package/attribution-types.d.ts +2 -2
- package/brand-types.d.ts +8 -0
- package/brands-client.d.ts +5 -36
- package/brands-client.js +7 -33
- package/by-colour-types.d.ts +40 -13
- package/by-spec-types.d.ts +0 -67
- package/by-spec-types.js +7 -5
- package/catalog-discovery-types.d.ts +1 -16
- package/catalogue-client.d.ts +12 -31
- package/catalogue-client.js +10 -46
- package/catalogue-types.d.ts +40 -0
- package/categories-client.d.ts +63 -0
- package/categories-client.js +48 -0
- package/category-types.d.ts +114 -0
- package/client-config.d.ts +2 -4
- package/client-config.js +2 -4
- package/client-vocabulary.d.ts +2 -4
- package/client-vocabulary.js +11 -9
- package/client.d.ts +103 -203
- package/client.js +126 -251
- package/codes-types.d.ts +21 -0
- package/colours-client.d.ts +4 -9
- package/colours-client.js +0 -8
- package/errors.d.ts +1 -1
- package/errors.js +1 -0
- package/identify-types.d.ts +49 -39
- package/identify-types.js +5 -3
- package/index.d.ts +11 -21
- package/index.js +4 -4
- 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 +13 -0
- package/match-stream-types.js +2 -0
- package/match-stream.js +12 -8
- package/match-types.d.ts +24 -0
- package/material-asset-types.d.ts +69 -49
- package/material-block-types.d.ts +2 -0
- 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 +57 -141
- package/material-resources-client.d.ts +9 -7
- package/material-resources-client.js +19 -4
- package/material-types.d.ts +6 -14
- package/operations.d.ts +1 -1
- package/operations.js +20 -117
- package/package.json +5 -1
- package/palette-search-types.d.ts +59 -24
- package/query-understanding-types.d.ts +27 -5
- package/resolve-client.d.ts +6 -10
- package/resolve-client.js +5 -7
- 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 +12 -93
- package/runs.d.ts +1 -37
- package/runs.js +2 -79
- package/schema-resource-types.d.ts +14 -1
- package/search-card-types.d.ts +22 -0
- package/search-index-plan-types.d.ts +70 -0
- package/search-refinement-types.d.ts +8 -38
- package/search-refinement-types.js +1 -0
- package/search-request-types.d.ts +117 -200
- package/search-response-receipt-types.d.ts +12 -268
- package/search-result-validation.d.ts +0 -16
- package/search-result-validation.js +22 -180
- package/spec-facet-types.d.ts +3 -8
- package/spec-types.d.ts +17 -10
- package/spec-types.js +8 -7
- package/specifications.d.ts +13 -55
- package/specifications.js +29 -73
- package/suggest-types.d.ts +4 -10
- package/suggest-types.js +1 -1
- package/types.d.ts +135 -338
- package/webmcp.d.ts +1 -1
- package/webmcp.generated.js +10 -94
- 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 -71
- package/client-exploration.d.ts +0 -9
- package/client-exploration.js +0 -5
- package/compare-types.d.ts +0 -89
- package/composition-client.d.ts +0 -33
- package/composition-client.js +0 -28
- 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/render-assets-types.d.ts +0 -65
- package/render-assets-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,20 +1,4 @@
|
|
|
1
1
|
import type { ValidationFailure } from "./validation.js";
|
|
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"];
|
|
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
|
|
@@ -1,44 +1,23 @@
|
|
|
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) => {
|
|
@@ -50,23 +29,6 @@ const checkPaletteSwatch = (value, what, fail) => {
|
|
|
50
29
|
requireNullableString(record.name, `${what}.name`, fail);
|
|
51
30
|
}
|
|
52
31
|
};
|
|
53
|
-
/**
|
|
54
|
-
* `variants[]` — one entry of a product-collapsed result's ranked variant list
|
|
55
|
-
* (`collapsedVariantSchema`). A SLIM projection, not a nested result item: the
|
|
56
|
-
* checks here are the whole shape.
|
|
57
|
-
*/
|
|
58
|
-
const checkCollapsedVariant = (value, what, fail) => {
|
|
59
|
-
const record = requireRecord(value, what, fail);
|
|
60
|
-
requireString(record.variantId, `${what}.variantId`, fail);
|
|
61
|
-
requireNullableString(record.variantName, `${what}.variantName`, fail);
|
|
62
|
-
requireNullableString(record.color, `${what}.color`, fail);
|
|
63
|
-
// `.optional()`, NOT nullable: the emit path omits the key when no palette is
|
|
64
|
-
// on record rather than sending null.
|
|
65
|
-
requireOptionalString(record.hex, `${what}.hex`, fail);
|
|
66
|
-
requireNullableString(record.imageUrl, `${what}.imageUrl`, fail);
|
|
67
|
-
requireOptionalString(record.materialBankId, `${what}.materialBankId`, fail);
|
|
68
|
-
requireNumber(record.score, `${what}.score`, fail);
|
|
69
|
-
};
|
|
70
32
|
/** `colourMeta` — three independently optional strings. */
|
|
71
33
|
const checkColourMeta = (value, what, fail) => {
|
|
72
34
|
const record = requireRecord(value, what, fail);
|
|
@@ -74,112 +36,10 @@ const checkColourMeta = (value, what, fail) => {
|
|
|
74
36
|
requireOptionalString(record.dominantFamily, `${what}.dominantFamily`, fail);
|
|
75
37
|
requireOptionalString(record.dominantHex, `${what}.dominantHex`, fail);
|
|
76
38
|
};
|
|
77
|
-
/** `searchResultSignalsSchema` —
|
|
39
|
+
/** `searchResultSignalsSchema` — the Match image cosine, nullable. */
|
|
78
40
|
const checkSignals = (value, what, fail) => {
|
|
79
41
|
const record = requireRecord(value, what, fail);
|
|
80
|
-
|
|
81
|
-
requireNullableNumber(record.portrait, `${what}.portrait`, fail);
|
|
82
|
-
}
|
|
83
|
-
for (const signal of ["image", "category", "colour", "lexical", "text"]) {
|
|
84
|
-
requireNullableNumber(record[signal], `${what}.${signal}`, fail);
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
/** `visualMeaningSchema` — note the NON-EMPTY `values` array and the closed `basis`. */
|
|
88
|
-
const checkVisualMeaning = (value, what, fail) => {
|
|
89
|
-
const record = requireRecord(value, what, fail);
|
|
90
|
-
requireString(record.id, `${what}.id`, fail);
|
|
91
|
-
requireString(record.label, `${what}.label`, fail);
|
|
92
|
-
requireNonEmptyStringArray(record.values, `${what}.values`, fail);
|
|
93
|
-
requireEnum(record.basis, VISUAL_MEANING_BASES, `${what}.basis`, fail);
|
|
94
|
-
requireOptionalNumber(record.confidence, `${what}.confidence`, fail);
|
|
95
|
-
requireOptionalString(record.sourceBasis, `${what}.sourceBasis`, fail);
|
|
96
|
-
requireOptionalString(record.version, `${what}.version`, fail);
|
|
97
|
-
requireOptionalString(record.appliedAt, `${what}.appliedAt`, fail);
|
|
98
|
-
};
|
|
99
|
-
// ── Verdict subtree ──────────────────────────────────────────────────────────
|
|
100
|
-
/**
|
|
101
|
-
* `verdictBaselineSchema` — a discriminated union on `kind`. An unknown
|
|
102
|
-
* discriminant is rejected, exactly as `z.discriminatedUnion` rejects it, and
|
|
103
|
-
* each arm carries only the fields it needs.
|
|
104
|
-
*/
|
|
105
|
-
const checkVerdictBaseline = (value, what, fail) => {
|
|
106
|
-
const record = requireRecord(value, what, fail);
|
|
107
|
-
const kind = requireEnum(record.kind, VERDICT_BASELINE_KINDS, `${what}.kind`, fail);
|
|
108
|
-
switch (kind) {
|
|
109
|
-
case "term": {
|
|
110
|
-
requireNonEmptyString(record.term, `${what}.term`, fail);
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
case "presence": {
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
case "seed": {
|
|
117
|
-
requireNonEmptyString(record.variantId, `${what}.variantId`, fail);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
default: {
|
|
121
|
-
// `image` — the like-this-picture arm.
|
|
122
|
-
requireEnum(record.source, VERDICT_IMAGE_SOURCES, `${what}.source`, fail);
|
|
123
|
-
requireNullableNonEmptyString(record.variantId, `${what}.variantId`, fail);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
/** `verdictDeviationSchema` — a named near-miss. */
|
|
128
|
-
const checkVerdictDeviation = (value, what, fail) => {
|
|
129
|
-
const record = requireRecord(value, what, fail);
|
|
130
|
-
requireEnum(record.direction, VERDICT_DEVIATION_DIRECTIONS, `${what}.direction`, fail);
|
|
131
|
-
requireNullableNumber(record.delta, `${what}.delta`, fail);
|
|
132
|
-
requireNullableString(record.unit, `${what}.unit`, fail);
|
|
133
|
-
requireNonEmptyString(record.label, `${what}.label`, fail);
|
|
134
|
-
};
|
|
135
|
-
/**
|
|
136
|
-
* `attributeVerdictSchema` — nine fields plus the TWO cross-field invariants its
|
|
137
|
-
* `.superRefine()` enforces. Those are relational rather than field-wise, which
|
|
138
|
-
* is no obstacle to a hand-rolled validator: they are two conditionals, and the
|
|
139
|
-
* schema's own doc calls them out as the reason a malformed cell can never reach
|
|
140
|
-
* a consumer.
|
|
141
|
-
*/
|
|
142
|
-
const checkAttributeVerdict = (value, what, fail) => {
|
|
143
|
-
const record = requireRecord(value, what, fail);
|
|
144
|
-
requireNonEmptyString(record.attributeId, `${what}.attributeId`, fail);
|
|
145
|
-
const state = requireEnum(record.state, VERDICT_STATES, `${what}.state`, fail);
|
|
146
|
-
requireEnum(record.strength, VERDICT_STRENGTHS, `${what}.strength`, fail);
|
|
147
|
-
checkVerdictBaseline(record.baseline, `${what}.baseline`, fail);
|
|
148
|
-
requireNullableString(record.value, `${what}.value`, fail);
|
|
149
|
-
requireNullableBoundedNumber(record.confidence, `${what}.confidence`, fail, 0, 1);
|
|
150
|
-
requireNullableString(record.sourceBasis, `${what}.sourceBasis`, fail);
|
|
151
|
-
if (record.citation !== undefined) {
|
|
152
|
-
const citation = requireRecord(record.citation, `${what}.citation`, fail);
|
|
153
|
-
requireNonEmptyString(citation.citationId, `${what}.citation.citationId`, fail);
|
|
154
|
-
requireNonEmptyString(citation.sourceType, `${what}.citation.sourceType`, fail);
|
|
155
|
-
const count = requireNumber(citation.count, `${what}.citation.count`, fail);
|
|
156
|
-
if (!Number.isInteger(count) || count <= 0) {
|
|
157
|
-
throw fail(`${what}.citation.count must be a positive integer`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
const { deviation } = record;
|
|
161
|
-
if (deviation !== null) {
|
|
162
|
-
checkVerdictDeviation(deviation, `${what}.deviation`, fail);
|
|
163
|
-
}
|
|
164
|
-
const trustGap = requireNullableEnum(record.trustGap, VERDICT_TRUST_GAPS, `${what}.trustGap`, fail);
|
|
165
|
-
// superRefine invariant 1 — a deviation only describes a `deviated` cell.
|
|
166
|
-
if (deviation !== null && state !== "deviated") {
|
|
167
|
-
throw fail(`${what}.deviation is set on a cell whose state is "${state}", not "deviated"`);
|
|
168
|
-
}
|
|
169
|
-
// superRefine invariant 2 — a trust gap only explains an `unverified` cell.
|
|
170
|
-
if (trustGap !== null && state !== "unverified") {
|
|
171
|
-
throw fail(`${what}.trustGap is set on a cell whose state is "${state}", not "unverified"`);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
/** `candidateVerdictSchema` — the whole report card. */
|
|
175
|
-
const checkCandidateVerdict = (value, what, fail) => {
|
|
176
|
-
const record = requireRecord(value, what, fail);
|
|
177
|
-
const cells = requireArray(record.cells, `${what}.cells`, fail);
|
|
178
|
-
cells.forEach((cell, index) => {
|
|
179
|
-
checkAttributeVerdict(cell, `${what}.cells[${index}]`, fail);
|
|
180
|
-
});
|
|
181
|
-
requireEnum(record.grade, VERDICT_GRADES, `${what}.grade`, fail);
|
|
182
|
-
requireNullableBoundedNumber(record.minConfidence, `${what}.minConfidence`, fail, 0, 1);
|
|
42
|
+
requireNullableNumber(record.image, `${what}.image`, fail);
|
|
183
43
|
};
|
|
184
44
|
// ── The item ─────────────────────────────────────────────────────────────────
|
|
185
45
|
/**
|
|
@@ -230,24 +90,6 @@ export const checkSearchResultItem = (value, what, fail) => {
|
|
|
230
90
|
requireOptionalBoolean(record.sampleable, `${what}.sampleable`, fail);
|
|
231
91
|
requireOptionalBoolean(record.belowFloor, `${what}.belowFloor`, fail);
|
|
232
92
|
requireOptionalBoolean(record.admittedOnColour, `${what}.admittedOnColour`, fail);
|
|
233
|
-
if (record.siblingCount !== undefined) {
|
|
234
|
-
requireIntegerAtLeast(record.siblingCount, `${what}.siblingCount`, fail, 1);
|
|
235
|
-
}
|
|
236
|
-
if (record.variants !== undefined) {
|
|
237
|
-
const variants = requireArray(record.variants, `${what}.variants`, fail);
|
|
238
|
-
variants.forEach((variant, index) => {
|
|
239
|
-
checkCollapsedVariant(variant, `${what}.variants[${index}]`, fail);
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
if (record.visualMeanings !== undefined) {
|
|
243
|
-
const meanings = requireArray(record.visualMeanings, `${what}.visualMeanings`, fail);
|
|
244
|
-
meanings.forEach((meaning, index) => {
|
|
245
|
-
checkVisualMeaning(meaning, `${what}.visualMeanings[${index}]`, fail);
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
if (record.verdict !== undefined) {
|
|
249
|
-
checkCandidateVerdict(record.verdict, `${what}.verdict`, fail);
|
|
250
|
-
}
|
|
251
93
|
};
|
|
252
94
|
/** Prove every entry of a `results[]` array is a {@link SearchResultItem}. */
|
|
253
95
|
export const checkSearchResultItems = (items, what, fail) => {
|
package/spec-facet-types.d.ts
CHANGED
|
@@ -29,8 +29,9 @@ export type SpecFacet = {
|
|
|
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;
|
|
@@ -45,9 +46,3 @@ export type SpecFacetsListResponse = {
|
|
|
45
46
|
count: number;
|
|
46
47
|
facets: SpecFacet[];
|
|
47
48
|
};
|
|
48
|
-
export type SpecFacetsDistributionResponse = {
|
|
49
|
-
mode: "distribution";
|
|
50
|
-
attributeId: string;
|
|
51
|
-
count: number;
|
|
52
|
-
values: SpecFacetValue[];
|
|
53
|
-
};
|
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,17 +58,23 @@ 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
64
|
export type SpecTermFilter = {
|
|
64
65
|
attributeId: string;
|
|
65
66
|
term: string;
|
|
66
67
|
};
|
|
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
|
+
*/
|
|
68
74
|
export type SpecExclusionFilter = {
|
|
69
75
|
attributeId: string;
|
|
70
76
|
excludesTerm: string;
|
|
77
|
+
whenUnstated?: "drop" | "pass";
|
|
71
78
|
};
|
|
72
79
|
/**
|
|
73
80
|
* A presence spec criterion: the attribute must have ANY accepted value —
|
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
|
package/specifications.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { CertificationProgrammeFilter, SpecExclusionFilter, SpecPresenceFilter, SpecTermFilter } from "./spec-types.js";
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* A hard criterion excluding one canonical value.
|
|
4
|
+
*
|
|
5
|
+
* By default retains only variants with a KNOWN value (unstated does not
|
|
6
|
+
* pass). Pass `whenUnstated: "pass"` to admit unstated variants too — only a
|
|
7
|
+
* variant that STATES the excluded term is dropped.
|
|
8
|
+
*/
|
|
9
|
+
export declare const excludeSpec: (attributeId: string, excludesTerm: string, whenUnstated?: "drop" | "pass") => SpecExclusionFilter;
|
|
4
10
|
/**
|
|
5
11
|
* A term-equality criterion for `search({ filters })` — the attribute must
|
|
6
12
|
* carry this canonical value.
|
|
@@ -24,61 +30,13 @@ export declare const hasSpec: (attributeId: string) => SpecPresenceFilter;
|
|
|
24
30
|
* virtual attribute so a programme matches wherever it landed at ingest
|
|
25
31
|
* (compliance / sustainability / material-health / VOC buckets).
|
|
26
32
|
*
|
|
27
|
-
* The value
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
33
|
+
* The value is a canonical programme token (e.g. `greenguard_certificate`,
|
|
34
|
+
* `has_greenguard_gold_certification`). The one search matches tokens on its
|
|
35
|
+
* index and does not resolve display names, so a name such as
|
|
36
|
+
* "GREENGUARD Gold" is a 400 naming the term.
|
|
31
37
|
*
|
|
32
38
|
* @example
|
|
33
39
|
* // "find a GREENGUARD-certified alternative"
|
|
34
|
-
* client.search({ similarTo: { variantId: seed }, filters: [certificationProgramme("
|
|
40
|
+
* client.search({ similarTo: { variantId: seed }, filters: [certificationProgramme("has_greenguard_gold_certification")] });
|
|
35
41
|
*/
|
|
36
42
|
export declare const certificationProgramme: (programme: string) => CertificationProgrammeFilter;
|
|
37
|
-
/**
|
|
38
|
-
* An `attribute:value` criterion string for `bySpec`. The `bySpec` route splits
|
|
39
|
-
* on the FIRST `:`, so a value may itself contain colons; the attribute id may
|
|
40
|
-
* not.
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* client.bySpec([specCriterion("primary_material", "textile"), specCriterion("fire_classifications", "astm_e84_class_a")]);
|
|
44
|
-
*/
|
|
45
|
-
export declare const specCriterion: (attributeId: string, value: string) => string;
|
|
46
|
-
/**
|
|
47
|
-
* An `attribute:min..max` RANGE criterion string for `bySpec`, for a
|
|
48
|
-
* NUMBER-valued spec (`light_reflectance_value`, `nrc_rating`,
|
|
49
|
-
* `slip_resistance_dcof`, …). Pass the result in `bySpec`'s `ranges` option.
|
|
50
|
-
*
|
|
51
|
-
* BOTH BOUNDS ARE INCLUSIVE — `{ min: 60 }` matches a value of exactly 60 —
|
|
52
|
-
* because spec language is written that way ("LRV of 60 or more", "DCOF ≥ 0.42")
|
|
53
|
-
* and because inclusive-both-ends means adjacent ranges overlap at the join
|
|
54
|
-
* rather than leaving a gap that hides products.
|
|
55
|
-
*
|
|
56
|
-
* At least one bound is REQUIRED: a range with neither is a presence check, and
|
|
57
|
-
* this throws rather than sending a criterion the server would have to refuse.
|
|
58
|
-
* Discover which attributes take a range — and the span they actually occupy —
|
|
59
|
-
* from `specificationFacets()`: those are the facets with `kind: "numeric"`.
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* // light paints, every brand at once
|
|
63
|
-
* client.bySpec([], { ranges: [specRangeCriterion("light_reflectance_value", { min: 60 })] });
|
|
64
|
-
* @example
|
|
65
|
-
* // a mid-tone band
|
|
66
|
-
* specRangeCriterion("light_reflectance_value", { min: 30, max: 45 }); // "light_reflectance_value:30..45"
|
|
67
|
-
*/
|
|
68
|
-
export declare const specRangeCriterion: (attributeId: string, bounds: {
|
|
69
|
-
min?: number;
|
|
70
|
-
max?: number;
|
|
71
|
-
}) => string;
|
|
72
|
-
/**
|
|
73
|
-
* An `attribute:value` criterion string for `bySpec` that filters by
|
|
74
|
-
* certification PROGRAMME across all four certification buckets.
|
|
75
|
-
*
|
|
76
|
-
* The value may be a display name or alias ("GREENGUARD Gold", "LEED"), resolved
|
|
77
|
-
* server-side to its canonical programme token, or a canonical token directly
|
|
78
|
-
* (e.g. `greenguard_certificate`, `has_greenguard_gold_certification`).
|
|
79
|
-
*
|
|
80
|
-
* @example
|
|
81
|
-
* // every brand's GREENGUARD-Gold materials
|
|
82
|
-
* client.bySpec([certificationProgrammeCriterion("GREENGUARD Gold")]);
|
|
83
|
-
*/
|
|
84
|
-
export declare const certificationProgrammeCriterion: (programme: string) => string;
|
package/specifications.js
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Zero-dependency builders for the specification surface. Turn a
|
|
3
|
-
* spec intent into a typed
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* {@link specTerm}, {@link hasSpec}, and {@link certificationProgramme}.
|
|
8
|
-
* - `bySpec(specs)` takes `attribute:value` strings — the value-anchored,
|
|
9
|
-
* spec-only primitive. Build them with {@link specCriterion} and
|
|
10
|
-
* {@link certificationProgrammeCriterion}.
|
|
2
|
+
* Zero-dependency builders for the specification surface. Turn a
|
|
3
|
+
* plain-language spec intent into a typed {@link SpecFilter} `search({
|
|
4
|
+
* filters })` accepts: hard token sets on the one search's index, run in the
|
|
5
|
+
* same query. Build them with {@link specTerm}, {@link hasSpec}, and
|
|
6
|
+
* {@link certificationProgramme}.
|
|
11
7
|
*
|
|
12
8
|
* The point of these helpers: Desk (and any spec-driven caller) never has to
|
|
13
|
-
* remember that a certification programme lives under a VIRTUAL attribute
|
|
14
|
-
*
|
|
15
|
-
*
|
|
9
|
+
* remember that a certification programme lives under a VIRTUAL attribute.
|
|
10
|
+
*
|
|
11
|
+
* A hard specification across every brand (formerly `bySpec`, built with
|
|
12
|
+
* `specCriterion`/`specRangeCriterion`) is now `list_variants`'s `filters`:
|
|
13
|
+
* `{field: attributeId, op: "in", values: [...]}` for a term,
|
|
14
|
+
* `{field: attributeId, op: "gte"/"lte"/etc, value}` for a number — a plain
|
|
15
|
+
* object literal needs no encoder (MG-2222).
|
|
16
16
|
*/
|
|
17
17
|
import { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID } from "./spec-types.js";
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* A hard criterion excluding one canonical value.
|
|
20
|
+
*
|
|
21
|
+
* By default retains only variants with a KNOWN value (unstated does not
|
|
22
|
+
* pass). Pass `whenUnstated: "pass"` to admit unstated variants too — only a
|
|
23
|
+
* variant that STATES the excluded term is dropped.
|
|
24
|
+
*/
|
|
25
|
+
export const excludeSpec = (attributeId, excludesTerm, whenUnstated) => ({
|
|
26
|
+
attributeId,
|
|
27
|
+
excludesTerm,
|
|
28
|
+
...(whenUnstated === undefined ? {} : { whenUnstated }),
|
|
29
|
+
});
|
|
20
30
|
/**
|
|
21
31
|
* A term-equality criterion for `search({ filters })` — the attribute must
|
|
22
32
|
* carry this canonical value.
|
|
@@ -43,70 +53,16 @@ export const hasSpec = (attributeId) => ({
|
|
|
43
53
|
* virtual attribute so a programme matches wherever it landed at ingest
|
|
44
54
|
* (compliance / sustainability / material-health / VOC buckets).
|
|
45
55
|
*
|
|
46
|
-
* The value
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
56
|
+
* The value is a canonical programme token (e.g. `greenguard_certificate`,
|
|
57
|
+
* `has_greenguard_gold_certification`). The one search matches tokens on its
|
|
58
|
+
* index and does not resolve display names, so a name such as
|
|
59
|
+
* "GREENGUARD Gold" is a 400 naming the term.
|
|
50
60
|
*
|
|
51
61
|
* @example
|
|
52
62
|
* // "find a GREENGUARD-certified alternative"
|
|
53
|
-
* client.search({ similarTo: { variantId: seed }, filters: [certificationProgramme("
|
|
63
|
+
* client.search({ similarTo: { variantId: seed }, filters: [certificationProgramme("has_greenguard_gold_certification")] });
|
|
54
64
|
*/
|
|
55
65
|
export const certificationProgramme = (programme) => ({
|
|
56
66
|
attributeId: CERTIFICATION_PROGRAMME_ATTRIBUTE_ID,
|
|
57
67
|
term: programme,
|
|
58
68
|
});
|
|
59
|
-
/**
|
|
60
|
-
* An `attribute:value` criterion string for `bySpec`. The `bySpec` route splits
|
|
61
|
-
* on the FIRST `:`, so a value may itself contain colons; the attribute id may
|
|
62
|
-
* not.
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* client.bySpec([specCriterion("primary_material", "textile"), specCriterion("fire_classifications", "astm_e84_class_a")]);
|
|
66
|
-
*/
|
|
67
|
-
export const specCriterion = (attributeId, value) => `${attributeId}:${value}`;
|
|
68
|
-
/**
|
|
69
|
-
* An `attribute:min..max` RANGE criterion string for `bySpec`, for a
|
|
70
|
-
* NUMBER-valued spec (`light_reflectance_value`, `nrc_rating`,
|
|
71
|
-
* `slip_resistance_dcof`, …). Pass the result in `bySpec`'s `ranges` option.
|
|
72
|
-
*
|
|
73
|
-
* BOTH BOUNDS ARE INCLUSIVE — `{ min: 60 }` matches a value of exactly 60 —
|
|
74
|
-
* because spec language is written that way ("LRV of 60 or more", "DCOF ≥ 0.42")
|
|
75
|
-
* and because inclusive-both-ends means adjacent ranges overlap at the join
|
|
76
|
-
* rather than leaving a gap that hides products.
|
|
77
|
-
*
|
|
78
|
-
* At least one bound is REQUIRED: a range with neither is a presence check, and
|
|
79
|
-
* this throws rather than sending a criterion the server would have to refuse.
|
|
80
|
-
* Discover which attributes take a range — and the span they actually occupy —
|
|
81
|
-
* from `specificationFacets()`: those are the facets with `kind: "numeric"`.
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* // light paints, every brand at once
|
|
85
|
-
* client.bySpec([], { ranges: [specRangeCriterion("light_reflectance_value", { min: 60 })] });
|
|
86
|
-
* @example
|
|
87
|
-
* // a mid-tone band
|
|
88
|
-
* specRangeCriterion("light_reflectance_value", { min: 30, max: 45 }); // "light_reflectance_value:30..45"
|
|
89
|
-
*/
|
|
90
|
-
export const specRangeCriterion = (attributeId, bounds) => {
|
|
91
|
-
const { min, max } = bounds;
|
|
92
|
-
if (min === undefined && max === undefined) {
|
|
93
|
-
throw new Error(`specRangeCriterion("${attributeId}") needs a min, a max, or both — a range with neither would match every recorded value. Use hasSpec() for a presence check.`);
|
|
94
|
-
}
|
|
95
|
-
if (min !== undefined && max !== undefined && min > max) {
|
|
96
|
-
throw new Error(`specRangeCriterion("${attributeId}") is inverted (min ${min} > max ${max}) and can only ever match nothing.`);
|
|
97
|
-
}
|
|
98
|
-
return `${attributeId}:${min ?? ""}..${max ?? ""}`;
|
|
99
|
-
};
|
|
100
|
-
/**
|
|
101
|
-
* An `attribute:value` criterion string for `bySpec` that filters by
|
|
102
|
-
* certification PROGRAMME across all four certification buckets.
|
|
103
|
-
*
|
|
104
|
-
* The value may be a display name or alias ("GREENGUARD Gold", "LEED"), resolved
|
|
105
|
-
* server-side to its canonical programme token, or a canonical token directly
|
|
106
|
-
* (e.g. `greenguard_certificate`, `has_greenguard_gold_certification`).
|
|
107
|
-
*
|
|
108
|
-
* @example
|
|
109
|
-
* // every brand's GREENGUARD-Gold materials
|
|
110
|
-
* client.bySpec([certificationProgrammeCriterion("GREENGUARD Gold")]);
|
|
111
|
-
*/
|
|
112
|
-
export const certificationProgrammeCriterion = (programme) => specCriterion(CERTIFICATION_PROGRAMME_ATTRIBUTE_ID, programme);
|
package/suggest-types.d.ts
CHANGED
|
@@ -12,11 +12,10 @@
|
|
|
12
12
|
* `mg.searchVocabulary()` covers attribute/value tokens. Suggestions are a page
|
|
13
13
|
* of the best matches, never a census of matching entities.
|
|
14
14
|
*/
|
|
15
|
-
import type { CorpusScope, CorpusScopeReceipt } from "./corpus-scope-types.js";
|
|
16
15
|
/** Which kind of entity a suggestion is — and therefore what `refId` means. */
|
|
17
16
|
export type SuggestKind = "product" | "brand" | "category";
|
|
18
17
|
/** How the query met the suggestion's label. Strongest first. */
|
|
19
|
-
export type SuggestMatch = "prefix" | "word" | "
|
|
18
|
+
export type SuggestMatch = "prefix" | "word" | "fuzzy";
|
|
20
19
|
/** One ranked typeahead suggestion, carrying the identity it resolves to. */
|
|
21
20
|
export type Suggestion = {
|
|
22
21
|
kind: SuggestKind;
|
|
@@ -36,17 +35,15 @@ export type Suggestion = {
|
|
|
36
35
|
* Opaque, and null when none was picked — not a product without variants.
|
|
37
36
|
*/
|
|
38
37
|
variantId: string | null;
|
|
39
|
-
/**
|
|
38
|
+
/** How many of this entity's variants the search index holds: a coverage figure. */
|
|
40
39
|
variantCount: number;
|
|
41
40
|
};
|
|
42
41
|
/** The suggest response: the query as interpreted, plus the ranked suggestions. */
|
|
43
42
|
export type SuggestResponse = {
|
|
44
|
-
/** The
|
|
43
|
+
/** The typed words the search index was matched against. */
|
|
45
44
|
query: string;
|
|
46
|
-
/** Ranked best-match-first. Empty when nothing came close
|
|
45
|
+
/** Ranked best-match-first. Empty when nothing came close: a miss, not an error. */
|
|
47
46
|
suggestions: Suggestion[];
|
|
48
|
-
/** Present only when a narrowing governed corpus was applied. */
|
|
49
|
-
corpusScope?: CorpusScopeReceipt;
|
|
50
47
|
};
|
|
51
48
|
/** Options for {@link MaterialGraphClient.suggest}. */
|
|
52
49
|
export type SuggestOptions = {
|
|
@@ -54,8 +51,6 @@ export type SuggestOptions = {
|
|
|
54
51
|
limit?: number;
|
|
55
52
|
/** Restrict to these kinds. Omit for all of them. */
|
|
56
53
|
kind?: SuggestKind | SuggestKind[];
|
|
57
|
-
/** Governed corpus membership; absent means the whole graph. */
|
|
58
|
-
corpusScope?: CorpusScope;
|
|
59
54
|
};
|
|
60
55
|
/**
|
|
61
56
|
* Shape the query string for a suggest call.
|
|
@@ -69,5 +64,4 @@ export declare const suggestQuery: (q: string, options?: SuggestOptions) => {
|
|
|
69
64
|
q: string;
|
|
70
65
|
limit?: number;
|
|
71
66
|
kind?: string;
|
|
72
|
-
corpusScope?: CorpusScope;
|
|
73
67
|
};
|
package/suggest-types.js
CHANGED
|
@@ -24,5 +24,5 @@ export const suggestQuery = (q, options) => {
|
|
|
24
24
|
const kind = Array.isArray(options?.kind)
|
|
25
25
|
? options.kind.join(",")
|
|
26
26
|
: options?.kind;
|
|
27
|
-
return {
|
|
27
|
+
return { kind, limit: options?.limit, q };
|
|
28
28
|
};
|