@fuzdev/fuz_app 0.148.0 → 0.149.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/dist/actions/CLAUDE.md +2 -2
- package/dist/actions/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +14 -1
- package/dist/actions/perform_action.d.ts +17 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +22 -2
- package/dist/auth/CLAUDE.md +3 -2
- package/dist/auth/account_action_specs.d.ts +12 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +16 -2
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +7 -4
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +29 -8
- package/dist/auth/audit_emitter.d.ts +14 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +5 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +5 -3
- package/dist/http/CLAUDE.md +14 -2
- package/dist/http/error_schemas.d.ts +8 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -0
- package/dist/http/jsonrpc_errors.d.ts +40 -15
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +90 -35
- package/dist/schema_meta.d.ts +60 -0
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +64 -0
- package/dist/testing/CLAUDE.md +162 -61
- package/dist/testing/adversarial_input_cases.d.ts +67 -7
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
- package/dist/testing/adversarial_input_cases.js +210 -165
- package/dist/testing/audit_drift_guard.d.ts +3 -2
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +5 -3
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
- package/dist/testing/cross_backend/capabilities.d.ts +27 -4
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +10 -6
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +117 -2
- package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
- package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
- package/dist/testing/cross_backend/cross_rpc.js +2 -2
- package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +3 -3
- package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
- package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
- package/dist/testing/cross_backend/setup.d.ts +2 -2
- package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_suites.js +291 -20
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
- package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +136 -41
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
- package/dist/testing/rpc_attack_surface.d.ts +10 -0
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +15 -3
- package/dist/testing/rpc_envelope_cases.d.ts +5 -2
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
- package/dist/testing/rpc_envelope_cases.js +4 -1
- package/dist/testing/rpc_round_trip.js +3 -3
- package/dist/testing/schema_generators.d.ts +99 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +244 -76
- package/dist/testing/stubs.d.ts +4 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- package/dist/zod.d.ts +9 -0
- package/dist/zod.d.ts.map +1 -1
- package/dist/zod.js +20 -0
- package/package.json +1 -1
|
@@ -12,34 +12,78 @@ import { z } from 'zod';
|
|
|
12
12
|
import { zod_unwrap_def, zod_get_base_type, zod_unwrap_to_object, zod_extract_fields } from '../zod.js';
|
|
13
13
|
import { is_null_schema } from '../http/schema_helpers.js';
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* The JSON Schema of a field's input side — where its format, pattern and
|
|
16
|
+
* bounds live — or `null` when JSON Schema can't render the field or any part
|
|
17
|
+
* of it (`z.date()`, `z.custom()`, a union with a `z.date()` branch).
|
|
18
|
+
*
|
|
19
|
+
* Rendered with `io: 'input'`, so a field carrying a `.transform()` or a pipe
|
|
20
|
+
* (`Username`) still yields the constraints its input is checked against;
|
|
21
|
+
* the default output side throws on a transform. A pipe yields its input
|
|
22
|
+
* side only, so constraints of the stage after it are not here. A
|
|
23
|
+
* `.nullable()` / `.nullish()` field renders as
|
|
24
|
+
* `anyOf: [{…}, {type: 'null'}]`; its one non-null branch is returned, so
|
|
25
|
+
* the bounds and pattern read the same as on the bare field. A union of two
|
|
26
|
+
* or more non-null branches returns the whole schema — its branches' bounds
|
|
27
|
+
* are no single field's bounds.
|
|
28
|
+
*
|
|
29
|
+
* A `.refine()` is never here: JSON Schema can't express one, so a bound a
|
|
30
|
+
* refinement enforces (a code-point length, a cross-field rule) generates no
|
|
31
|
+
* boundary case.
|
|
32
|
+
*
|
|
33
|
+
* @param field_schema - the field to read
|
|
34
|
+
* @returns the field's input-side JSON Schema, or `null` when it can't be rendered
|
|
17
35
|
*/
|
|
18
|
-
export const
|
|
36
|
+
export const to_field_json_schema = (field_schema) => {
|
|
37
|
+
let json;
|
|
19
38
|
try {
|
|
20
|
-
|
|
21
|
-
if (typeof json.format === 'string')
|
|
22
|
-
return json.format;
|
|
23
|
-
if (typeof json.pattern === 'string')
|
|
24
|
-
return 'pattern';
|
|
25
|
-
// `.nullish()` / `.nullable()` produce `anyOf: [{format, …}, {type: 'null'}]`
|
|
26
|
-
// — descend into the first non-null branch so format/pattern surface.
|
|
27
|
-
if (Array.isArray(json.anyOf)) {
|
|
28
|
-
for (const branch of json.anyOf) {
|
|
29
|
-
if (branch.type === 'null')
|
|
30
|
-
continue;
|
|
31
|
-
if (typeof branch.format === 'string')
|
|
32
|
-
return branch.format;
|
|
33
|
-
if (typeof branch.pattern === 'string')
|
|
34
|
-
return 'pattern';
|
|
35
|
-
}
|
|
36
|
-
}
|
|
39
|
+
json = z.toJSONSchema(field_schema, { io: 'input' });
|
|
37
40
|
}
|
|
38
41
|
catch {
|
|
39
|
-
//
|
|
42
|
+
return null; // a type JSON Schema can't represent
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(json.anyOf)) {
|
|
45
|
+
const branches = json.anyOf.filter((branch) => branch.type !== 'null');
|
|
46
|
+
if (branches.length === 1)
|
|
47
|
+
return branches[0];
|
|
48
|
+
}
|
|
49
|
+
return json;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The format constraint a field's input-side JSON Schema carries: its
|
|
53
|
+
* `format` (e.g. `'uuid'`, `'email'`), `'pattern'` for a regex, or `null`.
|
|
54
|
+
* A union (`anyOf` of several non-null branches) answers its first branch
|
|
55
|
+
* carrying one — a value violating every branch is what the caller needs,
|
|
56
|
+
* and the generators check the field refuses the candidate.
|
|
57
|
+
*
|
|
58
|
+
* @param json - a field's JSON Schema, as `to_field_json_schema` returns it
|
|
59
|
+
* @returns the format, `'pattern'`, or `null`
|
|
60
|
+
*/
|
|
61
|
+
export const detect_json_schema_format = (json) => {
|
|
62
|
+
if (typeof json.format === 'string')
|
|
63
|
+
return json.format;
|
|
64
|
+
if (typeof json.pattern === 'string')
|
|
65
|
+
return 'pattern';
|
|
66
|
+
if (Array.isArray(json.anyOf)) {
|
|
67
|
+
for (const branch of json.anyOf) {
|
|
68
|
+
if (branch.type === 'null')
|
|
69
|
+
continue;
|
|
70
|
+
if (typeof branch.format === 'string')
|
|
71
|
+
return branch.format;
|
|
72
|
+
if (typeof branch.pattern === 'string')
|
|
73
|
+
return 'pattern';
|
|
74
|
+
}
|
|
40
75
|
}
|
|
41
76
|
return null;
|
|
42
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* Detect format constraints on a field from its input-side JSON Schema
|
|
80
|
+
* (`to_field_json_schema`). Returns the format string (e.g. `'uuid'`,
|
|
81
|
+
* `'email'`), `'pattern'` for a regex, or `null`.
|
|
82
|
+
*/
|
|
83
|
+
export const detect_format = (field_schema) => {
|
|
84
|
+
const json = to_field_json_schema(field_schema);
|
|
85
|
+
return json && detect_json_schema_format(json);
|
|
86
|
+
};
|
|
43
87
|
/**
|
|
44
88
|
* Extract a candidate value from a JSON Schema `pattern` when the shape is a
|
|
45
89
|
* fixed-length hex character class, optionally behind a literal prefix —
|
|
@@ -110,8 +154,8 @@ const generate_valid_string = (field_schema) => {
|
|
|
110
154
|
let min_length = 0;
|
|
111
155
|
let max_length = Infinity;
|
|
112
156
|
let pattern = null;
|
|
113
|
-
|
|
114
|
-
|
|
157
|
+
const json = to_field_json_schema(field_schema);
|
|
158
|
+
if (json) {
|
|
115
159
|
if (typeof json.minLength === 'number')
|
|
116
160
|
min_length = json.minLength;
|
|
117
161
|
if (typeof json.maxLength === 'number')
|
|
@@ -119,9 +163,6 @@ const generate_valid_string = (field_schema) => {
|
|
|
119
163
|
if (typeof json.pattern === 'string')
|
|
120
164
|
pattern = json.pattern;
|
|
121
165
|
}
|
|
122
|
-
catch {
|
|
123
|
-
// no constraints
|
|
124
|
-
}
|
|
125
166
|
const target = Math.max(min_length, Math.min(10, max_length));
|
|
126
167
|
const base = 'x'.repeat(target) || 'test_value';
|
|
127
168
|
// Validate against the full schema (including refinements/brands).
|
|
@@ -155,6 +196,10 @@ const generate_valid_string = (field_schema) => {
|
|
|
155
196
|
const as_url = 'https://example.com/' + base;
|
|
156
197
|
if (field_schema.safeParse(as_url).success)
|
|
157
198
|
return as_url;
|
|
199
|
+
// Two-segment path refinement (an `owner/name` repo slug)
|
|
200
|
+
const segments = `${base}/${base}`;
|
|
201
|
+
if (field_schema.safeParse(segments).success)
|
|
202
|
+
return segments;
|
|
158
203
|
return base; // fall through — generate_valid_body will report the failure
|
|
159
204
|
};
|
|
160
205
|
/** Generate a valid-ish value for a field based on its base type. */
|
|
@@ -180,8 +225,8 @@ export const generate_valid_value = (field, field_schema) => {
|
|
|
180
225
|
if (field_schema.safeParse(candidate).success)
|
|
181
226
|
return candidate;
|
|
182
227
|
}
|
|
183
|
-
|
|
184
|
-
|
|
228
|
+
const json = to_field_json_schema(field_schema);
|
|
229
|
+
if (json) {
|
|
185
230
|
if (typeof json.minimum === 'number')
|
|
186
231
|
return Math.ceil(json.minimum);
|
|
187
232
|
if (typeof json.exclusiveMinimum === 'number')
|
|
@@ -191,23 +236,13 @@ export const generate_valid_value = (field, field_schema) => {
|
|
|
191
236
|
if (typeof json.exclusiveMaximum === 'number')
|
|
192
237
|
return Math.ceil(json.exclusiveMaximum) - 1;
|
|
193
238
|
}
|
|
194
|
-
catch {
|
|
195
|
-
// no bounds to read
|
|
196
|
-
}
|
|
197
239
|
return 1; // fall through — generate_valid_body will report the failure
|
|
198
240
|
}
|
|
199
241
|
case 'boolean':
|
|
200
242
|
return true;
|
|
201
243
|
case 'array': {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const json = z.toJSONSchema(field_schema);
|
|
205
|
-
if (typeof json.minItems === 'number')
|
|
206
|
-
min_items = json.minItems;
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
209
|
-
// no constraint
|
|
210
|
-
}
|
|
244
|
+
const json = to_field_json_schema(field_schema);
|
|
245
|
+
const min_items = typeof json?.minItems === 'number' ? json.minItems : 0;
|
|
211
246
|
if (min_items === 0)
|
|
212
247
|
return [];
|
|
213
248
|
const def = zod_unwrap_def(field_schema);
|
|
@@ -222,18 +257,13 @@ export const generate_valid_value = (field, field_schema) => {
|
|
|
222
257
|
return Array.from({ length: min_items }, () => item);
|
|
223
258
|
}
|
|
224
259
|
case 'object': {
|
|
225
|
-
//
|
|
260
|
+
// recurse with the same optional-field fallback as the top level, so a
|
|
261
|
+
// nested `.refine()` over optional fields (exactly-one-of) is satisfied;
|
|
262
|
+
// a nested object that still fails is returned as built, and the
|
|
263
|
+
// top-level parse reports it
|
|
226
264
|
const nested_schema = zod_unwrap_to_object(field_schema);
|
|
227
|
-
if (nested_schema)
|
|
228
|
-
|
|
229
|
-
const nested = {};
|
|
230
|
-
for (const nf of nested_fields) {
|
|
231
|
-
if (!nf.required && !nf.has_default)
|
|
232
|
-
continue;
|
|
233
|
-
nested[nf.name] = generate_valid_value(nf, nested_schema.shape[nf.name]);
|
|
234
|
-
}
|
|
235
|
-
return nested;
|
|
236
|
-
}
|
|
265
|
+
if (nested_schema)
|
|
266
|
+
return generate_valid_object(nested_schema, field_schema).value;
|
|
237
267
|
return {};
|
|
238
268
|
}
|
|
239
269
|
case 'null':
|
|
@@ -284,6 +314,166 @@ export const generate_valid_value = (field, field_schema) => {
|
|
|
284
314
|
return 'test_value';
|
|
285
315
|
}
|
|
286
316
|
};
|
|
317
|
+
/**
|
|
318
|
+
* Generate a valid object for an object schema: its required and defaulted
|
|
319
|
+
* fields first; when `schema` still refuses it, each optional field alone on
|
|
320
|
+
* top of those, in declaration order, taking the first that parses; and
|
|
321
|
+
* failing that, the optional fields added one at a time, in declaration
|
|
322
|
+
* order, until it parses. The fill covers a `.refine()` over optional fields
|
|
323
|
+
* (at least one of, exactly one of, both of), which the required-only object
|
|
324
|
+
* can't satisfy, and the single-field pass keeps an unrelated optional field
|
|
325
|
+
* declared ahead of the refined ones (an `acting`, a note) out of the object.
|
|
326
|
+
*
|
|
327
|
+
* Shared by the top-level body generators and `generate_valid_value`'s
|
|
328
|
+
* nested-object case, so a refined object validates the same way at any depth.
|
|
329
|
+
*
|
|
330
|
+
* @param object_schema - the unwrapped object whose fields are generated
|
|
331
|
+
* @param schema - the schema the object must satisfy, refinements included
|
|
332
|
+
* (`object_schema` or a schema wrapping it)
|
|
333
|
+
* @returns the generated object and its last `safeParse` result — the caller
|
|
334
|
+
* decides whether a failure throws
|
|
335
|
+
*/
|
|
336
|
+
export const generate_valid_object = (object_schema, schema) => {
|
|
337
|
+
const base = {};
|
|
338
|
+
const optional = [];
|
|
339
|
+
for (const field of zod_extract_fields(object_schema)) {
|
|
340
|
+
const value = generate_valid_value(field, object_schema.shape[field.name]);
|
|
341
|
+
if (is_optional_field(field))
|
|
342
|
+
optional.push([field.name, value]);
|
|
343
|
+
else
|
|
344
|
+
base[field.name] = value;
|
|
345
|
+
}
|
|
346
|
+
return fill_optional_fields(schema, base, optional);
|
|
347
|
+
};
|
|
348
|
+
/** A field the object may omit — optional and without a default Zod fills. */
|
|
349
|
+
const is_optional_field = (field) => !field.required && !field.has_default;
|
|
350
|
+
/**
|
|
351
|
+
* `start` as is when `schema` accepts it; else `start` plus each candidate
|
|
352
|
+
* alone, in order, taking the first that parses; else the candidates added
|
|
353
|
+
* one at a time, in order, until it parses.
|
|
354
|
+
*
|
|
355
|
+
* @param schema - the schema the object must satisfy
|
|
356
|
+
* @param start - the fields every result keeps
|
|
357
|
+
* @param candidates - optional fields and their values, in declaration order
|
|
358
|
+
* @returns the object and its last `safeParse` result
|
|
359
|
+
*/
|
|
360
|
+
const fill_optional_fields = (schema, start, candidates) => {
|
|
361
|
+
let result = schema.safeParse(start);
|
|
362
|
+
if (result.success)
|
|
363
|
+
return { value: start, result };
|
|
364
|
+
// each optional field alone, so one the refinement doesn't need stays out
|
|
365
|
+
for (const [name, field_value] of candidates) {
|
|
366
|
+
const value = { ...start, [name]: field_value };
|
|
367
|
+
const single_result = schema.safeParse(value);
|
|
368
|
+
if (single_result.success)
|
|
369
|
+
return { value, result: single_result };
|
|
370
|
+
}
|
|
371
|
+
// then cumulatively, for a refinement needing several
|
|
372
|
+
const value = { ...start };
|
|
373
|
+
for (const [name, field_value] of candidates) {
|
|
374
|
+
value[name] = field_value;
|
|
375
|
+
result = schema.safeParse(value);
|
|
376
|
+
if (result.success)
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
return { value, result };
|
|
380
|
+
};
|
|
381
|
+
/**
|
|
382
|
+
* A valid object that carries `field`, built from `base`, a valid object of
|
|
383
|
+
* `object_schema` — the body a case breaking `field` changes, so the case
|
|
384
|
+
* breaks that field and nothing else. `base` itself when it carries the
|
|
385
|
+
* field. Otherwise the field takes a generated value and joins `base`, with
|
|
386
|
+
* the optional fields `base` omits filled as `generate_valid_object` fills
|
|
387
|
+
* them (a pair rule: adding `scope_kind` needs `scope_id`); failing that, it
|
|
388
|
+
* replaces `base`'s optional fields, refilled the same way around it (an
|
|
389
|
+
* exactly-one-of rule: adding `slug` means dropping `id`).
|
|
390
|
+
*
|
|
391
|
+
* @param object_schema - the unwrapped object `base` is an instance of
|
|
392
|
+
* @param schema - the schema the object must satisfy, refinements included
|
|
393
|
+
* @param base - a valid object, the generated or seeded base
|
|
394
|
+
* @param field - the field the object must carry
|
|
395
|
+
* @returns the object, or `null` when no object the generator can build
|
|
396
|
+
* carries the field and passes `schema`
|
|
397
|
+
*/
|
|
398
|
+
export const generate_valid_object_with_field = (object_schema, schema, base, field) => {
|
|
399
|
+
if (Object.hasOwn(base, field.name))
|
|
400
|
+
return base;
|
|
401
|
+
const fields = zod_extract_fields(object_schema);
|
|
402
|
+
const value_of = (f) => Object.hasOwn(base, f.name)
|
|
403
|
+
? base[f.name]
|
|
404
|
+
: generate_valid_value(f, object_schema.shape[f.name]);
|
|
405
|
+
const field_value = value_of(field);
|
|
406
|
+
// the field joins the base, which keeps its optional fields
|
|
407
|
+
const absent = fields
|
|
408
|
+
.filter((f) => f.name !== field.name && is_optional_field(f) && !Object.hasOwn(base, f.name))
|
|
409
|
+
.map((f) => [f.name, value_of(f)]);
|
|
410
|
+
const joined = fill_optional_fields(schema, { ...base, [field.name]: field_value }, absent);
|
|
411
|
+
if (joined.result.success)
|
|
412
|
+
return joined.value;
|
|
413
|
+
// the field in place of the base's optional fields, which are refilled around it
|
|
414
|
+
const optional_names = new Set(fields.filter(is_optional_field).map((f) => f.name));
|
|
415
|
+
const core = {};
|
|
416
|
+
for (const [k, v] of Object.entries(base)) {
|
|
417
|
+
if (!optional_names.has(k))
|
|
418
|
+
core[k] = v;
|
|
419
|
+
}
|
|
420
|
+
core[field.name] = field_value;
|
|
421
|
+
const others = fields
|
|
422
|
+
.filter((f) => f.name !== field.name && is_optional_field(f))
|
|
423
|
+
.map((f) => [f.name, value_of(f)]);
|
|
424
|
+
const replaced = fill_optional_fields(schema, core, others);
|
|
425
|
+
return replaced.result.success ? replaced.value : null;
|
|
426
|
+
};
|
|
427
|
+
const NIL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
428
|
+
/**
|
|
429
|
+
* A string a URL-sourced field accepts — a route param or a query value, which
|
|
430
|
+
* arrive as strings whatever the schema parses them into. The nil UUID for a
|
|
431
|
+
* UUID field; else the first the field accepts of `test_value`, the generated
|
|
432
|
+
* valid value written as a string, `1`, `0`, `true` and `false`, the field's
|
|
433
|
+
* JSON Schema `default` or lower bound written as a string (a preprocessed
|
|
434
|
+
* integer whose minimum is above one), and a string synthesized from the
|
|
435
|
+
* field's constraints.
|
|
436
|
+
*
|
|
437
|
+
* @param field - the field's info, from `zod_extract_fields`
|
|
438
|
+
* @param field_schema - the field's schema
|
|
439
|
+
* @returns the string, or `null` when no candidate passes
|
|
440
|
+
*/
|
|
441
|
+
export const generate_valid_string_param = (field, field_schema) => {
|
|
442
|
+
const candidates = [];
|
|
443
|
+
if (field.base_type === 'uuid' || detect_format(field_schema) === 'uuid') {
|
|
444
|
+
candidates.push(NIL_UUID);
|
|
445
|
+
}
|
|
446
|
+
candidates.push('test_value');
|
|
447
|
+
const generated = generate_valid_value(field, field_schema);
|
|
448
|
+
if (typeof generated === 'string' ||
|
|
449
|
+
typeof generated === 'number' ||
|
|
450
|
+
typeof generated === 'boolean') {
|
|
451
|
+
candidates.push(String(generated));
|
|
452
|
+
}
|
|
453
|
+
candidates.push('1', '0', 'true', 'false');
|
|
454
|
+
// a number the URL string is preprocessed into: its default or its lower bound
|
|
455
|
+
const json = to_field_json_schema(field_schema);
|
|
456
|
+
if (json) {
|
|
457
|
+
const fallback = json.default;
|
|
458
|
+
if (typeof fallback === 'number' || typeof fallback === 'string') {
|
|
459
|
+
candidates.push(String(fallback));
|
|
460
|
+
}
|
|
461
|
+
if (typeof json.minimum === 'number')
|
|
462
|
+
candidates.push(String(Math.ceil(json.minimum)));
|
|
463
|
+
if (typeof json.exclusiveMinimum === 'number') {
|
|
464
|
+
candidates.push(String(Math.floor(json.exclusiveMinimum) + 1));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
candidates.push(generate_valid_string(field_schema));
|
|
468
|
+
return candidates.find((c) => field_schema.safeParse(c).success) ?? null;
|
|
469
|
+
};
|
|
470
|
+
/**
|
|
471
|
+
* Format Zod issues as `path: message` pairs for a generator's error message.
|
|
472
|
+
*
|
|
473
|
+
* @param error - the failed parse's error
|
|
474
|
+
* @returns the issues, comma-separated
|
|
475
|
+
*/
|
|
476
|
+
export const format_zod_issues = (error) => error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(', ');
|
|
287
477
|
/**
|
|
288
478
|
* The `Content-Type` a well-formed request to a route sends: `application/json`,
|
|
289
479
|
* or `application/octet-stream` for a `raw_body` route. Every `POST` route
|
|
@@ -351,32 +541,10 @@ export const generate_valid_body = (input_schema) => {
|
|
|
351
541
|
const object_schema = zod_unwrap_to_object(input_schema);
|
|
352
542
|
if (!object_schema)
|
|
353
543
|
return undefined;
|
|
354
|
-
const
|
|
355
|
-
const body = {};
|
|
356
|
-
for (const field of fields) {
|
|
357
|
-
if (!field.required && !field.has_default)
|
|
358
|
-
continue;
|
|
359
|
-
body[field.name] = generate_valid_value(field, object_schema.shape[field.name]);
|
|
360
|
-
}
|
|
361
|
-
let result = input_schema.safeParse(body);
|
|
362
|
-
if (!result.success) {
|
|
363
|
-
// Fallback for schemas with a top-level `.refine()` that requires at
|
|
364
|
-
// least one of N optional fields. Fill optional fields until the body
|
|
365
|
-
// satisfies validation.
|
|
366
|
-
for (const field of fields) {
|
|
367
|
-
if (field.required || field.has_default || field.name in body)
|
|
368
|
-
continue;
|
|
369
|
-
body[field.name] = generate_valid_value(field, object_schema.shape[field.name]);
|
|
370
|
-
result = input_schema.safeParse(body);
|
|
371
|
-
if (result.success)
|
|
372
|
-
break;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
544
|
+
const { value, result } = generate_valid_object(object_schema, input_schema);
|
|
375
545
|
if (!result.success) {
|
|
376
546
|
throw new Error(`generate_valid_body: generated body fails validation — ` +
|
|
377
|
-
`fix generate_valid_value for: ${result.error
|
|
378
|
-
.map((i) => `${i.path.join('.')}: ${i.message}`)
|
|
379
|
-
.join(', ')}`);
|
|
547
|
+
`fix generate_valid_value for: ${format_zod_issues(result.error)}`);
|
|
380
548
|
}
|
|
381
|
-
return
|
|
549
|
+
return value;
|
|
382
550
|
};
|
package/dist/testing/stubs.d.ts
CHANGED
|
@@ -64,9 +64,10 @@ export declare const stub_mw: (_c: any, next: any) => Promise<void>;
|
|
|
64
64
|
* wires a listener fails loudly instead of silently never firing
|
|
65
65
|
* (`create_recording_audit_emitter` accepts listeners); `listener_count`
|
|
66
66
|
* returns 0; `drain_inflight` resolves immediately, since nothing is ever
|
|
67
|
-
* written
|
|
68
|
-
* fan-out) build a real emitter
|
|
69
|
-
*
|
|
67
|
+
* written, and `is_tracking_inflight` answers `false`. Tests asserting on
|
|
68
|
+
* real audit-row persistence (or on listener fan-out) build a real emitter
|
|
69
|
+
* via `create_audit_emitter` against a stub or real DB — `create_test_app`
|
|
70
|
+
* already does this on the test backend.
|
|
70
71
|
*/
|
|
71
72
|
export declare const create_test_audit_emitter: () => AuditEmitter;
|
|
72
73
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAa7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAErF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAI3E,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAc9F;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,GAAG,GAAG,EAAE,OAAO,MAAM,KAAG,CAqBtD,CAAC;AAET;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,GAAG,GAAG,EAAE,QAAQ,MAAM,EAAE,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,CAOxF,CAAC;AAET,iEAAiE;AACjE,eAAO,MAAM,IAAI,EAAE,GAAkC,CAAC;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,QAAO,EAI/B,CAAC;AAEJ,gDAAgD;AAChD,eAAO,MAAM,YAAY,QAAO,QAAgC,CAAC;AAEjE,2CAA2C;AAC3C,eAAO,MAAM,OAAO,GAAU,IAAI,GAAG,EAAE,MAAM,GAAG,KAAG,OAAO,CAAC,IAAI,CAAW,CAAC;AAI3E
|
|
1
|
+
{"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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"}
|
package/dist/testing/stubs.js
CHANGED
|
@@ -102,9 +102,10 @@ const stub_db = create_noop_stub('stub_db');
|
|
|
102
102
|
* wires a listener fails loudly instead of silently never firing
|
|
103
103
|
* (`create_recording_audit_emitter` accepts listeners); `listener_count`
|
|
104
104
|
* returns 0; `drain_inflight` resolves immediately, since nothing is ever
|
|
105
|
-
* written
|
|
106
|
-
* fan-out) build a real emitter
|
|
107
|
-
*
|
|
105
|
+
* written, and `is_tracking_inflight` answers `false`. Tests asserting on
|
|
106
|
+
* real audit-row persistence (or on listener fan-out) build a real emitter
|
|
107
|
+
* via `create_audit_emitter` against a stub or real DB — `create_test_app`
|
|
108
|
+
* already does this on the test backend.
|
|
108
109
|
*/
|
|
109
110
|
export const create_test_audit_emitter = () => ({
|
|
110
111
|
emit: () => { },
|
|
@@ -115,7 +116,8 @@ export const create_test_audit_emitter = () => ({
|
|
|
115
116
|
throw new Error('create_test_audit_emitter accepts no listeners — use create_recording_audit_emitter');
|
|
116
117
|
},
|
|
117
118
|
listener_count: () => 0,
|
|
118
|
-
drain_inflight: async () => { }
|
|
119
|
+
drain_inflight: async () => { },
|
|
120
|
+
is_tracking_inflight: () => false
|
|
119
121
|
});
|
|
120
122
|
/**
|
|
121
123
|
* Build a no-op `AuditLogSse` for tests that wire `audit_sse` into the
|
package/dist/zod.d.ts
CHANGED
|
@@ -30,6 +30,15 @@ export declare const zod_get_base_type: (schema: z.ZodType) => string;
|
|
|
30
30
|
* @returns the innermost non-wrapper schema
|
|
31
31
|
*/
|
|
32
32
|
export declare const zod_get_innermost_type: (schema: z.ZodType) => z.ZodType;
|
|
33
|
+
/**
|
|
34
|
+
* Every layer of a schema, from the schema itself through each wrapper
|
|
35
|
+
* (optional, nullable, default, transform, pipe, prefault) to the innermost
|
|
36
|
+
* non-wrapper — for reading `.meta()` a wrapper does not carry over from the
|
|
37
|
+
* schema it wraps.
|
|
38
|
+
*
|
|
39
|
+
* @returns the layers, outermost first, ending at `zod_get_innermost_type(schema)`
|
|
40
|
+
*/
|
|
41
|
+
export declare const zod_get_schema_layers: (schema: z.ZodType) => Array<z.ZodType>;
|
|
33
42
|
/**
|
|
34
43
|
* Get all property keys from an object schema, unwrapping wrappers.
|
|
35
44
|
* Returns an empty array if the innermost type is not an object.
|
package/dist/zod.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/zod.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgC7B;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,CAAC,CAAC,IAAI,CAAC,WAOzD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,MAAqC,CAAC;AAgC5F;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,CAAC,CAAC,OAM5D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EACtD,QAAQ,CAAC,KACP,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAGjC,CAAC;AAaF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,CAAC,CAAC,OAAO,EAAE,KAAK,MAAM,KAAG,CAAC,CAAC,OAIvE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,CAAC,CAAC,SAAS,GAAG,IAUtE,CAAC;AAIF,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,CAAC,CAAC,SAAS,KAAG,KAAK,CAAC,YAAY,CAa1E,CAAC"}
|
|
1
|
+
{"version":3,"file":"zod.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/zod.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgC7B;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,CAAC,CAAC,IAAI,CAAC,WAOzD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,MAAqC,CAAC;AAgC5F;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,CAAC,CAAC,OAM5D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAUxE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EACtD,QAAQ,CAAC,KACP,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAGjC,CAAC;AAaF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,CAAC,CAAC,OAAO,EAAE,KAAK,MAAM,KAAG,CAAC,CAAC,OAIvE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,CAAC,CAAC,SAAS,GAAG,IAUtE,CAAC;AAIF,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,CAAC,CAAC,SAAS,KAAG,KAAK,CAAC,YAAY,CAa1E,CAAC"}
|
package/dist/zod.js
CHANGED
|
@@ -104,6 +104,26 @@ export const zod_get_innermost_type = (schema) => {
|
|
|
104
104
|
}
|
|
105
105
|
return schema;
|
|
106
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* Every layer of a schema, from the schema itself through each wrapper
|
|
109
|
+
* (optional, nullable, default, transform, pipe, prefault) to the innermost
|
|
110
|
+
* non-wrapper — for reading `.meta()` a wrapper does not carry over from the
|
|
111
|
+
* schema it wraps.
|
|
112
|
+
*
|
|
113
|
+
* @returns the layers, outermost first, ending at `zod_get_innermost_type(schema)`
|
|
114
|
+
*/
|
|
115
|
+
export const zod_get_schema_layers = (schema) => {
|
|
116
|
+
const layers = [schema];
|
|
117
|
+
let s = schema;
|
|
118
|
+
while (ZOD_WRAPPER_TYPES.has(s.def.type)) {
|
|
119
|
+
const sub = zod_to_subschema(s.def);
|
|
120
|
+
if (!sub)
|
|
121
|
+
break;
|
|
122
|
+
layers.push(sub);
|
|
123
|
+
s = sub;
|
|
124
|
+
}
|
|
125
|
+
return layers;
|
|
126
|
+
};
|
|
107
127
|
/**
|
|
108
128
|
* Get all property keys from an object schema, unwrapping wrappers.
|
|
109
129
|
* Returns an empty array if the innermost type is not an object.
|