@instruments/materialgraph 0.7.1 → 0.8.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.
@@ -245,10 +245,28 @@ export interface PaletteDirection {
245
245
  colours: BriefColourCue[];
246
246
  sourceRefs: CompositionSourceRef[];
247
247
  }
248
+ /**
249
+ * WHERE a slot's `category` came from (MG-954, extended MG-978): `resolved` —
250
+ * derived from the slot's own words against the catalog's category vectors;
251
+ * `resolved-coverage` — the words ended in a close race and the runner-up won it
252
+ * because the text winner held no value for one of the slot's required
253
+ * attributes; `model` — the interpreter's own draw stood because nothing
254
+ * resolved; `none` — neither produced one, so the slot has NO category and
255
+ * retrieval cannot gate it; `corrected` — a designer set it with a
256
+ * `set-slot-category` correction. Widened with {@link OpenEnum} so a newer
257
+ * server term still type-checks.
258
+ */
259
+ export type SlotCategorySource = OpenEnum<"resolved" | "resolved-coverage" | "model" | "none" | "corrected">;
248
260
  /**
249
261
  * Suspend payload for the compose `review-programme` step: the FULL programme
250
262
  * picture — every space and slot tagged with its provenance — so the card can
251
263
  * show the whole thing and highlight what was inferred.
264
+ *
265
+ * Each slot also discloses the catalog `category` it will shop in and the
266
+ * `categorySource` that won it (MG-954). Both are OPTIONAL and additive: an
267
+ * ABSENT `category` means the slot has none — not that it has an empty one — and
268
+ * a slot in that state cannot be gated at retrieval, so it is skipped there with
269
+ * a counted shortfall unless a `set-slot-category` correction supplies one.
252
270
  */
