@crewhaus/tool-schema 0.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/README.md +127 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +678 -0
- package/dist/lib/assert.d.ts +62 -0
- package/dist/lib/assert.js +343 -0
- package/dist/lib/compare.d.ts +67 -0
- package/dist/lib/compare.js +298 -0
- package/dist/lib/formats.d.ts +115 -0
- package/dist/lib/formats.js +395 -0
- package/dist/lib/infer.d.ts +51 -0
- package/dist/lib/infer.js +201 -0
- package/dist/lib/jsonschema.d.ts +50 -0
- package/dist/lib/jsonschema.js +605 -0
- package/dist/lib/records.d.ts +114 -0
- package/dist/lib/records.js +234 -0
- package/dist/lib/schemadiff.d.ts +26 -0
- package/dist/lib/schemadiff.js +350 -0
- package/dist/lib/suggest.d.ts +25 -0
- package/dist/lib/suggest.js +65 -0
- package/dist/lib/summarize.d.ts +28 -0
- package/dist/lib/summarize.js +212 -0
- package/dist/lib/value.d.ts +84 -0
- package/dist/lib/value.js +241 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @crewhaus/tool-schema
|
|
2
|
+
|
|
3
|
+
Deterministic validation tools. Every one is pure: no filesystem, no network,
|
|
4
|
+
no clock, no randomness. The same input always produces the same bytes.
|
|
5
|
+
|
|
6
|
+
These are the tools for the moments a harness needs a *verdict* rather than an
|
|
7
|
+
opinion. Does this payload match the contract; did this batch arrive clean; is
|
|
8
|
+
this response still what it was last week; is the new schema safe to ship. A
|
|
9
|
+
model asked those questions gives a plausible answer. These give the same
|
|
10
|
+
answer twice — and say exactly which field, at which path, was wrong.
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
tools:
|
|
14
|
+
- all-schema # every tool below
|
|
15
|
+
- -JsonSchemaInfer # ...except this one
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| Tool | What it does |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `Assert` | Evaluate declarative checks against a value and report which failed, with actual vs expected |
|
|
21
|
+
| `CheckRequiredFields` | Verify dotted paths are present and non-empty, in one value or every row |
|
|
22
|
+
| `CompareGolden` | Snapshot comparison with ignored paths, ignored array order and a numeric epsilon |
|
|
23
|
+
| `DeepEqual` | Strict structural equality, with the first difference as a JSON Pointer |
|
|
24
|
+
| `JsonSchemaInfer` | Infer a schema from example values |
|
|
25
|
+
| `JsonSchemaValidate` | Validate a value against a JSON Schema, every error with a pointer path |
|
|
26
|
+
| `MatchSubset` | Assert the parts of a value you care about, allowing extra properties |
|
|
27
|
+
| `SchemaDiff` | What changed between two schemas, and whether a reader survives it |
|
|
28
|
+
| `SchemaSummarize` | Flatten a schema into a field table — path, type, required, constraints |
|
|
29
|
+
| `ValidateEnum` | Check values against an allowed set, with a "did you mean" for near misses |
|
|
30
|
+
| `ValidateFormat` | Check strings against email, uri, ipv4, ipv6, uuid, date, date-time, hostname and more |
|
|
31
|
+
| `ValidateRecords` | Validate a batch against one schema: per-row errors plus a pass/fail summary |
|
|
32
|
+
| `ValidateReferences` | Check every reference points at something that exists |
|
|
33
|
+
| `ValidateUniqueKeys` | Find rows sharing a key, including a composite key |
|
|
34
|
+
|
|
35
|
+
## The JSON Schema subset
|
|
36
|
+
|
|
37
|
+
`JsonSchemaValidate` and `ValidateRecords` share one Draft-07 validator,
|
|
38
|
+
written out in `src/lib/jsonschema.ts` so its behaviour is auditable in a
|
|
39
|
+
single file. (`JsonSchemaInfer` writes schemas rather than checking against
|
|
40
|
+
them, and does not go through it.) The validator enforces:
|
|
41
|
+
|
|
42
|
+
| Group | Keywords |
|
|
43
|
+
|---|---|
|
|
44
|
+
| any | `type`, `enum`, `const` |
|
|
45
|
+
| number | `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` |
|
|
46
|
+
| string | `minLength`, `maxLength`, `pattern`, `format` (opt-in) |
|
|
47
|
+
| array | `items` (schema or tuple), `additionalItems`, `contains`, `minItems`, `maxItems`, `uniqueItems` |
|
|
48
|
+
| object | `properties`, `patternProperties`, `additionalProperties`, `propertyNames`, `required`, `minProperties`, `maxProperties` |
|
|
49
|
+
| logic | `allOf`, `anyOf`, `oneOf`, `not`, `if`/`then`/`else` |
|
|
50
|
+
| reference | `$ref` to a local JSON Pointer, including `#/$defs/…` and `#/definitions/…` |
|
|
51
|
+
|
|
52
|
+
Boolean schemas work wherever a schema is expected. `pattern` is an unanchored
|
|
53
|
+
ECMA-262 match, and string lengths are counted in code points, both per spec.
|
|
54
|
+
|
|
55
|
+
**What it does not enforce**, and says so: `dependencies`,
|
|
56
|
+
`dependentRequired`, `dependentSchemas`, `unevaluatedProperties`,
|
|
57
|
+
`unevaluatedItems`, the draft-04 boolean form of the exclusive bounds, and
|
|
58
|
+
anything else outside the table. Every unenforced constraint keyword
|
|
59
|
+
*encountered while validating* comes back in `unsupportedKeywords` — so a
|
|
60
|
+
pass can be read honestly rather than mistaken for coverage it does not have.
|
|
61
|
+
The list is built while walking, not by scanning the document, so a keyword
|
|
62
|
+
sitting in a branch this particular value never reached is not in it. A
|
|
63
|
+
`$ref` that points outside the document is an error, never a silent `true`.
|
|
64
|
+
|
|
65
|
+
`format` follows Draft-07 and is an annotation until you pass `assertFormat`.
|
|
66
|
+
|
|
67
|
+
## The formats
|
|
68
|
+
|
|
69
|
+
Each format is a stated subset, not a full grammar, and each rejects the grey
|
|
70
|
+
areas deliberately:
|
|
71
|
+
|
|
72
|
+
- **email** — unquoted ASCII local part, hostname domain, at least one dot.
|
|
73
|
+
Quoted local parts, comments, IP-literal domains and non-ASCII are rejected.
|
|
74
|
+
- **uri** — scheme, colon, non-empty remainder, no whitespace or control
|
|
75
|
+
characters. Use **uri-reference** for relative references.
|
|
76
|
+
- **hostname** — labels of 1–63 alphanumerics and hyphens, 253 total. A
|
|
77
|
+
trailing dot and underscores are rejected; Unicode must arrive punycoded.
|
|
78
|
+
- **ipv4** — four octets 0–255, **no leading zeros** (the ambiguity behind a
|
|
79
|
+
long line of SSRF bugs).
|
|
80
|
+
- **ipv6** — full form, `::` compression, and an IPv4 tail. Zone identifiers
|
|
81
|
+
are rejected.
|
|
82
|
+
- **uuid** — 8-4-4-4-12 hex, any version or variant, braces rejected.
|
|
83
|
+
- **date** / **time** / **date-time** — RFC 3339, calendar-checked, so
|
|
84
|
+
`2023-02-30` fails and `2024-02-29` passes. A time needs an offset; a
|
|
85
|
+
date-time needs the `T`. A leap second (`:60`) is allowed.
|
|
86
|
+
- **json-pointer**, **regex** — RFC 6901, and "does it compile".
|
|
87
|
+
|
|
88
|
+
These grammars overlap, and not symmetrically: a hostname label is letters,
|
|
89
|
+
digits and hyphens, so every dotted quad, every UUID and every `YYYY-MM-DD`
|
|
90
|
+
date is also a valid hostname. `suggestFormats` therefore lists what a value
|
|
91
|
+
matches narrowest first — `uuid` before `hostname` — rather than in the order
|
|
92
|
+
the formats are declared.
|
|
93
|
+
|
|
94
|
+
## What is deliberately not here
|
|
95
|
+
|
|
96
|
+
`SchemaDiff` classifies a change as `compatible`, `breaking`, or `unknown` —
|
|
97
|
+
and never rounds `unknown` to safe. Whether one regex accepts everything
|
|
98
|
+
another does, and how two `anyOf` unions relate, are questions this package
|
|
99
|
+
does not answer; it says so instead of guessing. A change to a keyword it does
|
|
100
|
+
not model at all — `patternProperties`, `contains`, `propertyNames`,
|
|
101
|
+
`dependencies` — is `unknown` too, rather than absent from the report. "Backward compatible" here
|
|
102
|
+
means every value valid under the old schema is still valid under the new one,
|
|
103
|
+
which is compatibility for a *reader* of the data.
|
|
104
|
+
|
|
105
|
+
`JsonSchemaInfer` describes the samples it was given. It infers no numeric
|
|
106
|
+
ranges, no string lengths and no patterns, because three samples never justify
|
|
107
|
+
a range, and enum detection is opt-in for the same reason. Format detection is
|
|
108
|
+
narrower than `ValidateFormat`: `json-pointer` is never inferred and
|
|
109
|
+
`hostname` needs a dot in every sample, because both accept so much that
|
|
110
|
+
`"active"` and `/tmp/x` would otherwise be labelled. A field whose distinct
|
|
111
|
+
values overflow the tracking limit gets no format at all, since a format
|
|
112
|
+
agreed on by the values it kept says nothing about the ones it dropped.
|
|
113
|
+
|
|
114
|
+
JSON and YAML *parsing* and linting belong to `@crewhaus/tool-data`, not here.
|
|
115
|
+
|
|
116
|
+
## Layout
|
|
117
|
+
|
|
118
|
+
`src/lib/` holds the pure functions and is where the behaviour is tested;
|
|
119
|
+
`src/index.ts` wraps them as tools. A bug in `isIpv6` reads better as a failing
|
|
120
|
+
unit than as a failing tool call.
|
|
121
|
+
|
|
122
|
+
## Safety flags
|
|
123
|
+
|
|
124
|
+
All fourteen are `readOnly`, non-destructive, `scope: "internal"`, and declare
|
|
125
|
+
no io capability, because none of them crosses a process or network boundary.
|
|
126
|
+
`packages/tool-schema/src/index.test.ts` asserts that for every tool, so a
|
|
127
|
+
future addition that reaches outside has to change the assertion deliberately.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RegisteredTool } from "@crewhaus/tool-catalog";
|
|
2
|
+
export declare const jsonSchemaValidate: RegisteredTool;
|
|
3
|
+
export declare const jsonSchemaInfer: RegisteredTool;
|
|
4
|
+
export declare const validateRecords: RegisteredTool;
|
|
5
|
+
export declare const assert: RegisteredTool;
|
|
6
|
+
export declare const compareGolden: RegisteredTool;
|
|
7
|
+
export declare const deepEqual: RegisteredTool;
|
|
8
|
+
export declare const matchSubset: RegisteredTool;
|
|
9
|
+
export declare const checkRequiredFields: RegisteredTool;
|
|
10
|
+
export declare const validateEnum: RegisteredTool;
|
|
11
|
+
export declare const validateFormat: RegisteredTool;
|
|
12
|
+
export declare const validateUniqueKeys: RegisteredTool;
|
|
13
|
+
export declare const validateReferences: RegisteredTool;
|
|
14
|
+
export declare const schemaDiff: RegisteredTool;
|
|
15
|
+
export declare const schemaSummarize: RegisteredTool;
|
|
16
|
+
/** Every tool this package registers, in the order a catalog should list them. */
|
|
17
|
+
export declare const SCHEMA_TOOLS: ReadonlyArray<RegisteredTool>;
|
|
18
|
+
/**
|
|
19
|
+
* The declarative check grammar, re-exported for other tool packages.
|
|
20
|
+
*
|
|
21
|
+
* `Assert` here and `Branch` in `@crewhaus/tool-flow` both have to answer
|
|
22
|
+
* "does this value satisfy this condition?", and two implementations of that
|
|
23
|
+
* question would drift: an operator who learned `startsWith` from one would
|
|
24
|
+
* find it missing, or subtly different, in the other. There is one
|
|
25
|
+
* vocabulary and one evaluator, and this is where it lives.
|
|
26
|
+
*/
|
|
27
|
+
export { ASSERT_OPS, type AssertOp, type AssertReport, type Check, type CheckResult, checkRequired, runChecks, } from "./lib/assert";
|