@instruments/materialgraph 0.9.0 → 0.10.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/brands-client.d.ts +48 -0
- package/brands-client.js +45 -0
- package/by-colour-types.d.ts +49 -5
- package/client.d.ts +36 -49
- package/client.js +57 -54
- package/errors.js +24 -8
- package/fan-deck-types.d.ts +39 -0
- package/fan-deck-types.js +9 -0
- package/http-transport.js +24 -5
- package/index.d.ts +3 -2
- package/index.js +2 -2
- package/match-stream-types.d.ts +36 -6
- package/match-stream-types.js +32 -5
- package/match-stream.js +14 -4
- package/material-types.d.ts +1 -1
- package/operations.d.ts +1 -1
- package/operations.js +162 -26
- package/package.json +1 -1
- package/resolve-client.d.ts +22 -3
- package/resolve-client.js +31 -4
- package/resolve-types.d.ts +70 -1
- package/run-events.d.ts +1 -1
- package/run-events.js +4 -1
- package/runs.d.ts +1 -1
- package/runs.js +8 -4
- package/search-refinement-types.d.ts +11 -3
- package/search-result-validation.js +21 -4
- package/source-material-validation.js +3 -1
- package/spec-facet-types.d.ts +53 -0
- package/spec-facet-types.js +7 -0
- package/specifications.d.ts +26 -0
- package/specifications.js +32 -0
- package/sse.js +3 -1
- package/suggest-types.js +3 -1
- package/types.d.ts +42 -32
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The BRAND lens: the catalogue collection, one brand's profile, and one
|
|
3
|
+
* brand's whole colour range, as thin wrappers over the shared
|
|
4
|
+
* {@link HttpTransport}. `MaterialGraphClient.brands` / `.brand` /
|
|
5
|
+
* `.brandColourDeck` delegate here; the contract lives in this file.
|
|
6
|
+
*
|
|
7
|
+
* A brand is a COMPANY, not a catalogue. Every count these reads return is
|
|
8
|
+
* coverage — what MaterialGraph has ingested — and never a claim about how
|
|
9
|
+
* many products the maker sells.
|
|
10
|
+
*/
|
|
11
|
+
import type { BrandCatalogListPage, BrandsOptions } from "./brand-types.js";
|
|
12
|
+
import type { BrandFanDeck } from "./fan-deck-types.js";
|
|
13
|
+
import { HttpTransport } from "./http-transport.js";
|
|
14
|
+
import type { BrandOptions, BrandProfileResponse } from "./types.js";
|
|
15
|
+
/**
|
|
16
|
+
* THE QUESTION: "whose catalogs are in here, and how big is each?" The SDK's
|
|
17
|
+
* one OFFSET-paginated surface: read `meta.total` for the collection size and
|
|
18
|
+
* `meta.hasMore` to keep walking. Instead use {@link brandProfile} for one
|
|
19
|
+
* brand's profile and trade signals.
|
|
20
|
+
*/
|
|
21
|
+
export declare function listBrands(transport: HttpTransport, options?: BrandsOptions, signal?: AbortSignal): Promise<BrandCatalogListPage>;
|
|
22
|
+
/**
|
|
23
|
+
* THE QUESTION: "who is this brand, and what do they claim?" — profile plus the
|
|
24
|
+
* trade/spec signals ON RECORD. `trade` is keyed by attribute id and an ABSENT
|
|
25
|
+
* key means no value is held — never infer `false`. Instead use `search()` to
|
|
26
|
+
* find their materials; this describes the company, not the catalog.
|
|
27
|
+
*/
|
|
28
|
+
export declare function brandProfile(transport: HttpTransport, brandSlug: string, options?: BrandOptions, signal?: AbortSignal): Promise<BrandProfileResponse>;
|
|
29
|
+
/**
|
|
30
|
+
* THE QUESTION: "show me this maker's whole range, the way a fan deck reads."
|
|
31
|
+
* Colours grouped by family — creams together, then greys, then browns — and
|
|
32
|
+
* ordered light to dark within each. No stored scalar produces that order: a
|
|
33
|
+
* lightness sort interleaves the whites and the creams, and a reader loses the
|
|
34
|
+
* sense that they are looking at two different families.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately unpaginated, so there is no cursor to walk and no all-brands
|
|
37
|
+
* form: family order is only correct over a COMPLETE range, because the greys
|
|
38
|
+
* cannot be placed against the browns while holding one page of each. A range
|
|
39
|
+
* past the server ceiling reports `truncatedAt` rather than being quietly cut,
|
|
40
|
+
* and an absent `truncatedAt` means the deck is whole.
|
|
41
|
+
*
|
|
42
|
+
* Every swatch carries the maker's PUBLISHED hex — this read never returns a
|
|
43
|
+
* colour derived from imagery. For "what is near this colour, across makers"
|
|
44
|
+
* use `byColour()` instead.
|
|
45
|
+
*
|
|
46
|
+
* @param brandSlug The brand's slug, as `listBrands()` reports it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function brandColourDeck(transport: HttpTransport, brandSlug: string, signal?: AbortSignal): Promise<BrandFanDeck>;
|
package/brands-client.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { HttpTransport } from "./http-transport.js";
|
|
2
|
+
import { resolveOperation } from "./operations.js";
|
|
3
|
+
/**
|
|
4
|
+
* THE QUESTION: "whose catalogs are in here, and how big is each?" The SDK's
|
|
5
|
+
* one OFFSET-paginated surface: read `meta.total` for the collection size and
|
|
6
|
+
* `meta.hasMore` to keep walking. Instead use {@link brandProfile} for one
|
|
7
|
+
* brand's profile and trade signals.
|
|
8
|
+
*/
|
|
9
|
+
export function listBrands(transport, options = {}, signal) {
|
|
10
|
+
return transport.requestJson(resolveOperation("list_brands"), {
|
|
11
|
+
query: { q: options.q, limit: options.limit, offset: options.offset },
|
|
12
|
+
signal,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* THE QUESTION: "who is this brand, and what do they claim?" — profile plus the
|
|
17
|
+
* trade/spec signals ON RECORD. `trade` is keyed by attribute id and an ABSENT
|
|
18
|
+
* key means no value is held — never infer `false`. Instead use `search()` to
|
|
19
|
+
* find their materials; this describes the company, not the catalog.
|
|
20
|
+
*/
|
|
21
|
+
export function brandProfile(transport, brandSlug, options, signal) {
|
|
22
|
+
return transport.requestJson(resolveOperation("get_brand_profile"), { query: { q: options?.q }, pathParams: { brandSlug }, signal });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* THE QUESTION: "show me this maker's whole range, the way a fan deck reads."
|
|
26
|
+
* Colours grouped by family — creams together, then greys, then browns — and
|
|
27
|
+
* ordered light to dark within each. No stored scalar produces that order: a
|
|
28
|
+
* lightness sort interleaves the whites and the creams, and a reader loses the
|
|
29
|
+
* sense that they are looking at two different families.
|
|
30
|
+
*
|
|
31
|
+
* Deliberately unpaginated, so there is no cursor to walk and no all-brands
|
|
32
|
+
* form: family order is only correct over a COMPLETE range, because the greys
|
|
33
|
+
* cannot be placed against the browns while holding one page of each. A range
|
|
34
|
+
* past the server ceiling reports `truncatedAt` rather than being quietly cut,
|
|
35
|
+
* and an absent `truncatedAt` means the deck is whole.
|
|
36
|
+
*
|
|
37
|
+
* Every swatch carries the maker's PUBLISHED hex — this read never returns a
|
|
38
|
+
* colour derived from imagery. For "what is near this colour, across makers"
|
|
39
|
+
* use `byColour()` instead.
|
|
40
|
+
*
|
|
41
|
+
* @param brandSlug The brand's slug, as `listBrands()` reports it.
|
|
42
|
+
*/
|
|
43
|
+
export function brandColourDeck(transport, brandSlug, signal) {
|
|
44
|
+
return transport.requestJson(resolveOperation("brand_colour_deck"), { pathParams: { brandSlug }, signal });
|
|
45
|
+
}
|
package/by-colour-types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* enums are widened so a new server value still type-checks.
|
|
8
8
|
*/
|
|
9
9
|
import type { VariantId } from "./identifiers.js";
|
|
10
|
-
import type { OklabTriple, PaletteSwatch } from "./types.js";
|
|
10
|
+
import type { OklabTriple, OpenEnum, PaletteSwatch } from "./types.js";
|
|
11
11
|
/** Coarse group + fine type of a colour match's primary product type. */
|
|
12
12
|
export interface MatchCategory {
|
|
13
13
|
group: string | null;
|
|
@@ -16,10 +16,10 @@ export interface MatchCategory {
|
|
|
16
16
|
/**
|
|
17
17
|
* One colour-palette match.
|
|
18
18
|
*
|
|
19
|
-
* `
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* `distance` and `closeness` are OKLab geometry over colour EXTRACTED FROM
|
|
20
|
+
* PRODUCT IMAGERY. Report the ORDER as visual likeness ("reads close to this
|
|
21
|
+
* colour"), never as a stated colour specification. `hex` itself may be the
|
|
22
|
+
* manufacturer's published colour — read `sourceBasis` before repeating it.
|
|
23
23
|
*/
|
|
24
24
|
export interface ByColourMatch {
|
|
25
25
|
/** Opaque — round-trip verbatim. */
|
|
@@ -34,6 +34,31 @@ export interface ByColourMatch {
|
|
|
34
34
|
closeness: number;
|
|
35
35
|
category: MatchCategory | null;
|
|
36
36
|
oklab: OklabTriple | null;
|
|
37
|
+
/**
|
|
38
|
+
* Where `hex` came from — PER MATCH, never per response. `"declared"`: the
|
|
39
|
+
* manufacturer published it (a paint's own colour system), so it may be
|
|
40
|
+
* repeated as their stated colour. `"derived"`: extracted from product
|
|
41
|
+
* imagery — visual likeness only. Widened to `string` so a new server value
|
|
42
|
+
* still type-checks. ABSENT when `hex` is null, or on a response captured
|
|
43
|
+
* before the field existed.
|
|
44
|
+
*/
|
|
45
|
+
sourceBasis?: OpenEnum<"declared" | "derived">;
|
|
46
|
+
/**
|
|
47
|
+
* OKLab distance from the QUERY colour to this match's `hex` — two single
|
|
48
|
+
* colours. NOT `distance`, which is the imagery ranking's geometry over whole
|
|
49
|
+
* PALETTES. The two disagree routinely: a match ranked 4th by imagery is often
|
|
50
|
+
* the closest by hex. ABSENT when the match has no hex, or when the query was
|
|
51
|
+
* a palette rather than one colour.
|
|
52
|
+
*/
|
|
53
|
+
colourDistance?: number;
|
|
54
|
+
/**
|
|
55
|
+
* `colourDistance` as a named band, so nobody thresholds a float. Not a
|
|
56
|
+
* confidence and not a percentage — `very-close` still covers differences a
|
|
57
|
+
* client would reject in a paint order, so read `sourceBasis` before quoting
|
|
58
|
+
* any of it as a specification. Widened to `string` so a new server band still
|
|
59
|
+
* type-checks. ABSENT exactly when `colourDistance` is.
|
|
60
|
+
*/
|
|
61
|
+
proximity?: OpenEnum<"indistinguishable" | "very-close" | "close" | "related" | "unrelated">;
|
|
37
62
|
/**
|
|
38
63
|
* The match's own image palette — the FULL stored palette, uncapped and in
|
|
39
64
|
* stored order, each swatch with `oklab` (see {@link ByColourSwatch}), so a
|
|
@@ -86,6 +111,11 @@ export interface ByColourResponse {
|
|
|
86
111
|
query: {
|
|
87
112
|
colours: string[];
|
|
88
113
|
categories?: string[];
|
|
114
|
+
/**
|
|
115
|
+
* Echoed brand filter as resolved SLUGS — what the names you passed
|
|
116
|
+
* resolved to. Present only when a brand scope applied.
|
|
117
|
+
*/
|
|
118
|
+
brands?: string[];
|
|
89
119
|
/** Echoed `family` filter, normalised. Present only when one was applied. */
|
|
90
120
|
family?: string;
|
|
91
121
|
oklab?: OklabTriple;
|
|
@@ -118,6 +148,20 @@ export interface ByColourParams {
|
|
|
118
148
|
b?: number;
|
|
119
149
|
/** HARD category filter — display names or taxonomy ids (repeatable). */
|
|
120
150
|
category?: string | string[];
|
|
151
|
+
/**
|
|
152
|
+
* HARD brand filter (repeatable) — a brand SLUG (`sherwin-williams`) or its
|
|
153
|
+
* display name (`Sherwin-Williams`), case-insensitive.
|
|
154
|
+
*
|
|
155
|
+
* This is how the CROSS-BRAND question is asked: pass one brand's colour as
|
|
156
|
+
* `hex` and scope to another to get that brand's nearest colours to it. It is
|
|
157
|
+
* applied inside retrieval, so `limit` still fills from the scoped brands —
|
|
158
|
+
* filtering a returned page instead comes back short, because a
|
|
159
|
+
* nearest-neighbour scan does not know to look further.
|
|
160
|
+
*
|
|
161
|
+
* An unknown brand is a 400 naming it, never an empty page. Omitting it (or
|
|
162
|
+
* passing an empty list) searches every brand.
|
|
163
|
+
*/
|
|
164
|
+
brand?: string | string[];
|
|
121
165
|
/**
|
|
122
166
|
* HARD colour-family filter — ONE colorscope taxonomy family id, from the
|
|
123
167
|
* closed 15-value vocabulary colorscope itself publishes (`white`, `cream`,
|
package/client.d.ts
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
import { type RateLimit } from "./errors.js";
|
|
2
1
|
import type { MaterialGraphClientOptions } from "./client-config.js";
|
|
2
|
+
import { type RateLimit } from "./errors.js";
|
|
3
3
|
export * from "./client-config.js";
|
|
4
4
|
import { type BrowseNamespace } from "./client-browse.js";
|
|
5
5
|
export type { BrowseNamespace } from "./client-browse.js";
|
|
6
|
-
import type { CapabilityManifestResponse, CreatePublicRunRequest, CreatePublicRunResponse, DrainRunEventsOptions, ListRunsOptions, PublicRunListResponse, PublicRunResponse, ResumeRunOptions, RunEvent, SourceMaterialBatchDetail, SourceMaterialBatchReceipt, StreamRunEventsOptions, UploadSourceMaterialInput } from "./run-types.js";
|
|
7
|
-
import type { FinishScheduleResponse, GetRunScheduleOptions } from "./schedule-types.js";
|
|
8
|
-
import type { MatchStreamEvent } from "./match-stream-types.js";
|
|
9
|
-
import type { AppendProjectEventRequest, CreateProjectRequest, ProjectEventReceipt, ProjectListResponse, ProjectState } from "./project-types.js";
|
|
10
|
-
import type { VarySchemesRequest, VarySchemesResponse } from "./composition-types.js";
|
|
11
|
-
import { CodesClient } from "./codes-client.js";
|
|
12
|
-
import { ColoursClient } from "./colours-client.js";
|
|
13
|
-
import type { CodeAdvisoryInput, CodeAdvisoryResponse } from "./codes-types.js";
|
|
14
6
|
import type { AssociatedOptions } from "./association-types.js";
|
|
15
7
|
import type { BrandCatalogListPage, BrandsOptions } from "./brand-types.js";
|
|
8
|
+
import type { ByColourParams, ByColourResponse } from "./by-colour-types.js";
|
|
9
|
+
import { CodesClient } from "./codes-client.js";
|
|
10
|
+
import type { CodeAdvisoryInput, CodeAdvisoryResponse } from "./codes-types.js";
|
|
11
|
+
import { ColoursClient } from "./colours-client.js";
|
|
12
|
+
import type { VarySchemesRequest, VarySchemesResponse } from "./composition-types.js";
|
|
13
|
+
import type { BatchId, ProjectId, RunId, VariantId } from "./identifiers.js";
|
|
14
|
+
import type { MatchStreamEvent } from "./match-stream-types.js";
|
|
15
|
+
import type { MatchInput, MatchResponse } from "./match-types.js";
|
|
16
16
|
import type { MaterialAssetProjection } from "./material-asset-types.js";
|
|
17
17
|
import type { MaterialBlockProjection } from "./material-block-types.js";
|
|
18
|
+
import type { AppendProjectEventRequest, CreateProjectRequest, ProjectEventReceipt, ProjectListResponse, ProjectState } from "./project-types.js";
|
|
19
|
+
import { resolvePhrasesRequest as resolvePhrases } from "./resolve-client.js";
|
|
20
|
+
import type { CapabilityManifestResponse, CreatePublicRunRequest, CreatePublicRunResponse, DrainRunEventsOptions, ListRunsOptions, PublicRunListResponse, PublicRunResponse, ResumeRunOptions, RunEvent, SourceMaterialBatchDetail, SourceMaterialBatchReceipt, StreamRunEventsOptions, UploadSourceMaterialInput } from "./run-types.js";
|
|
21
|
+
import type { FinishScheduleResponse, GetRunScheduleOptions } from "./schedule-types.js";
|
|
18
22
|
import type { SchemaResource, SchemaResourceResponse } from "./schema-resource-types.js";
|
|
19
|
-
import type { BatchId, ProjectId, RunId, VariantId } from "./identifiers.js";
|
|
20
|
-
import type { ByColourParams, ByColourResponse } from "./by-colour-types.js";
|
|
21
23
|
import type { ApplicableSpecsResponse, BrandOptions, BrandProfileResponse, BySpecOptions, BySpecResponse, ColourFacetsResponse, CoordinateInput, CoordinateResponse, DemandBatchInput, DemandBatchResponse, DemandParams, DemandResponse, ExtractPaletteInput, ExtractPaletteResponse, GlossaryTermResponse, MaterialResponse, MaterialsBatchDetailResponse, MaterialsBatchGetOptions, MaterialsBatchOptions, MaterialsBatchTileResponse, PairingsOptions, PairingsResponse, ResolveAttributeOptions, ResolveAttributeResponse, ResolveAttributeValueOptions, ResolveAttributeValueResponse, ResolveColourResponse, SearchGetParams, SearchInput, SearchResponse, SiblingsResponse, SpecFacetsDistributionResponse, SpecFacetsListResponse, SuggestColourResponse, SuggestOptions, SuggestResponse, TaxonomyResponse, VocabularySearchOptions, WhoamiResponse } from "./types.js";
|
|
22
|
-
import type { MatchInput, MatchResponse } from "./match-types.js";
|
|
23
24
|
/**
|
|
24
25
|
* The MaterialGraph client — every lens on the graph, on one object.
|
|
25
26
|
*
|
|
@@ -172,8 +173,7 @@ export declare class MaterialGraphClient {
|
|
|
172
173
|
* {@link MaterialGraphClient.browse} to walk by lightness/vividness, and
|
|
173
174
|
* {@link MaterialGraphClient.search} when colour is one signal among many.
|
|
174
175
|
* Report matches as visual likeness, never a stated colour specification.
|
|
175
|
-
* `family` gates
|
|
176
|
-
* alone crosses a hue boundary; a gated page can be shorter than `limit`.
|
|
176
|
+
* `family` gates to ONE colorscope family (a gated page can be short); `brand` gates to named brands — one brand's nearest colour to another's.
|
|
177
177
|
*/
|
|
178
178
|
byColour(params: ByColourParams, signal?: AbortSignal): Promise<ByColourResponse>;
|
|
179
179
|
/**
|
|
@@ -326,33 +326,29 @@ export declare class MaterialGraphClient {
|
|
|
326
326
|
resolveAttributeValue(attributeId: string, query: string, options?: ResolveAttributeValueOptions, signal?: AbortSignal): Promise<ResolveAttributeValueResponse>;
|
|
327
327
|
/** "Is this phrase an attribute or a value?" — see {@link searchVocabularyRequest}. */
|
|
328
328
|
searchVocabulary(query: string, options?: VocabularySearchOptions, signal?: AbortSignal): Promise<import("./catalog-discovery-types.js").VocabularySearchResponse>;
|
|
329
|
-
/** "I have words — what do they mean
|
|
330
|
-
resolve(phrases: string[],
|
|
329
|
+
/** "I have words — what do they mean, and what stands behind them?" — see {@link resolvePhrases}. */
|
|
330
|
+
resolve(phrases: string[], scope?: Parameters<typeof resolvePhrases>[2]): Promise<import("./resolve-types.js").ResolveResponse>;
|
|
331
331
|
/**
|
|
332
332
|
* THE QUESTION: "which materials, from ANYONE, are ASTM E84 Class A AND
|
|
333
|
-
* GREENGUARD-certified?" — hard specifications, no ranking, every brand at
|
|
334
|
-
* whatever each called the value in source.
|
|
333
|
+
* GREENGUARD-certified?" — hard specifications, no ranking, every brand at
|
|
334
|
+
* once whatever each called the value in source. The value-anchored,
|
|
335
|
+
* spec-only primitive (GET `/api/v1/search/specifications`).
|
|
335
336
|
*
|
|
336
337
|
* Instead use `search({ filters })` when the spec merely GATES a ranked
|
|
337
338
|
* question, `codes.advisory()` when the real question is what a space demands,
|
|
338
|
-
* and {@link
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* `sourceBasis`). Answers "which materials, from anyone, are ASTM E84 Class A
|
|
352
|
-
* AND GREENGUARD-certified?" Build the strings with `specCriterion` /
|
|
353
|
-
* `certificationProgrammeCriterion` (e.g.
|
|
354
|
-
* `certificationProgrammeCriterion("GREENGUARD Gold")`); discover the queryable
|
|
355
|
-
* vocabulary with {@link specificationFacets} and {@link attributeValues}.
|
|
339
|
+
* and {@link specificationFacets} first if you do not know the canonical
|
|
340
|
+
* tokens. Guessing one no longer returns a silently empty answer: an
|
|
341
|
+
* unresolvable criterion means the query NEVER RAN and the response says so —
|
|
342
|
+
* read {@link BySpecResponse.unresolvedCriteria} before treating an empty
|
|
343
|
+
* `matches` as "nothing qualifies". A PAGE, NOT A CORPUS, unusually so:
|
|
344
|
+
* `totalMatches` is the returned sample's size under `limit`, not a corpus
|
|
345
|
+
* count. Each `spec` is an `attribute:value` criterion built with
|
|
346
|
+
* `specCriterion` / `certificationProgrammeCriterion`; a variant must satisfy
|
|
347
|
+
* ALL of them, and each match carries per-criterion trust (`confidence` +
|
|
348
|
+
* `sourceBasis`). NUMBER-valued specs take BOUNDS, not values — pass
|
|
349
|
+
* `options.ranges` built with `specRangeCriterion("light_reflectance_value",
|
|
350
|
+
* { min: 60 })`; bounds are inclusive, at least one is required, and a
|
|
351
|
+
* non-numeric stored value is excluded rather than read as zero.
|
|
356
352
|
*/
|
|
357
353
|
bySpec(spec: string | string[], options?: BySpecOptions, signal?: AbortSignal): Promise<BySpecResponse>;
|
|
358
354
|
/**
|
|
@@ -479,21 +475,12 @@ export declare class MaterialGraphClient {
|
|
|
479
475
|
* {@link MaterialGraphClient.attributeValues} for the values it takes.
|
|
480
476
|
*/
|
|
481
477
|
glossary(term: string, signal?: AbortSignal): Promise<GlossaryTermResponse>;
|
|
482
|
-
/**
|
|
483
|
-
* THE QUESTION: "whose catalogs are in here, and how big is each?" The SDK's
|
|
484
|
-
* one OFFSET-paginated surface: read `meta.total` for the collection size and
|
|
485
|
-
* `meta.hasMore` to keep walking. Instead use {@link MaterialGraphClient.brand}
|
|
486
|
-
* for one brand's profile and trade signals.
|
|
487
|
-
*/
|
|
478
|
+
/** "Whose catalogs are in here, and how big is each?" — {@link listBrands}. */
|
|
488
479
|
brands(options?: BrandsOptions, signal?: AbortSignal): Promise<BrandCatalogListPage>;
|
|
489
|
-
/**
|
|
490
|
-
* THE QUESTION: "who is this brand, and what do they claim?" — profile plus the
|
|
491
|
-
* trade/spec signals ON RECORD. `trade` is keyed by attribute id and an ABSENT
|
|
492
|
-
* key means no value is held — never infer `false`. Instead use
|
|
493
|
-
* {@link MaterialGraphClient.search} to find their materials; this describes the
|
|
494
|
-
* company, not the catalog.
|
|
495
|
-
*/
|
|
480
|
+
/** "Who is this brand, and what do they claim?" — {@link brandProfile}. */
|
|
496
481
|
brand(brandSlug: string, options?: BrandOptions, signal?: AbortSignal): Promise<BrandProfileResponse>;
|
|
482
|
+
/** "This maker's whole range, the way a fan deck reads" — {@link brandColourDeck}. */
|
|
483
|
+
brandColourDeck(brandSlug: string, signal?: AbortSignal): Promise<import("./fan-deck-types.js").BrandFanDeck>;
|
|
497
484
|
/**
|
|
498
485
|
* Upload one atomic source-material batch (POST `/api/v1/source-material`,
|
|
499
486
|
* multipart). Pass a heterogeneous pile via `{ files }` (any mix of PDF,
|
package/client.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
+
import { DEFAULT_BASE_URL, MAX_BATCH_IDS } from "./client-config.js";
|
|
1
2
|
import { MaterialGraphError } from "./errors.js";
|
|
2
3
|
import { HttpTransport } from "./http-transport.js";
|
|
3
4
|
// Aliased to `operation` — the local name every call site here already used.
|
|
4
|
-
import { resolveOperation as operation } from "./operations.js";
|
|
5
|
+
import { resolveOperation as operation, } from "./operations.js";
|
|
5
6
|
import { cancelRun, capabilities as capabilitiesRun, createRun, drainRunEvents, getRun, getRunSchedule, getSourceMaterialBatch as getSourceMaterialBatchCall, listRuns, resumeRun, streamRunEvents, uploadSourceMaterial, } from "./runs.js";
|
|
6
|
-
import { DEFAULT_BASE_URL, MAX_BATCH_IDS } from "./client-config.js";
|
|
7
7
|
// Re-exported whole so `index.ts` and every existing importer keep their path.
|
|
8
8
|
export * from "./client-config.js";
|
|
9
|
-
import { browseQuery, createBrowseNamespace } from "./client-browse.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { appendProjectEvent as appendProjectEventCall, createProject as createProjectCall, getProject as getProjectCall, listProjects as listProjectsCall, } from "./projects.js";
|
|
9
|
+
import { browseQuery, createBrowseNamespace, } from "./client-browse.js";
|
|
10
|
+
import { declaredFinishes, declaredProducts } from "./associations-client.js";
|
|
11
|
+
import { brandColourDeck, brandProfile, listBrands } from "./brands-client.js";
|
|
13
12
|
import { CodesClient } from "./codes-client.js";
|
|
14
13
|
import { ColoursClient } from "./colours-client.js";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
14
|
+
import { matchStream as matchStreamCall } from "./match-stream.js";
|
|
15
|
+
import { appendProjectEvent as appendProjectEventCall, createProject as createProjectCall, getProject as getProjectCall, listProjects as listProjectsCall, } from "./projects.js";
|
|
16
|
+
import { resolvePhrasesRequest as resolvePhrases, searchVocabularyRequest, } from "./resolve-client.js";
|
|
17
17
|
import { SCHEMA_RESOURCE_OPERATION_IDS } from "./schema-resource-types.js";
|
|
18
|
+
import { suggestQuery } from "./suggest-types.js";
|
|
18
19
|
/**
|
|
19
20
|
* The MaterialGraph client — every lens on the graph, on one object.
|
|
20
21
|
*
|
|
@@ -112,10 +113,17 @@ export class MaterialGraphClient {
|
|
|
112
113
|
return this.#transport.lastRateLimit;
|
|
113
114
|
}
|
|
114
115
|
#get(operationId, query, signal, pathParams) {
|
|
115
|
-
return this.#transport.requestJson(operation(operationId), {
|
|
116
|
+
return this.#transport.requestJson(operation(operationId), {
|
|
117
|
+
query,
|
|
118
|
+
signal,
|
|
119
|
+
pathParams,
|
|
120
|
+
});
|
|
116
121
|
}
|
|
117
122
|
#post(operationId, body, signal) {
|
|
118
|
-
return this.#transport.requestJson(operation(operationId), {
|
|
123
|
+
return this.#transport.requestJson(operation(operationId), {
|
|
124
|
+
body,
|
|
125
|
+
signal,
|
|
126
|
+
});
|
|
119
127
|
}
|
|
120
128
|
/**
|
|
121
129
|
* THE QUESTION: "find me materials like this" — a RANKED answer over words, a
|
|
@@ -202,8 +210,7 @@ export class MaterialGraphClient {
|
|
|
202
210
|
* {@link MaterialGraphClient.browse} to walk by lightness/vividness, and
|
|
203
211
|
* {@link MaterialGraphClient.search} when colour is one signal among many.
|
|
204
212
|
* Report matches as visual likeness, never a stated colour specification.
|
|
205
|
-
* `family` gates
|
|
206
|
-
* alone crosses a hue boundary; a gated page can be shorter than `limit`.
|
|
213
|
+
* `family` gates to ONE colorscope family (a gated page can be short); `brand` gates to named brands — one brand's nearest colour to another's.
|
|
207
214
|
*/
|
|
208
215
|
byColour(params, signal) {
|
|
209
216
|
return this.#get("search_by_colour", {
|
|
@@ -213,6 +220,7 @@ export class MaterialGraphClient {
|
|
|
213
220
|
a: params.a,
|
|
214
221
|
b: params.b,
|
|
215
222
|
category: params.category,
|
|
223
|
+
brand: params.brand,
|
|
216
224
|
family: params.family,
|
|
217
225
|
limit: params.limit,
|
|
218
226
|
}, signal);
|
|
@@ -307,7 +315,11 @@ export class MaterialGraphClient {
|
|
|
307
315
|
if (ids.length > MAX_BATCH_IDS) {
|
|
308
316
|
throw MaterialGraphError.unexpected(`MaterialGraphClient.materials(): at most ${MAX_BATCH_IDS} ids per call, received ${ids.length}. Split into multiple batches.`);
|
|
309
317
|
}
|
|
310
|
-
return this.#post("get_materials_batch_post", {
|
|
318
|
+
return this.#post("get_materials_batch_post", {
|
|
319
|
+
ids,
|
|
320
|
+
view: options?.view ?? "tile",
|
|
321
|
+
idKind: options?.idKind ?? "variantId",
|
|
322
|
+
}, signal);
|
|
311
323
|
}
|
|
312
324
|
materialsCached(ids, options, signal) {
|
|
313
325
|
if (ids.length > MAX_BATCH_IDS) {
|
|
@@ -388,38 +400,38 @@ export class MaterialGraphClient {
|
|
|
388
400
|
searchVocabulary(query, options, signal) {
|
|
389
401
|
return searchVocabularyRequest(this.#transport, query, options, signal);
|
|
390
402
|
}
|
|
391
|
-
/** "I have words — what do they mean
|
|
392
|
-
resolve(phrases,
|
|
393
|
-
return
|
|
403
|
+
/** "I have words — what do they mean, and what stands behind them?" — see {@link resolvePhrases}. */
|
|
404
|
+
resolve(phrases, scope) {
|
|
405
|
+
return resolvePhrases(this.#transport, phrases, scope);
|
|
394
406
|
}
|
|
395
407
|
/**
|
|
396
408
|
* THE QUESTION: "which materials, from ANYONE, are ASTM E84 Class A AND
|
|
397
|
-
* GREENGUARD-certified?" — hard specifications, no ranking, every brand at
|
|
398
|
-
* whatever each called the value in source.
|
|
409
|
+
* GREENGUARD-certified?" — hard specifications, no ranking, every brand at
|
|
410
|
+
* once whatever each called the value in source. The value-anchored,
|
|
411
|
+
* spec-only primitive (GET `/api/v1/search/specifications`).
|
|
399
412
|
*
|
|
400
413
|
* Instead use `search({ filters })` when the spec merely GATES a ranked
|
|
401
414
|
* question, `codes.advisory()` when the real question is what a space demands,
|
|
402
|
-
* and {@link
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* `sourceBasis`). Answers "which materials, from anyone, are ASTM E84 Class A
|
|
416
|
-
* AND GREENGUARD-certified?" Build the strings with `specCriterion` /
|
|
417
|
-
* `certificationProgrammeCriterion` (e.g.
|
|
418
|
-
* `certificationProgrammeCriterion("GREENGUARD Gold")`); discover the queryable
|
|
419
|
-
* vocabulary with {@link specificationFacets} and {@link attributeValues}.
|
|
415
|
+
* and {@link specificationFacets} first if you do not know the canonical
|
|
416
|
+
* tokens. Guessing one no longer returns a silently empty answer: an
|
|
417
|
+
* unresolvable criterion means the query NEVER RAN and the response says so —
|
|
418
|
+
* read {@link BySpecResponse.unresolvedCriteria} before treating an empty
|
|
419
|
+
* `matches` as "nothing qualifies". A PAGE, NOT A CORPUS, unusually so:
|
|
420
|
+
* `totalMatches` is the returned sample's size under `limit`, not a corpus
|
|
421
|
+
* count. Each `spec` is an `attribute:value` criterion built with
|
|
422
|
+
* `specCriterion` / `certificationProgrammeCriterion`; a variant must satisfy
|
|
423
|
+
* ALL of them, and each match carries per-criterion trust (`confidence` +
|
|
424
|
+
* `sourceBasis`). NUMBER-valued specs take BOUNDS, not values — pass
|
|
425
|
+
* `options.ranges` built with `specRangeCriterion("light_reflectance_value",
|
|
426
|
+
* { min: 60 })`; bounds are inclusive, at least one is required, and a
|
|
427
|
+
* non-numeric stored value is excluded rather than read as zero.
|
|
420
428
|
*/
|
|
421
429
|
bySpec(spec, options, signal) {
|
|
422
|
-
return this.#get("catalog_by_spec", {
|
|
430
|
+
return this.#get("catalog_by_spec", {
|
|
431
|
+
spec: Array.isArray(spec) ? spec : [spec],
|
|
432
|
+
...(options?.ranges?.length ? { specRange: options.ranges } : {}),
|
|
433
|
+
limit: options?.limit,
|
|
434
|
+
}, signal);
|
|
423
435
|
}
|
|
424
436
|
// ── Taxonomy + meta ──────────────────────────────────────────────────────────
|
|
425
437
|
/**
|
|
@@ -575,26 +587,17 @@ export class MaterialGraphClient {
|
|
|
575
587
|
return this.#get("explain_material_term", undefined, signal, { term });
|
|
576
588
|
}
|
|
577
589
|
// ── Brands ───────────────────────────────────────────────────────────────────
|
|
578
|
-
/**
|
|
579
|
-
* THE QUESTION: "whose catalogs are in here, and how big is each?" The SDK's
|
|
580
|
-
* one OFFSET-paginated surface: read `meta.total` for the collection size and
|
|
581
|
-
* `meta.hasMore` to keep walking. Instead use {@link MaterialGraphClient.brand}
|
|
582
|
-
* for one brand's profile and trade signals.
|
|
583
|
-
*/
|
|
590
|
+
/** "Whose catalogs are in here, and how big is each?" — {@link listBrands}. */
|
|
584
591
|
brands(options = {}, signal) {
|
|
585
|
-
return this.#
|
|
592
|
+
return listBrands(this.#transport, options, signal);
|
|
586
593
|
}
|
|
587
|
-
/**
|
|
588
|
-
* THE QUESTION: "who is this brand, and what do they claim?" — profile plus the
|
|
589
|
-
* trade/spec signals ON RECORD. `trade` is keyed by attribute id and an ABSENT
|
|
590
|
-
* key means no value is held — never infer `false`. Instead use
|
|
591
|
-
* {@link MaterialGraphClient.search} to find their materials; this describes the
|
|
592
|
-
* company, not the catalog.
|
|
593
|
-
*/
|
|
594
|
+
/** "Who is this brand, and what do they claim?" — {@link brandProfile}. */
|
|
594
595
|
brand(brandSlug, options, signal) {
|
|
595
|
-
return this.#
|
|
596
|
-
|
|
597
|
-
|
|
596
|
+
return brandProfile(this.#transport, brandSlug, options, signal);
|
|
597
|
+
}
|
|
598
|
+
/** "This maker's whole range, the way a fan deck reads" — {@link brandColourDeck}. */
|
|
599
|
+
brandColourDeck(brandSlug, signal) {
|
|
600
|
+
return brandColourDeck(this.#transport, brandSlug, signal);
|
|
598
601
|
}
|
|
599
602
|
// ── Runs (the hosted agentic surface) ────────────────────────────────────────
|
|
600
603
|
/**
|
package/errors.js
CHANGED
|
@@ -53,7 +53,9 @@ function sanitizeRecord(value, secrets, depth) {
|
|
|
53
53
|
.slice(0, MAX_EXTENSION_ITEMS)
|
|
54
54
|
.flatMap(([key, item]) => {
|
|
55
55
|
const sanitized = sanitizeValue(item, secrets, depth + 1);
|
|
56
|
-
return sanitized === undefined
|
|
56
|
+
return sanitized === undefined
|
|
57
|
+
? []
|
|
58
|
+
: [[safeString(key, secrets), sanitized]];
|
|
57
59
|
});
|
|
58
60
|
return Object.fromEntries(entries);
|
|
59
61
|
}
|
|
@@ -66,10 +68,13 @@ function sanitizeIssues(value, secrets) {
|
|
|
66
68
|
.map((issue) => sanitizeRecord(issue, secrets, 0));
|
|
67
69
|
}
|
|
68
70
|
function sanitizeSuggestions(value, secrets) {
|
|
69
|
-
if (!Array.isArray(value) ||
|
|
71
|
+
if (!Array.isArray(value) ||
|
|
72
|
+
value.some((suggestion) => typeof suggestion !== "string")) {
|
|
70
73
|
return undefined;
|
|
71
74
|
}
|
|
72
|
-
return value
|
|
75
|
+
return value
|
|
76
|
+
.slice(0, MAX_EXTENSION_ITEMS)
|
|
77
|
+
.map((suggestion) => safeString(suggestion, secrets));
|
|
73
78
|
}
|
|
74
79
|
function normalizeProblem(body, secrets) {
|
|
75
80
|
if (!isRecord(body) ||
|
|
@@ -89,11 +94,15 @@ function normalizeProblem(body, secrets) {
|
|
|
89
94
|
title: safeString(body.title, secrets),
|
|
90
95
|
status: body.status,
|
|
91
96
|
detail: safeString(body.detail, secrets),
|
|
92
|
-
...(body.instance === undefined
|
|
97
|
+
...(body.instance === undefined
|
|
98
|
+
? {}
|
|
99
|
+
: { instance: safeString(body.instance, secrets) }),
|
|
93
100
|
is_retriable: body.is_retriable,
|
|
94
101
|
trace_id: safeString(body.trace_id, secrets),
|
|
95
102
|
...(suggestions === undefined ? {} : { suggestions }),
|
|
96
|
-
...(typeof body.doc_uri === "string"
|
|
103
|
+
...(typeof body.doc_uri === "string"
|
|
104
|
+
? { doc_uri: safeString(body.doc_uri, secrets) }
|
|
105
|
+
: {}),
|
|
97
106
|
...(issues === undefined ? {} : { issues }),
|
|
98
107
|
};
|
|
99
108
|
}
|
|
@@ -107,7 +116,9 @@ function normalizeSimple(body, secrets) {
|
|
|
107
116
|
};
|
|
108
117
|
}
|
|
109
118
|
function unexpectedMessage(status, responseText, secrets) {
|
|
110
|
-
const sanitized = redact(responseText, secrets)
|
|
119
|
+
const sanitized = redact(responseText, secrets)
|
|
120
|
+
.trim()
|
|
121
|
+
.slice(0, MAX_UNEXPECTED_TEXT);
|
|
111
122
|
return sanitized.length > 0
|
|
112
123
|
? `MaterialGraph request failed with HTTP ${status}: ${sanitized}`
|
|
113
124
|
: `MaterialGraph request failed with HTTP ${status}`;
|
|
@@ -143,7 +154,10 @@ export class MaterialGraphError extends Error {
|
|
|
143
154
|
return this.retriable;
|
|
144
155
|
}
|
|
145
156
|
static unexpected(message) {
|
|
146
|
-
return new MaterialGraphError(message, {
|
|
157
|
+
return new MaterialGraphError(message, {
|
|
158
|
+
kind: "unexpected",
|
|
159
|
+
retriable: false,
|
|
160
|
+
});
|
|
147
161
|
}
|
|
148
162
|
static fromTransport(input) {
|
|
149
163
|
if (input.callerSignal?.aborted === true) {
|
|
@@ -174,7 +188,9 @@ export class MaterialGraphError extends Error {
|
|
|
174
188
|
const retriable = RETRIABLE_STATUS.has(input.status);
|
|
175
189
|
const metadata = requestMetadata(input);
|
|
176
190
|
if (input.status === 429) {
|
|
177
|
-
return new MaterialGraphError(problem?.detail ??
|
|
191
|
+
return new MaterialGraphError(problem?.detail ??
|
|
192
|
+
simple?.error ??
|
|
193
|
+
"MaterialGraph rate limit exceeded (429)", {
|
|
178
194
|
...metadata,
|
|
179
195
|
status: input.status,
|
|
180
196
|
rateLimit: input.rateLimit,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types for a maker's whole range — GET `/api/v1/brands/{brandSlug}/colours`.
|
|
3
|
+
*
|
|
4
|
+
* The one read in the SDK that is deliberately UNPAGINATED. Family order is only
|
|
5
|
+
* correct over a complete range: the greys cannot be placed against the browns
|
|
6
|
+
* while holding one page of each. So the response carries the brand, and
|
|
7
|
+
* `truncatedAt` appears only if a range ever exceeds the server's ceiling.
|
|
8
|
+
*/
|
|
9
|
+
export interface FanDeckSwatch {
|
|
10
|
+
/** Opaque token. Never parse or construct one. */
|
|
11
|
+
variantId: string;
|
|
12
|
+
brand: string;
|
|
13
|
+
/** The maker's own colour name. Null when the catalogue publishes none. */
|
|
14
|
+
name: string | null;
|
|
15
|
+
/** The maker's PUBLISHED hex. This read never returns a derived colour. */
|
|
16
|
+
hex: string;
|
|
17
|
+
/** colorscope's family id, lower case (`cream`, `gray`, `blue`). */
|
|
18
|
+
family: string;
|
|
19
|
+
/** OKLab L, 0–1. The sort key within a family: light to dark. */
|
|
20
|
+
lightness: number;
|
|
21
|
+
imageUrl: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface FanDeckFamily {
|
|
24
|
+
family: string;
|
|
25
|
+
/** Count over the WHOLE range, computed before any truncation. */
|
|
26
|
+
count: number;
|
|
27
|
+
}
|
|
28
|
+
export interface BrandFanDeck {
|
|
29
|
+
/** The brands the deck covers, as resolved from the slug. */
|
|
30
|
+
brands: string[];
|
|
31
|
+
/** Grouped by `families` order, light to dark within each family. */
|
|
32
|
+
swatches: FanDeckSwatch[];
|
|
33
|
+
families: FanDeckFamily[];
|
|
34
|
+
/**
|
|
35
|
+
* Present ONLY when the range exceeded the server ceiling. Absent means the
|
|
36
|
+
* deck is complete — absent is not zero.
|
|
37
|
+
*/
|
|
38
|
+
truncatedAt?: number;
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types for a maker's whole range — GET `/api/v1/brands/{brandSlug}/colours`.
|
|
3
|
+
*
|
|
4
|
+
* The one read in the SDK that is deliberately UNPAGINATED. Family order is only
|
|
5
|
+
* correct over a complete range: the greys cannot be placed against the browns
|
|
6
|
+
* while holding one page of each. So the response carries the brand, and
|
|
7
|
+
* `truncatedAt` appears only if a range ever exceeds the server's ceiling.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|