@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.
@@ -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
- export type SearchIndexPlanWant = {
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, requireNullableNumber, requireNullableString, requireNumber, requireOptionalBoolean, requireOptionalString, requireRecord, requireString, } from "./validation.js";
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
  };
@@ -1,12 +1,4 @@
1
- import type { CertificationProgrammeFilter, SpecExclusionFilter, SpecPresenceFilter, SpecTermFilter } from "./spec-types.js";
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
- * `matchStream()` frames.
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
- export const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
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
- export const requireJsonValue = (value, what, fail) => {
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
- };