adapt-schemas 1.0.0 → 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.
@@ -0,0 +1,32 @@
1
+ name: Release
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+
7
+ jobs:
8
+ release:
9
+ name: Release
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write # to be able to publish a GitHub release
13
+ issues: write # to be able to comment on released issues
14
+ pull-requests: write # to be able to comment on released pull requests
15
+ id-token: write # to enable use of OIDC for trusted publishing and npm provenance
16
+ steps:
17
+ - name: Checkout
18
+ uses: actions/checkout@v3
19
+ with:
20
+ fetch-depth: 0
21
+ - name: Setup Node.js
22
+ uses: actions/setup-node@v3
23
+ with:
24
+ node-version: 'lts/*'
25
+ - name: Update npm
26
+ run: npm install -g npm@latest
27
+ - name: Install dependencies
28
+ run: npm ci
29
+ - name: Release
30
+ env:
31
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32
+ run: npx semantic-release
@@ -0,0 +1,13 @@
1
+ name: Standard.js
2
+ on: push
3
+ jobs:
4
+ default:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@master
8
+ - uses: actions/setup-node@master
9
+ with:
10
+ node-version: 'lts/*'
11
+ cache: 'npm'
12
+ - run: npm ci
13
+ - run: npx standard
@@ -0,0 +1,13 @@
1
+ name: Tests
2
+ on: push
3
+ jobs:
4
+ default:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@master
8
+ - uses: actions/setup-node@master
9
+ with:
10
+ node-version: 'lts/*'
11
+ cache: 'npm'
12
+ - run: npm ci
13
+ - run: npm test
package/README.md CHANGED
@@ -43,11 +43,7 @@ const library = new Schemas({
43
43
  enableCache: true, // Enable schema build caching (default: true)
44
44
  xssWhitelist: {}, // Custom XSS whitelist tags/attributes
45
45
  xssWhitelistOverride: false, // Replace defaults instead of extending
46
- formatOverrides: {}, // Custom string format RegExp patterns
47
- directoryReplacements: { // Replacements for isDirectory keyword
48
- '$ROOT': '/app',
49
- '$DATA': '/app/data'
50
- }
46
+ formatOverrides: {} // Custom string format RegExp patterns
51
47
  })
52
48
  ```
53
49
 
@@ -153,7 +149,7 @@ Manually extends a schema with another.
153
149
  library.extendSchema('course', 'my-course-extension')
154
150
  ```
155
151
 
156
- ##### `addKeyword(definition)`
152
+ ##### `addKeyword(definition, options)`
157
153
  Adds a custom AJV keyword.
158
154
 
159
155
  ```javascript
@@ -162,6 +158,20 @@ library.addKeyword({
162
158
  type: 'number',
163
159
  validate: (schema, data) => data > 0
164
160
  })
161
+
162
+ // Override an existing keyword
163
+ library.addKeyword({
164
+ keyword: 'isPositive',
165
+ type: 'number',
166
+ validate: (schema, data) => data >= 0
167
+ }, { override: true })
168
+ ```
169
+
170
+ ##### `deregisterSchema(name)`
171
+ Removes a schema from the registry.
172
+
173
+ ```javascript
174
+ library.deregisterSchema('my-schema')
165
175
  ```
166
176
 
167
177
  ##### `addStringFormats(formats)`
@@ -257,7 +267,6 @@ The library includes these custom AJV keywords:
257
267
  | `isBytes` | Parses byte strings | `"1MB"` → `1048576` |
258
268
  | `isDate` | Parses date strings | `"2024-01-01"` → `Date` |
259
269
  | `isTimeMs` | Parses duration strings | `"7d"` → `604800000` |
260
- | `isDirectory` | Resolves path tokens | `"$ROOT/data"` → `"/app/data"` |
261
270
  | `isObjectId` | Marks ObjectId fields | No transformation |
262
271
 
263
272
  ## Error Handling
@@ -272,6 +281,7 @@ The library throws `SchemaError` with the following codes:
272
281
  | `INVALID_SCHEMA` | Schema fails JSON Schema validation |
273
282
  | `MISSING_SCHEMA` | Requested schema not found |
