@instruments/materialgraph 0.33.0 → 0.36.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/association-types.d.ts +11 -11
- package/associations-client.d.ts +7 -7
- package/associations-client.js +5 -5
- package/brand-types.d.ts +37 -41
- package/brand-types.js +3 -5
- package/brands-client.d.ts +22 -9
- package/brands-client.js +20 -6
- package/by-spec-types.d.ts +1 -1
- package/catalog-discovery-types.d.ts +3 -3
- package/catalogue-client.d.ts +2 -50
- package/catalogue-client.js +2 -10
- package/catalogue-types.d.ts +1 -1
- package/categories-client.d.ts +3 -5
- package/categories-client.js +1 -1
- package/category-types.d.ts +17 -30
- package/citation-types.d.ts +2 -2
- package/client-vocabulary.d.ts +2 -2
- package/client-vocabulary.js +4 -1
- package/client.d.ts +62 -105
- package/client.js +68 -106
- package/color-profiles-types.d.ts +70 -0
- package/colors-client.d.ts +76 -0
- package/colors-client.js +52 -0
- package/find-sampled-together-types.d.ts +70 -0
- package/{find-companions-types.js → find-sampled-together-types.js} +4 -4
- package/{find-variants-by-colour-types.d.ts → find-variants-by-color-types.d.ts} +70 -64
- package/find-variants-by-vector-types.d.ts +4 -1
- package/get-variant-types.d.ts +14 -2
- package/http-transport.d.ts +1 -1
- package/http-transport.js +1 -2
- package/identifiers.d.ts +2 -2
- package/identifiers.js +1 -1
- package/index.d.ts +5 -14
- package/index.js +1 -3
- package/list-variant-facets-types.d.ts +4 -4
- package/list-variants-types.d.ts +71 -21
- package/material-asset-types.d.ts +3 -3
- package/material-block-types.d.ts +9 -9
- package/material-types.d.ts +10 -8
- package/operations.d.ts +2 -2
- package/operations.js +19 -25
- package/package.json +2 -2
- package/{resolve-colour-types.d.ts → resolve-color-types.d.ts} +18 -18
- package/response-schemas.generated.d.ts +2 -2
- package/response-schemas.generated.js +41 -71
- package/response-schemas.js +1 -1
- package/samplize-catalogue-types.d.ts +4 -4
- package/samplize-colorvec.js +6 -6
- package/{corpus-scope-types.d.ts → search-catalogue-subset-types.d.ts} +3 -3
- package/search-variants-types.d.ts +33 -2
- package/types.d.ts +32 -284
- package/webmcp.generated.js +4 -9
- package/attribution-types.d.ts +0 -51
- package/attribution-types.js +0 -13
- package/by-colour-types.d.ts +0 -320
- package/colour-region-types.d.ts +0 -18
- package/colours-client.d.ts +0 -70
- package/colours-client.js +0 -38
- package/find-companions-types.d.ts +0 -70
- package/material-query-plan-types.d.ts +0 -70
- package/material-query-plan-types.js +0 -1
- package/ndjson.d.ts +0 -46
- package/ndjson.js +0 -111
- package/palette-search-types.d.ts +0 -171
- package/palette-search-types.js +0 -1
- package/query-understanding-types.d.ts +0 -254
- package/query-understanding-types.js +0 -1
- package/resolve-colour-types.js +0 -1
- package/search-card-types.d.ts +0 -86
- package/search-card-types.js +0 -1
- package/search-index-plan-types.d.ts +0 -74
- package/search-index-plan-types.js +0 -1
- package/search-refinement-types.d.ts +0 -103
- package/search-refinement-types.js +0 -2
- package/search-request-types.d.ts +0 -170
- package/search-request-types.js +0 -1
- package/search-response-receipt-types.d.ts +0 -24
- package/search-response-receipt-types.js +0 -1
- package/search-response-types.d.ts +0 -11
- package/search-response-types.js +0 -1
- package/search-result-validation.d.ts +0 -9
- package/search-result-validation.js +0 -83
- package/spec-types.d.ts +0 -109
- package/spec-types.js +0 -54
- package/specifications.d.ts +0 -34
- package/specifications.js +0 -56
- package/validation.d.ts +0 -43
- package/validation.js +0 -117
- /package/{by-colour-types.js → color-profiles-types.js} +0 -0
- /package/{colour-region-types.js → find-variants-by-color-types.js} +0 -0
- /package/{corpus-scope-types.js → resolve-color-types.js} +0 -0
- /package/{find-variants-by-colour-types.js → search-catalogue-subset-types.js} +0 -0
package/colours-client.d.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import type { QueryParams } from "./http-transport.js";
|
|
2
|
-
import type { SdkOperationId } from "./operations.js";
|
|
3
|
-
/**
|
|
4
|
-
* The colour surface, namespaced as `mg.colours` — MULTI-COLOUR search
|
|
5
|
-
* (`palette`).
|
|
6
|
-
*
|
|
7
|
-
* A namespace rather than a flat method on an already-large client facade:
|
|
8
|
-
* "what goes with these colours" (`palette`). `mg.codes` set the precedent for
|
|
9
|
-
* a namespace when a capability is a family rather than a single verb.
|
|
10
|
-
*
|
|
11
|
-
* The namespace holds no transport of its own: the client hands it a bound
|
|
12
|
-
* `get`/`post`, so auth, rate-limit accounting and error shaping stay in one
|
|
13
|
-
* place.
|
|
14
|
-
*
|
|
15
|
-
* `palette`'s response is live and uncached.
|
|
16
|
-
*
|
|
17
|
-
* ROUTING OUT OF THIS NAMESPACE. Colour is a lens, and this is not the only
|
|
18
|
-
* door into it:
|
|
19
|
-
* - ONE colour → `mg.byColour({ hex })`. This namespace's `palette` is for N
|
|
20
|
-
* colours at stated proportions, which is a different ranking, not a
|
|
21
|
-
* convenience wrapper.
|
|
22
|
-
* - A colour NAME rather than a hex → `mg.resolveColour(name)` first.
|
|
23
|
-
* - Colour WORDS (families, moods, undertones) → `mg.colourFacets()`.
|
|
24
|
-
* - Walking the corpus by lightness/vividness → `mg.listVariants({ orderBy })`.
|
|
25
|
-
* - Colour as one signal among words and a seed → `mg.search(...)`.
|
|
26
|
-
*
|
|
27
|
-
* And the honesty rule the whole lens rests on: every colour here is DERIVED
|
|
28
|
-
* FROM PRODUCT IMAGERY, not declared by the manufacturer. Report visual
|
|
29
|
-
* likeness, never a stated colour specification.
|
|
30
|
-
*/
|
|
31
|
-
import type { SearchPaletteInput, SearchPaletteResponse } from "./palette-search-types.js";
|
|
32
|
-
/** The client's own request primitives, handed down so this file owns no transport. */
|
|
33
|
-
export type ColoursTransport = {
|
|
34
|
-
get<T>(operationId: SdkOperationId, query?: QueryParams, signal?: AbortSignal): Promise<T>;
|
|
35
|
-
post<T>(operationId: SdkOperationId, body: unknown, signal?: AbortSignal): Promise<T>;
|
|
36
|
-
};
|
|
37
|
-
export declare class ColoursClient {
|
|
38
|
-
#private;
|
|
39
|
-
constructor(transport: ColoursTransport);
|
|
40
|
-
/**
|
|
41
|
-
* THE QUESTION: "what goes with this WHOLE PALETTE?" — N colours at once, at
|
|
42
|
-
* stated proportions, not one colour repeated (POST `/api/v1/search/palettes`).
|
|
43
|
-
*
|
|
44
|
-
* Use `mg.byColour()` for a single colour.
|
|
45
|
-
*
|
|
46
|
-
* Each colour scores `sim × prop`: `sim` is OKLab closeness on the same
|
|
47
|
-
* mapping `byColour` ranks with, and `prop` rewards a candidate whose own
|
|
48
|
-
* swatch SHARE sits near the `proportion` you asked for. So the same colours
|
|
49
|
-
* at different proportions come back in a different order — a colour at 60%
|
|
50
|
-
* of the surface is a different query from one at 5%. `logic: "all"` (the
|
|
51
|
-
* default) applies a per-colour eligibility floor and returns the weighted
|
|
52
|
-
* geometric mean, so a candidate missing one colour is rejected rather than
|
|
53
|
-
* merely ranked low; `logic: "any"` is a noisy-OR that accepts partial
|
|
54
|
-
* answers but saturates once several colours score well, so prefer `all`
|
|
55
|
-
* when the ORDER is what matters.
|
|
56
|
-
*
|
|
57
|
-
* Every match carries the candidate's full `palette` plus a per-colour
|
|
58
|
-
* receipt naming the swatch that answered each queried colour.
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* const { matches } = await mg.colours.palette({
|
|
62
|
-
* colours: [
|
|
63
|
-
* { hex: "F5EFE0", proportion: 0.6 },
|
|
64
|
-
* { hex: "9CAF88", proportion: 0.2 },
|
|
65
|
-
* { hex: "3E3B36", proportion: 0.2 },
|
|
66
|
-
* ],
|
|
67
|
-
* });
|
|
68
|
-
*/
|
|
69
|
-
palette(input: SearchPaletteInput, signal?: AbortSignal): Promise<SearchPaletteResponse>;
|
|
70
|
-
}
|
package/colours-client.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export class ColoursClient {
|
|
2
|
-
#transport;
|
|
3
|
-
constructor(transport) {
|
|
4
|
-
this.#transport = transport;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* THE QUESTION: "what goes with this WHOLE PALETTE?" — N colours at once, at
|
|
8
|
-
* stated proportions, not one colour repeated (POST `/api/v1/search/palettes`).
|
|
9
|
-
*
|
|
10
|
-
* Use `mg.byColour()` for a single colour.
|
|
11
|
-
*
|
|
12
|
-
* Each colour scores `sim × prop`: `sim` is OKLab closeness on the same
|
|
13
|
-
* mapping `byColour` ranks with, and `prop` rewards a candidate whose own
|
|
14
|
-
* swatch SHARE sits near the `proportion` you asked for. So the same colours
|
|
15
|
-
* at different proportions come back in a different order — a colour at 60%
|
|
16
|
-
* of the surface is a different query from one at 5%. `logic: "all"` (the
|
|
17
|
-
* default) applies a per-colour eligibility floor and returns the weighted
|
|
18
|
-
* geometric mean, so a candidate missing one colour is rejected rather than
|
|
19
|
-
* merely ranked low; `logic: "any"` is a noisy-OR that accepts partial
|
|
20
|
-
* answers but saturates once several colours score well, so prefer `all`
|
|
21
|
-
* when the ORDER is what matters.
|
|
22
|
-
*
|
|
23
|
-
* Every match carries the candidate's full `palette` plus a per-colour
|
|
24
|
-
* receipt naming the swatch that answered each queried colour.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* const { matches } = await mg.colours.palette({
|
|
28
|
-
* colours: [
|
|
29
|
-
* { hex: "F5EFE0", proportion: 0.6 },
|
|
30
|
-
* { hex: "9CAF88", proportion: 0.2 },
|
|
31
|
-
* { hex: "3E3B36", proportion: 0.2 },
|
|
32
|
-
* ],
|
|
33
|
-
* });
|
|
34
|
-
*/
|
|
35
|
-
async palette(input, signal) {
|
|
36
|
-
return await this.#transport.post("search_by_palette", input, signal);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module find-companions-types
|
|
3
|
-
* @description Hand-maintained mirror of `find_companions`' (MG-2332) input and
|
|
4
|
-
* output wire shapes. The source of truth is
|
|
5
|
-
* `packages/products/src/cospec/schema.ts` (`findCompanionsInputSchema`,
|
|
6
|
-
* `findCompanionsResultSchema`). This SDK has no RUNTIME dependency on
|
|
7
|
-
* `@materialgraph/products` (it's a devDependency only, for the parity tests in
|
|
8
|
-
* `__tests__/`), so the shapes are copied here rather than imported.
|
|
9
|
-
* `contract-parity.test.ts` keeps this mirror honest against the server types,
|
|
10
|
-
* the project-type list included.
|
|
11
|
-
*/
|
|
12
|
-
/** The closed sector vocabulary co-specification evidence is tagged with. */
|
|
13
|
-
export type CompanionProjectType = "Workplace" | "Residential" | "Institution Education" | "Hospitality: Hotel" | "Mixed-Use & Multi-Unit Residential" | "Healthcare" | "Hospitality: Restaurant" | "Government" | "Senior Living" | "Retail" | "Other" | "Sports Entertainment" | "Wellness" | "Medical Dental" | "Aviation" | "Industrial" | "Tech" | "Transportation" | "Single Family" | "Condominium Cooperative" | "Marine" | "Power & Energy" | "University Design Program: Interior Design" | "Non-Billable" | "University Design Program: Interior Architecture" | "University Design Program: Architecture" | "Historic Preservation";
|
|
14
|
-
/** At least one of `variantIds` and `materials`, at most twelve together. */
|
|
15
|
-
export type FindCompanionsInput = {
|
|
16
|
-
/** Optional row fields: image adds imageUrl, colour adds swatches. */
|
|
17
|
-
include?: ("image" | "colour")[];
|
|
18
|
-
/** Ops and audit switch: leave sample cards in. Default false. */
|
|
19
|
-
includeSamplingArtifacts?: boolean;
|
|
20
|
-
/** Companions to return, default 20, at most 100. */
|
|
21
|
-
limit?: number;
|
|
22
|
-
/** Names to resolve when you hold no ids. Misses come back unresolved. */
|
|
23
|
-
materials?: string[];
|
|
24
|
-
/** Only companions sampled together in projects of this sector. */
|
|
25
|
-
projectType?: CompanionProjectType;
|
|
26
|
-
/** Variants to find companions for. */
|
|
27
|
-
variantIds?: string[];
|
|
28
|
-
};
|
|
29
|
-
export type FindCompanionsRow = {
|
|
30
|
-
brand: string;
|
|
31
|
-
/** Palette likeness to the seeds. Present with `include: ["colour"]`; never reorders. */
|
|
32
|
-
colourFit?: number | null;
|
|
33
|
-
/** Present with `include: ["image"]`. */
|
|
34
|
-
imageUrl?: string | null;
|
|
35
|
-
/** Mean lift over the seeds it pairs with; above 1 is above average. */
|
|
36
|
-
lift: number;
|
|
37
|
-
product: string;
|
|
38
|
-
/** Why it was suggested, in one line. */
|
|
39
|
-
reason: string;
|
|
40
|
-
/** `companion` is another brand; `brand_sibling` and `colourway` share the seed's. */
|
|
41
|
-
relation: "companion" | "brand_sibling" | "colourway";
|
|
42
|
-
/** How many seeds it was sampled with. */
|
|
43
|
-
seedMatchCount: number;
|
|
44
|
-
/** Present with `include: ["colour"]`. */
|
|
45
|
-
swatches?: {
|
|
46
|
-
hex: string;
|
|
47
|
-
proportion: number;
|
|
48
|
-
}[];
|
|
49
|
-
variantId: string;
|
|
50
|
-
variantName: string | null;
|
|
51
|
-
};
|
|
52
|
-
export type FindCompanionsOutput = {
|
|
53
|
-
/** Strongest first: seeds matched, then summed lift, one row per product. */
|
|
54
|
-
companions: FindCompanionsRow[];
|
|
55
|
-
/** The seeds as resolved, so a caller sees what a name became. */
|
|
56
|
-
seeds: {
|
|
57
|
-
brand: string;
|
|
58
|
-
/** Present with `include: ["image"]`. */
|
|
59
|
-
imageUrl?: string | null;
|
|
60
|
-
product: string;
|
|
61
|
-
/** The id or name this seed came from. */
|
|
62
|
-
resolvedFrom: string;
|
|
63
|
-
variantId: string;
|
|
64
|
-
variantName: string | null;
|
|
65
|
-
}[];
|
|
66
|
-
/** Seed ids outside the materials catalogue; present only when at least one was. */
|
|
67
|
-
excludedByCorpusScope?: string[];
|
|
68
|
-
/** References that matched no variant. */
|
|
69
|
-
unresolved: string[];
|
|
70
|
-
};
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/** Hand-mirrored wire types for `POST /api/v1/search/plan`. */
|
|
2
|
-
import type { OpenEnum } from "./types.js";
|
|
3
|
-
/** One wanted value: hard filters the page, soft only orders it. */
|
|
4
|
-
export type MaterialQueryWant = {
|
|
5
|
-
attributeId: string;
|
|
6
|
-
terms: string[];
|
|
7
|
-
phrase: string;
|
|
8
|
-
strength: OpenEnum<"hard" | "soft">;
|
|
9
|
-
fill: number;
|
|
10
|
-
};
|
|
11
|
-
export type MaterialQueryExclusion = {
|
|
12
|
-
attributeId: string;
|
|
13
|
-
term: string;
|
|
14
|
-
phrase: string;
|
|
15
|
-
};
|
|
16
|
-
/** The dictionary reading of one query, as the index search runs it. */
|
|
17
|
-
export type MaterialQueryReading = {
|
|
18
|
-
normalised: string;
|
|
19
|
-
category: {
|
|
20
|
-
productTypeIds: string[];
|
|
21
|
-
label: string;
|
|
22
|
-
phrase: string;
|
|
23
|
-
} | null;
|
|
24
|
-
brands: {
|
|
25
|
-
ids: string[];
|
|
26
|
-
names: string[];
|
|
27
|
-
phrase: string;
|
|
28
|
-
} | null;
|
|
29
|
-
wants: MaterialQueryWant[];
|
|
30
|
-
excludes: MaterialQueryExclusion[];
|
|
31
|
-
excludeBrands: string[];
|
|
32
|
-
colour: {
|
|
33
|
-
phrase: string;
|
|
34
|
-
familyId: string;
|
|
35
|
-
hex: string;
|
|
36
|
-
oklab: [number, number, number];
|
|
37
|
-
family12: string[];
|
|
38
|
-
} | null;
|
|
39
|
-
vendorColourNames: string[];
|
|
40
|
-
leftover: string[];
|
|
41
|
-
softAlone: boolean;
|
|
42
|
-
};
|
|
43
|
-
/** Present when a word no material carries was corrected: showing results for `to`. */
|
|
44
|
-
export type MaterialQueryCorrection = {
|
|
45
|
-
from: string;
|
|
46
|
-
to: string;
|
|
47
|
-
words: {
|
|
48
|
-
word: string;
|
|
49
|
-
correction: string;
|
|
50
|
-
}[];
|
|
51
|
-
};
|
|
52
|
-
/** One search over the index. */
|
|
53
|
-
export type MaterialSearchRequest = {
|
|
54
|
-
text: string;
|
|
55
|
-
view?: OpenEnum<"card" | "full">;
|
|
56
|
-
limit?: number;
|
|
57
|
-
cursor?: string;
|
|
58
|
-
};
|
|
59
|
-
export type MaterialQueryPlan = {
|
|
60
|
-
version: OpenEnum<"2">;
|
|
61
|
-
rawQuery: string;
|
|
62
|
-
normalisedQuery: string;
|
|
63
|
-
readingVersion: string;
|
|
64
|
-
source: OpenEnum<"dictionary" | "jev">;
|
|
65
|
-
cached: boolean;
|
|
66
|
-
plan: MaterialQueryReading;
|
|
67
|
-
correction: MaterialQueryCorrection | null;
|
|
68
|
-
/** The one search to run, in the phrasing that was read. */
|
|
69
|
-
requests: MaterialSearchRequest[];
|
|
70
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/ndjson.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A minimal, dependency-free NDJSON (newline-delimited JSON) line reader — the
|
|
3
|
-
* primitive for line-oriented JSON streams. Hand-rolled so the
|
|
4
|
-
* SDK stays zero-runtime-dep and works unchanged inside Node route handlers,
|
|
5
|
-
* where `fetch` yields a byte `ReadableStream`.
|
|
6
|
-
*
|
|
7
|
-
* Deliberately a SIBLING of `sse.ts`, not a reuse of it. NDJSON and SSE agree on
|
|
8
|
-
* nothing but "text arrives in chunks": SSE frames are `field: value` lines
|
|
9
|
-
* terminated by a BLANK line, with comment lines and multi-line `data:`
|
|
10
|
-
* accumulation, whereas an NDJSON frame is exactly one line of JSON. Feeding
|
|
11
|
-
* NDJSON through the SSE tokenizer would read `{"type":"step",…}` as a field
|
|
12
|
-
* named `{"type"` and silently discard every frame. The two also want OPPOSITE
|
|
13
|
-
* end-of-stream policies: SSE dispatches a trailing partial frame as valid,
|
|
14
|
-
* while a truncated NDJSON line must surface as an error (enforced by the
|
|
15
|
-
* caller, which parses whatever `flush` returns rather than dropping it).
|
|
16
|
-
*
|
|
17
|
-
* Line endings: the reader splits on `\n` only, per the NDJSON contract. A `\r`
|
|
18
|
-
* left by a CRLF-normalising proxy stays as trailing whitespace on the line,
|
|
19
|
-
* which `JSON.parse` ignores.
|
|
20
|
-
*/
|
|
21
|
-
/**
|
|
22
|
-
* A push-based NDJSON line assembler. Feed it decoded text with {@link push};
|
|
23
|
-
* it returns the lines that text COMPLETED, terminators removed. Any partial
|
|
24
|
-
* trailing line is held in an internal buffer until a later chunk terminates it,
|
|
25
|
-
* or until {@link flush} surrenders it at end of stream.
|
|
26
|
-
*/
|
|
27
|
-
export declare class NdjsonDecoder {
|
|
28
|
-
#private;
|
|
29
|
-
/** Absorb one decoded chunk and return the lines it completed, in order. */
|
|
30
|
-
push(text: string): string[];
|
|
31
|
-
/**
|
|
32
|
-
* Surrender the unterminated remainder at end of stream — a well-formed
|
|
33
|
-
* NDJSON stream's final line need not carry a trailing newline. The remainder
|
|
34
|
-
* is RETURNED, never dropped: a truncated frame is the caller's error to
|
|
35
|
-
* raise, not this decoder's fragment to discard.
|
|
36
|
-
*/
|
|
37
|
-
flush(): string[];
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Iterate the NDJSON lines of a byte stream. Only lines with non-whitespace
|
|
41
|
-
* content are yielded — a blank separator line carries no frame and must never
|
|
42
|
-
* reach `JSON.parse`, where it would become an empty object. Stops cleanly when
|
|
43
|
-
* the stream ends or `signal` aborts (mid-read abort rejections are swallowed
|
|
44
|
-
* once the signal is set).
|
|
45
|
-
*/
|
|
46
|
-
export declare function readNdjsonLines(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<string, void, unknown>;
|
package/ndjson.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A minimal, dependency-free NDJSON (newline-delimited JSON) line reader — the
|
|
3
|
-
* primitive for line-oriented JSON streams. Hand-rolled so the
|
|
4
|
-
* SDK stays zero-runtime-dep and works unchanged inside Node route handlers,
|
|
5
|
-
* where `fetch` yields a byte `ReadableStream`.
|
|
6
|
-
*
|
|
7
|
-
* Deliberately a SIBLING of `sse.ts`, not a reuse of it. NDJSON and SSE agree on
|
|
8
|
-
* nothing but "text arrives in chunks": SSE frames are `field: value` lines
|
|
9
|
-
* terminated by a BLANK line, with comment lines and multi-line `data:`
|
|
10
|
-
* accumulation, whereas an NDJSON frame is exactly one line of JSON. Feeding
|
|
11
|
-
* NDJSON through the SSE tokenizer would read `{"type":"step",…}` as a field
|
|
12
|
-
* named `{"type"` and silently discard every frame. The two also want OPPOSITE
|
|
13
|
-
* end-of-stream policies: SSE dispatches a trailing partial frame as valid,
|
|
14
|
-
* while a truncated NDJSON line must surface as an error (enforced by the
|
|
15
|
-
* caller, which parses whatever `flush` returns rather than dropping it).
|
|
16
|
-
*
|
|
17
|
-
* Line endings: the reader splits on `\n` only, per the NDJSON contract. A `\r`
|
|
18
|
-
* left by a CRLF-normalising proxy stays as trailing whitespace on the line,
|
|
19
|
-
* which `JSON.parse` ignores.
|
|
20
|
-
*/
|
|
21
|
-
/**
|
|
22
|
-
* A push-based NDJSON line assembler. Feed it decoded text with {@link push};
|
|
23
|
-
* it returns the lines that text COMPLETED, terminators removed. Any partial
|
|
24
|
-
* trailing line is held in an internal buffer until a later chunk terminates it,
|
|
25
|
-
* or until {@link flush} surrenders it at end of stream.
|
|
26
|
-
*/
|
|
27
|
-
export class NdjsonDecoder {
|
|
28
|
-
#buffer = "";
|
|
29
|
-
/** Absorb one decoded chunk and return the lines it completed, in order. */
|
|
30
|
-
push(text) {
|
|
31
|
-
this.#buffer += text;
|
|
32
|
-
const lines = [];
|
|
33
|
-
for (;;) {
|
|
34
|
-
const index = this.#buffer.indexOf("\n");
|
|
35
|
-
if (index === -1) {
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
lines.push(this.#buffer.slice(0, index));
|
|
39
|
-
this.#buffer = this.#buffer.slice(index + 1);
|
|
40
|
-
}
|
|
41
|
-
return lines;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Surrender the unterminated remainder at end of stream — a well-formed
|
|
45
|
-
* NDJSON stream's final line need not carry a trailing newline. The remainder
|
|
46
|
-
* is RETURNED, never dropped: a truncated frame is the caller's error to
|
|
47
|
-
* raise, not this decoder's fragment to discard.
|
|
48
|
-
*/
|
|
49
|
-
flush() {
|
|
50
|
-
if (this.#buffer.length === 0) {
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
const remainder = this.#buffer;
|
|
54
|
-
this.#buffer = "";
|
|
55
|
-
return [remainder];
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Iterate the NDJSON lines of a byte stream. Only lines with non-whitespace
|
|
60
|
-
* content are yielded — a blank separator line carries no frame and must never
|
|
61
|
-
* reach `JSON.parse`, where it would become an empty object. Stops cleanly when
|
|
62
|
-
* the stream ends or `signal` aborts (mid-read abort rejections are swallowed
|
|
63
|
-
* once the signal is set).
|
|
64
|
-
*/
|
|
65
|
-
export async function* readNdjsonLines(body, signal) {
|
|
66
|
-
const reader = body.getReader();
|
|
67
|
-
const decoder = new TextDecoder();
|
|
68
|
-
const ndjson = new NdjsonDecoder();
|
|
69
|
-
try {
|
|
70
|
-
for (;;) {
|
|
71
|
-
if (signal?.aborted) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
let result;
|
|
75
|
-
try {
|
|
76
|
-
result = await reader.read();
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
if (signal?.aborted) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
throw error;
|
|
83
|
-
}
|
|
84
|
-
if (result.done) {
|
|
85
|
-
// `decode()` with no argument flushes any dangling multi-byte sequence.
|
|
86
|
-
for (const line of ndjson.push(decoder.decode())) {
|
|
87
|
-
if (line.trim().length > 0) {
|
|
88
|
-
yield line;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
for (const line of ndjson.flush()) {
|
|
92
|
-
if (line.trim().length > 0) {
|
|
93
|
-
yield line;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
for (const line of ndjson.push(decoder.decode(result.value, { stream: true }))) {
|
|
99
|
-
if (signal?.aborted) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (line.trim().length > 0) {
|
|
103
|
-
yield line;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
finally {
|
|
109
|
-
reader.releaseLock();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import type { ColourCategoryGate } from "./by-colour-types.js";
|
|
2
|
-
import type { CorpusScope, CorpusScopeReceipt } from "./corpus-scope-types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Wire types for MULTI-COLOUR search — `POST /api/v1/search/palettes`.
|
|
5
|
-
*
|
|
6
|
-
* Kept out of `types.ts` for the repository's source-file size budget. Same
|
|
7
|
-
* tolerant-parsing contract as the rest of the mirror — response enums are
|
|
8
|
-
* widened so a new server value still type-checks.
|
|
9
|
-
*
|
|
10
|
-
* The MATCH SEMANTICS these types carry are the server's, stated on
|
|
11
|
-
* {@link SearchPaletteInput.logic} rather than left to the endpoint docs.
|
|
12
|
-
*/
|
|
13
|
-
import type { VariantId } from "./identifiers.js";
|
|
14
|
-
import type { OklabTriple, OpenEnum } from "./types.js";
|
|
15
|
-
/** How the per-colour scores are combined across a palette. */
|
|
16
|
-
export type PaletteLogic = OpenEnum<"all" | "any">;
|
|
17
|
-
/** One queried palette colour — exactly one of `hex` or `oklab`. */
|
|
18
|
-
export type PaletteQueryColour = {
|
|
19
|
-
hex?: string;
|
|
20
|
-
oklab?: OklabTriple;
|
|
21
|
-
/**
|
|
22
|
-
* Share of the surface this colour should occupy, in (0,1]. A colour stated
|
|
23
|
-
* below 5% is dropped; otherwise not applied, since a palette is ordered by
|
|
24
|
-
* its first colour alone.
|
|
25
|
-
*/
|
|
26
|
-
proportion?: number;
|
|
27
|
-
/** Relative importance. Not applied: a palette is ordered by its first colour. */
|
|
28
|
-
weight?: number;
|
|
29
|
-
};
|
|
30
|
-
export type SearchPaletteInput = {
|
|
31
|
-
/**
|
|
32
|
-
* HARD category filter: category-group or product-type names or keys.
|
|
33
|
-
* Absent, the page holds surface materials only, so lights, taps and
|
|
34
|
-
* furniture are left out.
|
|
35
|
-
*/
|
|
36
|
-
category?: string[];
|
|
37
|
-
corpusScope?: CorpusScope;
|
|
38
|
-
/**
|
|
39
|
-
* The palette to match, 1–8 colours. The page is ordered by the FIRST colour;
|
|
40
|
-
* the rest come back in `query.droppedColours`.
|
|
41
|
-
*/
|
|
42
|
-
colours: PaletteQueryColour[];
|
|
43
|
-
/**
|
|
44
|
-
* Kept so existing callers still parse: the one search orders the page by
|
|
45
|
-
* the first colour with no eligibility floor, so `all` and `any` return the
|
|
46
|
-
* same page.
|
|
47
|
-
*/
|
|
48
|
-
logic?: PaletteLogic;
|
|
49
|
-
/** Results to return; defaults to 24, capped at 48. */
|
|
50
|
-
limit?: number;
|
|
51
|
-
};
|
|
52
|
-
/**
|
|
53
|
-
* One swatch used for scoring. Samplize paint uses its selected published hex;
|
|
54
|
-
* other materials use colorscope palettes extracted from imagery. `name` is null when no human
|
|
55
|
-
* name was resolved — unnamed, not colourless.
|
|
56
|
-
*/
|
|
57
|
-
export type CandidateSwatch = {
|
|
58
|
-
hex: string;
|
|
59
|
-
proportion: number;
|
|
60
|
-
name: string | null;
|
|
61
|
-
};
|
|
62
|
-
/** Why a queried colour (or gradient sample) scored what it scored. */
|
|
63
|
-
export type ColourMatchReceipt = {
|
|
64
|
-
queryHex: string;
|
|
65
|
-
/** `colourScore × proportionScore`, so `colourScore` on the one search. */
|
|
66
|
-
score: number;
|
|
67
|
-
/**
|
|
68
|
-
* Closeness in [0,1] of the matched swatch to the queried colour, from plain
|
|
69
|
-
* OKLab distance with lightness counted in full. Not a confidence.
|
|
70
|
-
*/
|
|
71
|
-
colourScore: number;
|
|
72
|
-
/** Proportion agreement; always 1, since no proportion is applied. */
|
|
73
|
-
proportionScore: number;
|
|
74
|
-
/** The swatch that produced the score, or null when nothing matched. */
|
|
75
|
-
matched: CandidateSwatch | null;
|
|
76
|
-
/**
|
|
77
|
-
* Whether `matched` has a hue compatible with the queried colour, at the
|
|
78
|
-
* response's `hueGate` thresholds. A flag, never a gate: an incompatible
|
|
79
|
-
* swatch still scores and ranks. Absent when there is no `matched` swatch.
|
|
80
|
-
*/
|
|
81
|
-
hueCompatible?: boolean;
|
|
82
|
-
};
|
|
83
|
-
/** Identity and evidence shared by both multi-colour match shapes. */
|
|
84
|
-
export type MultiColourMatchBase = {
|
|
85
|
-
/** Opaque — round-trip verbatim. */
|
|
86
|
-
variantId: VariantId;
|
|
87
|
-
brand: string;
|
|
88
|
-
product: string;
|
|
89
|
-
brandSlug: string | null;
|
|
90
|
-
productId: string | null;
|
|
91
|
-
color: string | null;
|
|
92
|
-
variantName: string | null;
|
|
93
|
-
imageUrl: string | null;
|
|
94
|
-
/**
|
|
95
|
-
* SHA-256 of the displayed image `imageUrl` points at, as a hex digest.
|
|
96
|
-
* ABSENT means no hash is on record, never that there is no image.
|
|
97
|
-
*/
|
|
98
|
-
imageSha256?: string;
|
|
99
|
-
/** MB has retired or hidden this SKU. */
|
|
100
|
-
retired: boolean;
|
|
101
|
-
/**
|
|
102
|
-
* The catalog GROUP — "Flooring", "Textile Materials", "Coatings" — or null
|
|
103
|
-
* when the material has no accepted primary product type.
|
|
104
|
-
*
|
|
105
|
-
* A palette query is cross-category BY INTENT: a designer building a scheme,
|
|
106
|
-
* not browsing one aisle. So this is the field that says whether a result
|
|
107
|
-
* answers the question — one real palette returned 30 of 40 results in a
|
|
108
|
-
* single category while every one of the fourteen groups held something
|
|
109
|
-
* closer on the same colours (MG-1245). Group by it, and keep null as its own
|
|
110
|
-
* bucket: null means UNKNOWN, not uncategorised, and it is not rare.
|
|
111
|
-
*/
|
|
112
|
-
category: string | null;
|
|
113
|
-
/** The candidate's full extracted palette — the evidence scored against. */
|
|
114
|
-
palette: CandidateSwatch[];
|
|
115
|
-
/**
|
|
116
|
-
* Closeness in [0,1] of the material's own colour to the searched colour,
|
|
117
|
-
* the page's order. Not a confidence or a percentage.
|
|
118
|
-
*/
|
|
119
|
-
score: number;
|
|
120
|
-
};
|
|
121
|
-
export type PaletteMatch = {
|
|
122
|
-
/** One receipt per queried colour, in query order. */
|
|
123
|
-
colours: ColourMatchReceipt[];
|
|
124
|
-
} & MultiColourMatchBase;
|
|
125
|
-
/**
|
|
126
|
-
* The thresholds behind every receipt's `hueCompatible`: the exact values
|
|
127
|
-
* passed to colorscope's `isHueCompatible`, fixed server-side with no request
|
|
128
|
-
* knob. A FLAG, never admission or ranking: every swatch still scores, and the
|
|
129
|
-
* page and its order are the same with or without it.
|
|
130
|
-
*/
|
|
131
|
-
export type HueGateReceipt = {
|
|
132
|
-
/** Below this query chroma the colour is achromatic and any hue is admitted. */
|
|
133
|
-
achromaticQueryChroma: number;
|
|
134
|
-
/** A swatch must carry at least this share of the query colour's chroma. */
|
|
135
|
-
minChromaRatio: number;
|
|
136
|
-
/** Floor on the cosine agreement between the two hue vectors. */
|
|
137
|
-
minHueCosine: number;
|
|
138
|
-
};
|
|
139
|
-
/**
|
|
140
|
-
* A PAGE, NOT A CORPUS. `count` is `matches.length` under `limit` (24 default,
|
|
141
|
-
* 48 max) — this surface states no corpus total and has no cursor, so "N
|
|
142
|
-
* materials match this palette" is not a claim you can make from it.
|
|
143
|
-
*/
|
|
144
|
-
export type SearchPaletteResponse = {
|
|
145
|
-
corpusScope?: CorpusScopeReceipt;
|
|
146
|
-
/** The query as interpreted — check it before trusting a surprising order. */
|
|
147
|
-
query: {
|
|
148
|
-
/** Colours that actually ran after minor stated shares were omitted. */
|
|
149
|
-
colours: {
|
|
150
|
-
hex: string;
|
|
151
|
-
proportion?: number;
|
|
152
|
-
weight?: number;
|
|
153
|
-
}[];
|
|
154
|
-
/** Colours not searched because their stated share was below 5%. */
|
|
155
|
-
droppedColours: {
|
|
156
|
-
hex: string;
|
|
157
|
-
proportion?: number;
|
|
158
|
-
weight?: number;
|
|
159
|
-
}[];
|
|
160
|
-
logic: PaletteLogic;
|
|
161
|
-
/** `no_effective_colours` means no material query ran. */
|
|
162
|
-
outcome: OpenEnum<"matches" | "no_effective_colours">;
|
|
163
|
-
};
|
|
164
|
-
/** The category gate the page ran under. */
|
|
165
|
-
categoryGate?: ColourCategoryGate;
|
|
166
|
-
/** The thresholds behind each receipt's `hueCompatible`. Absent only on responses stored before it existed. */
|
|
167
|
-
hueGate?: HueGateReceipt;
|
|
168
|
-
/** `matches.length`, capped by `limit`. Never a corpus total. */
|
|
169
|
-
count: number;
|
|
170
|
-
matches: PaletteMatch[];
|
|
171
|
-
};
|
package/palette-search-types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|