@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.
Files changed (158) hide show
  1. package/association-types.d.ts +38 -27
  2. package/associations-client.d.ts +4 -4
  3. package/associations-client.js +3 -10
  4. package/attribution-types.d.ts +4 -4
  5. package/brand-types.d.ts +14 -6
  6. package/brands-client.d.ts +7 -38
  7. package/brands-client.js +10 -40
  8. package/by-colour-types.d.ts +58 -31
  9. package/by-spec-types.d.ts +6 -73
  10. package/by-spec-types.js +7 -5
  11. package/catalog-discovery-types.d.ts +11 -26
  12. package/catalogue-client.d.ts +16 -35
  13. package/catalogue-client.js +32 -67
  14. package/catalogue-types.d.ts +44 -4
  15. package/categories-client.d.ts +63 -0
  16. package/categories-client.js +48 -0
  17. package/category-types.d.ts +114 -0
  18. package/citation-types.d.ts +4 -4
  19. package/client-config.d.ts +4 -6
  20. package/client-config.js +2 -4
  21. package/client-source-material-comments.d.ts +4 -4
  22. package/client-source-material-comments.js +4 -38
  23. package/client-vocabulary.d.ts +7 -9
  24. package/client-vocabulary.js +22 -30
  25. package/client.d.ts +110 -244
  26. package/client.js +217 -387
  27. package/codes-client.d.ts +2 -2
  28. package/codes-client.js +8 -8
  29. package/codes-types.d.ts +55 -34
  30. package/colour-aggregate-types.d.ts +24 -24
  31. package/colours-client.d.ts +6 -11
  32. package/colours-client.js +11 -19
  33. package/coordinate-client.d.ts +2 -2
  34. package/coordinate-client.js +1 -13
  35. package/corpus-scope-types.d.ts +3 -3
  36. package/corpus-scope-types.js +1 -25
  37. package/demand-batch-types.d.ts +18 -18
  38. package/demand-types.d.ts +24 -24
  39. package/errors.d.ts +13 -13
  40. package/errors.js +54 -52
  41. package/http-transport.d.ts +13 -21
  42. package/http-transport.js +31 -28
  43. package/identify-types.d.ts +54 -44
  44. package/identify-types.js +5 -3
  45. package/index.d.ts +11 -27
  46. package/index.js +4 -6
  47. package/list-variant-facets-types.d.ts +60 -0
  48. package/list-variants-types.d.ts +142 -0
  49. package/list-variants-types.js +12 -0
  50. package/match-stream-types.d.ts +45 -32
  51. package/match-stream-types.js +2 -0
  52. package/match-stream.d.ts +2 -2
  53. package/match-stream.js +67 -49
  54. package/match-types.d.ts +32 -8
  55. package/material-asset-types.d.ts +89 -69
  56. package/material-block-types.d.ts +8 -6
  57. package/material-composition-client.d.ts +21 -0
  58. package/material-composition-client.js +13 -0
  59. package/material-composition-types.d.ts +403 -0
  60. package/material-query-plan-types.d.ts +61 -145
  61. package/material-resources-client.d.ts +12 -10
  62. package/material-resources-client.js +27 -22
  63. package/material-types.d.ts +16 -24
  64. package/ndjson.js +16 -8
  65. package/operations.d.ts +6 -6
  66. package/operations.js +29 -156
  67. package/package.json +5 -1
  68. package/palette-search-types.d.ts +87 -52
  69. package/query-understanding-types.d.ts +65 -40
  70. package/resolve-client.d.ts +8 -12
  71. package/resolve-client.js +9 -13
  72. package/resolve-colour-types.d.ts +8 -8
  73. package/resolve-types.d.ts +10 -10
  74. package/response-schemas.d.ts +20 -0
  75. package/response-schemas.generated.d.ts +5 -0
  76. package/response-schemas.generated.js +160 -0
  77. package/response-schemas.js +113 -0
  78. package/run-types.d.ts +64 -145
  79. package/runs.d.ts +9 -58
  80. package/runs.js +60 -149
  81. package/samplize-catalogue-types.d.ts +18 -18
  82. package/samplize-colorvec.d.ts +20 -20
  83. package/samplize-colorvec.js +66 -42
  84. package/schema-resource-types.d.ts +52 -39
  85. package/schema-resource-types.js +5 -5
  86. package/search-card-types.d.ts +26 -4
  87. package/search-index-plan-types.d.ts +70 -0
  88. package/search-refinement-types.d.ts +10 -40
  89. package/search-refinement-types.js +1 -0
  90. package/search-request-types.d.ts +121 -204
  91. package/search-response-receipt-types.d.ts +13 -253
  92. package/search-response-types.d.ts +2 -2
  93. package/search-result-validation.d.ts +3 -19
  94. package/search-result-validation.js +42 -183
  95. package/source-material-validation.d.ts +3 -3
  96. package/source-material-validation.js +35 -39
  97. package/spec-facet-types.d.ts +11 -16
  98. package/spec-types.d.ts +23 -16
  99. package/spec-types.js +8 -7
  100. package/specifications.d.ts +14 -72
  101. package/specifications.js +39 -89
  102. package/sse.d.ts +2 -2
  103. package/sse.js +32 -16
  104. package/substitute-types.d.ts +22 -22
  105. package/suggest-types.d.ts +11 -17
  106. package/suggest-types.js +3 -3
  107. package/taxonomy-coverage-types.d.ts +8 -8
  108. package/types.d.ts +202 -405
  109. package/validation.d.ts +26 -26
  110. package/validation.js +97 -92
  111. package/webmcp.d.ts +3 -3
  112. package/webmcp.generated.js +10 -93
  113. package/brand-most-sampled-types.d.ts +0 -33
  114. package/browse-types.d.ts +0 -236
  115. package/client-browse.d.ts +0 -78
  116. package/client-browse.js +0 -72
  117. package/client-exploration.d.ts +0 -9
  118. package/client-exploration.js +0 -7
  119. package/compare-types.d.ts +0 -89
  120. package/composition-client.d.ts +0 -33
  121. package/composition-client.js +0 -27
  122. package/composition-types.d.ts +0 -455
  123. package/composition-types.js +0 -10
  124. package/correction-types.d.ts +0 -88
  125. package/exploration-types.d.ts +0 -118
  126. package/exploration-types.js +0 -1
  127. package/fan-deck-types.d.ts +0 -42
  128. package/fan-deck-types.js +0 -9
  129. package/match-rejection-types.d.ts +0 -43
  130. package/match-rejection-types.js +0 -7
  131. package/palette-completion-types.d.ts +0 -115
  132. package/palette-completion-types.js +0 -1
  133. package/project-types.d.ts +0 -79
  134. package/project-types.js +0 -9
  135. package/projects.d.ts +0 -13
  136. package/projects.js +0 -46
  137. package/render-assets-types.d.ts +0 -65
  138. package/render-assets-types.js +0 -1
  139. package/run-events.d.ts +0 -106
  140. package/run-events.js +0 -32
  141. package/schedule-reference-types.d.ts +0 -46
  142. package/schedule-reference-types.js +0 -1
  143. package/schedule-references-client.d.ts +0 -14
  144. package/schedule-references-client.js +0 -26
  145. package/schedule-types.d.ts +0 -261
  146. package/schedule-types.js +0 -1
  147. package/search-collapse-types.d.ts +0 -69
  148. package/search-collapse-types.js +0 -1
  149. package/search-mood-types.d.ts +0 -6
  150. package/search-mood-types.js +0 -1
  151. package/search-window-types.d.ts +0 -25
  152. package/search-window-types.js +0 -1
  153. package/slot-pool-types.d.ts +0 -372
  154. package/slot-pool-types.js +0 -1
  155. /package/{brand-most-sampled-types.js → category-types.js} +0 -0
  156. /package/{browse-types.js → list-variant-facets-types.js} +0 -0
  157. /package/{compare-types.js → material-composition-types.js} +0 -0
  158. /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 interface SearchResponse {
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 { 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"];
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 function checkSearchResultItem(value: unknown, what: string, fail: ValidationFailure): void;
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 function checkSearchResultItems(items: readonly unknown[], what: string, fail: ValidationFailure): void;
11
+ export declare const checkSearchResultItems: (items: readonly unknown[], what: string, fail: ValidationFailure) => void;
@@ -1,178 +1,46 @@
1
- import { requireArray, requireBoolean, requireEnum, requireNonEmptyString, requireNonEmptyStringArray, requireNullableBoundedNumber, requireNullableEnum, requireNullableNonEmptyString, requireNullableNumber, requireNullableString, requireNumber, requireOptionalBoolean, requireOptionalNumber, requireOptionalString, requireRecord, requireString, requireIntegerAtLeast, } from "./validation.js";
2
- // ── Closed enum members (canonical `z.enum` mirrors) ─────────────────────────
3
- /** `verdictStateSchema` — the six live cell states. */
4
- export const VERDICT_STATES = [
5
- "matched",
6
- "deviated",
7
- "failed",
8
- "unverified",
9
- "missing",
10
- "not_evaluated",
11
- ];
12
- /** `attributeVerdictSchema.strength`. */
13
- export const VERDICT_STRENGTHS = ["required", "preferred"];
14
- /** `verdictBaselineSchema` discriminant — the four arms of what a cell was judged against. */
15
- export const VERDICT_BASELINE_KINDS = [
16
- "term",
17
- "presence",
18
- "seed",
19
- "image",
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
- function checkPaletteSwatch(value, what, fail) {
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
- function checkColourMeta(value, what, fail) {
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` — five required scores plus additive portrait. */
77
- function checkSignals(value, what, fail) {
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
- const cells = requireArray(record.cells, `${what}.cells`, fail);
172
- cells.forEach((cell, index) => checkAttributeVerdict(cell, `${what}.cells[${index}]`, fail));
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 function checkSearchResultItem(value, what, fail) {
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) => checkPaletteSwatch(swatch, `${what}.palette[${index}]`, fail));
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
- if (record.siblingCount !== undefined) {
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 function checkSearchResultItems(items, what, fail) {
239
- items.forEach((item, index) => checkSearchResultItem(item, `${what}[${index}]`, fail));
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 function checkSourceMaterialBatchDetail(value: unknown, batchId: string): SourceMaterialBatchDetail;
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 function checkSourceMaterialCommentThread(value: unknown, sourceId: string): SourceMaterialCommentThread;
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 function checkPostSourceMaterialCommentResult(value: unknown, sourceId: string): PostSourceMaterialCommentResult;
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
- function detailFailure(batchId) {
52
- return (reason) => MaterialGraphError.unexpected(`GET /api/v1/source-material/${batchId} returned a malformed body: ${reason}`);
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
- analysedAt: requireNonEmptyString(record.analysedAt, `${what}.analysedAt`, fail),
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
- function checkCommentBase(record, what, fail) {
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
- function checkComment(value, what, fail) {
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
- function checkFile(value, what, fail) {
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
- sourceId: requireString(record.sourceId, `${what}.sourceId`, fail),
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 function checkSourceMaterialBatchDetail(value, batchId) {
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
- function commentsFailure(verb, sourceId) {
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 function checkSourceMaterialCommentThread(value, sourceId) {
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
- function checkStoredComment(value, what, fail) {
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 function checkPostSourceMaterialCommentResult(value, sourceId) {
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
+ };
@@ -1,9 +1,9 @@
1
- export interface SpecFacetValue {
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 interface SpecFacetRange {
20
+ export type SpecFacetRange = {
21
21
  min: number;
22
22
  max: number;
23
23
  variants: number;
24
24
  unparseableVariants: number;
25
- }
26
- export interface SpecFacet {
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 `attribute:value`. `numeric` — `values` is EMPTY and `range` carries the
33
- * span; send inclusive bounds via `specRangeCriterion` instead.
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 interface SpecFacetsListResponse {
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.bySpec(...)` — the value-anchored, SPEC-ONLY primitive, which takes
20
- * the same criteria as `attribute:value` strings (build them with the helpers
21
- * in `./specifications`). Reach for it when the spec is the whole question and
22
- * ranking would be noise.
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.resolveAttribute()` / `client.resolveAttributeValue()` to turn
27
- * a phrase into a canonical pair. A hand-guessed token returns an honest empty
28
- * answer, which is the hardest kind of bug to see.
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 resolve free text with
61
- * `client.resolveAttribute()`.
61
+ * and `client.attributeValues()`, or find an attribute from free text with
62
+ * `client.listAttributes({ query })`.
62
63
  */
63
- export interface SpecTermFilter {
64
+ export type SpecTermFilter = {
64
65
  attributeId: string;
65
66
  term: string;
66
- }
67
- /** A negative spec criterion; absence of a value remains unproven. */
68
- export interface SpecExclusionFilter {
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 interface CertificationProgrammeFilter {
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.bySpec(...)` — the value-anchored, SPEC-ONLY primitive, which takes
20
- * the same criteria as `attribute:value` strings (build them with the helpers
21
- * in `./specifications`). Reach for it when the spec is the whole question and
22
- * ranking would be noise.
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.resolveAttribute()` / `client.resolveAttributeValue()` to turn
27
- * a phrase into a canonical pair. A hand-guessed token returns an honest empty
28
- * answer, which is the hardest kind of bug to see.
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