@instruments/materialgraph 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/by-spec-types.d.ts +73 -0
  2. package/by-spec-types.js +7 -0
  3. package/client-browse.d.ts +46 -0
  4. package/client-browse.js +42 -0
  5. package/client.d.ts +83 -28
  6. package/client.js +99 -47
  7. package/codes-client.d.ts +82 -0
  8. package/codes-client.js +73 -0
  9. package/codes-types.d.ts +154 -9
  10. package/colour-aggregate-types.d.ts +93 -0
  11. package/colour-aggregate-types.js +1 -0
  12. package/colours-client.d.ts +52 -0
  13. package/colours-client.js +39 -0
  14. package/composition-types.d.ts +228 -2
  15. package/http-transport.d.ts +19 -2
  16. package/http-transport.js +72 -2
  17. package/index.d.ts +7 -0
  18. package/index.js +3 -0
  19. package/match-stream-types.d.ts +150 -0
  20. package/match-stream-types.js +28 -0
  21. package/match-stream.d.ts +22 -0
  22. package/match-stream.js +280 -0
  23. package/material-asset-types.d.ts +1 -1
  24. package/material-block-types.d.ts +23 -2
  25. package/material-types.d.ts +92 -0
  26. package/material-types.js +1 -0
  27. package/ndjson.d.ts +46 -0
  28. package/ndjson.js +103 -0
  29. package/operations.d.ts +1 -1
  30. package/operations.js +7 -0
  31. package/package.json +1 -1
  32. package/run-types.d.ts +65 -1
  33. package/runs.d.ts +45 -3
  34. package/runs.js +82 -43
  35. package/schedule-types.d.ts +88 -0
  36. package/schedule-types.js +12 -0
  37. package/search-refinement-types.d.ts +13 -0
  38. package/search-result-validation.d.ts +27 -0
  39. package/search-result-validation.js +174 -0
  40. package/source-material-validation.d.ts +3 -0
  41. package/source-material-validation.js +109 -0
  42. package/spec-types.d.ts +4 -0
  43. package/spec-types.js +4 -0
  44. package/specifications.d.ts +9 -0
  45. package/specifications.js +9 -0
  46. package/suggest-types.d.ts +58 -0
  47. package/suggest-types.js +21 -0
  48. package/types.d.ts +60 -73
  49. package/validation.d.ts +119 -0
  50. package/validation.js +241 -0
