all files / src/ schema.js

90.91% Statements 50/55
86.49% Branches 32/37
100% Functions 15/15
90.2% Lines 46/51
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127            16× 16× 16× 20×     20× 20×   12× 12×       16×         26× 23× 23×   20×   13× 13×       26×         26× 23× 23×   16× 13×             26×                   26× 26×   26×         26×           33×   32×   30×               21×   18×     18×               16×   13×     26×       26×       13×    
import isEmpty from 'lodash/isEmpty';
import omit from 'lodash/omit';
import { schema } from 'normalizr';
 
 
function getRelatedThrough(entity1, entity2) {
  const entity1Schema = entity1.schema;
  const entity2Schema = entity2;
  const t = Object.keys(entity1Schema).find((prop) => {
    Iif (prop.startsWith('_')) {
      return false;
    }
    const relation = entity1Schema[prop];
    if (relation.key) {
      return relation.key === entity2Schema.key;
    }
    else Eif (Array.isArray(relation)) {
      return relation[0].key === entity2Schema.key;
    }
    return false;
  });
  return t;
}
 
 
function createSchemaDefinition(config, bag) {
  const definition = Object.keys(config.attributes).reduce((memo, a) => {
    const attribute = config.attributes[a];
    if (typeof attribute === 'function') { // Treat it like a computed property
      return { ...memo, _computed: { ...memo._computed, [a]: attribute } };
    }
    else if (typeof attribute === 'string') { // Single reference to another schema
      return { ...memo, [a]: bag[attribute] };
    }
    else Eif (attribute.isArray) { // Array reference to another schema
      return { ...memo, [a]: [ bag[attribute.relatedSchema] ] };
    }
    return memo;  // Should never come to here
  }, {});
  return { ...definition, ...omit(config, 'attributes') };
}
 
 
function extractRelatedEntities(attributes) {
  return Object.keys(attributes).reduce((memo, a) => {
    const attribute = attributes[a];
    if (typeof attribute === 'string') { // Single reference to another schema
      return [ ...memo, { prop: a, entity: attribute, isArray: false } ];
    }
    else if (attribute.isArray) { // Array reference to another schema
      return [ ...memo, { prop: a, entity: attribute.relatedSchema, isArray: true } ];
    }
    return memo;
  }, []);
}
 
 
function getRelations(schema, relatedEntities, bag) {
  return () => relatedEntities.map((relatedEntity) => ({
    to: relatedEntity.entity,
    through: relatedEntity.prop,
    foreign: getRelatedThrough(bag[relatedEntity.entity], schema),
    isMany: relatedEntity.isArray,
  }));
}
 
 
function generateSchema(schema, bag) {
  const finalSchema = bag[schema.name];
  finalSchema.define(createSchemaDefinition(schema.config, bag));
 
  Object.defineProperty(finalSchema, 'getRelations', {
    enumerable: false,
    value: getRelations(finalSchema, extractRelatedEntities(schema.config.attributes), bag),
  });
 
  return finalSchema;
}
 
 
 
export function defineSchema(name, config={}) {
  if (isEmpty(name)) {
    throw new Error('[INVALID NAME]');
  }
  if ((typeof config !== 'object') && config) {
    throw new Error('[INVALID CONFIG]');
  }
  return {
    name,
    config: { ...config, attributes: config.attributes ? config.attributes : {}},
  };
}
 
 
export function hasMany(schema) {
  if (isEmpty(schema)) {
    throw new Error('[INVALID SCHEMA]');
  }
  Iif (typeof schema !== 'string' && ! schema.hasOwnProperty('name')) {
    throw new Error('[INVALID SCHEMA]');
  }
  return {
    relatedSchema: schema.hasOwnProperty('name') ? schema.name : schema,
    isArray: true,
  };
}
 
 
export function generateSchemas(schemas) {
  if (isEmpty(schemas)) {
    throw new Error('[INVALID SCHEMAS]');
  }
  Iif ( ! Array.isArray(schemas)) {
    throw new Error('[INVALID SCHEMAS]');
  }
  const schemasBag = schemas.reduce((bag, s) => ({
    ...bag,
    [s.name]: new schema.Entity(s.name),
  }), {});
  const t = schemas.reduce((result, s) => ({
    ...result,
    [s.name]: generateSchema(s, schemasBag),
  }), {});
  return t;
}