You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
4.2 KiB
JavaScript

"use strict";
const mapObj = require("map-obj");
const typeRules = require("./type-rules");
const generateDescriptor = require("./generate-descriptor");
const getSchemaKeys = require("./util/get-schema-keys");
const nullMissingFields = require("./util/null-missing-fields");
const generateTypeHash = require("./type-hash");
const serialize = require("./serialization/serialize");
const deserialize = require("./serialization/deserialize");
let registeredTypes = new Set();
let registeredTypesDirty = false;
let typeMap = new Map();
function hashAllTypes() {
if (registeredTypesDirty) {
registeredTypesDirty = false;
for (let type of registeredTypes) {
type._ensureHash();
}
}
}
module.exports = function createType(name, schema) {
if (schema._isTypeRule === true) {
return typeRules._createTypeRule({
_typeName: name,
_isTypeAlias: true,
_alias: schema
});
} else {
let propertyDescriptors = mapObj(schema, (key, rule) => {
return generateDescriptor(rule, key, false);
});
let protoProperties = {
_typeName: name,
/* We pre-define this here so that instances can be monomorphic; this provides a 4x performance improvement. */
_data: mapObj(schema, (key, _value) => {
return [key, undefined];
}),
/* FIXME: Disallow overriding this. */
serialize: function (serializer) {
return serialize(this, {
serializer: serializer
});
}
};
let proto = Object.create(protoProperties, propertyDescriptors);
let schemaKeys = getSchemaKeys(schema);
let factory = function createInstance(instanceProps) {
factory._isUsed = true;
let instance = Object.create(proto);
instance._data = {};
// for (let key of factory._schemaKeys) {
// if (instanceProps[key] != null) {
// instance[key] = instanceProps[key];
// } else {
// instance[key] = null;
// }
// }
/* FIXME: Investigate ways to optimize this */
let nullFields = nullMissingFields(instanceProps, factory._schemaKeys);
Object.assign(instance, instanceProps, nullFields);
return instance;
};
proto._type = factory;
factory._schemaKeys = schemaKeys;
factory._cumulativeSchema = Object.assign({}, schema);
factory._isUsed = false;
factory._typeHash = null;
factory._isCustomType = true;
factory._name = name;
factory.prototype = proto;
factory.description = name;
factory._validator = function (value) {
return (value instanceof factory);
};
factory._ensureHash = function () {
if (this._typeHash == null) {
this._isUsed = true;
this._typeHash = generateTypeHash(this);
typeMap.set(this._typeHash, this);
}
};
/* FIXME: Is the below actually necessary? */
proto._selfValidator = factory._validator;
factory._implementedTraits = new Set();
factory.implements = function (trait, implementation) {
/* FIXME: Deregister old hash from global hash registry after mutating through trait implementation */
if (this._isUsed !== false) {
throw new Error("Cannot modify a custom type that has already been instantiated");
} else {
let {implementationDescriptors, slotSchemaKeys, implementationSchema} = trait.applyImplementation(implementation);
let plainImplementationSchema = mapObj(implementationSchema, (key, value) => {
return [key, value.rule];
});
// FIXME: Test this
Object.keys(implementationDescriptors).forEach((key) => {
if (proto.key != null) {
throw new Error(`Trait implementation attempted to define a property '${key}' on the '${name}' type, but that property already exists`);
}
});
Object.defineProperties(proto, implementationDescriptors);
this._implementedTraits.add(trait);
this._schemaKeys = this._schemaKeys.concat(slotSchemaKeys);
Object.assign(this._cumulativeSchema, plainImplementationSchema);
/* TODO: Verify that this produces the expected performance benefit for trait-having typesw. */
for (let key of slotSchemaKeys) {
protoProperties._data[key] = null;
}
return this;
}
};
factory.deserialize = function (data, deserializer) {
hashAllTypes();
return deserialize(this, JSON.parse(data), typeMap, {
deserializer: deserializer
});
};
let rule = typeRules._createTypeRule(factory);
registeredTypes.add(rule);
registeredTypesDirty = true;
return rule;
}
};