schema-shield 1.0.5 → 1.1.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 CHANGED
@@ -1,248 +1,256 @@
1
1
  # SchemaShield 🛡️
2
2
 
3
- **Validation for Modern Architectures: Secure, Stack-Safe, and Domain-Aware.**
3
+ **Secure, Stack-Safe, Offline, and Domain-Aware JSON Schema Validation.**
4
4
 
5
- SchemaShield is a secure interpreter for JSON Schema engineered for strict environments and complex domain logic. It prioritizes **architectural stability** and **developer experience** over raw synthetic throughput.
5
+ SchemaShield is a high-performance JSON Schema interpreter for JavaScript applications that need predictable validation without runtime code generation or hidden network access.
6
6
 
7
- > 🏆 **Fastest JSON Schema Validator on Bun** 2.5x faster than ajv, 4x faster than schemasafe
8
- >
9
- > 📊 **#3 fastest on Node.js** — 70% ajv speed, 50x faster than others
7
+ In the latest benchmark run, SchemaShield reached approximately **80% of AJV's throughput** and ran approximately **15% faster than schemasafe**.
8
+
9
+ - No `eval()` or `new Function()`
10
+ - Synchronous, offline validation
11
+ - No implicit fetch, HTTP, DNS, or network I/O
12
+ - First-class validation for JavaScript objects and domain rules
13
+ - Structured error paths, causes, and trees
14
+ - Zero runtime dependencies
15
+ - Verified CJS, ESM, browser, and TypeScript declaration packages
10
16
 
11
17
  ## Quick Start
12
18
 
19
+ Install SchemaShield:
20
+
21
+ ```bash
22
+ npm install schema-shield
23
+ ```
24
+
25
+ Or with Bun:
26
+
27
+ ```bash
28
+ bun add schema-shield
29
+ ```
30
+
31
+ Compile a schema and reuse the validator:
32
+
13
33
  ```javascript
14
34
  import { SchemaShield } from "schema-shield";
15
35
 
16
- const validator = new SchemaShield().compile({
36
+ const validateUser = new SchemaShield({ failFast: false }).compile({
17
37
  type: "object",
18
38
  properties: {
19
- name: { type: "string" },
20
- age: { type: "number" }
21
- }
39
+ name: { type: "string", minLength: 1 },
40
+ age: { type: "number", minimum: 18 }
41
+ },
42
+ required: ["name", "age"],
43
+ additionalProperties: false
22
44
  });
23
45
 
24
- validator({ name: "John", age: 30 });
25
- // { valid: true, data: { name: "John", age: 30 } }
46
+ const validResult = validateUser({ name: "Ada", age: 37 });
47
+ // { valid: true, data: { name: "Ada", age: 37 }, error: null }
26
48
 
27
- validator({ name: "John", age: "30" });
28
- // { valid: false, error: ValidationError }
49
+ const invalidResult = validateUser({ name: "Ada", age: 16 });
50
+ // { valid: false, data: { name: "Ada", age: 16 }, error: ValidationError }
29
51
  ```
30
52
 
31
- ## Table of Contents
53
+ > The default `failFast: true` mode returns `error: true` when validation fails. With `failFast: false`, built-in failures and custom keyword failures created with `defineError()` return a detailed `ValidationError` with paths, causes, and an error tree. A custom keyword that returns `true` directly still produces `error: true`. Use `defineError()` for detailed errors.
54
+
55
+ ## Contents
32
56
 
