@instruments/materialgraph 0.6.0 → 0.7.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/brand-types.d.ts +20 -1
- package/brand-types.js +7 -0
- package/by-spec-types.d.ts +62 -3
- package/catalog-discovery-types.d.ts +19 -2
- package/client-browse.d.ts +7 -1
- package/client.d.ts +339 -53
- package/client.js +316 -33
- package/codes-client.d.ts +36 -0
- package/codes-client.js +26 -0
- package/codes-types.d.ts +77 -11
- package/colours-client.d.ts +41 -6
- package/colours-client.js +27 -6
- package/composition-types.d.ts +27 -18
- package/demand-batch-types.d.ts +118 -0
- package/demand-batch-types.js +1 -0
- package/demand-types.d.ts +134 -0
- package/demand-types.js +1 -0
- package/identifiers.d.ts +71 -0
- package/identifiers.js +26 -0
- package/index.d.ts +1 -0
- package/material-asset-types.d.ts +20 -1
- package/material-block-types.d.ts +30 -1
- package/material-types.d.ts +45 -7
- package/package.json +1 -1
- package/palette-search-types.d.ts +17 -2
- package/project-types.d.ts +20 -8
- package/project-types.js +0 -7
- package/run-types.d.ts +20 -8
- package/schedule-types.d.ts +18 -7
- package/schedule-types.js +0 -11
- package/schema-resource-types.d.ts +15 -0
- package/search-refinement-types.d.ts +24 -4
- package/spec-types.d.ts +14 -5
- package/spec-types.js +14 -5
- package/suggest-types.d.ts +10 -1
- package/suggest-types.js +5 -0
- package/types.d.ts +215 -214
package/client.js
CHANGED
|
@@ -28,18 +28,69 @@ function operation(operationId) {
|
|
|
28
28
|
}
|
|
29
29
|
return match;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The MaterialGraph client — every lens on the graph, on one object.
|
|
33
|
+
*
|
|
34
|
+
* PICK YOUR LENS BY THE QUESTION, not by the noun. Most wrong answers from this
|
|
35
|
+
* API come from routing a question to a lens that cannot see it:
|
|
36
|
+
*
|
|
37
|
+
* | The question | The lens |
|
|
38
|
+
* | --- | --- |
|
|
39
|
+
* | "find me something like this / matching these words" | {@link MaterialGraphClient.search} |
|
|
40
|
+
* | "what's IN this photo?" | {@link MaterialGraphClient.match} / {@link MaterialGraphClient.matchStream} |
|
|
41
|
+
* | "which materials, from anyone, meet this hard spec?" | {@link MaterialGraphClient.bySpec} |
|
|
42
|
+
* | "what's near this colour / this palette / this gradient?" | {@link MaterialGraphClient.byColour}, `colours.palette()`, `colours.gradient()` |
|
|
43
|
+
* | "what goes with THIS material?" | {@link MaterialGraphClient.pairings}, {@link MaterialGraphClient.siblings}, {@link MaterialGraphClient.coordinate} |
|
|
44
|
+
* | "how much is this specified?" | {@link MaterialGraphClient.demand}, {@link MaterialGraphClient.demandBatch} |
|
|
45
|
+
* | "is this allowed in an A-2 corridor?" | `codes.*` |
|
|
46
|
+
* | "what can I even ask for?" | {@link MaterialGraphClient.specificationFacets}, {@link MaterialGraphClient.attributeValues}, {@link MaterialGraphClient.taxonomy}, {@link MaterialGraphClient.colourFacets}, {@link MaterialGraphClient.glossary} |
|
|
47
|
+
* | "do the whole job for me, async" | {@link MaterialGraphClient.createRun} + `mg:compose` |
|
|
48
|
+
*
|
|
49
|
+
* Two structural rules that fall out of that table:
|
|
50
|
+
* - **Colour is a separate lens.** {@link MaterialGraphClient.search} matches
|
|
51
|
+
* WORDS. A colour word in `text` finds materials whose text mentions it, not
|
|
52
|
+
* materials that look it. Route visual intent to the colour lenses.
|
|
53
|
+
* - **Relationships hang off a material, not off a query.** You cannot ask "what
|
|
54
|
+
* pairs with beige oak"; you resolve a material first, then ask what pairs
|
|
55
|
+
* with THAT `variantId`.
|
|
56
|
+
*
|
|
57
|
+
* And three reading rules that apply to every lens: ids are opaque (see
|
|
58
|
+
* `./identifiers.ts`), absent is not false, and a page is not a corpus.
|
|
59
|
+
*/
|
|
31
60
|
export class MaterialGraphClient {
|
|
32
61
|
baseUrl;
|
|
62
|
+
/**
|
|
63
|
+
* THE QUESTION: "show me everything, ordered lightest / most vivid / most
|
|
64
|
+
* muted." Pure colour scalars — no query, no relevance. `browse({ sort })` is
|
|
65
|
+
* one page; `browse.pages({ sort })` walks them all, and only the walk can
|
|
66
|
+
* support a corpus claim.
|
|
67
|
+
*
|
|
68
|
+
* Instead use {@link MaterialGraphClient.byColour} for materials NEAR a colour,
|
|
69
|
+
* {@link MaterialGraphClient.search} when there is anything to rank against,
|
|
70
|
+
* and `colours.profiles()` for a category's palette rather than its members.
|
|
71
|
+
*/
|
|
33
72
|
browse;
|
|
34
73
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
74
|
+
* THE QUESTION: "is this material allowed HERE?" — a jurisdiction + occupancy +
|
|
75
|
+
* finish location, not a property of the material alone. `codes.context()`
|
|
76
|
+
* discovers the vocabulary, `codes.requirements()` states a space,
|
|
77
|
+
* `codes.advisory()` badges candidates.
|
|
78
|
+
*
|
|
79
|
+
* Instead use {@link MaterialGraphClient.bySpec} to find materials by a
|
|
80
|
+
* classification you already know you need; codes is what tells you WHICH one
|
|
81
|
+
* the space demands. ADVISORY ONLY: model code as adopted, never a
|
|
82
|
+
* determination for an authority having jurisdiction.
|
|
38
83
|
*/
|
|
39
84
|
codes;
|
|
40
85
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
86
|
+
* THE QUESTION: "what colours exist here?" (`profiles`, `stats`) and "what
|
|
87
|
+
* matches this whole SET of colours?" (`palette`, `gradient`).
|
|
88
|
+
*
|
|
89
|
+
* Instead use {@link MaterialGraphClient.byColour} for ONE colour,
|
|
90
|
+
* {@link MaterialGraphClient.browse} to walk by a colour scalar, and
|
|
91
|
+
* {@link MaterialGraphClient.colourFacets} for the colour VOCABULARY rather
|
|
92
|
+
* than values. Colour throughout is derived from product imagery — visual
|
|
93
|
+
* likeness, never a declared colour specification.
|
|
43
94
|
*/
|
|
44
95
|
colours;
|
|
45
96
|
#transport;
|
|
@@ -73,6 +124,16 @@ export class MaterialGraphClient {
|
|
|
73
124
|
return this.#transport.requestJson(operation(operationId), { body, signal });
|
|
74
125
|
}
|
|
75
126
|
/**
|
|
127
|
+
* THE QUESTION: "find me materials like this" — a RANKED answer over words, a
|
|
128
|
+
* seed image, colours and a mood, optionally gated by hard specs.
|
|
129
|
+
*
|
|
130
|
+
* Instead use {@link MaterialGraphClient.bySpec} when the spec IS the question
|
|
131
|
+
* (ranking would be noise), {@link MaterialGraphClient.byColour} /
|
|
132
|
+
* `colours.palette()` for visual colour intent (`text` matches WORDS — "sage
|
|
133
|
+
* green" finds materials that SAY sage, not ones that look it),
|
|
134
|
+
* {@link MaterialGraphClient.match} for a photo, {@link MaterialGraphClient.pairings}
|
|
135
|
+
* for what goes WITH something, {@link MaterialGraphClient.suggest} for typeahead.
|
|
136
|
+
*
|
|
76
137
|
* The full search primitive (POST `/api/v1/search`). Layer a hard
|
|
77
138
|
* specification gate onto a ranked (colour / visual / lexical / seed) search
|
|
78
139
|
* via `input.filters` — each entry is a typed {@link SearchFilter} criterion:
|
|
@@ -86,9 +147,28 @@ export class MaterialGraphClient {
|
|
|
86
147
|
search(input, signal) {
|
|
87
148
|
return this.#post("search_materials_post", input, signal);
|
|
88
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* The same question as {@link MaterialGraphClient.search}; the difference is
|
|
152
|
+
* transport — a cacheable, link-shareable GET for queries that must survive in
|
|
153
|
+
* a URL. `filters`, `preferences`, `verdicts`, image bytes and `weights` have
|
|
154
|
+
* no query-string form, so anything using those must go through `search`.
|
|
155
|
+
*/
|
|
89
156
|
searchGet(params, signal) {
|
|
90
157
|
return this.#get("search_materials_get", { ...params }, signal);
|
|
91
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* THE QUESTION: "what materials are in this PHOTO?" — Material Match classifies
|
|
161
|
+
* the image, decomposes it into surfaces, and answers each one.
|
|
162
|
+
*
|
|
163
|
+
* Instead use {@link MaterialGraphClient.matchStream} whenever a human is
|
|
164
|
+
* watching, {@link MaterialGraphClient.search} with `image` for ONE ranked list
|
|
165
|
+
* rather than a per-surface decomposition, and
|
|
166
|
+
* {@link MaterialGraphClient.extractPalette} for the image's colours only.
|
|
167
|
+
*
|
|
168
|
+
* Exactly one of `imageBase64`, `imageUrl` or `vector`. Read `qualityFloor` and
|
|
169
|
+
* `droppedBelowFloor` before reporting an empty result: "nothing was good
|
|
170
|
+
* enough" and "nothing was found" are different answers.
|
|
171
|
+
*/
|
|
92
172
|
match(input, signal) {
|
|
93
173
|
return this.#post("match_material", input, signal);
|
|
94
174
|
}
|
|
@@ -118,6 +198,17 @@ export class MaterialGraphClient {
|
|
|
118
198
|
matchStream(input, signal) {
|
|
119
199
|
return matchStreamCall(this.#transport, input, signal);
|
|
120
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* THE QUESTION: "what materials are near THIS colour?" — one colour (or a small
|
|
203
|
+
* palette), ranked by OKLab closeness to colour extracted from product imagery.
|
|
204
|
+
*
|
|
205
|
+
* Instead use `colours.palette()` for N colours at stated proportions (a
|
|
206
|
+
* different ranking, not a wrapper), `colours.gradient()` for a transition,
|
|
207
|
+
* {@link MaterialGraphClient.resolveColour} first if you hold a NAME,
|
|
208
|
+
* {@link MaterialGraphClient.browse} to walk by lightness/vividness, and
|
|
209
|
+
* {@link MaterialGraphClient.search} when colour is one signal among many.
|
|
210
|
+
* Report matches as visual likeness, never a stated colour specification.
|
|
211
|
+
*/
|
|
121
212
|
byColour(params, signal) {
|
|
122
213
|
return this.#get("search_by_colour", {
|
|
123
214
|
hex: params.hex,
|
|
@@ -130,33 +221,82 @@ export class MaterialGraphClient {
|
|
|
130
221
|
}, signal);
|
|
131
222
|
}
|
|
132
223
|
// ── Materials ──────────────────────────────────────────────────────────────
|
|
133
|
-
/**
|
|
224
|
+
/**
|
|
225
|
+
* THE QUESTION: "tell me everything about THIS material" (GET
|
|
226
|
+
* `/api/v1/materials/{variantId}`).
|
|
227
|
+
*
|
|
228
|
+
* The hinge of the API: every relationship lens
|
|
229
|
+
* ({@link MaterialGraphClient.siblings}, {@link MaterialGraphClient.pairings},
|
|
230
|
+
* {@link MaterialGraphClient.demand}, `codes.advisory()`) hangs off a
|
|
231
|
+
* `variantId`. Instead use {@link MaterialGraphClient.materials} for many ids,
|
|
232
|
+
* {@link MaterialGraphClient.materialBlocks} to tell "no value" from "not
|
|
233
|
+
* checked", {@link MaterialGraphClient.search} when you have no id yet.
|
|
234
|
+
*
|
|
235
|
+
* @param variantId Opaque — pass back exactly what you were given.
|
|
236
|
+
*/
|
|
134
237
|
material(variantId, options, signal) {
|
|
135
238
|
return this.#get("get_material", options?.includeSiblings ? { include: "siblings" } : undefined, signal, { variantId });
|
|
136
239
|
}
|
|
137
|
-
/**
|
|
240
|
+
/**
|
|
241
|
+
* THE QUESTION: "what other colourways and finishes does this product come in?"
|
|
242
|
+
* — the SAME product, different variants.
|
|
243
|
+
*
|
|
244
|
+
* Instead use {@link MaterialGraphClient.pairings} for a DIFFERENT product that
|
|
245
|
+
* goes with this one, and `search({ similarTo })` for one that LOOKS like it.
|
|
246
|
+
*/
|
|
138
247
|
siblings(variantId, signal) {
|
|
139
248
|
return this.#get("get_material_siblings", undefined, signal, { variantId });
|
|
140
249
|
}
|
|
141
|
-
/**
|
|
250
|
+
/**
|
|
251
|
+
* THE QUESTION: "what do we actually KNOW about this material, and what have we
|
|
252
|
+
* simply not checked?" The only read that distinguishes a determined absence
|
|
253
|
+
* from an unasked question — every field carries a `status`, so this is the one
|
|
254
|
+
* surface entitled to back a negative ("no fire rating").
|
|
255
|
+
* {@link MaterialGraphClient.material} cannot: there an absence is merely unknown.
|
|
256
|
+
*/
|
|
142
257
|
materialBlocks(variantId, signal) {
|
|
143
258
|
return this.#get("list_material_blocks", undefined, signal, {
|
|
144
259
|
variantId,
|
|
145
260
|
});
|
|
146
261
|
}
|
|
147
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* THE QUESTION: "what images and documents may I SHOW for this material?" — the
|
|
264
|
+
* redistribution-safe set (GET `/api/v1/materials/{variantId}/assets`), not
|
|
265
|
+
* every file on record.
|
|
266
|
+
*/
|
|
148
267
|
materialAssets(variantId, signal) {
|
|
149
268
|
return this.#get("list_material_assets", undefined, signal, {
|
|
150
269
|
variantId,
|
|
151
270
|
});
|
|
152
271
|
}
|
|
153
|
-
/**
|
|
272
|
+
/**
|
|
273
|
+
* THE QUESTION: "what does the schema DEFINE?" — the governed static registry.
|
|
274
|
+
*
|
|
275
|
+
* Definitions, not data: use {@link MaterialGraphClient.specificationFacets} for
|
|
276
|
+
* what the corpus actually holds and {@link MaterialGraphClient.glossary} to
|
|
277
|
+
* explain one term.
|
|
278
|
+
*/
|
|
154
279
|
schema(resource, signal) {
|
|
155
280
|
return this.#get(SCHEMA_RESOURCE_OPERATION_IDS[resource], undefined, signal);
|
|
156
281
|
}
|
|
157
|
-
/**
|
|
282
|
+
/**
|
|
283
|
+
* THE QUESTION: "what goes WITH this material?" — the companionship rail, hung
|
|
284
|
+
* off one resolved material (GET `/api/v1/materials/{variantId}/pairings`).
|
|
285
|
+
*
|
|
286
|
+
* Instead use {@link MaterialGraphClient.coordinate} for a whole palette of
|
|
287
|
+
* picks, {@link MaterialGraphClient.siblings} for the same product in another
|
|
288
|
+
* colour, and `search({ similarTo })` for a LIKE-THIS substitute — similar and
|
|
289
|
+
* complementary are opposite questions.
|
|
290
|
+
*
|
|
291
|
+
* READ `item.source` BEFORE PHRASING: the rail mixes three tiers of evidence —
|
|
292
|
+
* `cospec` (real orders), `considered` (board activity), `colour` (palette
|
|
293
|
+
* look-alike). Only `cospec` supports "specified together". `cospecCount` tells
|
|
294
|
+
* you how much of the rail is real order evidence.
|
|
295
|
+
*/
|
|
158
296
|
pairings(variantId, options, signal) {
|
|
159
|
-
return this.#get("get_material_pairings", {
|
|
297
|
+
return this.#get("get_material_pairings", { ...options }, signal, {
|
|
298
|
+
variantId,
|
|
299
|
+
});
|
|
160
300
|
}
|
|
161
301
|
materials(ids, options, signal) {
|
|
162
302
|
if (ids.length > MAX_BATCH_IDS) {
|
|
@@ -175,11 +315,20 @@ export class MaterialGraphClient {
|
|
|
175
315
|
}, signal);
|
|
176
316
|
}
|
|
177
317
|
// ── Colour helpers ───────────────────────────────────────────────────────────
|
|
178
|
-
/**
|
|
318
|
+
/**
|
|
319
|
+
* THE QUESTION: "what colour WORDS may I use?" — family, mood and undertone
|
|
320
|
+
* with live counts. Vocabulary, not values: instead use `colours.profiles()`
|
|
321
|
+
* for a category's palette, {@link MaterialGraphClient.byColour} to search by a
|
|
322
|
+
* colour, {@link MaterialGraphClient.resolveColour} to turn a NAME into a hex.
|
|
323
|
+
*/
|
|
179
324
|
colourFacets(signal) {
|
|
180
325
|
return this.#get("get_colour_facets", undefined, signal);
|
|
181
326
|
}
|
|
182
|
-
/**
|
|
327
|
+
/**
|
|
328
|
+
* THE QUESTION: "what hex is 'Hague Blue'?" — the step before
|
|
329
|
+
* {@link MaterialGraphClient.byColour} when you hold a name rather than a hex.
|
|
330
|
+
* A `null` match means unrecognised, not "no such colour".
|
|
331
|
+
*/
|
|
183
332
|
resolveColour(name, signal) {
|
|
184
333
|
return this.#get("resolve_colour_name", { name }, signal);
|
|
185
334
|
}
|
|
@@ -188,6 +337,11 @@ export class MaterialGraphClient {
|
|
|
188
337
|
return this.#get("suggest_colour_names", { q, limit: options?.limit }, signal);
|
|
189
338
|
}
|
|
190
339
|
/**
|
|
340
|
+
* THE QUESTION: "what is the user starting to type?" It ranks NAMES, not
|
|
341
|
+
* materials — instead use {@link MaterialGraphClient.search} once they commit,
|
|
342
|
+
* {@link MaterialGraphClient.suggestColour} for colour names, and
|
|
343
|
+
* {@link MaterialGraphClient.searchVocabulary} for attribute/value tokens.
|
|
344
|
+
*
|
|
191
345
|
* Entity typeahead across products, brands and categories
|
|
192
346
|
* (GET `/api/v1/search/suggest`). Built for per-keystroke use: one indexed
|
|
193
347
|
* lookup, no embedding call and no model. Every suggestion carries the
|
|
@@ -202,19 +356,53 @@ export class MaterialGraphClient {
|
|
|
202
356
|
return this.#get("search_suggest", suggestQuery(q, options), signal);
|
|
203
357
|
}
|
|
204
358
|
// ── Catalog ──────────────────────────────────────────────────────────────────
|
|
205
|
-
/**
|
|
359
|
+
/**
|
|
360
|
+
* THE QUESTION: "the user said 'fire rating' — which canonical attribute is
|
|
361
|
+
* that?" Free text in, canonical `attributeId`s out; pair with
|
|
362
|
+
* {@link MaterialGraphClient.resolveAttributeValue} for the VALUE, then spend
|
|
363
|
+
* the pair on {@link MaterialGraphClient.bySpec} or `search({ filters })`.
|
|
364
|
+
* Instead use {@link MaterialGraphClient.specificationFacets} to browse rather
|
|
365
|
+
* than resolve.
|
|
366
|
+
*/
|
|
206
367
|
resolveAttribute(q, options, signal) {
|
|
207
368
|
return this.#get("catalog_resolve_attribute", { q, limit: options?.limit, facetableOnly: options?.facetableOnly }, signal);
|
|
208
369
|
}
|
|
209
|
-
/**
|
|
370
|
+
/**
|
|
371
|
+
* THE QUESTION: "the user said 'class A' — what canonical term is that, under
|
|
372
|
+
* THIS attribute?" The value half of the pair
|
|
373
|
+
* {@link MaterialGraphClient.resolveAttribute} starts. Read
|
|
374
|
+
* `resolution.recommendedAction`, not just `best`: `ambiguous` means ask.
|
|
375
|
+
*
|
|
376
|
+
* @param attributeId Canonical vocabulary, not an opaque id — discover it, do
|
|
377
|
+
* not invent it.
|
|
378
|
+
*/
|
|
210
379
|
resolveAttributeValue(attributeId, query, options, signal) {
|
|
211
380
|
return this.#get("catalog_resolve_value", { q: query, limit: options?.limit }, signal, { attributeId });
|
|
212
381
|
}
|
|
213
|
-
/**
|
|
382
|
+
/**
|
|
383
|
+
* THE QUESTION: "is 'FloorScore' an attribute or a value, and what is it called
|
|
384
|
+
* here?" The catch-all when you do not know which half of an `attribute:value`
|
|
385
|
+
* pair a phrase belongs to. Instead use
|
|
386
|
+
* {@link MaterialGraphClient.resolveAttribute} /
|
|
387
|
+
* {@link MaterialGraphClient.resolveAttributeValue} once you do, and
|
|
388
|
+
* {@link MaterialGraphClient.glossary} to EXPLAIN a term rather than find it.
|
|
389
|
+
*/
|
|
214
390
|
searchVocabulary(query, options, signal) {
|
|
215
391
|
return this.#get("catalog_vocabulary_search", { q: query, limit: options?.limit, kind: options?.kind }, signal);
|
|
216
392
|
}
|
|
217
393
|
/**
|
|
394
|
+
* THE QUESTION: "which materials, from ANYONE, are ASTM E84 Class A AND
|
|
395
|
+
* GREENGUARD-certified?" — hard specifications, no ranking, every brand at once
|
|
396
|
+
* whatever each called the value in source.
|
|
397
|
+
*
|
|
398
|
+
* Instead use `search({ filters })` when the spec merely GATES a ranked
|
|
399
|
+
* question, `codes.advisory()` when the real question is what a space demands,
|
|
400
|
+
* and {@link MaterialGraphClient.specificationFacets} first if you do not know
|
|
401
|
+
* the canonical tokens — guessing one returns an honest, empty answer.
|
|
402
|
+
*
|
|
403
|
+
* A PAGE, NOT A CORPUS, unusually so: `totalMatches` is the size of the
|
|
404
|
+
* returned sample under `limit`, not a corpus count. See {@link BySpecResponse}.
|
|
405
|
+
*
|
|
218
406
|
* Find materials by canonical specification across every brand — the
|
|
219
407
|
* value-anchored, spec-only primitive (GET `/api/v1/search/specifications`).
|
|
220
408
|
* Each `spec` is an `attribute:value` criterion; a variant must satisfy ALL of
|
|
@@ -229,16 +417,31 @@ export class MaterialGraphClient {
|
|
|
229
417
|
return this.#get("catalog_by_spec", { spec: Array.isArray(spec) ? spec : [spec], limit: options?.limit }, signal);
|
|
230
418
|
}
|
|
231
419
|
// ── Taxonomy + meta ──────────────────────────────────────────────────────────
|
|
232
|
-
/**
|
|
420
|
+
/**
|
|
421
|
+
* THE QUESTION: "what kinds of thing exist, and what are their canonical keys?"
|
|
422
|
+
* The source of every `categoryKey` / product-type id the other lenses accept
|
|
423
|
+
* (`search({ category })`, `colours.profiles()`,
|
|
424
|
+
* {@link MaterialGraphClient.applicableSpecs}). Discover, never hand-write.
|
|
425
|
+
*/
|
|
233
426
|
taxonomy(signal) {
|
|
234
427
|
return this.#get("get_category_taxonomy", undefined, signal);
|
|
235
428
|
}
|
|
236
|
-
/**
|
|
429
|
+
/**
|
|
430
|
+
* THE QUESTION: "what is this key allowed to do?" Check `scopes` before
|
|
431
|
+
* offering a capability: the write surfaces need `mg:ingest` / `mg:compose`,
|
|
432
|
+
* and `mg:admin` unlocks extra evidence fields.
|
|
433
|
+
*/
|
|
237
434
|
whoami(signal) {
|
|
238
435
|
return this.#get("whoami", undefined, signal);
|
|
239
436
|
}
|
|
240
437
|
// ── Pairings / demand ────────────────────────────────────────────────────────
|
|
241
438
|
/**
|
|
439
|
+
* THE QUESTION: "here is my whole scheme so far — what completes it?" The
|
|
440
|
+
* multi-seed form of {@link MaterialGraphClient.pairings}: lift is summed across
|
|
441
|
+
* seeds, so a companion suiting one pick ranks below one suiting several.
|
|
442
|
+
* Instead use `pairings` for a single material and `colours.palette()` when the
|
|
443
|
+
* scheme is COLOURS rather than materials. Check `unresolved` before presenting.
|
|
444
|
+
*
|
|
242
445
|
* Coordinate a palette of already-chosen materials (POST
|
|
243
446
|
* `/api/v1/pairings/coordinate`). Returns demand-lift-ranked companions with
|
|
244
447
|
* colour swatches and an annotate-only `colourFit` (never reorders in v1).
|
|
@@ -247,13 +450,25 @@ export class MaterialGraphClient {
|
|
|
247
450
|
return this.#post("coordinate_materials", input, signal);
|
|
248
451
|
}
|
|
249
452
|
/**
|
|
250
|
-
*
|
|
251
|
-
*
|
|
453
|
+
* THE QUESTION: "how much is this material actually specified, and what gets
|
|
454
|
+
* specified alongside it?" — real order behaviour, trailing 36 months.
|
|
455
|
+
*
|
|
456
|
+
* Instead use {@link MaterialGraphClient.demandBatch} for many ids (this one
|
|
457
|
+
* also resolves free text, which the batch deliberately does not) and
|
|
458
|
+
* {@link MaterialGraphClient.pairings} for a presentable rail. A null `demand`
|
|
459
|
+
* means UNREAD, never zero. Provide EXACTLY ONE of `variantId` or `material`.
|
|
252
460
|
*/
|
|
253
461
|
demand(params, signal) {
|
|
254
462
|
return this.#get("get_material_demand", { variantId: params.variantId, material: params.material }, signal);
|
|
255
463
|
}
|
|
256
464
|
/**
|
|
465
|
+
* THE QUESTION: "for each of these picks I already hold, how much momentum does
|
|
466
|
+
* it have?" Enrichment, never discovery or ranking. Instead use
|
|
467
|
+
* {@link MaterialGraphClient.demand} for one material named in free text and
|
|
468
|
+
* {@link MaterialGraphClient.search} to find candidates at all. Only
|
|
469
|
+
* `state: "available"` carries numbers; the other three mean "no number", never
|
|
470
|
+
* "no demand".
|
|
471
|
+
*
|
|
257
472
|
* Enrich a bounded set of ALREADY-CHOSEN candidate ids with their real
|
|
258
473
|
* specification demand (POST `/api/v1/demand/batch`, 1–48 ids). The bounded
|
|
259
474
|
* batch complement to {@link demand}: no fuzzy resolution, ranking, or
|
|
@@ -278,20 +493,36 @@ export class MaterialGraphClient {
|
|
|
278
493
|
}
|
|
279
494
|
// ── Codes (advisory building-code compliance) ────────────────────────────────
|
|
280
495
|
/**
|
|
496
|
+
* THE QUESTION: "may I put these materials in an A-2 corridor in Texas?"
|
|
497
|
+
*
|
|
281
498
|
* Badge materials against the advisory building-code registry (POST
|
|
282
499
|
* `/api/v1/codes/advisory`). Identical to {@link CodesClient.advisory}, kept
|
|
283
|
-
* flat because it shipped before the `codes` namespace existed
|
|
500
|
+
* flat because it shipped before the `codes` namespace existed — prefer
|
|
501
|
+
* `mg.codes.advisory()` in new code, and start at `mg.codes.context()` to
|
|
502
|
+
* discover every accepted context value. `unverified` is not a pass.
|
|
284
503
|
*/
|
|
285
504
|
codeAdvisory(input, signal) {
|
|
286
505
|
return this.codes.advisory(input, signal);
|
|
287
506
|
}
|
|
288
507
|
// ── Colour helpers (extract) ─────────────────────────────────────────────────
|
|
289
|
-
/**
|
|
508
|
+
/**
|
|
509
|
+
* THE QUESTION: "what colours are in this image?" — the palette only, no
|
|
510
|
+
* materials. The bridge from an inspiration image into the colour lens: extract
|
|
511
|
+
* here, then feed the swatches to `colours.palette()` or
|
|
512
|
+
* {@link MaterialGraphClient.byColour}. Instead use
|
|
513
|
+
* {@link MaterialGraphClient.match} for the MATERIALS in the photo.
|
|
514
|
+
*/
|
|
290
515
|
extractPalette(input, signal) {
|
|
291
516
|
return this.#post("extract_image_palette", input, signal);
|
|
292
517
|
}
|
|
293
518
|
// ── Catalog (facets / applicable specs / glossary) ───────────────────────────
|
|
294
519
|
/**
|
|
520
|
+
* THE QUESTION: "what can I even filter on, and what values does the corpus
|
|
521
|
+
* actually hold?" The start of every spec-driven workflow. Instead use
|
|
522
|
+
* {@link MaterialGraphClient.attributeValues} for ONE attribute's distribution,
|
|
523
|
+
* {@link MaterialGraphClient.applicableSpecs} for what a product type COULD
|
|
524
|
+
* carry, {@link MaterialGraphClient.schema} for the definition.
|
|
525
|
+
*
|
|
295
526
|
* Discover the live specification vocabulary (GET
|
|
296
527
|
* `/api/v1/catalog/specifications/facets`) — the facetable spec attributes that
|
|
297
528
|
* have real product-graph data, each with its top canonical values and the
|
|
@@ -318,20 +549,43 @@ export class MaterialGraphClient {
|
|
|
318
549
|
attributeId,
|
|
319
550
|
});
|
|
320
551
|
}
|
|
321
|
-
/**
|
|
552
|
+
/**
|
|
553
|
+
* THE QUESTION: "what specs COULD a material of this type carry?" — the
|
|
554
|
+
* template, not the data. Use it to know which absences are meaningful; instead
|
|
555
|
+
* use {@link MaterialGraphClient.specificationFacets} for what the corpus holds
|
|
556
|
+
* and {@link MaterialGraphClient.materialBlocks} for one material.
|
|
557
|
+
*
|
|
558
|
+
* @param key A canonical taxonomy key from {@link MaterialGraphClient.taxonomy}.
|
|
559
|
+
*/
|
|
322
560
|
applicableSpecs(key, signal) {
|
|
323
561
|
return this.#get("get_applicable_specs", undefined, signal, { key });
|
|
324
562
|
}
|
|
325
|
-
/**
|
|
563
|
+
/**
|
|
564
|
+
* THE QUESTION: "what does 'DCOF' MEAN?" — one term explained, for a human.
|
|
565
|
+
* Explanation, not retrieval: instead use
|
|
566
|
+
* {@link MaterialGraphClient.searchVocabulary} to FIND a token and
|
|
567
|
+
* {@link MaterialGraphClient.attributeValues} for the values it takes.
|
|
568
|
+
*/
|
|
326
569
|
glossary(term, signal) {
|
|
327
570
|
return this.#get("explain_material_term", undefined, signal, { term });
|
|
328
571
|
}
|
|
329
572
|
// ── Brands ───────────────────────────────────────────────────────────────────
|
|
330
|
-
/**
|
|
573
|
+
/**
|
|
574
|
+
* THE QUESTION: "whose catalogs are in here, and how big is each?" The SDK's
|
|
575
|
+
* one OFFSET-paginated surface: read `meta.total` for the collection size and
|
|
576
|
+
* `meta.hasMore` to keep walking. Instead use {@link MaterialGraphClient.brand}
|
|
577
|
+
* for one brand's profile and trade signals.
|
|
578
|
+
*/
|
|
331
579
|
brands(options = {}, signal) {
|
|
332
580
|
return this.#get("list_brands", { q: options.q, limit: options.limit, offset: options.offset }, signal);
|
|
333
581
|
}
|
|
334
|
-
/**
|
|
582
|
+
/**
|
|
583
|
+
* THE QUESTION: "who is this brand, and what do they claim?" — profile plus the
|
|
584
|
+
* trade/spec signals ON RECORD. `trade` is keyed by attribute id and an ABSENT
|
|
585
|
+
* key means no value is held — never infer `false`. Instead use
|
|
586
|
+
* {@link MaterialGraphClient.search} to find their materials; this describes the
|
|
587
|
+
* company, not the catalog.
|
|
588
|
+
*/
|
|
335
589
|
brand(brandSlug, options, signal) {
|
|
336
590
|
return this.#get("get_brand_profile", { q: options?.q }, signal, {
|
|
337
591
|
brandSlug,
|
|
@@ -358,15 +612,33 @@ export class MaterialGraphClient {
|
|
|
358
612
|
sourceMaterialBatch(batchId, signal) {
|
|
359
613
|
return getSourceMaterialBatchCall(this.#transport, batchId, signal);
|
|
360
614
|
}
|
|
361
|
-
/**
|
|
615
|
+
/**
|
|
616
|
+
* THE QUESTION: "what can the hosted agentic surface be asked to DO?" Start
|
|
617
|
+
* here before {@link MaterialGraphClient.createRun}: each capability carries its
|
|
618
|
+
* live input/output schemas, scopes and side-effect class, so nothing about a
|
|
619
|
+
* Run need be hardcoded.
|
|
620
|
+
*/
|
|
362
621
|
capabilities(signal) {
|
|
363
622
|
return capabilitiesRun(this.#transport, signal);
|
|
364
623
|
}
|
|
365
|
-
/**
|
|
624
|
+
/**
|
|
625
|
+
* THE QUESTION: "do a whole job for me" — the ASYNC lens. Everything else here
|
|
626
|
+
* answers in one round trip; a Run may suspend for human input and finish
|
|
627
|
+
* minutes later. Follow with {@link MaterialGraphClient.streamRunEvents} (live)
|
|
628
|
+
* or {@link MaterialGraphClient.run} (poll); resume via
|
|
629
|
+
* {@link MaterialGraphClient.resumeRun}; pick the capability from
|
|
630
|
+
* {@link MaterialGraphClient.capabilities}. Pass `idempotencyKey` so a retry
|
|
631
|
+
* replays rather than duplicates.
|
|
632
|
+
*/
|
|
366
633
|
createRun(body, signal) {
|
|
367
634
|
return createRun(this.#transport, body, signal);
|
|
368
635
|
}
|
|
369
|
-
/**
|
|
636
|
+
/**
|
|
637
|
+
* THE QUESTION: "where has this Run got to?" — the caller-owned, redacted
|
|
638
|
+
* ledger record. Polling; prefer {@link MaterialGraphClient.streamRunEvents}
|
|
639
|
+
* when a human is waiting. `suspended` is not a failure — it means the Run is
|
|
640
|
+
* asking you something (see {@link MaterialGraphClient.resumeRun}).
|
|
641
|
+
*/
|
|
370
642
|
run(runId, signal) {
|
|
371
643
|
return getRun(this.#transport, runId, signal);
|
|
372
644
|
}
|
|
@@ -390,7 +662,11 @@ export class MaterialGraphClient {
|
|
|
390
662
|
resumeRun(runId, resumeData, signalOrOptions) {
|
|
391
663
|
return resumeRun(this.#transport, runId, resumeData, signalOrOptions);
|
|
392
664
|
}
|
|
393
|
-
/**
|
|
665
|
+
/**
|
|
666
|
+
* THE QUESTION: "stop this Run." Requests DURABLE cancellation — the Run
|
|
667
|
+
* acknowledges and unwinds; the call returning does not mean it has stopped.
|
|
668
|
+
* Poll {@link MaterialGraphClient.run} for the terminal state.
|
|
669
|
+
*/
|
|
394
670
|
cancelRun(runId, signal) {
|
|
395
671
|
return cancelRun(this.#transport, runId, signal);
|
|
396
672
|
}
|
|
@@ -421,7 +697,11 @@ export class MaterialGraphClient {
|
|
|
421
697
|
createProject(body = {}, signal) {
|
|
422
698
|
return createProjectCall(this.#transport, body, signal);
|
|
423
699
|
}
|
|
424
|
-
/**
|
|
700
|
+
/**
|
|
701
|
+
* THE QUESTION: "what has happened on this project?" — the append-only event log
|
|
702
|
+
* FOLDED into a read model, plus the raw `events`. Read the folded collections;
|
|
703
|
+
* the log is there for audit.
|
|
704
|
+
*/
|
|
425
705
|
getProject(projectId, signal) {
|
|
426
706
|
return getProjectCall(this.#transport, projectId, signal);
|
|
427
707
|
}
|
|
@@ -432,7 +712,10 @@ export class MaterialGraphClient {
|
|
|
432
712
|
appendProjectEvent(projectId, body, signal) {
|
|
433
713
|
return appendProjectEventCall(this.#transport, projectId, body, signal);
|
|
434
714
|
}
|
|
435
|
-
/**
|
|
715
|
+
/**
|
|
716
|
+
* THE QUESTION: "what Boxes do I have?" Scoped to the authenticated principal —
|
|
717
|
+
* another key's projects are invisible, not forbidden.
|
|
718
|
+
*/
|
|
436
719
|
listProjects(signal) {
|
|
437
720
|
return listProjectsCall(this.#transport, signal);
|
|
438
721
|
}
|
package/codes-client.d.ts
CHANGED
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
*
|
|
13
13
|
* ADVISORY ONLY: every verdict and every envelope carries the AHJ caveat — this
|
|
14
14
|
* is model code as adopted, never a legal determination.
|
|
15
|
+
*
|
|
16
|
+
* ROUTING. Codes answers "is this material allowed in THIS SPACE?" — a question
|
|
17
|
+
* about a jurisdiction, an occupancy group and a finish location, not about a
|
|
18
|
+
* material alone.
|
|
19
|
+
* - "which materials carry Class A?" → `mg.bySpec("fire_classifications:...")`.
|
|
20
|
+
* That is a property question; this namespace is what tells you the space
|
|
21
|
+
* demands Class A in the first place.
|
|
22
|
+
* - "what does this one material hold?" → `mg.materialBlocks(variantId)`.
|
|
23
|
+
* - "find me something like this, but compliant" → `mg.search({ filters })`
|
|
24
|
+
* once `requirements()` has told you which filter to apply.
|
|
15
25
|
*/
|
|
16
26
|
import type { CodeAdvisoryInput, CodeAdvisoryResponse, CodeContextRequest, CodeContextResponse, CodeRequirementsResponse } from "./codes-types.js";
|
|
17
27
|
import type { QueryParams } from "./http-transport.js";
|
|
@@ -25,6 +35,13 @@ export declare class CodesClient {
|
|
|
25
35
|
#private;
|
|
26
36
|
constructor(transport: CodesTransport);
|
|
27
37
|
/**
|
|
38
|
+
* THE QUESTION: "what may I even say about a space?" — the vocabulary for
|
|
39
|
+
* stating a code context, and the first call of the three.
|
|
40
|
+
*
|
|
41
|
+
* Everything {@link CodesClient.requirements} and {@link CodesClient.advisory}
|
|
42
|
+
* accept is listed here, so nothing need be hardcoded and a 400 for an unknown
|
|
43
|
+
* jurisdiction / occupancy / space is always avoidable.
|
|
44
|
+
*
|
|
28
45
|
* The vocabulary for stating a code context (GET `/api/v1/codes/context`):
|
|
29
46
|
* seeded jurisdictions and their coverage tier, the occupancy groups the
|
|
30
47
|
* registry keys on, finish locations, dimensions, classification scales with
|
|
@@ -43,6 +60,12 @@ export declare class CodesClient {
|
|
|
43
60
|
*/
|
|
44
61
|
context(signal?: AbortSignal): Promise<CodeContextResponse>;
|
|
45
62
|
/**
|
|
63
|
+
* THE QUESTION: "what does this space DEMAND?" — before any candidate exists.
|
|
64
|
+
*
|
|
65
|
+
* Run it before shortlisting, not after: it tells you which specification to
|
|
66
|
+
* filter on, which you then spend on `mg.bySpec()` or `mg.search({ filters })`.
|
|
67
|
+
* Use {@link CodesClient.advisory} once you hold candidates to badge.
|
|
68
|
+
*
|
|
46
69
|
* What a space demands, before any candidate exists (GET
|
|
47
70
|
* `/api/v1/codes/requirements`). State the space by explicit axes, or by a
|
|
48
71
|
* `roomType` the axes are correlated from.
|
|
@@ -61,6 +84,19 @@ export declare class CodesClient {
|
|
|
61
84
|
*/
|
|
62
85
|
requirements(input: CodeContextRequest, signal?: AbortSignal): Promise<CodeRequirementsResponse>;
|
|
63
86
|
/**
|
|
87
|
+
* THE QUESTION: "do THESE candidates satisfy what this space demands?" — the
|
|
88
|
+
* third call, once you hold materials.
|
|
89
|
+
*
|
|
90
|
+
* Context is CONDITIONAL: supply `occupancyGroup` + `spaceType`, or a
|
|
91
|
+
* `roomType` for them to correlate from, or a mix — an explicit axis always
|
|
92
|
+
* wins, and an axis that resolves to nothing is a 400 naming it. The full rule,
|
|
93
|
+
* with the validator's own messages, is on {@link CodeContextRequest}.
|
|
94
|
+
*
|
|
95
|
+
* Read `summary.status` honestly: `unverified` means no comparable rating is on
|
|
96
|
+
* file. It is not a pass, and it is common. Use {@link CodesClient.requirements}
|
|
97
|
+
* when you have no candidates yet, and `mg.materialBlocks()` to see whether a
|
|
98
|
+
* material's fire/slip facts are missing or merely unverified.
|
|
99
|
+
*
|
|
64
100
|
* Badge materials against the advisory building-code registry for one context
|
|
65
101
|
* (POST `/api/v1/codes/advisory`, ≤ 100 ids). Each applicable requirement is
|
|
66
102
|
* badged `meets` / `shortfall` / `unknown` from the material's accepted fire
|
package/codes-client.js
CHANGED
|
@@ -4,6 +4,13 @@ export class CodesClient {
|
|
|
4
4
|
this.#transport = transport;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
+
* THE QUESTION: "what may I even say about a space?" — the vocabulary for
|
|
8
|
+
* stating a code context, and the first call of the three.
|
|
9
|
+
*
|
|
10
|
+
* Everything {@link CodesClient.requirements} and {@link CodesClient.advisory}
|
|
11
|
+
* accept is listed here, so nothing need be hardcoded and a 400 for an unknown
|
|
12
|
+
* jurisdiction / occupancy / space is always avoidable.
|
|
13
|
+
*
|
|
7
14
|
* The vocabulary for stating a code context (GET `/api/v1/codes/context`):
|
|
8
15
|
* seeded jurisdictions and their coverage tier, the occupancy groups the
|
|
9
16
|
* registry keys on, finish locations, dimensions, classification scales with
|
|
@@ -24,6 +31,12 @@ export class CodesClient {
|
|
|
24
31
|
return this.#transport.get("list_code_context", undefined, signal);
|
|
25
32
|
}
|
|
26
33
|
/**
|
|
34
|
+
* THE QUESTION: "what does this space DEMAND?" — before any candidate exists.
|
|
35
|
+
*
|
|
36
|
+
* Run it before shortlisting, not after: it tells you which specification to
|
|
37
|
+
* filter on, which you then spend on `mg.bySpec()` or `mg.search({ filters })`.
|
|
38
|
+
* Use {@link CodesClient.advisory} once you hold candidates to badge.
|
|
39
|
+
*
|
|
27
40
|
* What a space demands, before any candidate exists (GET
|
|
28
41
|
* `/api/v1/codes/requirements`). State the space by explicit axes, or by a
|
|
29
42
|
* `roomType` the axes are correlated from.
|
|
@@ -50,6 +63,19 @@ export class CodesClient {
|
|
|
50
63
|
}, signal);
|
|
51
64
|
}
|
|
52
65
|
/**
|
|
66
|
+
* THE QUESTION: "do THESE candidates satisfy what this space demands?" — the
|
|
67
|
+
* third call, once you hold materials.
|
|
68
|
+
*
|
|
69
|
+
* Context is CONDITIONAL: supply `occupancyGroup` + `spaceType`, or a
|
|
70
|
+
* `roomType` for them to correlate from, or a mix — an explicit axis always
|
|
71
|
+
* wins, and an axis that resolves to nothing is a 400 naming it. The full rule,
|
|
72
|
+
* with the validator's own messages, is on {@link CodeContextRequest}.
|
|
73
|
+
*
|
|
74
|
+
* Read `summary.status` honestly: `unverified` means no comparable rating is on
|
|
75
|
+
* file. It is not a pass, and it is common. Use {@link CodesClient.requirements}
|
|
76
|
+
* when you have no candidates yet, and `mg.materialBlocks()` to see whether a
|
|
77
|
+
* material's fire/slip facts are missing or merely unverified.
|
|
78
|
+
*
|
|
53
79
|
* Badge materials against the advisory building-code registry for one context
|
|
54
80
|
* (POST `/api/v1/codes/advisory`, ≤ 100 ids). Each applicable requirement is
|
|
55
81
|
* badged `meets` / `shortfall` / `unknown` from the material's accepted fire
|