@instruments/materialgraph 0.1.0 → 0.2.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,117 @@
1
+ /**
2
+ * Hand-mirrored wire types for the synchronous composition-variation surface
3
+ * (`POST /api/v1/schemes/vary`, operation `vary_schemes`, MG-438). Like the rest
4
+ * of the SDK these interfaces carry NO runtime dependency on any
5
+ * `@materialgraph/*` package — they mirror the `VarySchemesRequest` /
6
+ * `VarySchemesResponse` shapes in `apps/web/public/openapi.yaml`.
7
+ *
8
+ * Vary is deterministic and synchronous: it re-derives an "option B" from a
9
+ * succeeded `compose-material-schemes` Run along one constraint axis, so unlike
10
+ * the async Run surface the response IS the scheme — no run envelope, no events.
11
+ *
12
+ * TOLERANT-PARSING CONTRACT: response enums are widened with {@link OpenEnum}
13
+ * so a new server value still type-checks; objects are read structurally.
14
+ */
15
+ import type { OpenEnum } from "./types.js";
16
+ /**
17
+ * The four v1 variation axes. Request-side, so this is a STRICT union (the
18
+ * caller picks exactly one). `uniqueness` is deferred server-side (MG-445).
19
+ */
20
+ export type VaryAxis = "palette" | "sustainability" | "brandDiversity" | "priceTier";
21
+ /**
22
+ * The variation axes as a runtime array, for consumers that render an axis
23
+ * picker. Order is the canonical presentation order.
24
+ */
25
+ export declare const VARY_AXES: readonly VaryAxis[];
26
+ /** Pin one slot to a specific variant so vary re-derives only the rest. */
27
+ export interface SchemeLock {
28
+ slotId: string;
29
+ /** Must equal that slot's CURRENT pick in the source scheme. */
30
+ variantId: string;
31
+ }
32
+ /** Candidate exclusions applied on top of the automatic source-pick exclusion. */
33
+ export interface SchemeExclusions {
34
+ variantIds?: string[];
35
+ brandSlugs?: string[];
36
+ }
37
+ /** Body for `POST /api/v1/schemes/vary` ({@link MaterialGraphClient.varyScheme}). */
38
+ export interface VarySchemesRequest {
39
+ /** The succeeded `compose-material-schemes` Run to vary from. */
40
+ runId: string;
41
+ /** Which scheme in the Run is option A. Defaults to the Run's FIRST scheme. */
42
+ schemeId?: string;
43
+ axis: VaryAxis;
44
+ /** Slots kept pinned to their source pick; only unlocked slots re-derive. */
45
+ locks?: SchemeLock[];
46
+ exclusions?: SchemeExclusions;
47
+ }
48
+ export type CompositionMatchState = OpenEnum<"matched" | "mismatched" | "unknown">;
49
+ /** One resolved criterion → material match, as scored during composition. */
50
+ export interface CompositionMatch {
51
+ attributeId: string;
52
+ state: CompositionMatchState;
53
+ confidence: number | null;
54
+ sourceBasis: string | null;
55
+ }
56
+ /** One slot's chosen material plus the reason it was picked. */
57
+ export interface CompositionPick {
58
+ slotId: string;
59
+ slotName: string;
60
+ spaceId?: string | null;
61
+ element?: string | null;
62
+ material: {
63
+ variantId: string;
64
+ brand: string;
65
+ product: string;
66
+ variant: string;
67
+ imageUrl: string | null;
68
+ hex: string | null;
69
+ };
70
+ reason: {
71
+ score: number;
72
+ matched: CompositionMatch[];
73
+ notes: string[];
74
+ };
75
+ }
76
+ /** The multi-objective score for one scheme (each component in [0, 1]). */
77
+ export interface CompositionObjective {
78
+ total: number;
79
+ briefFit: number;
80
+ coverage: number;
81
+ coherence: number;
82
+ novelty: number;
83
+ }
84
+ /** A hydrated scheme: one pick per slot, plus its objective breakdown. */
85
+ export interface CompositionScheme {
86
+ schemeId: string;
87
+ title: string;
88
+ picks: CompositionPick[];
89
+ objective: CompositionObjective;
90
+ }
91
+ /** How far short of the request the derivation fell, with honest reasons. */
92
+ export interface CompositionShortfall {
93
+ requested: number;
94
+ produced: number;
95
+ reasons: string[];
96
+ }
97
+ /** The axis + source provenance stamped onto every varied scheme. */
98
+ export interface VaryProvenance {
99
+ axis: VaryAxis;
100
+ sourceRunId: string;
101
+ sourceSchemeId: string;
102
+ lockedSlotIds: string[];
103
+ /**
104
+ * Human-readable notes recording exactly what the axis did — counts, not
105
+ * adjectives (e.g. "brandDiversity: hard-excluded 2 brand(s) appearing ≥2×").
106
+ * These are the variation's receipts.
107
+ */
108
+ notes: string[];
109
+ }
110
+ /** The `200` body from `POST /api/v1/schemes/vary`. */
111
+ export interface VarySchemesResponse {
112
+ /** The composition version the scheme was stamped with. */
113
+ version: string;
114
+ scheme: CompositionScheme;
115
+ provenance: VaryProvenance;
116
+ shortfall: CompositionShortfall | null;
117
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The variation axes as a runtime array, for consumers that render an axis
3
+ * picker. Order is the canonical presentation order.
4
+ */
5
+ export const VARY_AXES = Object.freeze([
6
+ "palette",
7
+ "sustainability",
8
+ "brandDiversity",
9
+ "priceTier",
10
+ ]);
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The shared low-level HTTP transport for {@link MaterialGraphClient}. It owns
3
+ * URL building, the bearer header, rate-limit capture, response reading, and the
4
+ * one error-mapping path — so the JSON read surface (`client.ts`), the multipart
5
+ * upload, and the SSE stream (`runs.ts`) all fail identically. Retry-free by
6
+ * design; a single failure surfaces as one {@link MaterialGraphError}.
7
+ */
8
+ import { type RateLimit } from "./errors.js";
9
+ import { type SdkOperation } from "./operations.js";
10
+ export type QueryValue = string | number | boolean | undefined | null | Array<string | number | boolean>;
11
+ export type QueryParams = Record<string, QueryValue>;
12
+ export interface RequestOptions {
13
+ query?: QueryParams;
14
+ body?: unknown;
15
+ signal?: AbortSignal;
16
+ pathParams?: Readonly<Record<string, string>>;
17
+ }
18
+ /** A read, status-checked HTTP response with its parsed body. */
19
+ export interface SentResponse {
20
+ response: Response;
21
+ text: string;
22
+ parsed: unknown;
23
+ }
24
+ interface SendInput {
25
+ method: string;
26
+ url: string;
27
+ headers: Record<string, string>;
28
+ /** The only bodies the SDK sends: a JSON string or a multipart `FormData`. */
29
+ body?: string | FormData;
30
+ signal?: AbortSignal;
31
+ }
32
+ export interface HttpTransportOptions {
33
+ apiKey: string;
34
+ baseUrl: string;
35
+ fetch: typeof fetch;
36
+ }
37
+ export declare class HttpTransport {
38
+ #private;
39
+ readonly baseUrl: string;
40
+ readonly apiKey: string;
41
+ readonly fetch: typeof fetch;
42
+ constructor(options: HttpTransportOptions);
43
+ get lastRateLimit(): RateLimit | null;
44
+ /** The bearer + JSON `Accept` headers, plus any per-request additions. */
45
+ authHeaders(extra?: Record<string, string>): Record<string, string>;
46
+ buildUrl(path: string, query?: QueryParams): string;
47
+ /**
48
+ * Send one request, capture rate limits, read the body, and map a non-2xx or
49
+ * transport failure to a {@link MaterialGraphError}. The parsed JSON body (or
50
+ * raw text when unparseable) is returned on success. Not for streaming reads —
51
+ * it consumes the whole body via `response.text()`.
52
+ */
53
+ send(input: SendInput): Promise<SentResponse>;
54
+ /** Execute a registered JSON operation and return its typed body. */
55
+ requestJson<T>(sdkOperation: SdkOperation, options?: RequestOptions): Promise<T>;
56
+ }
57
+ export {};
@@ -0,0 +1,131 @@
1
+ /**
2
+ * The shared low-level HTTP transport for {@link MaterialGraphClient}. It owns
3
+ * URL building, the bearer header, rate-limit capture, response reading, and the
4
+ * one error-mapping path — so the JSON read surface (`client.ts`), the multipart
5
+ * upload, and the SSE stream (`runs.ts`) all fail identically. Retry-free by
6
+ * design; a single failure surfaces as one {@link MaterialGraphError}.
7
+ */
8
+ import { MaterialGraphError } from "./errors.js";
9
+ import { operationPath } from "./operations.js";
10
+ function parseIntOrNull(value) {
11
+ if (value === null)
12
+ return null;
13
+ const parsed = Number.parseInt(value, 10);
14
+ return Number.isNaN(parsed) ? null : parsed;
15
+ }
16
+ function parseRateLimit(headers) {
17
+ const limit = parseIntOrNull(headers.get("x-ratelimit-limit"));
18
+ const remaining = parseIntOrNull(headers.get("x-ratelimit-remaining"));
19
+ const reset = parseIntOrNull(headers.get("x-ratelimit-reset"));
20
+ const retryAfter = parseIntOrNull(headers.get("retry-after"));
21
+ if (limit === null && remaining === null && reset === null && retryAfter === null) {
22
+ return null;
23
+ }
24
+ return { limit, remaining, reset, retryAfter };
25
+ }
26
+ export class HttpTransport {
27
+ baseUrl;
28
+ apiKey;
29
+ fetch;
30
+ #lastRateLimit = null;
31
+ constructor(options) {
32
+ this.apiKey = options.apiKey;
33
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
34
+ this.fetch = options.fetch;
35
+ }
36
+ get lastRateLimit() {
37
+ return this.#lastRateLimit;
38
+ }
39
+ /** The bearer + JSON `Accept` headers, plus any per-request additions. */
40
+ authHeaders(extra) {
41
+ return { Authorization: `Bearer ${this.apiKey}`, Accept: "application/json", ...extra };
42
+ }
43
+ buildUrl(path, query) {
44
+ const url = new URL(`${this.baseUrl}${path}`);
45
+ if (query) {
46
+ for (const [key, value] of Object.entries(query)) {
47
+ if (value === undefined || value === null)
48
+ continue;
49
+ if (Array.isArray(value)) {
50
+ for (const item of value) {
51
+ if (item !== undefined && item !== null)
52
+ url.searchParams.append(key, String(item));
53
+ }
54
+ }
55
+ else {
56
+ url.searchParams.append(key, String(value));
57
+ }
58
+ }
59
+ }
60
+ return url.toString();
61
+ }
62
+ /**
63
+ * Send one request, capture rate limits, read the body, and map a non-2xx or
64
+ * transport failure to a {@link MaterialGraphError}. The parsed JSON body (or
65
+ * raw text when unparseable) is returned on success. Not for streaming reads —
66
+ * it consumes the whole body via `response.text()`.
67
+ */
68
+ async send(input) {
69
+ const requestUrl = new URL(input.url);
70
+ const metadata = {
71
+ method: input.method,
72
+ origin: requestUrl.origin,
73
+ path: requestUrl.pathname,
74
+ };
75
+ let response;
76
+ try {
77
+ response = await this.fetch(input.url, {
78
+ method: input.method,
79
+ headers: input.headers,
80
+ body: input.body,
81
+ signal: input.signal,
82
+ });
83
+ }
84
+ catch (cause) {
85
+ throw MaterialGraphError.fromTransport({ ...metadata, cause, callerSignal: input.signal });
86
+ }
87
+ const rateLimit = parseRateLimit(response.headers);
88
+ this.#lastRateLimit = rateLimit;
89
+ let text;
90
+ try {
91
+ text = await response.text();
92
+ }
93
+ catch (cause) {
94
+ throw MaterialGraphError.fromTransport({ ...metadata, cause, callerSignal: input.signal });
95
+ }
96
+ let parsed;
97
+ if (text.length > 0) {
98
+ try {
99
+ parsed = JSON.parse(text);
100
+ }
101
+ catch {
102
+ parsed = text;
103
+ }
104
+ }
105
+ if (!response.ok) {
106
+ throw MaterialGraphError.fromResponse({
107
+ ...metadata,
108
+ status: response.status,
109
+ body: parsed,
110
+ responseText: text,
111
+ rateLimit,
112
+ apiKey: this.apiKey,
113
+ queryValues: [...requestUrl.searchParams.values()],
114
+ });
115
+ }
116
+ return { response, text, parsed };
117
+ }
118
+ /** Execute a registered JSON operation and return its typed body. */
119
+ async requestJson(sdkOperation, options = {}) {
120
+ const url = this.buildUrl(operationPath(sdkOperation, options.pathParams), options.query);
121
+ const hasBody = options.body !== undefined;
122
+ const { parsed } = await this.send({
123
+ method: sdkOperation.method,
124
+ url,
125
+ headers: this.authHeaders(hasBody ? { "Content-Type": "application/json" } : undefined),
126
+ body: hasBody ? JSON.stringify(options.body) : undefined,
127
+ signal: options.signal,
128
+ });
129
+ return parsed;
130
+ }
131
+ }
package/index.d.ts CHANGED
@@ -2,11 +2,23 @@ export { MaterialGraphClient, DEFAULT_BASE_URL, MAX_BATCH_IDS } from "./client.j
2
2
  export type { MaterialGraphClientOptions, BrowseNamespace } from "./client.js";
3
3
  export { MATERIALGRAPH_API_VERSION, SDK_OPERATIONS } from "./operations.js";
4
4
  export type { SdkOperation, SdkOperationId } from "./operations.js";
5
+ export { TERMINAL_RUN_EVENT_TYPES } from "./run-types.js";
6
+ export type * from "./run-types.js";
7
+ export { VARY_AXES } from "./composition-types.js";
8
+ export type * from "./composition-types.js";
9
+ export { PROJECT_EVENT_KINDS } from "./project-types.js";
10
+ export type * from "./project-types.js";
11
+ export { COMPOSITION_PHASE_EVENT_TYPES, COMPOSITION_PROGRAMME_EVENT_TYPES, COMPOSITION_REVIEW_CRITERIA_EVENT_TYPES, parseRunEvent, } from "./run-events.js";
12
+ export type * from "./run-events.js";
5
13
  export { MaterialGraphError } from "./errors.js";
6
14
  export type { MaterialGraphErrorInit, MaterialGraphErrorKind, MaterialGraphRequestMetadata, ProblemDetail, RateLimit, SanitizedErrorValue, SanitizedIssue, SimpleErrorBody, } from "./errors.js";
7
15
  export type * from "./types.js";
16
+ export type * from "./codes-types.js";
8
17
  export type * from "./brand-types.js";
9
18
  export type * from "./material-asset-types.js";
10
19
  export type * from "./material-block-types.js";
11
20
  export type * from "./schema-resource-types.js";
12
21
  export type * from "./search-refinement-types.js";
22
+ export { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID } from "./spec-types.js";
23
+ export type * from "./spec-types.js";
24
+ export { certificationProgramme, certificationProgrammeCriterion, hasSpec, specCriterion, specTerm, } from "./specifications.js";
package/index.js CHANGED
@@ -1,3 +1,9 @@
1
1
  export { MaterialGraphClient, DEFAULT_BASE_URL, MAX_BATCH_IDS } from "./client.js";
2
2
  export { MATERIALGRAPH_API_VERSION, SDK_OPERATIONS } from "./operations.js";
3
+ export { TERMINAL_RUN_EVENT_TYPES } from "./run-types.js";
4
+ export { VARY_AXES } from "./composition-types.js";
5
+ export { PROJECT_EVENT_KINDS } from "./project-types.js";
6
+ export { COMPOSITION_PHASE_EVENT_TYPES, COMPOSITION_PROGRAMME_EVENT_TYPES, COMPOSITION_REVIEW_CRITERIA_EVENT_TYPES, parseRunEvent, } from "./run-events.js";
3
7
  export { MaterialGraphError } from "./errors.js";
8
+ export { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID } from "./spec-types.js";
9
+ export { certificationProgramme, certificationProgrammeCriterion, hasSpec, specCriterion, specTerm, } from "./specifications.js";
package/operations.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export declare const MATERIALGRAPH_API_VERSION = "0.6.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/{slug}", "get_brand_profile"]];
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"]];
3
3
  export type SdkOperationId = (typeof OPERATION_DEFINITIONS)[number][3];