33
- - [Quick Start](#quick-start)
34
57
  - [Why SchemaShield?](#why-schemashield)
35
- - [Comparison with Other Approaches](#comparison-with-other-approaches)
36
- - [Usage](#usage)
58
+ - [Architecture Comparison](#architecture-comparison)
37
59
  - [Performance](#performance)
38
- - [Understanding Performance Context](#understanding-performance-context)
39
- - [1. Modern Runtimes (Bun)](#1-modern-runtimes-bun)
40
- - [2. Standard Runtimes (Node.js)](#2-standard-runtimes-nodejs)
41
- - [Edge \& Serverless Ready](#edge--serverless-ready)
42
- - [Features](#features)
43
- - [Security Philosophy: Hermetic Validation](#security-philosophy-hermetic-validation)
44
- - [Why No Remote References?](#why-no-remote-references)
45
- - [No Code Generation](#no-code-generation)
46
- - [Error Handling](#error-handling)
47
- - [Adding Custom Types](#adding-custom-types)
48
- - [Method Signature](#method-signature)
49
- - [Example: Adding a Custom Type](#example-adding-a-custom-type)
50
- - [Adding Custom Formats](#adding-custom-formats)
51
- - [Method Signature](#method-signature-1)
52
- - [Example: Adding a Custom Format](#example-adding-a-custom-format)
53
- - [Adding Custom Keywords](#adding-custom-keywords)
54
- - [Method Signature](#method-signature-2)
55
- - [Example: Adding a Custom Keyword](#example-adding-a-custom-keyword)
56
- - [Complex example: Adding a Custom Keyword that uses the instance](#complex-example-adding-a-custom-keyword-that-uses-the-instance)
57
- - [Supported Formats](#supported-formats)
58
- - [Validating Runtime Objects](#validating-runtime-objects)
59
- - [More on Error Handling](#more-on-error-handling)
60
- - [ValidationError Properties](#validationerror-properties)
61
- - [Get the path to the error location](#get-the-path-to-the-error-location)
62
- - [Get the full error chain as a tree](#get-the-full-error-chain-as-a-tree)
63
- - [Get the cause of the error](#get-the-cause-of-the-error)
64
- - [Immutable Mode](#immutable-mode)
65
- - [TypeScript Support](#typescript-support)
66
- - [Known Limitations](#known-limitations)
67
- - [1. Dynamic ID Scope Resolution (Scope Alteration)](#1-dynamic-id-scope-resolution-scope-alteration)
68
- - [2. Unicode Length Validation](#2-unicode-length-validation)
69
- - [3. Conservative Equality Path (Performance Trade-off)](#3-conservative-equality-path-performance-trade-off)
60
+ - [Offline by Design](#offline-by-design)
61
+ - [Domain-Aware Validation](#domain-aware-validation)
62
+ - [Edge, Serverless, and Package Support](#edge-serverless-and-package-support)
63
+ - [Core API](#core-api)
64
+ - [Errors and Debugging](#errors-and-debugging)
65
+ - [Extensibility](#extensibility)
66
+ - [Supported Formats and Compatibility](#supported-formats-and-compatibility)
67
+ - [Immutable Mode and Known Limitations](#immutable-mode-and-known-limitations)
70
68
  - [Testing](#testing)
71
69
  - [Contribute](#contribute)
72
70
  - [Legal](#legal)
73
71
 
74
72
  ## Why SchemaShield?
75
73
 
76
- Most validators optimize for "operations per second" in synthetic benchmarks, often sacrificing security, stability, or maintainability. SchemaShield optimizes for the **Total Cost of Ownership** of your software.
74
+ SchemaShield gives developers competitive performance without surrendering control of the validation runtime.
77
75
 
78
- | Feature | JIT Compilers | SchemaShield | Why it matters |
79
- | :------------------- | :------------------------------------------------------------------- | :--------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
80
- | **Security Model** | **Reactive**<br>(Requires config to prevent Prototype Pollution/DoS) | **Preventive**<br>(Hermetic & Immutable by design) | "Secure by default" prevents human error and vulnerabilities in production. |
81
- | **Debug Experience** | **Black Box**<br>(Debugs opaque generated strings/code) | **White Box**<br>(Standard JS stack traces) | Drastically reduces time-to-fix when validation fails in complex logic. |
82
- | **Stability** | **Recursive**<br>(Risk of Stack Overflow on deep data) | **Flat Loop**<br>(Constant memory usage) | Protects your server availability against malicious deep-payload attacks. |
83
- | **Domain Logic** | **Disconnected**<br>(Hard to validate Class Instances/State) | **Integrated**<br>(Native `instanceof` & state validation) | Unifies DTO validation and Business Rules, eliminating "spaghetti code" in controllers. |
84
- | **Ecosystem** | **Fragmented**<br>(Requires plugins for errors/formats) | **Cohesive**<br>(Advanced error trees & formats built-in) | Reduces dependency fatigue and maintenance burden. |
76
+ JIT validators generate executable code to maximize throughput. SchemaShield uses an interpreter architecture that stays inside ordinary JavaScript execution, works in restrictive CSP environments, and keeps validation visible through standard stack traces and structured errors.
85
77
 
86
- > **The Trade-off:** While JIT compilers can be faster in raw throughput on V8 (Node.js), SchemaShield offers a balanced architecture where validation is never the bottleneck in real-world I/O bound applications (Database/Network APIs).
78
+ Choose SchemaShield when your application needs:
87
79
 
88
- ### Comparison with Other Approaches
80
+ - **Secure-by-design execution:** No `eval()`, `new Function()`, or validator-initiated network access.
81
+ - **Competitive throughput:** In the latest benchmark run, SchemaShield reached approximately 80% of AJV's throughput and ran approximately 15% faster than schemasafe.
82
+ - **Stack-safe validation:** Schemas beyond the fixed compile-depth limit are rejected, while recursive validation paths that exceed `maxDepth` fail with a controlled error instead of a stack overflow.
83
+ - **Domain-aware rules:** Validate class instances, Dates, application state, and custom business logic through the same API.
84
+ - **Inspectable failures:** Follow schema paths, instance paths, root causes, and complete error trees.
85
+ - **A cohesive runtime:** Built-in formats, custom types, custom keywords, immutable mode, and zero runtime dependencies.
89
86
 
90
- | Feature | SchemaShield | JIT Compilers | Classic Interpreters |
91
- | :---------------------------- | :-------------------------------------------------- | :------------------------------- | :-------------------- |
92
- | **Architecture** | **Secure Flat Interpreter** | JIT Compiler (eval/new Function) | Recursive Interpreter |
93
- | **Relative Speed** | **High (~70%)** | Reference (100%) | Low (1% - 20%) |
94
- | **CSP Compliance** | **Native (100% Safe)** | Requires Build Config | Variable |
95
- | **Edge Ready** | **Native** | Complex Setup | Variable |
96
- | **Stack Safety** | **Minimized stack usage (non-recursive core loop)** | Risk of Overflow | Risk of Overflow |
97
- | **Class Instance Validation** | **Native** | No | No |
98
- | **Debug Experience** | **Clean Stack Trace** | Opaque Generated Code | Variable |
87
+ ## Architecture Comparison
99
88
 
100
- > **Note:** Stack Safety refers to the risk of stack overflow errors when validating deeply nested data structures. SchemaShield's flat interpreter design minimizes this risk in its core validation loop. Keep in mind that custom keywords can still introduce recursion if not implemented carefully.
89
+ SchemaShield was designed for developers who want strong performance, predictable execution, and direct integration with real application domains.
101
90
 
102
- ## Usage
91
+ | Capability | SchemaShield | JIT / Codegen Validators | Conventional Interpreters |
92
+ | :------------------------------ | :-------------------------------------------------------------------- | :----------------------------------------- | :-------------------------------------- |
93
+ | **Runtime execution** | Interpreter without runtime code generation | Generated executable code | Interpreted validation |
94
+ | **`eval()` / `new Function()`** | Not used | Common to the codegen model | Usually not required |
95
+ | **Network behavior** | Offline with no implicit network access | Depends on library and configuration | Varies |
96
+ | **Stack behavior** | Controlled compile and runtime depth errors instead of stack overflow | Depends on generated execution | Recursive traversal is common |
97
+ | **Debugging** | Standard JavaScript stacks and structured error trees | Generated code can obscure validation flow | Varies |
98
+ | **Domain integration** | Class instances, runtime objects, and custom business rules | Extension model varies | Usually requires additional integration |
99
+ | **Extensibility** | Types, formats, keywords, and error tooling in one API | Frequently configuration or plugin driven | Varies |
103
100
 
104
- **1. Install the package**
101
+ ## Performance
105
102
 
106
- ```bash
107
- npm install schema-shield
108
- # or
109
- bun add schema-shield
110
- ```
103
+ SchemaShield delivers high-performance interpreted validation, offline execution, stack safety, standard JavaScript debugging, and domain-aware extensibility without runtime code generation.
111
104
 
112
- **2. Import the SchemaShield class**
105
+ In the latest benchmark run:
113
106
 
114
- ```javascript
115
- import { SchemaShield } from "schema-shield";
116
- // or
117
- const { SchemaShield } = require("schema-shield");
118
- ```
107
+ - SchemaShield reached approximately **80% of AJV's throughput**.
108
+ - SchemaShield ran approximately **15% faster than schemasafe**.
119
109
 
120
- **3. Instantiate the SchemaShield class**
110
+ These results position SchemaShield as a the second fastest interpreter.
121
111
 
122
- ```javascript
123
- const schemaShield = new SchemaShield();
124
- ```
112
+ > Benchmark results may vary by runtime, hardware, schema mix, and validator configuration. The benchmark results shown remain consistent across multiple runs on different environments.
125
113
 
126
- - **`immutable`** (optional): Set to `true` to ensure that input data remains unmodified during validation. Default is `false` for better performance.
127
- - **`failFast`** (optional): Set to `false` to receive detailed error objects on validation failure. Default is `true` for lightweight failure indication.
114
+ ## Offline by Design
128
115
 
129
- **3.5. Add custom types, keywords, and formats (optional)**
116
+ SchemaShield keeps validation inside your application's trust boundary.
130
117
 
131
- ```javascript
132
- schemaShield.addType("customType", (data) => {
133
- // Custom type validation logic
134
- });
118
+ SchemaShield itself performs no fetches, HTTP requests, DNS lookups, or other network I/O during schema compilation or validation. Validation behavior ships with your application and does not depend on a remote schema host remaining available or unchanged.
135
119
 
136
- schemaShield.addFormat("customFormat", (data) => {
137
- // Custom format validation logic
138
- });
120
+ ### No Remote References by Design
139
121
 
140
- schemaShield.addKeyword(
141
- "customKeyword",
142
- (schema, data, defineError, instance) => {
143
- // Custom keyword validation logic
144
- }
145
- );
146
- ```
122
+ Remote `$ref` fetching is intentionally excluded from SchemaShield.
147
123
 
148
- **4. Compile a schema**
124
+ A validator that retrieves schemas at runtime introduces a network-capable component into the validation path. That expands the attack surface, creates an SSRF vector, adds DNS and availability dependencies, and allows a remote schema change to alter validation behavior without an application deployment.
149
125
 
150
- ```javascript
151
- const schema = {
152
- type: "object",
153
- properties: {
154
- name: { type: "string" },
155
- age: { type: "number" }
156
- }
157
- };
126
+ SchemaShield removes that runtime dependency:
158
127
 
159
- const validator = schemaShield.compile(schema);
160
- ```
128
+ - No validator-initiated network requests
129
+ - No hidden schema downloads
130
+ - No DNS or remote-host dependency
131
+ - No remote schema substitution during validation
132
+ - No network latency inside the validation path
161
133
 
162
- **5. Validate some data**
134
+ > Treat external schemas as code dependencies. Review them, version them, and incorporate every required target into the single root schema passed to `compile()`. Deploy that controlled schema with the application.
163
135
 
164
- ```javascript
165
- const data = {
166
- name: "John Doe",
167
- age: 30
168
- };
136
+ ### No Runtime Code Generation
169
137
 
170
- const validationResult = validator(data);
138
+ SchemaShield does not use `eval()` or `new Function()` to generate executable validators. This keeps validation compatible with strict Content Security Policy environments and lets custom validation functions work directly with JavaScript values, classes, and references.
171
139
 
172
- if (validationResult.valid) {
173
- console.log("Data is valid:", validationResult.data);
174
- } else {
175
- console.error("Validation error:", validationResult.error);
176
- }
177
- ```
140
+ ```javascript
141
+ import { SchemaShield } from "schema-shield";
178
142
 
179
- **`validationResult`**: Contains the following properties:
143
+ const schemaShield = new SchemaShield();
180
144
 
181
- - `data`: The validated (and potentially modified) data.
182
- - `error`: A `ValidationError` instance if validation failed (when `failFast: false`), `true` if validation failed in fail-fast mode, otherwise `null`.
183
- - `valid`: true if validation was successful, otherwise false.
145
+ schemaShield.addType("date-class", (data) => data instanceof Date);
184
146
 
185
- > Note: When SchemaShield is instantiated with `{ failFast: false }`, `validationResult.error` will contain a detailed `ValidationError` instance if validation fails. In `failFast: true` mode, `error` is just `true` as a lightweight sentinel.
147
+ class CustomDate extends Date {}
148
+ schemaShield.addType("custom-date-class", (data) => data instanceof CustomDate);
186
149
 
187
- **6. Intelligent Defaults**
150
+ const validateDate = schemaShield.compile({
151
+ type: "object",
152
+ properties: {
153
+ createdAt: { type: "date-class" },
154
+ updatedAt: { type: "custom-date-class" }
155
+ },
156
+ required: ["createdAt", "updatedAt"]
157
+ });
188
158
 
189
- SchemaShield applies `default` values only when necessary to fulfill the schema contract. A `default` value is injected if and only if:
159
+ validateDate({
160
+ createdAt: new Date(),
161
+ updatedAt: new CustomDate()
162
+ });
163
+ // { valid: true, data: { createdAt: Date, updatedAt: CustomDate }, error: null }
164
+ ```
190
165
 
191
- 1. The property is missing in the input data.
192
- 2. The property is marked as `required` in the schema.
166
+ ## Domain-Aware Validation
193
167
 
194
- ## Performance
168
+ JSON Schema is commonly used for serialized JSON data. SchemaShield also validates live JavaScript objects, including class instances, Dates, and internal application state.
195
169
 
196
- SchemaShield is engineered with a **Flat Loop Interpreter** architecture. This design choice implies zero compilation overhead, making it exceptionally stable and significantly faster in modern runtimes.
170
+ ```javascript
171
+ import { SchemaShield } from "schema-shield";
197
172
 
198
- ### Understanding Performance Context
173
+ class Project {
174
+ constructor(name, requiredSkills) {
175
+ this.name = name;
176
+ this.requiredSkills = requiredSkills;
177
+ }
178
+ }
199
179
 
200
- In real-world applications, validation latency is often negligible compared to Network I/O (~20-100ms) or Database queries (~5-50ms).
180
+ class Employee {
181
+ constructor(name, skills) {
182
+ this.name = name;
183
+ this.skills = skills;
184
+ }
201
185
 
202
- SchemaShield is engineered to be **"Elite Fast" (Sufficiently Fast)**:
186
+ canJoin(project) {
187
+ return project.requiredSkills.every((skill) => this.skills.includes(skill));
188
+ }
189
+ }
190
+
191
+ const shield = new SchemaShield({ failFast: false });
203
192
 
204
- - **Zero Compilation Overhead:** Ideal for Serverless/Edge cold-starts.
205
- - **Predictable Throughput:** Consistent performance regardless of schema complexity.
193
+ shield.addType("project", (data) => data instanceof Project);
194
+ shield.addType("employee", (data) => data instanceof Employee);
206
195
 
207
- While JIT compilers may show higher numbers in micro-benchmarks on V8, SchemaShield processes thousands of requests per second—more than enough for high-traffic APIs—without the architectural risks of code generation.
196
+ shield.addKeyword("qualifiedAssignment", (schema, data, defineError) => {
197
+ if (!schema.qualifiedAssignment) {
198
+ return;
199
+ }
208
200
 
209
- ### 1. Modern Runtimes (Bun)
201
+ if (!(data?.project instanceof Project)) {
202
+ return;
203
+ }
210
204
 
211
- In runtimes using JavaScriptCore (like Bun), SchemaShield outperforms JIT compilers because it avoids the heavy cost of runtime code generation and optimization overhead.
205
+ if (!(data?.employee instanceof Employee)) {
206
+ return;
207
+ }
212
208
 
213
- | Validator | Relative Speed | Context |
214
- | :----------------- | :------------- | :----------- |
215
- | **SchemaShield** | **100%** | **Fastest** |
216
- | ajv | ~40% | JIT Compiler |
217
- | @exodus/schemasafe | ~24% | Interpreter |
218
- | jsonschema | ~2% | Legacy |
209
+ if (!data.employee.canJoin(data.project)) {
210
+ return defineError("Employee does not meet the project's requirements", {
211
+ data
212
+ });
213
+ }
214
+ });
219
215
 
220
- ### 2. Standard Runtimes (Node.js)
216
+ const validateAssignment = shield.compile({
217
+ type: "object",
218
+ properties: {
219
+ project: { type: "project" },
220
+ employee: { type: "employee" }
221
+ },
222
+ required: ["project", "employee"],
223
+ qualifiedAssignment: true
224
+ });
221
225
 
222
- In V8-based environments (Node.js), SchemaShield maintains elite performance for a secure interpreter, being roughly **70x faster** than legacy libraries.
226
+ const project = new Project("Website Redesign", ["HTML", "CSS", "JavaScript"]);
227
+ const employee = new Employee("Ada", ["HTML", "CSS"]);
223
228
 
224
- | Validator | Relative Speed | Context |
225
- | :----------------- | :------------- | :------------------ |
226
- | ajv | 100% | Reference (JIT) |
227
- | @exodus/schemasafe | ~74% | Interpreter |
228
- | **SchemaShield** | **~70%** | **Secure Standard** |
229
- | jsonschema | ~1% | Legacy |
229
+ const result = validateAssignment({ project, employee });
230
+ // { valid: false, data: { project, employee }, error: ValidationError }
231
+ ```
230
232
 
231
- **Key Takeaway:** SchemaShield delivers consistent high performance in Node.js without the security risks, memory leaks, or "cold start" latency associated with code generation.
233
+ This keeps transport validation and domain rules in one validation system without converting live application objects into generated source code.
232
234
 
233
- ## Edge & Serverless Ready
235
+ ## Edge, Serverless, and Package Support
234
236
 
235
- SchemaShield is designed to run seamlessly in restrictive environments like **Cloudflare Workers**, **Vercel Edge Functions**, **Deno Deploy**, and **Bun**.
237
+ SchemaShield is designed for restrictive JavaScript environments and reusable validators.
236
238
 
237
- - **Zero Dependencies:** No strict reliance on Node.js built-ins.
238
- - **CSP Compliant:** Works in environments where `eval()` and `new Function()` are banned for security.
239
- - **Instant Startup:** No compilation overhead, minimizing "cold start" latency in Serverless functions.
239
+ - **Zero runtime dependencies**
240
+ - **Node.js 16.1 or later** for the CJS and ESM package entry points
241
+ - **No runtime code generation**, for environments that prohibit `eval()` and `new Function()`
242
+ - **Synchronous execution**, with no implicit network access
243
+ - **Verified package paths** for CJS, ESM, browser, and TypeScript declarations
244
+ - **Reusable validators** that can be compiled once and shared across requests
240
245
 
241
246
  ### Serverless Example
242
247
 
248
+ Compile validators at module scope and reuse them across invocations:
249
+
243
250
  ```javascript
244
- // Compile at module level - runs once at cold start
245
- const validateRequest = new SchemaShield().compile({
251
+ import { SchemaShield } from "schema-shield";
252
+
253
+ const validateRequest = new SchemaShield({ failFast: false }).compile({
246
254
  type: "object",
247
255
  properties: {
248
256
  name: { type: "string" },
@@ -251,403 +259,279 @@ const validateRequest = new SchemaShield().compile({
251
259
  required: ["name", "email"]
252
260
  });
253
261
 
254
- // Vercel Edge / Cloudflare Worker handler
255
- export default function handler(request) {
256
- const result = validateRequest(request.body);
257
-
262
+ export default async function handler(request) {
263
+ const body = await request.json();
264
+ const result = validateRequest(body);
265
+
258
266
  if (!result.valid) {
259
267
  return new Response(JSON.stringify({ error: result.error.message }), {
260
268
  status: 400,
261
269
  headers: { "Content-Type": "application/json" }
262
270
  });
263
271
  }
264
-
265
- return new Response(JSON.stringify(result.data), { status: 200 });
272
+
273
+ return new Response(JSON.stringify(result.data), {
274
+ status: 200,
275
+ headers: { "Content-Type": "application/json" }
276
+ });
266
277
  }
267
278
  ```
268
279
 
269
- ### Sharing Compiled Validators
280
+ ## Core API
270
281
 
271
- Compiled validators are safe to share across requests:
282
+ ### Import
272
283
 
273
284
  ```javascript
274
- // Compile once at startup
275
- const validateUser = new SchemaShield().compile(userSchema);
276
-
277
- // Reuse across requests - no mutable state
278
- app.get("/user/:id", (req) => {
279
- const result = validateUser(req.body);
280
- // ...
281
- });
285
+ import { SchemaShield } from "schema-shield";
282
286
  ```
283
287
 
284
- ## Interpreter Architecture
285
-
286
- Unlike JIT compilers that generate code at runtime, SchemaShield uses a flat-loop interpreter:
287
-
288
- 1. **Compilation:** Schema is compiled into a tree of validator functions at compile-time
289
- 2. **Reference Resolution:** $ref links are resolved once during compilation
290
- 3. **Validation:** Data flows through the validator tree in a flat loop (no recursion)
291
-
292
- ### Performance Characteristics
293
-
294
- | Schema Type | Performance | Notes |
295
- |-------------|-------------|-------|
296
- | Simple (type, properties) | Very fast | ~same as JIT |
297
- | allOf/anyOf/oneOf | ~70% JIT | Sequential branch evaluation |
298
- | Deep nesting | Stack-safe | Constant memory, no recursion |
299
- | Many $refs | Fast | Resolved at compile-time |
300
-
301
- ### Performance Optimization Tips
288
+ CommonJS is also supported:
302
289
 
303
290
  ```javascript
304
- // Best: Flat structure
305
- const fastSchema = {
306
- type: "object",
307
- properties: {
308
- name: { type: "string" },
309
- email: { type: "string", format: "email" }
310
- }
311
- };
312
-
313
- // Slower: Deeply nested allOf/anyOf
314
- const slowSchema = {
315
- allOf: [
316
- { allOf: [{ allOf: [...] }]}
317
- ]
318
- };
319
-
320
- // Better: Flatten your schema
321
- const betterSchema = {
322
- type: "object",
323
- properties: { /* ... */ }
324
- };
291
+ const { SchemaShield } = require("schema-shield");
325
292
  ```
326
293
 
327
- ## Features
328
-
329
- - Supports draft-06 and draft-07 of the [JSON Schema](https://json-schema.org/) specification.
330
- - Full support for internal references ($ref) and anchors ($id).
331
- - No Code Generation for Enhanced Safety and Validation Flexibility.
332
- - Custom type, keyword, and format validators.
333
- - Runtime object validation: first-class support for business logic checks (type checking and schema validation).
334
- - Immutable mode for data protection.
335
- - Lightweight and fast.
336
- - Easy to use and extend.
337
- - No dependencies.
338
- - TypeScript support.
339
-
340
- ## Security Philosophy: Hermetic Validation
341
-
342
- SchemaShield adopts a **Zero Trust** and **Hermetic Architecture** approach. Unlike validators that allow runtime network access, SchemaShield is strictly synchronous and offline by design.
343
-
344
- ### Why No Remote References?
345
-
346
- Allowing a validator to fetch schemas from remote URLs (`$ref: "https://..."`) at runtime introduces critical security vectors and stability issues:
347
-
348
- 1. **SSRF (Server-Side Request Forgery):** Prevents attackers from manipulating schemas to force internal network scanning or access metadata services.
349
- 2. **Supply Chain Attacks:** Eliminates the risk of a remote schema being silently compromised, which could alter validation logic without code deployment.
350
- 3. **Deterministic Reliability:** Validation never fails due to network latency, DNS issues, or third-party server downtime.
351
-
352
- **Recommendation:** Treat schemas as **code dependencies**, not dynamic assets. Download and bundle remote schemas locally during your build process to ensure immutable, versioned, and audit-ready validation.
353
-
354
- ## No Code Generation
355
-
356
- Unlike some other validation libraries that rely on code generation to achieve fast performance, SchemaShield does not use code generation.
357
-
358
- This design decision ensures that you can safely pass real references to objects, classes, or variables in your custom validation functions without any unintended side effects or security concerns.
359
-
360
- For example, you can easily use `instanceof` to check if the provided data is an instance of a particular class or a subclass:
294
+ ### Create an Instance
361
295
 
362
296
  ```javascript
363
- schemaShield.addType("date-class", (data) => data instanceof Date);
364
- // or use your custom classes, functions or references
365
- class CustomDate extends Date {}
366
- schemaShield.addType("custom-date-class", (data) => data instanceof CustomDate);
297
+ const schemaShield = new SchemaShield({
298
+ immutable: false,
299
+ failFast: true,
300
+ maxDepth: 128,
301
+ useDefaults: false
302
+ });
367
303
  ```
368
304
 
369
- ## Security Implementation Examples
305
+ | Option | Default | Behavior |
306
+ | :------------ | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
307
+ | `immutable` | `false` | Set to `true` to preserve the original input when defaults or custom keywords could modify data. |
308
+ | `failFast` | `true` | Returns `error: true` on failure. With `false`, built-ins and custom failures created with `defineError()` return a detailed `ValidationError`. A custom keyword that returns `true` directly still returns `error: true`. |
309
+ | `maxDepth` | `128` | Sets the maximum recursive validation depth. Accepts an integer from `1` to `256`; validation paths that exceed it fail with a controlled error. |
310
+ | `useDefaults` | `false` | Leaves `default` as a JSON Schema annotation. Use `true` to replace absent or `undefined` properties, or `"empty"` to also replace `null` and empty strings. |
370
311
 
371
- ### CSP-Compliant Validation
312
+ Schema compilation applies its own depth limit before a validator is created. This compile-time protection is separate from the runtime `maxDepth` option.
372
313
 
373
- SchemaShield works in strict CSP environments because it uses no code generation:
314
+ ### Compile and Validate
374
315
 
375
316
  ```javascript
376
- // No eval(), no new Function() - CSP-safe
377
- const validator = new SchemaShield().compile({
317
+ const schema = {
378
318
  type: "object",
379
319
  properties: {
380
- name: { type: "string" }
381
- }
382
- });
320
+ name: { type: "string" },
321
+ age: { type: "number" }
322
+ },
323
+ required: ["name"]
324
+ };
383
325
 
384
- // This works in strict CSP policies
385
- const result = validator({ name: "John" });
326
+ const validator = schemaShield.compile(schema);
327
+ const result = validator({ name: "Ada", age: 37 });
386
328
  ```
387
329
 
388
- ### Preventing Prototype Pollution
389
-
390
- SchemaShield prevents prototype pollution by design:
330
+ Every result contains:
391
331
 
392
- ```javascript
393
- const schema = {
394
- type: "object",
395
- properties: {
396
- name: { type: "string" }
397
- },
398
- additionalProperties: false
399
- };
332
+ - `valid`: `true` when validation succeeds, otherwise `false`.
333
+ - `data`: The validated data, including any applied defaults.
334
+ - `error`: `null` on success, `true` on a fail-fast failure or when a custom keyword returns `true` directly, or a `ValidationError` for built-in failures and custom failures created with `defineError()` when `failFast: false`.
400
335
 
401
- const validator = new SchemaShield().compile(schema);
336
+ ### Defaults
402
337
 
403
- // Malicious input {"__proto__": {"evil": "value"}} will fail
404
- validator({ "__proto__": { "evil": "value" } });
405
- // { valid: false, error: ValidationError }
338
+ SchemaShield keeps data shaping explicit. By default, `default` remains a standard JSON Schema annotation and your input stays unchanged.
406
339
 
407
- // Only explicitly defined properties are allowed
408
- validator({ name: "John", age: 30 });
409
- // { valid: false, error: ValidationError (additionalProperties: false) }
410
- ```
340
+ Enable `useDefaults` when you want validated data to apply defaults and leave the validator ready to use:
411
341
 
412
- ### Immutable Mode
342
+ | `useDefaults` | Behavior |
343
+ | :----------------- | :------------------------------------------------------------------------------------ |
344
+ | Omitted or `false` | Keeps defaults as annotations and does not modify data. |
345
+ | `true` | Completes missing required and optional properties. |
346
+ | `"empty"` | Also replaces `null` and `""`, while preserving valid values such as `0` and `false`. |
413
347
 
414
- Protect input data from accidental modification:
348
+ Combine `useDefaults` with `immutable: true` to receive completed data without modifying the original object:
415
349
 
416
350
  ```javascript
417
- const shield = new SchemaShield({ immutable: true });
418
- const validator = shield.compile({
351
+ const validateUser = new SchemaShield({
352
+ useDefaults: true,
353
+ immutable: true
354
+ }).compile({
419
355
  type: "object",
420
356
  properties: {
421
- name: { type: "string" }
422
- }
357
+ role: { type: "string", default: "member" },
358
+ theme: { type: "string", default: "system" }
359
+ },
360
+ required: ["role"]
423
361
  });
424
362
 
425
- const input = { name: "John" };
426
- const result = validator(input);
363
+ const input = {};
364
+ const result = validateUser(input);
427
365
 
428
- // Input remains unchanged
429
- console.log(input);
430
- // { name: "John" }
431
- ```
366
+ input;
367
+ // {}
432
368
 
433
- ## Error Handling
434
-
435
- SchemaShield provides comprehensive error handling for schema validation. When a validation error occurs:
369
+ result.data;
370
+ // { role: "member", theme: "system" }
371
+ ```
436
372
 
437
- - If the instance was created with `{ failFast: true }` (the default), the `error` property will be `true` as a lightweight sentinel indicating that validation failed.
438
- - If the instance was created with `{ failFast: false }`, the `error` property will contain a `ValidationError` instance with rich debugging information.
373
+ ## Errors and Debugging
439
374
 
440
- This error object has the `getPath()` method, which is particularly useful for quickly identifying the location of an error in both the schema and the data.
375
+ SchemaShield provides two error modes:
441
376
 
442
- **Example:**
377
+ - `failFast: true` returns `error: true` with minimal allocation.
378
+ - `failFast: false` returns a `ValidationError` with rich debugging information for built-in failures and custom keyword failures created with `defineError()`. A custom keyword that returns `true` directly still produces `error: true`. Use `defineError()` for detailed errors.
443
379
 
444
380
  ```javascript
445
381
  import { SchemaShield } from "schema-shield";
446
382
 
447
- const schemaShield = new SchemaShield({ failFast: false });
448
-
449
- const schema = {
383
+ const validatePerson = new SchemaShield({ failFast: false }).compile({
450
384
  type: "object",
451
385
  properties: {
452
- name: { type: "string" },
453
- age: {
454
- type: "number",
455
- minimum: 18
456
- }
457
- }
458
- };
459
-
460
- const validator = schemaShield.compile(schema);
461
-
462
- const invalidData = {
463
- name: "John Doe",
464
- age: 15
465
- };
386
+ age: { type: "number", minimum: 18 }
387
+ },
388
+ required: ["age"]
389
+ });
466
390
 
467
- const validationResult = validator(invalidData);
391
+ const result = validatePerson({ age: 15 });
468
392
 
469
- if (validationResult.valid) {
470
- console.log("Data is valid:", validationResult.data);
471
- } else {
472
- console.error("Validation error:", validationResult.error.message); // "Property is invalid"
393
+ if (!result.valid) {
394
+ const paths = result.error.getPath();
473
395
 
474
- // Get the paths to the error location in the schema and in the data
475
- const errorPaths = validationResult.error.getPath();
476
- console.error("Schema path:", errorPaths.schemaPath); // "#/properties/age/minimum"
477
- console.error("Instance path:", errorPaths.instancePath); // "#/age"
396
+ console.error(result.error.message);
397
+ console.error(paths.schemaPath);
398
+ console.error(paths.instancePath);
478
399
  }
479
400
  ```
480
401
 
481
- For more advanced error handling and a detailed explanation of the ValidationError properties and methods, refer to the [More on Error Handling](#more-on-error-handling) section.
482
-
483
- ## Adding Custom Types
484
-
485
- SchemaShield allows you to add custom types for validation using the `addType` method.
486
-
487
- ### Method Signature
402
+ ### ValidationError Properties
488
403
 
489
- ```javascript
490
- interface TypeFunction {
491
- (data: any): boolean;
492
- }
404
+ - `message`: Description of the validation error.
405
+ - `item`: Final item in the failing path, when available.
406
+ - `keyword`: Keyword that triggered the error.
407
+ - `cause`: Nested `ValidationError` that caused the current error.
408
+ - `code`: Stable error code, when the error provides one.
409
+ - `schemaPath`: JSON Pointer to the failing schema location.
410
+ - `instancePath`: JSON Pointer to the failing data location.
411
+ - `data`: Data that caused the error, when available.
412
+ - `schema`: Schema value that caused the error, when available.
493
413
 
494
- addType(name: string, validator: TypeFunction, overwrite?: boolean): void;
495
- ```
414
+ `schemaPath` and `instancePath` become available after calling `getCause()`, `getTree()`, or `getPath()`.
496
415
 
497
- - `name`: The name of the custom type. This should be a unique string that does not conflict with existing types.
498
- - `validator`: A `TypeFunction` that takes a single argument `data` and returns a boolean value. The function should return `true` if the provided data is valid for the custom type, and `false` otherwise.
499
- - `overwrite` (optional): Set to `true` to overwrite an existing type with the same name. Default is `false`. If set to `false` and a type with the same name already exists, an error will be thrown.
416
+ ### Error Methods
500
417
 
501
- ### Example: Adding a Custom Type
418
+ #### `getPath()`
502
419
 
503
- In this example, we'll add a custom type called age that validates if a given number is between 18 and 120.
420
+ Returns the schema and instance paths for the root validation failure.
504
421
 
505
422
  ```javascript
506
- import { SchemaShield } from "schema-shield";
507
-
508
- const schemaShield = new SchemaShield({ failFast: false });
423
+ const { schemaPath, instancePath } = result.error.getPath();
424
+ ```
509
425
 
510
- // Custom type 'age' validator function
511
- const ageValidator = (data) => {
512
- return typeof data === "number" && data >= 18 && data <= 120;
513
- };
426
+ #### `getCause()`
514
427
 
515
- // Adding the custom type 'age'
516
- schemaShield.addType("age", ageValidator);
428
+ Returns the root `ValidationError` that caused the validation chain.
517
429
 
518
- const schema = {
519
- type: "object",
520
- properties: {
521
- name: { type: "string" },
522
- age: { type: "age" }
523
- }
524
- };
525
-
526
- const validator = schemaShield.compile(schema);
430
+ ```javascript
431
+ const cause = result.error.getCause();
432
+ console.error(cause.message, cause.keyword);
433
+ ```
527
434
 
528
- const validData = {
529
- name: "John Doe",
530
- age: 25
531
- };
435
+ #### `getTree()`
532
436
 
533
- const validationResult = validator(validData);
437
+ Returns the complete nested error chain as an error tree.
534
438
 
535
- if (validationResult.valid) {
536
- console.log("Data is valid:", validationResult.data);
537
- } else {
538
- console.error("Validation error:", validationResult.error.getCause().message);
439
+ ```typescript
440
+ interface ErrorTree {
441
+ message: string;
442
+ keyword: string;
443
+ item?: string | number;
444
+ schemaPath: string;
445
+ instancePath: string;
446
+ data?: any;
447
+ cause?: ErrorTree;
539
448
  }
540
449
  ```
541
450
 
542
- ## Adding Custom Formats
451
+ ## Extensibility
543
452
 
544
- SchemaShield allows you to add custom formats for validation using the `addFormat` method.
453
+ SchemaShield exposes custom types, formats, and keywords through the same instance used to compile validators.
545
454
 
546
- ### Method Signature
455
+ The examples in this section use the following instance:
547
456
 
548
457
  ```javascript
549
- interface FormatFunction {
550
- (data: any): boolean;
551
- }
458
+ import { SchemaShield } from "schema-shield";
552
459
 
553
- addFormat(name: string, validator: FormatFunction, overwrite?: boolean): void;
460
+ const schemaShield = new SchemaShield();
554
461
  ```
555
462
 
556
- - `name`: The name of the custom format. This should be a unique string that does not conflict with existing formats.
557
- - `validator`: A FormatFunction that takes a single argument data and returns a boolean value. The function should return true if the provided data is valid for the custom format, and false otherwise.
558
- - `overwrite` (optional): Set to true to overwrite an existing format with the same name. Default is false. If set to false and a format with the same name already exists, an error will be thrown.
463
+ ### Custom Types
559
464
 
560
- ### Example: Adding a Custom Format
465
+ ```typescript
466
+ interface TypeFunction {
467
+ (data: any): boolean;
468
+ }
561
469
 
562
- In this example, we'll add a custom format called ssn that validates if a given string is a valid U.S. Social Security Number (SSN).
470
+ interface SchemaShieldTypeAPI {
471
+ addType(name: string, validator: TypeFunction, overwrite?: boolean): void;
472
+ }
473
+ ```
563
474
 
564
475
  ```javascript
565
- import { SchemaShield } from "schema-shield";
566
-
567
- const schemaShield = new SchemaShield({ failFast: false });
476
+ schemaShield.addType(
477
+ "adult-age",
478
+ (data) => typeof data === "number" && data >= 18
479
+ );
568
480
 
569
- // Custom format 'ssn' validator function
570
- const ssnValidator = (data) => {
571
- const ssnPattern = /^(?!000|.+0{4})(?:\d{9}|\d{3}-\d{2}-\d{4})$/;
572
- return typeof data === "string" && ssnPattern.test(data);
573
- };
481
+ const validator = schemaShield.compile({ type: "adult-age" });
482
+ ```
574
483
 
575
- // Adding the custom format 'ssn'
576
- schemaShield.addFormat("ssn", ssnValidator);
484
+ Set `overwrite` to `true` to replace an existing type with the same name. The default is `false`.
577
485
 
578
- const schema = {
579
- type: "object",
580
- properties: {
581
- name: { type: "string" },
582
- ssn: { type: "string", format: "ssn" }
583
- }
584
- };
486
+ ### Custom Formats
585
487
 
586
- const validator = schemaShield.compile(schema);
488
+ ```typescript
489
+ interface FormatFunction {
490
+ (data: any): boolean;
491
+ }
587
492
 
588
- const validData = {
589
- name: "John Doe",
590
- ssn: "123-45-6789"
591
- };
493
+ interface SchemaShieldFormatAPI {
494
+ addFormat(name: string, validator: FormatFunction, overwrite?: boolean): void;
495
+ }
496
+ ```
592
497
 
593
- const validationResult = validator(validData);
498
+ ```javascript
499
+ schemaShield.addFormat(
500
+ "username",
501
+ (data) => typeof data === "string" && /^[a-z0-9._-]{3,}$/i.test(data)
502
+ );
594
503
 
595
- if (validationResult.valid) {
596
- console.log("Data is valid:", validationResult.data);
597
- } else {
598
- console.error("Validation error:", validationResult.error.getCause().message);
599
- }
504
+ const validator = schemaShield.compile({
505
+ type: "string",
506
+ format: "username"
507
+ });
600
508
  ```
601
509
 
602
- ## Adding Custom Keywords
510
+ Set `overwrite` to `true` to replace an existing format with the same name. The default is `false`.
603
511
 
604
- SchemaShield allows you to add custom keywords for validation using the `addKeyword` method. This is the most powerful method for adding custom validation logic to SchemaShield because it allows you to interact with the entire schema and data being validated at the level of the keyword.
512
+ ### Custom Keywords
605
513
 
606
- ### Method Signature
514
+ ```typescript
515
+ import type {
516
+ CompiledSchema,
517
+ SchemaShield,
518
+ ValidationError
519
+ } from "schema-shield";
607
520
 
608
- ```javascript
609
521
  type Result = void | ValidationError | true;
610
522
 
611
523
  interface DefineErrorOptions {
612
- item?: any; // Final item in the path
613
- cause?: ValidationError | true; // Cause of the error (or true in failFast mode)
614
- data?: any; // Data that caused the error
524
+ code?: string;
525
+ item?: any;
526
+ cause?: ValidationError | true;
527
+ data?: any;
615
528
  }
616
529
 
617
530
  interface DefineErrorFunction {
618
- (message: string, options?: DefineErrorOptions): ValidationError | true;
619
- }
620
-
621
- interface ValidateFunction {
622
- (data: any): Result;
623
- }
624
-
625
- interface CompiledSchema {
626
- $validate?: ValidateFunction;
627
- [key: string]: any;
628
- }
629
-
630
- interface FormatFunction {
631
- (data: any): boolean;
632
- }
633
-
634
- interface TypeFunction {
635
- (data: any): boolean;
636
- }
637
-
638
- declare class SchemaShield {
639
- constructor(options?: {
640
- immutable?: boolean;
641
- failFast?: boolean;
642
- });
643
- compile(schema: any): Validator;
644
- addType(name: string, validator: TypeFunction, overwrite?: boolean): void;
645
- addFormat(name: string, validator: FormatFunction, overwrite?: boolean): void;
646
- addKeyword(name: string, validator: KeywordFunction, overwrite?: boolean): void;
647
- getType(type: string): TypeFunction | false;
648
- getFormat(format: string): FormatFunction | false;
649
- getKeyword(keyword: string): KeywordFunction | false;
650
- isSchemaLike(subSchema: any): boolean;
531
+ (
532
+ message: string,
533
+ options?: DefineErrorOptions
534
+ ): ValidationError | void | true;
651
535
  }
652
536
 
653
537
  interface KeywordFunction {
@@ -659,46 +543,21 @@ interface KeywordFunction {
659
543
  ): Result;
660
544
  }
661
545
 
662
-
663
-
664
- addKeyword(name: string, validator: KeywordFunction, overwrite?: boolean): void;
546
+ interface SchemaShieldKeywordAPI {
547
+ addKeyword(
548
+ name: string,
549
+ validator: KeywordFunction,
550
+ overwrite?: boolean
551
+ ): void;
552
+ }
665
553
  ```
666
554
 
667
- - `name`: The name of the custom keyword. This should be a unique string that does not conflict with existing keywords.
668
- - `validator`: A `KeywordFunction` that takes four arguments: `schema`, `data`, `defineError`, and `instance` (The SchemaShield instance that is currently running the validation). The function should not return anything if the data is valid for the custom keyword, and should return a `ValidationError` instance if the data is invalid when `failFast` is `false`, or `true` when `failFast` is `true`.
669
- - `overwrite` (optional): Set to true to overwrite an existing keyword with the same name. Default is false. If set to false and a keyword with the same name already exists, an error will be thrown.
670
-
671
- #### About the `defineError` Function
672
-
673
- Take into account that the error must be generated using the `defineError` function because the error returned by this method has the required data relevant for the current keyword (`schema`, `keyword`, `getCause` method).
674
-
675
- - `message`: A string that describes the validation error.
676
- - `options`: An optional object with properties that provide more context for the error:
677
- - `item`?: An optional value representing the final item in the path where the validation error occurred. (e.g. index of an array item)
678
- - `cause`?: An optional `ValidationError` (or `true` in fail-fast mode) that represents the cause of the current error.
679
- - `data`?: An optional value representing the data that caused the validation error.
680
-
681
- When the SchemaShield instance is created with `failFast: true`, `defineError` returns `true` instead of a `ValidationError`, and keyword implementations should simply `return` whatever `defineError` gives them.
682
-
683
- #### About the `instance` Argument
684
-
685
- The `instance` argument is the SchemaShield instance that is currently running the validation. This can be used to access to other `types`, `keywords` or `formats` that have been added to the instance.
686
-
687
- ### Example: Adding a Custom Keyword
688
-
689
- In this example, we'll add a custom keyword called divisibleBy that validates if a given number is divisible by a specified divisor.
555
+ Use `defineError()` so SchemaShield can attach the current keyword, schema, data, and error-chain context:
690
556
 
691
557
  ```javascript
692
- import { SchemaShield, ValidationError } from "schema-shield";
693
-
694
- const schemaShield = new SchemaShield({ failFast: false });
695
-
696
- // Custom keyword 'divisibleBy' validator function
697
- const divisibleByValidator = (schema, data, defineError, instance) => {
558
+ schemaShield.addKeyword("divisibleBy", (schema, data, defineError) => {
698
559
  if (typeof data !== "number") {
699
- return defineError("Value must be a number", {
700
- data
701
- });
560
+ return defineError("Value must be a number", { data });
702
561
  }
703
562
 
704
563
  if (data % schema.divisibleBy !== 0) {
@@ -706,603 +565,94 @@ const divisibleByValidator = (schema, data, defineError, instance) => {
706
565
  data
707
566
  });
708
567
  }
709
- };
710
-
711
- // Adding the custom keyword 'divisibleBy'
712
- schemaShield.addKeyword("divisibleBy", divisibleByValidator);
713
-
714
- const schema = {
715
- type: "object",
716
- properties: {
717
- value: { type: "number", divisibleBy: 5 }
718
- }
719
- };
720
-
721
- const validator = schemaShield.compile(schema);
722
-
723
- const validData = {
724
- value: 15
725
- };
726
-
727
- const validationResult = validator(validData);
728
-
729
- if (validationResult.valid) {
730
- console.log("Data is valid:", validationResult.data);
731
- } else {
732
- console.error("Validation error:", validationResult.error.getCause().message);
733
- }
734
- ```
735
-
736
- ### Complex example: Adding a Custom Keyword that uses the instance
737
-
738
- In this example we'll add a custom keyword called `prefixedUsername` that will validate if a given string is a valid username and has a specific prefix. This will only work if the additional validation methods and types have been added to the instance.
739
-
740
- ```javascript
741
- import { SchemaShield, ValidationError } from "schema-shield";
742
-
743
- const schemaShield = new SchemaShield({ failFast: false });
744
-
745
- // Custom type validator: nonEmptyString
746
- const nonEmptyStringValidator = (data) =>
747
- typeof data === "string" && data.length > 0;
748
- schemaShield.addType("nonEmptyString", nonEmptyStringValidator);
749
-
750
- // Custom keyword validator: hasPrefix
751
- const hasPrefixValidator = (schema, data, defineError, instance) => {
752
- const { prefix } = schema.hasPrefix;
753
- if (typeof data === "string" && !data.startsWith(prefix)) {
754
- return defineError(`String must have the prefix "${prefix}"`, {
755
- data
756
- });
757
- }
758
- };
759
- schemaShield.addKeyword("hasPrefix", hasPrefixValidator);
760
-
761
- // Custom format validator: username
762
- const usernameValidator = (data) => /^[a-zA-Z0-9._-]{3,}$/i.test(data);
763
- schemaShield.addFormat("username", usernameValidator);
764
-
765
- // Custom keyword 'prefixedUsername' validator function
766
- const prefixedUsername = (schema, data, defineError, instance) => {
767
- const { validType, prefixValidator, validFormat } = schema.prefixedUsername;
768
-
769
- // Get the validators for the specified types and formats from the instance
770
- // (if they exist)
771
- const typeValidator = instance.getType(validType);
772
- const prefixKeyword = instance.getKeyword(prefixValidator);
773
- const formatValidator = instance.getFormat(validFormat);
774
-
775
- for (let i = 0; i < data.length; i++) {
776
- const item = data[i];
777
-
778
- // Validate that the data is of the correct type if specified
779
- if (validType && typeValidator) {
780
- if (!typeValidator(item)) {
781
- return defineError(`Invalid type: ${validType}`, {
782
- item: i,
783
- data: data[i]
784
- });
785
- }
786
- }
787
-
788
- // Validate that the data has the correct format if specified
789
- if (validFormat && formatValidator) {
790
- if (!formatValidator(item)) {
791
- return defineError(`Invalid format: ${validFormat}`, {
792
- item: i,
793
- data: data[i]
794
- });
795
- }
796
- }
797
-
798
- // Validate that the data has the correct prefix if specified
799
- if (prefixKeyword) {
800
- const error = prefixKeyword(schema, item, defineError, instance);
801
- if (error) {
802
- return defineError(`Invalid prefix: ${prefixValidator}`, {
803
- cause: error,
804
- item: i,
805
- data: data[i]
806
- });
807
- }
808
- }
809
- }
810
- };
811
-
812
- schemaShield.addKeyword("prefixedUsername", prefixedUsername);
813
-
814
- const schema = {
815
- type: "array",
816
- prefixedUsername: {
817
- validType: "nonEmptyString",
818
- prefixValidator: "hasPrefix",
819
- validFormat: "username"
820
- },
821
- items: {
822
- type: "string"
823
- }
824
- };
825
-
826
- const validator = schemaShield.compile(schema);
827
-
828
- const validData = ["user.john", "user.jane"];
829
-
830
- const validationResult = validator(validData);
831
-
832
- if (validationResult.valid) {
833
- console.log("Data is valid:", validationResult.data);
834
- } else {
835
- console.error("Validation error:", validationResult.error.getCause().message);
836
- }
837
- ```
838
-
839
- ## Supported Formats
840
-
841
- SchemaShield includes built-in validators for the following formats:
842
-
843
- - **Date & Time:** `date`, `time`, `date-time`, `duration`.
844
- - **Email:** `email`, `idn-email`.
845
- - **Hostnames:** `hostname`, `idn-hostname`.
846
- - **IP Addresses:** `ipv4`, `ipv6`.
847
- - **Resource Identifiers:** `uuid`, `uri`, `uri-reference`, `uri-template`, `iri`, `iri-reference`.
848
- - **JSON Pointers:** `json-pointer`, `relative-json-pointer`.
849
- - **Regex:** `regex`.
850
-
851
- You can override any of these or add new ones using `schemaShield.addFormat`.
852
-
853
- ## Validating Runtime Objects
854
-
855
- JSON Schema is traditionally for serialized JSON text. SchemaShield extends this concept to **JavaScript Objects**. It allows validation of class instances, Dates, and internal application state directly.
856
-
857
- For example, imagine you have a custom class representing a project and another representing an employee. You could create a custom validator to ensure that only employees with the right qualifications are assigned to a specific project:
858
-
859
- ```javascript
860
- import { SchemaShield, ValidationError } from "schema-shield";
861
-
862
- const schemaShield = new SchemaShield({ failFast: false });
863
-
864
- // Custom classes
865
- class Project {
866
- constructor(name: string, requiredSkills: string[]) {
867
- this.name = name;
868
- this.requiredSkills = requiredSkills;
869
- }
870
- }
871
-
872
- class Employee {
873
- constructor(name: string, skills: string[]) {
874
- this.name = name;
875
- this.skills = skills;
876
- }
877
-
878
- hasSkillsForProject(project: Project) {
879
- return project.requiredSkills.every((skill) => this.skills.includes(skill));
880
- }
881
- }
882
-
883
- // Add custom types to the instance
884
- schemaShield.addType("project", (data) => data instanceof Project);
885
- schemaShield.addType("employee", (data) => data instanceof Employee);
886
-
887
- schemaShield.addKeyword(
888
- "requiresQualifiedEmployee",
889
- (schema, data, defineError, instance) => {
890
- const { assignment, project, employee } = data;
891
-
892
- const stringTypeValidator = instance.getType("string");
893
- const projectTypeValidator = instance.getType("project");
894
- const employeeTypeValidator = instance.getType("employee");
895
-
896
- if (!stringTypeValidator(assignment)) {
897
- return defineError("Assignment must be a string", {
898
- item: "assignment",
899
- data: assignment
900
- });
901
- }
902
-
903
- if (!projectTypeValidator(project)) {
904
- return defineError("Project must be a Project instance", {
905
- item: "project",
906
- data: project
907
- });
908
- }
909
-
910
- if (!employeeTypeValidator(employee)) {
911
- return defineError("Employee must be an Employee instance", {
912
- item: "employee",
913
- data: employee
914
- });
915
- }
916
-
917
- if (schema.requiresQualifiedEmployee) {
918
- if (!employee.hasSkillsForProject(project)) {
919
- return defineError(
920
- "Employee does not meet the project's requirements",
921
- {
922
- data: {
923
- assignment,
924
- project,
925
- employee
926
- }
927
- }
928
- );
929
- }
930
- }
931
- }
932
- );
933
-
934
- // Create and compile the schema
935
- const schema = {
936
- type: "object",
937
- properties: {
938
- assignment: {}, // Empty schema because we will validate it with the custom keyword
939
- project: {}, // Empty schema because we will validate it with the custom keyword
940
- employee: {} // Empty schema because we will validate it with the custom keyword
941
- },
942
- required: ["assignment", "project", "employee"],
943
- requiresQualifiedEmployee: true
944
- };
945
-
946
- const validator = schemaShield.compile(schema);
947
-
948
- // Create some data to validate
949
- const employee1 = new Employee("Employee 1", ["A", "B", "C"]);
950
-
951
- const project1 = new Project("Project 1", ["A", "B"]);
952
-
953
- const dataToValidate = {
954
- assignment: "Assignment 1 for Project 1",
955
- project: project1,
956
- employee: employee1
957
- };
958
-
959
- // Validate the data
960
- const validationResult = validator(dataToValidate);
961
-
962
- if (validationResult.valid) {
963
- console.log("Assignment is valid:", validationResult.data);
964
- } else {
965
- console.error("Validation error:", validationResult.error.message);
966
- }
967
- ```
968
-
969
- In this example, SchemaShield safely accesses instances of custom classes and utilizes them in the validation process. This level of complexity and flexibility would not be possible or would require a lot of boilerplate code with other libraries that rely on code generation.
970
-
971
- ## More on Error Handling
972
-
973
- SchemaShield provides a `ValidationError` class to handle errors that occur during schema validation. When a validation error is encountered, a `ValidationError` instance is returned in the error property of the validation result when `failFast: false` is used; otherwise `error` is `true`.
974
-
975
- This error instance uses the [`Error.cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) property. This allows you to analyze the whole error chain or retrieve the root cause of the error using the `getCause()`, `getTree()`, and `getPath()` methods.
976
-
977
- ### ValidationError Properties
978
-
979
- - `message`: A string containing a description of the error.
980
- - `item`: The final item in the path that caused the error (either a string or a number) (optional).
981
- - `keyword`: The keyword that triggered the error.
982
- - `cause`: A nested ValidationError or a normal Error that caused the current error.
983
- - `schemaPath`: The JSON Pointer path to the error location in the schema.
984
- - `instancePath`: The JSON Pointer path to the error location in the data.
985
- - `data`: The data that caused the error (optional).
986
- - `schema`: The schema that caused the error (optional).
987
-
988
- _Note:_ The `schemaPath` and `instancePath` will be only available after using the `getCause()` `getTree()` or `getPath()` methods.
989
-
990
- ### Get the path to the error location
991
-
992
- You can use the `getPath` method to get the JSON Pointer path to the error location in the schema and in the data. This method returns an object containing the `schemaPath` and `instancePath`.
993
-
994
- **Example:**
995
-
996
- ```javascript
997
- import { SchemaShield } from "schema-shield";
998
-
999
- const schemaShield = new SchemaShield({ failFast: false });
1000
-
1001
- const schema = {
1002
- type: "object",
1003
- properties: {
1004
- description: { type: "string" },
1005
- shouldLoadDb: { type: "boolean" },
1006
- enableNetConnectFor: { type: "array", items: { type: "string" } },
1007
- params: {
1008
- type: "object",
1009
- additionalProperties: {
1010
- type: "object",
1011
- properties: {
1012
- description: { type: "string" },
1013
- default: { type: "string" }
1014
- },
1015
- required: ["description"]
1016
- }
1017
- },
1018
- run: { type: "string" }
1019
- }
1020
- };
1021
-
1022
- const validator = schemaShield.compile(schema);
1023
-
1024
- const invalidData = {
1025
- description: "Say hello to the bot.",
1026
- shouldLoadDb: false,
1027
- enableNetConnectFor: [],
1028
- params: {
1029
- color: {
1030
- type: "string",
1031
- // description: "The color of the text", // Missing description on purpose
1032
- default: "red"
1033
- }
1034
- },
1035
- run: "run"
1036
- };
1037
-
1038
- const validationResult = validator(invalidData);
1039
-
1040
- if (validationResult.valid) {
1041
- console.log("Data is valid:", validationResult.data);
1042
- } else {
1043
- console.error("Validation error:", validationResult.error.message); // "Property is invalid"
1044
-
1045
- // Get the paths to the error location in the schema and in the data
1046
- const errorPaths = validationResult.error.getPath();
1047
- console.error("Schema path:", errorPaths.schemaPath); // "#/properties/params/additionalProperties/required"
1048
- console.error("Instance path:", errorPaths.instancePath); // "#/params/color/description"
1049
- }
1050
- ```
1051
-
1052
- ### Get the full error chain as a tree
1053
-
1054
- You can use the `getTree()` method to retrieve the full error chain as a tree. This method returns an ErrorTree object with the complete nested error structure, allowing you to analyze the full chain of errors that occurred during validation.
1055
-
1056
- #### ErrorTree Signature
1057
-
1058
- ```typescript
1059
- interface ErrorTree {
1060
- message: string; // The error message
1061
- keyword: string; // The keyword that triggered the error
1062
- item?: string | number; // The final item in the path that caused the error (either a string or a number) (optional)
1063
- schemaPath: string; // The JSON Pointer path to the error location in the schema
1064
- instancePath: string; // The JSON Pointer path to the error location in the data
1065
- data?: any; // The data that caused the error (optional)
1066
- cause?: ErrorTree; // A nested ErrorTree representation of the nested error that caused the current error
1067
- }
1068
- ```
1069
-
1070
- **Example:**
1071
-
1072
- ```javascript
1073
- import { SchemaShield } from "schema-shield";
1074
-
1075
- const schemaShield = new SchemaShield({ failFast: false });
1076
-
1077
- const schema = {
1078
- type: "object",
1079
- properties: {
1080
- description: { type: "string" },
1081
- shouldLoadDb: { type: "boolean" },
1082
- enableNetConnectFor: { type: "array", items: { type: "string" } },
1083
- params: {
1084
- type: "object",
1085
- additionalProperties: {
1086
- type: "object",
1087
- properties: {
1088
- description: { type: "string" },
1089
- default: { type: "string" }
1090
- },
1091
- required: ["description"]
1092
- }
1093
- },
1094
- run: { type: "string" }
1095
- }
1096
- };
1097
-
1098
- const validator = schemaShield.compile(schema);
1099
-
1100
- const invalidData = {
1101
- description: "Say hello to the bot.",
1102
- shouldLoadDb: false,
1103
- enableNetConnectFor: [],
1104
- params: {
1105
- color: {
1106
- type: "string",
1107
- // description: "The color of the text", // Missing description on purpose
1108
- default: "red"
1109
- }
1110
- },
1111
- run: "run"
1112
- };
568
+ });
1113
569
 
1114
- const validationResult = validator(invalidData);
1115
-
1116
- if (validationResult.valid) {
1117
- console.log("Data is valid:", validationResult.data);
1118
- } else {
1119
- console.error("Validation error:", validationResult.error.message); // "Property is invalid"
1120
-
1121
- // Get the full error chain as a tree
1122
- const errorTree = validationResult.error.getTree();
1123
- console.error(errorTree);
1124
-
1125
- /*
1126
- {
1127
- message: "Property is invalid",
1128
- keyword: "properties",
1129
- item: "params",
1130
- schemaPath: "#/properties/params",
1131
- instancePath: "#/params",
1132
- data: { color: { type: "string", default: "red" } },
1133
- cause: {
1134
- message: "Additional properties are invalid",
1135
- keyword: "additionalProperties",
1136
- item: "color",
1137
- schemaPath: "#/properties/params/additionalProperties",
1138
- instancePath: "#/params/color",
1139
- data: { type: "string", default: "red" },
1140
- cause: {
1141
- message: "Required property is missing",
1142
- keyword: "required",
1143
- item: "description",
1144
- schemaPath: "#/properties/params/additionalProperties/required",
1145
- instancePath: "#/params/color/description",
1146
- data: undefined
1147
- }
1148
- }
1149
- }
1150
- */
1151
- }
570
+ const validator = schemaShield.compile({
571
+ type: "number",
572
+ divisibleBy: 5
573
+ });
1152
574
  ```
1153
575
 
1154
- The `errorTree` object contains the full error chain with nested causes, allowing you to analyze the entire error structure.
576
+ The `instance` argument provides access to types, formats, and keywords registered on the active `SchemaShield` instance through `getType()`, `getFormat()`, and `getKeyword()`.
1155
577
 
1156
- ### Get the cause of the error
578
+ When `failFast: true`, `defineError()` returns `true`. When `failFast: false`, it returns a `ValidationError`. A custom keyword that returns `true` directly produces `error: true` in either mode, so use `defineError()` when you need detailed errors.
1157
579
 
1158
- You can use the `getCause()` method to retrieve the root cause of a validation error. This method returns the nested ValidationError instance that triggered the current error and contains the `schemaPath` and `instancePath` properties.
580
+ ## Supported Formats and Compatibility
1159
581
 
1160
- ```javascript
1161
- import { SchemaShield } from "schema-shield";
582
+ SchemaShield supports draft-06 and draft-07 JSON Schema validation and includes built-in validators for:
1162
583
 
1163
- const schemaShield = new SchemaShield({ failFast: false });
1164
-
1165
- const schema = {
1166
- type: "object",
1167
- properties: {
1168
- description: { type: "string" },
1169
- shouldLoadDb: { type: "boolean" },
1170
- enableNetConnectFor: { type: "array", items: { type: "string" } },
1171
- params: {
1172
- type: "object",
1173
- additionalProperties: {
1174
- type: "object",
1175
- properties: {
1176
- description: { type: "string" },
1177
- default: { type: "string" }
1178
- },
1179
- required: ["description"]
1180
- }
1181
- },
1182
- run: { type: "string" }
1183
- }
1184
- };
584
+ - **Date and time:** `date`, `time`, `date-time`, `duration`
585
+ - **Email:** `email`, `idn-email`
586
+ - **Hostnames:** `hostname`, `idn-hostname`
587
+ - **IP addresses:** `ipv4`, `ipv6`
588
+ - **Resource identifiers:** `uuid`, `uri`, `uri-reference`, `uri-template`, `iri`, `iri-reference`
589
+ - **JSON Pointers:** `json-pointer`, `relative-json-pointer`
590
+ - **Regular expressions:** `regex`
1185
591
 
1186
- const validator = schemaShield.compile(schema);
592
+ Remote references are intentionally outside the offline execution model described above.
1187
593
 
1188
- const invalidData = {
1189
- description: "Say hello to the bot.",
1190
- shouldLoadDb: false,
1191
- enableNetConnectFor: [],
1192
- params: {
1193
- color: {
1194
- type: "string",
1195
- // description: "The color of the text", // Missing description on purpose
1196
- default: "red"
1197
- }
1198
- },
1199
- run: "run"
1200
- };
594
+ Any built-in format can be replaced, and new formats can be added with `schemaShield.addFormat()`.
1201
595
 
1202
- const validationResult = validator(invalidData);
1203
-
1204
- if (validationResult.valid) {
1205
- console.log("Data is valid:", validationResult.data);
1206
- } else {
1207
- console.error("Validation error:", validationResult.error.message); // "Property is invalid"
1208
-
1209
- // Get the root cause of the error
1210
- const errorCause = validationResult.error.getCause();
1211
- console.error("Root cause:", errorCause.message); // "Required property is missing"
1212
- console.error("Schema path:", errorCause.schemaPath); // "#/properties/params/additionalProperties/required"
1213
- console.error("Instance path:", errorCause.instancePath); // "#/params/color/description"
1214
- console.error("Error data:", errorCause.data); // undefined
1215
- console.error("Error schema:", errorCause.schema); // ["description"]
1216
- console.error("Error keyword:", errorCause.keyword); // "required"
1217
- }
1218
- ```
596
+ ### TypeScript
1219
597
 
1220
- ## Immutable Mode
598
+ Type declarations are included in the package. CJS, ESM, browser, and declaration package smokes have been verified.
1221
599
 
1222
- SchemaShield offers an optional immutable mode to prevent modifications to the input data during validation. In some cases, SchemaShield may mutate the data when using the `default` keyword or within custom added keywords.
600
+ ## Immutable Mode and Known Limitations
1223
601
 
1224
- By enabling immutable mode, the library creates a deep copy of the input data before performing any validation checks, ensuring that the original data remains unchanged throughout the process. This feature can be useful in situations where preserving the integrity of the input data is essential.
602
+ ### Immutable Mode
1225
603
 
1226
- To enable immutable mode, simply pass the `immutable` option when creating a new `SchemaShield` instance:
604
+ SchemaShield can apply defaults, and application-provided custom keywords can modify data. Enable immutable mode when the original input must remain unchanged:
1227
605
 
1228
606
  ```javascript
1229
607
  const schemaShield = new SchemaShield({ immutable: true });
1230
608
  ```
1231
609
 
1232
- By default, the immutable mode is disabled. If you don't need the immutability guarantee, you can leave it disabled to optimize performance.
1233
-
1234
- However, there are some caveats to consider when using immutable mode. The deep copy may not accurately reproduce complex objects such as instantiated classes. In such cases, you can handle the cloning process yourself using a custom keyword to ensure the proper preservation of your data's structure.
1235
-
1236
- ## TypeScript Support
1237
-
1238
- SchemaShield offers comprehensive TypeScript support, enhancing the library's usability for TypeScript projects. Type definitions are included in the package, so you can import the library and use it in your TypeScript projects without any additional configuration.
1239
-
1240
- With the built in TypeScript support, you can take advantage of features like strong typing, autocompletion, and compile-time error checking, which can help you catch potential issues early and improve the overall quality of your code.
1241
-
1242
- ## Known Limitations
1243
-
1244
- SchemaShield is optimized for local execution and strict security.
1245
-
1246
- ### 1. Dynamic ID Scope Resolution (Scope Alteration)
1247
-
1248
- SchemaShield resolves references based on static JSON Pointers and unique IDs. It does not support changing the resolution base URI dynamically based on nested `$id` properties within sub-schemas.
610
+ Immutable mode creates a deep copy before validation. This preserves ordinary JSON-compatible data, but cloning may not reproduce instantiated classes or other complex objects accurately. Applications that validate such values can manage cloning explicitly.
1249
611
 
1250
- - **Impact:** Rare edge-cases in draft-07 involving complex relative URI resolution inside nested scopes are not supported.
612
+ Leave immutable mode disabled when input preservation is unnecessary and validation performance is the priority.
1251
613
 
1252
- ### 2. Unicode Length Validation
614
+ ### Remote References
1253
615
 
1254
- SchemaShield validates `minLength` and `maxLength` based on JavaScript's `length` property (UTF-16 code units), not Unicode Code Points.
616
+ SchemaShield does not retrieve remote references and does not provide an external registry or multi-document bundle API. Every referenced target must form part of the single root schema supplied to `compile()`.
1255
617
 
1256
- - **Impact:** Emoji or surrogate pairs may be counted as length 2.
618
+ ### Structural Equality
1257
619
 
1258
- ### 3. Conservative Equality Path (Performance Trade-off)
1259
-
1260
- For keywords like `enum`, `const`, and `uniqueItems`, SchemaShield prioritizes exact structural comparisons to preserve predictable behavior.
1261
-
1262
- - **Impact:** For very large arrays or enums with many complex objects, this conservative path can be slower than aggressive hashing strategies.
1263
- - **Future Option:** An opt-in aggressive mode based on structural hashing/bucketing could improve throughput in those extreme cases, but it is intentionally not enabled by default to avoid edge-case semantic divergence.
620
+ `enum`, `const`, and `uniqueItems` use exact structural comparison for predictable semantics. Very large arrays or enums containing complex objects can take longer to compare than strategies based on aggressive structural hashing.
1264
621
 
1265
622
  ## Testing
1266
623
 
1267
- SchemaShield prioritizes reliability and accuracy in JSON Schema validation by using the [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite).
624
+ SchemaShield is tested against the [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) and the package's own functional and distribution checks.
1268
625
 
1269
- This comprehensive test suite ensures compliance with the JSON Schema standard, providing developers with a dependable and consistent validation experience.
626
+ The latest observed functional suite completed with **1,635 passing** and **14 pending**. Package smokes for CJS, ESM, browser, and TypeScript declarations also passed.
1270
627
 
1271
628
  ```bash
1272
629
  npm test
1273
- # or
1274
- npm run test:dev # for development
1275
630
  ```
1276
631
 
1277
- ## Contribute
1278
-
1279
- SchemaShield is an open-source project, and we welcome contributions from the community. By contributing to SchemaShield, you can help improve the library and expand its feature set.
1280
-
1281
- If you are interested in contributing, please follow these steps:
1282
-
1283
- - **Fork the repository:** Fork the SchemaShield repository on GitHub and clone it to your local machine.
1284
-
1285
- - **Create a feature branch:** Create a new branch for your feature or bugfix. Make sure to give it a descriptive name.
632
+ For development:
1286
633
 
1287
- - **Implement your changes:** Make the necessary changes to the codebase. Be sure to add or update the relevant tests and documentation.
1288
-
1289
- - **Test your changes:** Before submitting your pull request, make sure your changes pass all existing tests and any new tests you've added. It's also a good idea to ensure that your changes do not introduce any performance regressions or new issues.
634
+ ```bash
635
+ npm run dev:test
636
+ ```
1290
637
 
1291
- - **Submit a pull request:** Once your changes are complete and tested, submit a pull request to the main SchemaShield repository. In your pull request description, please provide a brief summary of your changes and any relevant context.
638
+ ## Contribute
1292
639
 
1293
- - **Code review:** Your pull request will be reviewed and may request changes or provide feedback. Be prepared to engage in a discussion and possibly make further changes to your code based on the feedback.
640
+ SchemaShield is open source. Contributions that improve validation behavior, developer experience, documentation, compatibility, or performance are welcome.
1294
641
 
1295
- - **Merge:** Once your pull request is approved, it will be merged into the main SchemaShield repository.
642
+ Before opening a pull request:
1296
643
 
1297
- We appreciate your interest in contributing to SchemaShield and look forward to your valuable input. Together, we can make SchemaShield an even better library for the community.
644
+ 1. Add or update the relevant tests and documentation.
645
+ 2. Run the functional suite.
646
+ 3. Check that the change does not introduce a performance regression.
647
+ 4. Explain the behavior change and its motivation in the pull request.
1298
648
 
1299
649
  ## Acknowledgments
1300
650
 
1301
- - **ajv**: The gold standard for JSON Schema validation. Your performance sets the bar and drives the entire ecosystem forward.
1302
-
1303
- - **@exodus/schemasafe**: Your interpreter-first approach validates that security-first architectures can still deliver strong performance. Competing with you makes us better.
651
+ - **AJV:** The performance reference for JSON Schema validation. Its JIT architecture sets the raw-throughput bar for the ecosystem.
652
+ - **@exodus/schemasafe:** A strong interpreter-first validator that demonstrates the value of security-focused validation architectures. Competing with it makes SchemaShield better.
1304
653
 
1305
654
  ## Legal
1306
655
 
1307
656
  Author: [Masquerade Circus](http://masquerade-circus.net).
1308
- License [Apache-2.0](https://opensource.org/licenses/Apache-2.0)
657
+
658
+ License: [Apache-2.0](https://opensource.org/licenses/Apache-2.0).