@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.
Files changed (88) hide show
  1. package/dist/actions/CLAUDE.md +2 -2
  2. package/dist/actions/action_event.d.ts.map +1 -1
  3. package/dist/actions/action_event.js +14 -1
  4. package/dist/actions/perform_action.d.ts +17 -1
  5. package/dist/actions/perform_action.d.ts.map +1 -1
  6. package/dist/actions/perform_action.js +22 -2
  7. package/dist/auth/CLAUDE.md +3 -2
  8. package/dist/auth/account_action_specs.d.ts +12 -0
  9. package/dist/auth/account_action_specs.d.ts.map +1 -1
  10. package/dist/auth/account_action_specs.js +16 -2
  11. package/dist/auth/account_actions.d.ts.map +1 -1
  12. package/dist/auth/account_actions.js +7 -4
  13. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  14. package/dist/auth/admin_action_specs.js +29 -8
  15. package/dist/auth/audit_emitter.d.ts +14 -0
  16. package/dist/auth/audit_emitter.d.ts.map +1 -1
  17. package/dist/auth/audit_emitter.js +5 -1
  18. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  19. package/dist/auth/role_grant_offer_action_specs.js +5 -3
  20. package/dist/http/CLAUDE.md +14 -2
  21. package/dist/http/error_schemas.d.ts +8 -0
  22. package/dist/http/error_schemas.d.ts.map +1 -1
  23. package/dist/http/error_schemas.js +12 -0
  24. package/dist/http/jsonrpc_errors.d.ts +40 -15
  25. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  26. package/dist/http/jsonrpc_errors.js +90 -35
  27. package/dist/schema_meta.d.ts +60 -0
  28. package/dist/schema_meta.d.ts.map +1 -1
  29. package/dist/schema_meta.js +64 -0
  30. package/dist/testing/CLAUDE.md +162 -61
  31. package/dist/testing/adversarial_input_cases.d.ts +67 -7
  32. package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
  33. package/dist/testing/adversarial_input_cases.js +210 -165
  34. package/dist/testing/audit_drift_guard.d.ts +3 -2
  35. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  36. package/dist/testing/audit_drift_guard.js +5 -3
  37. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
  38. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
  39. package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
  40. package/dist/testing/cross_backend/capabilities.d.ts +27 -4
  41. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  42. package/dist/testing/cross_backend/capabilities.js +10 -6
  43. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  44. package/dist/testing/cross_backend/cell_crud.js +117 -2
  45. package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
  46. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
  47. package/dist/testing/cross_backend/cross_rpc.js +2 -2
  48. package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
  49. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  50. package/dist/testing/cross_backend/default_backend_configs.js +3 -3
  51. package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
  52. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
  53. package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
  54. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
  55. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  56. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
  57. package/dist/testing/cross_backend/setup.d.ts +2 -2
  58. package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
  59. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
  60. package/dist/testing/cross_backend/spine_suites.js +291 -20
  61. package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
  62. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  63. package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
  64. package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
  65. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  66. package/dist/testing/cross_backend/testing_backdoor.js +136 -41
  67. package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
  68. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  69. package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
  70. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  71. package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
  72. package/dist/testing/rpc_attack_surface.d.ts +10 -0
  73. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  74. package/dist/testing/rpc_attack_surface.js +15 -3
  75. package/dist/testing/rpc_envelope_cases.d.ts +5 -2
  76. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
  77. package/dist/testing/rpc_envelope_cases.js +4 -1
  78. package/dist/testing/rpc_round_trip.js +3 -3
  79. package/dist/testing/schema_generators.d.ts +99 -2
  80. package/dist/testing/schema_generators.d.ts.map +1 -1
  81. package/dist/testing/schema_generators.js +244 -76
  82. package/dist/testing/stubs.d.ts +4 -3
  83. package/dist/testing/stubs.d.ts.map +1 -1
  84. package/dist/testing/stubs.js +6 -4
  85. package/dist/zod.d.ts +9 -0
  86. package/dist/zod.d.ts.map +1 -1
  87. package/dist/zod.js +20 -0
  88. 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
- * Detect format constraints on a field by converting to JSON Schema.
16
- * Returns format string (e.g. 'uuid', 'email') or null.
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 detect_format = (field_schema) => {
36
+ export const to_field_json_schema = (field_schema) => {
37
+ let json;
19
38
  try {
20
- const json = z.toJSONSchema(field_schema);
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
- // schema can't be converted, no format
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
- try {
114
- const json = z.toJSONSchema(field_schema);
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
- try {
184
- const json = z.toJSONSchema(field_schema);
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
- let min_items = 0;
203
- try {
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
- // Recursively generate valid nested objects
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
- const nested_fields = zod_extract_fields(nested_schema);
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 fields = zod_extract_fields(object_schema);
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.issues
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 body;
549
+ return value;
382
550
  };
@@ -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. Tests asserting on real audit-row persistence (or on listener
68
- * fan-out) build a real emitter via `create_audit_emitter` against a stub or
69
- * real DB — `create_test_app` already does this on the test backend.
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":"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"}
1
+ {"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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"}
@@ -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. Tests asserting on real audit-row persistence (or on listener
106
- * fan-out) build a real emitter via `create_audit_emitter` against a stub or
107
- * real DB — `create_test_app` already does this on the test backend.
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.148.0",
3
+ "version": "0.149.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",