@instruments/materialgraph 0.3.0 → 0.4.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.
@@ -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;
@@ -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, BrowseParams, BrowseResponse, 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, TaxonomyResponse, VocabularySearchOptions, VocabularySearchResponse, WhoamiResponse } from "./types.js";
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,9 +53,7 @@ export class MaterialGraphClient {
51
53
  fetch: options.fetch ?? globalThis.fetch,
52
54
  });
53
55
  this.baseUrl = this.#transport.baseUrl;
54
- const browse = (params, signal) => this.#browsePage(params, signal);
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),
@@ -173,25 +173,6 @@ export class MaterialGraphClient {
173
173
  idKind: options?.idKind ?? "variantId",
174
174
  }, signal);
175
175
  }
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
176
  // ── Colour helpers ───────────────────────────────────────────────────────────
196
177
  /** The three canonical colour vocabularies with live counts (GET `/api/v1/colours/facets`). */
197
178
  colourFacets(signal) {
@@ -205,6 +186,20 @@ export class MaterialGraphClient {
205
186
  suggestColour(q, options, signal) {
206
187
  return this.#get("suggest_colour_names", { q, limit: options?.limit }, signal);
207
188
  }
189
+ /**
190
+ * Entity typeahead across products, brands and categories
191
+ * (GET `/api/v1/search/suggest`). Built for per-keystroke use: one indexed
192
+ * lookup, no embedding call and no model. Every suggestion carries the
193
+ * `refId` it resolves to — a product id, a brand slug, or a product-type key
194
+ * — so selecting one should navigate to that entity rather than re-running
195
+ * `label` as a search.
196
+ *
197
+ * @example
198
+ * const { suggestions } = await client.suggest("carr", { kind: "product" });
199
+ */
200
+ suggest(q, options, signal) {
201
+ return this.#get("search_suggest", suggestQuery(q, options), signal);
202
+ }
208
203
  // ── Catalog ──────────────────────────────────────────────────────────────────
209
204
  /** Resolve a free-text spec phrase to canonical attribute ids (GET `/api/v1/catalog/attributes/resolve`). */
210
205
  resolveAttribute(q, options, signal) {
@@ -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 ["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"]];
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materialgraph",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -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
+ };
@@ -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;