schema-shield 0.0.6 → 1.0.1
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 +219 -65
- package/dist/formats.d.ts.map +1 -1
- package/dist/index.d.ts +25 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1837 -484
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +1837 -484
- package/dist/keywords/array-keywords.d.ts.map +1 -1
- package/dist/keywords/object-keywords.d.ts.map +1 -1
- package/dist/keywords/other-keywords.d.ts.map +1 -1
- package/dist/keywords/string-keywords.d.ts.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/deep-freeze.d.ts +5 -0
- package/dist/utils/deep-freeze.d.ts.map +1 -0
- package/dist/utils/has-changed.d.ts +2 -0
- package/dist/utils/has-changed.d.ts.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/{utils.d.ts → utils/main-utils.d.ts} +7 -9
- package/dist/utils/main-utils.d.ts.map +1 -0
- package/dist/utils/pattern-matcher.d.ts +3 -0
- package/dist/utils/pattern-matcher.d.ts.map +1 -0
- package/lib/formats.ts +468 -155
- package/lib/index.ts +702 -107
- package/lib/keywords/array-keywords.ts +260 -52
- package/lib/keywords/number-keywords.ts +1 -1
- package/lib/keywords/object-keywords.ts +295 -88
- package/lib/keywords/other-keywords.ts +263 -70
- package/lib/keywords/string-keywords.ts +123 -7
- package/lib/types.ts +5 -18
- package/lib/utils/deep-freeze.ts +208 -0
- package/lib/utils/has-changed.ts +51 -0
- package/lib/utils/index.ts +4 -0
- package/lib/{utils.ts → utils/main-utils.ts} +63 -77
- package/lib/utils/pattern-matcher.ts +66 -0
- package/package.json +2 -2
- package/tsconfig.json +4 -4
- package/dist/utils.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,16 +1,47 @@
|
|
|
1
|
-
# SchemaShield
|
|
1
|
+
# SchemaShield 🛡️
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Validation for Modern Architectures: Secure, Stack-Safe, and Domain-Aware.**
|
|
4
4
|
|
|
5
|
-
|
|
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.
|
|
6
6
|
|
|
7
|
-
|
|
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 jsonschema
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { SchemaShield } from "schema-shield";
|
|
15
|
+
|
|
16
|
+
const validator = new SchemaShield().compile({
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
name: { type: "string" },
|
|
20
|
+
age: { type: "number" }
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
validator({ name: "John", age: 30 });
|
|
25
|
+
// { valid: true, data: { name: "John", age: 30 } }
|
|
26
|
+
|
|
27
|
+
validator({ name: "John", age: "30" });
|
|
28
|
+
// { valid: false, error: ValidationError }
|
|
29
|
+
```
|
|
8
30
|
|
|
9
31
|
## Table of Contents
|
|
10
32
|
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
33
|
+
- [Quick Start](#quick-start)
|
|
34
|
+
- [Why SchemaShield?](#why-schemashield)
|
|
35
|
+
- [Comparison with Other Approaches](#comparison-with-other-approaches)
|
|
13
36
|
- [Usage](#usage)
|
|
37
|
+
- [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)
|
|
14
45
|
- [No Code Generation](#no-code-generation)
|
|
15
46
|
- [Error Handling](#error-handling)
|
|
16
47
|
- [Adding Custom Types](#adding-custom-types)
|
|
@@ -23,7 +54,8 @@ Despite its feature-rich and easy extendable nature, SchemaShield is designed to
|
|
|
23
54
|
- [Method Signature](#method-signature-2)
|
|
24
55
|
- [Example: Adding a Custom Keyword](#example-adding-a-custom-keyword)
|
|
25
56
|
- [Complex example: Adding a Custom Keyword that uses the instance](#complex-example-adding-a-custom-keyword-that-uses-the-instance)
|
|
26
|
-
- [
|
|
57
|
+
- [Supported Formats](#supported-formats)
|
|
58
|
+
- [Validating Runtime Objects](#validating-runtime-objects)
|
|
27
59
|
- [More on Error Handling](#more-on-error-handling)
|
|
28
60
|
- [ValidationError Properties](#validationerror-properties)
|
|
29
61
|
- [Get the path to the error location](#get-the-path-to-the-error-location)
|
|
@@ -32,22 +64,40 @@ Despite its feature-rich and easy extendable nature, SchemaShield is designed to
|
|
|
32
64
|
- [Immutable Mode](#immutable-mode)
|
|
33
65
|
- [TypeScript Support](#typescript-support)
|
|
34
66
|
- [Known Limitations](#known-limitations)
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
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)
|
|
37
70
|
- [Testing](#testing)
|
|
38
71
|
- [Contribute](#contribute)
|
|
39
72
|
- [Legal](#legal)
|
|
40
73
|
|
|
41
|
-
##
|
|
74
|
+
## Why SchemaShield?
|
|
42
75
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
|
|
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.
|
|
77
|
+
|
|
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. |
|
|
85
|
+
|
|
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).
|
|
87
|
+
|
|
88
|
+
### Comparison with Other Approaches
|
|
89
|
+
|
|
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 |
|
|
99
|
+
|
|
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.
|
|
51
101
|
|
|
52
102
|
## Usage
|
|
53
103
|
|
|
@@ -56,7 +106,7 @@ Despite its feature-rich and easy extendable nature, SchemaShield is designed to
|
|
|
56
106
|
```bash
|
|
57
107
|
npm install schema-shield
|
|
58
108
|
# or
|
|
59
|
-
|
|
109
|
+
bun add schema-shield
|
|
60
110
|
```
|
|
61
111
|
|
|
62
112
|
**2. Import the SchemaShield class**
|
|
@@ -70,10 +120,11 @@ const { SchemaShield } = require("schema-shield");
|
|
|
70
120
|
**3. Instantiate the SchemaShield class**
|
|
71
121
|
|
|
72
122
|
```javascript
|
|
73
|
-
const schemaShield = new SchemaShield(
|
|
123
|
+
const schemaShield = new SchemaShield();
|
|
74
124
|
```
|
|
75
125
|
|
|
76
|
-
**`immutable`** (optional): Set to `true` to ensure that input data remains unmodified during validation. Default is `false` for better performance.
|
|
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.
|
|
77
128
|
|
|
78
129
|
**3.5. Add custom types, keywords, and formats (optional)**
|
|
79
130
|
|
|
@@ -128,9 +179,92 @@ if (validationResult.valid) {
|
|
|
128
179
|
**`validationResult`**: Contains the following properties:
|
|
129
180
|
|
|
130
181
|
- `data`: The validated (and potentially modified) data.
|
|
131
|
-
- `error`: A `ValidationError` instance if validation failed, otherwise null
|
|
182
|
+
- `error`: A `ValidationError` instance if validation failed (when `failFast: false`), `true` if validation failed in fail-fast mode, otherwise `null`.
|
|
132
183
|
- `valid`: true if validation was successful, otherwise false.
|
|
133
184
|
|
|
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.
|
|
186
|
+
|
|
187
|
+
**6. Intelligent Defaults**
|
|
188
|
+
|
|
189
|
+
SchemaShield applies `default` values only when necessary to fulfill the schema contract. A `default` value is injected if and only if:
|
|
190
|
+
|
|
191
|
+
1. The property is missing in the input data.
|
|
192
|
+
2. The property is marked as `required` in the schema.
|
|
193
|
+
|
|
194
|
+
## Performance
|
|
195
|
+
|
|
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.
|
|
197
|
+
|
|
198
|
+
### Understanding Performance Context
|
|
199
|
+
|
|
200
|
+
In real-world applications, validation latency is often negligible compared to Network I/O (~20-100ms) or Database queries (~5-50ms).
|
|
201
|
+
|
|
202
|
+
SchemaShield is engineered to be **"Elite Fast" (Sufficiently Fast)**:
|
|
203
|
+
|
|
204
|
+
- **Zero Compilation Overhead:** Ideal for Serverless/Edge cold-starts.
|
|
205
|
+
- **Predictable Throughput:** Consistent performance regardless of schema complexity.
|
|
206
|
+
|
|
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.
|
|
208
|
+
|
|
209
|
+
### 1. Modern Runtimes (Bun)
|
|
210
|
+
|
|
211
|
+
In runtimes using JavaScriptCore (like Bun), SchemaShield outperforms JIT compilers because it avoids the heavy cost of runtime code generation and optimization overhead.
|
|
212
|
+
|
|
213
|
+
| Validator | Relative Speed | Context |
|
|
214
|
+
| :----------------- | :------------- | :----------- |
|
|
215
|
+
| **SchemaShield** | **100%** | **Fastest** |
|
|
216
|
+
| ajv | ~40% | JIT Compiler |
|
|
217
|
+
| @exodus/schemasafe | ~24% | Interpreter |
|
|
218
|
+
| jsonschema | ~2% | Legacy |
|
|
219
|
+
|
|
220
|
+
### 2. Standard Runtimes (Node.js)
|
|
221
|
+
|
|
222
|
+
In V8-based environments (Node.js), SchemaShield maintains elite performance for a secure interpreter, being roughly **70x faster** than legacy libraries.
|
|
223
|
+
|
|
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 |
|
|
230
|
+
|
|
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.
|
|
232
|
+
|
|
233
|
+
## Edge & Serverless Ready
|
|
234
|
+
|
|
235
|
+
SchemaShield is designed to run seamlessly in restrictive environments like **Cloudflare Workers**, **Vercel Edge Functions**, **Deno Deploy**, and **Bun**.
|
|
236
|
+
|
|
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.
|
|
240
|
+
|
|
241
|
+
## Features
|
|
242
|
+
|
|
243
|
+
- Supports draft-06 and draft-07 of the [JSON Schema](https://json-schema.org/) specification.
|
|
244
|
+
- Full support for internal references ($ref) and anchors ($id).
|
|
245
|
+
- No Code Generation for Enhanced Safety and Validation Flexibility.
|
|
246
|
+
- Custom type, keyword, and format validators.
|
|
247
|
+
- Runtime object validation: first-class support for business logic checks (type checking and schema validation).
|
|
248
|
+
- Immutable mode for data protection.
|
|
249
|
+
- Lightweight and fast.
|
|
250
|
+
- Easy to use and extend.
|
|
251
|
+
- No dependencies.
|
|
252
|
+
- TypeScript support.
|
|
253
|
+
|
|
254
|
+
## Security Philosophy: Hermetic Validation
|
|
255
|
+
|
|
256
|
+
SchemaShield adopts a **Zero Trust** and **Hermetic Architecture** approach. Unlike validators that allow runtime network access, SchemaShield is strictly synchronous and offline by design.
|
|
257
|
+
|
|
258
|
+
### Why No Remote References?
|
|
259
|
+
|
|
260
|
+
Allowing a validator to fetch schemas from remote URLs (`$ref: "https://..."`) at runtime introduces critical security vectors and stability issues:
|
|
261
|
+
|
|
262
|
+
1. **SSRF (Server-Side Request Forgery):** Prevents attackers from manipulating schemas to force internal network scanning or access metadata services.
|
|
263
|
+
2. **Supply Chain Attacks:** Eliminates the risk of a remote schema being silently compromised, which could alter validation logic without code deployment.
|
|
264
|
+
3. **Deterministic Reliability:** Validation never fails due to network latency, DNS issues, or third-party server downtime.
|
|
265
|
+
|
|
266
|
+
**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.
|
|
267
|
+
|
|
134
268
|
## No Code Generation
|
|
135
269
|
|
|
136
270
|
Unlike some other validation libraries that rely on code generation to achieve fast performance, SchemaShield does not use code generation.
|
|
@@ -146,18 +280,21 @@ class CustomDate extends Date {}
|
|
|
146
280
|
schemaShield.addType("custom-date-class", (data) => data instanceof CustomDate);
|
|
147
281
|
```
|
|
148
282
|
|
|
149
|
-
You can see a full example of this in the [No Code Generation opened possibilities](#no-code-generation-opened-possibilities) section.
|
|
150
|
-
|
|
151
283
|
## Error Handling
|
|
152
284
|
|
|
153
|
-
SchemaShield provides comprehensive error handling for schema validation. When a validation error occurs
|
|
285
|
+
SchemaShield provides comprehensive error handling for schema validation. When a validation error occurs:
|
|
286
|
+
|
|
287
|
+
- If the instance was created with `{ failFast: true }` (the default), the `error` property will be `true` as a lightweight sentinel indicating that validation failed.
|
|
288
|
+
- If the instance was created with `{ failFast: false }`, the `error` property will contain a `ValidationError` instance with rich debugging information.
|
|
289
|
+
|
|
290
|
+
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.
|
|
154
291
|
|
|
155
292
|
**Example:**
|
|
156
293
|
|
|
157
294
|
```javascript
|
|
158
295
|
import { SchemaShield } from "schema-shield";
|
|
159
296
|
|
|
160
|
-
const schemaShield = new SchemaShield();
|
|
297
|
+
const schemaShield = new SchemaShield({ failFast: false });
|
|
161
298
|
|
|
162
299
|
const schema = {
|
|
163
300
|
type: "object",
|
|
@@ -218,7 +355,7 @@ In this example, we'll add a custom type called age that validates if a given nu
|
|
|
218
355
|
```javascript
|
|
219
356
|
import { SchemaShield } from "schema-shield";
|
|
220
357
|
|
|
221
|
-
const schemaShield = new SchemaShield();
|
|
358
|
+
const schemaShield = new SchemaShield({ failFast: false });
|
|
222
359
|
|
|
223
360
|
// Custom type 'age' validator function
|
|
224
361
|
const ageValidator = (data) => {
|
|
@@ -275,9 +412,9 @@ addFormat(name: string, validator: FormatFunction, overwrite?: boolean): void;
|
|
|
275
412
|
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).
|
|
276
413
|
|
|
277
414
|
```javascript
|
|
278
|
-
import { SchemaShield } from "
|
|
415
|
+
import { SchemaShield } from "schema-shield";
|
|
279
416
|
|
|
280
|
-
const schemaShield = new SchemaShield();
|
|
417
|
+
const schemaShield = new SchemaShield({ failFast: false });
|
|
281
418
|
|
|
282
419
|
// Custom format 'ssn' validator function
|
|
283
420
|
const ssnValidator = (data) => {
|
|
@@ -314,21 +451,21 @@ if (validationResult.valid) {
|
|
|
314
451
|
|
|
315
452
|
## Adding Custom Keywords
|
|
316
453
|
|
|
317
|
-
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 to interact with the entire schema and data being validated at the level of the keyword.
|
|
454
|
+
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.
|
|
318
455
|
|
|
319
456
|
### Method Signature
|
|
320
457
|
|
|
321
458
|
```javascript
|
|
322
|
-
type Result = void | ValidationError;
|
|
459
|
+
type Result = void | ValidationError | true;
|
|
323
460
|
|
|
324
461
|
interface DefineErrorOptions {
|
|
325
462
|
item?: any; // Final item in the path
|
|
326
|
-
cause?: ValidationError; // Cause of the error
|
|
463
|
+
cause?: ValidationError | true; // Cause of the error (or true in failFast mode)
|
|
327
464
|
data?: any; // Data that caused the error
|
|
328
465
|
}
|
|
329
466
|
|
|
330
467
|
interface DefineErrorFunction {
|
|
331
|
-
(message: string, options?: DefineErrorOptions): ValidationError;
|
|
468
|
+
(message: string, options?: DefineErrorOptions): ValidationError | true;
|
|
332
469
|
}
|
|
333
470
|
|
|
334
471
|
interface ValidateFunction {
|
|
@@ -349,8 +486,9 @@ interface TypeFunction {
|
|
|
349
486
|
}
|
|
350
487
|
|
|
351
488
|
declare class SchemaShield {
|
|
352
|
-
constructor(
|
|
353
|
-
|
|
489
|
+
constructor(options?: {
|
|
490
|
+
immutable?: boolean;
|
|
491
|
+
failFast?: boolean;
|
|
354
492
|
});
|
|
355
493
|
compile(schema: any): Validator;
|
|
356
494
|
addType(name: string, validator: TypeFunction, overwrite?: boolean): void;
|
|
@@ -377,7 +515,7 @@ addKeyword(name: string, validator: KeywordFunction, overwrite?: boolean): void;
|
|
|
377
515
|
```
|
|
378
516
|
|
|
379
517
|
- `name`: The name of the custom keyword. This should be a unique string that does not conflict with existing keywords.
|
|
380
|
-
- `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
|
|
518
|
+
- `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`.
|
|
381
519
|
- `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.
|
|
382
520
|
|
|
383
521
|
#### About the `defineError` Function
|
|
@@ -387,9 +525,11 @@ Take into account that the error must be generated using the `defineError` funct
|
|
|
387
525
|
- `message`: A string that describes the validation error.
|
|
388
526
|
- `options`: An optional object with properties that provide more context for the error:
|
|
389
527
|
- `item`?: An optional value representing the final item in the path where the validation error occurred. (e.g. index of an array item)
|
|
390
|
-
- `cause`?: An optional `ValidationError` that represents the cause of the current error.
|
|
528
|
+
- `cause`?: An optional `ValidationError` (or `true` in fail-fast mode) that represents the cause of the current error.
|
|
391
529
|
- `data`?: An optional value representing the data that caused the validation error.
|
|
392
530
|
|
|
531
|
+
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.
|
|
532
|
+
|
|
393
533
|
#### About the `instance` Argument
|
|
394
534
|
|
|
395
535
|
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.
|
|
@@ -399,9 +539,9 @@ The `instance` argument is the SchemaShield instance that is currently running t
|
|
|
399
539
|
In this example, we'll add a custom keyword called divisibleBy that validates if a given number is divisible by a specified divisor.
|
|
400
540
|
|
|
401
541
|
```javascript
|
|
402
|
-
import { SchemaShield, ValidationError } from "
|
|
542
|
+
import { SchemaShield, ValidationError } from "schema-shield";
|
|
403
543
|
|
|
404
|
-
const schemaShield = new SchemaShield();
|
|
544
|
+
const schemaShield = new SchemaShield({ failFast: false });
|
|
405
545
|
|
|
406
546
|
// Custom keyword 'divisibleBy' validator function
|
|
407
547
|
const divisibleByValidator = (schema, data, defineError, instance) => {
|
|
@@ -450,7 +590,7 @@ In this example we'll add a custom keyword called `prefixedUsername` that will v
|
|
|
450
590
|
```javascript
|
|
451
591
|
import { SchemaShield, ValidationError } from "schema-shield";
|
|
452
592
|
|
|
453
|
-
const schemaShield = new SchemaShield();
|
|
593
|
+
const schemaShield = new SchemaShield({ failFast: false });
|
|
454
594
|
|
|
455
595
|
// Custom type validator: nonEmptyString
|
|
456
596
|
const nonEmptyStringValidator = (data) =>
|
|
@@ -479,7 +619,7 @@ const prefixedUsername = (schema, data, defineError, instance) => {
|
|
|
479
619
|
// Get the validators for the specified types and formats from the instance
|
|
480
620
|
// (if they exist)
|
|
481
621
|
const typeValidator = instance.getType(validType);
|
|
482
|
-
const
|
|
622
|
+
const prefixKeyword = instance.getKeyword(prefixValidator);
|
|
483
623
|
const formatValidator = instance.getFormat(validFormat);
|
|
484
624
|
|
|
485
625
|
for (let i = 0; i < data.length; i++) {
|
|
@@ -506,8 +646,8 @@ const prefixedUsername = (schema, data, defineError, instance) => {
|
|
|
506
646
|
}
|
|
507
647
|
|
|
508
648
|
// Validate that the data has the correct prefix if specified
|
|
509
|
-
if (
|
|
510
|
-
const error =
|
|
649
|
+
if (prefixKeyword) {
|
|
650
|
+
const error = prefixKeyword(schema, item, defineError, instance);
|
|
511
651
|
if (error) {
|
|
512
652
|
return defineError(`Invalid prefix: ${prefixValidator}`, {
|
|
513
653
|
cause: error,
|
|
@@ -546,16 +686,30 @@ if (validationResult.valid) {
|
|
|
546
686
|
}
|
|
547
687
|
```
|
|
548
688
|
|
|
549
|
-
##
|
|
689
|
+
## Supported Formats
|
|
690
|
+
|
|
691
|
+
SchemaShield includes built-in validators for the following formats:
|
|
692
|
+
|
|
693
|
+
- **Date & Time:** `date`, `time`, `date-time`, `duration`.
|
|
694
|
+
- **Email:** `email`, `idn-email`.
|
|
695
|
+
- **Hostnames:** `hostname`, `idn-hostname`.
|
|
696
|
+
- **IP Addresses:** `ipv4`, `ipv6`.
|
|
697
|
+
- **Resource Identifiers:** `uuid`, `uri`, `uri-reference`, `uri-template`, `iri`, `iri-reference`.
|
|
698
|
+
- **JSON Pointers:** `json-pointer`, `relative-json-pointer`.
|
|
699
|
+
- **Regex:** `regex`.
|
|
700
|
+
|
|
701
|
+
You can override any of these or add new ones using `schemaShield.addFormat`.
|
|
702
|
+
|
|
703
|
+
## Validating Runtime Objects
|
|
550
704
|
|
|
551
|
-
|
|
705
|
+
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.
|
|
552
706
|
|
|
553
707
|
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:
|
|
554
708
|
|
|
555
709
|
```javascript
|
|
556
710
|
import { SchemaShield, ValidationError } from "schema-shield";
|
|
557
711
|
|
|
558
|
-
const schemaShield = new SchemaShield();
|
|
712
|
+
const schemaShield = new SchemaShield({ failFast: false });
|
|
559
713
|
|
|
560
714
|
// Custom classes
|
|
561
715
|
class Project {
|
|
@@ -653,7 +807,7 @@ const dataToValidate = {
|
|
|
653
807
|
};
|
|
654
808
|
|
|
655
809
|
// Validate the data
|
|
656
|
-
const validationResult = validator(
|
|
810
|
+
const validationResult = validator(dataToValidate);
|
|
657
811
|
|
|
658
812
|
if (validationResult.valid) {
|
|
659
813
|
console.log("Assignment is valid:", validationResult.data);
|
|
@@ -666,9 +820,9 @@ In this example, SchemaShield safely accesses instances of custom classes and ut
|
|
|
666
820
|
|
|
667
821
|
## More on Error Handling
|
|
668
822
|
|
|
669
|
-
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
|
|
823
|
+
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`.
|
|
670
824
|
|
|
671
|
-
This error instance uses the
|
|
825
|
+
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.
|
|
672
826
|
|
|
673
827
|
### ValidationError Properties
|
|
674
828
|
|
|
@@ -692,7 +846,7 @@ You can use the `getPath` method to get the JSON Pointer path to the error locat
|
|
|
692
846
|
```javascript
|
|
693
847
|
import { SchemaShield } from "schema-shield";
|
|
694
848
|
|
|
695
|
-
const schemaShield = new SchemaShield();
|
|
849
|
+
const schemaShield = new SchemaShield({ failFast: false });
|
|
696
850
|
|
|
697
851
|
const schema = {
|
|
698
852
|
type: "object",
|
|
@@ -768,7 +922,7 @@ interface ErrorTree {
|
|
|
768
922
|
```javascript
|
|
769
923
|
import { SchemaShield } from "schema-shield";
|
|
770
924
|
|
|
771
|
-
const schemaShield = new SchemaShield();
|
|
925
|
+
const schemaShield = new SchemaShield({ failFast: false });
|
|
772
926
|
|
|
773
927
|
const schema = {
|
|
774
928
|
type: "object",
|
|
@@ -856,7 +1010,7 @@ You can use the `getCause()` method to retrieve the root cause of a validation e
|
|
|
856
1010
|
```javascript
|
|
857
1011
|
import { SchemaShield } from "schema-shield";
|
|
858
1012
|
|
|
859
|
-
const schemaShield = new SchemaShield();
|
|
1013
|
+
const schemaShield = new SchemaShield({ failFast: false });
|
|
860
1014
|
|
|
861
1015
|
const schema = {
|
|
862
1016
|
type: "object",
|
|
@@ -937,26 +1091,26 @@ With the built in TypeScript support, you can take advantage of features like st
|
|
|
937
1091
|
|
|
938
1092
|
## Known Limitations
|
|
939
1093
|
|
|
940
|
-
SchemaShield is
|
|
1094
|
+
SchemaShield is optimized for local execution and strict security.
|
|
1095
|
+
|
|
1096
|
+
### 1. Dynamic ID Scope Resolution (Scope Alteration)
|
|
941
1097
|
|
|
942
|
-
|
|
1098
|
+
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.
|
|
943
1099
|
|
|
944
|
-
|
|
1100
|
+
- **Impact:** Rare edge-cases in draft-07 involving complex relative URI resolution inside nested scopes are not supported.
|
|
945
1101
|
|
|
946
|
-
|
|
1102
|
+
### 2. Unicode Length Validation
|
|
947
1103
|
|
|
948
|
-
|
|
1104
|
+
SchemaShield validates `minLength` and `maxLength` based on JavaScript's `length` property (UTF-16 code units), not Unicode Code Points.
|
|
949
1105
|
|
|
950
|
-
|
|
1106
|
+
- **Impact:** Emoji or surrogate pairs may be counted as length 2.
|
|
951
1107
|
|
|
952
|
-
|
|
1108
|
+
### 3. Conservative Equality Path (Performance Trade-off)
|
|
953
1109
|
|
|
954
|
-
|
|
955
|
-
- `idn-hostname`
|
|
956
|
-
- `iri`
|
|
957
|
-
- `iri-reference`
|
|
1110
|
+
For keywords like `enum`, `const`, and `uniqueItems`, SchemaShield prioritizes exact structural comparisons to preserve predictable behavior.
|
|
958
1111
|
|
|
959
|
-
|
|
1112
|
+
- **Impact:** For very large arrays or enums with many complex objects, this conservative path can be slower than aggressive hashing strategies.
|
|
1113
|
+
- **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.
|
|
960
1114
|
|
|
961
1115
|
## Testing
|
|
962
1116
|
|
|
@@ -994,5 +1148,5 @@ We appreciate your interest in contributing to SchemaShield and look forward to
|
|
|
994
1148
|
|
|
995
1149
|
## Legal
|
|
996
1150
|
|
|
997
|
-
Author: [Masquerade Circus](http://masquerade-circus.net).
|
|
1151
|
+
Author: [Masquerade Circus](http://masquerade-circus.net).
|
|
998
1152
|
License [Apache-2.0](https://opensource.org/licenses/Apache-2.0)
|
package/dist/formats.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../lib/formats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../lib/formats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA8TzC,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,KAAK,CA4O1D,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export {
|
|
4
|
-
export
|
|
1
|
+
/****************** Path: lib/index.ts ******************/
|
|
2
|
+
import { DefineErrorFunction, ValidationError } from "./utils/main-utils";
|
|
3
|
+
export { ValidationError } from "./utils/main-utils";
|
|
4
|
+
export { deepCloneUnfreeze as deepClone } from "./utils/deep-freeze";
|
|
5
|
+
export type Result = void | ValidationError | true;
|
|
5
6
|
export interface KeywordFunction {
|
|
6
7
|
(schema: CompiledSchema, data: any, defineError: DefineErrorFunction, instance: SchemaShield): Result;
|
|
7
8
|
}
|
|
@@ -21,7 +22,7 @@ export interface CompiledSchema {
|
|
|
21
22
|
export interface Validator {
|
|
22
23
|
(data: any): {
|
|
23
24
|
data: any;
|
|
24
|
-
error: ValidationError | null;
|
|
25
|
+
error: ValidationError | null | true;
|
|
25
26
|
valid: boolean;
|
|
26
27
|
};
|
|
27
28
|
compiledSchema: CompiledSchema;
|
|
@@ -31,17 +32,35 @@ export declare class SchemaShield {
|
|
|
31
32
|
private formats;
|
|
32
33
|
private keywords;
|
|
33
34
|
private immutable;
|
|
34
|
-
|
|
35
|
+
private rootSchema;
|
|
36
|
+
private idRegistry;
|
|
37
|
+
private failFast;
|
|
38
|
+
constructor({ immutable, failFast }?: {
|
|
35
39
|
immutable?: boolean;
|
|
40
|
+
failFast?: boolean;
|
|
36
41
|
});
|
|
37
42
|
addType(name: string, validator: TypeFunction, overwrite?: boolean): void;
|
|
38
43
|
getType(type: string): TypeFunction | false;
|
|
39
44
|
addFormat(name: string, validator: FormatFunction, overwrite?: boolean): void;
|
|
40
45
|
getFormat(format: string): FormatFunction | false;
|
|
46
|
+
isDefaultFormatValidator(format: string, validator: FormatFunction): boolean;
|
|
41
47
|
addKeyword(name: string, validator: KeywordFunction, overwrite?: boolean): void;
|
|
42
48
|
getKeyword(keyword: string): KeywordFunction | false;
|
|
49
|
+
getSchemaRef(path: string): CompiledSchema | undefined;
|
|
50
|
+
getSchemaById(id: string): CompiledSchema | undefined;
|
|
43
51
|
compile(schema: any): Validator;
|
|
52
|
+
private isPlainObject;
|
|
53
|
+
private isTrivialAlwaysValidSubschema;
|
|
54
|
+
private shallowArrayEquals;
|
|
55
|
+
private flattenAssociativeBranches;
|
|
56
|
+
private flattenSingleWrapperOneOf;
|
|
57
|
+
private normalizeSchemaForCompile;
|
|
58
|
+
private markSchemaHasRef;
|
|
59
|
+
private shouldSkipKeyword;
|
|
60
|
+
private hasRequiredDefaults;
|
|
61
|
+
private isDefaultTypeValidator;
|
|
44
62
|
private compileSchema;
|
|
45
63
|
isSchemaLike(subSchema: any): boolean;
|
|
64
|
+
private linkReferences;
|
|
46
65
|
}
|
|
47
66
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"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"}
|