package/validation.js ADDED
@@ -0,0 +1,241 @@
1
+ /**
2
+ * The SDK's shared wire-validation primitives — the vocabulary every hand-rolled
3
+ * mirror of a canonical Zod schema is built from.
4
+ *
5
+ * The SDK publishes with ZERO runtime dependencies, so it cannot call
6
+ * `schema.parse()` on anything. Where a wire payload must be proven rather than
7
+ * trusted, it is checked with these helpers and the result is pinned against the
8
+ * real schema in `contract-parity.test.ts`. Each helper mirrors one Zod
9
+ * construct, named for it, so a reviewer can read the mirror against the schema
10
+ * line by line.
11
+ *
12
+ * Every helper takes the same three shapes: the value, a `what` path used in the
13
+ * diagnostic (`results[3].verdict.cells[0].state`), and a {@link ValidationFailure}
14
+ * owned by the calling module — so one payload's failures all read alike without
15
+ * this file knowing what a Match frame or a Run event is.
16
+ */
17
+ import { MaterialGraphError } from "./errors.js";
18
+ export function isRecord(value) {
19
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20
+ }
21
+ /** A `z.object({…})`. */
22
+ export function requireRecord(value, what, fail) {
23
+ if (!isRecord(value))
24
+ throw fail(`${what} is not a JSON object`);
25
+ return value;
26
+ }
27
+ /** A `z.string()`. */
28
+ export function requireString(value, what, fail) {
29
+ if (typeof value !== "string")
30
+ throw fail(`${what} is not a string`);
31
+ return value;
32
+ }
33
+ /** A `z.string().min(1)`. */
34
+ export function requireNonEmptyString(value, what, fail) {
35
+ const text = requireString(value, what, fail);
36
+ if (text.length === 0)
37
+ throw fail(`${what} is an empty string`);
38
+ return text;
39
+ }
40
+ /** A `z.string().optional()`: absent is fine, present-but-wrong is not. */
41
+ export function requireOptionalString(value, what, fail) {
42
+ if (value === undefined)
43
+ return;
44
+ requireString(value, what, fail);
45
+ }
46
+ /** A `z.string().nullable()`. */
47
+ export function requireNullableString(value, what, fail) {
48
+ if (value === null)
49
+ return null;
50
+ return requireString(value, what, fail);
51
+ }
52
+ /** A `z.string().min(1).nullable()`. */
53
+ export function requireNullableNonEmptyString(value, what, fail) {
54
+ if (value === null)
55
+ return null;
56
+ return requireNonEmptyString(value, what, fail);
57
+ }
58
+ /** A `z.boolean()`. */
59
+ export function requireBoolean(value, what, fail) {
60
+ if (typeof value !== "boolean")
61
+ throw fail(`${what} is not a boolean`);
62
+ return value;
63
+ }
64
+ /** A `z.boolean().optional()`. */
65
+ export function requireOptionalBoolean(value, what, fail) {
66
+ if (value === undefined)
67
+ return;
68
+ requireBoolean(value, what, fail);
69
+ }
70
+ /**
71
+ * A `z.number()`. Zod 4 rejects `NaN` AND `±Infinity` for it, and `1e999` parses
72
+ * out of JSON as `Infinity`, so finiteness is a reachable check.
73
+ */
74
+ export function requireNumber(value, what, fail) {
75
+ if (typeof value !== "number" || !Number.isFinite(value)) {
76
+ throw fail(`${what} is not a finite number`);
77
+ }
78
+ return value;
79
+ }
80
+ /** A `z.number().optional()`. */
81
+ export function requireOptionalNumber(value, what, fail) {
82
+ if (value === undefined)
83
+ return;
84
+ requireNumber(value, what, fail);
85
+ }
86
+ /** A `z.number().nullable()`. */
87
+ export function requireNullableNumber(value, what, fail) {
88
+ if (value === null)
89
+ return null;
90
+ return requireNumber(value, what, fail);
91
+ }
92
+ /**
93
+ * A `z.number().min(min).max(max)`. `NaN` and both infinities fail the
94
+ * comparisons, exactly as the bounded Zod schema rejects them.
95
+ */
96
+ export function requireBoundedNumber(value, what, fail, min, max) {
97
+ if (typeof value !== "number" || !(value >= min && value <= max)) {
98
+ throw fail(`${what} is not a number within [${min}, ${max}]`);
99
+ }
100
+ return value;
101
+ }
102
+ /** A `z.number().min(min).max(max).nullable()`. */
103
+ export function requireNullableBoundedNumber(value, what, fail, min, max) {
104
+ if (value === null)
105
+ return null;
106
+ return requireBoundedNumber(value, what, fail, min, max);
107
+ }
108
+ /**
109
+ * A bare `z.number().int()` — integer, finite, and within the SAFE range, with
110
+ * NO lower bound.
111
+ *
112
+ * Measured against the pinned Zod (4.4.3), `.int()` is exactly
113
+ * `Number.isSafeInteger`: it accepts `0`, `-1`, and `±MAX_SAFE_INTEGER`, and
114
+ * rejects `1.5`, `±2**53`, `NaN`, and `±Infinity`.
115
+ *
116
+ * Two ways to get this wrong, both of which break parity in a different
117
+ * direction. Adding a `min: 0` REJECTS a negative the schema accepts — a client
118
+ * refusing a legitimate response. Using `Number.isInteger` ACCEPTS `2**53`,
119
+ * which the schema rejects — so `requireIntegerAtLeast` is not this helper with
120
+ * the bound dropped, and the safe-range test is why.
121
+ */
122
+ export function requireSafeInteger(value, what, fail) {
123
+ if (!Number.isSafeInteger(value))
124
+ throw fail(`${what} is not a safe integer`);
125
+ return requireNumber(value, what, fail);
126
+ }
127
+ /** A `z.number().int().min(min)` — the integer constraint AND the lower bound. */
128
+ export function requireIntegerAtLeast(value, what, fail, min) {
129
+ const numeric = requireNumber(value, what, fail);
130
+ if (!Number.isInteger(numeric))
131
+ throw fail(`${what} is not an integer`);
132
+ if (numeric < min)
133
+ throw fail(`${what} is below the minimum of ${min}`);
134
+ return numeric;
135
+ }
136
+ /** A `z.enum([…])` — CLOSED, exactly like the canonical member list. */
137
+ export function requireEnum(value, allowed, what, fail) {
138
+ const text = requireString(value, what, fail);
139
+ if (!allowed.includes(text)) {
140
+ throw fail(`${what} is not one of ${allowed.join(" | ")} (${text})`);
141
+ }
142
+ return text;
143
+ }
144
+ /** A `z.enum([…]).nullable()`. */
145
+ export function requireNullableEnum(value, allowed, what, fail) {
146
+ if (value === null)
147
+ return null;
148
+ return requireEnum(value, allowed, what, fail);
149
+ }
150
+ /** A `z.array(…)`. */
151
+ export function requireArray(value, what, fail) {
152
+ if (!Array.isArray(value))
153
+ throw fail(`${what} is not an array`);
154
+ return value;
155
+ }
156
+ /**
157
+ * A `z.array(…).default([])`: an OMITTED array is valid and materialises as
158
+ * `[]`; a present non-array is not.
159
+ */
160
+ export function requireDefaultedArray(value, what, fail) {
161
+ if (value === undefined)
162
+ return [];
163
+ return requireArray(value, what, fail);
164
+ }
165
+ /** A `z.array(z.string()).min(1)` — non-empty, and every element a string. */
166
+ export function requireNonEmptyStringArray(value, what, fail) {
167
+ const items = requireArray(value, what, fail);
168
+ if (items.length === 0)
169
+ throw fail(`${what} is empty`);
170
+ return items.map((item, index) => requireString(item, `${what}[${index}]`, fail));
171
+ }
172
+ /**
173
+ * A `z.json()` — a JSON value, recursively: `string | number | boolean | null`,
174
+ * an array of them, or an object of them, at ANY depth.
175
+ *
176
+ * WHY THIS EXISTS, given it can never fire over the wire. Every SDK response
177
+ * has already been through `JSON.parse`, so a `Date`, `bigint`, `undefined`, or
178
+ * function CANNOT be present by construction — this check is unreachable in
179
+ * practice. It is here for PARITY: the canonical `sourceImageAnalysisResultSchema`
180
+ * uses `z.json()` precisely because those four break differently on the way to
181
+ * `jsonb` (a `bigint` throws in `JSON.stringify`, an `undefined` is silently
182
+ * dropped, and a `Date` round-trips as an ISO *string* — a different type read
183
+ * back than written). The mirror has to reject what the schema rejects or the
184
+ * parity test is lying. Do not delete this as dead code.
185
+ */
186
+ export function requireJsonValue(value, what, fail) {
187
+ if (value === null)
188
+ return null;
189
+ if (typeof value === "string" || typeof value === "boolean")
190
+ return value;
191
+ if (typeof value === "number") {
192
+ // `z.json()`'s number leaf rejects NaN and ±Infinity, and `1e999` parses out
193
+ // of JSON as `Infinity` — so finiteness IS reachable, unlike the rest.
194
+ if (!Number.isFinite(value))
195
+ throw fail(`${what} is not a finite number`);
196
+ return value;
197
+ }
198
+ if (Array.isArray(value)) {
199
+ return value.map((item, index) => requireJsonValue(item, `${what}[${index}]`, fail));
200
+ }
201
+ if (isRecord(value)) {
202
+ // A PLAIN object only. `isRecord` alone is not enough: it answers true for a
203
+ // `Date`, a `Map`, and any class instance, none of which have own enumerable
204
+ // entries — so recursing over them would silently accept them as `{}` while
205
+ // `z.json()` rejects them outright. A `Date` reaching `jsonb` is exactly the
206
+ // failure this check exists to stop.
207
+ const prototype = Object.getPrototypeOf(value);
208
+ if (prototype !== Object.prototype && prototype !== null) {
209
+ throw fail(`${what} is not a JSON value`);
210
+ }
211
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
212
+ key,
213
+ requireJsonValue(item, `${what}.${key}`, fail),
214
+ ]));
215
+ }
216
+ throw fail(`${what} is not a JSON value`);
217
+ }
218
+ /**
219
+ * A `z.record(z.string(), z.json())` — a PLAIN JSON object of JSON values.
220
+ *
221
+ * Deliberately NOT `requireRecord` + walk the entries. That combination has a
222
+ * top-level hole: `Object.entries(new Date())` and `Object.entries(new Map())`
223
+ * are both `[]`, so there is nothing to recurse over and the value is accepted
224
+ * as `{}` — while `z.record(z.string(), z.json())` rejects both outright. A
225
+ * "non-array object" guard would close those two and still miss a class instance
226
+ * with real own properties, which canonical also rejects. Only a prototype test
227
+ * matches, and {@link requireJsonValue} already applies it at every depth — so
228
+ * this delegates there and then narrows to an object, rather than reimplementing
229
+ * the test and acquiring the same hole one level up.
230
+ *
231
+ * `requireRecord` is left alone: its other callers mirror `z.object({…})`, which
232
+ * genuinely does accept any record, and tightening a shared helper under them
233
+ * would be a change none of them asked for.
234
+ */
235
+ export function requireJsonRecord(value, what, fail) {
236
+ const json = requireJsonValue(value, what, fail);
237
+ if (json === null || typeof json !== "object" || Array.isArray(json)) {
238
+ throw fail(`${what} is not a JSON object`);
239
+ }
240
+ return json;
241
+ }