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.
154 lines
4.4 KiB
JavaScript
154 lines
4.4 KiB
JavaScript
"use strict";
|
|
|
|
const events = require("events");
|
|
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(events.EventEmitter.prototype, propertyDescriptors);
|
|
Object.assign(proto, protoProperties);
|
|
|
|
let schemaKeys = getSchemaKeys(schema);
|
|
|
|
let factory = function createInstance(instanceProps) {
|
|
factory._isUsed = true;
|
|
|
|
let instance = Object.create(proto);
|
|
events.EventEmitter.call(instance);
|
|
instance._data = {}; /* FIXME: Is this not a deopt, because it breaks monomorphism? */
|
|
|
|
// 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;
|
|
}
|
|
};
|