@instruments/materialgraph 0.1.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.
- package/by-spec-types.d.ts +73 -0
- package/by-spec-types.js +7 -0
- package/client.d.ts +186 -7
- package/client.js +242 -125
- package/codes-client.d.ts +82 -0
- package/codes-client.js +73 -0
- package/codes-types.d.ts +238 -0
- package/codes-types.js +1 -0
- 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 +343 -0
- package/composition-types.js +10 -0
- package/http-transport.d.ts +74 -0
- package/http-transport.js +201 -0
- package/index.d.ts +19 -0
- package/index.js +9 -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-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 +6 -2
- package/operations.js +30 -2
- package/package.json +1 -1
- package/project-types.d.ts +67 -0
- package/project-types.js +16 -0
- package/projects.d.ts +13 -0
- package/projects.js +46 -0
- package/run-events.d.ts +82 -0
- package/run-events.js +29 -0
- package/run-types.d.ts +266 -0
- package/run-types.js +6 -0
- package/runs.d.ts +84 -0
- package/runs.js +253 -0
- package/schedule-types.d.ts +88 -0
- package/schedule-types.js +12 -0
- package/search-refinement-types.d.ts +26 -1
- 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 +82 -0
- package/spec-types.js +44 -0
- package/specifications.d.ts +72 -0
- package/specifications.js +82 -0
- package/sse.d.ts +35 -0
- package/sse.js +147 -0
- package/types.d.ts +204 -77
- package/validation.d.ts +119 -0
- package/validation.js +241 -0
package/client.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { MaterialGraphError } from "./errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { HttpTransport } from "./http-transport.js";
|
|
3
|
+
import { findSdkOperation } from "./operations.js";
|
|
4
|
+
import { cancelRun, capabilities as capabilitiesRun, createRun, drainRunEvents, getRun, getRunSchedule, getSourceMaterialBatch as getSourceMaterialBatchCall, resumeRun, streamRunEvents, uploadSourceMaterial, } from "./runs.js";
|
|
5
|
+
import { matchStream as matchStreamCall } from "./match-stream.js";
|
|
6
|
+
import { appendProjectEvent as appendProjectEventCall, createProject as createProjectCall, getProject as getProjectCall, listProjects as listProjectsCall, } from "./projects.js";
|
|
7
|
+
import { CodesClient } from "./codes-client.js";
|
|
8
|
+
import { ColoursClient } from "./colours-client.js";
|
|
3
9
|
export const DEFAULT_BASE_URL = "https://beta.materialgraph.com";
|
|
4
10
|
export const MAX_BATCH_IDS = 100;
|
|
5
11
|
const SCHEMA_RESOURCE_OPERATION_IDS = {
|
|
@@ -14,145 +20,68 @@ const SCHEMA_RESOURCE_OPERATION_IDS = {
|
|
|
14
20
|
mappings: "list_schema_mappings",
|
|
15
21
|
};
|
|
16
22
|
function operation(operationId) {
|
|
17
|
-
const match =
|
|
23
|
+
const match = findSdkOperation(operationId);
|
|
18
24
|
if (!match) {
|
|
19
25
|
throw MaterialGraphError.unexpected(`Unknown SDK operation: ${operationId}`);
|
|
20
26
|
}
|
|
21
27
|
return match;
|
|
22
28
|
}
|
|
23
|
-
function operationPath(sdkOperation, pathParams = {}) {
|
|
24
|
-
return Object.entries(pathParams).reduce((path, [name, value]) => path.replace(`{${name}}`, encodeURIComponent(value)), sdkOperation.path);
|
|
25
|
-
}
|
|
26
|
-
function parseIntOrNull(value) {
|
|
27
|
-
if (value === null)
|
|
28
|
-
return null;
|
|
29
|
-
const n = Number.parseInt(value, 10);
|
|
30
|
-
return Number.isNaN(n) ? null : n;
|
|
31
|
-
}
|
|
32
|
-
function parseRateLimit(headers) {
|
|
33
|
-
const limit = parseIntOrNull(headers.get("x-ratelimit-limit"));
|
|
34
|
-
const remaining = parseIntOrNull(headers.get("x-ratelimit-remaining"));
|
|
35
|
-
const reset = parseIntOrNull(headers.get("x-ratelimit-reset"));
|
|
36
|
-
const retryAfter = parseIntOrNull(headers.get("retry-after"));
|
|
37
|
-
if (limit === null && remaining === null && reset === null && retryAfter === null) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
return { limit, remaining, reset, retryAfter };
|
|
41
|
-
}
|
|
42
29
|
export class MaterialGraphClient {
|
|
43
30
|
baseUrl;
|
|
44
31
|
browse;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The advisory building-code surface: `codes.context()` to discover the
|
|
34
|
+
* vocabulary, `codes.requirements()` to state a space, `codes.advisory()` to
|
|
35
|
+
* badge candidates against it.
|
|
36
|
+
*/
|
|
37
|
+
codes;
|
|
38
|
+
/**
|
|
39
|
+
* The colour AGGREGATE surface: `colours.profiles()` for a category palette,
|
|
40
|
+
* `colours.stats()` for corpus colour coverage.
|
|
41
|
+
*/
|
|
42
|
+
colours;
|
|
43
|
+
#transport;
|
|
48
44
|
constructor(options) {
|
|
49
45
|
if (!options.apiKey) {
|
|
50
46
|
throw MaterialGraphError.unexpected("MaterialGraphClient: `apiKey` is required.");
|
|
51
47
|
}
|
|
52
|
-
this.#
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
this.#transport = new HttpTransport({
|
|
49
|
+
apiKey: options.apiKey,
|
|
50
|
+
baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
|
|
51
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
52
|
+
});
|
|
53
|
+
this.baseUrl = this.#transport.baseUrl;
|
|
55
54
|
const browse = (params, signal) => this.#browsePage(params, signal);
|
|
56
55
|
browse.pages = (params, signal) => this.#browsePages(params, signal);
|
|
57
56
|
this.browse = browse;
|
|
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
|
+
});
|
|
58
64
|
}
|
|
59
65
|
get lastRateLimit() {
|
|
60
|
-
return this.#lastRateLimit;
|
|
61
|
-
}
|
|
62
|
-
#buildUrl(path, query) {
|
|
63
|
-
const url = new URL(`${this.baseUrl}${path}`);
|
|
64
|
-
if (query) {
|
|
65
|
-
for (const [key, value] of Object.entries(query)) {
|
|
66
|
-
if (value === undefined || value === null)
|
|
67
|
-
continue;
|
|
68
|
-
if (Array.isArray(value)) {
|
|
69
|
-
for (const item of value) {
|
|
70
|
-
if (item !== undefined && item !== null) {
|
|
71
|
-
url.searchParams.append(key, String(item));
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
url.searchParams.append(key, String(value));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return url.toString();
|
|
81
|
-
}
|
|
82
|
-
async #request(sdkOperation, options = {}) {
|
|
83
|
-
const path = operationPath(sdkOperation, options.pathParams);
|
|
84
|
-
const url = this.#buildUrl(path, options.query);
|
|
85
|
-
const requestUrl = new URL(url);
|
|
86
|
-
const requestMetadata = {
|
|
87
|
-
method: sdkOperation.method,
|
|
88
|
-
origin: requestUrl.origin,
|
|
89
|
-
path: requestUrl.pathname,
|
|
90
|
-
};
|
|
91
|
-
const headers = {
|
|
92
|
-
Authorization: `Bearer ${this.#apiKey}`,
|
|
93
|
-
Accept: "application/json",
|
|
94
|
-
};
|
|
95
|
-
const hasBody = options.body !== undefined;
|
|
96
|
-
if (hasBody)
|
|
97
|
-
headers["Content-Type"] = "application/json";
|
|
98
|
-
let response;
|
|
99
|
-
try {
|
|
100
|
-
response = await this.#fetch(url, {
|
|
101
|
-
method: sdkOperation.method,
|
|
102
|
-
headers,
|
|
103
|
-
body: hasBody ? JSON.stringify(options.body) : undefined,
|
|
104
|
-
signal: options.signal,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
catch (cause) {
|
|
108
|
-
throw MaterialGraphError.fromTransport({
|
|
109
|
-
...requestMetadata,
|
|
110
|
-
cause,
|
|
111
|
-
callerSignal: options.signal,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
const rateLimit = parseRateLimit(response.headers);
|
|
115
|
-
this.#lastRateLimit = rateLimit;
|
|
116
|
-
let text;
|
|
117
|
-
try {
|
|
118
|
-
text = await response.text();
|
|
119
|
-
}
|
|
120
|
-
catch (cause) {
|
|
121
|
-
throw MaterialGraphError.fromTransport({
|
|
122
|
-
...requestMetadata,
|
|
123
|
-
cause,
|
|
124
|
-
callerSignal: options.signal,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
let parsed;
|
|
128
|
-
if (text.length > 0) {
|
|
129
|
-
try {
|
|
130
|
-
parsed = JSON.parse(text);
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
parsed = text;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (!response.ok) {
|
|
137
|
-
const querySecrets = [...requestUrl.searchParams.values()];
|
|
138
|
-
throw MaterialGraphError.fromResponse({
|
|
139
|
-
...requestMetadata,
|
|
140
|
-
status: response.status,
|
|
141
|
-
body: parsed,
|
|
142
|
-
responseText: text,
|
|
143
|
-
rateLimit,
|
|
144
|
-
apiKey: this.#apiKey,
|
|
145
|
-
queryValues: querySecrets,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
return parsed;
|
|
66
|
+
return this.#transport.lastRateLimit;
|
|
149
67
|
}
|
|
150
68
|
#get(operationId, query, signal, pathParams) {
|
|
151
|
-
return this.#
|
|
69
|
+
return this.#transport.requestJson(operation(operationId), { query, signal, pathParams });
|
|
152
70
|
}
|
|
153
71
|
#post(operationId, body, signal) {
|
|
154
|
-
return this.#
|
|
72
|
+
return this.#transport.requestJson(operation(operationId), { body, signal });
|
|
155
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The full search primitive (POST `/api/v1/search`). Layer a hard
|
|
76
|
+
* specification gate onto a ranked (colour / visual / lexical / seed) search
|
|
77
|
+
* via `input.filters` — each entry is a typed {@link SearchFilter} criterion:
|
|
78
|
+
* a term match (`{ attributeId, term }`), a presence check
|
|
79
|
+
* (`{ attributeId, hasValue: true }`), or a certification programme
|
|
80
|
+
* (`{ attributeId: "certification_programme", term }`). This is the seam for
|
|
81
|
+
* "a GREENGUARD-certified alternative to THIS material" (seed by `similarTo`,
|
|
82
|
+
* gate by `filters`) or "the same fire class, with an EPD on record". Build the
|
|
83
|
+
* filters with the `certificationProgramme` / `specTerm` / `hasSpec` helpers.
|
|
84
|
+
*/
|
|
156
85
|
search(input, signal) {
|
|
157
86
|
return this.#post("search_materials_post", input, signal);
|
|
158
87
|
}
|
|
@@ -162,6 +91,32 @@ export class MaterialGraphClient {
|
|
|
162
91
|
match(input, signal) {
|
|
163
92
|
return this.#post("match_material", input, signal);
|
|
164
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
|
+
}
|
|
165
120
|
byColour(params, signal) {
|
|
166
121
|
return this.#get("search_by_colour", {
|
|
167
122
|
hex: params.hex,
|
|
@@ -264,8 +219,15 @@ export class MaterialGraphClient {
|
|
|
264
219
|
return this.#get("catalog_vocabulary_search", { q: query, limit: options?.limit, kind: options?.kind }, signal);
|
|
265
220
|
}
|
|
266
221
|
/**
|
|
267
|
-
* Find materials by canonical specification
|
|
268
|
-
*
|
|
222
|
+
* Find materials by canonical specification across every brand — the
|
|
223
|
+
* value-anchored, spec-only primitive (GET `/api/v1/search/specifications`).
|
|
224
|
+
* Each `spec` is an `attribute:value` criterion; a variant must satisfy ALL of
|
|
225
|
+
* them, and each match carries per-criterion trust (`confidence` +
|
|
226
|
+
* `sourceBasis`). Answers "which materials, from anyone, are ASTM E84 Class A
|
|
227
|
+
* AND GREENGUARD-certified?" Build the strings with `specCriterion` /
|
|
228
|
+
* `certificationProgrammeCriterion` (e.g.
|
|
229
|
+
* `certificationProgrammeCriterion("GREENGUARD Gold")`); discover the queryable
|
|
230
|
+
* vocabulary with {@link specificationFacets} and {@link attributeValues}.
|
|
269
231
|
*/
|
|
270
232
|
bySpec(spec, options, signal) {
|
|
271
233
|
return this.#get("catalog_by_spec", { spec: Array.isArray(spec) ? spec : [spec], limit: options?.limit }, signal);
|
|
@@ -295,17 +257,66 @@ export class MaterialGraphClient {
|
|
|
295
257
|
demand(params, signal) {
|
|
296
258
|
return this.#get("get_material_demand", { variantId: params.variantId, material: params.material }, signal);
|
|
297
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Enrich a bounded set of ALREADY-CHOSEN candidate ids with their real
|
|
262
|
+
* specification demand (POST `/api/v1/demand/batch`, 1–48 ids). The bounded
|
|
263
|
+
* batch complement to {@link demand}: no fuzzy resolution, ranking, or
|
|
264
|
+
* reordering — each id yields one discriminated `state` (`available` /
|
|
265
|
+
* `insufficient_data` / `no_signal` / `unknown_variant`) in input order and
|
|
266
|
+
* cardinality. This is the Desk's "show demand momentum for every pick in this
|
|
267
|
+
* scheme" read; an `mg:admin` key additionally sees per-result `evidence`.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* // Rank a shortlist by real specification momentum before locking the scheme.
|
|
271
|
+
* const { results } = await mg.demandBatch({
|
|
272
|
+
* variantIds: scheme.picks.map((p) => p.variantId),
|
|
273
|
+
* signal: "sampled",
|
|
274
|
+
* context: { projectType: "hospitality" },
|
|
275
|
+
* });
|
|
276
|
+
* const rising = results.filter(
|
|
277
|
+
* (r) => r.state === "available" && r.momentum.state === "rising",
|
|
278
|
+
* );
|
|
279
|
+
*/
|
|
280
|
+
demandBatch(input, signal) {
|
|
281
|
+
return this.#post("get_demand_batch", input, signal);
|
|
282
|
+
}
|
|
283
|
+
// ── Codes (advisory building-code compliance) ────────────────────────────────
|
|
284
|
+
/**
|
|
285
|
+
* Badge materials against the advisory building-code registry (POST
|
|
286
|
+
* `/api/v1/codes/advisory`). Identical to {@link CodesClient.advisory}, kept
|
|
287
|
+
* flat because it shipped before the `codes` namespace existed.
|
|
288
|
+
*/
|
|
289
|
+
codeAdvisory(input, signal) {
|
|
290
|
+
return this.codes.advisory(input, signal);
|
|
291
|
+
}
|
|
298
292
|
// ── Colour helpers (extract) ─────────────────────────────────────────────────
|
|
299
293
|
/** Extract a named colour palette from an uploaded image (POST `/api/v1/search/palettes/extract`). */
|
|
300
294
|
extractPalette(input, signal) {
|
|
301
295
|
return this.#post("extract_image_palette", input, signal);
|
|
302
296
|
}
|
|
303
297
|
// ── Catalog (facets / applicable specs / glossary) ───────────────────────────
|
|
304
|
-
/**
|
|
298
|
+
/**
|
|
299
|
+
* Discover the live specification vocabulary (GET
|
|
300
|
+
* `/api/v1/catalog/specifications/facets`) — the facetable spec attributes that
|
|
301
|
+
* have real product-graph data, each with its top canonical values and the
|
|
302
|
+
* variants/brands behind them. This is where a caller LEARNS that the data
|
|
303
|
+
* exists: LEED, GREENGUARD, ASTM E84 Class A, FloorScore, EPDs all surface here
|
|
304
|
+
* with counts. Includes the virtual `certification_programme` facet (its values
|
|
305
|
+
* are programme names). Learn an `attribute:value` here, then reuse it as a
|
|
306
|
+
* {@link bySpec} criterion or a {@link search} `filters` entry. NOTE: continuous
|
|
307
|
+
* numeric specs (e.g. `slip_resistance_dcof`, a 0–1 DCOF range) are NOT
|
|
308
|
+
* token-facetable and do not appear here — read those per-attribute.
|
|
309
|
+
*/
|
|
305
310
|
specificationFacets(signal) {
|
|
306
311
|
return this.#get("list_spec_facets", undefined, signal);
|
|
307
312
|
}
|
|
308
|
-
/**
|
|
313
|
+
/**
|
|
314
|
+
* List the live value distribution for ONE canonical attribute (GET
|
|
315
|
+
* `/api/v1/catalog/attributes/{attributeId}/values`), ordered by cross-brand
|
|
316
|
+
* reach with variant + brand counts. The drill-in companion to
|
|
317
|
+
* {@link specificationFacets} — e.g. every certification programme on record,
|
|
318
|
+
* or every fire classification and how many variants carry each.
|
|
319
|
+
*/
|
|
309
320
|
attributeValues(attributeId, signal) {
|
|
310
321
|
return this.#get("list_attribute_values", undefined, signal, {
|
|
311
322
|
attributeId,
|
|
@@ -324,10 +335,116 @@ export class MaterialGraphClient {
|
|
|
324
335
|
brands(options = {}, signal) {
|
|
325
336
|
return this.#get("list_brands", { q: options.q, limit: options.limit, offset: options.offset }, signal);
|
|
326
337
|
}
|
|
327
|
-
/** Resolve a brand and return its profile + trade signals (GET `/api/v1/brands/{
|
|
328
|
-
brand(
|
|
338
|
+
/** Resolve a brand and return its profile + trade signals (GET `/api/v1/brands/{brandSlug}`). */
|
|
339
|
+
brand(brandSlug, options, signal) {
|
|
329
340
|
return this.#get("get_brand_profile", { q: options?.q }, signal, {
|
|
330
|
-
|
|
341
|
+
brandSlug,
|
|
331
342
|
});
|
|
332
343
|
}
|
|
344
|
+
// ── Runs (the hosted agentic surface) ────────────────────────────────────────
|
|
345
|
+
/**
|
|
346
|
+
* Upload one atomic source-material batch (POST `/api/v1/source-material`,
|
|
347
|
+
* multipart). Pass a heterogeneous pile via `{ files }` (any mix of PDF,
|
|
348
|
+
* text/markdown, and images — the server classifies each) or the legacy
|
|
349
|
+
* `{ brief, references }` shape. Requires the `mg:ingest` scope.
|
|
350
|
+
*/
|
|
351
|
+
uploadSourceMaterial(input, signal) {
|
|
352
|
+
return uploadSourceMaterial(this.#transport, input, signal);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Read back an uploaded batch (GET `/api/v1/source-material/{batchId}`): every
|
|
356
|
+
* file with a fetchable `url` and its analyses inlined. `analyses` is always
|
|
357
|
+
* an array — `[]` means nothing has been computed yet. Analyses are shared by
|
|
358
|
+
* content hash, so re-uploading an already-analysed image returns its analysis
|
|
359
|
+
* immediately. A batch you do not own raises the same 404 as one that does not
|
|
360
|
+
* exist. Requires the `mg:compose` scope.
|
|
361
|
+
*/
|
|
362
|
+
sourceMaterialBatch(batchId, signal) {
|
|
363
|
+
return getSourceMaterialBatchCall(this.#transport, batchId, signal);
|
|
364
|
+
}
|
|
365
|
+
/** List invocable agents, workflows, and tools (GET `/api/v1/capabilities`). */
|
|
366
|
+
capabilities(signal) {
|
|
367
|
+
return capabilitiesRun(this.#transport, signal);
|
|
368
|
+
}
|
|
369
|
+
/** Start an asynchronous capability Run (POST `/api/v1/runs`). */
|
|
370
|
+
createRun(body, signal) {
|
|
371
|
+
return createRun(this.#transport, body, signal);
|
|
372
|
+
}
|
|
373
|
+
/** Get a Run's current state (GET `/api/v1/runs/{runId}`). */
|
|
374
|
+
run(runId, signal) {
|
|
375
|
+
return getRun(this.#transport, runId, signal);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Export a completed compose Run's scheme as a finish schedule — one row per
|
|
379
|
+
* slot, every attribute value carrying its source and verification state
|
|
380
|
+
* (GET `/api/v1/runs/{runId}/schedule`). Unknown values are explicit
|
|
381
|
+
* `missing` cells, never blanks. The `.xlsx` form is a direct-URL download:
|
|
382
|
+
* `GET /api/v1/runs/{runId}/schedule.xlsx` with the same bearer key.
|
|
383
|
+
*/
|
|
384
|
+
runSchedule(runId, options) {
|
|
385
|
+
return getRunSchedule(this.#transport, runId, options);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Resume a suspended Run (POST `/api/v1/runs/{runId}/resume`).
|
|
389
|
+
*
|
|
390
|
+
* The third argument accepts either an `AbortSignal` (the published form) or
|
|
391
|
+
* {@link ResumeRunOptions}, whose `stepId` guards against resuming a step the
|
|
392
|
+
* run has already moved past — the API answers 409 on a mismatch (MG-608).
|
|
393
|
+
*/
|
|
394
|
+
resumeRun(runId, resumeData, signalOrOptions) {
|
|
395
|
+
return resumeRun(this.#transport, runId, resumeData, signalOrOptions);
|
|
396
|
+
}
|
|
397
|
+
/** Request durable Run cancellation (POST `/api/v1/runs/{runId}/cancel`). */
|
|
398
|
+
cancelRun(runId, signal) {
|
|
399
|
+
return cancelRun(this.#transport, runId, signal);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Derive an "option B" scheme from a succeeded compose Run along one
|
|
403
|
+
* constraint axis (POST `/api/v1/schemes/vary`). Deterministic and
|
|
404
|
+
* synchronous — the response IS the hydrated scheme (no Run envelope), stamped
|
|
405
|
+
* with axis + source provenance whose `notes` are the variation's receipts.
|
|
406
|
+
* Requires the `mg:read` scope.
|
|
407
|
+
*/
|
|
408
|
+
varyScheme(body, signal) {
|
|
409
|
+
return this.#post("vary_schemes", body, signal);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Stream ordered Run events as an async iterator (GET
|
|
413
|
+
* `/api/v1/runs/{runId}/events`, SSE). Iterates until the stream ends or the
|
|
414
|
+
* signal aborts; pass `after` to resume from the last observed sequence.
|
|
415
|
+
*/
|
|
416
|
+
streamRunEvents(runId, options) {
|
|
417
|
+
return streamRunEvents(this.#transport, runId, options);
|
|
418
|
+
}
|
|
419
|
+
// ── Projects (the persistent Material Box entity) ────────────────────────────
|
|
420
|
+
/**
|
|
421
|
+
* Create a persistent project owned by the caller (POST `/api/v1/projects`).
|
|
422
|
+
* Returns the freshly assembled state (an empty event log). Requires the
|
|
423
|
+
* `mg:ingest` scope.
|
|
424
|
+
*/
|
|
425
|
+
createProject(body = {}, signal) {
|
|
426
|
+
return createProjectCall(this.#transport, body, signal);
|
|
427
|
+
}
|
|
428
|
+
/** Read a project's assembled state (GET `/api/v1/projects/{projectId}`). */
|
|
429
|
+
getProject(projectId, signal) {
|
|
430
|
+
return getProjectCall(this.#transport, projectId, signal);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Append one event to a project's append-only process trail (POST
|
|
434
|
+
* `/api/v1/projects/{projectId}/events`). Requires the `mg:ingest` scope.
|
|
435
|
+
*/
|
|
436
|
+
appendProjectEvent(projectId, body, signal) {
|
|
437
|
+
return appendProjectEventCall(this.#transport, projectId, body, signal);
|
|
438
|
+
}
|
|
439
|
+
/** List the caller's own projects, newest first (GET `/api/v1/projects`). */
|
|
440
|
+
listProjects(signal) {
|
|
441
|
+
return listProjectsCall(this.#transport, signal);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Collect Run events until a terminal event, the deadline, or the stream ends
|
|
445
|
+
* — the bounded events-log pattern (a single pass over {@link streamRunEvents}).
|
|
446
|
+
*/
|
|
447
|
+
drainRunEvents(runId, options) {
|
|
448
|
+
return drainRunEvents(this.#transport, runId, options);
|
|
449
|
+
}
|
|
333
450
|
}
|
|
@@ -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
|
+
}
|
package/codes-client.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export class CodesClient {
|
|
2
|
+
#transport;
|
|
3
|
+
constructor(transport) {
|
|
4
|
+
this.#transport = transport;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The vocabulary for stating a code context (GET `/api/v1/codes/context`):
|
|
8
|
+
* seeded jurisdictions and their coverage tier, the occupancy groups the
|
|
9
|
+
* registry keys on, finish locations, dimensions, classification scales with
|
|
10
|
+
* classes ordered best-first, and which `ms_room` term correlates to which
|
|
11
|
+
* regulatory axes.
|
|
12
|
+
*
|
|
13
|
+
* Start here — every value the other two calls accept is listed in this one
|
|
14
|
+
* response, so nothing need be hardcoded. A correlation the registry refuses to
|
|
15
|
+
* make comes back `null`, never a guess.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const vocab = await mg.codes.context();
|
|
19
|
+
* const texas = vocab.jurisdictions.find((j) => j.id === "us-tx");
|
|
20
|
+
* // `model_baseline` means unamended model IBC stood in for this AHJ's own table.
|
|
21
|
+
* console.log(texas?.coverage, texas?.adoptionNote);
|
|
22
|
+
*/
|
|
23
|
+
context(signal) {
|
|
24
|
+
return this.#transport.get("list_code_context", undefined, signal);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* What a space demands, before any candidate exists (GET
|
|
28
|
+
* `/api/v1/codes/requirements`). State the space by explicit axes, or by a
|
|
29
|
+
* `roomType` the axes are correlated from.
|
|
30
|
+
*
|
|
31
|
+
* An empty `requirements` array means nothing in the registry applies to this
|
|
32
|
+
* context. It is NOT a pass.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const { requirements } = await mg.codes.requirements({
|
|
36
|
+
* jurisdictionId: "us-tx",
|
|
37
|
+
* occupancyGroup: "A-2", // or: roomType: "hotel_room" to correlate the axes
|
|
38
|
+
* spaceType: "corridors",
|
|
39
|
+
* sprinklered: true,
|
|
40
|
+
* });
|
|
41
|
+
* requirements.map((r) => `${r.dimension}: ${r.label} (${r.section})`);
|
|
42
|
+
*/
|
|
43
|
+
requirements(input, signal) {
|
|
44
|
+
return this.#transport.get("list_code_requirements", {
|
|
45
|
+
jurisdiction: input.jurisdictionId,
|
|
46
|
+
sprinklered: input.sprinklered,
|
|
47
|
+
occupancy: input.occupancyGroup,
|
|
48
|
+
space: input.spaceType,
|
|
49
|
+
room: input.roomType,
|
|
50
|
+
}, signal);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Badge materials against the advisory building-code registry for one context
|
|
54
|
+
* (POST `/api/v1/codes/advisory`, ≤ 100 ids). Each applicable requirement is
|
|
55
|
+
* badged `meets` / `shortfall` / `unknown` from the material's accepted fire
|
|
56
|
+
* and slip facts, in input order; each material also carries a rolled-up
|
|
57
|
+
* `summary`, and `resolvedContext` reports the axes actually used.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const advisory = await mg.codes.advisory({
|
|
61
|
+
* variantIds: scheme.picks.map((p) => p.variantId),
|
|
62
|
+
* jurisdictionId: "us-tx",
|
|
63
|
+
* occupancyGroup: "A-2", // or: roomType: "hotel_room" to correlate the axes
|
|
64
|
+
* spaceType: "corridors", // corridors | exit_stairs_passageways | rooms_enclosed_spaces
|
|
65
|
+
* sprinklered: true,
|
|
66
|
+
* });
|
|
67
|
+
* // `unverified` means no comparable rating is on file — common, never a pass.
|
|
68
|
+
* const blocked = advisory.results.filter((r) => r.summary?.status === "shortfall");
|
|
69
|
+
*/
|
|
70
|
+
advisory(input, signal) {
|
|
71
|
+
return this.#transport.post("resolveCodeAdvisory", input, signal);
|
|
72
|
+
}
|
|
73
|
+
}
|