@instruments/materialgraph 3.0.2 → 3.2.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 +20 -20
- package/brand-types.d.ts +6 -6
- package/by-spec-types.d.ts +6 -6
- package/catalog-discovery-types.d.ts +4 -4
- package/categories-client.d.ts +3 -3
- package/category-types.d.ts +18 -18
- package/client-config.d.ts +2 -2
- package/client-response-schema.d.ts +2 -2
- package/client-response-schema.js +4 -4
- package/client-vocabulary.js +1 -1
- package/client.d.ts +1 -1
- package/client.js +14 -10
- package/color-profiles-types.d.ts +8 -8
- package/color-wire-types.d.ts +6 -6
- package/colors-client.d.ts +3 -3
- package/discovery-context-types.d.ts +8 -8
- package/errors.d.ts +12 -13
- package/errors.js +84 -74
- package/find-sampled-together-types.d.ts +6 -6
- package/find-variants-by-color-types.d.ts +30 -30
- package/find-variants-by-vector-types.d.ts +13 -13
- package/get-variant-types.d.ts +11 -7
- package/http-transport.d.ts +14 -10
- package/http-transport.js +14 -12
- package/image-reference-types.d.ts +2 -2
- package/index.d.ts +2 -2
- package/json.d.ts +18 -0
- package/json.js +36 -0
- package/list-enum-values-types.d.ts +8 -8
- package/list-sampled-together-pairs-types.d.ts +6 -6
- package/list-variant-facets-types.d.ts +8 -8
- package/list-variants-types.d.ts +59 -29
- package/material-asset-types.d.ts +37 -24
- package/material-block-types.d.ts +6 -6
- package/material-types.d.ts +6 -6
- package/nearest-paints-types.d.ts +8 -8
- package/operation-responses.d.ts +2 -2
- package/operations.d.ts +1 -1
- package/operations.js +1 -1
- package/package.json +1 -1
- package/parse-operation-response.d.ts +4 -3
- package/parse-operation-response.js +112 -135
- package/resolve-color-types.d.ts +6 -6
- package/resolve-variant-types.d.ts +10 -10
- package/response-schema-catalog.d.ts +8 -0
- package/response-schema-catalog.js +82 -0
- package/response-schemas.d.ts +32 -3
- package/response-schemas.generated.js +26 -25
- package/response-schemas.js +5 -77
- package/schema-resource-types.d.ts +12 -12
- package/search-catalog-subset-types.d.ts +2 -2
- package/search-variants-types.d.ts +12 -12
- package/source-material-types.d.ts +6 -6
- package/source-material.js +2 -32
- package/types.d.ts +38 -38
- package/webmcp.d.ts +10 -4
- package/webmcp.generated.js +5 -4
|
@@ -20,6 +20,13 @@ export type VariantAssetAvailability = OpenEnum<"available" | "unknown" | "unava
|
|
|
20
20
|
export type VariantImageRole = OpenEnum<"product" | "swatch" | "detail" | "scene" | "technical" | "color_chart" | "sample_collection" | "other" | "unknown">;
|
|
21
21
|
/** How an image must be framed, a different question from what it shows. */
|
|
22
22
|
export type VariantImagePresentation = OpenEnum<"edge_to_edge" | "packshot" | "cutout_margin" | "montage" | "unknown">;
|
|
23
|
+
/** What a photo shows of its material, so an app can place it at real size. */
|
|
24
|
+
export type VariantImageShows = OpenEnum<"sample" | "crop" | "laid_field" | "room" | "detail">;
|
|
25
|
+
/** The size of the surface in an image's frame, in millimeters. */
|
|
26
|
+
export interface VariantImageSurfaceMm {
|
|
27
|
+
width_mm: number;
|
|
28
|
+
height_mm: number;
|
|
29
|
+
}
|
|
23
30
|
/** What a document is for; a document can carry more than one purpose. */
|
|
24
31
|
export type VariantDocumentType = OpenEnum<"technical_data_sheet" | "product_sheet" | "technical_bulletin" | "sustainability_report" | "specification" | "installation_guide" | "care_maintenance_guide" | "operation_manual" | "warranty" | "parts_list" | "technical_drawing" | "test_report" | "certificate" | "declaration_of_performance" | "environmental_product_declaration" | "health_product_declaration" | "ingredient_disclosure" | "safety_data_sheet" | "brochure" | "catalog" | "color_finish_chart" | "other" | "unknown">;
|
|
25
32
|
/** Every asset kind the registry knows. */
|
|
@@ -29,7 +36,7 @@ export type VariantAssetKind = OpenEnum<"image" | "document" | "cad" | "bim" | "
|
|
|
29
36
|
* appear on their kind, and an absent value is unknown. `url` is absent when
|
|
30
37
|
* `availability` is `unavailable`; `appliesTo` appears only as `product`.
|
|
31
38
|
*/
|
|
32
|
-
export
|
|
39
|
+
export interface VariantAssetItem {
|
|
33
40
|
/** Only `product`: filed against the whole product. Absent: this variant's own. */
|
|
34
41
|
appliesTo?: "product";
|
|
35
42
|
assetId: string;
|
|
@@ -43,23 +50,29 @@ export type VariantAssetItem = {
|
|
|
43
50
|
/** Images only; absent when unknown. */
|
|
44
51
|
imageRole?: VariantImageRole;
|
|
45
52
|
kind: VariantAssetKind;
|
|
53
|
+
/** Millimeters per pixel of this file. Images only; absent when unknown. */
|
|
54
|
+
mmPerPx?: number;
|
|
46
55
|
/** Not on images; absent when unknown. */
|
|
47
56
|
name?: string;
|
|
48
57
|
/** Images only; absent when unknown. */
|
|
49
58
|
presentation?: VariantImagePresentation;
|
|
59
|
+
/** What the photo shows of the material. Images only; absent when unknown. */
|
|
60
|
+
shows?: VariantImageShows;
|
|
61
|
+
/** The surface in frame: width and height times `mmPerPx`. Images only; absent when unknown. */
|
|
62
|
+
surfaceMm?: VariantImageSurfaceMm;
|
|
50
63
|
url?: string;
|
|
51
64
|
/** Images only; absent when unknown. */
|
|
52
65
|
width?: number;
|
|
53
|
-
}
|
|
66
|
+
}
|
|
54
67
|
/** Whether the brand's document source was read for this variant. Absent when documents are filtered out. */
|
|
55
68
|
export type VariantDocumentCoverageState = OpenEnum<"available" | "none_declared" | "not_ingested" | "unavailable_source" | "dead_link" | "failed_ingest">;
|
|
56
|
-
export
|
|
69
|
+
export interface VariantAssetPageInfo {
|
|
57
70
|
nextCursor: string | null;
|
|
58
71
|
returnedCount: number;
|
|
59
72
|
totalMatches: number;
|
|
60
|
-
}
|
|
73
|
+
}
|
|
61
74
|
/** Request filters and paging for `listVariantAssets`. Each list is OR within itself, AND across lists. */
|
|
62
|
-
export
|
|
75
|
+
export interface ListVariantAssetsOptions {
|
|
63
76
|
kinds?: VariantAssetKind[];
|
|
64
77
|
imageRoles?: VariantImageRole[];
|
|
65
78
|
documentTypes?: VariantDocumentType[];
|
|
@@ -67,28 +80,28 @@ export type ListVariantAssetsOptions = {
|
|
|
67
80
|
limit?: number;
|
|
68
81
|
/** `pageInfo.nextCursor` from the previous page, with the same variantId and filters. */
|
|
69
82
|
cursor?: string;
|
|
70
|
-
}
|
|
83
|
+
}
|
|
71
84
|
/** One page. An unknown variant is a 404, never a page. */
|
|
72
|
-
export
|
|
85
|
+
export interface VariantAssetPage {
|
|
73
86
|
documentCoverage?: VariantDocumentCoverageState;
|
|
74
87
|
items: VariantAssetItem[];
|
|
75
88
|
pageInfo: VariantAssetPageInfo;
|
|
76
89
|
/** The variant's primary image. Absent when it has none. */
|
|
77
90
|
primaryAssetId?: string;
|
|
78
|
-
}
|
|
91
|
+
}
|
|
79
92
|
export type MaterialDocumentStatus = OpenEnum<"downloaded" | "linked_not_downloaded" | "unavailable_source" | "dead_link" | "failed_ingest">;
|
|
80
93
|
export type MaterialDocumentCoverageState = OpenEnum<"available" | "none_declared" | "not_ingested" | "unavailable_source" | "dead_link" | "failed_ingest">;
|
|
81
|
-
export
|
|
94
|
+
export interface MaterialDocumentScope {
|
|
82
95
|
kind: "product" | "variant";
|
|
83
96
|
id: string;
|
|
84
|
-
}
|
|
85
|
-
export
|
|
97
|
+
}
|
|
98
|
+
export interface MaterialDocumentSource {
|
|
86
99
|
/** Opaque, public identifier for one source collection; never a PIM/DAM path. */
|
|
87
100
|
id: string;
|
|
88
101
|
authority: string;
|
|
89
102
|
revision: string;
|
|
90
|
-
}
|
|
91
|
-
export
|
|
103
|
+
}
|
|
104
|
+
export interface MaterialDocumentProjectionItem {
|
|
92
105
|
id: string;
|
|
93
106
|
scope: MaterialDocumentScope & {
|
|
94
107
|
inherited: boolean;
|
|
@@ -113,34 +126,34 @@ export type MaterialDocumentProjectionItem = {
|
|
|
113
126
|
contentType: string | null;
|
|
114
127
|
fileExtension: string | null;
|
|
115
128
|
source: MaterialDocumentSource;
|
|
116
|
-
}
|
|
117
|
-
export
|
|
129
|
+
}
|
|
130
|
+
export interface MaterialDocumentCoverageSource {
|
|
118
131
|
scope: MaterialDocumentScope;
|
|
119
132
|
state: MaterialDocumentCoverageState;
|
|
120
133
|
exact: boolean;
|
|
121
134
|
declaredCount: number | null;
|
|
122
135
|
readableCount: number;
|
|
123
136
|
source: MaterialDocumentSource;
|
|
124
|
-
}
|
|
125
|
-
export
|
|
137
|
+
}
|
|
138
|
+
export interface MaterialDocumentCoverage {
|
|
126
139
|
productUnion: MaterialDocumentCoverageSummary;
|
|
127
140
|
requestedVariant: MaterialDocumentCoverageSummary;
|
|
128
141
|
sources: MaterialDocumentCoverageSource[];
|
|
129
|
-
}
|
|
130
|
-
export
|
|
142
|
+
}
|
|
143
|
+
export interface MaterialDocumentCoverageSummary {
|
|
131
144
|
state: MaterialDocumentCoverageState;
|
|
132
145
|
exact: boolean;
|
|
133
146
|
declaredCount: number | null;
|
|
134
147
|
readableCount: number;
|
|
135
|
-
}
|
|
136
|
-
export
|
|
148
|
+
}
|
|
149
|
+
export interface MaterialDocumentPageOptions {
|
|
137
150
|
cursor?: string;
|
|
138
151
|
limit?: number;
|
|
139
|
-
}
|
|
140
|
-
export
|
|
152
|
+
}
|
|
153
|
+
export interface MaterialDocumentPage {
|
|
141
154
|
variantId: VariantId;
|
|
142
155
|
documents: MaterialDocumentProjectionItem[];
|
|
143
156
|
coverage: MaterialDocumentCoverage;
|
|
144
157
|
totalDocuments: number;
|
|
145
158
|
nextCursor: string | null;
|
|
146
|
-
}
|
|
159
|
+
}
|
|
@@ -51,7 +51,7 @@ export type MaterialBlockFieldValue = string | number | boolean | unknown[] | Re
|
|
|
51
51
|
* `status: "complete"` is a real negative you may publish; the same `false`
|
|
52
52
|
* under `missing` is a placeholder for an unanswered question.
|
|
53
53
|
*/
|
|
54
|
-
export
|
|
54
|
+
export interface MaterialBlockProjectionField {
|
|
55
55
|
/** Canonical vocabulary, not an opaque id - the same token `listVariants` filters accept. */
|
|
56
56
|
attributeId: string;
|
|
57
57
|
label: string;
|
|
@@ -64,21 +64,21 @@ export type MaterialBlockProjectionField = {
|
|
|
64
64
|
valueSource?: string;
|
|
65
65
|
/** Claim confidence supplied by the graph; absence means no confidence was emitted. */
|
|
66
66
|
confidence?: number;
|
|
67
|
-
}
|
|
68
|
-
export
|
|
67
|
+
}
|
|
68
|
+
export interface MaterialBlockProjectionItem {
|
|
69
69
|
canonicalBlockId: string;
|
|
70
70
|
canonicalBlockName: string;
|
|
71
71
|
status: MaterialBlockStatus;
|
|
72
72
|
fields: MaterialBlockProjectionField[];
|
|
73
73
|
/** The work queue: attributes with `status: "missing"`. UNKNOWN, never "none". */
|
|
74
74
|
missingAttributeIds: string[];
|
|
75
|
-
}
|
|
75
|
+
}
|
|
76
76
|
/**
|
|
77
77
|
* Every canonical block for one material, each field carrying whether it is
|
|
78
78
|
* determined. The whole surface, not a page — there is no cursor here.
|
|
79
79
|
*/
|
|
80
|
-
export
|
|
80
|
+
export interface MaterialBlockProjection {
|
|
81
81
|
/** Opaque — echoed from the request. */
|
|
82
82
|
variantId: VariantId;
|
|
83
83
|
blocks: MaterialBlockProjectionItem[];
|
|
84
|
-
}
|
|
84
|
+
}
|
package/material-types.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type MaterialAvailability = OpenEnum<"available" | "discontinued" | "supe
|
|
|
34
34
|
* The grid-card projection of a material. Tiles carry `availability` but not a
|
|
35
35
|
* successor record.
|
|
36
36
|
*/
|
|
37
|
-
export
|
|
37
|
+
export interface MaterialTile {
|
|
38
38
|
/** Accepted sampleability fact. Absent means unknown, never false. */
|
|
39
39
|
sampleable?: boolean;
|
|
40
40
|
/** Opaque — round-trip verbatim into every other material read. */
|
|
@@ -57,9 +57,9 @@ export type MaterialTile = {
|
|
|
57
57
|
* the attribute is absent — never render a missing key as "unnamed".
|
|
58
58
|
*/
|
|
59
59
|
colorName?: string;
|
|
60
|
-
}
|
|
60
|
+
}
|
|
61
61
|
/** A retained product-type proposal. It is review evidence, never a category. */
|
|
62
|
-
|
|
62
|
+
interface ProposedProductType {
|
|
63
63
|
id: string;
|
|
64
64
|
displayName: string;
|
|
65
65
|
confidence: number | null;
|
|
@@ -67,10 +67,10 @@ type ProposedProductType = {
|
|
|
67
67
|
sourceUrl: string | null;
|
|
68
68
|
isPrimary: boolean;
|
|
69
69
|
scope: "variant" | "product";
|
|
70
|
-
}
|
|
70
|
+
}
|
|
71
71
|
/** Accepted display categories remain separate from this review-state receipt. */
|
|
72
|
-
export
|
|
72
|
+
export interface ProductTypeReceipt {
|
|
73
73
|
state: "accepted" | "proposed" | "unknown";
|
|
74
74
|
proposedProductTypes: ProposedProductType[];
|
|
75
|
-
}
|
|
75
|
+
}
|
|
76
76
|
export {};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { SearchCatalogSubset } from "./search-catalog-subset-types.js";
|
|
2
|
-
export
|
|
2
|
+
export interface NearestPaintsParams {
|
|
3
3
|
hexes: string[];
|
|
4
4
|
limit?: number;
|
|
5
5
|
searchCatalogSubset?: SearchCatalogSubset;
|
|
6
|
-
}
|
|
7
|
-
export
|
|
6
|
+
}
|
|
7
|
+
export interface NearestPaintCard {
|
|
8
8
|
variantId: string;
|
|
9
9
|
name: string;
|
|
10
10
|
brand: {
|
|
@@ -25,11 +25,11 @@ export type NearestPaintCard = {
|
|
|
25
25
|
lrv: number | null;
|
|
26
26
|
deltaE: number;
|
|
27
27
|
closeness: "very-near" | "very-close" | "close" | "related" | "unrelated";
|
|
28
|
-
}
|
|
29
|
-
export
|
|
28
|
+
}
|
|
29
|
+
export interface NearestPaintsHexResult {
|
|
30
30
|
hex: string;
|
|
31
31
|
paints: NearestPaintCard[];
|
|
32
|
-
}
|
|
33
|
-
export
|
|
32
|
+
}
|
|
33
|
+
export interface NearestPaintsOutput {
|
|
34
34
|
results: NearestPaintsHexResult[];
|
|
35
|
-
}
|
|
35
|
+
}
|
package/operation-responses.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ import type { SchemaResourceResponse } from "./schema-resource-types.js";
|
|
|
20
20
|
import type { SearchVariantsOutput } from "./search-variants-types.js";
|
|
21
21
|
import type { SourceMaterialBatchReceipt } from "./source-material-types.js";
|
|
22
22
|
import type { ExtractPaletteResponse, ListAttributesResult, PairingsResponse, ResolveColorResponse, WhoamiResponse } from "./types.js";
|
|
23
|
-
export
|
|
23
|
+
export interface OperationResponses {
|
|
24
24
|
get_discovery_reference: DiscoveryReference;
|
|
25
25
|
interpret_discovery_terms: DiscoveryTerms;
|
|
26
26
|
find_colorless_variants: ColorlessVariantsResult;
|
|
@@ -56,5 +56,5 @@ export type OperationResponses = {
|
|
|
56
56
|
search_variants: SearchVariantsOutput;
|
|
57
57
|
upload_source_material: SourceMaterialBatchReceipt;
|
|
58
58
|
whoami: WhoamiResponse;
|
|
59
|
-
}
|
|
59
|
+
}
|
|
60
60
|
export type JsonOperationId = keyof OperationResponses;
|
package/operations.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SdkOperation, SdkOperationId } from "./operations.generated.js";
|
|
2
2
|
export { MATERIALGRAPH_API_VERSION, SDK_OPERATIONS, } from "./operations.generated.js";
|
|
3
3
|
export type { SdkOperation, SdkOperationId } from "./operations.generated.js";
|
|
4
4
|
/** Look up a registered operation by its OpenAPI `operationId`. */
|
package/operations.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MaterialGraphError } from "./errors.js";
|
|
2
|
-
import { SDK_OPERATIONS
|
|
2
|
+
import { SDK_OPERATIONS } from "./operations.generated.js";
|
|
3
3
|
export { MATERIALGRAPH_API_VERSION, SDK_OPERATIONS, } from "./operations.generated.js";
|
|
4
4
|
/** Look up a registered operation by its OpenAPI `operationId`. */
|
|
5
5
|
export const findSdkOperation = (operationId) => SDK_OPERATIONS.find((candidate) => candidate.operationId === operationId);
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { JsonValue } from "./json-value-types.js";
|
|
2
|
+
import type { JsonObject } from "./json.js";
|
|
1
3
|
import type { JsonOperationId, OperationResponses } from "./operation-responses.js";
|
|
2
|
-
import type { JsonSchema } from "./response-schemas.js";
|
|
3
4
|
/** Evaluate the JSON Schema keywords used by the published response contracts. */
|
|
4
|
-
export declare const matchesResponseSchema: (value:
|
|
5
|
-
export declare const parseOperationResponse: <K extends JsonOperationId>(operationId: K, value:
|
|
5
|
+
export declare const matchesResponseSchema: (value: JsonValue | undefined, schema: JsonValue | undefined, root: JsonObject) => boolean;
|
|
6
|
+
export declare const parseOperationResponse: <K extends JsonOperationId>(operationId: K, value: JsonValue | undefined) => Promise<OperationResponses[K]>;
|
|
@@ -1,185 +1,162 @@
|
|
|
1
1
|
import { openClientResponseEnums } from "./client-response-schema.js";
|
|
2
2
|
import { MaterialGraphError } from "./errors.js";
|
|
3
|
-
|
|
3
|
+
import { isJsonNumber, isJsonObject, isJsonString, jsonKind } from "./json.js";
|
|
4
4
|
const sameJson = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
5
|
+
const SIMPLE_TYPES = new Set([
|
|
6
|
+
"array",
|
|
7
|
+
"boolean",
|
|
8
|
+
"null",
|
|
9
|
+
"object",
|
|
10
|
+
"string",
|
|
11
|
+
]);
|
|
5
12
|
const matchesType = (value, type) => {
|
|
6
13
|
if (Array.isArray(type)) {
|
|
7
14
|
return type.some((candidate) => matchesType(value, candidate));
|
|
8
15
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return Array.isArray(value);
|
|
18
|
-
}
|
|
19
|
-
case "object": {
|
|
20
|
-
return isRecord(value);
|
|
21
|
-
}
|
|
22
|
-
case "integer": {
|
|
23
|
-
return typeof value === "number" && Number.isInteger(value);
|
|
24
|
-
}
|
|
25
|
-
case "number": {
|
|
26
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
27
|
-
}
|
|
28
|
-
case "string": {
|
|
29
|
-
return typeof value === "string";
|
|
30
|
-
}
|
|
31
|
-
case "boolean": {
|
|
32
|
-
return typeof value === "boolean";
|
|
33
|
-
}
|
|
34
|
-
default: {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
16
|
+
if (type === undefined) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (type === "integer") {
|
|
20
|
+
return isJsonNumber(value) && Number.isInteger(value);
|
|
21
|
+
}
|
|
22
|
+
if (type === "number") {
|
|
23
|
+
return isJsonNumber(value) && Number.isFinite(value);
|
|
37
24
|
}
|
|
25
|
+
return (isJsonString(type) && SIMPLE_TYPES.has(type) && jsonKind(value) === type);
|
|
38
26
|
};
|
|
39
27
|
const matchesNumberKeywords = (value, schema) => {
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
28
|
+
if (!isJsonNumber(value)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = schema;
|
|
32
|
+
if (isJsonNumber(minimum) && value < minimum) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (isJsonNumber(maximum) && value > maximum) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (isJsonNumber(exclusiveMinimum) && value <= exclusiveMinimum) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return !(isJsonNumber(exclusiveMaximum) && value >= exclusiveMaximum);
|
|
42
|
+
};
|
|
43
|
+
const matchesStringKeywords = (value, schema) => {
|
|
44
|
+
if (!isJsonString(value)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const { maxLength, minLength, pattern } = schema;
|
|
48
|
+
let length = 0;
|
|
49
|
+
for (const _codePoint of value) {
|
|
50
|
+
length += 1;
|
|
51
|
+
}
|
|
52
|
+
if (isJsonNumber(minLength) && length < minLength) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (isJsonNumber(maxLength) && length > maxLength) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return !(isJsonString(pattern) && !new RegExp(pattern, "u").test(value));
|
|
59
|
+
};
|
|
60
|
+
const matchesValueKeywords = (value, schema) => {
|
|
61
|
+
if ("const" in schema && !sameJson(value, schema.const)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const { enum: allowed } = schema;
|
|
65
|
+
return !(Array.isArray(allowed) &&
|
|
66
|
+
!allowed.some((candidate) => sameJson(candidate, value)));
|
|
67
|
+
};
|
|
68
|
+
/** Evaluates the JSON Schema keywords the published response contracts use. */
|
|
69
|
+
class ResponseSchemaEvaluator {
|
|
70
|
+
#root;
|
|
71
|
+
constructor(root) {
|
|
72
|
+
this.#root = root;
|
|
73
|
+
}
|
|
74
|
+
matches(value, schema) {
|
|
75
|
+
if (schema === true || schema === false) {
|
|
76
|
+
return schema;
|
|
43
77
|
}
|
|
44
|
-
if (
|
|
78
|
+
if (!isJsonObject(schema)) {
|
|
45
79
|
return false;
|
|
46
80
|
}
|
|
47
|
-
if (
|
|
48
|
-
value <= schema.exclusiveMinimum) {
|
|
81
|
+
if (!this.#matchesReference(value, schema)) {
|
|
49
82
|
return false;
|
|
50
83
|
}
|
|
51
|
-
if (
|
|
52
|
-
value >= schema.exclusiveMaximum) {
|
|
84
|
+
if (!matchesType(value, schema.type)) {
|
|
53
85
|
return false;
|
|
54
86
|
}
|
|
55
|
-
|
|
56
|
-
return true;
|
|
57
|
-
};
|
|
58
|
-
const matchesStringKeywords = (value, schema) => {
|
|
59
|
-
if (typeof value === "string") {
|
|
60
|
-
let length = 0;
|
|
61
|
-
for (const _codePoint of value) {
|
|
62
|
-
length += 1;
|
|
63
|
-
}
|
|
64
|
-
if (typeof schema.minLength === "number" && length < schema.minLength) {
|
|
87
|
+
if (!matchesValueKeywords(value, schema)) {
|
|
65
88
|
return false;
|
|
66
89
|
}
|
|
67
|
-
if (
|
|
90
|
+
if (Array.isArray(schema.anyOf) &&
|
|
91
|
+
!schema.anyOf.some((candidate) => this.matches(value, candidate))) {
|
|
68
92
|
return false;
|
|
69
93
|
}
|
|
70
|
-
if (
|
|
71
|
-
!new RegExp(schema.pattern, "u").test(value)) {
|
|
94
|
+
if ("not" in schema && this.matches(value, schema.not)) {
|
|
72
95
|
return false;
|
|
73
96
|
}
|
|
97
|
+
return (matchesNumberKeywords(value, schema) &&
|
|
98
|
+
matchesStringKeywords(value, schema) &&
|
|
99
|
+
this.#matchesArrayKeywords(value, schema) &&
|
|
100
|
+
this.#matchesObjectKeywords(value, schema));
|
|
74
101
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
102
|
+
#matchesArrayKeywords(value, schema) {
|
|
103
|
+
if (!Array.isArray(value)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
const { items, maxItems, minItems, prefixItems } = schema;
|
|
107
|
+
if (isJsonNumber(minItems) && value.length < minItems) {
|
|
80
108
|
return false;
|
|
81
109
|
}
|
|
82
|
-
if (
|
|
110
|
+
if (isJsonNumber(maxItems) && value.length > maxItems) {
|
|
83
111
|
return false;
|
|
84
112
|
}
|
|
85
|
-
const prefix = Array.isArray(
|
|
113
|
+
const prefix = Array.isArray(prefixItems) ? prefixItems : [];
|
|
86
114
|
for (const [index, item] of value.entries()) {
|
|
87
|
-
const itemSchema = index < prefix.length ? prefix[index] :
|
|
88
|
-
if (itemSchema !== undefined &&
|
|
89
|
-
!matchesResponseSchema(item, itemSchema, root)) {
|
|
115
|
+
const itemSchema = index < prefix.length ? prefix[index] : items;
|
|
116
|
+
if (itemSchema !== undefined && !this.matches(item, itemSchema)) {
|
|
90
117
|
return false;
|
|
91
118
|
}
|
|
92
119
|
}
|
|
120
|
+
return true;
|
|
93
121
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
122
|
+
#matchesObjectKeywords(value, schema) {
|
|
123
|
+
if (!isJsonObject(value)) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
const { additionalProperties, propertyNames, required } = schema;
|
|
127
|
+
if (Array.isArray(required) &&
|
|
128
|
+
!required.every((key) => isJsonString(key) && Object.hasOwn(value, key))) {
|
|
100
129
|
return false;
|
|
101
130
|
}
|
|
102
|
-
const properties =
|
|
131
|
+
const properties = isJsonObject(schema.properties) ? schema.properties : {};
|
|
103
132
|
for (const [key, item] of Object.entries(value)) {
|
|
104
|
-
if (
|
|
105
|
-
!matchesResponseSchema(key, schema.propertyNames, root)) {
|
|
133
|
+
if (propertyNames !== undefined && !this.matches(key, propertyNames)) {
|
|
106
134
|
return false;
|
|
107
135
|
}
|
|
108
136
|
const property = Object.hasOwn(properties, key)
|
|
109
137
|
? properties[key]
|
|
110
|
-
:
|
|
111
|
-
if (property !== undefined &&
|
|
112
|
-
!matchesResponseSchema(item, property, root)) {
|
|
138
|
+
: additionalProperties;
|
|
139
|
+
if (property !== undefined && !this.matches(item, property)) {
|
|
113
140
|
return false;
|
|
114
141
|
}
|
|
115
142
|
}
|
|
143
|
+
return true;
|
|
116
144
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (!schema.$ref.startsWith("#/$defs/")) {
|
|
122
|
-
return false;
|
|
145
|
+
#matchesReference(value, schema) {
|
|
146
|
+
const reference = schema.$ref;
|
|
147
|
+
if (!isJsonString(reference)) {
|
|
148
|
+
return true;
|
|
123
149
|
}
|
|
124
|
-
|
|
125
|
-
.slice(8)
|
|
126
|
-
.replaceAll("~1", "/")
|
|
127
|
-
.replaceAll("~0", "~");
|
|
128
|
-
if (!isRecord(root.$defs) ||
|
|
129
|
-
!matchesResponseSchema(value, root.$defs[name], root)) {
|
|
150
|
+
if (!reference.startsWith("#/$defs/")) {
|
|
130
151
|
return false;
|
|
131
152
|
}
|
|
153
|
+
const name = reference.slice(8).replaceAll("~1", "/").replaceAll("~0", "~");
|
|
154
|
+
const definitions = this.#root.$defs;
|
|
155
|
+
return isJsonObject(definitions) && this.matches(value, definitions[name]);
|
|
132
156
|
}
|
|
133
|
-
|
|
134
|
-
};
|
|
135
|
-
const matchesResponseValue = (value, schema) => {
|
|
136
|
-
if ("const" in schema && !sameJson(value, schema.const)) {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
if (Array.isArray(schema.enum) &&
|
|
140
|
-
!schema.enum.some((candidate) => sameJson(candidate, value))) {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
return true;
|
|
144
|
-
};
|
|
157
|
+
}
|
|
145
158
|
/** Evaluate the JSON Schema keywords used by the published response contracts. */
|
|
146
|
-
export const matchesResponseSchema = (value, schema, root) =>
|
|
147
|
-
if (typeof schema === "boolean") {
|
|
148
|
-
return schema;
|
|
149
|
-
}
|
|
150
|
-
if (!isRecord(schema)) {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
if (!matchesResponseReference(value, schema, root)) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
if (!matchesType(value, schema.type)) {
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
if (!matchesResponseValue(value, schema)) {
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
if (Array.isArray(schema.anyOf) &&
|
|
163
|
-
!schema.anyOf.some((candidate) => matchesResponseSchema(value, candidate, root))) {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
if ("not" in schema && matchesResponseSchema(value, schema.not, root)) {
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
if (!matchesNumberKeywords(value, schema)) {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
if (!matchesStringKeywords(value, schema)) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
if (!matchesArrayKeywords(value, schema, root)) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
if (!matchesObjectKeywords(value, schema, root)) {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
return true;
|
|
182
|
-
};
|
|
159
|
+
export const matchesResponseSchema = (value, schema, root) => new ResponseSchemaEvaluator(root).matches(value, schema);
|
|
183
160
|
const schemas = new Map();
|
|
184
161
|
/** The operation's published schema, rather than a caller's type argument, proves its response. */
|
|
185
162
|
const isOperationResponse = (operationId, value) => {
|
|
@@ -190,8 +167,8 @@ export const parseOperationResponse = async (operationId, value) => {
|
|
|
190
167
|
let schema = schemas.get(operationId);
|
|
191
168
|
if (!schema) {
|
|
192
169
|
// oxlint-disable-next-line howells/no-runtime-dynamic-imports -- Load the large response schema bundle only when a response needs validation.
|
|
193
|
-
const
|
|
194
|
-
schema =
|
|
170
|
+
const catalog = await import("./response-schema-catalog.js");
|
|
171
|
+
schema = catalog.buildResponseSchema(operationId);
|
|
195
172
|
openClientResponseEnums(schema);
|
|
196
173
|
schemas.set(operationId, schema);
|
|
197
174
|
}
|
package/resolve-color-types.d.ts
CHANGED
|
@@ -7,15 +7,15 @@ import type { VariantId } from "./identifiers.js";
|
|
|
7
7
|
import type { SearchCatalogSubset } from "./search-catalog-subset-types.js";
|
|
8
8
|
import type { OpenEnum } from "./types.js";
|
|
9
9
|
/** Options for `resolveColor`. */
|
|
10
|
-
export
|
|
10
|
+
export interface ResolveColorOptions {
|
|
11
11
|
/**
|
|
12
12
|
* Sets `inScope` on each catalog variant the lookup reports. It never
|
|
13
13
|
* narrows the lookup. Absent leaves every `inScope` null.
|
|
14
14
|
*/
|
|
15
15
|
searchCatalogSubset?: SearchCatalogSubset;
|
|
16
|
-
}
|
|
16
|
+
}
|
|
17
17
|
/** One catalog variant carrying the named paint. */
|
|
18
|
-
export
|
|
18
|
+
export interface ResolveColorCatalogVariant {
|
|
19
19
|
variantId: VariantId;
|
|
20
20
|
/** The brand exactly as stored, suspect or not. */
|
|
21
21
|
brand: {
|
|
@@ -25,7 +25,7 @@ export type ResolveColorCatalogVariant = {
|
|
|
25
25
|
};
|
|
26
26
|
/** Whether the variant is in the requested scope; null when none was asked for. */
|
|
27
27
|
inScope: boolean | null;
|
|
28
|
-
}
|
|
28
|
+
}
|
|
29
29
|
/**
|
|
30
30
|
* Whether the named paint is a variant in the catalog. Exact, global, and
|
|
31
31
|
* never narrowed by `searchCatalogSubset`, and run only for a `brand` library hit.
|
|
@@ -43,7 +43,7 @@ export type ResolveColorVariant = ({
|
|
|
43
43
|
status: "not-looked-up";
|
|
44
44
|
reason: OpenEnum<"reference-color" | "descriptive-color" | "no-paint-identity" | "generic-color" | "several-paints">;
|
|
45
45
|
};
|
|
46
|
-
export
|
|
46
|
+
export interface ResolveColorResponse {
|
|
47
47
|
/** Examples within the governed range. They do not replace its representative or bounds. */
|
|
48
48
|
rangeSamples?: string[];
|
|
49
49
|
/** Explicit brand choices for an ambiguous paint name. Never selected automatically. */
|
|
@@ -73,4 +73,4 @@ export type ResolveColorResponse = {
|
|
|
73
73
|
/** The catalog lookup for the name. Absent from servers that predate it. */
|
|
74
74
|
variant?: ResolveColorVariant;
|
|
75
75
|
} | null;
|
|
76
|
-
}
|
|
76
|
+
}
|