274
283
  | `VALIDATION_FAILED` | Data fails schema validation |
284
+ | `KEYWORD_EXISTS` | Keyword already defined |
275
285
  | `MODIFY_PROTECTED_ATTR` | Attempt to modify internal/read-only field |
276
286
 
277
287
  ```javascript
package/lib/Keywords.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import bytes from 'bytes'
2
2
  import ms from 'ms'
3
- import path from 'path'
4
3
 
5
4
  /**
6
5
  * Custom JSON schema keywords for AJV
@@ -8,16 +7,16 @@ import path from 'path'
8
7
  class Keywords {
9
8
  /**
10
9
  * Returns all custom keywords
11
- * @param {Object} directoryReplacements Replacements for isDirectory (e.g. { '$ROOT': '/app' })
12
10
  * @returns {Object[]} Array of AJV keyword definitions
13
11
  */
14
- static all(directoryReplacements = {}) {
12
+ static all () {
15
13
  const keywords = {
16
14
  /**
17
15
  * Parses byte string values (e.g., "1MB" -> 1048576)
18
16
  */
19
17
  isBytes: function () {
20
18
  return (value, { parentData, parentDataProperty }) => {
19
+ if (value === false) return false
21
20
  try {
22
21
  parentData[parentDataProperty] = bytes.parse(value)
23
22
  return true
@@ -32,6 +31,7 @@ class Keywords {
32
31
  */
33
32
  isDate: function () {
34
33
  return (value, { parentData, parentDataProperty }) => {
34
+ if (value === false) return false
35
35
  try {
36
36
  parentData[parentDataProperty] = new Date(value)
37
37
  return true
@@ -41,31 +41,12 @@ class Keywords {
41
41
  }
42
42
  },
43
43
 
44
- /**
45
- * Resolves directory path tokens ($ROOT, $DATA, $TEMP, etc.)
46
- */
47
- isDirectory: function () {
48
- const doReplace = value => {
49
- const replacements = Object.entries(directoryReplacements)
50
- return replacements.reduce((m, [k, v]) => {
51
- return m.startsWith(k) ? path.resolve(v, m.replace(k, '').slice(1)) : m
52
- }, value)
53
- }
54
- return (value, { parentData, parentDataProperty }) => {
55
- try {
56
- parentData[parentDataProperty] = doReplace(value)
57
- } catch (e) {
58
- // Keep original value on error
59
- }
60
- return true
61
- }
62
- },
63
-
64
44
  /**
65
45
  * Parses time duration strings into milliseconds (e.g., "7d" -> 604800000)
66
46
  */
67
47
  isTimeMs: function () {
68
48
  return (value, { parentData, parentDataProperty }) => {
49
+ if (value === false) return false
69
50
  try {
70
51
  parentData[parentDataProperty] = ms(value)
71
52
  return true
@@ -78,8 +59,8 @@ class Keywords {
78
59
  /**
79
60
  * Marker for ObjectId fields (no transformation, just marks the field)
80
61
  */
81
- isObjectId: function () {
82
- return () => true
62
+ isObjectId: function (value) {
63
+ return () => value
83
64
  }
84
65
  }
85
66
 
package/lib/Schema.js CHANGED
@@ -9,7 +9,7 @@ const BASE_SCHEMA_NAME = 'base'
9
9
  * Schema-specific error class
10
10
  */
11
11
  export class SchemaError extends Error {
12
- constructor(code, message, data = {}) {
12
+ constructor (code, message, data = {}) {
13
13
  super(message)
14
14
  this.code = code
15
15
  this.data = data
@@ -29,7 +29,7 @@ class Schema extends EventEmitter {
29
29
  * @param {Object} options.xssWhitelist XSS whitelist configuration
30
30
  * @param {SchemaLibrary} options.schemaLibrary Reference to the parent library
31
31
  */
32
- constructor({ enableCache, filePath, validator, xssWhitelist, schemaLibrary }) {
32
+ constructor ({ enableCache, filePath, validator, xssWhitelist, schemaLibrary }) {
33
33
  super()
34
34
 
35
35
  /**
@@ -115,7 +115,7 @@ class Schema extends EventEmitter {
115
115
  * Determines whether the current schema build is valid using last modification timestamp
116
116
  * @returns {Promise<boolean>}
117
117
  */
118
- async isBuildValid() {
118
+ async isBuildValid () {
119
119
  if (!this.built) return false
120
120
 
121
121
  let schema = this
@@ -131,7 +131,7 @@ class Schema extends EventEmitter {
131
131
  * Returns the parent schema if $merge is defined (or the base schema if a root schema)
132
132
  * @returns {Promise<Schema|undefined>}
133
133
  */
134
- async getParent() {
134
+ async getParent () {
135
135
  if (this.name === BASE_SCHEMA_NAME) return undefined
136
136
 
137
137
  const parentRef = this.raw?.$merge?.source?.$ref ?? BASE_SCHEMA_NAME
@@ -142,7 +142,7 @@ class Schema extends EventEmitter {
142
142
  * Loads the schema file
143
143
  * @returns {Promise<Schema>} This instance
144
144
  */
145
- async load() {
145
+ async load () {
146
146
  try {
147
147
  const content = await fs.readFile(this.filePath, 'utf-8')
148
148
  this.raw = JSON.parse(content)
@@ -178,7 +178,7 @@ class Schema extends EventEmitter {
178
178
  * @param {function} options.extensionFilter Filter function for extensions
179
179
  * @returns {Promise<Schema>}
180
180
  */
181
- async build(options = {}) {
181
+ async build (options = {}) {
182
182
  if (options.useCache !== false && this.enableCache && await this.isBuildValid()) {
183
183
  return this
184
184
  }
@@ -196,7 +196,7 @@ class Schema extends EventEmitter {
196
196
 
197
197
  while (parent) {
198
198
  const parentBuilt = _.cloneDeep((await parent.build({ ...options, compile: false })).built)
199
- built = this.patch(parentBuilt, built, { strict: !parent.name == BASE_SCHEMA_NAME })
199
+ built = this.patch(parentBuilt, built, { strict: false })
200
200
  parent = await parent.getParent()
201
201
  }
202
202
 
@@ -237,7 +237,7 @@ class Schema extends EventEmitter {
237
237
  * @param {boolean} options.strict Require $patch or $merge
238
238
  * @returns {Object} The base schema
239
239
  */
240
- patch(baseSchema, patchSchema, options = {}) {
240
+ patch (baseSchema, patchSchema, options = {}) {
241
241
  const opts = _.defaults(options, {
242
242
  extendAnnotations: patchSchema.$anchor !== BASE_SCHEMA_NAME,
243
243
  overwriteProperties: true,
@@ -284,7 +284,7 @@ class Schema extends EventEmitter {
284
284
  * @param {boolean} options.ignoreRequired Ignore required field errors
285
285
  * @returns {Object} The validated data
286
286
  */
287
- validate(dataToValidate, options = {}) {
287
+ validate (dataToValidate, options = {}) {
288
288
  const opts = _.defaults(options, { useDefaults: true, ignoreRequired: false })
289
289
  const data = _.defaults(_.cloneDeep(dataToValidate), opts.useDefaults ? this.getObjectDefaults() : {})
290
290
 
@@ -322,7 +322,7 @@ class Schema extends EventEmitter {
322
322
  * @param {Object} schema Schema to use (defaults to built schema)
323
323
  * @returns {Object} The sanitised data
324
324
  */
325
- sanitise(dataToSanitise, options = {}, schema) {
325
+ sanitise (dataToSanitise, options = {}, schema) {
326
326
  const opts = _.defaults(options, {
327
327
  isInternal: false,
328
328
  isReadOnly: false,
@@ -363,7 +363,7 @@ class Schema extends EventEmitter {
363
363
  * Adds an extension schema
364
364
  * @param {string} extSchemaName Extension schema name
365
365
  */
366
- addExtension(extSchemaName) {
366
+ addExtension (extSchemaName) {
367
367
  if (!this.extensions.includes(extSchemaName)) {
368
368
  this.extensions.push(extSchemaName)
369
369
  }
@@ -374,7 +374,7 @@ class Schema extends EventEmitter {
374
374
  * @param {Object} schema Schema to extract defaults from
375
375
  * @returns {Object} The defaults object
376
376
  */
377
- getObjectDefaults(schema) {
377
+ getObjectDefaults (schema) {
378
378
  schema = schema ?? this.built
379
379
  const props = schema.properties ?? schema.$merge?.with?.properties ?? schema.$patch?.with?.properties
380
380
 
package/lib/Schemas.js CHANGED
@@ -16,7 +16,7 @@ const BASE_SCHEMA_PATH = '../schema/base.schema.json'
16
16
  * Schema library errors
17
17
  */
18
18
  export class SchemaError extends Error {
19
- constructor(code, message, data = {}) {
19
+ constructor (code, message, data = {}) {
20
20
  super(message)
21
21
  this.code = code
22
22
  this.data = data
@@ -35,17 +35,15 @@ class Schemas extends EventEmitter {
35
35
  * @param {Object} options.xssWhitelist Custom XSS whitelist tags/attributes
36
36
  * @param {Boolean} options.xssWhitelistOverride Replace default whitelist instead of extending
37
37
  * @param {Object} options.formatOverrides Custom string format RegExp patterns
38
- * @param {Object} options.directoryReplacements Replacements for isDirectory keyword (e.g. { '$ROOT': '/app' })
39
38
  */
40
- constructor(options = {}) {
39
+ constructor (options = {}) {
41
40
  super()
42
41
 
43
42
  this.options = _.defaults(options, {
44
43
  enableCache: true,
45
44
  xssWhitelist: {},
46
45
  xssWhitelistOverride: false,
47
- formatOverrides: {},
48
- directoryReplacements: {}
46
+ formatOverrides: {}
49
47
  })
50
48
 
51
49
  /**
@@ -82,7 +80,7 @@ class Schemas extends EventEmitter {
82
80
  removeAdditional: 'all',
83
81
  strict: false,
84
82
  verbose: true,
85
- keywords: Keywords.all(this.options.directoryReplacements)
83
+ keywords: Keywords.all()
86
84
  })
87
85
 
88
86
  this.addStringFormats({
@@ -102,7 +100,7 @@ class Schemas extends EventEmitter {
102
100
  * Initializes the schema library by loading the base schema
103
101
  * @returns {Promise<SchemaLibrary>}
104
102
  */
105
- async init() {
103
+ async init () {
106
104
  if (this._initialized) return this
107
105
 
108
106
  await this.resetSchemaRegistry()
@@ -115,7 +113,7 @@ class Schemas extends EventEmitter {
115
113
  * Empties the schema registry (with the exception of the base schema)
116
114
  * @returns {Promise<void>}
117
115
  */
118
- async resetSchemaRegistry() {
116
+ async resetSchemaRegistry () {
119
117
  this.schemas = {
120
118
  base: await this.createSchema(path.resolve(__dirname, BASE_SCHEMA_PATH), { enableCache: true })
121
119
  }
@@ -126,7 +124,7 @@ class Schemas extends EventEmitter {
126
124
  * Adds string formats to the Ajv validator
127
125
  * @param {Object<string, RegExp>} formats
128
126
  */
129
- addStringFormats(formats) {
127
+ addStringFormats (formats) {
130
128
  Object.entries(formats).forEach(([name, re]) => {
131
129
  const isUnsafe = !safeRegex(re)
132
130
  if (isUnsafe) {
@@ -139,9 +137,17 @@ class Schemas extends EventEmitter {
139
137
  /**
140
138
  * Adds a new keyword to be used in JSON schemas
141
139
  * @param {Object} definition AJV keyword definition
140
+ * @param {Object} options Configuration options
141
+ * @param {Boolean} options.override Whether an existing keyword should be overridden
142
142
  */
143
- addKeyword(definition) {
143
+ addKeyword (definition, options = {}) {
144
144
  try {
145
+ if (this.validator.getKeyword(definition.keyword)) {
146
+ if (options.override !== true) {
147
+ throw new SchemaError('KEYWORD_EXISTS', 'Keyword already exists')
148
+ }
149
+ this.validator.removeKeyword(definition.keyword)
150
+ }
145
151
  this.validator.addKeyword(definition)
146
152
  } catch (e) {
147
153
  this.emit('warning', `Failed to define keyword '${definition.keyword}', ${e}`)
@@ -156,7 +162,7 @@ class Schemas extends EventEmitter {
156
162
  * @param {string} options.cwd Base directory for glob patterns
157
163
  * @returns {Promise<void>}
158
164
  */
159
- async loadSchemas(patterns, options = {}) {
165
+ async loadSchemas (patterns, options = {}) {
160
166
  if (!this._initialized) {
161
167
  await this.init()
162
168
  }
@@ -196,7 +202,7 @@ class Schemas extends EventEmitter {
196
202
  * @param {boolean} options.replace Replace existing schema with same name
197
203
  * @returns {Promise<Schema>}
198
204
  */
199
- async registerSchema(filePath, options = {}) {
205
+ async registerSchema (filePath, options = {}) {
200
206
  if (!_.isString(filePath)) {
201
207
  throw new SchemaError('INVALID_PARAMS', 'filePath must be a string', { params: ['filePath'] })
202
208
  }
@@ -229,7 +235,7 @@ class Schemas extends EventEmitter {
229
235
  * Deregisters a single JSON schema
230
236
  * @param {string} name Schema name to deregister
231
237
  */
232
- deregisterSchema(name) {
238
+ deregisterSchema (name) {
233
239
  if (this.schemas[name]) {
234
240
  delete this.schemas[name]
235
241
  }
@@ -246,7 +252,7 @@ class Schemas extends EventEmitter {
246
252
  * @param {Object} options Options passed to Schema constructor
247
253
  * @returns {Promise<Schema>}
248
254
  */
249
- async createSchema(filePath, options = {}) {
255
+ async createSchema (filePath, options = {}) {
250
256
  const schema = new Schema({
251
257
  enableCache: options.enableCache ?? this.options.enableCache,
252
258
  filePath,
@@ -267,7 +273,7 @@ class Schemas extends EventEmitter {
267
273
  * @param {string} baseSchemaName The name of the schema to extend
268
274
  * @param {string} extSchemaName The name of the schema to extend with
269
275
  */
270
- extendSchema(baseSchemaName, extSchemaName) {
276
+ extendSchema (baseSchemaName, extSchemaName) {
271
277
  const baseSchema = this.schemas[baseSchemaName]
272
278
  if (baseSchema) {
273
279
  baseSchema.addExtension(extSchemaName)
@@ -290,7 +296,7 @@ class Schemas extends EventEmitter {
290
296
  * @param {function} options.extensionFilter Filter function for extensions
291
297
  * @returns {Promise<Schema>}
292
298
  */
293
- async getSchema(schemaName, options = {}) {
299
+ async getSchema (schemaName, options = {}) {
294
300
  const schema = this.schemas[schemaName]
295
301
  if (!schema) {
296
302
  throw new SchemaError('MISSING_SCHEMA', `Schema '${schemaName}' not found`, { schemaName })
@@ -305,7 +311,7 @@ class Schemas extends EventEmitter {
305
311
  * @param {Object} options Validation options
306
312
  * @returns {Promise<Object>} The validated data with defaults applied
307
313
  */
308
- async validate(schemaName, data, options = {}) {
314
+ async validate (schemaName, data, options = {}) {
309
315
  const schema = await this.getSchema(schemaName)
310
316
  return schema.validate(data, options)
311
317
  }
@@ -316,7 +322,7 @@ class Schemas extends EventEmitter {
316
322
  * @param {Object} options Build options
317
323
  * @returns {Promise<Object>} The built schema object
318
324
  */
319
- async getBuiltSchema(schemaName, options = {}) {
325
+ async getBuiltSchema (schemaName, options = {}) {
320
326
  const schema = await this.getSchema(schemaName)
321
327
  return schema.built
322
328
  }
@@ -326,7 +332,7 @@ class Schemas extends EventEmitter {
326
332
  * @param {string} schemaName The schema name
327
333
  * @returns {Promise<Object>} The defaults object
328
334
  */
329
- async getSchemaDefaults(schemaName) {
335
+ async getSchemaDefaults (schemaName) {
330
336
  const schema = await this.getSchema(schemaName)
331
337
  return schema.getObjectDefaults()
332
338
  }
@@ -336,7 +342,7 @@ class Schemas extends EventEmitter {
336
342
  * @param {string} schemaName Schema name (defaults to 'course')
337
343
  * @returns {Promise<Object>} The _globals defaults
338
344
  */
339
- async getGlobalsDefaults(schemaName = 'course') {
345
+ async getGlobalsDefaults (schemaName = 'course') {
340
346
  const schema = await this.getSchema(schemaName)
341
347
  const defaults = schema.getObjectDefaults()
342
348
  return defaults._globals || {}
@@ -346,7 +352,7 @@ class Schemas extends EventEmitter {
346
352
  * Returns list of all registered schema names
347
353
  * @returns {string[]}
348
354
  */
349
- getSchemaNames() {
355
+ getSchemaNames () {
350
356
  return Object.keys(this.schemas)
351
357
  }
352
358
 
@@ -354,7 +360,7 @@ class Schemas extends EventEmitter {
354
360
  * Returns information about all registered schemas
355
361
  * @returns {Object}
356
362
  */
357
- getSchemaInfo() {
363
+ getSchemaInfo () {
358
364
  return Object.entries(this.schemas).reduce((info, [name, schema]) => {
359
365
  info[name] = {
360
366
  filePath: schema.filePath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-schemas",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Standalone JSON Schema library for the Adapt framework",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -29,5 +29,30 @@
29
29
  "ms": "^2.1.3",
30
30
  "safe-regex": "^2.1.1",
31
31
  "xss": "^1.0.14"
32
+ },
33
+ "devDependencies": {
34
+ "@semantic-release/git": "^10.0.1",
35
+ "conventional-changelog-eslint": "^6.0.0",
36
+ "semantic-release": "^25.0.3",
37
+ "standard": "^17.1.2"
38
+ },
39
+ "release": {
40
+ "plugins": [
41
+ [
42
+ "@semantic-release/commit-analyzer",
43
+ {
44
+ "preset": "eslint"
45
+ }
46
+ ],
47
+ [
48
+ "@semantic-release/release-notes-generator",
49
+ {
50
+ "preset": "eslint"
51
+ }
52
+ ],
53
+ "@semantic-release/npm",
54
+ "@semantic-release/github",
55
+ "@semantic-release/git"
56
+ ]
32
57
  }
33
58
  }
package/test.js CHANGED
@@ -9,7 +9,7 @@ import fs from 'fs/promises'
9
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
10
  const hasSpecifiedPath = Boolean(process.argv[2])
11
11
 
12
- async function setupTestSchemas() {
12
+ async function setupTestSchemas () {
13
13
  if (hasSpecifiedPath) return
14
14
  // Create test schema directory
15
15
  const testSchemaDir = path.join(__dirname, 'test-schemas')
@@ -17,51 +17,51 @@ async function setupTestSchemas() {
17
17
 
18
18
  // Create a course schema with _globals
19
19
  const courseSchema = {
20
- "$schema": "https://json-schema.org/draft/2020-12/schema",
21
- "$anchor": "course",
22
- "$merge": {
23
- "source": { "$ref": "base" },
24
- "with": {
25
- "properties": {
26
- "title": {
27
- "type": "string",
28
- "description": "Course title",
29
- "default": "Untitled Course"
20
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
21
+ $anchor: 'course',
22
+ $merge: {
23
+ source: { $ref: 'base' },
24
+ with: {
25
+ properties: {
26
+ title: {
27
+ type: 'string',
28
+ description: 'Course title',
29
+ default: 'Untitled Course'
30
30
  },
31
- "description": {
32
- "type": "string",
33
- "description": "Course description",
34
- "default": ""
31
+ description: {
32
+ type: 'string',
33
+ description: 'Course description',
34
+ default: ''
35
35
  },
36
- "_globals": {
37
- "type": "object",
38
- "description": "Global settings",
39
- "properties": {
40
- "_accessibility": {
41
- "type": "object",
42
- "properties": {
43
- "_isEnabled": {
44
- "type": "boolean",
45
- "default": true
36
+ _globals: {
37
+ type: 'object',
38
+ description: 'Global settings',
39
+ properties: {
40
+ _accessibility: {
41
+ type: 'object',
42
+ properties: {
43
+ _isEnabled: {
44
+ type: 'boolean',
45
+ default: true
46
46
  },
47
- "skipNavigationText": {
48
- "type": "string",
49
- "default": "Skip navigation"
47
+ skipNavigationText: {
48
+ type: 'string',
49
+ default: 'Skip navigation'
50
50
  }
51
51
  },
52
- "required": [
53
- "skipNavigationText"
52
+ required: [
53
+ 'skipNavigationText'
54
54
  ]
55
55
  },
56
- "_extensions": {
57
- "type": "object",
58
- "properties": {
59
- "_trickle": {
60
- "type": "object",
61
- "properties": {
62
- "incompleteContent": {
63
- "type": "string",
64
- "default": "There is incomplete content above"
56
+ _extensions: {
57
+ type: 'object',
58
+ properties: {
59
+ _trickle: {
60
+ type: 'object',
61
+ properties: {
62
+ incompleteContent: {
63
+ type: 'string',
64
+ default: 'There is incomplete content above'
65
65
  }
66
66
  }
67
67
  }
@@ -70,31 +70,31 @@ async function setupTestSchemas() {
70
70
  }
71
71
  }
72
72
  },
73
- "required": ["title"]
73
+ required: ['title']
74
74
  }
75
75
  }
76
76
  }
77
77
 
78
78
  // Create a content schema
79
79
  const contentSchema = {
80
- "$schema": "https://json-schema.org/draft/2020-12/schema",
81
- "$anchor": "content",
82
- "$merge": {
83
- "source": { "$ref": "base" },
84
- "with": {
85
- "properties": {
86
- "_type": {
87
- "type": "string",
88
- "description": "Content type"
80
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
81
+ $anchor: 'content',
82
+ $merge: {
83
+ source: { $ref: 'base' },
84
+ with: {
85
+ properties: {
86
+ _type: {
87
+ type: 'string',
88
+ description: 'Content type'
89
89
  },
90
- "body": {
91
- "type": "string",
92
- "description": "Content body",
93
- "default": ""
90
+ body: {
91
+ type: 'string',
92
+ description: 'Content body',
93
+ default: ''
94
94
  },
95
- "_isOptional": {
96
- "type": "boolean",
97
- "default": false
95
+ _isOptional: {
96
+ type: 'boolean',
97
+ default: false
98
98
  }
99
99
  }
100
100
  }
@@ -103,39 +103,39 @@ async function setupTestSchemas() {
103
103
 
104
104
  // Create a component schema that extends content
105
105
  const componentSchema = {
106
- "$schema": "https://json-schema.org/draft/2020-12/schema",
107
- "$anchor": "component",
108
- "$merge": {
109
- "source": { "$ref": "content" },
110
- "with": {
111
- "properties": {
112
- "_component": {
113
- "type": "string",
114
- "description": "Component type"
106
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
107
+ $anchor: 'component',
108
+ $merge: {
109
+ source: { $ref: 'content' },
110
+ with: {
111
+ properties: {
112
+ _component: {
113
+ type: 'string',
114
+ description: 'Component type'
115
115
  }
116
116
  },
117
- "required": ["_component"]
117
+ required: ['_component']
118
118
  }
119
119
  }
120
120
  }
121
121
 
122
122
  // Create a patch schema that extends course
123
123
  const coursePatchSchema = {
124
- "$schema": "https://json-schema.org/draft/2020-12/schema",
125
- "$anchor": "course-extension",
126
- "$patch": {
127
- "source": { "$ref": "course" },
128
- "with": {
129
- "properties": {
130
- "_globals": {
131
- "type": "object",
132
- "properties": {
133
- "_myPlugin": {
134
- "type": "object",
135
- "properties": {
136
- "buttonLabel": {
137
- "type": "string",
138
- "default": "Click me"
124
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
125
+ $anchor: 'course-extension',
126
+ $patch: {
127
+ source: { $ref: 'course' },
128
+ with: {
129
+ properties: {
130
+ _globals: {
131
+ type: 'object',
132
+ properties: {
133
+ _myPlugin: {
134
+ type: 'object',
135
+ properties: {
136
+ buttonLabel: {
137
+ type: 'string',
138
+ default: 'Click me'
139
139
  }
140
140
  }
141
141
  }
@@ -166,10 +166,10 @@ async function setupTestSchemas() {
166
166
  return testSchemaDir
167
167
  }
168
168
 
169
- async function runTests() {
169
+ async function runTests () {
170
170
  console.log('=== Adapt Schema Library Tests ===\n')
171
171
 
172
- const testSchemaDir = hasSpecifiedPath
172
+ const testSchemaDir = hasSpecifiedPath
173
173
  ? path.join(__dirname, process.argv[2])
174
174
  : await setupTestSchemas()
175
175
 
@@ -177,10 +177,7 @@ async function runTests() {
177
177
  // Test 1: Initialize library
178
178
  console.log('Test 1: Initialize library')
179
179
  const library = new Schemas({
180
- enableCache: true,
181
- directoryReplacements: {
182
- '$ROOT': process.cwd()
183
- }
180
+ enableCache: true
184
181
  })
185
182
  await library.init()
186
183
  console.log(' ✓ Library initialized\n')
@@ -202,12 +199,12 @@ async function runTests() {
202
199
  // Test 4: Get defaults
203
200
  console.log('Test 4: Get schema defaults')
204
201
  const courseDefaults = await library.getSchemaDefaults('course')
205
- console.log(` ✓ Course defaults:`, JSON.stringify(courseDefaults, null, 4).split('\n').map(l => ' ' + l).join('\n'), '\n')
202
+ console.log(' ✓ Course defaults:', JSON.stringify(courseDefaults, null, 4).split('\n').map(l => ' ' + l).join('\n'), '\n')
206
203
 
207
204
  // Test 5: Get _globals defaults
208
205
  console.log('Test 5: Get _globals defaults')
209
206
  const globalsDefaults = await library.getGlobalsDefaults('course')
210
- console.log(` ✓ _globals defaults:`, JSON.stringify(globalsDefaults, null, 4).split('\n').map(l => ' ' + l).join('\n'), '\n')
207
+ console.log(' ✓ _globals defaults:', JSON.stringify(globalsDefaults, null, 4).split('\n').map(l => ' ' + l).join('\n'), '\n')
211
208
 
212
209
  // Test 6: Validate data
213
210
  console.log('Test 6: Validate data')
@@ -234,7 +231,7 @@ async function runTests() {
234
231
  console.log('Test 7b: Type validation error')
235
232
  try {
236
233
  await library.validate('course', {
237
- title: 12345 // Should be string, not number
234
+ title: 12345 // Should be string, not number
238
235
  })
239
236
  console.log(' ✗ Should have thrown validation error\n')
240
237
  } catch (e) {
@@ -267,22 +264,22 @@ async function runTests() {
267
264
  // Create another test schema to trigger the event
268
265
  const newSchemaPath = path.join(testSchemaDir, 'test-event.schema.json')
269
266
  await fs.writeFile(newSchemaPath, JSON.stringify({
270
- "$schema": "https://json-schema.org/draft/2020-12/schema",
271
- "$anchor": "test-event",
272
- "$merge": {
273
- "source": { "$ref": "base" },
274
- "with": { "properties": { "test": { "type": "string" } } }
267
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
268
+ $anchor: 'test-event',
269
+ $merge: {
270
+ source: { $ref: 'base' },
271
+ with: { properties: { test: { type: 'string' } } }
275
272
  }
276
273
  }))
277
274
  await library.registerSchema(newSchemaPath)
278
275
  console.log('')
279
276
 
280
277
  console.log('=== All tests passed! ===')
281
-
282
278
  } finally {
283
- if (hasSpecifiedPath) return
284
- // Cleanup
285
- await fs.rm(testSchemaDir, { recursive: true, force: true })
279
+ if (!hasSpecifiedPath) {
280
+ // Cleanup
281
+ await fs.rm(testSchemaDir, { recursive: true, force: true })
282
+ }
286
283
  }
287
284
  }
288
285