@instruments/materialgraph 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/browse-types.d.ts +147 -0
  2. package/browse-types.js +1 -0
  3. package/by-colour-types.d.ts +56 -0
  4. package/client-browse.d.ts +13 -3
  5. package/client-browse.js +12 -2
  6. package/client-exploration.d.ts +9 -0
  7. package/client-exploration.js +7 -0
  8. package/client-source-material-comments.d.ts +51 -0
  9. package/client-source-material-comments.js +40 -0
  10. package/client.d.ts +28 -22
  11. package/client.js +33 -24
  12. package/colour-aggregate-types.d.ts +58 -0
  13. package/colours-client.d.ts +8 -2
  14. package/colours-client.js +15 -2
  15. package/composition-client.d.ts +33 -0
  16. package/composition-client.js +27 -0
  17. package/composition-types.d.ts +46 -0
  18. package/coordinate-client.d.ts +23 -0
  19. package/coordinate-client.js +25 -0
  20. package/exploration-types.d.ts +110 -0
  21. package/exploration-types.js +1 -0
  22. package/fan-deck-types.d.ts +4 -1
  23. package/index.d.ts +4 -1
  24. package/operations.d.ts +1 -1
  25. package/operations.js +25 -0
  26. package/package.json +1 -1
  27. package/palette-search-types.d.ts +31 -0
  28. package/run-types.d.ts +132 -0
  29. package/runs.d.ts +35 -1
  30. package/runs.js +61 -1
  31. package/schedule-types.d.ts +128 -0
  32. package/schema-resource-types.d.ts +1 -1
  33. package/search-refinement-types.d.ts +28 -1
  34. package/search-request-types.d.ts +172 -0
  35. package/search-request-types.js +1 -0
  36. package/slot-pool-types.d.ts +370 -0
  37. package/slot-pool-types.js +1 -0
  38. package/source-material-validation.d.ts +5 -1
  39. package/source-material-validation.js +111 -4
  40. package/types.d.ts +149 -199
  41. package/validation.d.ts +12 -0
  42. package/validation.js +23 -0
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Wire types for the browse lane — GET `/api/v1/search/browse`.
3
+ *
4
+ * The SDK's one genuinely cursor-paginated surface, and the only lane with no
5
+ * query, no relevance and no ranking: the corpus in a stated order. That is what
6
+ * a category listing or a brand fan deck actually needs, and it is why `count`
7
+ * matters here more than anywhere else — a listing that cannot state its own
8
+ * size has to either walk every page or report a page size as a total.
9
+ *
10
+ * Lifted out of `types.ts` for the repository's source-file size budget when the
11
+ * lane gained its non-colour sorts and its feed count (MG-1483 / MG-1542).
12
+ * Nothing about the client's surface changed: `index.ts` re-exports these
13
+ * exactly as before.
14
+ */
15
+ import type { ByColourSwatch } from "./by-colour-types.js";
16
+ import type { Cursor, ProductId, VariantId } from "./identifiers.js";
17
+ import type { CountedTotal, OpenEnum } from "./types.js";
18
+ /** One item from the pure colour-scalar browse. */
19
+ export interface BrowseResultItem {
20
+ /** Opaque — round-trip verbatim. */
21
+ variantId: VariantId;
22
+ brand: string;
23
+ product: string;
24
+ brandSlug: string | null;
25
+ productId: ProductId | null;
26
+ color: string | null;
27
+ variantName: string | null;
28
+ imageUrl: string | null;
29
+ scalars: {
30
+ l: number | null;
31
+ chroma: number | null;
32
+ vibrancy: number | null;
33
+ };
34
+ retired?: boolean;
35
+ /**
36
+ * The row's own image palette — the FULL stored palette of the SAME image
37
+ * `imageUrl` serves, uncapped and in stored order, each swatch with `oklab`
38
+ * (see {@link ByColourSwatch}), so a card can render, sort or group colours
39
+ * without a second call. A one-swatch palette means the extractor found one
40
+ * colour (solid-colour materials are genuinely monochrome), not that the list
41
+ * was trimmed. ABSENT when no palette is on record for that image: absent
42
+ * means unknown, NOT that the material has no colours. These swatches come
43
+ * from product imagery, not from the manufacturer.
44
+ */
45
+ palette?: ByColourSwatch[];
46
+ }
47
+ /** The browse orders. `name` (A→Z) is the default; `recent` is newest first. */
48
+ export type BrowseSort = OpenEnum<"name" | "recent" | "light" | "dark" | "vivid" | "muted" | "colourful">;
49
+ /**
50
+ * The browse feed's total — how many materials the browse DESCRIBES, which is
51
+ * never `results.length`.
52
+ *
53
+ * Uncapped, unlike {@link SearchCounts}: `basis:"exact"` is the true figure
54
+ * whatever its size, and `basis:"lowerBound"` means the count query timed out
55
+ * and the value is a floor. Counted over the SAME filters the page was drawn
56
+ * from, so a `types=`, `brands=` or `corpusScope=` narrowed browse reports the
57
+ * narrowed total — you will never get a whole-graph number beside a filtered
58
+ * page.
59
+ */
60
+ export interface BrowseCounts {
61
+ variants: CountedTotal;
62
+ }
63
+ /**
64
+ * A single page of the browse — the SDK's one genuinely cursor-paginated
65
+ * surface. `results.length` is this page. Ask for `count: true` and `counts`
66
+ * states the total; without it the browse states no total at all, and a corpus
67
+ * claim can only come from walking every page (`client.browse.pages(...)` does
68
+ * the loop).
69
+ */
70
+ export interface BrowseResponse {
71
+ /** THIS PAGE. */
72
+ results: BrowseResultItem[];
73
+ /** Opaque keyset for the next page. Absent = the walk is finished. */
74
+ cursor?: Cursor;
75
+ /**
76
+ * The order this page was built in — echoed, because `sort` is optional and
77
+ * defaulted, so a caller that omitted it need not know the server's default
78
+ * to label its own listing.
79
+ */
80
+ sort: BrowseSort;
81
+ /**
82
+ * The total, present ONLY when `count: true` was passed. Absent means it was
83
+ * not asked for — never that the set is empty.
84
+ */
85
+ counts?: BrowseCounts;
86
+ /**
87
+ * The corpus this page was drawn from — present ONLY when the request
88
+ * narrowed it. ABSENT means the whole graph: every material MaterialGraph
89
+ * knows, orderable or not.
90
+ */
91
+ corpusScope?: {
92
+ scope: OpenEnum<"all" | "materialBankOrderable">;
93
+ /** What the scope admits, in one sentence, for a human reader. */
94
+ description: string;
95
+ };
96
+ }
97
+ /** Params for the pure colour `search/browse` surface. */
98
+ export interface BrowseParams {
99
+ /**
100
+ * OPTIONAL. Defaults to `name` (A→Z); `recent` is newest first.
101
+ *
102
+ * The five colour ramps (`light`/`dark`/`vivid`/`muted`/`colourful`) order by
103
+ * an EXTRACTED colour scalar and therefore see only colour-extracted
104
+ * materials — a genuinely smaller set than `name` and `recent` walk. That is
105
+ * not a bug to route around; ask for `count` and each sort reports the size of
106
+ * the set it actually describes.
107
+ */
108
+ sort?: BrowseSort;
109
+ /**
110
+ * The previous page's `cursor`, verbatim. Opaque keyset state — not an offset.
111
+ * Changing `sort`, `limit`, `types`, `brands` or `corpusScope` invalidates it.
112
+ */
113
+ cursor?: Cursor;
114
+ /** Page size. */
115
+ limit?: number;
116
+ /**
117
+ * Return `counts.variants` — the total size of the set this browse walks,
118
+ * counted over the same filters the page came from.
119
+ *
120
+ * Opt-in, because it is a second query. A category listing that cannot state
121
+ * how many things it is listing has to either walk every page or report the
122
+ * page size as a total; this is the third option.
123
+ */
124
+ count?: boolean;
125
+ /**
126
+ * Comma-separated taxonomy keys to restrict the browse to, at EITHER tier of
127
+ * `/api/v1/taxonomy/categories`. A GROUP key (`tile_surfaces`) expands to
128
+ * every product type under it, a TYPE key (`ceramic_porcelain_tile`) stands
129
+ * for itself, and a mixed list is their deduplicated union. A key in neither
130
+ * tier is a 400 listing the valid keys — never an empty page.
131
+ */
132
+ types?: string;
133
+ /**
134
+ * Comma-separated brand slugs (`farrow-ball`) or display names
135
+ * (`Farrow & Ball`), case-insensitive. One brand plus a colour `sort` is that
136
+ * brand's fan deck. An unknown brand is a 400 naming it, never an empty page.
137
+ */
138
+ brands?: string;
139
+ /**
140
+ * Which corpus to walk. ABSENT means the whole graph — every material
141
+ * MaterialGraph knows, orderable or not. `materialBankOrderable` restricts to
142
+ * materials carrying a Material Bank identity, applied inside the scan, so a
143
+ * scoped page is still a full page and `cursor` still means another eligible
144
+ * row exists.
145
+ */
146
+ corpusScope?: OpenEnum<"all" | "materialBankOrderable">;
147
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -106,6 +106,44 @@ export interface ColourCollapse {
106
106
  /** Rows dropped for resolving to a better-ranked row's stored photograph. */
107
107
  droppedBySharedImage: number;
108
108
  }
109
+ /**
110
+ * Receipt naming which retrieval lane answered, and the colours it actually ran
111
+ * on. The live endpoint always sends it.
112
+ *
113
+ * Two lanes answer two different questions. A ONE-COLOUR query is a CONTAINMENT
114
+ * question — which materials hold a swatch near this colour — and is answered as
115
+ * a bounded OKLab ball over stored palette swatches. A multi-colour query is a
116
+ * SIMILARITY question — which palettes as a whole resemble this palette — and is
117
+ * answered by nearest-neighbour over the palette embedding. The two rank on
118
+ * different geometries, so `colourDistance` on a match means a different thing
119
+ * in each and is NOT comparable across them.
120
+ */
121
+ export interface ColourLaneReceipt {
122
+ /** Which lane answered. Widened so a new server lane still type-checks. */
123
+ lane: OpenEnum<"containment" | "palette">;
124
+ /** Why this lane, in one sentence, for a human reader. */
125
+ reason: string;
126
+ /**
127
+ * How the page was ordered. `representative` — the containment default —
128
+ * draws the page in the contained set's own proportions of solid and
129
+ * multi-swatch materials, nearest-first within each, because ordering
130
+ * containment by proximity alone returns a page of solid-colour materials.
131
+ */
132
+ order: OpenEnum<"representative" | "nearest" | "similarity">;
133
+ /**
134
+ * The colours the search ACTUALLY ran on, after minor-proportion accents were
135
+ * dropped. Compare against `query.colours`: a difference means the search that
136
+ * ran is not the search you asked for.
137
+ */
138
+ effectiveColours: string[];
139
+ /**
140
+ * Colours removed from your query for carrying a proportion below the accent
141
+ * threshold. EMPTY is the normal case. Non-empty and those colours were absent
142
+ * from retrieval entirely — they did not merely rank low, so an empty-looking
143
+ * result is not evidence that nothing holds them.
144
+ */
145
+ droppedColours: string[];
146
+ }
109
147
  /** The `search/colours` response. */
110
148
  export interface ByColourResponse {
111
149
  query: {
@@ -135,6 +173,24 @@ export interface ByColourResponse {
135
173
  * overfetch allowed, and this says so in counts rather than adjectives.
136
174
  */
137
175
  collapse?: ColourCollapse;
176
+ /**
177
+ * Which retrieval lane answered and the colours it ran on. The live endpoint
178
+ * always sends it; optional here only so responses captured before the lane
179
+ * split still type-check.
180
+ */
181
+ lane?: ColourLaneReceipt;
182
+ /**
183
+ * The corpus this page was drawn from — present ONLY when the request
184
+ * narrowed it. ABSENT means the whole graph: every material MaterialGraph
185
+ * knows, orderable or not. Under a scope the `familyGate` and `collapse` pool
186
+ * counts describe the SCOPED corpus too, because the scope was applied inside
187
+ * retrieval rather than over the page it produced.
188
+ */
189
+ corpusScope?: {
190
+ scope: OpenEnum<"all" | "materialBankOrderable">;
191
+ /** What the scope admits, in one sentence, for a human reader. */
192
+ description: string;
193
+ };
138
194
  }
139
195
  /** Params for `search/colours`. */
140
196
  export interface ByColourParams {
@@ -9,7 +9,7 @@
9
9
  * the pagination LOOP, which is the only real logic here and the only part worth
10
10
  * testing on its own.
11
11
  */
12
- import type { BrowseParams, BrowseResponse } from "./types.js";
12
+ import type { BrowseParams, BrowseResponse } from "./browse-types.js";
13
13
  /**
14
14
  * `client.browse(...)` — callable for ONE PAGE, `.pages()` for the whole walk.
15
15
  *
@@ -24,14 +24,24 @@ export interface BrowseNamespace {
24
24
  /** Fetch one page. The client supplies this, so the transport stays private. */
25
25
  export type BrowsePageFetcher = (params: BrowseParams, signal?: AbortSignal) => Promise<BrowseResponse>;
26
26
  /**
27
- * The query string for one browse page — the four params the route reads, named
28
- * here so the client method and the pagination loop cannot disagree about them.
27
+ * The query string for one browse page — EVERY param the route reads, named here
28
+ * so the client method and the pagination loop cannot disagree about them.
29
+ *
30
+ * "Every" is load-bearing. This is an allow-list wrapper, and an allow-list
31
+ * wrapper that rebuilds a query from named fields is precisely the shape that
32
+ * silently drops a param the server already honours: `brands` shipped on the
33
+ * route and never reached it from here, so the fan deck was unreachable through
34
+ * the published client while the server had supported it all along. When the
35
+ * route gains a param, it gains a line HERE too, or it is not shipped.
29
36
  */
30
37
  export declare function browseQuery(params: BrowseParams): {
31
38
  sort: BrowseParams["sort"];
32
39
  cursor: BrowseParams["cursor"];
33
40
  limit: BrowseParams["limit"];
41
+ count: BrowseParams["count"];
34
42
  types: BrowseParams["types"];
43
+ brands: BrowseParams["brands"];
44
+ corpusScope: BrowseParams["corpusScope"];
35
45
  };
36
46
  /**
37
47
  * Walk every page from `params.cursor` onward.
package/client-browse.js CHANGED
@@ -1,13 +1,23 @@
1
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.
2
+ * The query string for one browse page — EVERY param the route reads, named here
3
+ * so the client method and the pagination loop cannot disagree about them.
4
+ *
5
+ * "Every" is load-bearing. This is an allow-list wrapper, and an allow-list
6
+ * wrapper that rebuilds a query from named fields is precisely the shape that
7
+ * silently drops a param the server already honours: `brands` shipped on the
8
+ * route and never reached it from here, so the fan deck was unreachable through
9
+ * the published client while the server had supported it all along. When the
10
+ * route gains a param, it gains a line HERE too, or it is not shipped.
4
11
  */
5
12
  export function browseQuery(params) {
6
13
  return {
7
14
  sort: params.sort,
8
15
  cursor: params.cursor,
9
16
  limit: params.limit,
17
+ count: params.count,
10
18
  types: params.types,
19
+ brands: params.brands,
20
+ corpusScope: params.corpusScope,
11
21
  };
12
22
  }
13
23
  /**
@@ -0,0 +1,9 @@
1
+ import type { CandidateReadInput, CandidateReadResponse, GraphOperatorEvidence } from "./exploration-types.js";
2
+ import type { HttpTransport } from "./http-transport.js";
3
+ import type { VariantId } from "./identifiers.js";
4
+ /** Discovery and traversal share one namespace so availability precedes use. */
5
+ export interface ExplorationNamespace {
6
+ operators(variantId: VariantId, signal?: AbortSignal): Promise<GraphOperatorEvidence>;
7
+ candidates(variantId: VariantId, input: CandidateReadInput, signal?: AbortSignal): Promise<CandidateReadResponse>;
8
+ }
9
+ export declare function createExplorationNamespace(transport: HttpTransport): ExplorationNamespace;
@@ -0,0 +1,7 @@
1
+ import { resolveOperation } from "./operations.js";
2
+ export function createExplorationNamespace(transport) {
3
+ return {
4
+ operators: (variantId, signal) => transport.requestJson(resolveOperation("list_material_operators"), { pathParams: { variantId }, signal }),
5
+ candidates: (variantId, input, signal) => transport.requestJson(resolveOperation("read_material_candidates"), { body: input, pathParams: { variantId }, signal }),
6
+ };
7
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The per-file comment surface, namespaced as `mg.sourceMaterialComments` — the
3
+ * argument people leave on one uploaded file (MG-1345).
4
+ *
5
+ * A namespace rather than two more flat methods on an already-large facade,
6
+ * following `mg.codes` and `mg.colours`: reading a thread and adding to it are
7
+ * one family, they share a `(batchId, sourceId)` referent, and neither makes
8
+ * sense without the other. The namespace holds no transport of its own — the
9
+ * client hands it the shared one, so auth, rate-limit accounting and error
10
+ * shaping stay in one place.
11
+ *
12
+ * TWO RULES A CONSUMER MUST NOT GET WRONG.
13
+ *
14
+ * **The author name is REPORTED, never verified.** Every comment carries
15
+ * `author.attribution: "reported"` — a name somebody typed — plus `recordedBy`,
16
+ * the authenticated principal that actually wrote the row. Render "recorded as
17
+ * from Ruth Fielding", never "Ruth Fielding said". The API takes a machine key,
18
+ * and the person whose argument a remark carries is usually not the person at
19
+ * the keyboard; presenting the one as the other is the failure this field
20
+ * exists to prevent.
21
+ *
22
+ * **A reply is not a root.** {@link SourceMaterialCommentsNamespace.post}
23
+ * returns a result discriminated on `kind`: switch on it rather than reaching
24
+ * for `replies`, which a `reply` does not have. Threads are one level deep and
25
+ * the database enforces it, so a reply can never grow replies of its own.
26
+ *
27
+ * ROUTING OUT OF THIS NAMESPACE. `mg.sourceMaterialBatch(batchId)` already
28
+ * inlines every file's thread, so opening a batch is ONE call — do not loop this
29
+ * namespace over a batch's files to rebuild what that read returns. Reach for
30
+ * `list` when you are watching a single file.
31
+ */
32
+ import { HttpTransport } from "./http-transport.js";
33
+ import type { PostSourceMaterialCommentInput, PostSourceMaterialCommentResult, SourceMaterialCommentThread } from "./run-types.js";
34
+ export interface SourceMaterialCommentsNamespace {
35
+ /**
36
+ * One file's thread, oldest root first, each root carrying its replies.
37
+ * `comments` is always an array; `truncated` says whether you are holding the
38
+ * whole thread. A file that is not in that batch, or a batch you do not own,
39
+ * raises the same 404 as a file that never existed.
40
+ */
41
+ list(batchId: string, sourceId: string, signal?: AbortSignal): Promise<SourceMaterialCommentThread>;
42
+ /**
43
+ * Add one remark. Pass `parentCommentId` to answer a ROOT comment on the same
44
+ * file; nesting deeper is a 400. Pass `idempotencyKey` so a retry after a
45
+ * network timeout returns the comment you already posted rather than a
46
+ * permanent duplicate.
47
+ */
48
+ post(batchId: string, sourceId: string, input: PostSourceMaterialCommentInput, signal?: AbortSignal): Promise<PostSourceMaterialCommentResult>;
49
+ }
50
+ /** Bind the comment operations to the client's shared transport. */
51
+ export declare function createSourceMaterialCommentsNamespace(transport: HttpTransport): SourceMaterialCommentsNamespace;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The per-file comment surface, namespaced as `mg.sourceMaterialComments` — the
3
+ * argument people leave on one uploaded file (MG-1345).
4
+ *
5
+ * A namespace rather than two more flat methods on an already-large facade,
6
+ * following `mg.codes` and `mg.colours`: reading a thread and adding to it are
7
+ * one family, they share a `(batchId, sourceId)` referent, and neither makes
8
+ * sense without the other. The namespace holds no transport of its own — the
9
+ * client hands it the shared one, so auth, rate-limit accounting and error
10
+ * shaping stay in one place.
11
+ *
12
+ * TWO RULES A CONSUMER MUST NOT GET WRONG.
13
+ *
14
+ * **The author name is REPORTED, never verified.** Every comment carries
15
+ * `author.attribution: "reported"` — a name somebody typed — plus `recordedBy`,
16
+ * the authenticated principal that actually wrote the row. Render "recorded as
17
+ * from Ruth Fielding", never "Ruth Fielding said". The API takes a machine key,
18
+ * and the person whose argument a remark carries is usually not the person at
19
+ * the keyboard; presenting the one as the other is the failure this field
20
+ * exists to prevent.
21
+ *
22
+ * **A reply is not a root.** {@link SourceMaterialCommentsNamespace.post}
23
+ * returns a result discriminated on `kind`: switch on it rather than reaching
24
+ * for `replies`, which a `reply` does not have. Threads are one level deep and
25
+ * the database enforces it, so a reply can never grow replies of its own.
26
+ *
27
+ * ROUTING OUT OF THIS NAMESPACE. `mg.sourceMaterialBatch(batchId)` already
28
+ * inlines every file's thread, so opening a batch is ONE call — do not loop this
29
+ * namespace over a batch's files to rebuild what that read returns. Reach for
30
+ * `list` when you are watching a single file.
31
+ */
32
+ import { HttpTransport } from "./http-transport.js";
33
+ import { listSourceMaterialComments, postSourceMaterialComment, } from "./runs.js";
34
+ /** Bind the comment operations to the client's shared transport. */
35
+ export function createSourceMaterialCommentsNamespace(transport) {
36
+ return {
37
+ list: (batchId, sourceId, signal) => listSourceMaterialComments(transport, batchId, sourceId, signal),
38
+ post: (batchId, sourceId, input, signal) => postSourceMaterialComment(transport, batchId, sourceId, input, signal),
39
+ };
40
+ }
package/client.d.ts CHANGED
@@ -2,7 +2,11 @@ import type { MaterialGraphClientOptions } from "./client-config.js";
2
2
  import { type RateLimit } from "./errors.js";
3
3
  export * from "./client-config.js";
4
4
  import { type BrowseNamespace } from "./client-browse.js";
5
+ import { type ExplorationNamespace } from "./client-exploration.js";
5
6
  export type { BrowseNamespace } from "./client-browse.js";
7
+ export type { ExplorationNamespace } from "./client-exploration.js";
8
+ import { type SourceMaterialCommentsNamespace } from "./client-source-material-comments.js";
9
+ export type { SourceMaterialCommentsNamespace } from "./client-source-material-comments.js";
6
10
  import type { AssociatedOptions } from "./association-types.js";
7
11
  import type { BrandCatalogListPage, BrandsOptions } from "./brand-types.js";
8
12
  import type { ByColourParams, ByColourResponse } from "./by-colour-types.js";
@@ -20,6 +24,7 @@ import { resolvePhrasesRequest as resolvePhrases } from "./resolve-client.js";
20
24
  import type { CapabilityManifestResponse, CreatePublicRunRequest, CreatePublicRunResponse, DrainRunEventsOptions, ListRunsOptions, PublicRunListResponse, PublicRunResponse, ResumeRunOptions, RunEvent, SourceMaterialBatchDetail, SourceMaterialBatchReceipt, StreamRunEventsOptions, UploadSourceMaterialInput } from "./run-types.js";
21
25
  import type { FinishScheduleResponse, GetRunScheduleOptions } from "./schedule-types.js";
22
26
  import type { SchemaResource, SchemaResourceResponse } from "./schema-resource-types.js";
27
+ import type { SlotPoolsRequest, SlotPoolsResponse } from "./slot-pool-types.js";
23
28
  import type { ApplicableSpecsResponse, BrandOptions, BrandProfileResponse, BySpecOptions, BySpecResponse, ColourFacetsResponse, CoordinateInput, CoordinateResponse, DemandBatchInput, DemandBatchResponse, DemandParams, DemandResponse, ExtractPaletteInput, ExtractPaletteResponse, GlossaryTermResponse, MaterialResponse, MaterialsBatchDetailResponse, MaterialsBatchGetOptions, MaterialsBatchOptions, MaterialsBatchTileResponse, PairingsOptions, PairingsResponse, ResolveAttributeOptions, ResolveAttributeResponse, ResolveAttributeValueOptions, ResolveAttributeValueResponse, ResolveColourResponse, SearchGetParams, SearchInput, SearchResponse, SiblingsResponse, SpecFacetsDistributionResponse, SpecFacetsListResponse, SuggestColourResponse, SuggestOptions, SuggestResponse, TaxonomyResponse, VocabularySearchOptions, WhoamiResponse } from "./types.js";
24
29
  /**
25
30
  * The MaterialGraph client — every lens on the graph, on one object.
@@ -61,14 +66,21 @@ export declare class MaterialGraphClient {
61
66
  #private;
62
67
  readonly baseUrl: string;
63
68
  /**
64
- * THE QUESTION: "show me everything, ordered lightest / most vivid / most
65
- * muted." Pure colour scalars — no query, no relevance. `browse({ sort })` is
66
- * one page; `browse.pages({ sort })` walks them all, and only the walk can
67
- * support a corpus claim.
69
+ * THE QUESTION: "show me everything in this category / this brand, in a plain
70
+ * order." No query, no relevance, no ranking. `browse(params)` is one page;
71
+ * `browse.pages(params)` walks them all.
72
+ *
73
+ * `sort` is optional (`name` A→Z by default, `recent` for newest first, plus
74
+ * the five colour ramps), `types`/`brands`/`corpusScope` narrow it, and
75
+ * `count: true` returns the total so a listing can say how many things it is
76
+ * listing without walking every page. `results.length` remains a PAGE.
68
77
  *
69
78
  * Instead use {@link MaterialGraphClient.byColour} for materials NEAR a colour,
70
79
  * {@link MaterialGraphClient.search} when there is anything to rank against,
71
80
  * and `colours.profiles()` for a category's palette rather than its members.
81
+ *
82
+ * @example A category listing that can state its own size
83
+ * const page = await client.browse({ types: "tile_surfaces", count: true });
72
84
  */
73
85
  readonly browse: BrowseNamespace;
74
86
  /**
@@ -94,6 +106,14 @@ export declare class MaterialGraphClient {
94
106
  * likeness, never a declared colour specification.
95
107
  */
96
108
  readonly colours: ColoursClient;
109
+ /** Discover honest graph questions, then traverse one explicit operator. */
110
+ readonly exploration: ExplorationNamespace;
111
+ /**
112
+ * THE QUESTION: "what did people say about this uploaded file, and record what
113
+ * they said." Names on these remarks are REPORTED, never verified — see
114
+ * {@link SourceMaterialCommentsNamespace}.
115
+ */
116
+ readonly sourceMaterialComments: SourceMaterialCommentsNamespace;
97
117
  constructor(options: MaterialGraphClientOptions);
98
118
  get lastRateLimit(): RateLimit | null;
99
119
  /**
@@ -364,17 +384,7 @@ export declare class MaterialGraphClient {
364
384
  * and `mg:admin` unlocks extra evidence fields.
365
385
  */
366
386
  whoami(signal?: AbortSignal): Promise<WhoamiResponse>;
367
- /**
368
- * THE QUESTION: "here is my whole scheme so far — what completes it?" The
369
- * multi-seed form of {@link MaterialGraphClient.pairings}: lift is summed across
370
- * seeds, so a companion suiting one pick ranks below one suiting several.
371
- * Instead use `pairings` for a single material and `colours.palette()` when the
372
- * scheme is COLOURS rather than materials. Check `unresolved` before presenting.
373
- *
374
- * Coordinate a palette of already-chosen materials (POST
375
- * `/api/v1/pairings/coordinate`). Returns demand-lift-ranked companions with
376
- * colour swatches and an annotate-only `colourFit` (never reorders in v1).
377
- */
387
+ /** What completes a scheme already part-chosen. See {@link coordinateCall}. */
378
388
  coordinate(input: CoordinateInput, signal?: AbortSignal): Promise<CoordinateResponse>;
379
389
  /**
380
390
  * THE QUESTION: "how much is this material actually specified, and what gets
@@ -553,14 +563,10 @@ export declare class MaterialGraphClient {
553
563
  * Poll {@link MaterialGraphClient.run} for the terminal state.
554
564
  */
555
565
  cancelRun(runId: RunId, signal?: AbortSignal): Promise<PublicRunResponse>;
556
- /**
557
- * Derive an "option B" scheme from a succeeded compose Run along one
558
- * constraint axis (POST `/api/v1/schemes/vary`). Deterministic and
559
- * synchronous — the response IS the hydrated scheme (no Run envelope), stamped
560
- * with axis + source provenance whose `notes` are the variation's receipts.
561
- * Requires the `mg:read` scope.
562
- */
566
+ /** Option B from a composed Run, along one axis. See {@link varySchemeCall}. */
563
567
  varyScheme(body: VarySchemesRequest, signal?: AbortSignal): Promise<VarySchemesResponse>;
568
+ /** Ranked candidate pools for up to 24 slots. See {@link slotPoolsCall}. */
569
+ slotPools(body: SlotPoolsRequest, signal?: AbortSignal): Promise<SlotPoolsResponse>;
564
570
  /**
565
571
  * Stream ordered Run events as an async iterator (GET
566
572
  * `/api/v1/runs/{runId}/events`, SSE). Iterates until the stream ends or the
package/client.js CHANGED
@@ -7,10 +7,14 @@ import { cancelRun, capabilities as capabilitiesRun, createRun, drainRunEvents,
7
7
  // Re-exported whole so `index.ts` and every existing importer keep their path.
8
8
  export * from "./client-config.js";
9
9
  import { browseQuery, createBrowseNamespace, } from "./client-browse.js";
10
+ import { createExplorationNamespace, } from "./client-exploration.js";
11
+ import { createSourceMaterialCommentsNamespace, } from "./client-source-material-comments.js";
10
12
  import { declaredFinishes, declaredProducts } from "./associations-client.js";
11
13
  import { brandColourDeck, brandProfile, listBrands } from "./brands-client.js";
12
14
  import { CodesClient } from "./codes-client.js";
13
15
  import { ColoursClient } from "./colours-client.js";
16
+ import { slotPools as slotPoolsCall, varyScheme as varySchemeCall, } from "./composition-client.js";
17
+ import { coordinate as coordinateCall } from "./coordinate-client.js";
14
18
  import { matchStream as matchStreamCall } from "./match-stream.js";
15
19
  import { appendProjectEvent as appendProjectEventCall, createProject as createProjectCall, getProject as getProjectCall, listProjects as listProjectsCall, } from "./projects.js";
16
20
  import { resolvePhrasesRequest as resolvePhrases, searchVocabularyRequest, } from "./resolve-client.js";
@@ -55,14 +59,21 @@ import { suggestQuery } from "./suggest-types.js";
55
59
  export class MaterialGraphClient {
56
60
  baseUrl;
57
61
  /**
58
- * THE QUESTION: "show me everything, ordered lightest / most vivid / most
59
- * muted." Pure colour scalars — no query, no relevance. `browse({ sort })` is
60
- * one page; `browse.pages({ sort })` walks them all, and only the walk can
61
- * support a corpus claim.
62
+ * THE QUESTION: "show me everything in this category / this brand, in a plain
63
+ * order." No query, no relevance, no ranking. `browse(params)` is one page;
64
+ * `browse.pages(params)` walks them all.
65
+ *
66
+ * `sort` is optional (`name` A→Z by default, `recent` for newest first, plus
67
+ * the five colour ramps), `types`/`brands`/`corpusScope` narrow it, and
68
+ * `count: true` returns the total so a listing can say how many things it is
69
+ * listing without walking every page. `results.length` remains a PAGE.
62
70
  *
63
71
  * Instead use {@link MaterialGraphClient.byColour} for materials NEAR a colour,
64
72
  * {@link MaterialGraphClient.search} when there is anything to rank against,
65
73
  * and `colours.profiles()` for a category's palette rather than its members.
74
+ *
75
+ * @example A category listing that can state its own size
76
+ * const page = await client.browse({ types: "tile_surfaces", count: true });
66
77
  */
67
78
  browse;
68
79
  /**
@@ -88,6 +99,14 @@ export class MaterialGraphClient {
88
99
  * likeness, never a declared colour specification.
89
100
  */
90
101
  colours;
102
+ /** Discover honest graph questions, then traverse one explicit operator. */
103
+ exploration;
104
+ /**
105
+ * THE QUESTION: "what did people say about this uploaded file, and record what
106
+ * they said." Names on these remarks are REPORTED, never verified — see
107
+ * {@link SourceMaterialCommentsNamespace}.
108
+ */
109
+ sourceMaterialComments;
91
110
  #transport;
92
111
  constructor(options) {
93
112
  if (!options.apiKey) {
@@ -108,6 +127,8 @@ export class MaterialGraphClient {
108
127
  get: (operationId, query, signal) => this.#get(operationId, query, signal),
109
128
  post: (operationId, body, signal) => this.#post(operationId, body, signal),
110
129
  });
130
+ this.exploration = createExplorationNamespace(this.#transport);
131
+ this.sourceMaterialComments = createSourceMaterialCommentsNamespace(this.#transport);
111
132
  }
112
133
  get lastRateLimit() {
113
134
  return this.#transport.lastRateLimit;
@@ -452,19 +473,9 @@ export class MaterialGraphClient {
452
473
  return this.#get("whoami", undefined, signal);
453
474
  }
454
475
  // ── Pairings / demand ────────────────────────────────────────────────────────
455
- /**
456
- * THE QUESTION: "here is my whole scheme so far — what completes it?" The
457
- * multi-seed form of {@link MaterialGraphClient.pairings}: lift is summed across
458
- * seeds, so a companion suiting one pick ranks below one suiting several.
459
- * Instead use `pairings` for a single material and `colours.palette()` when the
460
- * scheme is COLOURS rather than materials. Check `unresolved` before presenting.
461
- *
462
- * Coordinate a palette of already-chosen materials (POST
463
- * `/api/v1/pairings/coordinate`). Returns demand-lift-ranked companions with
464
- * colour swatches and an annotate-only `colourFit` (never reorders in v1).
465
- */
476
+ /** What completes a scheme already part-chosen. See {@link coordinateCall}. */
466
477
  coordinate(input, signal) {
467
- return this.#post("coordinate_materials", input, signal);
478
+ return coordinateCall(this.#transport, input, signal);
468
479
  }
469
480
  /**
470
481
  * THE QUESTION: "how much is this material actually specified, and what gets
@@ -690,15 +701,13 @@ export class MaterialGraphClient {
690
701
  cancelRun(runId, signal) {
691
702
  return cancelRun(this.#transport, runId, signal);
692
703
  }
693
- /**
694
- * Derive an "option B" scheme from a succeeded compose Run along one
695
- * constraint axis (POST `/api/v1/schemes/vary`). Deterministic and
696
- * synchronous — the response IS the hydrated scheme (no Run envelope), stamped
697
- * with axis + source provenance whose `notes` are the variation's receipts.
698
- * Requires the `mg:read` scope.
699
- */
704
+ /** Option B from a composed Run, along one axis. See {@link varySchemeCall}. */
700
705
  varyScheme(body, signal) {
701
- return this.#post("vary_schemes", body, signal);
706
+ return varySchemeCall(this.#transport, body, signal);
707
+ }
708
+ /** Ranked candidate pools for up to 24 slots. See {@link slotPoolsCall}. */
709
+ slotPools(body, signal) {
710
+ return slotPoolsCall(this.#transport, body, signal);
702
711
  }
703
712
  /**
704
713
  * Stream ordered Run events as an async iterator (GET