@cloudflare/cabidela 0.1.1 → 0.2.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/CHANGELOG.md +6 -0
- package/README.md +45 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +41 -19
- package/dist/index.mjs +41 -19
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
@@ -71,6 +71,8 @@ Cabidela takes a JSON-Schema and optional configuration flags:
|
|
71
71
|
- `applyDefaults`: boolean - If true, the validator will apply default values to the input object. Default is false.
|
72
72
|
- `errorMessages`: boolean - If true, the validator will use custom `errorMessage` messages from the schema. Default is false.
|
73
73
|
- `fullErrors`: boolean - If true, the validator will be more verbose when throwing errors for complex schemas (example: anyOf, oneOf's), set to false for shorter exceptions. Default is true.
|
74
|
+
- `useMerge`: boolean - Set to true if you want to use the `$merge` keyword. Default is false. See below for more information.
|
75
|
+
- `subSchema`: any[] - An optional array of sub-schemas that can be used with `$id` and `$ref`. See below for more information.
|
74
76
|
|
75
77
|
Returns a validation object.
|
76
78
|
|
@@ -249,6 +251,49 @@ cabidela.validate({
|
|
249
251
|
});
|
250
252
|
```
|
251
253
|
|
254
|
+
## Combined schemas and $merge
|
255
|
+
|
256
|
+
The standard way of combining and extending schemas is by using the [`allOf`](https://json-schema.org/understanding-json-schema/reference/combining#allOf) (AND), [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) (OR), [`oneOf`](https://json-schema.org/understanding-json-schema/reference/combining#oneOf) (XOR) and [`not`](https://json-schema.org/understanding-json-schema/reference/combining#not) keywords, all supported by this library.
|
257
|
+
|
258
|
+
Cabidela supports an additional keyword `$merge` (inspired by [Ajv](https://ajv.js.org/guide/combining-schemas.html#merge-and-patch-keywords)) that allows you to merge two objects. This is useful when you want to extend a schema with additional properties and `allOf`` is not enough.
|
259
|
+
|
260
|
+
Here's how it works:
|
261
|
+
|
262
|
+
```json
|
263
|
+
{
|
264
|
+
"$merge": {
|
265
|
+
"source": {
|
266
|
+
"type": "object",
|
267
|
+
"properties": { "p": { "type": "string" } },
|
268
|
+
"additionalProperties": false
|
269
|
+
},
|
270
|
+
"with": {
|
271
|
+
"properties": { "q": { "type": "number" } }
|
272
|
+
}
|
273
|
+
}
|
274
|
+
}
|
275
|
+
```
|
276
|
+
|
277
|
+
Resolves to:
|
278
|
+
|
279
|
+
```json
|
280
|
+
{
|
281
|
+
"type": "object",
|
282
|
+
"properties": {
|
283
|
+
"q": {
|
284
|
+
"type": "number"
|
285
|
+
}
|
286
|
+
},
|
287
|
+
"additionalProperties": false
|
288
|
+
}
|
289
|
+
```
|
290
|
+
|
291
|
+
To use `$merge` set the `useMerge` flag to true when creating the instance.
|
292
|
+
|
293
|
+
```js
|
294
|
+
new Cabidela(schema, { useMerge: true });
|
295
|
+
```
|
296
|
+
|
252
297
|
## Custom errors
|
253
298
|
|
254
299
|
If the new instance options has the `errorMessages` flag set to true, you can use the property `errorMessage` in the schema to define custom error messages.
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
@@ -31,25 +31,45 @@ var parse$ref = (ref) => {
|
|
31
31
|
const $path = parts[1].split("/").filter((part) => part !== "");
|
32
32
|
return { $id, $path };
|
33
33
|
};
|
34
|
-
|
35
|
-
|
36
|
-
Object.keys(
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
34
|
+
function deepMerge(target, source) {
|
35
|
+
const result = Array.isArray(target) && Array.isArray(source) ? target.concat(source) : { ...target, ...source };
|
36
|
+
for (const key of Object.keys(result)) {
|
37
|
+
result[key] = typeof target[key] == "object" && typeof source[key] == "object" ? deepMerge(target[key], source[key]) : structuredClone(result[key]);
|
38
|
+
}
|
39
|
+
return result;
|
40
|
+
}
|
41
|
+
var traverseSchema = (options, definitions, obj, cb) => {
|
42
|
+
let hits;
|
43
|
+
do {
|
44
|
+
hits = 0;
|
45
|
+
Object.keys(obj).forEach((key) => {
|
46
|
+
if (obj[key] !== null && typeof obj[key] === "object") {
|
47
|
+
traverseSchema(options, definitions, obj[key], (value) => {
|
48
|
+
hits++;
|
49
|
+
obj[key] = value;
|
50
|
+
});
|
51
|
+
if (options.useMerge && key === "$merge") {
|
52
|
+
const merge = deepMerge(obj[key].source, obj[key].with);
|
53
|
+
if (cb) {
|
54
|
+
cb(merge);
|
55
|
+
} else {
|
56
|
+
Object.assign(obj, merge);
|
57
|
+
delete obj[key];
|
58
|
+
}
|
59
|
+
}
|
60
|
+
} else {
|
61
|
+
if (key === "$ref") {
|
62
|
+
const { $id, $path } = parse$ref(obj[key]);
|
63
|
+
const { resolvedObject } = resolvePayload($path, definitions[$id]);
|
64
|
+
if (resolvedObject) {
|
65
|
+
cb(resolvedObject);
|
66
|
+
} else {
|
67
|
+
throw new Error(`Could not resolve '${obj[key]}' $ref`);
|
68
|
+
}
|
49
69
|
}
|
50
70
|
}
|
51
|
-
}
|
52
|
-
});
|
71
|
+
});
|
72
|
+
} while (hits > 0);
|
53
73
|
};
|
54
74
|
var resolvePayload = (path, obj) => {
|
55
75
|
let resolvedObject = path.reduce(function(prev, curr) {
|
@@ -114,7 +134,9 @@ var Cabidela = class {
|
|
114
134
|
for (const subSchema of this.options.subSchemas) {
|
115
135
|
this.addSchema(subSchema, false);
|
116
136
|
}
|
117
|
-
|
137
|
+
}
|
138
|
+
if (this.options.useMerge || this.options.subSchemas.length > 0) {
|
139
|
+
traverseSchema(this.options, this.definitions, this.schema);
|
118
140
|
}
|
119
141
|
}
|
120
142
|
setSchema(schema) {
|
@@ -133,7 +155,7 @@ var Cabidela = class {
|
|
133
155
|
} else {
|
134
156
|
throw new Error("subSchemas need $id https://json-schema.org/understanding-json-schema/structuring#id");
|
135
157
|
}
|
136
|
-
if (combine == true) traverseSchema(this.definitions, this.schema);
|
158
|
+
if (combine == true) traverseSchema(this.options, this.definitions, this.schema);
|
137
159
|
}
|
138
160
|
getSchema() {
|
139
161
|
return this.schema;
|
package/dist/index.mjs
CHANGED
@@ -5,25 +5,45 @@ var parse$ref = (ref) => {
|
|
5
5
|
const $path = parts[1].split("/").filter((part) => part !== "");
|
6
6
|
return { $id, $path };
|
7
7
|
};
|
8
|
-
|
9
|
-
|
10
|
-
Object.keys(
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
8
|
+
function deepMerge(target, source) {
|
9
|
+
const result = Array.isArray(target) && Array.isArray(source) ? target.concat(source) : { ...target, ...source };
|
10
|
+
for (const key of Object.keys(result)) {
|
11
|
+
result[key] = typeof target[key] == "object" && typeof source[key] == "object" ? deepMerge(target[key], source[key]) : structuredClone(result[key]);
|
12
|
+
}
|
13
|
+
return result;
|
14
|
+
}
|
15
|
+
var traverseSchema = (options, definitions, obj, cb) => {
|
16
|
+
let hits;
|
17
|
+
do {
|
18
|
+
hits = 0;
|
19
|
+
Object.keys(obj).forEach((key) => {
|
20
|
+
if (obj[key] !== null && typeof obj[key] === "object") {
|
21
|
+
traverseSchema(options, definitions, obj[key], (value) => {
|
22
|
+
hits++;
|
23
|
+
obj[key] = value;
|
24
|
+
});
|
25
|
+
if (options.useMerge && key === "$merge") {
|
26
|
+
const merge = deepMerge(obj[key].source, obj[key].with);
|
27
|
+
if (cb) {
|
28
|
+
cb(merge);
|
29
|
+
} else {
|
30
|
+
Object.assign(obj, merge);
|
31
|
+
delete obj[key];
|
32
|
+
}
|
33
|
+
}
|
34
|
+
} else {
|
35
|
+
if (key === "$ref") {
|
36
|
+
const { $id, $path } = parse$ref(obj[key]);
|
37
|
+
const { resolvedObject } = resolvePayload($path, definitions[$id]);
|
38
|
+
if (resolvedObject) {
|
39
|
+
cb(resolvedObject);
|
40
|
+
} else {
|
41
|
+
throw new Error(`Could not resolve '${obj[key]}' $ref`);
|
42
|
+
}
|
23
43
|
}
|
24
44
|
}
|
25
|
-
}
|
26
|
-
});
|
45
|
+
});
|
46
|
+
} while (hits > 0);
|
27
47
|
};
|
28
48
|
var resolvePayload = (path, obj) => {
|
29
49
|
let resolvedObject = path.reduce(function(prev, curr) {
|
@@ -88,7 +108,9 @@ var Cabidela = class {
|
|
88
108
|
for (const subSchema of this.options.subSchemas) {
|
89
109
|
this.addSchema(subSchema, false);
|
90
110
|
}
|
91
|
-
|
111
|
+
}
|
112
|
+
if (this.options.useMerge || this.options.subSchemas.length > 0) {
|
113
|
+
traverseSchema(this.options, this.definitions, this.schema);
|
92
114
|
}
|
93
115
|
}
|
94
116
|
setSchema(schema) {
|
@@ -107,7 +129,7 @@ var Cabidela = class {
|
|
107
129
|
} else {
|
108
130
|
throw new Error("subSchemas need $id https://json-schema.org/understanding-json-schema/structuring#id");
|
109
131
|
}
|
110
|
-
if (combine == true) traverseSchema(this.definitions, this.schema);
|
132
|
+
if (combine == true) traverseSchema(this.options, this.definitions, this.schema);
|
111
133
|
}
|
112
134
|
getSchema() {
|
113
135
|
return this.schema;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@cloudflare/cabidela",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.2.1",
|
4
4
|
"description": "Cabidela is a small, fast, eval-less, Cloudflare Workers compatible, dynamic JSON Schema validator",
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"module": "dist/index.mjs",
|
@@ -27,6 +27,9 @@
|
|
27
27
|
"README.md",
|
28
28
|
"CHANGELOG.md"
|
29
29
|
],
|
30
|
+
"prettier": {
|
31
|
+
"embeddedLanguageFormatting": "auto"
|
32
|
+
},
|
30
33
|
"repository": {
|
31
34
|
"type": "git",
|
32
35
|
"url": "https://github.com/cloudflare/cabidela.git"
|
@@ -39,6 +42,7 @@
|
|
39
42
|
"@vitest/ui": "^3.0.3",
|
40
43
|
"ajv": "^8.17.1",
|
41
44
|
"ajv-errors": "^3.0.0",
|
45
|
+
"ajv-merge-patch": "^5.0.1",
|
42
46
|
"tsup": "^8.3.6",
|
43
47
|
"typescript": "^5.7.3",
|
44
48
|
"vitest": "^3.0.3"
|