adapt-schemas 2.1.0 → 3.0.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 +11 -11
- package/lib/Schema.js +24 -36
- package/lib/Schemas.js +33 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,16 +15,16 @@ import Schemas from 'adapt-schemas'
|
|
|
15
15
|
|
|
16
16
|
// Create and initialize the library
|
|
17
17
|
const library = new Schemas()
|
|
18
|
-
|
|
18
|
+
library.init()
|
|
19
19
|
|
|
20
20
|
// Load schemas from plugin directories
|
|
21
|
-
|
|
21
|
+
library.loadSchemas('**/schema/*.schema.json', {
|
|
22
22
|
cwd: './plugins',
|
|
23
23
|
ignore: ['**/node_modules/**']
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
// Validate data against a schema (defaults are applied automatically)
|
|
27
|
-
const validatedData =
|
|
27
|
+
const validatedData = library.validate('course', {
|
|
28
28
|
title: 'My Course'
|
|
29
29
|
})
|
|
30
30
|
```
|
|
@@ -50,20 +50,20 @@ const library = new Schemas({
|
|
|
50
50
|
Initializes the library and loads the base schema.
|
|
51
51
|
|
|
52
52
|
```javascript
|
|
53
|
-
|
|
53
|
+
library.init()
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
##### `loadSchemas(patterns, options)`
|
|
57
57
|
Loads schemas from directories matching glob patterns.
|
|
58
58
|
|
|
59
59
|
```javascript
|
|
60
|
-
|
|
60
|
+
library.loadSchemas('**/schema/*.schema.json', {
|
|
61
61
|
cwd: './plugins', // Base directory for patterns
|
|
62
62
|
ignore: ['**/excluded/**'] // Patterns to exclude
|
|
63
63
|
})
|
|
64
64
|
|
|
65
65
|
// Multiple patterns
|
|
66
|
-
|
|
66
|
+
library.loadSchemas([
|
|
67
67
|
'core/**/schema/*.schema.json',
|
|
68
68
|
'plugins/**/schema/*.schema.json'
|
|
69
69
|
], { ignore: ['**/node_modules/**'] })
|
|
@@ -73,7 +73,7 @@ await library.loadSchemas([
|
|
|
73
73
|
Registers a single schema file.
|
|
74
74
|
|
|
75
75
|
```javascript
|
|
76
|
-
|
|
76
|
+
library.registerSchema('/path/to/schema.json', {
|
|
77
77
|
replace: false // Replace existing schema with same name
|
|
78
78
|
})
|
|
79
79
|
```
|
|
@@ -82,7 +82,7 @@ await library.registerSchema('/path/to/schema.json', {
|
|
|
82
82
|
Retrieves and builds a schema by name.
|
|
83
83
|
|
|
84
84
|
```javascript
|
|
85
|
-
const schema =
|
|
85
|
+
const schema = library.getSchema('course', {
|
|
86
86
|
useCache: true, // Use cached build if available
|
|
87
87
|
compile: true, // Compile the schema
|
|
88
88
|
applyExtensions: true // Apply $patch extensions
|
|
@@ -93,7 +93,7 @@ const schema = await library.getSchema('course', {
|
|
|
93
93
|
Returns the built schema object.
|
|
94
94
|
|
|
95
95
|
```javascript
|
|
96
|
-
const schemaObj =
|
|
96
|
+
const schemaObj = library.getBuiltSchema('course')
|
|
97
97
|
console.log(schemaObj.properties)
|
|
98
98
|
```
|
|
99
99
|
|
|
@@ -101,7 +101,7 @@ console.log(schemaObj.properties)
|
|
|
101
101
|
Validates data against a named schema.
|
|
102
102
|
|
|
103
103
|
```javascript
|
|
104
|
-
const validated =
|
|
104
|
+
const validated = library.validate('course', inputData, {
|
|
105
105
|
useDefaults: true, // Apply schema defaults (default: true)
|
|
106
106
|
ignoreRequired: false // Ignore required field errors
|
|
107
107
|
})
|
|
@@ -269,7 +269,7 @@ The library throws `SchemaError` with the following codes:
|
|
|
269
269
|
import { SchemaError } from 'adapt-schemas'
|
|
270
270
|
|
|
271
271
|
try {
|
|
272
|
-
|
|
272
|
+
library.validate('course', data)
|
|
273
273
|
} catch (e) {
|
|
274
274
|
if (e instanceof SchemaError) {
|
|
275
275
|
console.log(e.code) // 'VALIDATION_FAILED'
|
package/lib/Schema.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
import { EventEmitter } from 'events'
|
|
3
|
-
import fs from 'fs
|
|
3
|
+
import fs from 'fs'
|
|
4
4
|
import SchemaError from './SchemaError.js'
|
|
5
5
|
import xss from 'xss'
|
|
6
6
|
|
|
@@ -114,38 +114,38 @@ class Schema extends EventEmitter {
|
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
116
|
* Determines whether the current schema build is valid using last modification timestamp
|
|
117
|
-
* @returns {
|
|
117
|
+
* @returns {boolean}
|
|
118
118
|
*/
|
|
119
|
-
|
|
119
|
+
isBuildValid () {
|
|
120
120
|
if (!this.built) return false
|
|
121
121
|
|
|
122
122
|
let schema = this
|
|
123
123
|
while (schema) {
|
|
124
|
-
const { mtimeMs } =
|
|
124
|
+
const { mtimeMs } = fs.statSync(schema.filePath)
|
|
125
125
|
if (mtimeMs > this.lastBuildTime) return false
|
|
126
|
-
schema =
|
|
126
|
+
schema = schema.getParent()
|
|
127
127
|
}
|
|
128
128
|
return true
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
132
|
* Returns the parent schema if $merge is defined (or the base schema if a root schema)
|
|
133
|
-
* @returns {
|
|
133
|
+
* @returns {Schema|undefined}
|
|
134
134
|
*/
|
|
135
|
-
|
|
135
|
+
getParent () {
|
|
136
136
|
if (this.name === BASE_SCHEMA_NAME) return undefined
|
|
137
137
|
|
|
138
138
|
const parentRef = this.raw?.$merge?.source?.$ref ?? BASE_SCHEMA_NAME
|
|
139
|
-
return
|
|
139
|
+
return this.schemaLibrary.getSchema(parentRef)
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
143
|
* Loads the schema file
|
|
144
|
-
* @returns {
|
|
144
|
+
* @returns {Schema} This instance
|
|
145
145
|
*/
|
|
146
|
-
|
|
146
|
+
load () {
|
|
147
147
|
try {
|
|
148
|
-
const content =
|
|
148
|
+
const content = fs.readFileSync(this.filePath, 'utf-8')
|
|
149
149
|
this.raw = JSON.parse(content)
|
|
150
150
|
this.name = this.raw.$anchor
|
|
151
151
|
} catch (e) {
|
|
@@ -177,54 +177,42 @@ class Schema extends EventEmitter {
|
|
|
177
177
|
* @param {boolean} options.compile Compile the schema (default: true)
|
|
178
178
|
* @param {boolean} options.applyExtensions Apply extension schemas (default: true)
|
|
179
179
|
* @param {function} options.extensionFilter Filter function for extensions
|
|
180
|
-
* @returns {
|
|
180
|
+
* @returns {Schema} This instance
|
|
181
181
|
*/
|
|
182
|
-
|
|
183
|
-
if (options.useCache !== false && this.enableCache &&
|
|
182
|
+
build (options = {}) {
|
|
183
|
+
if (options.useCache !== false && this.enableCache && this.isBuildValid()) {
|
|
184
184
|
return this
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
if (this.isBuilding) {
|
|
188
|
-
return new Promise(resolve => this._buildCallbacks.push(() => resolve(this)))
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
this.isBuilding = true
|
|
192
|
-
|
|
193
187
|
const { applyExtensions, extensionFilter } = options
|
|
194
188
|
|
|
195
189
|
let built = _.cloneDeep(this.raw)
|
|
196
|
-
let parent =
|
|
190
|
+
let parent = this.getParent()
|
|
197
191
|
|
|
198
192
|
while (parent) {
|
|
199
|
-
const parentBuilt = _.cloneDeep((
|
|
193
|
+
const parentBuilt = _.cloneDeep((parent.build({ ...options, compile: false })).built)
|
|
200
194
|
built = this.patch(parentBuilt, built, { strict: false })
|
|
201
|
-
parent =
|
|
195
|
+
parent = parent.getParent()
|
|
202
196
|
}
|
|
203
197
|
|
|
204
198
|
if (this.extensions.length) {
|
|
205
|
-
|
|
199
|
+
this.extensions.forEach(s => {
|
|
206
200
|
const applyPatch = typeof extensionFilter === 'function' ? extensionFilter(s) : applyExtensions !== false
|
|
207
|
-
if (applyPatch)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}))
|
|
201
|
+
if (!applyPatch) return
|
|
202
|
+
const extSchema = this.schemaLibrary.getSchema(s)
|
|
203
|
+
this.patch(built, extSchema.raw, { extendAnnotations: false })
|
|
204
|
+
})
|
|
212
205
|
}
|
|
213
206
|
|
|
214
207
|
this.built = built
|
|
215
208
|
|
|
216
209
|
if (options.compile !== false) {
|
|
217
|
-
this.compiledWithDefaults =
|
|
218
|
-
this.compiled =
|
|
210
|
+
this.compiledWithDefaults = this.validatorWithDefaults.compile(built)
|
|
211
|
+
this.compiled = this.validator.compile(built)
|
|
219
212
|
}
|
|
220
213
|
|
|
221
|
-
this.isBuilding = false
|
|
222
214
|
this.lastBuildTime = Date.now()
|
|
223
215
|
|
|
224
|
-
// Notify waiting callbacks
|
|
225
|
-
this._buildCallbacks.forEach(cb => cb())
|
|
226
|
-
this._buildCallbacks = []
|
|
227
|
-
|
|
228
216
|
this.emit('built', this)
|
|
229
217
|
return this
|
|
230
218
|
}
|
package/lib/Schemas.js
CHANGED
|
@@ -2,13 +2,13 @@ import _ from 'lodash'
|
|
|
2
2
|
import Ajv from 'ajv/dist/2020.js'
|
|
3
3
|
import { EventEmitter } from 'events'
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
|
-
import { glob } from 'glob'
|
|
6
5
|
import Keywords from './Keywords.js'
|
|
7
6
|
import path from 'path'
|
|
8
7
|
import safeRegex from 'safe-regex'
|
|
9
8
|
import Schema from './Schema.js'
|
|
10
9
|
import SchemaError from './SchemaError.js'
|
|
11
10
|
import XSSDefaults from './XSSDefaults.js'
|
|
11
|
+
import { globSync } from 'fs'
|
|
12
12
|
|
|
13
13
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
14
14
|
const BASE_SCHEMA_PATH = '../schema/base.schema.json'
|
|
@@ -61,7 +61,7 @@ class Schemas extends EventEmitter {
|
|
|
61
61
|
addUsedSchema: false,
|
|
62
62
|
allErrors: true,
|
|
63
63
|
allowUnionTypes: true,
|
|
64
|
-
loadSchema:
|
|
64
|
+
loadSchema: (uri) => (this.getSchema(uri, { compile: false })).built,
|
|
65
65
|
removeAdditional: 'all',
|
|
66
66
|
strict: false,
|
|
67
67
|
verbose: true,
|
|
@@ -95,12 +95,12 @@ class Schemas extends EventEmitter {
|
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
97
|
* Initializes the schema library by loading the base schema
|
|
98
|
-
* @returns {
|
|
98
|
+
* @returns {Schemas}
|
|
99
99
|
*/
|
|
100
|
-
|
|
100
|
+
init () {
|
|
101
101
|
if (this._initialized) return this
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
this.resetSchemaRegistry()
|
|
104
104
|
this._initialized = true
|
|
105
105
|
this.emit('initialized')
|
|
106
106
|
return this
|
|
@@ -108,11 +108,10 @@ class Schemas extends EventEmitter {
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* Empties the schema registry (with the exception of the base schema)
|
|
111
|
-
* @returns {Promise<void>}
|
|
112
111
|
*/
|
|
113
|
-
|
|
112
|
+
resetSchemaRegistry () {
|
|
114
113
|
this.schemas = {
|
|
115
|
-
base:
|
|
114
|
+
base: this.createSchema(path.resolve(__dirname, BASE_SCHEMA_PATH), { enableCache: true })
|
|
116
115
|
}
|
|
117
116
|
this.emit('reset')
|
|
118
117
|
}
|
|
@@ -161,33 +160,36 @@ class Schemas extends EventEmitter {
|
|
|
161
160
|
* @param {Object} options Glob options
|
|
162
161
|
* @param {string[]} options.ignore Patterns to exclude
|
|
163
162
|
* @param {string} options.cwd Base directory for glob patterns
|
|
164
|
-
* @returns {Promise<void>}
|
|
165
163
|
*/
|
|
166
|
-
|
|
164
|
+
loadSchemas (patterns, options = {}) {
|
|
167
165
|
if (!this._initialized) {
|
|
168
|
-
|
|
166
|
+
this.init()
|
|
169
167
|
}
|
|
170
168
|
|
|
171
169
|
const globOptions = {
|
|
172
170
|
cwd: options.cwd || process.cwd(),
|
|
173
|
-
|
|
174
|
-
ignore: options.ignore || []
|
|
171
|
+
exclude: options.ignore || []
|
|
175
172
|
}
|
|
176
173
|
|
|
177
174
|
const patternList = Array.isArray(patterns) ? patterns : [patterns]
|
|
178
175
|
const allFiles = []
|
|
179
176
|
|
|
180
177
|
for (const pattern of patternList) {
|
|
181
|
-
const files =
|
|
182
|
-
allFiles.push(...files)
|
|
178
|
+
const files = globSync(pattern, globOptions)
|
|
179
|
+
allFiles.push(...files.map(f => path.resolve(globOptions.cwd, f)))
|
|
183
180
|
}
|
|
184
181
|
|
|
185
182
|
// Deduplicate files
|
|
186
183
|
const uniqueFiles = [...new Set(allFiles)]
|
|
187
184
|
|
|
188
|
-
const results =
|
|
189
|
-
|
|
190
|
-
|
|
185
|
+
const results = uniqueFiles.map(f => {
|
|
186
|
+
try {
|
|
187
|
+
this.registerSchema(f)
|
|
188
|
+
} catch (err) {
|
|
189
|
+
return { status: 'rejected', reason: err }
|
|
190
|
+
}
|
|
191
|
+
return { status: 'fulfilled' }
|
|
192
|
+
})
|
|
191
193
|
|
|
192
194
|
results
|
|
193
195
|
.filter(r => r.status === 'rejected')
|
|
@@ -201,14 +203,14 @@ class Schemas extends EventEmitter {
|
|
|
201
203
|
* @param {string} filePath Path to the schema file
|
|
202
204
|
* @param {Object} options Extra options
|
|
203
205
|
* @param {boolean} options.replace Replace existing schema with same name
|
|
204
|
-
* @returns {
|
|
206
|
+
* @returns {Schema}
|
|
205
207
|
*/
|
|
206
|
-
|
|
208
|
+
registerSchema (filePath, options = {}) {
|
|
207
209
|
if (!_.isString(filePath)) {
|
|
208
210
|
throw new SchemaError('INVALID_PARAMS', 'filePath must be a string', { params: ['filePath'] })
|
|
209
211
|
}
|
|
210
212
|
|
|
211
|
-
const schema =
|
|
213
|
+
const schema = this.createSchema(filePath, options)
|
|
212
214
|
|
|
213
215
|
if (this.schemas[schema.name]) {
|
|
214
216
|
if (options.replace) {
|
|
@@ -251,9 +253,9 @@ class Schemas extends EventEmitter {
|
|
|
251
253
|
* Creates a new Schema instance
|
|
252
254
|
* @param {string} filePath Path to the schema file
|
|
253
255
|
* @param {Object} options Options passed to Schema constructor
|
|
254
|
-
* @returns {
|
|
256
|
+
* @returns {Schema}
|
|
255
257
|
*/
|
|
256
|
-
|
|
258
|
+
createSchema (filePath, options = {}) {
|
|
257
259
|
const schema = new Schema({
|
|
258
260
|
enableCache: options.enableCache ?? this.options.enableCache,
|
|
259
261
|
filePath,
|
|
@@ -296,9 +298,9 @@ class Schemas extends EventEmitter {
|
|
|
296
298
|
* @param {boolean} options.compile Compile the schema (default: true)
|
|
297
299
|
* @param {boolean} options.applyExtensions Apply extension schemas (default: true)
|
|
298
300
|
* @param {function} options.extensionFilter Filter function for extensions
|
|
299
|
-
* @returns {
|
|
301
|
+
* @returns {Schema}
|
|
300
302
|
*/
|
|
301
|
-
|
|
303
|
+
getSchema (schemaName, options = {}) {
|
|
302
304
|
const schema = this.schemas[schemaName]
|
|
303
305
|
if (!schema) {
|
|
304
306
|
throw new SchemaError('MISSING_SCHEMA', `Schema '${schemaName}' not found`, { schemaName })
|
|
@@ -311,10 +313,10 @@ class Schemas extends EventEmitter {
|
|
|
311
313
|
* @param {string} schemaName The schema name
|
|
312
314
|
* @param {Object} data The data to validate
|
|
313
315
|
* @param {Object} options Validation options
|
|
314
|
-
* @returns {
|
|
316
|
+
* @returns {Object} The validated data with defaults applied
|
|
315
317
|
*/
|
|
316
|
-
|
|
317
|
-
const schema =
|
|
318
|
+
validate (schemaName, data, options = {}) {
|
|
319
|
+
const schema = this.getSchema(schemaName)
|
|
318
320
|
return schema.validate(data, options)
|
|
319
321
|
}
|
|
320
322
|
|
|
@@ -322,10 +324,10 @@ class Schemas extends EventEmitter {
|
|
|
322
324
|
* Returns the built schema object
|
|
323
325
|
* @param {string} schemaName The schema name
|
|
324
326
|
* @param {Object} options Build options
|
|
325
|
-
* @returns {
|
|
327
|
+
* @returns {Object} The built schema object
|
|
326
328
|
*/
|
|
327
|
-
|
|
328
|
-
const schema =
|
|
329
|
+
getBuiltSchema (schemaName, options = {}) {
|
|
330
|
+
const schema = this.getSchema(schemaName)
|
|
329
331
|
return schema.built
|
|
330
332
|
}
|
|
331
333
|
|