@composedb/devtools
Version:
Development tools for ComposeDB projects.
367 lines (366 loc) • 13.4 kB
JavaScript
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));
}