253
271
  export interface ReviewProgrammeSuspendPayload {
254
272
  spaces: Array<{
@@ -262,12 +280,20 @@ export interface ReviewProgrammeSuspendPayload {
262
280
  name: string;
263
281
  spaceId?: string;
264
282
  element?: SlotElement;
283
+ category?: string;
284
+ categorySource?: SlotCategorySource;
265
285
  provenance: ProgrammeProvenance;
266
286
  }>;
267
287
  }
268
288
  /**
269
289
  * Resume grammar for the `review-programme` step: drop a slot, drop a space
270
- * (cascade-dropping its slots), or rename a space.
290
+ * (cascade-dropping its slots), rename a space, or set a slot's category.
291
+ *
292
+ * `set-slot-category` (MG-954) is an action INSIDE this same corrections array;
293
+ * its `category` must be one of the server's catalog category GROUP names (the
294
+ * same vocabulary the payload's `category` is drawn from) or the resume is
295
+ * refused. An empty `corrections` array stays legal however the step came to
296
+ * suspend — the suspension discloses, it does not coerce.
271
297
  */
272
298
  export interface ReviewProgrammeResumeData {
273
299
  corrections: Array<{
@@ -280,6 +306,10 @@ export interface ReviewProgrammeResumeData {
280
306
  action: "rename-space";
281
307
  spaceId: string;
282
308
  name: string;
309
+ } | {
310
+ action: "set-slot-category";
311
+ slotId: string;
312
+ category: string;
283
313
  }>;
284
314
  }
285
315
  /**
package/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export type * from "./run-events.js";
15
15
  export { MaterialGraphError } from "./errors.js";
16
16
  export type { MaterialGraphErrorInit, MaterialGraphErrorKind, MaterialGraphRequestMetadata, ProblemDetail, RateLimit, SanitizedErrorValue, SanitizedIssue, SimpleErrorBody, } from "./errors.js";
17
17
  export type * from "./types.js";
18
+ export type * from "./by-colour-types.js";
18
19
  export { MATCH_IMAGE_TYPES, MATCH_MATERIAL_FUNCTIONS, MATCH_STREAM_EVENT_TYPES, MATCH_SURFACE_STATUSES, MATCH_WORKFLOW_STEPS, } from "./match-stream-types.js";
19
20
  export type * from "./match-stream-types.js";
20
21
  export { CodesClient } from "./codes-client.js";
@@ -23,6 +24,7 @@ export { ColoursClient } from "./colours-client.js";
23
24
  export type { ColoursTransport } from "./colours-client.js";
24
25
  export type * from "./colour-aggregate-types.js";
25
26
  export type * from "./palette-search-types.js";
27
+ export type * from "./association-types.js";
26
28
  export type * from "./brand-types.js";
27
29
  export type * from "./material-asset-types.js";
28
30
  export type * from "./material-block-types.js";
@@ -67,6 +67,14 @@ export interface MaterialTile {
67
67
  category: string | null;
68
68
  colour: MaterialColour;
69
69
  availability: MaterialAvailability;
70
+ /**
71
+ * The accepted Colour Name attribute — the colourway's true name where
72
+ * `variantName` collapsed to the product's (MG-940: 83.7% of visible
73
+ * colourway families name every variant after the product, e.g. all 10 CF
74
+ * Stinson "Drift" colourways share `variantName: "Drift"`). Absent means
75
+ * the attribute is absent — never render a missing key as "unnamed".
76
+ */
77
+ colourName?: string;
70
78
  }
71
79
  /**
72
80
  * A canonical attribute/term pair carried on a detail record.
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 ["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"]];
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 ["associatedFinishes", "GET", "/api/v1/associated/finishes", "list_associated_finishes"], readonly ["associatedProducts", "GET", "/api/v1/associated/products", "list_associated_products"], 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 ["resolve", "POST", "/api/v1/resolve", "resolve_phrases"], 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 ["listRuns", "GET", "/api/v1/runs", "list_public_runs"], 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];
@@ -10,6 +10,12 @@ export type SdkOperation = Readonly<{
10
10
  export declare const SDK_OPERATIONS: readonly SdkOperation[];
11
11
  /** Look up a registered operation by its OpenAPI `operationId`. */
12
12
  export declare function findSdkOperation(operationId: SdkOperationId): SdkOperation | undefined;
13
+ /**
14
+ * Look up a registered operation, or throw. The lookup every caller that holds
15
+ * a literal `operationId` wants — lived as a private copy in `client.ts` and
16
+ * `projects.ts` before the association lens would have made it a third.
17
+ */
18
+ export declare function resolveOperation(operationId: SdkOperationId): SdkOperation;
13
19
  /** Substitute `{param}` path placeholders with percent-encoded values. */
14
20
  export declare function operationPath(sdkOperation: SdkOperation, pathParams?: Readonly<Record<string, string>>): string;
15
21
  export {};
package/operations.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { MaterialGraphError } from "./errors.js";
1
2
  export const MATERIALGRAPH_API_VERSION = "0.7.0";
2
3
  const OPERATION_DEFINITIONS = [
3
4
  ["search", "POST", "/api/v1/search", "search_materials_post"],
@@ -18,6 +19,8 @@ const OPERATION_DEFINITIONS = [
18
19
  ["schema", "GET", "/api/v1/schema/product-type-templates", "list_schema_product_type_templates"],
19
20
  ["schema", "GET", "/api/v1/schema/mappings", "list_schema_mappings"],
20
21
  ["pairings", "GET", "/api/v1/materials/{variantId}/pairings", "get_material_pairings"],
22
+ ["associatedFinishes", "GET", "/api/v1/associated/finishes", "list_associated_finishes"],
23
+ ["associatedProducts", "GET", "/api/v1/associated/products", "list_associated_products"],
21
24
  ["materials", "POST", "/api/v1/materials/batch", "get_materials_batch_post"],
22
25
  ["materialsCached", "GET", "/api/v1/materials/batch", "get_materials_batch_get"],
23
26
  ["browse", "GET", "/api/v1/search/browse", "search_browse_by_colour"],
@@ -35,6 +38,7 @@ const OPERATION_DEFINITIONS = [
35
38
  "catalog_resolve_value",
36
39
  ],
37
40
  ["searchVocabulary", "GET", "/api/v1/catalog/vocabulary/search", "catalog_vocabulary_search"],
41
+ ["resolve", "POST", "/api/v1/resolve", "resolve_phrases"],
38
42
  ["bySpec", "GET", "/api/v1/search/specifications", "catalog_by_spec"],
39
43
  ["taxonomy", "GET", "/api/v1/taxonomy/categories", "get_category_taxonomy"],
40
44
  ["whoami", "GET", "/api/v1/whoami", "whoami"],
@@ -58,6 +62,7 @@ const OPERATION_DEFINITIONS = [
58
62
  ["sourceMaterialBatch", "GET", "/api/v1/source-material/{batchId}", "get_source_material_batch"],
59
63
  ["capabilities", "GET", "/api/v1/capabilities", "list_public_capabilities"],
60
64
  ["createRun", "POST", "/api/v1/runs", "create_public_run"],
65
+ ["listRuns", "GET", "/api/v1/runs", "list_public_runs"],
61
66
  ["run", "GET", "/api/v1/runs/{runId}", "get_public_run"],
62
67
  ["runSchedule", "GET", "/api/v1/runs/{runId}/schedule", "get_run_finish_schedule"],
63
68
  ["streamRunEvents", "GET", "/api/v1/runs/{runId}/events", "list_public_run_events"],
@@ -78,6 +83,17 @@ export const SDK_OPERATIONS = Object.freeze(OPERATION_DEFINITIONS.map(([clientMe
78
83
  export function findSdkOperation(operationId) {
79
84
  return SDK_OPERATIONS.find((candidate) => candidate.operationId === operationId);
80
85
  }
86
+ /**
87
+ * Look up a registered operation, or throw. The lookup every caller that holds
88
+ * a literal `operationId` wants — lived as a private copy in `client.ts` and
89
+ * `projects.ts` before the association lens would have made it a third.
90
+ */
91
+ export function resolveOperation(operationId) {
92
+ const match = findSdkOperation(operationId);
93
+ if (!match)
94
+ throw MaterialGraphError.unexpected(`Unknown SDK operation: ${operationId}`);
95
+ return match;
96
+ }
81
97
  /** Substitute `{param}` path placeholders with percent-encoded values. */
82
98
  export function operationPath(sdkOperation, pathParams = {}) {
83
99
  return Object.entries(pathParams).reduce((path, [name, value]) => path.replace(`{${name}}`, encodeURIComponent(value)), sdkOperation.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materialgraph",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The VOCABULARY lens — words in, canonical tokens out. The two requests that
3
+ * answer "what is this phrase CALLED here?" rather than "what materials match
4
+ * it?", lifted out of the client facade so the facade stays a facade and the
5
+ * hover documentation has room to say what a caller actually needs.
6
+ *
7
+ * Standalone functions over the shared {@link HttpTransport}, not a namespace:
8
+ * `mg.resolve()` and `mg.searchVocabulary()` are flat methods on the public API
9
+ * and stay that way — `MaterialGraphClient` keeps a one-line delegating method
10
+ * for each, so nothing about the published surface moves. Same shape as
11
+ * `associations-client.ts`; auth, rate-limit accounting and error shaping stay
12
+ * in the one transport.
13
+ *
14
+ * ROUTING INTO AND OUT OF THIS LENS. It is the lens you reach for INSTEAD OF
15
+ * keeping a hand-written table of your users' words mapped onto this API's
16
+ * tokens: that table is a copy of a registry you do not own, and it drifts
17
+ * silently the moment the registry moves.
18
+ * - The whole phrase, any vocabulary → {@link resolvePhrasesRequest}. Batched,
19
+ * deterministic, and it says so out loud when a word means nothing here.
20
+ * - You already know it is an attribute, or a value under a known attribute →
21
+ * `mg.resolveAttribute()` / `mg.resolveAttributeValue()`.
22
+ * - You want to EXPLAIN a token you already hold → `mg.glossary(term)`.
23
+ * - You want to complete a name as someone types → `mg.suggest()`.
24
+ * - You want MATERIALS → `mg.search()`. This lens never returns one.
25
+ */
26
+ import type { HttpTransport } from "./http-transport.js";
27
+ import type { ResolveResponse } from "./resolve-types.js";
28
+ import type { VocabularySearchOptions, VocabularySearchResponse } from "./types.js";
29
+ /**
30
+ * THE QUESTION: "I have words — what do they mean in YOUR vocabulary?" Send up
31
+ * to 50 phrases as your users typed them; each comes back with every canonical
32
+ * token MaterialGraph knows it as — brand, exact paint, colour family, category,
33
+ * attribute, value — best first, in request order. Backs `mg.resolve()`.
34
+ *
35
+ * A phrase can legitimately mean several things at once ("sage" is two vendor
36
+ * paints AND a colour family), so each entry carries a LIST of readings rather
37
+ * than a winner, and `kind` says which vocabulary each came from.
38
+ *
39
+ * Three rules worth reading before you spend a result:
40
+ * - **`unresolved` is stated, not inferred.** A phrase this vocabulary has no
41
+ * word for returns `resolved: []` AND `unresolved: true`, so you can tell an
42
+ * absent word from a dropped one. Absent is not false: it is a statement about
43
+ * this vocabulary, never about the material world.
44
+ * - **Ids are opaque.** Hand them back verbatim (see `./identifiers.ts`);
45
+ * `colour` and `attribute_value` ids arrive namespaced by their dictionary,
46
+ * because a term id is unique only within one.
47
+ * - **`rung` says how it matched** — `exact`, `alias`, or `lexical`. There is no
48
+ * semantic rung: every tier that would cost an embedding round-trip is off,
49
+ * which is what keeps this millisecond-class. A phrase only a semantic match
50
+ * could have caught comes back unresolved rather than slow.
51
+ *
52
+ * Deterministic — the same batch resolves to the same bytes every time. Instead
53
+ * use `mg.search()` when you want materials rather than words, `mg.suggest()` to
54
+ * complete a name as someone types, and `mg.glossary()` to EXPLAIN a token you
55
+ * have already resolved.
56
+ *
57
+ * @param phrases 1-50 phrases, each 1-200 characters. Sent as-is; each is
58
+ * resolved independently, so one meaningless phrase never spoils the batch.
59
+ */
60
+ export declare function resolvePhrasesRequest(transport: HttpTransport, phrases: string[], signal?: AbortSignal): Promise<ResolveResponse>;
61
+ /**
62
+ * THE QUESTION: "is 'FloorScore' an attribute or a value, and what is it called
63
+ * here?" The catch-all for ONE phrase when you do not know which half of an
64
+ * `attribute:value` pair it belongs to. Backs `mg.searchVocabulary()`.
65
+ *
66
+ * Instead use `mg.resolveAttribute()` / `mg.resolveAttributeValue()` once you do
67
+ * know, {@link resolvePhrasesRequest} when you have SEVERAL phrases or the
68
+ * phrase might name a brand, paint, colour or category rather than a spec, and
69
+ * `mg.glossary()` to EXPLAIN a term rather than find it.
70
+ */
71
+ export declare function searchVocabularyRequest(transport: HttpTransport, query: string, options?: VocabularySearchOptions, signal?: AbortSignal): Promise<VocabularySearchResponse>;
@@ -0,0 +1,51 @@
1
+ import { resolveOperation } from "./operations.js";
2
+ /**
3
+ * THE QUESTION: "I have words — what do they mean in YOUR vocabulary?" Send up
4
+ * to 50 phrases as your users typed them; each comes back with every canonical
5
+ * token MaterialGraph knows it as — brand, exact paint, colour family, category,
6
+ * attribute, value — best first, in request order. Backs `mg.resolve()`.
7
+ *
8
+ * A phrase can legitimately mean several things at once ("sage" is two vendor
9
+ * paints AND a colour family), so each entry carries a LIST of readings rather
10
+ * than a winner, and `kind` says which vocabulary each came from.
11
+ *
12
+ * Three rules worth reading before you spend a result:
13
+ * - **`unresolved` is stated, not inferred.** A phrase this vocabulary has no
14
+ * word for returns `resolved: []` AND `unresolved: true`, so you can tell an
15
+ * absent word from a dropped one. Absent is not false: it is a statement about
16
+ * this vocabulary, never about the material world.
17
+ * - **Ids are opaque.** Hand them back verbatim (see `./identifiers.ts`);
18
+ * `colour` and `attribute_value` ids arrive namespaced by their dictionary,
19
+ * because a term id is unique only within one.
20
+ * - **`rung` says how it matched** — `exact`, `alias`, or `lexical`. There is no
21
+ * semantic rung: every tier that would cost an embedding round-trip is off,
22
+ * which is what keeps this millisecond-class. A phrase only a semantic match
23
+ * could have caught comes back unresolved rather than slow.
24
+ *
25
+ * Deterministic — the same batch resolves to the same bytes every time. Instead
26
+ * use `mg.search()` when you want materials rather than words, `mg.suggest()` to
27
+ * complete a name as someone types, and `mg.glossary()` to EXPLAIN a token you
28
+ * have already resolved.
29
+ *
30
+ * @param phrases 1-50 phrases, each 1-200 characters. Sent as-is; each is
31
+ * resolved independently, so one meaningless phrase never spoils the batch.
32
+ */
33
+ export function resolvePhrasesRequest(transport, phrases, signal) {
34
+ return transport.requestJson(resolveOperation("resolve_phrases"), {
35
+ body: { phrases },
36
+ signal,
37
+ });
38
+ }
39
+ /**
40
+ * THE QUESTION: "is 'FloorScore' an attribute or a value, and what is it called
41
+ * here?" The catch-all for ONE phrase when you do not know which half of an
42
+ * `attribute:value` pair it belongs to. Backs `mg.searchVocabulary()`.
43
+ *
44
+ * Instead use `mg.resolveAttribute()` / `mg.resolveAttributeValue()` once you do
45
+ * know, {@link resolvePhrasesRequest} when you have SEVERAL phrases or the
46
+ * phrase might name a brand, paint, colour or category rather than a spec, and
47
+ * `mg.glossary()` to EXPLAIN a term rather than find it.
48
+ */
49
+ export function searchVocabularyRequest(transport, query, options, signal) {
50
+ return transport.requestJson(resolveOperation("catalog_vocabulary_search"), { query: { q: query, limit: options?.limit, kind: options?.kind }, signal });
51
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The vocabulary lens (MG-913) — the hand-mirrored wire types for
3
+ * `POST /api/v1/resolve`.
4
+ *
5
+ * Split out of `types.ts` for the repository's source-file size budget, the same
6
+ * reason `suggest-types.ts` sits beside it; both are re-exported from `types.ts`,
7
+ * so importers see no difference.
8
+ *
9
+ * THE LENS: it turns WORDS into vocabulary, never into materials.
10
+ * `mg.search()` answers "I want materials" and `mg.answer` questions want prose;
11
+ * this answers "what do these words mean in your vocabulary?" — the question you
12
+ * ask BEFORE you spend a token on a filter, a facet or a brand slug. It is the
13
+ * reason you should not keep a hand-written table mapping your users' words onto
14
+ * this API's tokens: that table is a copy of a registry you do not own, and it
15
+ * drifts silently the moment the registry moves.
16
+ */
17
+ /**
18
+ * Which controlled vocabulary a reading came from — and therefore what `id`
19
+ * means and where you can spend it.
20
+ *
21
+ * - `brand` — a brand slug, for `mg.brand()` or a brand filter.
22
+ * - `paint_identity` — one exact vendor paint (a library colour), by its vendor
23
+ * code where the library has one.
24
+ * - `colour` — a canonical colour-family term, namespaced (`colour_family.green`).
25
+ * - `category` — a product-category taxonomy node key.
26
+ * - `attribute` — a registry attribute id: the LEFT half of an `attribute:value`
27
+ * pair, for `mg.bySpec()` or `search({ filters })`.
28
+ * - `attribute_value` — a value-dictionary term, namespaced (`ms_sheen.matte`):
29
+ * the RIGHT half.
30
+ */
31
+ export type ResolutionKind = "brand" | "paint_identity" | "colour" | "category" | "attribute" | "attribute_value";
32
+ /**
33
+ * How the phrase matched, strongest first. `exact` — it IS the canonical token;
34
+ * `alias` — it IS a recorded alternative name for it; `lexical` — it resembles
35
+ * one closely enough to clear the resolver's own confidence floor.
36
+ *
37
+ * There is deliberately no `semantic` rung on this lens. Every tier that would
38
+ * cost an embedding round-trip is switched off, which is what keeps resolution
39
+ * millisecond-class; a phrase only a semantic match could have caught comes back
40
+ * unresolved rather than slow.
41
+ */
42
+ export type ResolutionRung = "exact" | "alias" | "lexical";
43
+ /** One thing a phrase turned out to mean. */
44
+ export interface ResolvedMeaning {
45
+ kind: ResolutionKind;
46
+ /**
47
+ * The canonical token. OPAQUE — hand it back verbatim, never parse, tidy or
48
+ * construct one. The single thing worth knowing about the SHAPE: a
49
+ * value-dictionary term id is unique only within its dictionary, so `colour`
50
+ * and `attribute_value` ids arrive namespaced as `<dictionaryId>.<termId>`.
51
+ */
52
+ id: string;
53
+ /** The canonical display term — what to show a human, not what to send back. */
54
+ term: string;
55
+ rung: ResolutionRung;
56
+ /**
57
+ * The resolver's own confidence in [0,1], present only when the underlying
58
+ * resolver produces one. Its ABSENCE is meaningful rather than missing data:
59
+ * the rung is then the whole statement, because an exact paint identity is an
60
+ * equality and not a score.
61
+ */
62
+ confidence?: number;
63
+ /**
64
+ * Present only when the resolver reported a runner-up. A small `gap` means the
65
+ * answer was nearly something else; when the resolver refused to break the tie,
66
+ * every tied reading is listed and `gap` is 0.
67
+ */
68
+ ambiguity?: {
69
+ gap: number;
70
+ };
71
+ }
72
+ /** What one phrase resolved to — including, explicitly, nothing. */
73
+ export interface PhraseResolution {
74
+ /** The phrase exactly as you sent it, so you can rejoin the batch by value. */
75
+ phrase: string;
76
+ /**
77
+ * The phrase in the one normalised form the resolvers fold to — the same
78
+ * `normalise` stage a search query passes through. Empty when the phrase was
79
+ * all punctuation.
80
+ */
81
+ normalised: string;
82
+ /**
83
+ * Everything the phrase means here, best first. A phrase can legitimately mean
84
+ * several things at once ("sage" is two vendor paints AND a colour family), so
85
+ * this is a list rather than a winner.
86
+ */
87
+ resolved: ResolvedMeaning[];
88
+ /**
89
+ * TRUE when nothing resolved. Read this rather than inferring it from
90
+ * `resolved.length`: the redundancy is deliberate, because a consumer that
91
+ * cannot SEE what failed to resolve cannot tell a word this vocabulary does
92
+ * not have from a word something quietly dropped. Absent is not false — an
93
+ * unresolved phrase is a statement about this vocabulary, never about the
94
+ * material world.
95
+ */
96
+ unresolved: boolean;
97
+ }
98
+ /** The resolve response: one entry per phrase, in the order you sent them. */
99
+ export interface ResolveResponse {
100
+ /** One per phrase, in request order — never reordered, never deduplicated. */
101
+ resolutions: PhraseResolution[];
102
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The vocabulary lens (MG-913) — the hand-mirrored wire types for
3
+ * `POST /api/v1/resolve`.
4
+ *
5
+ * Split out of `types.ts` for the repository's source-file size budget, the same
6
+ * reason `suggest-types.ts` sits beside it; both are re-exported from `types.ts`,
7
+ * so importers see no difference.
8
+ *
9
+ * THE LENS: it turns WORDS into vocabulary, never into materials.
10
+ * `mg.search()` answers "I want materials" and `mg.answer` questions want prose;
11
+ * this answers "what do these words mean in your vocabulary?" — the question you
12
+ * ask BEFORE you spend a token on a filter, a facet or a brand slug. It is the
13
+ * reason you should not keep a hand-written table mapping your users' words onto
14
+ * this API's tokens: that table is a copy of a registry you do not own, and it
15
+ * drifts silently the moment the registry moves.
16
+ */
17
+ export {};
package/run-types.d.ts CHANGED
@@ -233,6 +233,31 @@ export interface CreatePublicRunResponse {
233
233
  export interface PublicRunResponse {
234
234
  run: PublicRun;
235
235
  }
236
+ /** Query for `GET /api/v1/runs` ({@link MaterialGraphClient.listRuns}). */
237
+ export interface ListRunsOptions {
238
+ /**
239
+ * Return only Runs in this status. `"suspended"` is the recovery question:
240
+ * which of my Runs is still waiting on an answer?
241
+ */
242
+ status?: PublicRunStatus;
243
+ /** Page size, 1–100. Defaults to 20 server-side. */
244
+ limit?: number;
245
+ /**
246
+ * The previous page's {@link PublicRunListResponse.nextCursor}, round-tripped
247
+ * VERBATIM. It is opaque — never parse or construct one.
248
+ */
249
+ cursor?: string;
250
+ signal?: AbortSignal;
251
+ }
252
+ /** The `GET /api/v1/runs` response envelope — one page, newest first. */
253
+ export interface PublicRunListResponse {
254
+ runs: PublicRun[];
255
+ /**
256
+ * Pass as {@link ListRunsOptions.cursor} for the next page. ABSENT — not
257
+ * empty-string, not null — once the last page has been served.
258
+ */
259
+ nextCursor?: string;
260
+ }
236
261
  /** Body for `POST /api/v1/runs/{runId}/resume`. */
237
262
  export interface ResumePublicRunRequest {
238
263
  /** The resume payload, validated against the suspended step's resume schema. */
package/runs.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { HttpTransport } from "./http-transport.js";
2
2
  import type { FinishScheduleResponse, GetRunScheduleOptions } from "./schedule-types.js";
3
- import { type CapabilityManifestResponse, type CreatePublicRunRequest, type CreatePublicRunResponse, type DrainRunEventsOptions, type PublicRunResponse, type ResumeRunOptions, type RunEvent, type SourceMaterialBatchDetail, type SourceMaterialBatchReceipt, type StreamRunEventsOptions, type UploadSourceMaterialInput } from "./run-types.js";
3
+ import { type CapabilityManifestResponse, type CreatePublicRunRequest, type CreatePublicRunResponse, type DrainRunEventsOptions, type ListRunsOptions, type PublicRunListResponse, type PublicRunResponse, type ResumeRunOptions, type RunEvent, type SourceMaterialBatchDetail, type SourceMaterialBatchReceipt, type StreamRunEventsOptions, type UploadSourceMaterialInput } from "./run-types.js";
4
4
  /**
5
5
  * Upload one atomic source-material batch (`POST /api/v1/source-material`).
6
6
  * Accepts the pile-of-files shape (`{ files }` → repeatable `files` field) or
@@ -25,6 +25,20 @@ export declare function getSourceMaterialBatch(transport: HttpTransport, batchId
25
25
  export declare function capabilities(transport: HttpTransport, signal?: AbortSignal): Promise<CapabilityManifestResponse>;
26
26
  /** Start an asynchronous capability Run (`POST /api/v1/runs`). */
27
27
  export declare function createRun(transport: HttpTransport, body: CreatePublicRunRequest, signal?: AbortSignal): Promise<CreatePublicRunResponse>;
28
+ /**
29
+ * List your own Runs, newest first (`GET /api/v1/runs`).
30
+ *
31
+ * The recovery path for a client that still holds its API key but has lost a Run
32
+ * id — a reinstalled app, or a crash before the id from {@link createRun} was
33
+ * persisted. `{ status: "suspended" }` finds the Run still waiting on an answer.
34
+ *
35
+ * Only the calling principal's own Runs are ever returned; there is no
36
+ * cross-principal listing.
37
+ *
38
+ * Page by following `nextCursor` until it is ABSENT — a full page is not the
39
+ * signal to stop, and an absent cursor is the only one.
40
+ */
41
+ export declare function listRuns(transport: HttpTransport, options?: ListRunsOptions): Promise<PublicRunListResponse>;
28
42
  /** Get a Run's current state (`GET /api/v1/runs/{runId}`). */
29
43
  export declare function getRun(transport: HttpTransport, runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
30
44
  /**
package/runs.js CHANGED
@@ -99,6 +99,30 @@ export function createRun(transport, body, signal) {
99
99
  signal,
100
100
  });
101
101
  }
102
+ /**
103
+ * List your own Runs, newest first (`GET /api/v1/runs`).
104
+ *
105
+ * The recovery path for a client that still holds its API key but has lost a Run
106
+ * id — a reinstalled app, or a crash before the id from {@link createRun} was
107
+ * persisted. `{ status: "suspended" }` finds the Run still waiting on an answer.
108
+ *
109
+ * Only the calling principal's own Runs are ever returned; there is no
110
+ * cross-principal listing.
111
+ *
112
+ * Page by following `nextCursor` until it is ABSENT — a full page is not the
113
+ * signal to stop, and an absent cursor is the only one.
114
+ */
115
+ export function listRuns(transport, options = {}) {
116
+ const query = {
117
+ ...(options.status === undefined ? {} : { status: options.status }),
118
+ ...(options.limit === undefined ? {} : { limit: options.limit }),
119
+ ...(options.cursor === undefined ? {} : { cursor: options.cursor }),
120
+ };
121
+ return transport.requestJson(runOperation("list_public_runs"), {
122
+ ...(Object.keys(query).length > 0 ? { query } : {}),
123
+ ...(options.signal ? { signal: options.signal } : {}),
124
+ });
125
+ }
102
126
  /** Get a Run's current state (`GET /api/v1/runs/{runId}`). */
103
127
  export function getRun(transport, runId, signal) {
104
128
  return transport.requestJson(runOperation("get_public_run"), {
@@ -1,20 +1,23 @@
1
- /**
2
- * Wire types for the governed STATIC schema (`GET /api/v1/schema/*`).
3
- *
4
- * THE LENS: "what does the registry DEFINE?" — blocks, attributes, units, value
5
- * dictionaries, product types and their external mappings. Definitions, not
6
- * data: nothing here says how many materials carry an attribute or which values
7
- * the corpus holds. For that, use `mg.specificationFacets()` and
8
- * `mg.attributeValues()`; for one term explained in prose, `mg.glossary()`; for
9
- * what one material has been determined on, `mg.materialBlocks()`.
10
- *
11
- * Every id here (`attributeId`, `blockId`, product-type keys) is canonical
12
- * VOCABULARY, not an opaque token — this is where you discover it rather than
13
- * invent it.
14
- */
15
1
  import type { OpenEnum } from "./types.js";
16
2
  /** The nine governed static resources. */
17
3
  export type SchemaResource = "blocks" | "attributes" | "verticals" | "productTypes" | "units" | "valueDictionaries" | "sourceRequirements" | "productTypeTemplates" | "mappings";
4
+ /**
5
+ * Resource → the operation that serves it. Internal to the SDK (`index.ts`
6
+ * re-exports this module's TYPES only), and it lives beside
7
+ * {@link SchemaResource} rather than in the client so the mapping and the union
8
+ * it must cover cannot drift apart.
9
+ */
10
+ export declare const SCHEMA_RESOURCE_OPERATION_IDS: {
11
+ readonly blocks: "list_schema_blocks";
12
+ readonly attributes: "list_schema_attributes";
13
+ readonly verticals: "list_schema_verticals";
14
+ readonly productTypes: "list_schema_product_types";
15
+ readonly units: "list_schema_units";
16
+ readonly valueDictionaries: "list_schema_value_dictionaries";
17
+ readonly sourceRequirements: "list_schema_source_requirements";
18
+ readonly productTypeTemplates: "list_schema_product_type_templates";
19
+ readonly mappings: "list_schema_mappings";
20
+ };
18
21
  export type SchemaResourceRequest = SchemaResource;
19
22
  export interface SchemaLifecycle {
20
23
  status: OpenEnum<"draft" | "active" | "deprecated">;
@@ -1 +1,17 @@
1
- export {};
1
+ /**
2
+ * Resource → the operation that serves it. Internal to the SDK (`index.ts`
3
+ * re-exports this module's TYPES only), and it lives beside
4
+ * {@link SchemaResource} rather than in the client so the mapping and the union
5
+ * it must cover cannot drift apart.
6
+ */
7
+ export const SCHEMA_RESOURCE_OPERATION_IDS = {
8
+ blocks: "list_schema_blocks",
9
+ attributes: "list_schema_attributes",
10
+ verticals: "list_schema_verticals",
11
+ productTypes: "list_schema_product_types",
12
+ units: "list_schema_units",
13
+ valueDictionaries: "list_schema_value_dictionaries",
14
+ sourceRequirements: "list_schema_source_requirements",
15
+ productTypeTemplates: "list_schema_product_type_templates",
16
+ mappings: "list_schema_mappings",
17
+ };
@@ -143,6 +143,9 @@ export function checkSearchResultItem(value, what, fail) {
143
143
  requireString(record.product, `${what}.product`, fail);
144
144
  requireNullableString(record.brandSlug, `${what}.brandSlug`, fail);
145
145
  requireNullableString(record.productId, `${what}.productId`, fail);
146
+ // `z.string().optional()` — absent, or a string. NOT nullable: the emit path
147
+ // omits the key rather than sending null when no MB identity is known.
148
+ requireOptionalString(record.materialBankId, `${what}.materialBankId`, fail);
146
149
  requireNullableString(record.color, `${what}.color`, fail);
147
150
  requireNullableString(record.variantName, `${what}.variantName`, fail);
148
151
  requireNullableString(record.imageUrl, `${what}.imageUrl`, fail);