adapt-schemas 2.0.1 → 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 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
- await library.init()
18
+ library.init()
19
19
 
20
20
  // Load schemas from plugin directories
21
- await library.loadSchemas('**/schema/*.schema.json', {
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 = await library.validate('course', {
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
- await library.init()
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
- await library.loadSchemas('**/schema/*.schema.json', {
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
- await library.loadSchemas([
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
- await library.registerSchema('/path/to/schema.json', {
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 = await library.getSchema('course', {
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 = await library.getBuiltSchema('course')
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 = await library.validate('course', inputData, {
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
- await library.validate('course', data)
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/promises'
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 {Promise<boolean>}
117
+ * @returns {boolean}
118
118
  */
119
- async isBuildValid () {
119
+ isBuildValid () {
120
120
  if (!this.built) return false
121
121
 
122
122
  let schema = this
123
123
  while (schema) {
124
- const { mtimeMs } = await fs.stat(schema.filePath)
124
+ const { mtimeMs } = fs.statSync(schema.filePath)
125
125
  if (mtimeMs > this.lastBuildTime) return false
126
- schema = await schema.getParent()
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 {Promise<Schema|undefined>}
133
+ * @returns {Schema|undefined}
134
134
  */
135
- async getParent () {
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 await this.schemaLibrary.getSchema(parentRef)
139
+ return this.schemaLibrary.getSchema(parentRef)
140
140
  }
141
141
 
142
142
  /**
143
143
  * Loads the schema file
144
- * @returns {Promise<Schema>} This instance
144
+ * @returns {Schema} This instance
145
145
  */
146
- async load () {
146
+ load () {
147
147
  try {
148
- const content = await fs.readFile(this.filePath, 'utf-8')
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 {Promise<Schema>}
180
+ * @returns {Schema} This instance
181
181
  */
182
- async build (options = {}) {
183
- if (options.useCache !== false && this.enableCache && await this.isBuildValid()) {
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 = await this.getParent()
190
+ let parent = this.getParent()
197
191
 
198
192
  while (parent) {
199
- const parentBuilt = _.cloneDeep((await parent.build({ ...options, compile: false })).built)
193
+ const parentBuilt = _.cloneDeep((parent.build({ ...options, compile: false })).built)
200
194
  built = this.patch(parentBuilt, built, { strict: false })
201
- parent = await parent.getParent()
195
+ parent = parent.getParent()
202
196
  }
203
197
 
204
198
  if (this.extensions.length) {
205
- await Promise.all(this.extensions.map(async s => {
199
+ this.extensions.forEach(s => {
206
200
  const applyPatch = typeof extensionFilter === 'function' ? extensionFilter(s) : applyExtensions !== false
207
- if (applyPatch) {
208
- const extSchema = await this.schemaLibrary.getSchema(s)
209
- this.patch(built, extSchema.raw, { extendAnnotations: false })
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 = await this.validatorWithDefaults.compileAsync(built)
218
- this.compiled = await this.validator.compileAsync(built)
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
  }
@@ -379,22 +367,22 @@ class Schema extends EventEmitter {
379
367
  * @param {Function} predicate - `(schemaField) => boolean`
380
368
  * @param {Object} [schema] - Internal: schema properties to walk (defaults to built.properties)
381
369
  * @param {string} [parentPath=''] - Internal: the slash-delimited path accumulated so far
382
- * @returns {Array<Object>} Array of `{ path, key, data, value }` matches
370
+ * @returns {Array<Object>} Array of `{ path, key, data, value, schemaField }` matches
383
371
  */
384
372
  walk (data, predicate, schema, parentPath = '') {
385
373
  schema = schema ?? this.built.properties
386
374
  const matches = []
387
- for (const [key, val] of Object.entries(schema)) {
375
+ for (const [key, schemaField] of Object.entries(schema)) {
388
376
  if (data[key] === undefined) continue
389
377
  const currentPath = parentPath ? `${parentPath}/${key}` : key
390
- if (val.properties) {
391
- matches.push(...this.walk(data[key], predicate, val.properties, currentPath))
392
- } else if (val?.items?.properties) {
378
+ if (schemaField.properties) {
379
+ matches.push(...this.walk(data[key], predicate, schemaField.properties, currentPath))
380
+ } else if (schemaField?.items?.properties) {
393
381
  data[key].forEach((item, i) => {
394
- matches.push(...this.walk(item, predicate, val.items.properties, `${currentPath}/${i}`))
382
+ matches.push(...this.walk(item, predicate, schemaField.items.properties, `${currentPath}/${i}`))
395
383
  })
396
- } else if (predicate(val)) {
397
- matches.push({ path: currentPath, key, data, value: data[key] })
384
+ } else if (predicate(schemaField)) {
385
+ matches.push({ path: currentPath, key, data, value: data[key], schemaField })
398
386
  }
399
387
  }
400
388
  return matches
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: async (uri) => (await this.getSchema(uri, { compile: false })).built,
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 {Promise<SchemaLibrary>}
98
+ * @returns {Schemas}
99
99
  */
100
- async init () {
100
+ init () {
101
101
  if (this._initialized) return this
102
102
 
103
- await this.resetSchemaRegistry()
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
- async resetSchemaRegistry () {
112
+ resetSchemaRegistry () {
114
113
  this.schemas = {
115
- base: await this.createSchema(path.resolve(__dirname, BASE_SCHEMA_PATH), { enableCache: true })
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
- async loadSchemas (patterns, options = {}) {
164
+ loadSchemas (patterns, options = {}) {
167
165
  if (!this._initialized) {
168
- await this.init()
166
+ this.init()
169
167
  }
170
168
 
171
169
  const globOptions = {
172
170
  cwd: options.cwd || process.cwd(),
173
- absolute: true,
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 = await glob(pattern, globOptions)
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 = await Promise.allSettled(
189
- uniqueFiles.map(f => this.registerSchema(f))
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 {Promise<Schema>}
206
+ * @returns {Schema}
205
207
  */
206
- async registerSchema (filePath, options = {}) {
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 = await this.createSchema(filePath, options)
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 {Promise<Schema>}
256
+ * @returns {Schema}
255
257
  */
256
- async createSchema (filePath, options = {}) {
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 {Promise<Schema>}
301
+ * @returns {Schema}
300
302
  */
301
- async getSchema (schemaName, options = {}) {
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 {Promise<Object>} The validated data with defaults applied
316
+ * @returns {Object} The validated data with defaults applied
315
317
  */
316
- async validate (schemaName, data, options = {}) {
317
- const schema = await this.getSchema(schemaName)
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 {Promise<Object>} The built schema object
327
+ * @returns {Object} The built schema object
326
328
  */
327
- async getBuiltSchema (schemaName, options = {}) {
328
- const schema = await this.getSchema(schemaName)
329
+ getBuiltSchema (schemaName, options = {}) {
330
+ const schema = this.getSchema(schemaName)
329
331
  return schema.built
330
332
  }
331
333
 
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/cgkineo/adapt-schemas"
6
6
  },
7
- "version": "2.0.1",
7
+ "version": "3.0.0",
8
8
  "description": "Standalone JSON Schema library for the Adapt framework",
9
9
  "type": "module",
10
10
  "main": "index.js",