@instruments/materialgraph 0.2.0 → 0.3.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.
Files changed (45) hide show
  1. package/by-spec-types.d.ts +73 -0
  2. package/by-spec-types.js +7 -0
  3. package/client.d.ts +68 -23
  4. package/client.js +82 -25
  5. package/codes-client.d.ts +82 -0
  6. package/codes-client.js +73 -0
  7. package/codes-types.d.ts +154 -9
  8. package/colour-aggregate-types.d.ts +93 -0
  9. package/colour-aggregate-types.js +1 -0
  10. package/colours-client.d.ts +52 -0
  11. package/colours-client.js +39 -0
  12. package/composition-types.d.ts +228 -2
  13. package/http-transport.d.ts +19 -2
  14. package/http-transport.js +72 -2
  15. package/index.d.ts +7 -0
  16. package/index.js +3 -0
  17. package/match-stream-types.d.ts +150 -0
  18. package/match-stream-types.js +28 -0
  19. package/match-stream.d.ts +22 -0
  20. package/match-stream.js +280 -0
  21. package/material-block-types.d.ts +23 -2
  22. package/material-types.d.ts +92 -0
  23. package/material-types.js +1 -0
  24. package/ndjson.d.ts +46 -0
  25. package/ndjson.js +103 -0
  26. package/operations.d.ts +1 -1
  27. package/operations.js +6 -0
  28. package/package.json +1 -1
  29. package/run-types.d.ts +65 -1
  30. package/runs.d.ts +45 -3
  31. package/runs.js +82 -43
  32. package/schedule-types.d.ts +88 -0
  33. package/schedule-types.js +12 -0
  34. package/search-refinement-types.d.ts +13 -0
  35. package/search-result-validation.d.ts +27 -0
  36. package/search-result-validation.js +174 -0
  37. package/source-material-validation.d.ts +3 -0
  38. package/source-material-validation.js +109 -0
  39. package/spec-types.d.ts +4 -0
  40. package/spec-types.js +4 -0
  41. package/specifications.d.ts +9 -0
  42. package/specifications.js +9 -0
  43. package/types.d.ts +59 -73
  44. package/validation.d.ts +119 -0
  45. package/validation.js +241 -0
@@ -1,6 +1,27 @@
1
1
  import type { OpenEnum } from "./types.js";
2
- export type MaterialBlockStatus = OpenEnum<"populated" | "partial" | "pending" | "not_applicable">;
3
- export type MaterialBlockFieldStatus = OpenEnum<"verified" | "partial" | "missing" | "not_applicable">;
2
+ /**
3
+ * Coverage status, shared by blocks and fields.
4
+ *
5
+ * `verified` — determined. For a field a value is known, INCLUDING `false`, `0`
6
+ * and `[]`, which are answers meaning "we looked and there is none". For a
7
+ * block, every field is determined.
8
+ * `partial` — determined for some fields but not all.
9
+ * `missing` — not determined yet. The work queue, and the only status that
10
+ * appears in `missingAttributeIds`.
11
+ * `not_applicable` — cannot apply to this product type. Never work, never a gap.
12
+ *
13
+ * BREAKING in this release: blocks previously reported `populated`/`pending`
14
+ * while fields reported `verified`/`missing` — two spellings of one concept
15
+ * that disagreed with each other on the same variant. Map `populated ->
16
+ * verified` and `pending -> missing`. Both types are retained as aliases so
17
+ * existing code keeps compiling; they now describe the same set.
18
+ *
19
+ * The authoritative list is served at runtime by
20
+ * `get-schema { kind: "coverageStatuses" }`.
21
+ */
22
+ export type MaterialCoverageStatus = OpenEnum<"verified" | "partial" | "missing" | "not_applicable">;
23
+ export type MaterialBlockStatus = MaterialCoverageStatus;
24
+ export type MaterialBlockFieldStatus = MaterialCoverageStatus;
4
25
  export type MaterialBlockFieldValue = string | number | boolean | unknown[] | Record<string, unknown> | null;
