@instruments/materialgraph 0.30.0 → 0.32.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/client.d.ts +14 -43
- package/client.js +14 -45
- package/errors.d.ts +1 -1
- package/errors.js +1 -0
- package/find-companions-types.d.ts +70 -0
- package/find-companions-types.js +12 -0
- package/http-transport.d.ts +1 -1
- package/index.d.ts +1 -3
- package/index.js +0 -1
- package/material-types.d.ts +2 -1
- package/ndjson.d.ts +1 -1
- package/ndjson.js +1 -1
- package/operations.d.ts +1 -1
- package/operations.js +1 -1
- package/package.json +1 -1
- package/response-schemas.generated.d.ts +1 -1
- package/response-schemas.generated.js +31 -39
- package/search-index-plan-types.d.ts +2 -1
- package/search-result-validation.d.ts +0 -2
- package/search-result-validation.js +1 -17
- package/specifications.d.ts +1 -9
- package/specifications.js +0 -12
- package/types.d.ts +2 -63
- package/validation.d.ts +0 -88
- package/validation.js +2 -154
- package/webmcp.generated.js +7 -8
- package/match-stream-types.d.ts +0 -306
- package/match-stream-types.js +0 -65
- package/match-stream.d.ts +0 -22
- package/match-stream.js +0 -308
- package/match-types.d.ts +0 -138
- package/match-types.js +0 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { ColourRegionRange } from "./colour-region-types.js";
|
|
9
9
|
import type { CorpusScope } from "./corpus-scope-types.js";
|
|
10
10
|
/** One attribute the plan wants: its terms, or any value when `presence`. */
|
|
11
|
-
|
|
11
|
+
type SearchIndexPlanWant = {
|
|
12
12
|
attributeId: string;
|
|
13
13
|
/** Share of the category's materials stating the attribute, 0 to 1. */
|
|
14
14
|
fill: number;
|
|
@@ -71,3 +71,4 @@ export type SearchPlanReceipt = {
|
|
|
71
71
|
source: "dictionary" | "jev";
|
|
72
72
|
version: string;
|
|
73
73
|
};
|
|
74
|
+
export {};
|
|
@@ -7,5 +7,3 @@ import type { ValidationFailure } from "./validation.js";
|
|
|
7
7
|
* normalisation the wire never had).
|
|
8
8
|
*/
|
|
9
9
|
export declare const checkSearchResultItem: (value: unknown, what: string, fail: ValidationFailure) => void;
|
|
10
|
-
/** Prove every entry of a `results[]` array is a {@link SearchResultItem}. */
|
|
11
|
-
export declare const checkSearchResultItems: (items: readonly unknown[], what: string, fail: ValidationFailure) => void;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* ENUMS ARE CLOSED, matching the canonical `z.enum(…)` members.
|
|
19
19
|
*/
|
|
20
|
-
import { requireArray, requireBoolean, requireEnum,
|
|
20
|
+
import { requireArray, requireBoolean, requireEnum, requireNullableString, requireNumber, requireOptionalBoolean, requireOptionalString, requireRecord, requireString, } from "./validation.js";
|
|
21
21
|
// ── Leaf checks ──────────────────────────────────────────────────────────────
|
|
22
22
|
/** `palette[]` — `{ hex, proportion, name: string.nullable().optional() }`. */
|
|
23
23
|
const checkPaletteSwatch = (value, what, fail) => {
|
|
@@ -36,11 +36,6 @@ const checkColourMeta = (value, what, fail) => {
|
|
|
36
36
|
requireOptionalString(record.dominantFamily, `${what}.dominantFamily`, fail);
|
|
37
37
|
requireOptionalString(record.dominantHex, `${what}.dominantHex`, fail);
|
|
38
38
|
};
|
|
39
|
-
/** `searchResultSignalsSchema` — the Match image cosine, nullable. */
|
|
40
|
-
const checkSignals = (value, what, fail) => {
|
|
41
|
-
const record = requireRecord(value, what, fail);
|
|
42
|
-
requireNullableNumber(record.image, `${what}.image`, fail);
|
|
43
|
-
};
|
|
44
39
|
// ── The item ─────────────────────────────────────────────────────────────────
|
|
45
40
|
/**
|
|
46
41
|
* Prove one wire value is a {@link SearchResultItem}, raising through `fail` at
|
|
@@ -83,17 +78,6 @@ export const checkSearchResultItem = (value, what, fail) => {
|
|
|
83
78
|
if (record.colourMeta !== undefined) {
|
|
84
79
|
checkColourMeta(record.colourMeta, `${what}.colourMeta`, fail);
|
|
85
80
|
}
|
|
86
|
-
if (record.signals !== undefined) {
|
|
87
|
-
checkSignals(record.signals, `${what}.signals`, fail);
|
|
88
|
-
}
|
|
89
81
|
requireOptionalBoolean(record.retired, `${what}.retired`, fail);
|
|
90
82
|
requireOptionalBoolean(record.sampleable, `${what}.sampleable`, fail);
|
|
91
|
-
requireOptionalBoolean(record.belowFloor, `${what}.belowFloor`, fail);
|
|
92
|
-
requireOptionalBoolean(record.admittedOnColour, `${what}.admittedOnColour`, fail);
|
|
93
|
-
};
|
|
94
|
-
/** Prove every entry of a `results[]` array is a {@link SearchResultItem}. */
|
|
95
|
-
export const checkSearchResultItems = (items, what, fail) => {
|
|
96
|
-
items.forEach((item, index) => {
|
|
97
|
-
checkSearchResultItem(item, `${what}[${index}]`, fail);
|
|
98
|
-
});
|
|
99
83
|
};
|
package/specifications.d.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import type { CertificationProgrammeFilter,
|
|
2
|
-
/**
|
|
3
|
-
* A hard criterion excluding one canonical value.
|
|
4
|
-
*
|
|
5
|
-
* By default retains only variants with a KNOWN value (unstated does not
|
|
6
|
-
* pass). Pass `whenUnstated: "pass"` to admit unstated variants too — only a
|
|
7
|
-
* variant that STATES the excluded term is dropped.
|
|
8
|
-
*/
|
|
9
|
-
export declare const excludeSpec: (attributeId: string, excludesTerm: string, whenUnstated?: "drop" | "pass") => SpecExclusionFilter;
|
|
1
|
+
import type { CertificationProgrammeFilter, SpecPresenceFilter, SpecTermFilter } from "./spec-types.js";
|
|
10
2
|
/**
|
|
11
3
|
* A term-equality criterion for `search({ filters })` — the attribute must
|
|
12
4
|
* carry this canonical value.
|
package/specifications.js
CHANGED
|
@@ -15,18 +15,6 @@
|
|
|
15
15
|
* object literal needs no encoder (MG-2222).
|
|
16
16
|
*/
|
|
17
17
|
import { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID } from "./spec-types.js";
|
|
18
|
-
/**
|
|
19
|
-
* A hard criterion excluding one canonical value.
|
|
20
|
-
*
|
|
21
|
-
* By default retains only variants with a KNOWN value (unstated does not
|
|
22
|
-
* pass). Pass `whenUnstated: "pass"` to admit unstated variants too — only a
|
|
23
|
-
* variant that STATES the excluded term is dropped.
|
|
24
|
-
*/
|
|
25
|
-
export const excludeSpec = (attributeId, excludesTerm, whenUnstated) => ({
|
|
26
|
-
attributeId,
|
|
27
|
-
excludesTerm,
|
|
28
|
-
...(whenUnstated === undefined ? {} : { whenUnstated }),
|
|
29
|
-
});
|
|
30
18
|
/**
|
|
31
19
|
* A term-equality criterion for `search({ filters })` — the attribute must
|
|
32
20
|
* carry this canonical value.
|
package/types.d.ts
CHANGED
|
@@ -78,13 +78,6 @@ export type MaterialColour = {
|
|
|
78
78
|
/** Where the served image came from. */
|
|
79
79
|
export type ImageSource = OpenEnum<"r2" | "origin">;
|
|
80
80
|
export type { MaterialAvailability, MaterialTile } from "./material-types.js";
|
|
81
|
-
/**
|
|
82
|
-
* Match only: the image-space cosine between the query image and this row's
|
|
83
|
-
* image, in [0,1]. Null when it was not measured.
|
|
84
|
-
*/
|
|
85
|
-
export type SearchResultSignals = {
|
|
86
|
-
image: number | null;
|
|
87
|
-
};
|
|
88
81
|
/**
|
|
89
82
|
* One material in a search / match result list.
|
|
90
83
|
*
|
|
@@ -110,7 +103,7 @@ export type SearchResultItem = {
|
|
|
110
103
|
* against Material Bank's own catalogue, so a result list can be joined to a
|
|
111
104
|
* cart, an order or an analytics event without a per-result lookup.
|
|
112
105
|
* Populated on EVERY lane: `search()` (all four arms), `match()`, and
|
|
113
|
-
*
|
|
106
|
+
* full search responses.
|
|
114
107
|
* ABSENT means no MB identity is known for the variant — never that the SKU
|
|
115
108
|
* cannot be ordered. Presence identifies the source; it does not establish sampleability.
|
|
116
109
|
*/
|
|
@@ -165,63 +158,9 @@ export type SearchResultItem = {
|
|
|
165
158
|
colourChip?: {
|
|
166
159
|
hex: string;
|
|
167
160
|
};
|
|
168
|
-
/**
|
|
169
|
-
* Selected mode's weighted primary score; see `maxScore`. On a Match page
|
|
170
|
-
* this is the retrieval score and does NOT order the page: the order is
|
|
171
|
-
* colour agreement, then `confidence` (MG-1900).
|
|
172
|
-
*/
|
|
161
|
+
/** Selected mode's weighted primary score; see maxScore. */
|
|
173
162
|
score: number;
|
|
174
|
-
/**
|
|
175
|
-
* Match only. How close this material is to the query, as an ABSOLUTE
|
|
176
|
-
* image-space similarity in [0,1], not adjusted for colour — the same unit as
|
|
177
|
-
* `qualityFloor` and `comparison.bestRejected`, so all three sit on one axis.
|
|
178
|
-
*
|
|
179
|
-
* Never normalised against the page, so the same material reads the same in
|
|
180
|
-
* two different searches. It is the TRUE value and MAY reach 1 — rendering it
|
|
181
|
-
* so that no card claims "100%" is yours to do (round toward zero, or show
|
|
182
|
-
* "99%+"); `Math.round` prints 100% from 0.995 up. Capping it server-side
|
|
183
|
-
* would put it on a different ceiling from `comparison.bestRejected`, which
|
|
184
|
-
* publishes the same unit uncapped, and the two are meant to be comparable.
|
|
185
|
-
* A Match page lists rows whose colour agrees (`colourDistance` under 0.1)
|
|
186
|
-
* first, then unmeasured ones, then the rest, and orders each set by this
|
|
187
|
-
* number. `score` is the retrieval score and lives on a different scale.
|
|
188
|
-
*/
|
|
189
|
-
confidence?: number;
|
|
190
|
-
/**
|
|
191
|
-
* `confidence` as a word — `strong` >= 0.85, `good` >= 0.72, `fair` >= 0.55
|
|
192
|
-
* (the default quality floor), `weak` below it and only reachable under
|
|
193
|
-
* `includeBelowFloor`. Present exactly when `confidence` is.
|
|
194
|
-
*/
|
|
195
|
-
confidenceBand?: "strong" | "good" | "fair" | "weak";
|
|
196
|
-
/**
|
|
197
|
-
* Match only. OKLab earth mover's distance between the query crop's palette
|
|
198
|
-
* and this material's, reading both palettes' proportions. Under 0.1 the
|
|
199
|
-
* colour agrees. `null` means the material carries no stored palette —
|
|
200
|
-
* UNKNOWN colour, never "no colour" and never "distant". Absent when the
|
|
201
|
-
* colour stage did not run.
|
|
202
|
-
*/
|
|
203
|
-
colourDistance?: number | null;
|
|
204
|
-
/** `colourDistance` as a word, or null when unmeasurable. */
|
|
205
|
-
colourBand?: string | null;
|
|
206
|
-
signals?: SearchResultSignals;
|
|
207
163
|
retired?: boolean;
|
|
208
|
-
/**
|
|
209
|
-
* Present (true) only on quality-floor rejects returned under `includeBelowFloor`
|
|
210
|
-
* (match only) — the item failed the match image-quality floor and would be
|
|
211
|
-
* dropped by default. Appended after the kept results.
|
|
212
|
-
*/
|
|
213
|
-
belowFloor?: boolean;
|
|
214
|
-
/**
|
|
215
|
-
* Present (true) only on Match rows the colour probe admitted (match only):
|
|
216
|
-
* this material is as close in colour as the catalogue gets, and its image
|
|
217
|
-
* similarity is BELOW the match quality floor by design. It is on the page
|
|
218
|
-
* because nothing image-retrieved is nearer in colour, not because it cleared
|
|
219
|
-
* the floor — present it as a colour answer or suppress it knowingly. Absent
|
|
220
|
-
* (never `false`) on rows that came through image retrieval. Distinct from
|
|
221
|
-
* {@link SearchResultItem.belowFloor}, which marks rejects returned only under
|
|
222
|
-
* `includeBelowFloor`.
|
|
223
|
-
*/
|
|
224
|
-
admittedOnColour?: boolean;
|
|
225
164
|
/**
|
|
226
165
|
* Resolved values for the attribute ids sent in `returnAttributes`, keyed by
|
|
227
166
|
* attribute id. ABSENT entirely when `returnAttributes` was not sent.
|
package/validation.d.ts
CHANGED
|
@@ -15,37 +15,19 @@
|
|
|
15
15
|
* this file knowing what a Match frame or a Run event is.
|
|
16
16
|
*/
|
|
17
17
|
import type { MaterialGraphError } from "./errors.js";
|
|
18
|
-
import type { JsonValue } from "./types.js";
|
|
19
18
|
/**
|
|
20
19
|
* Builds the one error a validation run raises. The calling module supplies it,
|
|
21
20
|
* so the message names that payload and quotes its source.
|
|
22
21
|
*/
|
|
23
22
|
export type ValidationFailure = (reason: string) => MaterialGraphError;
|
|
24
|
-
export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
|
|
25
23
|
/** A `z.object({…})`. */
|
|
26
24
|
export declare const requireRecord: (value: unknown, what: string, fail: ValidationFailure) => Record<string, unknown>;
|
|
27
25
|
/** A `z.string()`. */
|
|
28
26
|
export declare const requireString: (value: unknown, what: string, fail: ValidationFailure) => string;
|
|
29
|
-
/** A `z.string().min(1)`. */
|
|
30
|
-
export declare const requireNonEmptyString: (value: unknown, what: string, fail: ValidationFailure) => string;
|
|
31
27
|
/** A `z.string().optional()`: absent is fine, present-but-wrong is not. */
|
|
32
28
|
export declare const requireOptionalString: (value: unknown, what: string, fail: ValidationFailure) => void;
|
|
33
29
|
/** A `z.string().nullable()`. */
|
|
34
30
|
export declare const requireNullableString: (value: unknown, what: string, fail: ValidationFailure) => string | null;
|
|
35
|
-
/** A `z.string().min(1).nullable()`. */
|
|
36
|
-
export declare const requireNullableNonEmptyString: (value: unknown, what: string, fail: ValidationFailure) => string | null;
|
|
37
|
-
/**
|
|
38
|
-
* A `z.string().trim().min(1).max(max)`.
|
|
39
|
-
*
|
|
40
|
-
* The `.trim()` is a TRANSFORM, so the schema's parsed output is the trimmed
|
|
41
|
-
* string and its length bounds apply AFTER trimming. A mirror that checked the
|
|
42
|
-
* raw length would disagree with the schema twice over: it would accept a
|
|
43
|
-
* whitespace-only value the schema rejects, and reject a padded value the schema
|
|
44
|
-
* accepts. Returns the trimmed string, which is what the schema returns.
|
|
45
|
-
*/
|
|
46
|
-
export declare const requireTrimmedBoundedString: (value: unknown, what: string, max: number, fail: ValidationFailure) => string;
|
|
47
|
-
/** A `z.string().trim().min(1).max(max).nullable()`. */
|
|
48
|
-
export declare const requireNullableTrimmedBoundedString: (value: unknown, what: string, max: number, fail: ValidationFailure) => string | null;
|
|
49
31
|
/** A `z.boolean()`. */
|
|
50
32
|
export declare const requireBoolean: (value: unknown, what: string, fail: ValidationFailure) => boolean;
|
|
51
33
|
/** A `z.boolean().optional()`. */
|
|
@@ -55,77 +37,7 @@ export declare const requireOptionalBoolean: (value: unknown, what: string, fail
|
|
|
55
37
|
* out of JSON as `Infinity`, so finiteness is a reachable check.
|
|
56
38
|
*/
|
|
57
39
|
export declare const requireNumber: (value: unknown, what: string, fail: ValidationFailure) => number;
|
|
58
|
-
/** A `z.number().optional()`. */
|
|
59
|
-
export declare const requireOptionalNumber: (value: unknown, what: string, fail: ValidationFailure) => void;
|
|
60
|
-
/** A `z.number().nullable()`. */
|
|
61
|
-
export declare const requireNullableNumber: (value: unknown, what: string, fail: ValidationFailure) => number | null;
|
|
62
|
-
/**
|
|
63
|
-
* A `z.number().min(min).max(max)`. `NaN` and both infinities fail the
|
|
64
|
-
* comparisons, exactly as the bounded Zod schema rejects them.
|
|
65
|
-
*/
|
|
66
|
-
export declare const requireBoundedNumber: (value: unknown, what: string, fail: ValidationFailure, min: number, max: number) => number;
|
|
67
|
-
/** A `z.number().min(min).max(max).nullable()`. */
|
|
68
|
-
export declare const requireNullableBoundedNumber: (value: unknown, what: string, fail: ValidationFailure, min: number, max: number) => number | null;
|
|
69
|
-
/**
|
|
70
|
-
* A bare `z.number().int()` — integer, finite, and within the SAFE range, with
|
|
71
|
-
* NO lower bound.
|
|
72
|
-
*
|
|
73
|
-
* Measured against the pinned Zod (4.4.3), `.int()` is exactly
|
|
74
|
-
* `Number.isSafeInteger`: it accepts `0`, `-1`, and `±MAX_SAFE_INTEGER`, and
|
|
75
|
-
* rejects `1.5`, `±2**53`, `NaN`, and `±Infinity`.
|
|
76
|
-
*
|
|
77
|
-
* Two ways to get this wrong, both of which break parity in a different
|
|
78
|
-
* direction. Adding a `min: 0` REJECTS a negative the schema accepts — a client
|
|
79
|
-
* refusing a legitimate response. Using `Number.isInteger` ACCEPTS `2**53`,
|
|
80
|
-
* which the schema rejects — so `requireIntegerAtLeast` is not this helper with
|
|
81
|
-
* the bound dropped, and the safe-range test is why.
|
|
82
|
-
*/
|
|
83
|
-
export declare const requireSafeInteger: (value: unknown, what: string, fail: ValidationFailure) => number;
|
|
84
|
-
/** A `z.number().int().min(min)` — the integer constraint AND the lower bound. */
|
|
85
|
-
export declare const requireIntegerAtLeast: (value: unknown, what: string, fail: ValidationFailure, min: number) => number;
|
|
86
40
|
/** A `z.enum([…])` — CLOSED, exactly like the canonical member list. */
|
|
87
41
|
export declare const requireEnum: (value: unknown, allowed: readonly string[], what: string, fail: ValidationFailure) => string;
|
|
88
|
-
/** A `z.enum([…]).nullable()`. */
|
|
89
|
-
export declare const requireNullableEnum: (value: unknown, allowed: readonly string[], what: string, fail: ValidationFailure) => string | null;
|
|
90
42
|
/** A `z.array(…)`. */
|
|
91
43
|
export declare const requireArray: (value: unknown, what: string, fail: ValidationFailure) => unknown[];
|
|
92
|
-
/**
|
|
93
|
-
* A `z.array(…).default([])`: an OMITTED array is valid and materialises as
|
|
94
|
-
* `[]`; a present non-array is not.
|
|
95
|
-
*/
|
|
96
|
-
export declare const requireDefaultedArray: (value: unknown, what: string, fail: ValidationFailure) => unknown[];
|
|
97
|
-
/** A `z.array(z.string()).min(1)` — non-empty, and every element a string. */
|
|
98
|
-
export declare const requireNonEmptyStringArray: (value: unknown, what: string, fail: ValidationFailure) => string[];
|
|
99
|
-
/**
|
|
100
|
-
* A `z.json()` — a JSON value, recursively: `string | number | boolean | null`,
|
|
101
|
-
* an array of them, or an object of them, at ANY depth.
|
|
102
|
-
*
|
|
103
|
-
* WHY THIS EXISTS, given it can never fire over the wire. Every SDK response
|
|
104
|
-
* has already been through `JSON.parse`, so a `Date`, `bigint`, `undefined`, or
|
|
105
|
-
* function CANNOT be present by construction — this check is unreachable in
|
|
106
|
-
* practice. It is here for PARITY: the canonical `sourceImageAnalysisResultSchema`
|
|
107
|
-
* uses `z.json()` precisely because those four break differently on the way to
|
|
108
|
-
* `jsonb` (a `bigint` throws in `JSON.stringify`, an `undefined` is silently
|
|
109
|
-
* dropped, and a `Date` round-trips as an ISO *string* — a different type read
|
|
110
|
-
* back than written). The mirror has to reject what the schema rejects or the
|
|
111
|
-
* parity test is lying. Do not delete this as dead code.
|
|
112
|
-
*/
|
|
113
|
-
export declare const requireJsonValue: (value: unknown, what: string, fail: ValidationFailure) => JsonValue;
|
|
114
|
-
/**
|
|
115
|
-
* A `z.record(z.string(), z.json())` — a PLAIN JSON object of JSON values.
|
|
116
|
-
*
|
|
117
|
-
* Deliberately NOT `requireRecord` + walk the entries. That combination has a
|
|
118
|
-
* top-level hole: `Object.entries(new Date())` and `Object.entries(new Map())`
|
|
119
|
-
* are both `[]`, so there is nothing to recurse over and the value is accepted
|
|
120
|
-
* as `{}` — while `z.record(z.string(), z.json())` rejects both outright. A
|
|
121
|
-
* "non-array object" guard would close those two and still miss a class instance
|
|
122
|
-
* with real own properties, which canonical also rejects. Only a prototype test
|
|
123
|
-
* matches, and {@link requireJsonValue} already applies it at every depth — so
|
|
124
|
-
* this delegates there and then narrows to an object, rather than reimplementing
|
|
125
|
-
* the test and acquiring the same hole one level up.
|
|
126
|
-
*
|
|
127
|
-
* `requireRecord` is left alone: its other callers mirror `z.object({…})`, which
|
|
128
|
-
* genuinely does accept any record, and tightening a shared helper under them
|
|
129
|
-
* would be a change none of them asked for.
|
|
130
|
-
*/
|
|
131
|
-
export declare const requireJsonRecord: (value: unknown, what: string, fail: ValidationFailure) => Record<string, JsonValue>;
|
package/validation.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2
2
|
/** A `z.object({…})`. */
|
|
3
3
|
export const requireRecord = (value, what, fail) => {
|
|
4
4
|
if (!isRecord(value)) {
|
|
@@ -13,14 +13,6 @@ export const requireString = (value, what, fail) => {
|
|
|
13
13
|
}
|
|
14
14
|
return value;
|
|
15
15
|
};
|
|
16
|
-
/** A `z.string().min(1)`. */
|
|
17
|
-
export const requireNonEmptyString = (value, what, fail) => {
|
|
18
|
-
const text = requireString(value, what, fail);
|
|
19
|
-
if (text.length === 0) {
|
|
20
|
-
throw fail(`${what} is an empty string`);
|
|
21
|
-
}
|
|
22
|
-
return text;
|
|
23
|
-
};
|
|
24
16
|
/** A `z.string().optional()`: absent is fine, present-but-wrong is not. */
|
|
25
17
|
export const requireOptionalString = (value, what, fail) => {
|
|
26
18
|
if (value === undefined) {
|
|
@@ -35,39 +27,6 @@ export const requireNullableString = (value, what, fail) => {
|
|
|
35
27
|
}
|
|
36
28
|
return requireString(value, what, fail);
|
|
37
29
|
};
|
|
38
|
-
/** A `z.string().min(1).nullable()`. */
|
|
39
|
-
export const requireNullableNonEmptyString = (value, what, fail) => {
|
|
40
|
-
if (value === null) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
return requireNonEmptyString(value, what, fail);
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* A `z.string().trim().min(1).max(max)`.
|
|
47
|
-
*
|
|
48
|
-
* The `.trim()` is a TRANSFORM, so the schema's parsed output is the trimmed
|
|
49
|
-
* string and its length bounds apply AFTER trimming. A mirror that checked the
|
|
50
|
-
* raw length would disagree with the schema twice over: it would accept a
|
|
51
|
-
* whitespace-only value the schema rejects, and reject a padded value the schema
|
|
52
|
-
* accepts. Returns the trimmed string, which is what the schema returns.
|
|
53
|
-
*/
|
|
54
|
-
export const requireTrimmedBoundedString = (value, what, max, fail) => {
|
|
55
|
-
const text = requireString(value, what, fail).trim();
|
|
56
|
-
if (text.length === 0) {
|
|
57
|
-
throw fail(`${what} is empty once trimmed`);
|
|
58
|
-
}
|
|
59
|
-
if (text.length > max) {
|
|
60
|
-
throw fail(`${what} is longer than ${max} characters (${text.length})`);
|
|
61
|
-
}
|
|
62
|
-
return text;
|
|
63
|
-
};
|
|
64
|
-
/** A `z.string().trim().min(1).max(max).nullable()`. */
|
|
65
|
-
export const requireNullableTrimmedBoundedString = (value, what, max, fail) => {
|
|
66
|
-
if (value === null) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
return requireTrimmedBoundedString(value, what, max, fail);
|
|
70
|
-
};
|
|
71
30
|
/** A `z.boolean()`. */
|
|
72
31
|
export const requireBoolean = (value, what, fail) => {
|
|
73
32
|
if (typeof value !== "boolean") {
|
|
@@ -92,68 +51,6 @@ export const requireNumber = (value, what, fail) => {
|
|
|
92
51
|
}
|
|
93
52
|
return value;
|
|
94
53
|
};
|
|
95
|
-
/** A `z.number().optional()`. */
|
|
96
|
-
export const requireOptionalNumber = (value, what, fail) => {
|
|
97
|
-
if (value === undefined) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
requireNumber(value, what, fail);
|
|
101
|
-
};
|
|
102
|
-
/** A `z.number().nullable()`. */
|
|
103
|
-
export const requireNullableNumber = (value, what, fail) => {
|
|
104
|
-
if (value === null) {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
return requireNumber(value, what, fail);
|
|
108
|
-
};
|
|
109
|
-
/**
|
|
110
|
-
* A `z.number().min(min).max(max)`. `NaN` and both infinities fail the
|
|
111
|
-
* comparisons, exactly as the bounded Zod schema rejects them.
|
|
112
|
-
*/
|
|
113
|
-
export const requireBoundedNumber = (value, what, fail, min, max) => {
|
|
114
|
-
if (typeof value !== "number" || !(value >= min && value <= max)) {
|
|
115
|
-
throw fail(`${what} is not a number within [${min}, ${max}]`);
|
|
116
|
-
}
|
|
117
|
-
return value;
|
|
118
|
-
};
|
|
119
|
-
/** A `z.number().min(min).max(max).nullable()`. */
|
|
120
|
-
export const requireNullableBoundedNumber = (value, what, fail, min, max) => {
|
|
121
|
-
if (value === null) {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
return requireBoundedNumber(value, what, fail, min, max);
|
|
125
|
-
};
|
|
126
|
-
/**
|
|
127
|
-
* A bare `z.number().int()` — integer, finite, and within the SAFE range, with
|
|
128
|
-
* NO lower bound.
|
|
129
|
-
*
|
|
130
|
-
* Measured against the pinned Zod (4.4.3), `.int()` is exactly
|
|
131
|
-
* `Number.isSafeInteger`: it accepts `0`, `-1`, and `±MAX_SAFE_INTEGER`, and
|
|
132
|
-
* rejects `1.5`, `±2**53`, `NaN`, and `±Infinity`.
|
|
133
|
-
*
|
|
134
|
-
* Two ways to get this wrong, both of which break parity in a different
|
|
135
|
-
* direction. Adding a `min: 0` REJECTS a negative the schema accepts — a client
|
|
136
|
-
* refusing a legitimate response. Using `Number.isInteger` ACCEPTS `2**53`,
|
|
137
|
-
* which the schema rejects — so `requireIntegerAtLeast` is not this helper with
|
|
138
|
-
* the bound dropped, and the safe-range test is why.
|
|
139
|
-
*/
|
|
140
|
-
export const requireSafeInteger = (value, what, fail) => {
|
|
141
|
-
if (!Number.isSafeInteger(value)) {
|
|
142
|
-
throw fail(`${what} is not a safe integer`);
|
|
143
|
-
}
|
|
144
|
-
return requireNumber(value, what, fail);
|
|
145
|
-
};
|
|
146
|
-
/** A `z.number().int().min(min)` — the integer constraint AND the lower bound. */
|
|
147
|
-
export const requireIntegerAtLeast = (value, what, fail, min) => {
|
|
148
|
-
const numeric = requireNumber(value, what, fail);
|
|
149
|
-
if (!Number.isInteger(numeric)) {
|
|
150
|
-
throw fail(`${what} is not an integer`);
|
|
151
|
-
}
|
|
152
|
-
if (numeric < min) {
|
|
153
|
-
throw fail(`${what} is below the minimum of ${min}`);
|
|
154
|
-
}
|
|
155
|
-
return numeric;
|
|
156
|
-
};
|
|
157
54
|
/** A `z.enum([…])` — CLOSED, exactly like the canonical member list. */
|
|
158
55
|
export const requireEnum = (value, allowed, what, fail) => {
|
|
159
56
|
const text = requireString(value, what, fail);
|
|
@@ -162,13 +59,6 @@ export const requireEnum = (value, allowed, what, fail) => {
|
|
|
162
59
|
}
|
|
163
60
|
return text;
|
|
164
61
|
};
|
|
165
|
-
/** A `z.enum([…]).nullable()`. */
|
|
166
|
-
export const requireNullableEnum = (value, allowed, what, fail) => {
|
|
167
|
-
if (value === null) {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
return requireEnum(value, allowed, what, fail);
|
|
171
|
-
};
|
|
172
62
|
/** A `z.array(…)`. */
|
|
173
63
|
export const requireArray = (value, what, fail) => {
|
|
174
64
|
if (!Array.isArray(value)) {
|
|
@@ -176,24 +66,6 @@ export const requireArray = (value, what, fail) => {
|
|
|
176
66
|
}
|
|
177
67
|
return value;
|
|
178
68
|
};
|
|
179
|
-
/**
|
|
180
|
-
* A `z.array(…).default([])`: an OMITTED array is valid and materialises as
|
|
181
|
-
* `[]`; a present non-array is not.
|
|
182
|
-
*/
|
|
183
|
-
export const requireDefaultedArray = (value, what, fail) => {
|
|
184
|
-
if (value === undefined) {
|
|
185
|
-
return [];
|
|
186
|
-
}
|
|
187
|
-
return requireArray(value, what, fail);
|
|
188
|
-
};
|
|
189
|
-
/** A `z.array(z.string()).min(1)` — non-empty, and every element a string. */
|
|
190
|
-
export const requireNonEmptyStringArray = (value, what, fail) => {
|
|
191
|
-
const items = requireArray(value, what, fail);
|
|
192
|
-
if (items.length === 0) {
|
|
193
|
-
throw fail(`${what} is empty`);
|
|
194
|
-
}
|
|
195
|
-
return items.map((item, index) => requireString(item, `${what}[${index}]`, fail));
|
|
196
|
-
};
|
|
197
69
|
/**
|
|
198
70
|
* A `z.json()` — a JSON value, recursively: `string | number | boolean | null`,
|
|
199
71
|
* an array of them, or an object of them, at ANY depth.
|
|
@@ -208,7 +80,7 @@ export const requireNonEmptyStringArray = (value, what, fail) => {
|
|
|
208
80
|
* back than written). The mirror has to reject what the schema rejects or the
|
|
209
81
|
* parity test is lying. Do not delete this as dead code.
|
|
210
82
|
*/
|
|
211
|
-
|
|
83
|
+
const requireJsonValue = (value, what, fail) => {
|
|
212
84
|
if (value === null) {
|
|
213
85
|
return null;
|
|
214
86
|
}
|
|
@@ -243,27 +115,3 @@ export const requireJsonValue = (value, what, fail) => {
|
|
|
243
115
|
}
|
|
244
116
|
throw fail(`${what} is not a JSON value`);
|
|
245
117
|
};
|
|
246
|
-
/**
|
|
247
|
-
* A `z.record(z.string(), z.json())` — a PLAIN JSON object of JSON values.
|
|
248
|
-
*
|
|
249
|
-
* Deliberately NOT `requireRecord` + walk the entries. That combination has a
|
|
250
|
-
* top-level hole: `Object.entries(new Date())` and `Object.entries(new Map())`
|
|
251
|
-
* are both `[]`, so there is nothing to recurse over and the value is accepted
|
|
252
|
-
* as `{}` — while `z.record(z.string(), z.json())` rejects both outright. A
|
|
253
|
-
* "non-array object" guard would close those two and still miss a class instance
|
|
254
|
-
* with real own properties, which canonical also rejects. Only a prototype test
|
|
255
|
-
* matches, and {@link requireJsonValue} already applies it at every depth — so
|
|
256
|
-
* this delegates there and then narrows to an object, rather than reimplementing
|
|
257
|
-
* the test and acquiring the same hole one level up.
|
|
258
|
-
*
|
|
259
|
-
* `requireRecord` is left alone: its other callers mirror `z.object({…})`, which
|
|
260
|
-
* genuinely does accept any record, and tightening a shared helper under them
|
|
261
|
-
* would be a change none of them asked for.
|
|
262
|
-
*/
|
|
263
|
-
export const requireJsonRecord = (value, what, fail) => {
|
|
264
|
-
const json = requireJsonValue(value, what, fail);
|
|
265
|
-
if (json === null || typeof json !== "object" || Array.isArray(json)) {
|
|
266
|
-
throw fail(`${what} is not a JSON object`);
|
|
267
|
-
}
|
|
268
|
-
return json;
|
|
269
|
-
};
|