adapt-schemas 1.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 +292 -0
- package/index.js +7 -0
- package/lib/Keywords.js +98 -0
- package/lib/Schema.js +389 -0
- package/lib/Schemas.js +370 -0
- package/lib/XSSDefaults.js +145 -0
- package/package.json +33 -0
- package/schema/base.schema.json +13 -0
- package/test.js +289 -0
package/lib/Schemas.js
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import Ajv from 'ajv/dist/2020.js'
|
|
3
|
+
import { EventEmitter } from 'events'
|
|
4
|
+
import { glob } from 'glob'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { fileURLToPath } from 'url'
|
|
7
|
+
import safeRegex from 'safe-regex'
|
|
8
|
+
import Schema from './Schema.js'
|
|
9
|
+
import Keywords from './Keywords.js'
|
|
10
|
+
import XSSDefaults from './XSSDefaults.js'
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
13
|
+
const BASE_SCHEMA_PATH = '../schema/base.schema.json'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Schema library errors
|
|
17
|
+
*/
|
|
18
|
+
export class SchemaError extends Error {
|
|
19
|
+
constructor(code, message, data = {}) {
|
|
20
|
+
super(message)
|
|
21
|
+
this.code = code
|
|
22
|
+
this.data = data
|
|
23
|
+
this.name = 'SchemaError'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Standalone JSON Schema library for the Adapt framework
|
|
29
|
+
*/
|
|
30
|
+
class Schemas extends EventEmitter {
|
|
31
|
+
/**
|
|
32
|
+
* Creates a new Schemas instance
|
|
33
|
+
* @param {Object} options Configuration options
|
|
34
|
+
* @param {Boolean} options.enableCache Enable schema caching (default: true)
|
|
35
|
+
* @param {Object} options.xssWhitelist Custom XSS whitelist tags/attributes
|
|
36
|
+
* @param {Boolean} options.xssWhitelistOverride Replace default whitelist instead of extending
|
|
37
|
+
* @param {Object} options.formatOverrides Custom string format RegExp patterns
|
|
38
|
+
* @param {Object} options.directoryReplacements Replacements for isDirectory keyword (e.g. { '$ROOT': '/app' })
|
|
39
|
+
*/
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
super()
|
|
42
|
+
|
|
43
|
+
this.options = _.defaults(options, {
|
|
44
|
+
enableCache: true,
|
|
45
|
+
xssWhitelist: {},
|
|
46
|
+
xssWhitelistOverride: false,
|
|
47
|
+
formatOverrides: {},
|
|
48
|
+
directoryReplacements: {}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reference to all registered schemas
|
|
53
|
+
* @type {Object<string, Schema>}
|
|
54
|
+
*/
|
|
55
|
+
this.schemas = {}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Temporary store of extension schemas
|
|
59
|
+
* @type {Object<string, string[]>}
|
|
60
|
+
*/
|
|
61
|
+
this.schemaExtensions = {}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Tags and attributes to be whitelisted by the XSS filter
|
|
65
|
+
* @type {Object}
|
|
66
|
+
*/
|
|
67
|
+
this.xssWhitelist = Object.assign(
|
|
68
|
+
{},
|
|
69
|
+
this.options.xssWhitelistOverride ? {} : XSSDefaults,
|
|
70
|
+
this.options.xssWhitelist
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reference to the Ajv instance
|
|
75
|
+
* @type {Ajv}
|
|
76
|
+
*/
|
|
77
|
+
this.validator = new Ajv({
|
|
78
|
+
addUsedSchema: false,
|
|
79
|
+
allErrors: true,
|
|
80
|
+
allowUnionTypes: true,
|
|
81
|
+
loadSchema: this.getSchema.bind(this),
|
|
82
|
+
removeAdditional: 'all',
|
|
83
|
+
strict: false,
|
|
84
|
+
verbose: true,
|
|
85
|
+
keywords: Keywords.all(this.options.directoryReplacements)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
this.addStringFormats({
|
|
89
|
+
'date-time': /[A-Za-z0-9:+()]+/,
|
|
90
|
+
time: /^(\d{2}):(\d{2}):(\d{2})\+(\d{2}):(\d{2})$/,
|
|
91
|
+
uri: /^(.+):\/\/(www\.)?[-a-zA-Z0-9@:%_+.~#?&//=]{1,256}/
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
if (this.options.formatOverrides) {
|
|
95
|
+
this.addStringFormats(this.options.formatOverrides)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this._initialized = false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Initializes the schema library by loading the base schema
|
|
103
|
+
* @returns {Promise<SchemaLibrary>}
|
|
104
|
+
*/
|
|
105
|
+
async init() {
|
|
106
|
+
if (this._initialized) return this
|
|
107
|
+
|
|
108
|
+
await this.resetSchemaRegistry()
|
|
109
|
+
this._initialized = true
|
|
110
|
+
this.emit('initialized')
|
|
111
|
+
return this
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Empties the schema registry (with the exception of the base schema)
|
|
116
|
+
* @returns {Promise<void>}
|
|
117
|
+
*/
|
|
118
|
+
async resetSchemaRegistry() {
|
|
119
|
+
this.schemas = {
|
|
120
|
+
base: await this.createSchema(path.resolve(__dirname, BASE_SCHEMA_PATH), { enableCache: true })
|
|
121
|
+
}
|
|
122
|
+
this.emit('reset')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Adds string formats to the Ajv validator
|
|
127
|
+
* @param {Object<string, RegExp>} formats
|
|
128
|
+
*/
|
|
129
|
+
addStringFormats(formats) {
|
|
130
|
+
Object.entries(formats).forEach(([name, re]) => {
|
|
131
|
+
const isUnsafe = !safeRegex(re)
|
|
132
|
+
if (isUnsafe) {
|
|
133
|
+
this.emit('warning', `Unsafe RegExp for format '${name}' (${re}), using default`)
|
|
134
|
+
}
|
|
135
|
+
this.validator.addFormat(name, isUnsafe ? /.*/ : re)
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Adds a new keyword to be used in JSON schemas
|
|
141
|
+
* @param {Object} definition AJV keyword definition
|
|
142
|
+
*/
|
|
143
|
+
addKeyword(definition) {
|
|
144
|
+
try {
|
|
145
|
+
this.validator.addKeyword(definition)
|
|
146
|
+
} catch (e) {
|
|
147
|
+
this.emit('warning', `Failed to define keyword '${definition.keyword}', ${e}`)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Loads schemas from directories matching glob patterns
|
|
153
|
+
* @param {string|string[]} patterns Glob pattern(s) for schema directories
|
|
154
|
+
* @param {Object} options Glob options
|
|
155
|
+
* @param {string[]} options.ignore Patterns to exclude
|
|
156
|
+
* @param {string} options.cwd Base directory for glob patterns
|
|
157
|
+
* @returns {Promise<void>}
|
|
158
|
+
*/
|
|
159
|
+
async loadSchemas(patterns, options = {}) {
|
|
160
|
+
if (!this._initialized) {
|
|
161
|
+
await this.init()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const globOptions = {
|
|
165
|
+
cwd: options.cwd || process.cwd(),
|
|
166
|
+
absolute: true,
|
|
167
|
+
ignore: options.ignore || []
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const patternList = Array.isArray(patterns) ? patterns : [patterns]
|
|
171
|
+
const allFiles = []
|
|
172
|
+
|
|
173
|
+
for (const pattern of patternList) {
|
|
174
|
+
const files = await glob(pattern, globOptions)
|
|
175
|
+
allFiles.push(...files)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Deduplicate files
|
|
179
|
+
const uniqueFiles = [...new Set(allFiles)]
|
|
180
|
+
|
|
181
|
+
const results = await Promise.allSettled(
|
|
182
|
+
uniqueFiles.map(f => this.registerSchema(f))
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
results
|
|
186
|
+
.filter(r => r.status === 'rejected')
|
|
187
|
+
.forEach(r => this.emit('warning', r.reason))
|
|
188
|
+
|
|
189
|
+
this.emit('schemasLoaded', Object.keys(this.schemas))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Registers a single JSON schema for use
|
|
194
|
+
* @param {string} filePath Path to the schema file
|
|
195
|
+
* @param {Object} options Extra options
|
|
196
|
+
* @param {boolean} options.replace Replace existing schema with same name
|
|
197
|
+
* @returns {Promise<Schema>}
|
|
198
|
+
*/
|
|
199
|
+
async registerSchema(filePath, options = {}) {
|
|
200
|
+
if (!_.isString(filePath)) {
|
|
201
|
+
throw new SchemaError('INVALID_PARAMS', 'filePath must be a string', { params: ['filePath'] })
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const schema = await this.createSchema(filePath, options)
|
|
205
|
+
|
|
206
|
+
if (this.schemas[schema.name]) {
|
|
207
|
+
if (options.replace) {
|
|
208
|
+
this.deregisterSchema(schema.name)
|
|
209
|
+
} else {
|
|
210
|
+
throw new SchemaError('SCHEMA_EXISTS', `Schema '${schema.name}' already exists`, {
|
|
211
|
+
schemaName: schema.name,
|
|
212
|
+
filePath
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.schemas[schema.name] = schema
|
|
218
|
+
this.schemaExtensions?.[schema.name]?.forEach(s => schema.addExtension(s))
|
|
219
|
+
|
|
220
|
+
if (schema.raw.$patch) {
|
|
221
|
+
this.extendSchema(schema.raw.$patch?.source?.$ref, schema.name)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.emit('schemaRegistered', schema.name, filePath)
|
|
225
|
+
return schema
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Deregisters a single JSON schema
|
|
230
|
+
* @param {string} name Schema name to deregister
|
|
231
|
+
*/
|
|
232
|
+
deregisterSchema(name) {
|
|
233
|
+
if (this.schemas[name]) {
|
|
234
|
+
delete this.schemas[name]
|
|
235
|
+
}
|
|
236
|
+
// Remove schema from any extensions lists
|
|
237
|
+
Object.entries(this.schemaExtensions).forEach(([base, extensions]) => {
|
|
238
|
+
this.schemaExtensions[base] = extensions.filter(s => s !== name)
|
|
239
|
+
})
|
|
240
|
+
this.emit('schemaDeregistered', name)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Creates a new Schema instance
|
|
245
|
+
* @param {string} filePath Path to the schema file
|
|
246
|
+
* @param {Object} options Options passed to Schema constructor
|
|
247
|
+
* @returns {Promise<Schema>}
|
|
248
|
+
*/
|
|
249
|
+
async createSchema(filePath, options = {}) {
|
|
250
|
+
const schema = new Schema({
|
|
251
|
+
enableCache: options.enableCache ?? this.options.enableCache,
|
|
252
|
+
filePath,
|
|
253
|
+
validator: this.validator,
|
|
254
|
+
xssWhitelist: this.xssWhitelist,
|
|
255
|
+
schemaLibrary: this,
|
|
256
|
+
...options
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
this.schemaExtensions?.[schema.name]?.forEach(s => schema.addExtension(s))
|
|
260
|
+
delete this.schemaExtensions?.[schema.name]
|
|
261
|
+
|
|
262
|
+
return schema.load()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Extends an existing schema with extra properties
|
|
267
|
+
* @param {string} baseSchemaName The name of the schema to extend
|
|
268
|
+
* @param {string} extSchemaName The name of the schema to extend with
|
|
269
|
+
*/
|
|
270
|
+
extendSchema(baseSchemaName, extSchemaName) {
|
|
271
|
+
const baseSchema = this.schemas[baseSchemaName]
|
|
272
|
+
if (baseSchema) {
|
|
273
|
+
baseSchema.addExtension(extSchemaName)
|
|
274
|
+
} else {
|
|
275
|
+
if (!this.schemaExtensions[baseSchemaName]) {
|
|
276
|
+
this.schemaExtensions[baseSchemaName] = []
|
|
277
|
+
}
|
|
278
|
+
this.schemaExtensions[baseSchemaName].push(extSchemaName)
|
|
279
|
+
}
|
|
280
|
+
this.emit('schemaExtended', baseSchemaName, extSchemaName)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Retrieves the specified schema
|
|
285
|
+
* @param {string} schemaName The name of the schema to return
|
|
286
|
+
* @param {Object} options
|
|
287
|
+
* @param {boolean} options.useCache Use cached build if available
|
|
288
|
+
* @param {boolean} options.compile Compile the schema (default: true)
|
|
289
|
+
* @param {boolean} options.applyExtensions Apply extension schemas (default: true)
|
|
290
|
+
* @param {function} options.extensionFilter Filter function for extensions
|
|
291
|
+
* @returns {Promise<Schema>}
|
|
292
|
+
*/
|
|
293
|
+
async getSchema(schemaName, options = {}) {
|
|
294
|
+
const schema = this.schemas[schemaName]
|
|
295
|
+
if (!schema) {
|
|
296
|
+
throw new SchemaError('MISSING_SCHEMA', `Schema '${schemaName}' not found`, { schemaName })
|
|
297
|
+
}
|
|
298
|
+
return schema.build(options)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Validates data against a named schema
|
|
303
|
+
* @param {string} schemaName The schema name
|
|
304
|
+
* @param {Object} data The data to validate
|
|
305
|
+
* @param {Object} options Validation options
|
|
306
|
+
* @returns {Promise<Object>} The validated data with defaults applied
|
|
307
|
+
*/
|
|
308
|
+
async validate(schemaName, data, options = {}) {
|
|
309
|
+
const schema = await this.getSchema(schemaName)
|
|
310
|
+
return schema.validate(data, options)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Returns the built schema object
|
|
315
|
+
* @param {string} schemaName The schema name
|
|
316
|
+
* @param {Object} options Build options
|
|
317
|
+
* @returns {Promise<Object>} The built schema object
|
|
318
|
+
*/
|
|
319
|
+
async getBuiltSchema(schemaName, options = {}) {
|
|
320
|
+
const schema = await this.getSchema(schemaName)
|
|
321
|
+
return schema.built
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Returns schema defaults as a structured object
|
|
326
|
+
* @param {string} schemaName The schema name
|
|
327
|
+
* @returns {Promise<Object>} The defaults object
|
|
328
|
+
*/
|
|
329
|
+
async getSchemaDefaults(schemaName) {
|
|
330
|
+
const schema = await this.getSchema(schemaName)
|
|
331
|
+
return schema.getObjectDefaults()
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Extracts _globals defaults from the course schema
|
|
336
|
+
* @param {string} schemaName Schema name (defaults to 'course')
|
|
337
|
+
* @returns {Promise<Object>} The _globals defaults
|
|
338
|
+
*/
|
|
339
|
+
async getGlobalsDefaults(schemaName = 'course') {
|
|
340
|
+
const schema = await this.getSchema(schemaName)
|
|
341
|
+
const defaults = schema.getObjectDefaults()
|
|
342
|
+
return defaults._globals || {}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Returns list of all registered schema names
|
|
347
|
+
* @returns {string[]}
|
|
348
|
+
*/
|
|
349
|
+
getSchemaNames() {
|
|
350
|
+
return Object.keys(this.schemas)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Returns information about all registered schemas
|
|
355
|
+
* @returns {Object}
|
|
356
|
+
*/
|
|
357
|
+
getSchemaInfo() {
|
|
358
|
+
return Object.entries(this.schemas).reduce((info, [name, schema]) => {
|
|
359
|
+
info[name] = {
|
|
360
|
+
filePath: schema.filePath,
|
|
361
|
+
extensions: schema.extensions,
|
|
362
|
+
hasParent: !!schema.raw?.$merge?.source?.$ref,
|
|
363
|
+
isPatch: !!schema.raw?.$patch
|
|
364
|
+
}
|
|
365
|
+
return info
|
|
366
|
+
}, {})
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export default Schemas
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default XSS whitelist for HTML tags and attributes
|
|
3
|
+
*/
|
|
4
|
+
export default {
|
|
5
|
+
a: ['class', 'href', 'rel', 'target', 'title'],
|
|
6
|
+
abbr: ['title'],
|
|
7
|
+
address: [],
|
|
8
|
+
area: ['alt', 'coords', 'href', 'shape'],
|
|
9
|
+
article: [],
|
|
10
|
+
aside: ['aria-hidden', 'class', 'role'],
|
|
11
|
+
audio: [
|
|
12
|
+
'autoplay',
|
|
13
|
+
'controls',
|
|
14
|
+
'crossorigin',
|
|
15
|
+
'loop',
|
|
16
|
+
'muted',
|
|
17
|
+
'preload',
|
|
18
|
+
'src'
|
|
19
|
+
],
|
|
20
|
+
b: [],
|
|
21
|
+
bdi: ['dir'],
|
|
22
|
+
bdo: ['dir'],
|
|
23
|
+
big: [],
|
|
24
|
+
blockquote: ['cite'],
|
|
25
|
+
br: [],
|
|
26
|
+
button: ['class'],
|
|
27
|
+
caption: [],
|
|
28
|
+
center: [],
|
|
29
|
+
cite: [],
|
|
30
|
+
code: [],
|
|
31
|
+
col: ['align', 'span', 'valign', 'width'],
|
|
32
|
+
colgroup: ['align', 'span', 'valign', 'width'],
|
|
33
|
+
data: [],
|
|
34
|
+
dd: [],
|
|
35
|
+
del: ['datetime'],
|
|
36
|
+
dfn: [],
|
|
37
|
+
details: ['open'],
|
|
38
|
+
div: [
|
|
39
|
+
'aria-describedby',
|
|
40
|
+
'aria-description',
|
|
41
|
+
'aria-label',
|
|
42
|
+
'aria-hidden',
|
|
43
|
+
'class',
|
|
44
|
+
'role',
|
|
45
|
+
'tabindex'
|
|
46
|
+
],
|
|
47
|
+
dl: [],
|
|
48
|
+
dt: [],
|
|
49
|
+
em: [],
|
|
50
|
+
figcaption: [],
|
|
51
|
+
figure: ['class'],
|
|
52
|
+
font: ['color', 'face', 'size'],
|
|
53
|
+
footer: [],
|
|
54
|
+
h1: ['class'],
|
|
55
|
+
h2: ['class'],
|
|
56
|
+
h3: ['class'],
|
|
57
|
+
h4: ['class'],
|
|
58
|
+
h5: ['class'],
|
|
59
|
+
h6: ['class'],
|
|
60
|
+
header: [],
|
|
61
|
+
hr: [],
|
|
62
|
+
i: [],
|
|
63
|
+
img: [
|
|
64
|
+
'alt',
|
|
65
|
+
'aria-hidden',
|
|
66
|
+
'aria-label',
|
|
67
|
+
'class',
|
|
68
|
+
'height',
|
|
69
|
+
'loading',
|
|
70
|
+
'src',
|
|
71
|
+
'title',
|
|
72
|
+
'width'
|
|
73
|
+
],
|
|
74
|
+
ins: ['datetime'],
|
|
75
|
+
kbd: [],
|
|
76
|
+
li: ['class'],
|
|
77
|
+
mark: [],
|
|
78
|
+
math: [],
|
|
79
|
+
mfrac: [],
|
|
80
|
+
mi: [],
|
|
81
|
+
mn: [],
|
|
82
|
+
mo: [],
|
|
83
|
+
mover: [],
|
|
84
|
+
mrow: [],
|
|
85
|
+
ms: [],
|
|
86
|
+
mspace: [],
|
|
87
|
+
msub: [],
|
|
88
|
+
msubsup: [],
|
|
89
|
+
msup: [],
|
|
90
|
+
mtext: [],
|
|
91
|
+
munder: [],
|
|
92
|
+
munderover: [],
|
|
93
|
+
nav: [],
|
|
94
|
+
ol: ['class'],
|
|
95
|
+
p: ['lang'],
|
|
96
|
+
pre: [],
|
|
97
|
+
q: [],
|
|
98
|
+
rp: [],
|
|
99
|
+
rt: [],
|
|
100
|
+
ruby: [],
|
|
101
|
+
s: [],
|
|
102
|
+
samp: [],
|
|
103
|
+
section: [],
|
|
104
|
+
small: [],
|
|
105
|
+
span: [
|
|
106
|
+
'aria-describedby',
|
|
107
|
+
'aria-description',
|
|
108
|
+
'aria-label',
|
|
109
|
+
'aria-hidden',
|
|
110
|
+
'class',
|
|
111
|
+
'role',
|
|
112
|
+
'tabindex'
|
|
113
|
+
],
|
|
114
|
+
sub: [],
|
|
115
|
+
summary: [],
|
|
116
|
+
sup: [],
|
|
117
|
+
strong: [],
|
|
118
|
+
strike: [],
|
|
119
|
+
table: ['align', 'border', 'width', 'valign'],
|
|
120
|
+
tbody: ['align', 'valign'],
|
|
121
|
+
td: ['align', 'colspan', 'rowspan', 'valign', 'width'],
|
|
122
|
+
tfoot: ['align', 'valign'],
|
|
123
|
+
th: ['align', 'colspan', 'rowspan', 'valign', 'width'],
|
|
124
|
+
thead: ['align', 'valign'],
|
|
125
|
+
time: [],
|
|
126
|
+
tr: ['align', 'rowspan', 'valign'],
|
|
127
|
+
tt: [],
|
|
128
|
+
u: [],
|
|
129
|
+
ul: ['class'],
|
|
130
|
+
var: [],
|
|
131
|
+
video: [
|
|
132
|
+
'autoplay',
|
|
133
|
+
'controls',
|
|
134
|
+
'crossorigin',
|
|
135
|
+
'loop',
|
|
136
|
+
'muted',
|
|
137
|
+
'playsinline',
|
|
138
|
+
'poster',
|
|
139
|
+
'preload',
|
|
140
|
+
'src',
|
|
141
|
+
'height',
|
|
142
|
+
'width'
|
|
143
|
+
],
|
|
144
|
+
wbr: []
|
|
145
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "adapt-schemas",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Standalone JSON Schema library for the Adapt framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./Schemas": "./lib/Schemas.js",
|
|
10
|
+
"./Schema": "./lib/Schema.js",
|
|
11
|
+
"./Keywords": "./lib/Keywords.js",
|
|
12
|
+
"./XSSDefaults": "./lib/XSSDefaults.js"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node test.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"adapt",
|
|
19
|
+
"json-schema",
|
|
20
|
+
"schema",
|
|
21
|
+
"validation"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"ajv": "^8.12.0",
|
|
26
|
+
"bytes": "^3.1.2",
|
|
27
|
+
"glob": "^10.0.0",
|
|
28
|
+
"lodash": "^4.17.21",
|
|
29
|
+
"ms": "^2.1.3",
|
|
30
|
+
"safe-regex": "^2.1.1",
|
|
31
|
+
"xss": "^1.0.14"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$anchor": "base",
|
|
4
|
+
"description": "The base schema inherited by all other schemas",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"_id": {
|
|
8
|
+
"description": "Unique identifier",
|
|
9
|
+
"type": "string",
|
|
10
|
+
"isObjectId": true
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|