@ultimat3/schema 31.6.0 → 31.7.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/CLAUDE.md CHANGED
@@ -23,6 +23,11 @@ loop: **a loop that collects issues asks `stopCollecting(count, hasMore)` after
23
23
  union that discards member issues restores `issuesStopped()`. `standard.large.test.ts` pins 10k bad
24
24
  items → 100 issues, `issuesTruncated`, ≤ 100 item checks (a work count, never wall time). The cap is ambient module state because builtin
25
25
  validation is synchronous; `withIssueCap` is re-entrant and only the outermost run reports.
26
+ **A composite reads a builtin member's `Check` directly** (`checkOf` answers it from a `WeakMap`
27
+ keyed by the member's `'~standard'` object, filled in `makeSchema`; only a foreign Standard Schema
28
+ member goes through `validate`), and calls it through `checkAt(check, value, path, key)`: the
29
+ member runs at a shared empty path and its issues are re-rooted only on failure.
30
+ `standard.perf.test.ts` pins both as work counts (0 public validates, 1 path for 10k valid rows).
26
31
  `schema-rules.ts` is a leaf (`SCHEMA_RULES`, `issueRuleFacts`), imported by `standard.ts`,
27
32
  `errors.ts` (type only) and `builder.ts`. `sensitive.ts` reads `provider.ts` and is not in `t`'s graph.
28
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/schema",
3
- "version": "31.6.0",
3
+ "version": "31.7.0",
4
4
  "description": "Ultimate's validation seam: Standard Schema interface, the t namespace, JSON Schema output",