5
26
  export interface MaterialBlockProjectionField {
6
27
  attributeId: string;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Hand-mirrored wire types for the material projections — the grid-card tile,
3
+ * the detail record, and the availability/successor block they carry.
4
+ *
5
+ * Extracted from `./types.ts` (which re-exports everything here) so that module
6
+ * stays within the repository's source-size budget. Same contract, same
7
+ * tolerant-parsing rules: response enums are OPEN, so `MaterialAvailability` is
8
+ * widened — a new server state still type-checks.
9
+ */
10
+ import type { ImageSource, MaterialColour, OpenEnum, PaletteSwatch } from "./types.js";
11
+ /**
12
+ * Whether a material can still be bought.
13
+ *
14
+ * - `available` — listed in the catalog of record.
15
+ * - `discontinued` — WITHDRAWN from that catalog. This is the source's own
16
+ * listing state, not a reviewed manufacturer-discontinuation claim; do not
17
+ * present it as the manufacturer having discontinued the product.
18
+ * - `superseded` — a reviewed product relationship names a successor and the
19
+ * material is no longer listed. See `MaterialDetail.supersededBy`.
20
+ * - `unknown` — never checked. A real answer, not an error.
21
+ */
22
+ export type MaterialAvailability = OpenEnum<"available" | "discontinued" | "superseded" | "unknown">;
23
+ /**
24
+ * The variant a reviewed product relationship names as replacing another.
25
+ * `availability` is the successor's OWN state, resolved ONE HOP only — a
26
+ * successor that has itself been superseded reports `discontinued`/`unknown`
27
+ * here; follow `variantId` for the next link in the chain.
28
+ */
29
+ export interface MaterialSuccessor {
30
+ variantId: string;
31
+ brandSlug: string;
32
+ productId: string;
33
+ brand: string;
34
+ product: string;
35
+ variant: string;
36
+ color: string | null;
37
+ availability: MaterialAvailability;
38
+ }
39
+ /**
40
+ * The grid-card projection of a material. Tiles carry `availability` but not the
41
+ * successor record — read the detail projection for that.
42
+ */
43
+ export interface MaterialTile {
44
+ variantId: string;
45
+ brand: string;
46
+ product: string;
47
+ variantName: string | null;
48
+ imageUrl: string | null;
49
+ imageSource: ImageSource | null;
50
+ category: string | null;
51
+ colour: MaterialColour;
52
+ availability: MaterialAvailability;
53
+ }
54
+ /** A canonical attribute/term pair carried on a detail record. */
55
+ export interface MaterialFacet {
56
+ attributeId: string;
57
+ term: string;
58
+ }
59
+ /**
60
+ * One dimensional configuration of a material. Numeric fields are millimetres
61
+ * (mass in kilograms); string fields are the source's own descriptors. The
62
+ * index signature preserves the corpus's long tail of rarer keys.
63
+ */
64
+ export interface DimensionEntry {
65
+ label?: string;
66
+ tile_size?: string;
67
+ measurement_basis?: string;
68
+ width_mm?: number;
69
+ length_mm?: number;
70
+ height_mm?: number;
71
+ depth_mm?: number;
72
+ thickness_mm?: number;
73
+ diameter_mm?: number;
74
+ visible_projection_mm?: number;
75
+ weight_kg?: number;
76
+ [key: string]: unknown;
77
+ }
78
+ /** The record projection of a material — the tile plus detail-only fields. */
79
+ export interface MaterialDetail extends MaterialTile {
80
+ brandSlug: string;
81
+ productId: string;
82
+ color: string | null;
83
+ materialBankId: string | null;
84
+ installationImageUrl: string | null;
85
+ displayCategory: string | null;
86
+ categoryPath: string[];
87
+ palette: PaletteSwatch[];
88
+ facets: MaterialFacet[];
89
+ dimensions: DimensionEntry[] | null;
90
+ /** The declared successor, or null when no reviewed relationship names one. */
91
+ supersededBy: MaterialSuccessor | null;
92
+ }
@@ -0,0 +1 @@
1
+ export {};
package/ndjson.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A minimal, dependency-free NDJSON (newline-delimited JSON) line reader — the
3
+ * primitive behind {@link MaterialGraphClient.matchStream}. Hand-rolled so the
4
+ * SDK stays zero-runtime-dep and works unchanged inside Node route handlers,
5
+ * where `fetch` yields a byte `ReadableStream`.
6
+ *
7
+ * Deliberately a SIBLING of `sse.ts`, not a reuse of it. NDJSON and SSE agree on
8
+ * nothing but "text arrives in chunks": SSE frames are `field: value` lines
9
+ * terminated by a BLANK line, with comment lines and multi-line `data:`
10
+ * accumulation, whereas an NDJSON frame is exactly one line of JSON. Feeding
11
+ * NDJSON through the SSE tokenizer would read `{"type":"step",…}` as a field
12
+ * named `{"type"` and silently discard every frame. The two also want OPPOSITE
13
+ * end-of-stream policies: SSE dispatches a trailing partial frame as valid,
14
+ * while a truncated NDJSON line must surface as an error (enforced by the
15
+ * caller, which parses whatever `flush` returns rather than dropping it).
16
+ *
17
+ * Line endings: the reader splits on `\n` only, per the NDJSON contract. A `\r`
18
+ * left by a CRLF-normalising proxy stays as trailing whitespace on the line,
19
+ * which `JSON.parse` ignores.
20
+ */
21
+ /**
22
+ * A push-based NDJSON line assembler. Feed it decoded text with {@link push};
23
+ * it returns the lines that text COMPLETED, terminators removed. Any partial
24
+ * trailing line is held in an internal buffer until a later chunk terminates it,
25
+ * or until {@link flush} surrenders it at end of stream.
26
+ */
27
+ export declare class NdjsonDecoder {
28
+ #private;
29
+ /** Absorb one decoded chunk and return the lines it completed, in order. */
30
+ push(text: string): string[];
31
+ /**
32
+ * Surrender the unterminated remainder at end of stream — a well-formed
33
+ * NDJSON stream's final line need not carry a trailing newline. The remainder
34
+ * is RETURNED, never dropped: a truncated frame is the caller's error to
35
+ * raise, not this decoder's fragment to discard.
36
+ */
37
+ flush(): string[];
38
+ }
39
+ /**
40
+ * Iterate the NDJSON lines of a byte stream. Only lines with non-whitespace
41
+ * content are yielded — a blank separator line carries no frame and must never
42
+ * reach `JSON.parse`, where it would become an empty object. Stops cleanly when
43
+ * the stream ends or `signal` aborts (mid-read abort rejections are swallowed
44
+ * once the signal is set).
45
+ */
46
+ export declare function readNdjsonLines(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<string, void, unknown>;
package/ndjson.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * A minimal, dependency-free NDJSON (newline-delimited JSON) line reader — the
3
+ * primitive behind {@link MaterialGraphClient.matchStream}. Hand-rolled so the
4
+ * SDK stays zero-runtime-dep and works unchanged inside Node route handlers,
5
+ * where `fetch` yields a byte `ReadableStream`.
6
+ *
7
+ * Deliberately a SIBLING of `sse.ts`, not a reuse of it. NDJSON and SSE agree on
8
+ * nothing but "text arrives in chunks": SSE frames are `field: value` lines
9
+ * terminated by a BLANK line, with comment lines and multi-line `data:`
10
+ * accumulation, whereas an NDJSON frame is exactly one line of JSON. Feeding
11
+ * NDJSON through the SSE tokenizer would read `{"type":"step",…}` as a field
12
+ * named `{"type"` and silently discard every frame. The two also want OPPOSITE
13
+ * end-of-stream policies: SSE dispatches a trailing partial frame as valid,
14
+ * while a truncated NDJSON line must surface as an error (enforced by the
15
+ * caller, which parses whatever `flush` returns rather than dropping it).
16
+ *
17
+ * Line endings: the reader splits on `\n` only, per the NDJSON contract. A `\r`
18
+ * left by a CRLF-normalising proxy stays as trailing whitespace on the line,
19
+ * which `JSON.parse` ignores.
20
+ */
21
+ /**
22
+ * A push-based NDJSON line assembler. Feed it decoded text with {@link push};
23
+ * it returns the lines that text COMPLETED, terminators removed. Any partial
24
+ * trailing line is held in an internal buffer until a later chunk terminates it,
25
+ * or until {@link flush} surrenders it at end of stream.
26
+ */
27
+ export class NdjsonDecoder {
28
+ #buffer = "";
29
+ /** Absorb one decoded chunk and return the lines it completed, in order. */
30
+ push(text) {
31
+ this.#buffer += text;
32
+ const lines = [];
33
+ for (;;) {
34
+ const index = this.#buffer.indexOf("\n");
35
+ if (index === -1)
36
+ break;
37
+ lines.push(this.#buffer.slice(0, index));
38
+ this.#buffer = this.#buffer.slice(index + 1);
39
+ }
40
+ return lines;
41
+ }
42
+ /**
43
+ * Surrender the unterminated remainder at end of stream — a well-formed
44
+ * NDJSON stream's final line need not carry a trailing newline. The remainder
45
+ * is RETURNED, never dropped: a truncated frame is the caller's error to
46
+ * raise, not this decoder's fragment to discard.
47
+ */
48
+ flush() {
49
+ if (this.#buffer.length === 0)
50
+ return [];
51
+ const remainder = this.#buffer;
52
+ this.#buffer = "";
53
+ return [remainder];
54
+ }
55
+ }
56
+ /**
57
+ * Iterate the NDJSON lines of a byte stream. Only lines with non-whitespace
58
+ * content are yielded — a blank separator line carries no frame and must never
59
+ * reach `JSON.parse`, where it would become an empty object. Stops cleanly when
60
+ * the stream ends or `signal` aborts (mid-read abort rejections are swallowed
61
+ * once the signal is set).
62
+ */
63
+ export async function* readNdjsonLines(body, signal) {
64
+ const reader = body.getReader();
65
+ const decoder = new TextDecoder();
66
+ const ndjson = new NdjsonDecoder();
67
+ try {
68
+ for (;;) {
69
+ if (signal?.aborted)
70
+ return;
71
+ let result;
72
+ try {
73
+ result = await reader.read();
74
+ }
75
+ catch (error) {
76
+ if (signal?.aborted)
77
+ return;
78
+ throw error;
79
+ }
80
+ if (result.done) {
81
+ // `decode()` with no argument flushes any dangling multi-byte sequence.
82
+ for (const line of ndjson.push(decoder.decode())) {
83
+ if (line.trim().length > 0)
84
+ yield line;
85
+ }
86
+ for (const line of ndjson.flush()) {
87
+ if (line.trim().length > 0)
88
+ yield line;
89
+ }
90
+ return;
91
+ }
92
+ for (const line of ndjson.push(decoder.decode(result.value, { stream: true }))) {
93
+ if (signal?.aborted)
94
+ return;
95
+ if (line.trim().length > 0)
96
+ yield line;
97
+ }
98
+ }
99
+ }
100
+ finally {
101
+ reader.releaseLock();
102
+ }
103
+ }
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 ["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 ["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 ["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"]];
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"]];
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
@@ -22,6 +22,8 @@ const OPERATION_DEFINITIONS = [
22
22
  ["materialsCached", "GET", "/api/v1/materials/batch", "get_materials_batch_get"],
23
23
  ["browse", "GET", "/api/v1/search/browse", "search_browse_by_colour"],
24
24
  ["colourFacets", "GET", "/api/v1/colours/facets", "get_colour_facets"],
25
+ ["colours.profiles", "GET", "/api/v1/colours/profiles", "get_category_colour_profiles"],
26
+ ["colours.stats", "GET", "/api/v1/colours/stats", "get_colour_stats"],
25
27
  ["resolveColour", "GET", "/api/v1/search/colours/resolve", "resolve_colour_name"],
26
28
  ["suggestColour", "GET", "/api/v1/search/colours/suggest", "suggest_colour_names"],
27
29
  ["resolveAttribute", "GET", "/api/v1/catalog/attributes/resolve", "catalog_resolve_attribute"],
@@ -50,9 +52,11 @@ const OPERATION_DEFINITIONS = [
50
52
  ["brands", "GET", "/api/v1/brands", "list_brands"],
51
53
  ["brand", "GET", "/api/v1/brands/{brandSlug}", "get_brand_profile"],
52
54
  ["uploadSourceMaterial", "POST", "/api/v1/source-material", "upload_source_material"],
55
+ ["sourceMaterialBatch", "GET", "/api/v1/source-material/{batchId}", "get_source_material_batch"],
53
56
  ["capabilities", "GET", "/api/v1/capabilities", "list_public_capabilities"],
54
57
  ["createRun", "POST", "/api/v1/runs", "create_public_run"],
55
58
  ["run", "GET", "/api/v1/runs/{runId}", "get_public_run"],
59
+ ["runSchedule", "GET", "/api/v1/runs/{runId}/schedule", "get_run_finish_schedule"],
56
60
  ["streamRunEvents", "GET", "/api/v1/runs/{runId}/events", "list_public_run_events"],
57
61
  ["resumeRun", "POST", "/api/v1/runs/{runId}/resume", "resume_public_run"],
58
62
  ["cancelRun", "POST", "/api/v1/runs/{runId}/cancel", "cancel_public_run"],
@@ -63,6 +67,8 @@ const OPERATION_DEFINITIONS = [
63
67
  ["appendProjectEvent", "POST", "/api/v1/projects/{projectId}/events", "append_box_project_event"],
64
68
  ["demandBatch", "POST", "/api/v1/demand/batch", "get_demand_batch"],
65
69
  ["codeAdvisory", "POST", "/api/v1/codes/advisory", "resolveCodeAdvisory"],
70
+ ["codes.context", "GET", "/api/v1/codes/context", "list_code_context"],
71
+ ["codes.requirements", "GET", "/api/v1/codes/requirements", "list_code_requirements"],
66
72
  ];
67
73
  export const SDK_OPERATIONS = Object.freeze(OPERATION_DEFINITIONS.map(([clientMethod, method, path, operationId]) => Object.freeze({ clientMethod, method, path, operationId })));
68
74
  /** Look up a registered operation by its OpenAPI `operationId`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materialgraph",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/run-types.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * so a new server value still type-checks, and objects are read structurally —
11
11
  * unknown keys pass through untouched.
12
12
  */
13
- import type { OpenEnum } from "./types.js";
13
+ import type { JsonValue, OpenEnum } from "./types.js";
14
14
  /**
15
15
  * A file to upload. Two forms so the SDK is ergonomic on BOTH Node route
16
16
  * handlers and modern runtimes: pass a platform `Blob`/`File` directly, or the
@@ -74,6 +74,58 @@ export interface SourceMaterialBatchReceipt {
74
74
  batchId: string;
75
75
  sources: SourceMaterialReceiptFile[];
76
76
  }
77
+ /**
78
+ * One analysis computed over an uploaded image's bytes.
79
+ *
80
+ * Analyses are CONTENT-ADDRESSED: keyed by the file's SHA-256, not by the
81
+ * upload or the uploading key. Re-uploading an image someone has already
82
+ * analysed therefore returns that analysis immediately, with no work. That
83
+ * sharing is by design and discloses nothing — you only ever see analyses of
84
+ * images you hold yourself.
85
+ */
86
+ export interface SourceImageAnalysis {
87
+ /** The kind of read. {@link OpenEnum} keeps a newer server value type-checking. */
88
+ kind: OpenEnum<"scene_match">;
89
+ /** The model version that produced {@link SourceImageAnalysis.result}. */
90
+ model: string;
91
+ /**
92
+ * `failed` analyses are RETURNED, not hidden — so "we looked and it did not
93
+ * work" is distinguishable from "nobody has looked yet", which is an empty
94
+ * {@link SourceMaterialBatchFile.analyses}.
95
+ */
96
+ status: OpenEnum<"ok" | "failed">;
97
+ /**
98
+ * The payload verbatim, or the failure record when `status` is `failed`. For
99
+ * `scene_match`: the detected regions with their candidate variant ids and
100
+ * scores. The interior shape belongs to the producing model and may gain
101
+ * fields, so it is typed as JSON rather than pinned here.
102
+ */
103
+ result: Record<string, JsonValue>;
104
+ /** ISO-8601 timestamp of the most recent analysis. */
105
+ analysedAt: string;
106
+ }
107
+ /** One file as `GET /api/v1/source-material/{batchId}` returns it. */
108
+ export interface SourceMaterialBatchFile extends SourceMaterialReceiptFile {
109
+ /** Fetchable URL for the stored bytes. */
110
+ url: string;
111
+ /**
112
+ * ALWAYS an array. `[]` means nothing has been computed yet — never a missing
113
+ * field. Preprocessing is triggered manually today; when it becomes automatic
114
+ * only the trigger changes and this contract does not.
115
+ */
116
+ analyses: SourceImageAnalysis[];
117
+ }
118
+ /**
119
+ * The `200` body of `GET /api/v1/source-material/{batchId}` — the batch, every
120
+ * file it holds, and the analyses computed over each file's bytes.
121
+ */
122
+ export interface SourceMaterialBatchDetail {
123
+ /** The stored batch id (`smb_…`). */
124
+ batchId: string;
125
+ /** ISO-8601 timestamp of the upload. */
126
+ createdAt: string;
127
+ sources: SourceMaterialBatchFile[];
128
+ }
77
129
  export type CapabilityKind = OpenEnum<"agent" | "tool" | "workflow">;
78
130
  export type CapabilityExposure = OpenEnum<"public" | "partner" | "internal">;
79
131
  export type CapabilityScope = OpenEnum<"mg:read" | "mg:write" | "mg:ingest" | "mg:admin">;
@@ -200,3 +252,15 @@ export interface DrainRunEventsOptions {
200
252
  /** Stop collecting after this many milliseconds even without a terminal event. */
201
253
  deadlineMs: number;
202
254
  }
255
+ /** Options for {@link MaterialGraphClient.resumeRun}. */
256
+ export interface ResumeRunOptions {
257
+ /**
258
+ * The step the caller believes is suspended. When it mismatches the run
259
+ * doc's live `suspensionStepId` the API answers RFC-7807 409 (MG-608) —
260
+ * which is what stops a stale client resuming a step the run has already
261
+ * moved past. Omitted, the resume is unguarded, exactly as before.
262
+ */
263
+ stepId?: string;
264
+ /** Abort the request. */
265
+ signal?: AbortSignal;
266
+ }
package/runs.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { HttpTransport } from "./http-transport.js";
2
- import { type CapabilityManifestResponse, type CreatePublicRunRequest, type CreatePublicRunResponse, type DrainRunEventsOptions, type PublicRunResponse, type RunEvent, type SourceMaterialBatchReceipt, type StreamRunEventsOptions, type UploadSourceMaterialInput } from "./run-types.js";
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
4
  /**
4
5
  * Upload one atomic source-material batch (`POST /api/v1/source-material`).
5
6
  * Accepts the pile-of-files shape (`{ files }` → repeatable `files` field) or
@@ -7,14 +8,55 @@ import { type CapabilityManifestResponse, type CreatePublicRunRequest, type Crea
7
8
  * fields). Both wire to multipart the server accepts.
8
9
  */
9
10
  export declare function uploadSourceMaterial(transport: HttpTransport, input: UploadSourceMaterialInput, signal?: AbortSignal): Promise<SourceMaterialBatchReceipt>;
11
+ /**
12
+ * Read back one uploaded batch (`GET /api/v1/source-material/{batchId}`): every
13
+ * file with a fetchable `url`, and every analysis computed over its bytes
14
+ * inlined as `analyses` — always an array, `[]` when nothing has been computed.
15
+ *
16
+ * A batch you do not own is indistinguishable from one that does not exist:
17
+ * both raise a 404, never a 403, so batch ids cannot be probed for existence.
18
+ *
19
+ * The body is VALIDATED, not trusted: `checkSourceMaterialBatchDetail` mirrors
20
+ * the server's Zod contract so a malformed response fails here with a path into
21
+ * the payload rather than as an undefined three frames away in caller code.
22
+ */
23
+ export declare function getSourceMaterialBatch(transport: HttpTransport, batchId: string, signal?: AbortSignal): Promise<SourceMaterialBatchDetail>;
10
24
  /** List invocable agents, workflows, and tools (`GET /api/v1/capabilities`). */
11
25
  export declare function capabilities(transport: HttpTransport, signal?: AbortSignal): Promise<CapabilityManifestResponse>;
12
26
  /** Start an asynchronous capability Run (`POST /api/v1/runs`). */
13
27
  export declare function createRun(transport: HttpTransport, body: CreatePublicRunRequest, signal?: AbortSignal): Promise<CreatePublicRunResponse>;
14
28
  /** Get a Run's current state (`GET /api/v1/runs/{runId}`). */
15
29
  export declare function getRun(transport: HttpTransport, runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
16
- /** Resume a suspended Run (`POST /api/v1/runs/{runId}/resume`). */
17
- export declare function resumeRun(transport: HttpTransport, runId: string, resumeData: unknown, signal?: AbortSignal): Promise<PublicRunResponse>;
30
+ /**
31
+ * Export a completed compose Run's scheme as a finish schedule — one row per
32
+ * slot, every attribute value carrying its source and verification state
33
+ * (`GET /api/v1/runs/{runId}/schedule`).
34
+ *
35
+ * The `.xlsx` form of the same export is deliberately NOT an SDK method: this
36
+ * transport parses every response as text/JSON and has no binary read path, so
37
+ * a bytes-returning method would mean a second transport surface for one file.
38
+ * Fetch `GET /api/v1/runs/{runId}/schedule.xlsx` directly with the same bearer
39
+ * key when you want the workbook.
40
+ */
41
+ export declare function getRunSchedule(transport: HttpTransport, runId: string, options?: GetRunScheduleOptions): Promise<FinishScheduleResponse>;
42
+ /**
43
+ * Resume a suspended Run (`POST /api/v1/runs/{runId}/resume`).
44
+ *
45
+ * The third argument takes either an `AbortSignal` (legacy) or
46
+ * {@link ResumeRunOptions}.
47
+ *
48
+ * `stepId` is forwarded whenever it is PRESENT — `!== undefined`, not truthy.
49
+ * An empty string is a present-but-malformed value and must reach the server
50
+ * so its schema rejects it; treating it as absent would silently downgrade a
51
+ * guarded resume into an unguarded one, which is the exact failure this guard
52
+ * exists to prevent.
53
+ *
54
+ * Omitting it sends a body byte-identical to what this sent before. Note the
55
+ * wire cannot distinguish `{ stepId: undefined }` from no key at all —
56
+ * `JSON.stringify` drops both — so the distinction here is construction
57
+ * intent, kept so the serialised body is provably unchanged for old callers.
58
+ */
59
+ export declare function resumeRun(transport: HttpTransport, runId: string, resumeData: unknown, signalOrOptions?: AbortSignal | ResumeRunOptions): Promise<PublicRunResponse>;
18
60
  /** Request durable Run cancellation (`POST /api/v1/runs/{runId}/cancel`). */
19
61
  export declare function cancelRun(transport: HttpTransport, runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
20
62
  /**