"use strict"; const objectHash = require("object-hash"); 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"); module.exports = function createType(name, schema, options = {}) { if (schema._isTypeRule === true) { return typeRules._createTypeRule({ _typeName: name, _isTypeAlias: true, _alias: schema, _registry: options._registry }); } else { let propertyDescriptors = mapObj(schema, (key, rule) => { return generateDescriptor(rule, key, false, options._registry); }); 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]; }) }; 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._registry = options._registry; factory._schemaKeys = schemaKeys; factory._isCustomType = true; factory._name = name; factory.prototype = proto; factory.description = name; factory._validator = function (value) { return (value instanceof factory); }; /* FIXME: Is the below actually necessary? */ proto._selfValidator = factory._validator; factory._isUsed = false; factory._implementedTraits = new Set(); factory.implements = function (trait, implementation) { if (factory._isUsed !== false) { throw new Error("Cannot modify a custom type that has already been instantiated"); } else { let {implementationDescriptors, slotSchemaKeys} = trait.applyImplementation(implementation); // 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); factory._implementedTraits.add(trait); factory._schemaKeys = factory._schemaKeys.concat(slotSchemaKeys); return this; } }; return typeRules._createTypeRule(factory); } };