5
5
  "keywords": [
6
6
  "ultimate",
@@ -4,6 +4,7 @@
4
4
 
5
5
  import {
6
6
  type AnySchema,
7
+ checkAt,
7
8
  checkOf,
8
9
  failWith,
9
10
  makeSchema,
@@ -83,7 +84,7 @@ export function arraySchema<S extends AnySchema>(
83
84
  const issues: StandardIssue[] = [];
84
85
  const out: unknown[] = [];
85
86
  for (const [index, member] of value.entries()) {
86
- const result = itemCheck(member, [...path, index]);
87
+ const result = checkAt(itemCheck, member, path, index);
87
88
  if (result.ok) out.push(result.value);
88
89
  else {
89
90
  issues.push(...result.issues);
package/src/builder.ts CHANGED
@@ -253,13 +253,23 @@ function capped<Out>(check: Check<Out>, value: unknown): StandardResult<Out> {
253
253
  : cappedFailure(run.value.issues, run.cap, run.stopped);
254
254
  }
255
255
 
256
+ /**
257
+ * A builtin schema's check, keyed by its `'~standard'` object — the one part every spread
258
+ * (`{ ...base, min }`) keeps by reference. A composite reads a builtin member through this, never
259
+ * through the public `validate`: that door costs a cap closure, a run record, a result and a
260
+ * re-wrap per member even when the member passes (`standard.perf.test.ts`).
261
+ */
262
+ const BUILTIN_CHECKS = new WeakMap<object, Check<unknown>>();
263
+
256
264
  export function makeSchema<In, Out>(node: SchemaNode, check: Check<Out>): Schema<In, Out> {
265
+ const props: Schema<In, Out>['~standard'] = {
266
+ version: 1,
267
+ vendor: VENDOR,
268
+ validate: (value: unknown): StandardResult<Out> => capped(check, value),
269
+ };
270
+ BUILTIN_CHECKS.set(props, check as Check<unknown>);
257
271
  const schema: Schema<In, Out> = {
258
- '~standard': {
259
- version: 1,
260
- vendor: VENDOR,
261
- validate: (value: unknown): StandardResult<Out> => capped(check, value),
262
- },
272
+ '~standard': props,
263
273
  node,
264
274
  parse(value: unknown, root = 'value'): Out {
265
275
  const result = capped(check, value);
@@ -329,8 +339,14 @@ export function makeSchema<In, Out>(node: SchemaNode, check: Check<Out>): Schema
329
339
  return schema;
330
340
  }
331
341
 
332
- /** Read the check back off a builtin schema so composites can reuse it. */
342
+ /**
343
+ * Read the check back off a schema so composites can reuse it. A builtin schema answers its own
344
+ * check, joining the caller's issue cap directly; a foreign Standard Schema member keeps the
345
+ * public route, its issues re-rooted under the caller's path.
346
+ */
333
347
  export function checkOf<In, Out>(schema: Schema<In, Out>): Check<Out> {
348
+ const builtin = BUILTIN_CHECKS.get(schema['~standard']);
349
+ if (builtin !== undefined) return builtin as Check<Out>;
334
350
  return (value, path) => {
335
351
  const result = schema['~standard'].validate(value);
336
352
  if (isThenable(result)) {
@@ -348,3 +364,24 @@ export function checkOf<In, Out>(schema: Schema<In, Out>): Check<Out> {
348
364
  );
349
365
  };
350
366
  }
367
+
368
+ /** The path a composite hands a member: shared, never written to, so a pass allocates none. */
369
+ const MEMBER_ROOT: Path = Object.freeze([]);
370
+
371
+ /**
372
+ * A composite's member at `key` under `path`. The member runs at a shared empty path and its
373
+ * issues are re-rooted only when it fails, so a passing member costs no path array; order and
374
+ * count are the member's own, so the issue cap and issue order are untouched.
375
+ */
376
+ export function checkAt<Out>(
377
+ check: Check<Out>,
378
+ value: unknown,
379
+ path: Path,
380
+ key: PropertyKey,
381
+ ): CheckResult<Out> {
382
+ const result = check(value, MEMBER_ROOT);
383
+ if (result.ok) return result;
384
+ return failWith(
385
+ result.issues.map((issue) => ({ ...issue, path: [...path, key, ...(issue.path ?? [])] })),
386
+ );
387
+ }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import {
6
6
  type AnySchema,
7
+ checkAt,
7
8
  checkOf,
8
9
  failWith,
9
10
  isPlainObject,
@@ -49,14 +50,13 @@ export function recordSchema<S extends AnySchema>(
49
50
  // the same segment `arraySchema` already uses, so `formatPath` renders `meta[3]` with no
50
51
  // new spelling to learn. It is `Object.entries` order — integer-like keys sort ahead of
51
52
  // string ones — so it names an entry that exists rather than a byte offset in the body.
52
- const at = [...path, index];
53
53
  // A record is stored as `jsonb` far more often than not, and jsonb refuses an unstorable
54
54
  // KEY exactly as it does a value — so the key is refused here, by position, not quoted.
55
55
  const unfitKey = unstorable(key);
56
56
  if (unfitKey !== undefined) {
57
57
  issues.push({
58
58
  message: `expected a record key, received one ${unfitKey}`,
59
- path: at,
59
+ path: [...path, index],
60
60
  rule: 'record.key',
61
61
  });
62
62
  continue;
@@ -66,12 +66,12 @@ export function recordSchema<S extends AnySchema>(
66
66
  // The refused NAMES are a closed set declared right here, so the message may state
67
67
  // them: that is the one part of this line the caller did not choose.
68
68
  message: expected(`a record key that is not ${PROTOTYPE_KEYS.join(' | ')}`, key),
69
- path: at,
69
+ path: [...path, index],
70
70
  rule: 'record.key',
71
71
  });
72
72
  continue;
73
73
  }
74
- const result = valueCheck(entry, at);
74
+ const result = checkAt(valueCheck, entry, path, index);
75
75
  if (result.ok) out[key] = result.value;
76
76
  else issues.push(...result.issues);
77
77
  }
package/src/validators.ts CHANGED
@@ -7,6 +7,7 @@ import { type ArrayBounds, arraySchema } from './array-schema';
7
7
  import {
8
8
  type AnySchema,
9
9
  type Check,
10
+ checkAt,
10
11
  checkOf,
11
12
  failWith,
12
13
  isPlainObject,
@@ -224,7 +225,7 @@ export function objectSchema<S extends Shape>(shape: S): ObjectSchema<S> {
224
225
  // its `.default()` could never fire, and the HTTP path and this one disagreed about what
225
226
  // the caller sent.
226
227
  const raw = Object.hasOwn(value, key) ? value[key] : undefined;
227
- const result = memberCheck(raw, [...path, key]);
228
+ const result = checkAt(memberCheck, raw, path, key);
228
229
  if (result.ok) {
229
230
  // Unknown keys are dropped, never forwarded — no mass assignment through an action.
230
231
  if (result.value !== undefined) out[key] = result.value;