@instruments/materialgraph 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/by-spec-types.d.ts +73 -0
- package/by-spec-types.js +7 -0
- package/client-browse.d.ts +46 -0
- package/client-browse.js +42 -0
- package/client.d.ts +83 -28
- package/client.js +99 -47
- package/codes-client.d.ts +82 -0
- package/codes-client.js +73 -0
- package/codes-types.d.ts +154 -9
- package/colour-aggregate-types.d.ts +93 -0
- package/colour-aggregate-types.js +1 -0
- package/colours-client.d.ts +52 -0
- package/colours-client.js +39 -0
- package/composition-types.d.ts +228 -2
- package/http-transport.d.ts +19 -2
- package/http-transport.js +72 -2
- package/index.d.ts +7 -0
- package/index.js +3 -0
- package/match-stream-types.d.ts +150 -0
- package/match-stream-types.js +28 -0
- package/match-stream.d.ts +22 -0
- package/match-stream.js +280 -0
- package/material-asset-types.d.ts +1 -1
- package/material-block-types.d.ts +23 -2
- package/material-types.d.ts +92 -0
- package/material-types.js +1 -0
- package/ndjson.d.ts +46 -0
- package/ndjson.js +103 -0
- package/operations.d.ts +1 -1
- package/operations.js +7 -0
- package/package.json +1 -1
- package/run-types.d.ts +65 -1
- package/runs.d.ts +45 -3
- package/runs.js +82 -43
- package/schedule-types.d.ts +88 -0
- package/schedule-types.js +12 -0
- package/search-refinement-types.d.ts +13 -0
- package/search-result-validation.d.ts +27 -0
- package/search-result-validation.js +174 -0
- package/source-material-validation.d.ts +3 -0
- package/source-material-validation.js +109 -0
- package/spec-types.d.ts +4 -0
- package/spec-types.js +4 -0
- package/specifications.d.ts +9 -0
- package/specifications.js +9 -0
- package/suggest-types.d.ts +58 -0
- package/suggest-types.js +21 -0
- package/types.d.ts +60 -73
- package/validation.d.ts +119 -0
- package/validation.js +241 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export interface BySpecMatchedCell {
|
|
2
|
+
attributeId: string;
|
|
3
|
+
confidence: number | null;
|
|
4
|
+
sourceBasis: string | null;
|
|
5
|
+
}
|
|
6
|
+
/** The six states a verdict cell can report; open per the response-enum policy. */
|
|
7
|
+
export type BySpecVerdictState = "matched" | "deviated" | "failed" | "unverified" | "missing" | "not_evaluated";
|
|
8
|
+
/**
|
|
9
|
+
* One cell of a match's report card — how a single requested criterion scored.
|
|
10
|
+
* Hand-mirrors the `AttributeVerdict` contract from `@materialgraph/contracts`;
|
|
11
|
+
* the OpenAPI `CatalogBySpecResponse` schema is the source of truth on the wire.
|
|
12
|
+
*/
|
|
13
|
+
export interface BySpecVerdictCell {
|
|
14
|
+
attributeId: string;
|
|
15
|
+
state: BySpecVerdictState;
|
|
16
|
+
strength: "required" | "preferred";
|
|
17
|
+
baseline: {
|
|
18
|
+
kind: "term";
|
|
19
|
+
term: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "presence";
|
|
22
|
+
} | {
|
|
23
|
+
kind: "seed";
|
|
24
|
+
variantId: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "image";
|
|
27
|
+
source: "variant" | "vector";
|
|
28
|
+
variantId: string | null;
|
|
29
|
+
};
|
|
30
|
+
value: string | null;
|
|
31
|
+
deviation: {
|
|
32
|
+
direction: "above" | "below" | "other";
|
|
33
|
+
delta: number | null;
|
|
34
|
+
unit: string | null;
|
|
35
|
+
label: string;
|
|
36
|
+
} | null;
|
|
37
|
+
confidence: number | null;
|
|
38
|
+
sourceBasis: string | null;
|
|
39
|
+
trustGap: "below_confidence" | "basis_unmet" | "proposed" | null;
|
|
40
|
+
}
|
|
41
|
+
/** A match's full report card: every cell, the rolled-up grade, weakest confidence. */
|
|
42
|
+
export interface BySpecVerdict {
|
|
43
|
+
cells: BySpecVerdictCell[];
|
|
44
|
+
grade: "meets_all" | "close" | "hard_no";
|
|
45
|
+
minConfidence: number | null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The verdict a materials-search hit carries (MG-625). It IS the shared
|
|
49
|
+
* {@link BySpecVerdict} shape — the single CandidateVerdict mirror — aliased here
|
|
50
|
+
* so search-surface code can name it for what it is rather than duplicating it.
|
|
51
|
+
*/
|
|
52
|
+
export type MaterialVerdict = BySpecVerdict;
|
|
53
|
+
export interface BySpecMatch {
|
|
54
|
+
variantId: string;
|
|
55
|
+
brand: string;
|
|
56
|
+
product: string;
|
|
57
|
+
variant: string;
|
|
58
|
+
color: string | null;
|
|
59
|
+
imageUrl: string | null;
|
|
60
|
+
hex: string | null;
|
|
61
|
+
matched: BySpecMatchedCell[];
|
|
62
|
+
minConfidence: number | null;
|
|
63
|
+
verdict: BySpecVerdict;
|
|
64
|
+
}
|
|
65
|
+
export interface BySpecResponse {
|
|
66
|
+
criteria: Array<{
|
|
67
|
+
attribute: string;
|
|
68
|
+
value: string;
|
|
69
|
+
}>;
|
|
70
|
+
totalMatches: number;
|
|
71
|
+
brandsSpanned: number;
|
|
72
|
+
matches: BySpecMatch[];
|
|
73
|
+
}
|
package/by-spec-types.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// ── Catalog / spec — find-by-spec wire types ─────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Hand-maintained mirror of the `CatalogBySpecResponse` OpenAPI schema (the
|
|
4
|
+
// source of truth on the wire); the `sdk-openapi-parity` test gates this against
|
|
5
|
+
// the generated spec. Split out of `types.ts` to keep that file under its growth
|
|
6
|
+
// baseline; re-exported from `types.ts` so importers see no change.
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `browse` namespace's implementation — the paged colour-ordered walk of the
|
|
3
|
+
* corpus behind `client.browse(...)` and `client.browse.pages(...)`.
|
|
4
|
+
*
|
|
5
|
+
* Lifted out of `client.ts` for the repository's source-file size budget. These
|
|
6
|
+
* were already private implementations behind a public namespace, so nothing
|
|
7
|
+
* about the client's surface changes: `client.browse` is still a callable with a
|
|
8
|
+
* `.pages()` generator, and the class still owns the transport. What moved is
|
|
9
|
+
* the pagination LOOP, which is the only real logic here and the only part worth
|
|
10
|
+
* testing on its own.
|
|
11
|
+
*/
|
|
12
|
+
import type { BrowseParams, BrowseResponse } from "./types.js";
|
|
13
|
+
/** `client.browse(...)` — callable for one page, `.pages()` for the whole walk. */
|
|
14
|
+
export interface BrowseNamespace {
|
|
15
|
+
(params: BrowseParams, signal?: AbortSignal): Promise<BrowseResponse>;
|
|
16
|
+
pages(params: BrowseParams, signal?: AbortSignal): AsyncGenerator<BrowseResponse, void, unknown>;
|
|
17
|
+
}
|
|
18
|
+
/** Fetch one page. The client supplies this, so the transport stays private. */
|
|
19
|
+
export type BrowsePageFetcher = (params: BrowseParams, signal?: AbortSignal) => Promise<BrowseResponse>;
|
|
20
|
+
/**
|
|
21
|
+
* The query string for one browse page — the four params the route reads, named
|
|
22
|
+
* here so the client method and the pagination loop cannot disagree about them.
|
|
23
|
+
*/
|
|
24
|
+
export declare function browseQuery(params: BrowseParams): {
|
|
25
|
+
sort: BrowseParams["sort"];
|
|
26
|
+
cursor: BrowseParams["cursor"];
|
|
27
|
+
limit: BrowseParams["limit"];
|
|
28
|
+
types: BrowseParams["types"];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Walk every page from `params.cursor` onward.
|
|
32
|
+
*
|
|
33
|
+
* Stops on the first page that reports no next cursor OR comes back empty. Both
|
|
34
|
+
* conditions matter: a server that keeps handing out a cursor for an exhausted
|
|
35
|
+
* scan would otherwise spin here forever, and an infinite generator is a hang,
|
|
36
|
+
* not an error anyone can see.
|
|
37
|
+
*/
|
|
38
|
+
export declare function browsePages(fetchPage: BrowsePageFetcher, params: BrowseParams, signal?: AbortSignal): AsyncGenerator<BrowseResponse, void, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* Build the callable-with-`.pages()` object the client exposes as `browse`.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const page = await client.browse({ sort: "lightest" });
|
|
44
|
+
* for await (const page of client.browse.pages({ sort: "lightest" })) { … }
|
|
45
|
+
*/
|
|
46
|
+
export declare function createBrowseNamespace(fetchPage: BrowsePageFetcher): BrowseNamespace;
|
package/client-browse.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The query string for one browse page — the four params the route reads, named
|
|
3
|
+
* here so the client method and the pagination loop cannot disagree about them.
|
|
4
|
+
*/
|
|
5
|
+
export function browseQuery(params) {
|
|
6
|
+
return {
|
|
7
|
+
sort: params.sort,
|
|
8
|
+
cursor: params.cursor,
|
|
9
|
+
limit: params.limit,
|
|
10
|
+
types: params.types,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Walk every page from `params.cursor` onward.
|
|
15
|
+
*
|
|
16
|
+
* Stops on the first page that reports no next cursor OR comes back empty. Both
|
|
17
|
+
* conditions matter: a server that keeps handing out a cursor for an exhausted
|
|
18
|
+
* scan would otherwise spin here forever, and an infinite generator is a hang,
|
|
19
|
+
* not an error anyone can see.
|
|
20
|
+
*/
|
|
21
|
+
export async function* browsePages(fetchPage, params, signal) {
|
|
22
|
+
let cursor = params.cursor;
|
|
23
|
+
for (;;) {
|
|
24
|
+
const page = await fetchPage({ ...params, cursor }, signal);
|
|
25
|
+
yield page;
|
|
26
|
+
if (page.cursor === undefined || page.results.length === 0)
|
|
27
|
+
return;
|
|
28
|
+
cursor = page.cursor;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build the callable-with-`.pages()` object the client exposes as `browse`.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const page = await client.browse({ sort: "lightest" });
|
|
36
|
+
* for await (const page of client.browse.pages({ sort: "lightest" })) { … }
|
|
37
|
+
*/
|
|
38
|
+
export function createBrowseNamespace(fetchPage) {
|
|
39
|
+
const browse = (params, signal) => fetchPage(params, signal);
|
|
40
|
+
browse.pages = (params, signal) => browsePages(fetchPage, params, signal);
|
|
41
|
+
return browse;
|
|
42
|
+
}
|
package/client.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { type RateLimit } from "./errors.js";
|
|
2
|
-
import
|
|
2
|
+
import { type BrowseNamespace } from "./client-browse.js";
|
|
3
|
+
export type { BrowseNamespace } from "./client-browse.js";
|
|
4
|
+
import type { CapabilityManifestResponse, CreatePublicRunRequest, CreatePublicRunResponse, DrainRunEventsOptions, PublicRunResponse, ResumeRunOptions, RunEvent, SourceMaterialBatchDetail, SourceMaterialBatchReceipt, StreamRunEventsOptions, UploadSourceMaterialInput } from "./run-types.js";
|
|
5
|
+
import type { FinishScheduleResponse, GetRunScheduleOptions } from "./schedule-types.js";
|
|
6
|
+
import type { MatchStreamEvent } from "./match-stream-types.js";
|
|
3
7
|
import type { AppendProjectEventRequest, CreateProjectRequest, ProjectEventReceipt, ProjectListResponse, ProjectState } from "./project-types.js";
|
|
4
8
|
import type { VarySchemesRequest, VarySchemesResponse } from "./composition-types.js";
|
|
9
|
+
import { CodesClient } from "./codes-client.js";
|
|
10
|
+
import { ColoursClient } from "./colours-client.js";
|
|
5
11
|
import type { CodeAdvisoryInput, CodeAdvisoryResponse } from "./codes-types.js";
|
|
6
12
|
import type { BrandCatalogListPage, BrandsOptions } from "./brand-types.js";
|
|
7
13
|
import type { MaterialAssetProjection } from "./material-asset-types.js";
|
|
8
14
|
import type { MaterialBlockProjection } from "./material-block-types.js";
|
|
9
15
|
import type { SchemaResource, SchemaResourceResponse } from "./schema-resource-types.js";
|
|
10
|
-
import type { ApplicableSpecsResponse, BrandOptions, BrandProfileResponse, ByColourParams, ByColourResponse,
|
|
16
|
+
import type { ApplicableSpecsResponse, BrandOptions, BrandProfileResponse, ByColourParams, ByColourResponse, BySpecOptions, BySpecResponse, ColourFacetsResponse, CoordinateInput, CoordinateResponse, DemandBatchInput, DemandBatchResponse, DemandParams, DemandResponse, ExtractPaletteInput, ExtractPaletteResponse, GlossaryTermResponse, MaterialResponse, MaterialsBatchDetailResponse, MaterialsBatchGetOptions, MaterialsBatchOptions, MaterialsBatchTileResponse, MatchInput, MatchResponse, PairingsOptions, PairingsResponse, ResolveAttributeOptions, ResolveAttributeResponse, ResolveAttributeValueOptions, ResolveAttributeValueResponse, ResolveColourResponse, SearchGetParams, SearchInput, SearchResponse, SiblingsResponse, SpecFacetsDistributionResponse, SpecFacetsListResponse, SuggestColourResponse, SuggestOptions, SuggestResponse, TaxonomyResponse, VocabularySearchOptions, VocabularySearchResponse, WhoamiResponse } from "./types.js";
|
|
11
17
|
export declare const DEFAULT_BASE_URL = "https://beta.materialgraph.com";
|
|
12
18
|
export declare const MAX_BATCH_IDS = 100;
|
|
13
19
|
export interface MaterialGraphClientOptions {
|
|
@@ -18,14 +24,21 @@ export interface MaterialGraphClientOptions {
|
|
|
18
24
|
/** Inject a custom `fetch` (tests, proxies). Defaults to the global `fetch`. */
|
|
19
25
|
fetch?: typeof fetch;
|
|
20
26
|
}
|
|
21
|
-
export interface BrowseNamespace {
|
|
22
|
-
(params: BrowseParams, signal?: AbortSignal): Promise<BrowseResponse>;
|
|
23
|
-
pages(params: BrowseParams, signal?: AbortSignal): AsyncGenerator<BrowseResponse, void, unknown>;
|
|
24
|
-
}
|
|
25
27
|
export declare class MaterialGraphClient {
|
|
26
28
|
#private;
|
|
27
29
|
readonly baseUrl: string;
|
|
28
30
|
readonly browse: BrowseNamespace;
|
|
31
|
+
/**
|
|
32
|
+
* The advisory building-code surface: `codes.context()` to discover the
|
|
33
|
+
* vocabulary, `codes.requirements()` to state a space, `codes.advisory()` to
|
|
34
|
+
* badge candidates against it.
|
|
35
|
+
*/
|
|
36
|
+
readonly codes: CodesClient;
|
|
37
|
+
/**
|
|
38
|
+
* The colour AGGREGATE surface: `colours.profiles()` for a category palette,
|
|
39
|
+
* `colours.stats()` for corpus colour coverage.
|
|
40
|
+
*/
|
|
41
|
+
readonly colours: ColoursClient;
|
|
29
42
|
constructor(options: MaterialGraphClientOptions);
|
|
30
43
|
get lastRateLimit(): RateLimit | null;
|
|
31
44
|
/**
|
|
@@ -42,6 +55,30 @@ export declare class MaterialGraphClient {
|
|
|
42
55
|
search(input: SearchInput, signal?: AbortSignal): Promise<SearchResponse>;
|
|
43
56
|
searchGet(params: SearchGetParams, signal?: AbortSignal): Promise<SearchResponse>;
|
|
44
57
|
match(input: MatchInput, signal?: AbortSignal): Promise<MatchResponse>;
|
|
58
|
+
/**
|
|
59
|
+
* Material Match as a PROGRESSIVE stream — the same POST
|
|
60
|
+
* `/api/v1/search/match` endpoint negotiated with `Accept:
|
|
61
|
+
* application/x-ndjson`, which runs the scene workflow (classify → decompose →
|
|
62
|
+
* one match per surface) and emits an event as each surface resolves. Use this
|
|
63
|
+
* wherever the user watches a photo come apart; use {@link match} when you only
|
|
64
|
+
* want the finished answer.
|
|
65
|
+
*
|
|
66
|
+
* Iterate it directly — the request opens on the first `next()`:
|
|
67
|
+
*
|
|
68
|
+
* ```ts
|
|
69
|
+
* for await (const event of client.matchStream({ imageBase64 })) {
|
|
70
|
+
* if (event.type === "surface") render(event.surface);
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* Every frame is validated before it is yielded, and the iterator ends only on
|
|
75
|
+
* a `complete` or `error` event — a truncated connection raises rather than
|
|
76
|
+
* passing for a short run. `signal` cancels the in-flight request itself, as
|
|
77
|
+
* does breaking out of the loop; a cancelled stream ends without raising.
|
|
78
|
+
* Requires an image (`imageBase64` or `imageUrl`); a `vector` input is
|
|
79
|
+
* buffered-only.
|
|
80
|
+
*/
|
|
81
|
+
matchStream(input: MatchInput, signal?: AbortSignal): AsyncGenerator<MatchStreamEvent, void, unknown>;
|
|
45
82
|
byColour(params: ByColourParams, signal?: AbortSignal): Promise<ByColourResponse>;
|
|
46
83
|
/** Get a single material's detail record (GET `/api/v1/materials/{variantId}`). */
|
|
47
84
|
material(variantId: string, options?: {
|
|
@@ -91,6 +128,18 @@ export declare class MaterialGraphClient {
|
|
|
91
128
|
suggestColour(q: string, options?: {
|
|
92
129
|
limit?: number;
|
|
93
130
|
}, signal?: AbortSignal): Promise<SuggestColourResponse>;
|
|
131
|
+
/**
|
|
132
|
+
* Entity typeahead across products, brands and categories
|
|
133
|
+
* (GET `/api/v1/search/suggest`). Built for per-keystroke use: one indexed
|
|
134
|
+
* lookup, no embedding call and no model. Every suggestion carries the
|
|
135
|
+
* `refId` it resolves to — a product id, a brand slug, or a product-type key
|
|
136
|
+
* — so selecting one should navigate to that entity rather than re-running
|
|
137
|
+
* `label` as a search.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* const { suggestions } = await client.suggest("carr", { kind: "product" });
|
|
141
|
+
*/
|
|
142
|
+
suggest(q: string, options?: SuggestOptions, signal?: AbortSignal): Promise<SuggestResponse>;
|
|
94
143
|
/** Resolve a free-text spec phrase to canonical attribute ids (GET `/api/v1/catalog/attributes/resolve`). */
|
|
95
144
|
resolveAttribute(q: string, options?: ResolveAttributeOptions, signal?: AbortSignal): Promise<ResolveAttributeResponse>;
|
|
96
145
|
/** Resolve a value phrase against one canonical attribute's dictionary. */
|
|
@@ -146,26 +195,9 @@ export declare class MaterialGraphClient {
|
|
|
146
195
|
*/
|
|
147
196
|
demandBatch(input: DemandBatchInput, signal?: AbortSignal): Promise<DemandBatchResponse>;
|
|
148
197
|
/**
|
|
149
|
-
* Badge
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* `meets` / `shortfall` / `unknown` from the material's accepted fire and
|
|
153
|
-
* slip-resistance facts, in input order. ADVISORY ONLY — every verdict and the
|
|
154
|
-
* envelope carry the AHJ caveat; this is model code as adopted, never a legal
|
|
155
|
-
* determination.
|
|
156
|
-
*
|
|
157
|
-
* @example
|
|
158
|
-
* // Badge this scheme's picks for a Texas hospitality fire-code review.
|
|
159
|
-
* const advisory = await mg.codeAdvisory({
|
|
160
|
-
* variantIds: scheme.picks.map((p) => p.variantId),
|
|
161
|
-
* jurisdictionId: "us-tx",
|
|
162
|
-
* occupancyGroup: "A-2", // assembly — restaurant / bar
|
|
163
|
-
* spaceType: "corridor",
|
|
164
|
-
* sprinklered: true,
|
|
165
|
-
* });
|
|
166
|
-
* const shortfalls = advisory.results.filter((r) =>
|
|
167
|
-
* r.requirements.some((req) => req.verdict === "shortfall"),
|
|
168
|
-
* );
|
|
198
|
+
* Badge materials against the advisory building-code registry (POST
|
|
199
|
+
* `/api/v1/codes/advisory`). Identical to {@link CodesClient.advisory}, kept
|
|
200
|
+
* flat because it shipped before the `codes` namespace existed.
|
|
169
201
|
*/
|
|
170
202
|
codeAdvisory(input: CodeAdvisoryInput, signal?: AbortSignal): Promise<CodeAdvisoryResponse>;
|
|
171
203
|
/** Extract a named colour palette from an uploaded image (POST `/api/v1/search/palettes/extract`). */
|
|
@@ -206,14 +238,37 @@ export declare class MaterialGraphClient {
|
|
|
206
238
|
* `{ brief, references }` shape. Requires the `mg:ingest` scope.
|
|
207
239
|
*/
|
|
208
240
|
uploadSourceMaterial(input: UploadSourceMaterialInput, signal?: AbortSignal): Promise<SourceMaterialBatchReceipt>;
|
|
241
|
+
/**
|
|
242
|
+
* Read back an uploaded batch (GET `/api/v1/source-material/{batchId}`): every
|
|
243
|
+
* file with a fetchable `url` and its analyses inlined. `analyses` is always
|
|
244
|
+
* an array — `[]` means nothing has been computed yet. Analyses are shared by
|
|
245
|
+
* content hash, so re-uploading an already-analysed image returns its analysis
|
|
246
|
+
* immediately. A batch you do not own raises the same 404 as one that does not
|
|
247
|
+
* exist. Requires the `mg:compose` scope.
|
|
248
|
+
*/
|
|
249
|
+
sourceMaterialBatch(batchId: string, signal?: AbortSignal): Promise<SourceMaterialBatchDetail>;
|
|
209
250
|
/** List invocable agents, workflows, and tools (GET `/api/v1/capabilities`). */
|
|
210
251
|
capabilities(signal?: AbortSignal): Promise<CapabilityManifestResponse>;
|
|
211
252
|
/** Start an asynchronous capability Run (POST `/api/v1/runs`). */
|
|
212
253
|
createRun(body: CreatePublicRunRequest, signal?: AbortSignal): Promise<CreatePublicRunResponse>;
|
|
213
254
|
/** Get a Run's current state (GET `/api/v1/runs/{runId}`). */
|
|
214
255
|
run(runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
|
|
215
|
-
/**
|
|
216
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Export a completed compose Run's scheme as a finish schedule — one row per
|
|
258
|
+
* slot, every attribute value carrying its source and verification state
|
|
259
|
+
* (GET `/api/v1/runs/{runId}/schedule`). Unknown values are explicit
|
|
260
|
+
* `missing` cells, never blanks. The `.xlsx` form is a direct-URL download:
|
|
261
|
+
* `GET /api/v1/runs/{runId}/schedule.xlsx` with the same bearer key.
|
|
262
|
+
*/
|
|
263
|
+
runSchedule(runId: string, options?: GetRunScheduleOptions): Promise<FinishScheduleResponse>;
|
|
264
|
+
/**
|
|
265
|
+
* Resume a suspended Run (POST `/api/v1/runs/{runId}/resume`).
|
|
266
|
+
*
|
|
267
|
+
* The third argument accepts either an `AbortSignal` (the published form) or
|
|
268
|
+
* {@link ResumeRunOptions}, whose `stepId` guards against resuming a step the
|
|
269
|
+
* run has already moved past — the API answers 409 on a mismatch (MG-608).
|
|
270
|
+
*/
|
|
271
|
+
resumeRun(runId: string, resumeData: unknown, signalOrOptions?: AbortSignal | ResumeRunOptions): Promise<PublicRunResponse>;
|
|
217
272
|
/** Request durable Run cancellation (POST `/api/v1/runs/{runId}/cancel`). */
|
|
218
273
|
cancelRun(runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
|
|
219
274
|
/**
|
package/client.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { MaterialGraphError } from "./errors.js";
|
|
2
2
|
import { HttpTransport } from "./http-transport.js";
|
|
3
3
|
import { findSdkOperation } from "./operations.js";
|
|
4
|
-
import { cancelRun, capabilities as capabilitiesRun, createRun, drainRunEvents, getRun, resumeRun, streamRunEvents, uploadSourceMaterial, } from "./runs.js";
|
|
4
|
+
import { cancelRun, capabilities as capabilitiesRun, createRun, drainRunEvents, getRun, getRunSchedule, getSourceMaterialBatch as getSourceMaterialBatchCall, resumeRun, streamRunEvents, uploadSourceMaterial, } from "./runs.js";
|
|
5
|
+
import { browseQuery, createBrowseNamespace } from "./client-browse.js";
|
|
6
|
+
import { suggestQuery } from "./suggest-types.js";
|
|
7
|
+
import { matchStream as matchStreamCall } from "./match-stream.js";
|
|
5
8
|
import { appendProjectEvent as appendProjectEventCall, createProject as createProjectCall, getProject as getProjectCall, listProjects as listProjectsCall, } from "./projects.js";
|
|
9
|
+
import { CodesClient } from "./codes-client.js";
|
|
10
|
+
import { ColoursClient } from "./colours-client.js";
|
|
6
11
|
export const DEFAULT_BASE_URL = "https://beta.materialgraph.com";
|
|
7
12
|
export const MAX_BATCH_IDS = 100;
|
|
8
13
|
const SCHEMA_RESOURCE_OPERATION_IDS = {
|
|
@@ -26,6 +31,17 @@ function operation(operationId) {
|
|
|
26
31
|
export class MaterialGraphClient {
|
|
27
32
|
baseUrl;
|
|
28
33
|
browse;
|
|
34
|
+
/**
|
|
35
|
+
* The advisory building-code surface: `codes.context()` to discover the
|
|
36
|
+
* vocabulary, `codes.requirements()` to state a space, `codes.advisory()` to
|
|
37
|
+
* badge candidates against it.
|
|
38
|
+
*/
|
|
39
|
+
codes;
|
|
40
|
+
/**
|
|
41
|
+
* The colour AGGREGATE surface: `colours.profiles()` for a category palette,
|
|
42
|
+
* `colours.stats()` for corpus colour coverage.
|
|
43
|
+
*/
|
|
44
|
+
colours;
|
|
29
45
|
#transport;
|
|
30
46
|
constructor(options) {
|
|
31
47
|
if (!options.apiKey) {
|
|
@@ -37,9 +53,14 @@ export class MaterialGraphClient {
|
|
|
37
53
|
fetch: options.fetch ?? globalThis.fetch,
|
|
38
54
|
});
|
|
39
55
|
this.baseUrl = this.#transport.baseUrl;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
this.browse = createBrowseNamespace((params, signal) => this.#get("search_browse_by_colour", browseQuery(params), signal));
|
|
57
|
+
this.codes = new CodesClient({
|
|
58
|
+
get: (operationId, query, signal) => this.#get(operationId, query, signal),
|
|
59
|
+
post: (operationId, body, signal) => this.#post(operationId, body, signal),
|
|
60
|
+
});
|
|
61
|
+
this.colours = new ColoursClient({
|
|
62
|
+
get: (operationId, query, signal) => this.#get(operationId, query, signal),
|
|
63
|
+
});
|
|
43
64
|
}
|
|
44
65
|
get lastRateLimit() {
|
|
45
66
|
return this.#transport.lastRateLimit;
|
|
@@ -70,6 +91,32 @@ export class MaterialGraphClient {
|
|
|
70
91
|
match(input, signal) {
|
|
71
92
|
return this.#post("match_material", input, signal);
|
|
72
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Material Match as a PROGRESSIVE stream — the same POST
|
|
96
|
+
* `/api/v1/search/match` endpoint negotiated with `Accept:
|
|
97
|
+
* application/x-ndjson`, which runs the scene workflow (classify → decompose →
|
|
98
|
+
* one match per surface) and emits an event as each surface resolves. Use this
|
|
99
|
+
* wherever the user watches a photo come apart; use {@link match} when you only
|
|
100
|
+
* want the finished answer.
|
|
101
|
+
*
|
|
102
|
+
* Iterate it directly — the request opens on the first `next()`:
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* for await (const event of client.matchStream({ imageBase64 })) {
|
|
106
|
+
* if (event.type === "surface") render(event.surface);
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* Every frame is validated before it is yielded, and the iterator ends only on
|
|
111
|
+
* a `complete` or `error` event — a truncated connection raises rather than
|
|
112
|
+
* passing for a short run. `signal` cancels the in-flight request itself, as
|
|
113
|
+
* does breaking out of the loop; a cancelled stream ends without raising.
|
|
114
|
+
* Requires an image (`imageBase64` or `imageUrl`); a `vector` input is
|
|
115
|
+
* buffered-only.
|
|
116
|
+
*/
|
|
117
|
+
matchStream(input, signal) {
|
|
118
|
+
return matchStreamCall(this.#transport, input, signal);
|
|
119
|
+
}
|
|
73
120
|
byColour(params, signal) {
|
|
74
121
|
return this.#get("search_by_colour", {
|
|
75
122
|
hex: params.hex,
|
|
@@ -126,25 +173,6 @@ export class MaterialGraphClient {
|
|
|
126
173
|
idKind: options?.idKind ?? "variantId",
|
|
127
174
|
}, signal);
|
|
128
175
|
}
|
|
129
|
-
// ── Browse (private impls behind the `browse` namespace) ─────────────────────
|
|
130
|
-
#browsePage(params, signal) {
|
|
131
|
-
return this.#get("search_browse_by_colour", {
|
|
132
|
-
sort: params.sort,
|
|
133
|
-
cursor: params.cursor,
|
|
134
|
-
limit: params.limit,
|
|
135
|
-
types: params.types,
|
|
136
|
-
}, signal);
|
|
137
|
-
}
|
|
138
|
-
async *#browsePages(params, signal) {
|
|
139
|
-
let cursor = params.cursor;
|
|
140
|
-
for (;;) {
|
|
141
|
-
const page = await this.#browsePage({ ...params, cursor }, signal);
|
|
142
|
-
yield page;
|
|
143
|
-
if (page.cursor === undefined || page.results.length === 0)
|
|
144
|
-
return;
|
|
145
|
-
cursor = page.cursor;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
176
|
// ── Colour helpers ───────────────────────────────────────────────────────────
|
|
149
177
|
/** The three canonical colour vocabularies with live counts (GET `/api/v1/colours/facets`). */
|
|
150
178
|
colourFacets(signal) {
|
|
@@ -158,6 +186,20 @@ export class MaterialGraphClient {
|
|
|
158
186
|
suggestColour(q, options, signal) {
|
|
159
187
|
return this.#get("suggest_colour_names", { q, limit: options?.limit }, signal);
|
|
160
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Entity typeahead across products, brands and categories
|
|
191
|
+
* (GET `/api/v1/search/suggest`). Built for per-keystroke use: one indexed
|
|
192
|
+
* lookup, no embedding call and no model. Every suggestion carries the
|
|
193
|
+
* `refId` it resolves to — a product id, a brand slug, or a product-type key
|
|
194
|
+
* — so selecting one should navigate to that entity rather than re-running
|
|
195
|
+
* `label` as a search.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* const { suggestions } = await client.suggest("carr", { kind: "product" });
|
|
199
|
+
*/
|
|
200
|
+
suggest(q, options, signal) {
|
|
201
|
+
return this.#get("search_suggest", suggestQuery(q, options), signal);
|
|
202
|
+
}
|
|
161
203
|
// ── Catalog ──────────────────────────────────────────────────────────────────
|
|
162
204
|
/** Resolve a free-text spec phrase to canonical attribute ids (GET `/api/v1/catalog/attributes/resolve`). */
|
|
163
205
|
resolveAttribute(q, options, signal) {
|
|
@@ -235,29 +277,12 @@ export class MaterialGraphClient {
|
|
|
235
277
|
}
|
|
236
278
|
// ── Codes (advisory building-code compliance) ────────────────────────────────
|
|
237
279
|
/**
|
|
238
|
-
* Badge
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
* `meets` / `shortfall` / `unknown` from the material's accepted fire and
|
|
242
|
-
* slip-resistance facts, in input order. ADVISORY ONLY — every verdict and the
|
|
243
|
-
* envelope carry the AHJ caveat; this is model code as adopted, never a legal
|
|
244
|
-
* determination.
|
|
245
|
-
*
|
|
246
|
-
* @example
|
|
247
|
-
* // Badge this scheme's picks for a Texas hospitality fire-code review.
|
|
248
|
-
* const advisory = await mg.codeAdvisory({
|
|
249
|
-
* variantIds: scheme.picks.map((p) => p.variantId),
|
|
250
|
-
* jurisdictionId: "us-tx",
|
|
251
|
-
* occupancyGroup: "A-2", // assembly — restaurant / bar
|
|
252
|
-
* spaceType: "corridor",
|
|
253
|
-
* sprinklered: true,
|
|
254
|
-
* });
|
|
255
|
-
* const shortfalls = advisory.results.filter((r) =>
|
|
256
|
-
* r.requirements.some((req) => req.verdict === "shortfall"),
|
|
257
|
-
* );
|
|
280
|
+
* Badge materials against the advisory building-code registry (POST
|
|
281
|
+
* `/api/v1/codes/advisory`). Identical to {@link CodesClient.advisory}, kept
|
|
282
|
+
* flat because it shipped before the `codes` namespace existed.
|
|
258
283
|
*/
|
|
259
284
|
codeAdvisory(input, signal) {
|
|
260
|
-
return this
|
|
285
|
+
return this.codes.advisory(input, signal);
|
|
261
286
|
}
|
|
262
287
|
// ── Colour helpers (extract) ─────────────────────────────────────────────────
|
|
263
288
|
/** Extract a named colour palette from an uploaded image (POST `/api/v1/search/palettes/extract`). */
|
|
@@ -321,6 +346,17 @@ export class MaterialGraphClient {
|
|
|
321
346
|
uploadSourceMaterial(input, signal) {
|
|
322
347
|
return uploadSourceMaterial(this.#transport, input, signal);
|
|
323
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Read back an uploaded batch (GET `/api/v1/source-material/{batchId}`): every
|
|
351
|
+
* file with a fetchable `url` and its analyses inlined. `analyses` is always
|
|
352
|
+
* an array — `[]` means nothing has been computed yet. Analyses are shared by
|
|
353
|
+
* content hash, so re-uploading an already-analysed image returns its analysis
|
|
354
|
+
* immediately. A batch you do not own raises the same 404 as one that does not
|
|
355
|
+
* exist. Requires the `mg:compose` scope.
|
|
356
|
+
*/
|
|
357
|
+
sourceMaterialBatch(batchId, signal) {
|
|
358
|
+
return getSourceMaterialBatchCall(this.#transport, batchId, signal);
|
|
359
|
+
}
|
|
324
360
|
/** List invocable agents, workflows, and tools (GET `/api/v1/capabilities`). */
|
|
325
361
|
capabilities(signal) {
|
|
326
362
|
return capabilitiesRun(this.#transport, signal);
|
|
@@ -333,9 +369,25 @@ export class MaterialGraphClient {
|
|
|
333
369
|
run(runId, signal) {
|
|
334
370
|
return getRun(this.#transport, runId, signal);
|
|
335
371
|
}
|
|
336
|
-
/**
|
|
337
|
-
|
|
338
|
-
|
|
372
|
+
/**
|
|
373
|
+
* Export a completed compose Run's scheme as a finish schedule — one row per
|
|
374
|
+
* slot, every attribute value carrying its source and verification state
|
|
375
|
+
* (GET `/api/v1/runs/{runId}/schedule`). Unknown values are explicit
|
|
376
|
+
* `missing` cells, never blanks. The `.xlsx` form is a direct-URL download:
|
|
377
|
+
* `GET /api/v1/runs/{runId}/schedule.xlsx` with the same bearer key.
|
|
378
|
+
*/
|
|
379
|
+
runSchedule(runId, options) {
|
|
380
|
+
return getRunSchedule(this.#transport, runId, options);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Resume a suspended Run (POST `/api/v1/runs/{runId}/resume`).
|
|
384
|
+
*
|
|
385
|
+
* The third argument accepts either an `AbortSignal` (the published form) or
|
|
386
|
+
* {@link ResumeRunOptions}, whose `stepId` guards against resuming a step the
|
|
387
|
+
* run has already moved past — the API answers 409 on a mismatch (MG-608).
|
|
388
|
+
*/
|
|
389
|
+
resumeRun(runId, resumeData, signalOrOptions) {
|
|
390
|
+
return resumeRun(this.#transport, runId, resumeData, signalOrOptions);
|
|
339
391
|
}
|
|
340
392
|
/** Request durable Run cancellation (POST `/api/v1/runs/{runId}/cancel`). */
|
|
341
393
|
cancelRun(runId, signal) {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The advisory building-code surface, namespaced as `mg.codes`.
|
|
3
|
+
*
|
|
4
|
+
* Three calls that are one workflow — discover the vocabulary, state the space,
|
|
5
|
+
* then badge candidates against it — so they live together rather than as three
|
|
6
|
+
* flat methods on an already-large client facade. `mg.browse` set the precedent
|
|
7
|
+
* for a namespace when a capability is a small family rather than a single verb.
|
|
8
|
+
*
|
|
9
|
+
* The namespace holds no transport of its own: the client hands it bound
|
|
10
|
+
* `get`/`post` callbacks, so auth, rate-limit accounting and error shaping stay
|
|
11
|
+
* in exactly one place.
|
|
12
|
+
*
|
|
13
|
+
* ADVISORY ONLY: every verdict and every envelope carries the AHJ caveat — this
|
|
14
|
+
* is model code as adopted, never a legal determination.
|
|
15
|
+
*/
|
|
16
|
+
import type { CodeAdvisoryInput, CodeAdvisoryResponse, CodeContextRequest, CodeContextResponse, CodeRequirementsResponse } from "./codes-types.js";
|
|
17
|
+
import type { QueryParams } from "./http-transport.js";
|
|
18
|
+
import type { SdkOperationId } from "./operations.js";
|
|
19
|
+
/** The client's own request primitives, handed down so this file owns no transport. */
|
|
20
|
+
export interface CodesTransport {
|
|
21
|
+
get<T>(operationId: SdkOperationId, query?: QueryParams, signal?: AbortSignal): Promise<T>;
|
|
22
|
+
post<T>(operationId: SdkOperationId, body: unknown, signal?: AbortSignal): Promise<T>;
|
|
23
|
+
}
|
|
24
|
+
export declare class CodesClient {
|
|
25
|
+
#private;
|
|
26
|
+
constructor(transport: CodesTransport);
|
|
27
|
+
/**
|
|
28
|
+
* The vocabulary for stating a code context (GET `/api/v1/codes/context`):
|
|
29
|
+
* seeded jurisdictions and their coverage tier, the occupancy groups the
|
|
30
|
+
* registry keys on, finish locations, dimensions, classification scales with
|
|
31
|
+
* classes ordered best-first, and which `ms_room` term correlates to which
|
|
32
|
+
* regulatory axes.
|
|
33
|
+
*
|
|
34
|
+
* Start here — every value the other two calls accept is listed in this one
|
|
35
|
+
* response, so nothing need be hardcoded. A correlation the registry refuses to
|
|
36
|
+
* make comes back `null`, never a guess.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const vocab = await mg.codes.context();
|
|
40
|
+
* const texas = vocab.jurisdictions.find((j) => j.id === "us-tx");
|
|
41
|
+
* // `model_baseline` means unamended model IBC stood in for this AHJ's own table.
|
|
42
|
+
* console.log(texas?.coverage, texas?.adoptionNote);
|
|
43
|
+
*/
|
|
44
|
+
context(signal?: AbortSignal): Promise<CodeContextResponse>;
|
|
45
|
+
/**
|
|
46
|
+
* What a space demands, before any candidate exists (GET
|
|
47
|
+
* `/api/v1/codes/requirements`). State the space by explicit axes, or by a
|
|
48
|
+
* `roomType` the axes are correlated from.
|
|
49
|
+
*
|
|
50
|
+
* An empty `requirements` array means nothing in the registry applies to this
|
|
51
|
+
* context. It is NOT a pass.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const { requirements } = await mg.codes.requirements({
|
|
55
|
+
* jurisdictionId: "us-tx",
|
|
56
|
+
* occupancyGroup: "A-2", // or: roomType: "hotel_room" to correlate the axes
|
|
57
|
+
* spaceType: "corridors",
|
|
58
|
+
* sprinklered: true,
|
|
59
|
+
* });
|
|
60
|
+
* requirements.map((r) => `${r.dimension}: ${r.label} (${r.section})`);
|
|
61
|
+
*/
|
|
62
|
+
requirements(input: CodeContextRequest, signal?: AbortSignal): Promise<CodeRequirementsResponse>;
|
|
63
|
+
/**
|
|
64
|
+
* Badge materials against the advisory building-code registry for one context
|
|
65
|
+
* (POST `/api/v1/codes/advisory`, ≤ 100 ids). Each applicable requirement is
|
|
66
|
+
* badged `meets` / `shortfall` / `unknown` from the material's accepted fire
|
|
67
|
+
* and slip facts, in input order; each material also carries a rolled-up
|
|
68
|
+
* `summary`, and `resolvedContext` reports the axes actually used.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* const advisory = await mg.codes.advisory({
|
|
72
|
+
* variantIds: scheme.picks.map((p) => p.variantId),
|
|
73
|
+
* jurisdictionId: "us-tx",
|
|
74
|
+
* occupancyGroup: "A-2", // or: roomType: "hotel_room" to correlate the axes
|
|
75
|
+
* spaceType: "corridors", // corridors | exit_stairs_passageways | rooms_enclosed_spaces
|
|
76
|
+
* sprinklered: true,
|
|
77
|
+
* });
|
|
78
|
+
* // `unverified` means no comparable rating is on file — common, never a pass.
|
|
79
|
+
* const blocked = advisory.results.filter((r) => r.summary?.status === "shortfall");
|
|
80
|
+
*/
|
|
81
|
+
advisory(input: CodeAdvisoryInput, signal?: AbortSignal): Promise<CodeAdvisoryResponse>;
|
|
82
|
+
}
|