UNPKG

@composedb/devtools

Version:

Development tools for ComposeDB projects.

367 lines (366 loc) 13.4 kB
function _check_private_redeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } } function _class_apply_descriptor_get(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; } function _class_apply_descriptor_set(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } } function _class_extract_field_descriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); } function _class_private_field_get(receiver, privateMap) { var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get"); return _class_apply_descriptor_get(receiver, descriptor); } function _class_private_field_init(obj, privateMap, value) { _check_private_redeclaration(obj, privateMap); privateMap.set(obj, value); } function _class_private_field_set(receiver, privateMap, value) { var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set"); _class_apply_descriptor_set(receiver, descriptor, value); return value; } import { DOC_ID_FIELD } from '../constants.js'; import { viewRuntimeToModel } from '../utils.js'; import { parseSchema } from './parser.js'; import { isCommonScalar } from './scalars.js'; export function createReference(name) { return { $ref: `#/$defs/${name}` }; } export function extractReference(ref) { const name = ref.$ref.split('#/$defs/')[1]; if (name == null || name.length === 0) { throw new Error(`Could not extract name from reference: ${ref.$ref}`); } return name; } var _def = /*#__PURE__*/ new WeakMap(), _refs = /*#__PURE__*/ new WeakMap(), _src = /*#__PURE__*/ new WeakMap(); export class SchemaCompiler { _getReference(name) { const ref = _class_private_field_get(this, _refs)[name]; if (ref == null) { throw new Error(`Reference ${name} does not exist`); } return ref; } _extractDefinitions(name) { const unref = this._getReference(name); const defs = { [name]: unref.schema }; for (const subrefName of unref.refs){ Object.assign(defs, this._extractDefinitions(subrefName)); } return defs; } compile() { // Ensure enums are tracked in common embeds for (const name of Object.keys(_class_private_field_get(this, _src).enums)){ _class_private_field_get(this, _def).commonEmbeds.push(name); } // Only compile embedded objects in first pass so they can be added to models in second pass for (const [name, definition] of Object.entries(_class_private_field_get(this, _src).objects)){ if (_class_private_field_get(this, _src).models[name] == null) { this._compileEmbedObject(name, definition); _class_private_field_get(this, _def).commonEmbeds.push(name); } } // Compile models for (const [name, definition] of Object.entries(_class_private_field_get(this, _src).models)){ const object = _class_private_field_get(this, _src).objects[name]; if (object == null) { throw new Error(`Missing object definition for model: ${name}`); } // Compile object schema with embedded references for model _class_private_field_get(this, _def).models[name] = this._compileModel(name, definition, object); } return _class_private_field_get(this, _def); } _compileEmbedObject(name, definition) { const existing = _class_private_field_get(this, _refs)[name]; if (existing) { return existing; } const object = { type: 'object', title: name, properties: {}, additionalProperties: false }; const required = []; let refs = []; for (const [key, field] of Object.entries(definition.properties)){ if (field.required) { required.push(key); } let value; switch(field.type){ case 'enum': value = this._compileEnum(name, key, field); break; case 'list': value = this._compileList(name, key, field); break; case 'object': value = this._compileObjectReference(name, key, field); break; case 'scalar': value = this._compileScalar(field); break; case 'view': throw new Error(`Unsupported view on field ${key} of object ${name}. Views can only be set on models.`); } if (value == null) { throw new Error(`Could not compile value for field ${key} of object ${name}`); } object.properties[key] = value.schema; refs = [ ...refs, ...value.refs ]; } if (required.length !== 0) { object.required = required; } const schemaWithRefs = { schema: object, refs }; _class_private_field_get(this, _refs)[name] = schemaWithRefs; return schemaWithRefs; } _compileList(objectName, fieldName, definition) { const list = { type: 'array', maxItems: definition.maxLength }; if (definition.minLength != null) { list.minItems = definition.minLength; } let item; switch(definition.item.type){ case 'enum': item = this._compileEnum(objectName, fieldName, definition.item); break; case 'object': item = this._compileObjectReference(objectName, fieldName, definition.item); break; case 'scalar': item = this._compileScalar(definition.item); break; } if (item == null) { throw new Error(`Could not compile item schema for list ${fieldName} of object ${objectName}`); } list.items = item.schema; return { schema: list, refs: item.refs }; } _compileObjectReference(objectName, fieldName, definition) { if (_class_private_field_get(this, _src).models[definition.name] != null) { throw new Error(`Unsupported reference to model ${definition.name} in field ${fieldName} of object ${objectName}. References can only be made to embedded objects.`); } const target = _class_private_field_get(this, _src).objects[definition.name]; if (target == null) { throw new Error(`Missing object ${definition.name} referenced in field ${fieldName} of object ${objectName}`); } // Ensure object is compiled and injected to definitions record this._compileEmbedObject(definition.name, target); return { schema: { $ref: `#/$defs/${definition.name}` }, refs: [ definition.name ] }; } _compileEnum(objectName, fieldName, definition) { const values = _class_private_field_get(this, _src).enums[definition.name]; if (values == null) { throw new Error(`Missing enum ${definition.name} referenced in field ${fieldName} of object ${objectName}`); } if (_class_private_field_get(this, _refs)[definition.name] == null) { _class_private_field_get(this, _refs)[definition.name] = { schema: { type: 'string', title: definition.name, enum: values }, refs: [] }; } return { schema: { $ref: `#/$defs/${definition.name}` }, refs: [ definition.name ] }; } _compileScalar(definition) { const title = definition.schema.title; // Scalars without title or that have properties changed from the defaults are injected directly as they are not reusable if (title == null || !isCommonScalar(definition.schema)) { return { schema: definition.schema, refs: [] }; } // Scalars with title are injected in definitions and referenced if (_class_private_field_get(this, _refs)[title] == null) { _class_private_field_get(this, _refs)[title] = { schema: definition.schema, refs: [] }; } return { schema: { $ref: `#/$defs/${title}` }, refs: [ title ] }; } _compileModel(name, modelDefinition, objectDefinition) { const indices = objectDefinition.indices.map((idx)=>{ return { fields: idx.fields }; }); if (modelDefinition.action === 'load') { const views = {}; for (const [key, field] of Object.entries(objectDefinition.properties)){ if (key === DOC_ID_FIELD) { continue; } if (field.type === 'view') { views[key] = viewRuntimeToModel(field); } else { throw new Error(`Unsupported property ${key} on model ${name}, only views can be added to loaded models`); } } return { ...modelDefinition, views, indices }; } if (objectDefinition.properties[DOC_ID_FIELD] != null) { throw new Error(`Unsupported ${DOC_ID_FIELD} field on model ${name}, the ${DOC_ID_FIELD} field is reserved by ComposeDB`); } const views = {}; const object = { $schema: 'https://json-schema.org/draft/2020-12/schema', type: 'object', properties: {}, additionalProperties: false }; const required = []; let refs = []; for (const [key, field] of Object.entries(objectDefinition.properties)){ if (field.required && field.type !== 'view') { required.push(key); } let value = null; switch(field.type){ case 'enum': value = this._compileEnum(name, key, field); break; case 'list': value = this._compileList(name, key, field); break; case 'object': value = this._compileObjectReference(name, key, field); break; case 'scalar': value = this._compileScalar(field); break; case 'view': { views[key] = viewRuntimeToModel(field); break; } } if (value != null) { object.properties[key] = value.schema; refs = [ ...refs, ...value.refs ]; } } if (Object.keys(object.properties).length === 0) { throw new Error(`Invalid model ${name}: at least one content property must be defined`); } if (required.length !== 0) { object.required = required; } if (refs.length !== 0) { object.$defs = {}; for (const refName of new Set(refs)){ Object.assign(object.$defs, this._extractDefinitions(refName)); } } const { action: _a, ...definition } = modelDefinition; return { action: 'create', model: { ...definition, version: '2.0', name, schema: object, views }, indices }; } constructor(source){ _class_private_field_init(this, _def, { writable: true, value: { models: {}, commonEmbeds: [] } }); _class_private_field_init(this, _refs, { writable: true, value: {} }); _class_private_field_init(this, _src, { writable: true, value: void 0 }); _class_private_field_set(this, _src, source); } } export function compileSchema(definition) { return new SchemaCompiler(definition).compile(); } export function createAbstractCompositeDefinition(schema) { return compileSchema(parseSchema(schema)); }