schema-fun
Version:
JSON schema tools
180 lines • 9.35 kB
JavaScript
import { expandSchemaReferences, findPropertyInSchema, findRelationshipsInSchema, findTransientPropertiesInSchema, propertyIsRequiredInSchema } from './schemas.js';
/**
* A collection of schemas.
*
* The registry is a collection of name-value pairs, where the values are schemas. Schema IDs are arbitrary strings,
* though the recommended practice is to use absolute or relative retrival URIs, as described in the
* [JSON Schema documentation]{@link https://json-schema.org/understanding-json-schema/structuring#schema-identification}.
*
* The main purpose of a registry is to hold schemas that may contain references to one another, and to facilitate
* reference resolution. The registry has a SchemaRefTranslator function that translates $ref values into schema IDs.
* (It defaults to the identity function.)
*
* The registry offers methods that encapsulate the following schema functions:
* - findPropertyInSchema
* - findRelationshipsInSchema
* - findTransientPropertiesInSchema
* These methods omit the underlying functions' resolveSchemaRef parameters, because the registry itself has a schema
* reference resolver, which assumes that referenced schemas reside in the same registry and that their names can be
* determined by calling the SchemaRefTranslator.
*/
export class SchemaRegistry {
schemas = {};
schemaRefTranslator;
/**
* Create a new SchemaRegistry.
*
* @param schemaRefTranslator A function that translates schema references (the value of $ref properties in a schema)
* into schema IDs. It defaults to the identity function.
*/
constructor(schemaRefTranslator = (refPath) => refPath) {
this.schemaRefTranslator = schemaRefTranslator;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Registering and getting schemas
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Clear the registry.
*/
clear() {
this.schemas = {};
}
/**
* Deregister a schema.
*
* @param schemaId The schema ID.
*/
deregisterSchema(schemaId) {
delete this.schemas[schemaId];
}
/**
* Retrieve a schema from the registry.
*
* @param schemaId The schema ID.
* @returns The schema, or undefined if none was registered with the specified name.
*/
getSchema(schemaId) {
return this.schemas[schemaId];
}
/**
* Register a schema.
*
* If another schema has already been registered with the specified name, it will be replaced.
*
* @param schemaId The schema ID.
* @param schema The schema to add to the registry.
*/
registerSchema(schemaId, schema) {
this.schemas[schemaId] = Object.freeze(schema);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Using schemas
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Expand references to other schemas.
*
* The returned schema may contain circular references and is therefore not serializable as JSON without special
* handling.
*
* @param schema The schema to expand.
* @param resolveSchemaRef A function that resolves references to other schemas.
* @returns A new schema without schema references. It may contain circular references.
*/
async expandSchemaReferences(schema) {
return await expandSchemaReferences(schema, this.resolveSchemaRef.bind(this));
}
/**
* Find one property in a schema, traversing referenced schemas if necessary.
*
* @param schema The schema to search.
* @param path The property path to find, in dot-and-bracket notation or as an array.
* @returns The property schema if found, or else undefined.
*/
findPropertyInSchema(schema, path) {
return findPropertyInSchema(schema, path, this.resolveSchemaRef.bind(this)); // TODO Or (ref: string) => this.resolveRef(ref)
}
/**
* Find relationships in a schema.
*
* A relationship is a schema node that has entityTypeName and storage properties. This describes a relationship between
* two documents, with entityTypeName identifying the type of the child document. The schema node's storage property
* indicates whether it is stored
* - As a reference,
* - As an inverse reference
* - Or inline, as a copy of the related document.
* The list of relationships returned may optionally be limited to specified storage types using the allowedStorage
* parameter.
*
* If limitToPath is set, this function will only traverse path through the schema hierarchy. Otherwise, it will find
* all relationships.
*
* Since relationships my be cyclical, this function stops traversing the schema hierarchy whenever either
* - The end of limitToPath is reached,
* - limitToPath is not set and a specified maxDepth is reached,
* - Or limitToPath is not set and the function encounters a schema node it has already visited.
*
* @param schema The schema in which to look for relationships.
* @param resolveSchemaRef A function that resolves references to other schemas.
* @param allowedStorage A list of storage classes. If specified, relationships with other storage classes are ignored.
* @param limitToPath A property path (in dot-and-bracket notation or as an array) to traverse. If specified, only
* relationships in this path will be returned.
* @param maxDepth A maximum depth to traverse. Ignored if limitToPath is set.
* @returns A list of relationships contained in the schema.
*/
findRelationshipsInSchema(schema, allowedStorage, limitToPath, maxDepth = undefined) {
return findRelationshipsInSchema(schema, this.resolveSchemaRef.bind(this), allowedStorage, limitToPath, maxDepth);
}
/**
* List all the transient properties of a schema.
*
* Transient properties are identified by the custom JSON schema attribute "transient" being set to true.
*
* If limitToPath is set, this function will only traverse path through the schema hierarchy. Otherwise, it will find
* all relationships.
*
* Since relationships my be cyclical, this function stops traversing the schema hierarchy whenever either
* - The end of limitToPath is reached,
* - limitToPath is not set and a specified maxDepth is reached,
* - Or limitToPath is not set and the function encounters a schema node it has already visited.
*
* @param schema The schema in which to look for transient properties.
* @param resolveSchemaRef A function that resolves references to other schemas.
* @param limitToPath A property path (in dot-and-bracket notation or as an array) to traverse. If specified, only
* relationships in this path will be returned.
* @param maxDepth A maximum depth to traverse. Ignored if limitToPath is set.
* @param currentPath A parameter used when the function calls itself recursively. Should not be set by other callers.
* @param nodesTraversedInPath A parameter used when the function calls itself recursively. Should not be set by other
* callers.
* @returns An array of transient property paths in dot-and-bracket notation
*/
findTransientPropertiesInSchema(schema, limitToPath, maxDepth = undefined) {
return findTransientPropertiesInSchema(schema, this.resolveSchemaRef.bind(this), limitToPath, maxDepth);
}
/**
* Determine whether a property is required by a schema.
*
* A property is required if all of its ancestors are required. If any ancestor is optional, the property is optional.
* Therefore, calling this function on a subschema may produca a different result than calling it on the parent schema.
*
* "Required" status is not recorded in the property itself but in its parent object schema.
*
* For efficiency, this function does not always check that the specified property exists. It only tranverses the schema
* until some non-required ancestor is encountered. If the property does not exist, this function will return false.
*
* @param schema The schema to search.
* @param path The property path to check, in dot-and-bracket notation or as an array.
* @param resolveSchemaRef A function that resolves references to other schemas.
* @returns `true` if the property is required, or `false` if it is optional or does not exist.
*/
propertyIsRequiredInSchema(schema, path) {
return propertyIsRequiredInSchema(schema, path, this.resolveSchemaRef.bind(this));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Schema references
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
resolveSchemaRef(ref) {
const schemaId = this.schemaRefTranslator(ref);
return this.getSchema(schemaId);
}
}
//# sourceMappingURL=registry.js.map