@instruments/materialgraph 0.25.0 → 0.26.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 +20 -20
- package/associations-client.d.ts +4 -4
- package/associations-client.js +3 -10
- package/attribution-types.d.ts +3 -3
- package/brand-most-sampled-types.d.ts +4 -4
- package/brand-types.d.ts +6 -6
- package/brands-client.d.ts +4 -4
- package/brands-client.js +9 -13
- package/browse-types.d.ts +8 -8
- package/by-colour-types.d.ts +18 -18
- package/by-spec-types.d.ts +14 -14
- package/catalog-discovery-types.d.ts +14 -14
- package/catalogue-client.d.ts +4 -4
- package/catalogue-client.js +38 -37
- package/catalogue-types.d.ts +4 -4
- package/citation-types.d.ts +4 -4
- package/client-browse.d.ts +4 -4
- package/client-browse.js +24 -25
- package/client-config.d.ts +2 -2
- package/client-exploration.d.ts +3 -3
- package/client-exploration.js +4 -6
- package/client-source-material-comments.d.ts +4 -4
- package/client-source-material-comments.js +4 -38
- package/client-vocabulary.d.ts +8 -8
- package/client-vocabulary.js +20 -30
- package/client.d.ts +11 -45
- package/client.js +143 -188
- package/codes-client.d.ts +2 -2
- package/codes-client.js +8 -8
- package/codes-types.d.ts +34 -34
- package/colour-aggregate-types.d.ts +24 -24
- package/colours-client.d.ts +2 -2
- package/colours-client.js +15 -15
- package/compare-types.d.ts +10 -10
- package/composition-client.d.ts +3 -3
- package/composition-client.js +8 -7
- package/composition-types.d.ts +12 -12
- 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/correction-types.d.ts +10 -10
- package/demand-batch-types.d.ts +18 -18
- package/demand-types.d.ts +24 -24
- package/errors.d.ts +12 -12
- package/errors.js +53 -52
- package/exploration-types.d.ts +22 -22
- package/fan-deck-types.d.ts +6 -6
- package/http-transport.d.ts +13 -21
- package/http-transport.js +31 -28
- package/identify-types.d.ts +10 -10
- package/index.d.ts +0 -6
- package/index.js +0 -2
- package/match-rejection-types.d.ts +6 -6
- package/match-stream-types.d.ts +32 -32
- package/match-stream.d.ts +2 -2
- package/match-stream.js +55 -41
- package/match-types.d.ts +8 -8
- package/material-asset-types.d.ts +26 -26
- package/material-block-types.d.ts +6 -6
- package/material-query-plan-types.d.ts +26 -26
- package/material-resources-client.d.ts +5 -5
- package/material-resources-client.js +10 -20
- package/material-types.d.ts +12 -12
- package/ndjson.js +16 -8
- package/operations.d.ts +6 -6
- package/operations.js +9 -39
- package/package.json +1 -1
- package/palette-completion-types.d.ts +22 -22
- package/palette-search-types.d.ts +28 -28
- package/query-understanding-types.d.ts +38 -35
- package/render-assets-types.d.ts +8 -8
- package/resolve-client.d.ts +2 -2
- package/resolve-client.js +4 -6
- package/resolve-colour-types.d.ts +8 -8
- package/resolve-types.d.ts +10 -10
- package/run-types.d.ts +70 -70
- package/runs.d.ts +13 -26
- package/runs.js +77 -89
- 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 +38 -38
- package/schema-resource-types.js +5 -5
- package/search-card-types.d.ts +4 -4
- package/search-collapse-types.d.ts +4 -4
- package/search-mood-types.d.ts +2 -2
- package/search-refinement-types.d.ts +8 -8
- package/search-request-types.d.ts +5 -5
- package/search-response-receipt-types.d.ts +58 -42
- package/search-response-types.d.ts +2 -2
- package/search-result-validation.d.ts +3 -3
- package/search-result-validation.js +53 -36
- package/search-window-types.d.ts +2 -2
- package/slot-pool-types.d.ts +20 -20
- package/source-material-validation.d.ts +3 -3
- package/source-material-validation.js +35 -39
- package/spec-facet-types.d.ts +10 -10
- package/spec-types.d.ts +6 -6
- package/specifications.d.ts +9 -25
- package/specifications.js +15 -21
- package/sse.d.ts +2 -2
- package/sse.js +32 -16
- package/substitute-types.d.ts +22 -22
- package/suggest-types.d.ts +7 -7
- package/suggest-types.js +3 -3
- package/taxonomy-coverage-types.d.ts +8 -8
- package/types.d.ts +119 -119
- package/validation.d.ts +26 -26
- package/validation.js +97 -92
- package/webmcp.d.ts +2 -2
- package/webmcp.generated.js +94 -93
- 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/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
|
@@ -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,13 +17,13 @@ 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;
|
|
@@ -39,15 +39,15 @@ export interface SpecFacet {
|
|
|
39
39
|
/** Present ONLY on a `numeric` facet. */
|
|
40
40
|
range?: SpecFacetRange;
|
|
41
41
|
description?: string;
|
|
42
|
-
}
|
|
43
|
-
export
|
|
42
|
+
};
|
|
43
|
+
export type SpecFacetsListResponse = {
|
|
44
44
|
mode: "facets";
|
|
45
45
|
count: number;
|
|
46
46
|
facets: SpecFacet[];
|
|
47
|
-
}
|
|
48
|
-
export
|
|
47
|
+
};
|
|
48
|
+
export type SpecFacetsDistributionResponse = {
|
|
49
49
|
mode: "distribution";
|
|
50
50
|
attributeId: string;
|
|
51
51
|
count: number;
|
|
52
52
|
values: SpecFacetValue[];
|
|
53
|
-
}
|
|
53
|
+
};
|
package/spec-types.d.ts
CHANGED
|
@@ -60,15 +60,15 @@ export type CertificationProgrammeAttributeId = typeof CERTIFICATION_PROGRAMME_A
|
|
|
60
60
|
* and `client.attributeValues()`, or resolve free text with
|
|
61
61
|
* `client.resolveAttribute()`.
|
|
62
62
|
*/
|
|
63
|
-
export
|
|
63
|
+
export type SpecTermFilter = {
|
|
64
64
|
attributeId: string;
|
|
65
65
|
term: string;
|
|
66
|
-
}
|
|
66
|
+
};
|
|
67
67
|
/** A negative spec criterion; absence of a value remains unproven. */
|
|
68
|
-
export
|
|
68
|
+
export type SpecExclusionFilter = {
|
|
69
69
|
attributeId: string;
|
|
70
70
|
excludesTerm: string;
|
|
71
|
-
}
|
|
71
|
+
};
|
|
72
72
|
/**
|
|
73
73
|
* A presence spec criterion: the attribute must have ANY accepted value —
|
|
74
74
|
* "has an EPD on record", "has a measured slip figure at all". Not supported for
|
|
@@ -90,10 +90,10 @@ export type SpecPresenceFilter = {
|
|
|
90
90
|
* virtual {@link CERTIFICATION_PROGRAMME_ATTRIBUTE_ID}. Structurally assignable
|
|
91
91
|
* to {@link SpecFilter}, so it drops straight into `search({ filters })`.
|
|
92
92
|
*/
|
|
93
|
-
export
|
|
93
|
+
export type CertificationProgrammeFilter = {
|
|
94
94
|
attributeId: CertificationProgrammeAttributeId;
|
|
95
95
|
term: string;
|
|
96
|
-
}
|
|
96
|
+
};
|
|
97
97
|
/**
|
|
98
98
|
* A hard, canonical-attribute spec criterion — either a term match or a
|
|
99
99
|
* presence check. The union the search engine applies as a restriction BEFORE
|
package/specifications.d.ts
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* Zero-dependency builders for the specification surface. Turn a plain-language
|
|
3
|
-
* spec intent into a typed criterion the SDK's two spec operations accept:
|
|
4
|
-
*
|
|
5
|
-
* - `search({ filters })` takes {@link SpecFilter} objects — a spec gate layered
|
|
6
|
-
* onto a ranked (colour / visual / lexical) search. Build them with
|
|
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}.
|
|
11
|
-
*
|
|
12
|
-
* 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, nor
|
|
14
|
-
* hand-format the `attribute:value` string the `bySpec` route splits on its
|
|
15
|
-
* first `:`.
|
|
16
|
-
*/
|
|
17
|
-
import { type CertificationProgrammeFilter, type SpecExclusionFilter, type SpecPresenceFilter, type SpecTermFilter } from "./spec-types.js";
|
|
1
|
+
import type { CertificationProgrammeFilter, SpecExclusionFilter, SpecPresenceFilter, SpecTermFilter } from "./spec-types.js";
|
|
18
2
|
/** A hard criterion excluding one known canonical value while retaining only variants with known values. */
|
|
19
|
-
export declare
|
|
3
|
+
export declare const excludeSpec: (attributeId: string, excludesTerm: string) => SpecExclusionFilter;
|
|
20
4
|
/**
|
|
21
5
|
* A term-equality criterion for `search({ filters })` — the attribute must
|
|
22
6
|
* carry this canonical value.
|
|
@@ -25,7 +9,7 @@ export declare function excludeSpec(attributeId: string, excludesTerm: string):
|
|
|
25
9
|
* // "same fire class as the seed"
|
|
26
10
|
* client.search({ similarTo: { variantId }, filters: [specTerm("fire_classifications", "astm_e84_class_a")] });
|
|
27
11
|
*/
|
|
28
|
-
export declare
|
|
12
|
+
export declare const specTerm: (attributeId: string, term: string) => SpecTermFilter;
|
|
29
13
|
/**
|
|
30
14
|
* A presence criterion for `search({ filters })` — the attribute must have ANY
|
|
31
15
|
* accepted value.
|
|
@@ -34,7 +18,7 @@ export declare function specTerm(attributeId: string, term: string): SpecTermFil
|
|
|
34
18
|
* // "…and it must have an EPD on record"
|
|
35
19
|
* client.search({ similarTo: { variantId }, filters: [hasSpec("environmental_product_declaration")] });
|
|
36
20
|
*/
|
|
37
|
-
export declare
|
|
21
|
+
export declare const hasSpec: (attributeId: string) => SpecPresenceFilter;
|
|
38
22
|
/**
|
|
39
23
|
* A certification-programme criterion for `search({ filters })`, pinned to the
|
|
40
24
|
* virtual attribute so a programme matches wherever it landed at ingest
|
|
@@ -49,7 +33,7 @@ export declare function hasSpec(attributeId: string): SpecPresenceFilter;
|
|
|
49
33
|
* // "find a GREENGUARD-certified alternative"
|
|
50
34
|
* client.search({ similarTo: { variantId: seed }, filters: [certificationProgramme("GREENGUARD Gold")] });
|
|
51
35
|
*/
|
|
52
|
-
export declare
|
|
36
|
+
export declare const certificationProgramme: (programme: string) => CertificationProgrammeFilter;
|
|
53
37
|
/**
|
|
54
38
|
* An `attribute:value` criterion string for `bySpec`. The `bySpec` route splits
|
|
55
39
|
* on the FIRST `:`, so a value may itself contain colons; the attribute id may
|
|
@@ -58,7 +42,7 @@ export declare function certificationProgramme(programme: string): Certification
|
|
|
58
42
|
* @example
|
|
59
43
|
* client.bySpec([specCriterion("primary_material", "textile"), specCriterion("fire_classifications", "astm_e84_class_a")]);
|
|
60
44
|
*/
|
|
61
|
-
export declare
|
|
45
|
+
export declare const specCriterion: (attributeId: string, value: string) => string;
|
|
62
46
|
/**
|
|
63
47
|
* An `attribute:min..max` RANGE criterion string for `bySpec`, for a
|
|
64
48
|
* NUMBER-valued spec (`light_reflectance_value`, `nrc_rating`,
|
|
@@ -81,10 +65,10 @@ export declare function specCriterion(attributeId: string, value: string): strin
|
|
|
81
65
|
* // a mid-tone band
|
|
82
66
|
* specRangeCriterion("light_reflectance_value", { min: 30, max: 45 }); // "light_reflectance_value:30..45"
|
|
83
67
|
*/
|
|
84
|
-
export declare
|
|
68
|
+
export declare const specRangeCriterion: (attributeId: string, bounds: {
|
|
85
69
|
min?: number;
|
|
86
70
|
max?: number;
|
|
87
|
-
})
|
|
71
|
+
}) => string;
|
|
88
72
|
/**
|
|
89
73
|
* An `attribute:value` criterion string for `bySpec` that filters by
|
|
90
74
|
* certification PROGRAMME across all four certification buckets.
|
|
@@ -97,4 +81,4 @@ export declare function specRangeCriterion(attributeId: string, bounds: {
|
|
|
97
81
|
* // every brand's GREENGUARD-Gold materials
|
|
98
82
|
* client.bySpec([certificationProgrammeCriterion("GREENGUARD Gold")]);
|
|
99
83
|
*/
|
|
100
|
-
export declare
|
|
84
|
+
export declare const certificationProgrammeCriterion: (programme: string) => string;
|
package/specifications.js
CHANGED
|
@@ -14,11 +14,9 @@
|
|
|
14
14
|
* hand-format the `attribute:value` string the `bySpec` route splits on its
|
|
15
15
|
* first `:`.
|
|
16
16
|
*/
|
|
17
|
-
import { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID
|
|
17
|
+
import { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID } from "./spec-types.js";
|
|
18
18
|
/** A hard criterion excluding one known canonical value while retaining only variants with known values. */
|
|
19
|
-
export
|
|
20
|
-
return { attributeId, excludesTerm };
|
|
21
|
-
}
|
|
19
|
+
export const excludeSpec = (attributeId, excludesTerm) => ({ attributeId, excludesTerm });
|
|
22
20
|
/**
|
|
23
21
|
* A term-equality criterion for `search({ filters })` — the attribute must
|
|
24
22
|
* carry this canonical value.
|
|
@@ -27,9 +25,7 @@ export function excludeSpec(attributeId, excludesTerm) {
|
|
|
27
25
|
* // "same fire class as the seed"
|
|
28
26
|
* client.search({ similarTo: { variantId }, filters: [specTerm("fire_classifications", "astm_e84_class_a")] });
|
|
29
27
|
*/
|
|
30
|
-
export
|
|
31
|
-
return { attributeId, term };
|
|
32
|
-
}
|
|
28
|
+
export const specTerm = (attributeId, term) => ({ attributeId, term });
|
|
33
29
|
/**
|
|
34
30
|
* A presence criterion for `search({ filters })` — the attribute must have ANY
|
|
35
31
|
* accepted value.
|
|
@@ -38,9 +34,10 @@ export function specTerm(attributeId, term) {
|
|
|
38
34
|
* // "…and it must have an EPD on record"
|
|
39
35
|
* client.search({ similarTo: { variantId }, filters: [hasSpec("environmental_product_declaration")] });
|
|
40
36
|
*/
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
export const hasSpec = (attributeId) => ({
|
|
38
|
+
attributeId,
|
|
39
|
+
hasValue: true,
|
|
40
|
+
});
|
|
44
41
|
/**
|
|
45
42
|
* A certification-programme criterion for `search({ filters })`, pinned to the
|
|
46
43
|
* virtual attribute so a programme matches wherever it landed at ingest
|
|
@@ -55,9 +52,10 @@ export function hasSpec(attributeId) {
|
|
|
55
52
|
* // "find a GREENGUARD-certified alternative"
|
|
56
53
|
* client.search({ similarTo: { variantId: seed }, filters: [certificationProgramme("GREENGUARD Gold")] });
|
|
57
54
|
*/
|
|
58
|
-
export
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
export const certificationProgramme = (programme) => ({
|
|
56
|
+
attributeId: CERTIFICATION_PROGRAMME_ATTRIBUTE_ID,
|
|
57
|
+
term: programme,
|
|
58
|
+
});
|
|
61
59
|
/**
|
|
62
60
|
* An `attribute:value` criterion string for `bySpec`. The `bySpec` route splits
|
|
63
61
|
* on the FIRST `:`, so a value may itself contain colons; the attribute id may
|
|
@@ -66,9 +64,7 @@ export function certificationProgramme(programme) {
|
|
|
66
64
|
* @example
|
|
67
65
|
* client.bySpec([specCriterion("primary_material", "textile"), specCriterion("fire_classifications", "astm_e84_class_a")]);
|
|
68
66
|
*/
|
|
69
|
-
export
|
|
70
|
-
return `${attributeId}:${value}`;
|
|
71
|
-
}
|
|
67
|
+
export const specCriterion = (attributeId, value) => `${attributeId}:${value}`;
|
|
72
68
|
/**
|
|
73
69
|
* An `attribute:min..max` RANGE criterion string for `bySpec`, for a
|
|
74
70
|
* NUMBER-valued spec (`light_reflectance_value`, `nrc_rating`,
|
|
@@ -91,7 +87,7 @@ export function specCriterion(attributeId, value) {
|
|
|
91
87
|
* // a mid-tone band
|
|
92
88
|
* specRangeCriterion("light_reflectance_value", { min: 30, max: 45 }); // "light_reflectance_value:30..45"
|
|
93
89
|
*/
|
|
94
|
-
export
|
|
90
|
+
export const specRangeCriterion = (attributeId, bounds) => {
|
|
95
91
|
const { min, max } = bounds;
|
|
96
92
|
if (min === undefined && max === undefined) {
|
|
97
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.`);
|
|
@@ -100,7 +96,7 @@ export function specRangeCriterion(attributeId, bounds) {
|
|
|
100
96
|
throw new Error(`specRangeCriterion("${attributeId}") is inverted (min ${min} > max ${max}) and can only ever match nothing.`);
|
|
101
97
|
}
|
|
102
98
|
return `${attributeId}:${min ?? ""}..${max ?? ""}`;
|
|
103
|
-
}
|
|
99
|
+
};
|
|
104
100
|
/**
|
|
105
101
|
* An `attribute:value` criterion string for `bySpec` that filters by
|
|
106
102
|
* certification PROGRAMME across all four certification buckets.
|
|
@@ -113,6 +109,4 @@ export function specRangeCriterion(attributeId, bounds) {
|
|
|
113
109
|
* // every brand's GREENGUARD-Gold materials
|
|
114
110
|
* client.bySpec([certificationProgrammeCriterion("GREENGUARD Gold")]);
|
|
115
111
|
*/
|
|
116
|
-
export
|
|
117
|
-
return specCriterion(CERTIFICATION_PROGRAMME_ATTRIBUTE_ID, programme);
|
|
118
|
-
}
|
|
112
|
+
export const certificationProgrammeCriterion = (programme) => specCriterion(CERTIFICATION_PROGRAMME_ATTRIBUTE_ID, programme);
|
package/sse.d.ts
CHANGED
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
* chunks are held in an internal buffer until their terminating blank line.
|
|
12
12
|
*/
|
|
13
13
|
/** One dispatched SSE frame. `data` is the joined data lines (no trailing newline). */
|
|
14
|
-
export
|
|
14
|
+
export type SseFrame = {
|
|
15
15
|
id?: string;
|
|
16
16
|
event?: string;
|
|
17
17
|
data: string;
|
|
18
|
-
}
|
|
18
|
+
};
|
|
19
19
|
/**
|
|
20
20
|
* A push-based SSE line assembler. Feed it decoded text with {@link push}; it
|
|
21
21
|
* returns any frames completed by that text. Call {@link flush} once the stream
|
package/sse.js
CHANGED
|
@@ -26,13 +26,15 @@ export class SseDecoder {
|
|
|
26
26
|
const frames = [];
|
|
27
27
|
for (;;) {
|
|
28
28
|
const lineEnd = this.#nextLineEnd();
|
|
29
|
-
if (lineEnd === null)
|
|
29
|
+
if (lineEnd === null) {
|
|
30
30
|
break;
|
|
31
|
+
}
|
|
31
32
|
const line = this.#buffer.slice(0, lineEnd.index);
|
|
32
33
|
this.#buffer = this.#buffer.slice(lineEnd.nextIndex);
|
|
33
34
|
const frame = this.#consumeLine(line);
|
|
34
|
-
if (frame)
|
|
35
|
+
if (frame) {
|
|
35
36
|
frames.push(frame);
|
|
37
|
+
}
|
|
36
38
|
}
|
|
37
39
|
return frames;
|
|
38
40
|
}
|
|
@@ -45,8 +47,9 @@ export class SseDecoder {
|
|
|
45
47
|
: this.#buffer;
|
|
46
48
|
this.#buffer = "";
|
|
47
49
|
const frame = this.#consumeLine(line);
|
|
48
|
-
if (frame)
|
|
50
|
+
if (frame) {
|
|
49
51
|
return [frame];
|
|
52
|
+
}
|
|
50
53
|
}
|
|
51
54
|
const frame = this.#dispatch();
|
|
52
55
|
return frame ? [frame] : [];
|
|
@@ -55,11 +58,13 @@ export class SseDecoder {
|
|
|
55
58
|
#nextLineEnd() {
|
|
56
59
|
for (let i = 0; i < this.#buffer.length; i += 1) {
|
|
57
60
|
const char = this.#buffer[i];
|
|
58
|
-
if (char === "\n")
|
|
61
|
+
if (char === "\n") {
|
|
59
62
|
return { index: i, nextIndex: i + 1 };
|
|
63
|
+
}
|
|
60
64
|
if (char === "\r") {
|
|
61
|
-
if (i === this.#buffer.length - 1)
|
|
65
|
+
if (i === this.#buffer.length - 1) {
|
|
62
66
|
return null;
|
|
67
|
+
}
|
|
63
68
|
return this.#buffer[i + 1] === "\n"
|
|
64
69
|
? { index: i, nextIndex: i + 2 }
|
|
65
70
|
: { index: i, nextIndex: i + 1 };
|
|
@@ -68,15 +73,18 @@ export class SseDecoder {
|
|
|
68
73
|
return null;
|
|
69
74
|
}
|
|
70
75
|
#consumeLine(line) {
|
|
71
|
-
if (line.length === 0)
|
|
76
|
+
if (line.length === 0) {
|
|
72
77
|
return this.#dispatch();
|
|
73
|
-
|
|
78
|
+
}
|
|
79
|
+
if (line.startsWith(":")) {
|
|
74
80
|
return undefined;
|
|
81
|
+
}
|
|
75
82
|
const colon = line.indexOf(":");
|
|
76
83
|
const field = colon === -1 ? line : line.slice(0, colon);
|
|
77
84
|
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
78
|
-
if (value.startsWith(" "))
|
|
85
|
+
if (value.startsWith(" ")) {
|
|
79
86
|
value = value.slice(1);
|
|
87
|
+
}
|
|
80
88
|
if (field === "data") {
|
|
81
89
|
this.#dataLines.push(value);
|
|
82
90
|
this.#hasFields = true;
|
|
@@ -92,13 +100,16 @@ export class SseDecoder {
|
|
|
92
100
|
return undefined;
|
|
93
101
|
}
|
|
94
102
|
#dispatch() {
|
|
95
|
-
if (!this.#hasFields)
|
|
103
|
+
if (!this.#hasFields) {
|
|
96
104
|
return undefined;
|
|
105
|
+
}
|
|
97
106
|
const frame = { data: this.#dataLines.join("\n") };
|
|
98
|
-
if (this.#event !== undefined)
|
|
107
|
+
if (this.#event !== undefined) {
|
|
99
108
|
frame.event = this.#event;
|
|
100
|
-
|
|
109
|
+
}
|
|
110
|
+
if (this.#id !== undefined) {
|
|
101
111
|
frame.id = this.#id;
|
|
112
|
+
}
|
|
102
113
|
this.#dataLines = [];
|
|
103
114
|
this.#event = undefined;
|
|
104
115
|
this.#id = undefined;
|
|
@@ -117,28 +128,33 @@ export async function* readSseFrames(body, signal) {
|
|
|
117
128
|
const sse = new SseDecoder();
|
|
118
129
|
try {
|
|
119
130
|
for (;;) {
|
|
120
|
-
if (signal?.aborted)
|
|
131
|
+
if (signal?.aborted) {
|
|
121
132
|
return;
|
|
133
|
+
}
|
|
122
134
|
let result;
|
|
123
135
|
try {
|
|
124
136
|
result = await reader.read();
|
|
125
137
|
}
|
|
126
138
|
catch (error) {
|
|
127
|
-
if (signal?.aborted)
|
|
139
|
+
if (signal?.aborted) {
|
|
128
140
|
return;
|
|
141
|
+
}
|
|
129
142
|
throw error;
|
|
130
143
|
}
|
|
131
144
|
if (result.done) {
|
|
132
|
-
for (const frame of sse.push(decoder.decode()))
|
|
145
|
+
for (const frame of sse.push(decoder.decode())) {
|
|
133
146
|
yield frame;
|
|
134
|
-
|
|
147
|
+
}
|
|
148
|
+
for (const frame of sse.flush()) {
|
|
135
149
|
yield frame;
|
|
150
|
+
}
|
|
136
151
|
return;
|
|
137
152
|
}
|
|
138
153
|
const text = decoder.decode(result.value, { stream: true });
|
|
139
154
|
for (const frame of sse.push(text)) {
|
|
140
|
-
if (signal?.aborted)
|
|
155
|
+
if (signal?.aborted) {
|
|
141
156
|
return;
|
|
157
|
+
}
|
|
142
158
|
yield frame;
|
|
143
159
|
}
|
|
144
160
|
}
|
package/substitute-types.d.ts
CHANGED
|
@@ -2,40 +2,40 @@ import type { BySpecVerdict } from "./by-spec-types.js";
|
|
|
2
2
|
import type { VariantId } from "./identifiers.js";
|
|
3
3
|
import type { MaterialSuccessor } from "./material-types.js";
|
|
4
4
|
export type SubstituteSignatureFamily = "textile" | "tile" | "coating" | "lighting_electrical" | "acoustic" | "surface" | "furniture" | "sanitaryware" | "fittings" | "architectural_hardware";
|
|
5
|
-
export
|
|
5
|
+
export type SubstituteSignatureCriterion = {
|
|
6
6
|
role: "primary_material" | "performance";
|
|
7
7
|
attributeId: string;
|
|
8
8
|
term: string;
|
|
9
|
-
}
|
|
10
|
-
export
|
|
9
|
+
};
|
|
10
|
+
export type AvailableSubstituteSignature = {
|
|
11
11
|
family: SubstituteSignatureFamily;
|
|
12
12
|
form: string;
|
|
13
13
|
criteria: [SubstituteSignatureCriterion, SubstituteSignatureCriterion];
|
|
14
|
-
}
|
|
15
|
-
export
|
|
14
|
+
};
|
|
15
|
+
export type UnsupportedSubstituteSignature = {
|
|
16
16
|
family: SubstituteSignatureFamily;
|
|
17
17
|
form: string;
|
|
18
18
|
reason: string;
|
|
19
|
-
}
|
|
20
|
-
export
|
|
19
|
+
};
|
|
20
|
+
export type InsufficientSubstituteSignature = {
|
|
21
21
|
family: SubstituteSignatureFamily;
|
|
22
22
|
form: string;
|
|
23
23
|
missingRoles: ("primary_material" | "performance")[];
|
|
24
|
-
}
|
|
25
|
-
export
|
|
24
|
+
};
|
|
25
|
+
export type FindSubstitutesOptions = {
|
|
26
26
|
excludeBrands?: string[];
|
|
27
27
|
diversityBoost?: number;
|
|
28
28
|
limit?: number;
|
|
29
29
|
cursor?: string;
|
|
30
30
|
includeSamplingArtifacts?: boolean;
|
|
31
31
|
includeUnavailable?: boolean;
|
|
32
|
-
}
|
|
33
|
-
export
|
|
32
|
+
};
|
|
33
|
+
export type SubstituteMatchedCriterion = {
|
|
34
34
|
attributeId: string;
|
|
35
35
|
confidence: number | null;
|
|
36
36
|
sourceBasis: string | null;
|
|
37
|
-
}
|
|
38
|
-
export
|
|
37
|
+
};
|
|
38
|
+
export type SubstituteResultItem = {
|
|
39
39
|
variantId: VariantId;
|
|
40
40
|
brandSlug: string;
|
|
41
41
|
brand: string;
|
|
@@ -50,8 +50,8 @@ export interface SubstituteResultItem {
|
|
|
50
50
|
diversityAdjustment: number;
|
|
51
51
|
score: number;
|
|
52
52
|
verdict: BySpecVerdict;
|
|
53
|
-
}
|
|
54
|
-
export
|
|
53
|
+
};
|
|
54
|
+
export type AvailableSubstitutesResponse = {
|
|
55
55
|
status: "available";
|
|
56
56
|
signatureUsed: AvailableSubstituteSignature;
|
|
57
57
|
items: SubstituteResultItem[];
|
|
@@ -69,7 +69,7 @@ export interface AvailableSubstitutesResponse {
|
|
|
69
69
|
poolTruncated: boolean;
|
|
70
70
|
supersededBy: MaterialSuccessor | null;
|
|
71
71
|
nextCursor?: string;
|
|
72
|
-
}
|
|
72
|
+
};
|
|
73
73
|
/**
|
|
74
74
|
* MG-1686. The search read a bounded window of the matches and nothing in it
|
|
75
75
|
* reached the page, while more matches existed past the window. It is not an
|
|
@@ -77,7 +77,7 @@ export interface AvailableSubstitutesResponse {
|
|
|
77
77
|
* empty `items`. `reason` names what happened and the next move; follow
|
|
78
78
|
* `nextCursor` when it is present.
|
|
79
79
|
*/
|
|
80
|
-
export
|
|
80
|
+
export type InconclusiveSubstitutesResponse = {
|
|
81
81
|
status: "inconclusive";
|
|
82
82
|
signatureUsed: AvailableSubstituteSignature;
|
|
83
83
|
items: [];
|
|
@@ -86,17 +86,17 @@ export interface InconclusiveSubstitutesResponse {
|
|
|
86
86
|
reason: string;
|
|
87
87
|
supersededBy: MaterialSuccessor | null;
|
|
88
88
|
nextCursor?: string;
|
|
89
|
-
}
|
|
90
|
-
export
|
|
89
|
+
};
|
|
90
|
+
export type UnsupportedSubstitutesResponse = {
|
|
91
91
|
status: "unsupported";
|
|
92
92
|
signature: UnsupportedSubstituteSignature;
|
|
93
93
|
items: [];
|
|
94
94
|
supersededBy: MaterialSuccessor | null;
|
|
95
|
-
}
|
|
96
|
-
export
|
|
95
|
+
};
|
|
96
|
+
export type InsufficientSubstitutesResponse = {
|
|
97
97
|
status: "insufficient_signature";
|
|
98
98
|
signature: InsufficientSubstituteSignature;
|
|
99
99
|
items: [];
|
|
100
100
|
supersededBy: MaterialSuccessor | null;
|
|
101
|
-
}
|
|
101
|
+
};
|
|
102
102
|
export type FindSubstitutesResponse = AvailableSubstitutesResponse | InconclusiveSubstitutesResponse | UnsupportedSubstitutesResponse | InsufficientSubstitutesResponse;
|