@instruments/materialgraph 0.3.0 → 0.5.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/client-browse.d.ts +46 -0
- package/client-browse.js +42 -0
- package/client.d.ts +15 -5
- package/client.js +18 -22
- package/colours-client.d.ts +55 -12
- package/colours-client.js +42 -0
- package/index.d.ts +1 -0
- package/material-asset-types.d.ts +1 -1
- package/operations.d.ts +1 -1
- package/operations.js +3 -0
- package/package.json +1 -1
- package/palette-search-types.d.ts +122 -0
- package/palette-search-types.js +1 -0
- package/suggest-types.d.ts +58 -0
- package/suggest-types.js +21 -0
- package/types.d.ts +1 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `browse` namespace's implementation — the paged colour-ordered walk of the
|
|
3
|
+
* corpus behind `client.browse(...)` and `client.browse.pages(...)`.
|
|
4
|
+
*
|
|
5
|
+
* Lifted out of `client.ts` for the repository's source-file size budget. These
|
|
6
|
+
* were already private implementations behind a public namespace, so nothing
|
|
7
|
+
* about the client's surface changes: `client.browse` is still a callable with a
|
|
8
|
+
* `.pages()` generator, and the class still owns the transport. What moved is
|
|
9
|
+
* the pagination LOOP, which is the only real logic here and the only part worth
|
|
10
|
+
* testing on its own.
|
|
11
|
+
*/
|
|
12
|
+
import type { BrowseParams, BrowseResponse } from "./types.js";
|
|
13
|
+
/** `client.browse(...)` — callable for one page, `.pages()` for the whole walk. */
|
|
14
|
+
export interface BrowseNamespace {
|
|
15
|
+
(params: BrowseParams, signal?: AbortSignal): Promise<BrowseResponse>;
|
|
16
|
+
pages(params: BrowseParams, signal?: AbortSignal): AsyncGenerator<BrowseResponse, void, unknown>;
|
|
17
|
+
}
|
|
18
|
+
/** Fetch one page. The client supplies this, so the transport stays private. */
|
|
19
|
+
export type BrowsePageFetcher = (params: BrowseParams, signal?: AbortSignal) => Promise<BrowseResponse>;
|
|
20
|
+
/**
|
|
21
|
+
* The query string for one browse page — the four params the route reads, named
|
|
22
|
+
* here so the client method and the pagination loop cannot disagree about them.
|
|
23
|
+
*/
|
|
24
|
+
export declare function browseQuery(params: BrowseParams): {
|
|
25
|
+
sort: BrowseParams["sort"];
|
|
26
|
+
cursor: BrowseParams["cursor"];
|
|
27
|
+
limit: BrowseParams["limit"];
|
|
28
|
+
types: BrowseParams["types"];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Walk every page from `params.cursor` onward.
|
|
32
|
+
*
|
|
33
|
+
* Stops on the first page that reports no next cursor OR comes back empty. Both
|
|
34
|
+
* conditions matter: a server that keeps handing out a cursor for an exhausted
|
|
35
|
+
* scan would otherwise spin here forever, and an infinite generator is a hang,
|
|
36
|
+
* not an error anyone can see.
|
|
37
|
+
*/
|
|
38
|
+
export declare function browsePages(fetchPage: BrowsePageFetcher, params: BrowseParams, signal?: AbortSignal): AsyncGenerator<BrowseResponse, void, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* Build the callable-with-`.pages()` object the client exposes as `browse`.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const page = await client.browse({ sort: "lightest" });
|
|
44
|
+
* for await (const page of client.browse.pages({ sort: "lightest" })) { … }
|
|
45
|
+
*/
|
|
46
|
+
export declare function createBrowseNamespace(fetchPage: BrowsePageFetcher): BrowseNamespace;
|
package/client-browse.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The query string for one browse page — the four params the route reads, named
|
|
3
|
+
* here so the client method and the pagination loop cannot disagree about them.
|
|
4
|
+
*/
|
|
5
|
+
export function browseQuery(params) {
|
|
6
|
+
return {
|
|
7
|
+
sort: params.sort,
|
|
8
|
+
cursor: params.cursor,
|
|
9
|
+
limit: params.limit,
|
|
10
|
+
types: params.types,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Walk every page from `params.cursor` onward.
|
|
15
|
+
*
|
|
16
|
+
* Stops on the first page that reports no next cursor OR comes back empty. Both
|
|
17
|
+
* conditions matter: a server that keeps handing out a cursor for an exhausted
|
|
18
|
+
* scan would otherwise spin here forever, and an infinite generator is a hang,
|
|
19
|
+
* not an error anyone can see.
|
|
20
|
+
*/
|
|
21
|
+
export async function* browsePages(fetchPage, params, signal) {
|
|
22
|
+
let cursor = params.cursor;
|
|
23
|
+
for (;;) {
|
|
24
|
+
const page = await fetchPage({ ...params, cursor }, signal);
|
|
25
|
+
yield page;
|
|
26
|
+
if (page.cursor === undefined || page.results.length === 0)
|
|
27
|
+
return;
|
|
28
|
+
cursor = page.cursor;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build the callable-with-`.pages()` object the client exposes as `browse`.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const page = await client.browse({ sort: "lightest" });
|
|
36
|
+
* for await (const page of client.browse.pages({ sort: "lightest" })) { … }
|
|
37
|
+
*/
|
|
38
|
+
export function createBrowseNamespace(fetchPage) {
|
|
39
|
+
const browse = (params, signal) => fetchPage(params, signal);
|
|
40
|
+
browse.pages = (params, signal) => browsePages(fetchPage, params, signal);
|
|
41
|
+
return browse;
|
|
42
|
+
}
|
package/client.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type RateLimit } from "./errors.js";
|
|
2
|
+
import { type BrowseNamespace } from "./client-browse.js";
|
|
3
|
+
export type { BrowseNamespace } from "./client-browse.js";
|
|
2
4
|
import type { CapabilityManifestResponse, CreatePublicRunRequest, CreatePublicRunResponse, DrainRunEventsOptions, PublicRunResponse, ResumeRunOptions, RunEvent, SourceMaterialBatchDetail, SourceMaterialBatchReceipt, StreamRunEventsOptions, UploadSourceMaterialInput } from "./run-types.js";
|
|
3
5
|
import type { FinishScheduleResponse, GetRunScheduleOptions } from "./schedule-types.js";
|
|
4
6
|
import type { MatchStreamEvent } from "./match-stream-types.js";
|
|
@@ -11,7 +13,7 @@ import type { BrandCatalogListPage, BrandsOptions } from "./brand-types.js";
|
|
|
11
13
|
import type { MaterialAssetProjection } from "./material-asset-types.js";
|
|
12
14
|
import type { MaterialBlockProjection } from "./material-block-types.js";
|
|
13
15
|
import type { SchemaResource, SchemaResourceResponse } from "./schema-resource-types.js";
|
|
14
|
-
import type { ApplicableSpecsResponse, BrandOptions, BrandProfileResponse, ByColourParams, ByColourResponse,
|
|
16
|
+
import type { ApplicableSpecsResponse, BrandOptions, BrandProfileResponse, ByColourParams, ByColourResponse, BySpecOptions, BySpecResponse, ColourFacetsResponse, CoordinateInput, CoordinateResponse, DemandBatchInput, DemandBatchResponse, DemandParams, DemandResponse, ExtractPaletteInput, ExtractPaletteResponse, GlossaryTermResponse, MaterialResponse, MaterialsBatchDetailResponse, MaterialsBatchGetOptions, MaterialsBatchOptions, MaterialsBatchTileResponse, MatchInput, MatchResponse, PairingsOptions, PairingsResponse, ResolveAttributeOptions, ResolveAttributeResponse, ResolveAttributeValueOptions, ResolveAttributeValueResponse, ResolveColourResponse, SearchGetParams, SearchInput, SearchResponse, SiblingsResponse, SpecFacetsDistributionResponse, SpecFacetsListResponse, SuggestColourResponse, SuggestOptions, SuggestResponse, TaxonomyResponse, VocabularySearchOptions, VocabularySearchResponse, WhoamiResponse } from "./types.js";
|
|
15
17
|
export declare const DEFAULT_BASE_URL = "https://beta.materialgraph.com";
|
|
16
18
|
export declare const MAX_BATCH_IDS = 100;
|
|
17
19
|
export interface MaterialGraphClientOptions {
|
|
@@ -22,10 +24,6 @@ export interface MaterialGraphClientOptions {
|
|
|
22
24
|
/** Inject a custom `fetch` (tests, proxies). Defaults to the global `fetch`. */
|
|
23
25
|
fetch?: typeof fetch;
|
|
24
26
|
}
|
|
25
|
-
export interface BrowseNamespace {
|
|
26
|
-
(params: BrowseParams, signal?: AbortSignal): Promise<BrowseResponse>;
|
|
27
|
-
pages(params: BrowseParams, signal?: AbortSignal): AsyncGenerator<BrowseResponse, void, unknown>;
|
|
28
|
-
}
|
|
29
27
|
export declare class MaterialGraphClient {
|
|
30
28
|
#private;
|
|
31
29
|
readonly baseUrl: string;
|
|
@@ -130,6 +128,18 @@ export declare class MaterialGraphClient {
|
|
|
130
128
|
suggestColour(q: string, options?: {
|
|
131
129
|
limit?: number;
|
|
132
130
|
}, signal?: AbortSignal): Promise<SuggestColourResponse>;
|
|
131
|
+
/**
|
|
132
|
+
* Entity typeahead across products, brands and categories
|
|
133
|
+
* (GET `/api/v1/search/suggest`). Built for per-keystroke use: one indexed
|
|
134
|
+
* lookup, no embedding call and no model. Every suggestion carries the
|
|
135
|
+
* `refId` it resolves to — a product id, a brand slug, or a product-type key
|
|
136
|
+
* — so selecting one should navigate to that entity rather than re-running
|
|
137
|
+
* `label` as a search.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* const { suggestions } = await client.suggest("carr", { kind: "product" });
|
|
141
|
+
*/
|
|
142
|
+
suggest(q: string, options?: SuggestOptions, signal?: AbortSignal): Promise<SuggestResponse>;
|
|
133
143
|
/** Resolve a free-text spec phrase to canonical attribute ids (GET `/api/v1/catalog/attributes/resolve`). */
|
|
134
144
|
resolveAttribute(q: string, options?: ResolveAttributeOptions, signal?: AbortSignal): Promise<ResolveAttributeResponse>;
|
|
135
145
|
/** Resolve a value phrase against one canonical attribute's dictionary. */
|
package/client.js
CHANGED
|
@@ -2,6 +2,8 @@ import { MaterialGraphError } from "./errors.js";
|
|
|
2
2
|
import { HttpTransport } from "./http-transport.js";
|
|
3
3
|
import { findSdkOperation } from "./operations.js";
|
|
4
4
|
import { cancelRun, capabilities as capabilitiesRun, createRun, drainRunEvents, getRun, getRunSchedule, getSourceMaterialBatch as getSourceMaterialBatchCall, resumeRun, streamRunEvents, uploadSourceMaterial, } from "./runs.js";
|
|
5
|
+
import { browseQuery, createBrowseNamespace } from "./client-browse.js";
|
|
6
|
+
import { suggestQuery } from "./suggest-types.js";
|
|
5
7
|
import { matchStream as matchStreamCall } from "./match-stream.js";
|
|
6
8
|
import { appendProjectEvent as appendProjectEventCall, createProject as createProjectCall, getProject as getProjectCall, listProjects as listProjectsCall, } from "./projects.js";
|
|
7
9
|
import { CodesClient } from "./codes-client.js";
|
|
@@ -51,15 +53,14 @@ export class MaterialGraphClient {
|
|
|
51
53
|
fetch: options.fetch ?? globalThis.fetch,
|
|
52
54
|
});
|
|
53
55
|
this.baseUrl = this.#transport.baseUrl;
|
|
54
|
-
|
|
55
|
-
browse.pages = (params, signal) => this.#browsePages(params, signal);
|
|
56
|
-
this.browse = browse;
|
|
56
|
+
this.browse = createBrowseNamespace((params, signal) => this.#get("search_browse_by_colour", browseQuery(params), signal));
|
|
57
57
|
this.codes = new CodesClient({
|
|
58
58
|
get: (operationId, query, signal) => this.#get(operationId, query, signal),
|
|
59
59
|
post: (operationId, body, signal) => this.#post(operationId, body, signal),
|
|
60
60
|
});
|
|
61
61
|
this.colours = new ColoursClient({
|
|
62
62
|
get: (operationId, query, signal) => this.#get(operationId, query, signal),
|
|
63
|
+
post: (operationId, body, signal) => this.#post(operationId, body, signal),
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
66
|
get lastRateLimit() {
|
|
@@ -173,25 +174,6 @@ export class MaterialGraphClient {
|
|
|
173
174
|
idKind: options?.idKind ?? "variantId",
|
|
174
175
|
}, signal);
|
|
175
176
|
}
|
|
176
|
-
// ── Browse (private impls behind the `browse` namespace) ─────────────────────
|
|
177
|
-
#browsePage(params, signal) {
|
|
178
|
-
return this.#get("search_browse_by_colour", {
|
|
179
|
-
sort: params.sort,
|
|
180
|
-
cursor: params.cursor,
|
|
181
|
-
limit: params.limit,
|
|
182
|
-
types: params.types,
|
|
183
|
-
}, signal);
|
|
184
|
-
}
|
|
185
|
-
async *#browsePages(params, signal) {
|
|
186
|
-
let cursor = params.cursor;
|
|
187
|
-
for (;;) {
|
|
188
|
-
const page = await this.#browsePage({ ...params, cursor }, signal);
|
|
189
|
-
yield page;
|
|
190
|
-
if (page.cursor === undefined || page.results.length === 0)
|
|
191
|
-
return;
|
|
192
|
-
cursor = page.cursor;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
177
|
// ── Colour helpers ───────────────────────────────────────────────────────────
|
|
196
178
|
/** The three canonical colour vocabularies with live counts (GET `/api/v1/colours/facets`). */
|
|
197
179
|
colourFacets(signal) {
|
|
@@ -205,6 +187,20 @@ export class MaterialGraphClient {
|
|
|
205
187
|
suggestColour(q, options, signal) {
|
|
206
188
|
return this.#get("suggest_colour_names", { q, limit: options?.limit }, signal);
|
|
207
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Entity typeahead across products, brands and categories
|
|
192
|
+
* (GET `/api/v1/search/suggest`). Built for per-keystroke use: one indexed
|
|
193
|
+
* lookup, no embedding call and no model. Every suggestion carries the
|
|
194
|
+
* `refId` it resolves to — a product id, a brand slug, or a product-type key
|
|
195
|
+
* — so selecting one should navigate to that entity rather than re-running
|
|
196
|
+
* `label` as a search.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* const { suggestions } = await client.suggest("carr", { kind: "product" });
|
|
200
|
+
*/
|
|
201
|
+
suggest(q, options, signal) {
|
|
202
|
+
return this.#get("search_suggest", suggestQuery(q, options), signal);
|
|
203
|
+
}
|
|
208
204
|
// ── Catalog ──────────────────────────────────────────────────────────────────
|
|
209
205
|
/** Resolve a free-text spec phrase to canonical attribute ids (GET `/api/v1/catalog/attributes/resolve`). */
|
|
210
206
|
resolveAttribute(q, options, signal) {
|
package/colours-client.d.ts
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The colour
|
|
2
|
+
* The colour surface, namespaced as `mg.colours` — the aggregate reads
|
|
3
|
+
* (`stats`, `profiles`) plus MULTI-COLOUR search (`palette`, `gradient`).
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
* does the graph hold at all" (`stats`)
|
|
6
|
-
* come in" (`profiles`)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* single verb.
|
|
5
|
+
* One colour family rather than four more flat methods on an already-large
|
|
6
|
+
* client facade: "how much colour does the graph hold at all" (`stats`), "what
|
|
7
|
+
* colours does this category come in" (`profiles`), "what goes with these
|
|
8
|
+
* colours" (`palette`), and "what moves from this colour to that one"
|
|
9
|
+
* (`gradient`). `mg.codes` and `mg.browse` set the precedent for a namespace
|
|
10
|
+
* when a capability is a family rather than a single verb.
|
|
10
11
|
*
|
|
11
12
|
* The namespace holds no transport of its own: the client hands it a bound
|
|
12
|
-
* `get`, so auth, rate-limit accounting and error shaping stay in one
|
|
13
|
+
* `get`/`post`, so auth, rate-limit accounting and error shaping stay in one
|
|
14
|
+
* place.
|
|
13
15
|
*
|
|
14
|
-
*
|
|
15
|
-
* refreshed on a schedule, not on write, so a caller can and
|
|
16
|
-
* much of the corpus the answer actually covers before
|
|
16
|
+
* The two AGGREGATE responses carry a `basis`. Those are reads of a
|
|
17
|
+
* materialized view refreshed on a schedule, not on write, so a caller can and
|
|
18
|
+
* should check how much of the corpus the answer actually covers before
|
|
19
|
+
* treating it as complete. The two SEARCH responses are live and uncached.
|
|
17
20
|
*/
|
|
18
21
|
import type { CategoryColourProfilesResponse, ColourProfilesParams, ColourStatsResponse } from "./colour-aggregate-types.js";
|
|
19
22
|
import type { QueryParams } from "./http-transport.js";
|
|
20
23
|
import type { SdkOperationId } from "./operations.js";
|
|
21
|
-
|
|
24
|
+
import type { SearchGradientInput, SearchGradientResponse, SearchPaletteInput, SearchPaletteResponse } from "./palette-search-types.js";
|
|
25
|
+
/** The client's own request primitives, handed down so this file owns no transport. */
|
|
22
26
|
export interface ColoursTransport {
|
|
23
27
|
get<T>(operationId: SdkOperationId, query?: QueryParams, signal?: AbortSignal): Promise<T>;
|
|
28
|
+
post<T>(operationId: SdkOperationId, body: unknown, signal?: AbortSignal): Promise<T>;
|
|
24
29
|
}
|
|
25
30
|
export declare class ColoursClient {
|
|
26
31
|
#private;
|
|
@@ -49,4 +54,42 @@ export declare class ColoursClient {
|
|
|
49
54
|
* answer, listed rather than silently absent.
|
|
50
55
|
*/
|
|
51
56
|
stats(signal?: AbortSignal): Promise<ColourStatsResponse>;
|
|
57
|
+
/**
|
|
58
|
+
* Find materials that go with a WHOLE PALETTE — N colours at once, not one
|
|
59
|
+
* colour repeated (POST `/api/v1/search/palettes`).
|
|
60
|
+
*
|
|
61
|
+
* Each colour scores `sim × prop`: `sim` is OKLab closeness on the same
|
|
62
|
+
* mapping `byColour` ranks with, and `prop` rewards a candidate whose own
|
|
63
|
+
* swatch SHARE sits near the `proportion` you asked for. So the same colours
|
|
64
|
+
* at different proportions come back in a different order — a colour at 60%
|
|
65
|
+
* of the surface is a different query from one at 5%. `logic: "all"` (the
|
|
66
|
+
* default) applies a per-colour eligibility floor and returns the weighted
|
|
67
|
+
* geometric mean, so a candidate missing one colour is rejected rather than
|
|
68
|
+
* merely ranked low; `logic: "any"` is a noisy-OR that accepts partial
|
|
69
|
+
* answers but saturates once several colours score well, so prefer `all`
|
|
70
|
+
* when the ORDER is what matters.
|
|
71
|
+
*
|
|
72
|
+
* Every match carries the candidate's full `palette` plus a per-colour
|
|
73
|
+
* receipt naming the swatch that answered each queried colour.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const { matches } = await mg.colours.palette({
|
|
77
|
+
* colours: [
|
|
78
|
+
* { hex: "F5EFE0", proportion: 0.6 },
|
|
79
|
+
* { hex: "9CAF88", proportion: 0.2 },
|
|
80
|
+
* { hex: "3E3B36", proportion: 0.2 },
|
|
81
|
+
* ],
|
|
82
|
+
* });
|
|
83
|
+
*/
|
|
84
|
+
palette(input: SearchPaletteInput, signal?: AbortSignal): Promise<SearchPaletteResponse>;
|
|
85
|
+
/**
|
|
86
|
+
* Find materials that move ALONG a colour transition — "cream to rust", in
|
|
87
|
+
* order (POST `/api/v1/search/gradients`).
|
|
88
|
+
*
|
|
89
|
+
* Not a palette: the score is multiplied by `coverage`, the share of the path
|
|
90
|
+
* the candidate actually spans, so a material owning only one end ranks below
|
|
91
|
+
* one that travels the whole way, and a flat single-colour material is
|
|
92
|
+
* rejected outright however close its hue sits to the path.
|
|
93
|
+
*/
|
|
94
|
+
gradient(input: SearchGradientInput, signal?: AbortSignal): Promise<SearchGradientResponse>;
|
|
52
95
|
}
|
package/colours-client.js
CHANGED
|
@@ -36,4 +36,46 @@ export class ColoursClient {
|
|
|
36
36
|
stats(signal) {
|
|
37
37
|
return this.#transport.get("get_colour_stats", undefined, signal);
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Find materials that go with a WHOLE PALETTE — N colours at once, not one
|
|
41
|
+
* colour repeated (POST `/api/v1/search/palettes`).
|
|
42
|
+
*
|
|
43
|
+
* Each colour scores `sim × prop`: `sim` is OKLab closeness on the same
|
|
44
|
+
* mapping `byColour` ranks with, and `prop` rewards a candidate whose own
|
|
45
|
+
* swatch SHARE sits near the `proportion` you asked for. So the same colours
|
|
46
|
+
* at different proportions come back in a different order — a colour at 60%
|
|
47
|
+
* of the surface is a different query from one at 5%. `logic: "all"` (the
|
|
48
|
+
* default) applies a per-colour eligibility floor and returns the weighted
|
|
49
|
+
* geometric mean, so a candidate missing one colour is rejected rather than
|
|
50
|
+
* merely ranked low; `logic: "any"` is a noisy-OR that accepts partial
|
|
51
|
+
* answers but saturates once several colours score well, so prefer `all`
|
|
52
|
+
* when the ORDER is what matters.
|
|
53
|
+
*
|
|
54
|
+
* Every match carries the candidate's full `palette` plus a per-colour
|
|
55
|
+
* receipt naming the swatch that answered each queried colour.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* const { matches } = await mg.colours.palette({
|
|
59
|
+
* colours: [
|
|
60
|
+
* { hex: "F5EFE0", proportion: 0.6 },
|
|
61
|
+
* { hex: "9CAF88", proportion: 0.2 },
|
|
62
|
+
* { hex: "3E3B36", proportion: 0.2 },
|
|
63
|
+
* ],
|
|
64
|
+
* });
|
|
65
|
+
*/
|
|
66
|
+
palette(input, signal) {
|
|
67
|
+
return this.#transport.post("search_by_palette", input, signal);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Find materials that move ALONG a colour transition — "cream to rust", in
|
|
71
|
+
* order (POST `/api/v1/search/gradients`).
|
|
72
|
+
*
|
|
73
|
+
* Not a palette: the score is multiplied by `coverage`, the share of the path
|
|
74
|
+
* the candidate actually spans, so a material owning only one end ranks below
|
|
75
|
+
* one that travels the whole way, and a flat single-colour material is
|
|
76
|
+
* rejected outright however close its hue sits to the path.
|
|
77
|
+
*/
|
|
78
|
+
gradient(input, signal) {
|
|
79
|
+
return this.#transport.post("search_by_gradient", input, signal);
|
|
80
|
+
}
|
|
39
81
|
}
|
package/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type * from "./codes-types.js";
|
|
|
21
21
|
export { ColoursClient } from "./colours-client.js";
|
|
22
22
|
export type { ColoursTransport } from "./colours-client.js";
|
|
23
23
|
export type * from "./colour-aggregate-types.js";
|
|
24
|
+
export type * from "./palette-search-types.js";
|
|
24
25
|
export type * from "./brand-types.js";
|
|
25
26
|
export type * from "./material-asset-types.js";
|
|
26
27
|
export type * from "./material-block-types.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { OpenEnum } from "./types.js";
|
|
2
2
|
export type MaterialAssetVisualRole = OpenEnum<"grid" | "detail" | "scene" | "swatch" | "technical" | "unknown">;
|
|
3
|
-
export type MaterialAssetStatus = OpenEnum<"downloaded" | "linked_not_downloaded" | "failed">;
|
|
3
|
+
export type MaterialAssetStatus = OpenEnum<"downloaded" | "linked_not_downloaded" | "failed" | "retired_placeholder">;
|
|
4
4
|
export interface MaterialAssetProjectionImage {
|
|
5
5
|
key: string;
|
|
6
6
|
ordinal: number;
|
package/operations.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const MATERIALGRAPH_API_VERSION = "0.7.0";
|
|
2
|
-
declare const OPERATION_DEFINITIONS: readonly [readonly ["search", "POST", "/api/v1/search", "search_materials_post"], readonly ["searchGet", "GET", "/api/v1/search", "search_materials_get"], readonly ["match", "POST", "/api/v1/search/match", "match_material"], readonly ["byColour", "GET", "/api/v1/search/colours", "search_by_colour"], readonly ["material", "GET", "/api/v1/materials/{variantId}", "get_material"], readonly ["siblings", "GET", "/api/v1/materials/{variantId}/siblings", "get_material_siblings"], readonly ["materialBlocks", "GET", "/api/v1/materials/{variantId}/blocks", "list_material_blocks"], readonly ["materialAssets", "GET", "/api/v1/materials/{variantId}/assets", "list_material_assets"], readonly ["schema", "GET", "/api/v1/schema/blocks", "list_schema_blocks"], readonly ["schema", "GET", "/api/v1/schema/attributes", "list_schema_attributes"], readonly ["schema", "GET", "/api/v1/schema/verticals", "list_schema_verticals"], readonly ["schema", "GET", "/api/v1/schema/product-types", "list_schema_product_types"], readonly ["schema", "GET", "/api/v1/schema/units", "list_schema_units"], readonly ["schema", "GET", "/api/v1/schema/value-dictionaries", "list_schema_value_dictionaries"], readonly ["schema", "GET", "/api/v1/schema/source-requirements", "list_schema_source_requirements"], readonly ["schema", "GET", "/api/v1/schema/product-type-templates", "list_schema_product_type_templates"], readonly ["schema", "GET", "/api/v1/schema/mappings", "list_schema_mappings"], readonly ["pairings", "GET", "/api/v1/materials/{variantId}/pairings", "get_material_pairings"], readonly ["materials", "POST", "/api/v1/materials/batch", "get_materials_batch_post"], readonly ["materialsCached", "GET", "/api/v1/materials/batch", "get_materials_batch_get"], readonly ["browse", "GET", "/api/v1/search/browse", "search_browse_by_colour"], readonly ["colourFacets", "GET", "/api/v1/colours/facets", "get_colour_facets"], readonly ["colours.profiles", "GET", "/api/v1/colours/profiles", "get_category_colour_profiles"], readonly ["colours.stats", "GET", "/api/v1/colours/stats", "get_colour_stats"], readonly ["resolveColour", "GET", "/api/v1/search/colours/resolve", "resolve_colour_name"], readonly ["suggestColour", "GET", "/api/v1/search/colours/suggest", "suggest_colour_names"], readonly ["resolveAttribute", "GET", "/api/v1/catalog/attributes/resolve", "catalog_resolve_attribute"], readonly ["resolveAttributeValue", "GET", "/api/v1/catalog/attributes/{attributeId}/values/resolve", "catalog_resolve_value"], readonly ["searchVocabulary", "GET", "/api/v1/catalog/vocabulary/search", "catalog_vocabulary_search"], readonly ["bySpec", "GET", "/api/v1/search/specifications", "catalog_by_spec"], readonly ["taxonomy", "GET", "/api/v1/taxonomy/categories", "get_category_taxonomy"], readonly ["whoami", "GET", "/api/v1/whoami", "whoami"], readonly ["coordinate", "POST", "/api/v1/pairings/coordinate", "coordinate_materials"], readonly ["demand", "GET", "/api/v1/demand", "get_material_demand"], readonly ["extractPalette", "POST", "/api/v1/search/palettes/extract", "extract_image_palette"], readonly ["specificationFacets", "GET", "/api/v1/catalog/specifications/facets", "list_spec_facets"], readonly ["attributeValues", "GET", "/api/v1/catalog/attributes/{attributeId}/values", "list_attribute_values"], readonly ["applicableSpecs", "GET", "/api/v1/taxonomy/categories/{key}/specs", "get_applicable_specs"], readonly ["glossary", "GET", "/api/v1/glossary/{term}", "explain_material_term"], readonly ["brands", "GET", "/api/v1/brands", "list_brands"], readonly ["brand", "GET", "/api/v1/brands/{brandSlug}", "get_brand_profile"], readonly ["uploadSourceMaterial", "POST", "/api/v1/source-material", "upload_source_material"], readonly ["sourceMaterialBatch", "GET", "/api/v1/source-material/{batchId}", "get_source_material_batch"], readonly ["capabilities", "GET", "/api/v1/capabilities", "list_public_capabilities"], readonly ["createRun", "POST", "/api/v1/runs", "create_public_run"], readonly ["run", "GET", "/api/v1/runs/{runId}", "get_public_run"], readonly ["runSchedule", "GET", "/api/v1/runs/{runId}/schedule", "get_run_finish_schedule"], readonly ["streamRunEvents", "GET", "/api/v1/runs/{runId}/events", "list_public_run_events"], readonly ["resumeRun", "POST", "/api/v1/runs/{runId}/resume", "resume_public_run"], readonly ["cancelRun", "POST", "/api/v1/runs/{runId}/cancel", "cancel_public_run"], readonly ["varyScheme", "POST", "/api/v1/schemes/vary", "vary_schemes"], readonly ["createProject", "POST", "/api/v1/projects", "create_box_project"], readonly ["listProjects", "GET", "/api/v1/projects", "list_box_projects"], readonly ["getProject", "GET", "/api/v1/projects/{projectId}", "get_box_project"], readonly ["appendProjectEvent", "POST", "/api/v1/projects/{projectId}/events", "append_box_project_event"], readonly ["demandBatch", "POST", "/api/v1/demand/batch", "get_demand_batch"], readonly ["codeAdvisory", "POST", "/api/v1/codes/advisory", "resolveCodeAdvisory"], readonly ["codes.context", "GET", "/api/v1/codes/context", "list_code_context"], readonly ["codes.requirements", "GET", "/api/v1/codes/requirements", "list_code_requirements"]];
|
|
2
|
+
declare const OPERATION_DEFINITIONS: readonly [readonly ["search", "POST", "/api/v1/search", "search_materials_post"], readonly ["searchGet", "GET", "/api/v1/search", "search_materials_get"], readonly ["match", "POST", "/api/v1/search/match", "match_material"], readonly ["byColour", "GET", "/api/v1/search/colours", "search_by_colour"], readonly ["material", "GET", "/api/v1/materials/{variantId}", "get_material"], readonly ["siblings", "GET", "/api/v1/materials/{variantId}/siblings", "get_material_siblings"], readonly ["materialBlocks", "GET", "/api/v1/materials/{variantId}/blocks", "list_material_blocks"], readonly ["materialAssets", "GET", "/api/v1/materials/{variantId}/assets", "list_material_assets"], readonly ["schema", "GET", "/api/v1/schema/blocks", "list_schema_blocks"], readonly ["schema", "GET", "/api/v1/schema/attributes", "list_schema_attributes"], readonly ["schema", "GET", "/api/v1/schema/verticals", "list_schema_verticals"], readonly ["schema", "GET", "/api/v1/schema/product-types", "list_schema_product_types"], readonly ["schema", "GET", "/api/v1/schema/units", "list_schema_units"], readonly ["schema", "GET", "/api/v1/schema/value-dictionaries", "list_schema_value_dictionaries"], readonly ["schema", "GET", "/api/v1/schema/source-requirements", "list_schema_source_requirements"], readonly ["schema", "GET", "/api/v1/schema/product-type-templates", "list_schema_product_type_templates"], readonly ["schema", "GET", "/api/v1/schema/mappings", "list_schema_mappings"], readonly ["pairings", "GET", "/api/v1/materials/{variantId}/pairings", "get_material_pairings"], readonly ["materials", "POST", "/api/v1/materials/batch", "get_materials_batch_post"], readonly ["materialsCached", "GET", "/api/v1/materials/batch", "get_materials_batch_get"], readonly ["browse", "GET", "/api/v1/search/browse", "search_browse_by_colour"], readonly ["colourFacets", "GET", "/api/v1/colours/facets", "get_colour_facets"], readonly ["colours.profiles", "GET", "/api/v1/colours/profiles", "get_category_colour_profiles"], readonly ["colours.stats", "GET", "/api/v1/colours/stats", "get_colour_stats"], readonly ["resolveColour", "GET", "/api/v1/search/colours/resolve", "resolve_colour_name"], readonly ["suggestColour", "GET", "/api/v1/search/colours/suggest", "suggest_colour_names"], readonly ["suggest", "GET", "/api/v1/search/suggest", "search_suggest"], readonly ["resolveAttribute", "GET", "/api/v1/catalog/attributes/resolve", "catalog_resolve_attribute"], readonly ["resolveAttributeValue", "GET", "/api/v1/catalog/attributes/{attributeId}/values/resolve", "catalog_resolve_value"], readonly ["searchVocabulary", "GET", "/api/v1/catalog/vocabulary/search", "catalog_vocabulary_search"], readonly ["bySpec", "GET", "/api/v1/search/specifications", "catalog_by_spec"], readonly ["taxonomy", "GET", "/api/v1/taxonomy/categories", "get_category_taxonomy"], readonly ["whoami", "GET", "/api/v1/whoami", "whoami"], readonly ["coordinate", "POST", "/api/v1/pairings/coordinate", "coordinate_materials"], readonly ["demand", "GET", "/api/v1/demand", "get_material_demand"], readonly ["colours.palette", "POST", "/api/v1/search/palettes", "search_by_palette"], readonly ["extractPalette", "POST", "/api/v1/search/palettes/extract", "extract_image_palette"], readonly ["colours.gradient", "POST", "/api/v1/search/gradients", "search_by_gradient"], readonly ["specificationFacets", "GET", "/api/v1/catalog/specifications/facets", "list_spec_facets"], readonly ["attributeValues", "GET", "/api/v1/catalog/attributes/{attributeId}/values", "list_attribute_values"], readonly ["applicableSpecs", "GET", "/api/v1/taxonomy/categories/{key}/specs", "get_applicable_specs"], readonly ["glossary", "GET", "/api/v1/glossary/{term}", "explain_material_term"], readonly ["brands", "GET", "/api/v1/brands", "list_brands"], readonly ["brand", "GET", "/api/v1/brands/{brandSlug}", "get_brand_profile"], readonly ["uploadSourceMaterial", "POST", "/api/v1/source-material", "upload_source_material"], readonly ["sourceMaterialBatch", "GET", "/api/v1/source-material/{batchId}", "get_source_material_batch"], readonly ["capabilities", "GET", "/api/v1/capabilities", "list_public_capabilities"], readonly ["createRun", "POST", "/api/v1/runs", "create_public_run"], readonly ["run", "GET", "/api/v1/runs/{runId}", "get_public_run"], readonly ["runSchedule", "GET", "/api/v1/runs/{runId}/schedule", "get_run_finish_schedule"], readonly ["streamRunEvents", "GET", "/api/v1/runs/{runId}/events", "list_public_run_events"], readonly ["resumeRun", "POST", "/api/v1/runs/{runId}/resume", "resume_public_run"], readonly ["cancelRun", "POST", "/api/v1/runs/{runId}/cancel", "cancel_public_run"], readonly ["varyScheme", "POST", "/api/v1/schemes/vary", "vary_schemes"], readonly ["createProject", "POST", "/api/v1/projects", "create_box_project"], readonly ["listProjects", "GET", "/api/v1/projects", "list_box_projects"], readonly ["getProject", "GET", "/api/v1/projects/{projectId}", "get_box_project"], readonly ["appendProjectEvent", "POST", "/api/v1/projects/{projectId}/events", "append_box_project_event"], readonly ["demandBatch", "POST", "/api/v1/demand/batch", "get_demand_batch"], readonly ["codeAdvisory", "POST", "/api/v1/codes/advisory", "resolveCodeAdvisory"], readonly ["codes.context", "GET", "/api/v1/codes/context", "list_code_context"], readonly ["codes.requirements", "GET", "/api/v1/codes/requirements", "list_code_requirements"]];
|
|
3
3
|
export type SdkOperationId = (typeof OPERATION_DEFINITIONS)[number][3];
|
|
4
4
|
export type SdkOperation = Readonly<{
|
|
5
5
|
clientMethod: (typeof OPERATION_DEFINITIONS)[number][0];
|
package/operations.js
CHANGED
|
@@ -26,6 +26,7 @@ const OPERATION_DEFINITIONS = [
|
|
|
26
26
|
["colours.stats", "GET", "/api/v1/colours/stats", "get_colour_stats"],
|
|
27
27
|
["resolveColour", "GET", "/api/v1/search/colours/resolve", "resolve_colour_name"],
|
|
28
28
|
["suggestColour", "GET", "/api/v1/search/colours/suggest", "suggest_colour_names"],
|
|
29
|
+
["suggest", "GET", "/api/v1/search/suggest", "search_suggest"],
|
|
29
30
|
["resolveAttribute", "GET", "/api/v1/catalog/attributes/resolve", "catalog_resolve_attribute"],
|
|
30
31
|
[
|
|
31
32
|
"resolveAttributeValue",
|
|
@@ -39,7 +40,9 @@ const OPERATION_DEFINITIONS = [
|
|
|
39
40
|
["whoami", "GET", "/api/v1/whoami", "whoami"],
|
|
40
41
|
["coordinate", "POST", "/api/v1/pairings/coordinate", "coordinate_materials"],
|
|
41
42
|
["demand", "GET", "/api/v1/demand", "get_material_demand"],
|
|
43
|
+
["colours.palette", "POST", "/api/v1/search/palettes", "search_by_palette"],
|
|
42
44
|
["extractPalette", "POST", "/api/v1/search/palettes/extract", "extract_image_palette"],
|
|
45
|
+
["colours.gradient", "POST", "/api/v1/search/gradients", "search_by_gradient"],
|
|
43
46
|
["specificationFacets", "GET", "/api/v1/catalog/specifications/facets", "list_spec_facets"],
|
|
44
47
|
[
|
|
45
48
|
"attributeValues",
|
package/package.json
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types for MULTI-COLOUR search — `POST /api/v1/search/palettes` and
|
|
3
|
+
* `POST /api/v1/search/gradients`.
|
|
4
|
+
*
|
|
5
|
+
* Kept out of `types.ts` for the same reason `colour-aggregate-types.ts` and
|
|
6
|
+
* `codes-types.ts` are: the shared mirror is at its source-size budget. Same
|
|
7
|
+
* tolerant-parsing contract as the rest of the mirror — response enums are
|
|
8
|
+
* widened so a new server value still type-checks.
|
|
9
|
+
*
|
|
10
|
+
* The MATCH SEMANTICS these types carry are the server's, stated on
|
|
11
|
+
* {@link SearchPaletteInput.logic} and {@link GradientMatch.coverage} rather
|
|
12
|
+
* than left to the endpoint docs.
|
|
13
|
+
*/
|
|
14
|
+
import type { OklabTriple, OpenEnum } from "./types.js";
|
|
15
|
+
/** How the per-colour scores are combined across a palette. */
|
|
16
|
+
export type PaletteLogic = OpenEnum<"all" | "any">;
|
|
17
|
+
/** One queried palette colour — exactly one of `hex` or `oklab`. */
|
|
18
|
+
export interface PaletteQueryColour {
|
|
19
|
+
hex?: string;
|
|
20
|
+
oklab?: OklabTriple;
|
|
21
|
+
/**
|
|
22
|
+
* Share of the surface this colour should occupy, in (0,1]. Stating it
|
|
23
|
+
* CHANGES the ranking: a colour asked for at 0.6 rewards candidates whose own
|
|
24
|
+
* swatch sits near 0.6, not merely candidates that contain the colour.
|
|
25
|
+
*/
|
|
26
|
+
proportion?: number;
|
|
27
|
+
/** Relative importance; defaults to 1 and is normalised across the query. */
|
|
28
|
+
weight?: number;
|
|
29
|
+
}
|
|
30
|
+
/** One gradient stop — exactly one of `hex` or `oklab`. Order is meaningful. */
|
|
31
|
+
export interface GradientStop {
|
|
32
|
+
hex?: string;
|
|
33
|
+
oklab?: OklabTriple;
|
|
34
|
+
}
|
|
35
|
+
export interface SearchPaletteInput {
|
|
36
|
+
/** The palette to match, 1–8 colours. */
|
|
37
|
+
colours: PaletteQueryColour[];
|
|
38
|
+
/**
|
|
39
|
+
* `all` (the default) requires every queried colour to clear a per-colour
|
|
40
|
+
* eligibility floor and scores the weighted geometric mean; `any` is a
|
|
41
|
+
* noisy-OR and never rejects a candidate for missing one colour.
|
|
42
|
+
*/
|
|
43
|
+
logic?: PaletteLogic;
|
|
44
|
+
/** Results to return; defaults to 24, capped at 48. */
|
|
45
|
+
limit?: number;
|
|
46
|
+
}
|
|
47
|
+
export interface SearchGradientInput {
|
|
48
|
+
/** The ordered colour transition, 2–6 stops. */
|
|
49
|
+
stops: GradientStop[];
|
|
50
|
+
/** Results to return; defaults to 24, capped at 48. */
|
|
51
|
+
limit?: number;
|
|
52
|
+
}
|
|
53
|
+
/** One swatch of a candidate's colorscope-extracted palette. */
|
|
54
|
+
export interface CandidateSwatch {
|
|
55
|
+
hex: string;
|
|
56
|
+
proportion: number;
|
|
57
|
+
name: string | null;
|
|
58
|
+
}
|
|
59
|
+
/** Why a queried colour (or gradient sample) scored what it scored. */
|
|
60
|
+
export interface ColourMatchReceipt {
|
|
61
|
+
queryHex: string;
|
|
62
|
+
/** `colourScore × proportionScore` for the best-matching swatch. */
|
|
63
|
+
score: number;
|
|
64
|
+
/** OKLab closeness alone, in [0,1]. */
|
|
65
|
+
colourScore: number;
|
|
66
|
+
/** Proportion agreement alone, in [0,1]. */
|
|
67
|
+
proportionScore: number;
|
|
68
|
+
/** The swatch that produced the score, or null when nothing matched. */
|
|
69
|
+
matched: CandidateSwatch | null;
|
|
70
|
+
}
|
|
71
|
+
/** Identity and evidence shared by both multi-colour match shapes. */
|
|
72
|
+
export interface MultiColourMatchBase {
|
|
73
|
+
variantId: string;
|
|
74
|
+
brand: string;
|
|
75
|
+
product: string;
|
|
76
|
+
brandSlug: string | null;
|
|
77
|
+
productId: string | null;
|
|
78
|
+
color: string | null;
|
|
79
|
+
variantName: string | null;
|
|
80
|
+
imageUrl: string | null;
|
|
81
|
+
/** MB has retired or hidden this SKU. */
|
|
82
|
+
retired: boolean;
|
|
83
|
+
/** The candidate's full extracted palette — the evidence scored against. */
|
|
84
|
+
palette: CandidateSwatch[];
|
|
85
|
+
/** The aggregate match score in [0,1]; higher is closer. */
|
|
86
|
+
score: number;
|
|
87
|
+
}
|
|
88
|
+
export interface PaletteMatch extends MultiColourMatchBase {
|
|
89
|
+
/** One receipt per queried colour, in query order. */
|
|
90
|
+
colours: ColourMatchReceipt[];
|
|
91
|
+
}
|
|
92
|
+
export interface GradientMatch extends MultiColourMatchBase {
|
|
93
|
+
/**
|
|
94
|
+
* Distinct matched thirds of the path over 3. A material owning only one end
|
|
95
|
+
* of the transition caps out around 0.33 — coverage is what separates a real
|
|
96
|
+
* gradient match from a good match on a single stop.
|
|
97
|
+
*/
|
|
98
|
+
coverage: number;
|
|
99
|
+
/** One receipt per sampled point along the path, in path order. */
|
|
100
|
+
samples: ColourMatchReceipt[];
|
|
101
|
+
}
|
|
102
|
+
export interface SearchPaletteResponse {
|
|
103
|
+
query: {
|
|
104
|
+
colours: Array<{
|
|
105
|
+
hex: string;
|
|
106
|
+
proportion?: number;
|
|
107
|
+
weight?: number;
|
|
108
|
+
}>;
|
|
109
|
+
logic: PaletteLogic;
|
|
110
|
+
};
|
|
111
|
+
count: number;
|
|
112
|
+
matches: PaletteMatch[];
|
|
113
|
+
}
|
|
114
|
+
export interface SearchGradientResponse {
|
|
115
|
+
query: {
|
|
116
|
+
stops: string[];
|
|
117
|
+
/** The sampled path points the search actually ran against. */
|
|
118
|
+
samples: string[];
|
|
119
|
+
};
|
|
120
|
+
count: number;
|
|
121
|
+
matches: GradientMatch[];
|
|
122
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity typeahead (MG-654) — the hand-mirrored wire types for
|
|
3
|
+
* `GET /api/v1/search/suggest`, plus the one query-shaping rule the endpoint
|
|
4
|
+
* imposes.
|
|
5
|
+
*
|
|
6
|
+
* Split out of `types.ts` for the repository's source-file size budget, the same
|
|
7
|
+
* reason `catalog-discovery-types.ts` sits beside it; both are re-exported from
|
|
8
|
+
* `types.ts`, so importers see no difference.
|
|
9
|
+
*/
|
|
10
|
+
/** Which kind of entity a suggestion is — and therefore what `refId` means. */
|
|
11
|
+
export type SuggestKind = "product" | "brand" | "category";
|
|
12
|
+
/** How the query met the suggestion's label. Strongest first. */
|
|
13
|
+
export type SuggestMatch = "prefix" | "word" | "contains" | "fuzzy";
|
|
14
|
+
/** One ranked typeahead suggestion, carrying the identity it resolves to. */
|
|
15
|
+
export interface Suggestion {
|
|
16
|
+
kind: SuggestKind;
|
|
17
|
+
/** The entity's name as written, for display. */
|
|
18
|
+
label: string;
|
|
19
|
+
/**
|
|
20
|
+
* The resolvable identity: a product id, a brand slug, or a product-type key.
|
|
21
|
+
* Selecting a suggestion should navigate to this, not re-search `label`.
|
|
22
|
+
*/
|
|
23
|
+
refId: string;
|
|
24
|
+
match: SuggestMatch;
|
|
25
|
+
brandSlug: string | null;
|
|
26
|
+
brandName: string | null;
|
|
27
|
+
categoryKey: string | null;
|
|
28
|
+
/** A representative variant, so a client can open a material record directly. */
|
|
29
|
+
variantId: string | null;
|
|
30
|
+
variantCount: number;
|
|
31
|
+
}
|
|
32
|
+
/** The suggest response: the query as interpreted, plus the ranked suggestions. */
|
|
33
|
+
export interface SuggestResponse {
|
|
34
|
+
/** The normalised query the corpus was matched against. */
|
|
35
|
+
query: string;
|
|
36
|
+
/** Ranked best-match-first. Empty when nothing came close — a miss, not an error. */
|
|
37
|
+
suggestions: Suggestion[];
|
|
38
|
+
}
|
|
39
|
+
/** Options for {@link MaterialGraphClient.suggest}. */
|
|
40
|
+
export interface SuggestOptions {
|
|
41
|
+
/** How many suggestions to return (1–20, default 8). */
|
|
42
|
+
limit?: number;
|
|
43
|
+
/** Restrict to these kinds. Omit for all of them. */
|
|
44
|
+
kind?: SuggestKind | SuggestKind[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Shape the query string for a suggest call.
|
|
48
|
+
*
|
|
49
|
+
* The only non-obvious rule: `kind` is a COMMA LIST on the wire (`product,brand`),
|
|
50
|
+
* which is the house convention for multi-value v1 params, so an array is joined
|
|
51
|
+
* here rather than being sent as repeated `?kind=` pairs the route would ignore.
|
|
52
|
+
* Undefined members are dropped by the transport.
|
|
53
|
+
*/
|
|
54
|
+
export declare function suggestQuery(q: string, options?: SuggestOptions): {
|
|
55
|
+
q: string;
|
|
56
|
+
limit?: number;
|
|
57
|
+
kind?: string;
|
|
58
|
+
};
|
package/suggest-types.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity typeahead (MG-654) — the hand-mirrored wire types for
|
|
3
|
+
* `GET /api/v1/search/suggest`, plus the one query-shaping rule the endpoint
|
|
4
|
+
* imposes.
|
|
5
|
+
*
|
|
6
|
+
* Split out of `types.ts` for the repository's source-file size budget, the same
|
|
7
|
+
* reason `catalog-discovery-types.ts` sits beside it; both are re-exported from
|
|
8
|
+
* `types.ts`, so importers see no difference.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Shape the query string for a suggest call.
|
|
12
|
+
*
|
|
13
|
+
* The only non-obvious rule: `kind` is a COMMA LIST on the wire (`product,brand`),
|
|
14
|
+
* which is the house convention for multi-value v1 params, so an array is joined
|
|
15
|
+
* here rather than being sent as repeated `?kind=` pairs the route would ignore.
|
|
16
|
+
* Undefined members are dropped by the transport.
|
|
17
|
+
*/
|
|
18
|
+
export function suggestQuery(q, options) {
|
|
19
|
+
const kind = Array.isArray(options?.kind) ? options.kind.join(",") : options?.kind;
|
|
20
|
+
return { q, limit: options?.limit, kind };
|
|
21
|
+
}
|
package/types.d.ts
CHANGED
|
@@ -363,6 +363,7 @@ export interface ResolveAttributeResponse {
|
|
|
363
363
|
candidates: AttributeCandidate[];
|
|
364
364
|
}
|
|
365
365
|
export type * from "./catalog-discovery-types.js";
|
|
366
|
+
export type * from "./suggest-types.js";
|
|
366
367
|
export type * from "./by-spec-types.js";
|
|
367
368
|
export interface WhoamiResponse {
|
|
368
369
|
principal: string;
|