4
4
  export type SdkOperation = Readonly<{
5
5
  clientMethod: (typeof OPERATION_DEFINITIONS)[number][0];
@@ -8,4 +8,8 @@ export type SdkOperation = Readonly<{
8
8
  operationId: SdkOperationId;
9
9
  }>;
10
10
  export declare const SDK_OPERATIONS: readonly SdkOperation[];
11
+ /** Look up a registered operation by its OpenAPI `operationId`. */
12
+ export declare function findSdkOperation(operationId: SdkOperationId): SdkOperation | undefined;
13
+ /** Substitute `{param}` path placeholders with percent-encoded values. */
14
+ export declare function operationPath(sdkOperation: SdkOperation, pathParams?: Readonly<Record<string, string>>): string;
11
15
  export {};
package/operations.js CHANGED
@@ -1,4 +1,4 @@
1
- export const MATERIALGRAPH_API_VERSION = "0.6.0";
1
+ export const MATERIALGRAPH_API_VERSION = "0.7.0";
2
2
  const OPERATION_DEFINITIONS = [
3
3
  ["search", "POST", "/api/v1/search", "search_materials_post"],
4
4
  ["searchGet", "GET", "/api/v1/search", "search_materials_get"],
@@ -48,6 +48,28 @@ const OPERATION_DEFINITIONS = [
48
48
  ["applicableSpecs", "GET", "/api/v1/taxonomy/categories/{key}/specs", "get_applicable_specs"],
49
49
  ["glossary", "GET", "/api/v1/glossary/{term}", "explain_material_term"],
50
50
  ["brands", "GET", "/api/v1/brands", "list_brands"],
51
- ["brand", "GET", "/api/v1/brands/{slug}", "get_brand_profile"],
51
+ ["brand", "GET", "/api/v1/brands/{brandSlug}", "get_brand_profile"],
52
+ ["uploadSourceMaterial", "POST", "/api/v1/source-material", "upload_source_material"],
53
+ ["capabilities", "GET", "/api/v1/capabilities", "list_public_capabilities"],
54
+ ["createRun", "POST", "/api/v1/runs", "create_public_run"],
55
+ ["run", "GET", "/api/v1/runs/{runId}", "get_public_run"],
56
+ ["streamRunEvents", "GET", "/api/v1/runs/{runId}/events", "list_public_run_events"],
57
+ ["resumeRun", "POST", "/api/v1/runs/{runId}/resume", "resume_public_run"],
58
+ ["cancelRun", "POST", "/api/v1/runs/{runId}/cancel", "cancel_public_run"],
59
+ ["varyScheme", "POST", "/api/v1/schemes/vary", "vary_schemes"],
60
+ ["createProject", "POST", "/api/v1/projects", "create_box_project"],
61
+ ["listProjects", "GET", "/api/v1/projects", "list_box_projects"],
62
+ ["getProject", "GET", "/api/v1/projects/{projectId}", "get_box_project"],
63
+ ["appendProjectEvent", "POST", "/api/v1/projects/{projectId}/events", "append_box_project_event"],
64
+ ["demandBatch", "POST", "/api/v1/demand/batch", "get_demand_batch"],
65
+ ["codeAdvisory", "POST", "/api/v1/codes/advisory", "resolveCodeAdvisory"],
52
66
  ];
53
67
  export const SDK_OPERATIONS = Object.freeze(OPERATION_DEFINITIONS.map(([clientMethod, method, path, operationId]) => Object.freeze({ clientMethod, method, path, operationId })));
68
+ /** Look up a registered operation by its OpenAPI `operationId`. */
69
+ export function findSdkOperation(operationId) {
70
+ return SDK_OPERATIONS.find((candidate) => candidate.operationId === operationId);
71
+ }
72
+ /** Substitute `{param}` path placeholders with percent-encoded values. */
73
+ export function operationPath(sdkOperation, pathParams = {}) {
74
+ return Object.entries(pathParams).reduce((path, [name, value]) => path.replace(`{${name}}`, encodeURIComponent(value)), sdkOperation.path);
75
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materialgraph",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Wire types for the persistent Material Box project surface (`/api/v1/projects`).
3
+ * Hand-mirrored from `@materialgraph/products/projects`' Zod contract — the SDK
4
+ * has zero runtime dependency on `@materialgraph/*`. Kept shape-identical to the
5
+ * server contract; the OpenAPI parity test gates method/path/operationId, and the
6
+ * server route schemas gate the body shapes.
7
+ */
8
+ /** The six governed project-event kinds. */
9
+ export type ProjectEventKind = "batch-attached" | "run-started" | "clarifying-answer" | "pairwise-preference" | "reference-chosen" | "scheme-adopted";
10
+ /** The frozen tuple of every project-event kind, for enumeration. */
11
+ export declare const PROJECT_EVENT_KINDS: readonly ProjectEventKind[];
12
+ /** One clarifying question and the designer's answer. */
13
+ export interface ClarifyingAnswer {
14
+ question: string;
15
+ answer: string;
16
+ }
17
+ /** One pairwise preference; `chosenId` is `leftId` or `rightId`. */
18
+ export interface PairwisePreference {
19
+ leftId: string;
20
+ rightId: string;
21
+ chosenId: string;
22
+ }
23
+ /** One stored event; `payload` is validated to the shape of `kind`. */
24
+ export interface ProjectEvent {
25
+ eventId: string;
26
+ kind: ProjectEventKind;
27
+ payload: Record<string, unknown>;
28
+ createdAt: string;
29
+ }
30
+ /** A lightweight project row for list views (no event fold). */
31
+ export interface ProjectSummary {
32
+ projectId: string;
33
+ principalId: string;
34
+ name: string;
35
+ createdAt: string;
36
+ updatedAt: string;
37
+ }
38
+ /** A project's assembled read model — identity plus the folded event collections. */
39
+ export interface ProjectState extends ProjectSummary {
40
+ batchIds: string[];
41
+ runIds: string[];
42
+ answers: ClarifyingAnswer[];
43
+ preferences: PairwisePreference[];
44
+ chosenReferences: string[];
45
+ adoptedSchemes: string[];
46
+ events: ProjectEvent[];
47
+ }
48
+ /** `GET /api/v1/projects` — the caller's projects, newest first. */
49
+ export interface ProjectListResponse {
50
+ projects: ProjectSummary[];
51
+ }
52
+ /** `POST /api/v1/projects` request body. */
53
+ export interface CreateProjectRequest {
54
+ name?: string;
55
+ }
56
+ /** `POST /api/v1/projects/{projectId}/events` request body. */
57
+ export interface AppendProjectEventRequest {
58
+ kind: ProjectEventKind;
59
+ payload: Record<string, unknown>;
60
+ }
61
+ /** `POST /api/v1/projects/{projectId}/events` response — the appended event receipt. */
62
+ export interface ProjectEventReceipt {
63
+ eventId: string;
64
+ projectId: string;
65
+ kind: ProjectEventKind;
66
+ createdAt: string;
67
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Wire types for the persistent Material Box project surface (`/api/v1/projects`).
3
+ * Hand-mirrored from `@materialgraph/products/projects`' Zod contract — the SDK
4
+ * has zero runtime dependency on `@materialgraph/*`. Kept shape-identical to the
5
+ * server contract; the OpenAPI parity test gates method/path/operationId, and the
6
+ * server route schemas gate the body shapes.
7
+ */
8
+ /** The frozen tuple of every project-event kind, for enumeration. */
9
+ export const PROJECT_EVENT_KINDS = Object.freeze([
10
+ "batch-attached",
11
+ "run-started",
12
+ "clarifying-answer",
13
+ "pairwise-preference",
14
+ "reference-chosen",
15
+ "scheme-adopted",
16
+ ]);
package/projects.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { HttpTransport } from "./http-transport.js";
2
+ import type { AppendProjectEventRequest, CreateProjectRequest, ProjectEventReceipt, ProjectListResponse, ProjectState } from "./project-types.js";
3
+ /** Create a project (`POST /api/v1/projects`). Requires the `mg:ingest` scope. */
4
+ export declare function createProject(transport: HttpTransport, body?: CreateProjectRequest, signal?: AbortSignal): Promise<ProjectState>;
5
+ /** Read a project's assembled state (`GET /api/v1/projects/{projectId}`). */
6
+ export declare function getProject(transport: HttpTransport, projectId: string, signal?: AbortSignal): Promise<ProjectState>;
7
+ /**
8
+ * Append one event to a project's process trail
9
+ * (`POST /api/v1/projects/{projectId}/events`). Requires the `mg:ingest` scope.
10
+ */
11
+ export declare function appendProjectEvent(transport: HttpTransport, projectId: string, body: AppendProjectEventRequest, signal?: AbortSignal): Promise<ProjectEventReceipt>;
12
+ /** List your own projects, newest first (`GET /api/v1/projects`). */
13
+ export declare function listProjects(transport: HttpTransport, signal?: AbortSignal): Promise<ProjectListResponse>;
package/projects.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The persistent-project namespace of the SDK. Thin wrappers over the shared
3
+ * {@link HttpTransport} for the `/api/v1/projects` surface: create a project,
4
+ * read its assembled state, append process-trail events, and list your own.
5
+ * Box's proxy routes are thin wrappers over exactly these calls.
6
+ */
7
+ import { MaterialGraphError } from "./errors.js";
8
+ import { HttpTransport } from "./http-transport.js";
9
+ import { findSdkOperation } from "./operations.js";
10
+ function projectOperation(operationId) {
11
+ const match = findSdkOperation(operationId);
12
+ if (!match)
13
+ throw MaterialGraphError.unexpected(`Unknown SDK operation: ${operationId}`);
14
+ return match;
15
+ }
16
+ /** Create a project (`POST /api/v1/projects`). Requires the `mg:ingest` scope. */
17
+ export function createProject(transport, body = {}, signal) {
18
+ return transport.requestJson(projectOperation("create_box_project"), {
19
+ body,
20
+ signal,
21
+ });
22
+ }
23
+ /** Read a project's assembled state (`GET /api/v1/projects/{projectId}`). */
24
+ export function getProject(transport, projectId, signal) {
25
+ return transport.requestJson(projectOperation("get_box_project"), {
26
+ pathParams: { projectId },
27
+ signal,
28
+ });
29
+ }
30
+ /**
31
+ * Append one event to a project's process trail
32
+ * (`POST /api/v1/projects/{projectId}/events`). Requires the `mg:ingest` scope.
33
+ */
34
+ export function appendProjectEvent(transport, projectId, body, signal) {
35
+ return transport.requestJson(projectOperation("append_box_project_event"), {
36
+ body,
37
+ pathParams: { projectId },
38
+ signal,
39
+ });
40
+ }
41
+ /** List your own projects, newest first (`GET /api/v1/projects`). */
42
+ export function listProjects(transport, signal) {
43
+ return transport.requestJson(projectOperation("list_box_projects"), {
44
+ signal,
45
+ });
46
+ }