typ3s
Version:
Allow define type on json
905 lines • 34.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Type = void 0;
/* eslint-disable no-use-before-define */
const primitive_1 = require("../domain/primitive");
const helper_1 = require("./helper");
class Type {
// eslint-disable-next-line no-useless-constructor
constructor(primitive, obj, list, func) {
this.primitive = primitive;
this.obj = obj;
this.list = list;
this.func = func;
}
static get any() {
return new Type(primitive_1.Primitive.any);
}
static get string() {
return new Type(primitive_1.Primitive.string);
}
static get integer() {
return new Type(primitive_1.Primitive.integer);
}
static get decimal() {
return new Type(primitive_1.Primitive.decimal);
}
static get number() {
return new Type(primitive_1.Primitive.number);
}
static get boolean() {
return new Type(primitive_1.Primitive.boolean);
}
static get date() {
return new Type(primitive_1.Primitive.date);
}
static get dateTime() {
return new Type(primitive_1.Primitive.dateTime);
}
static get time() {
return new Type(primitive_1.Primitive.time);
}
static get void() {
return new Type(primitive_1.Primitive.void);
}
// eslint-disable-next-line no-use-before-define,
static Obj(properties = []) {
return new Type(primitive_1.Primitive.obj, { properties });
}
static List(items) {
return new Type(primitive_1.Primitive.list, undefined, { items });
}
static Function(params, ret) {
return new Type(primitive_1.Primitive.function, undefined, undefined, { params, ret });
}
static isPrimitive(type) {
let value;
if (typeof type === 'string') {
value = type;
}
else if (type !== undefined && type.primitive !== undefined) {
value = type.primitive.toString();
}
else {
return false;
}
return ['string', 'integer', 'decimal', 'number', 'boolean', 'date', 'dateTime', 'time'].includes(value);
}
static to(primitive) {
if (typeof primitive === 'string') {
const primitiveKey = primitive;
return new Type(primitive_1.Primitive[primitiveKey]);
}
return new Type(primitive);
}
static get(value) {
if (value === null || value === undefined) {
return Type.any;
}
else if (Array.isArray(value)) {
if (value.length > 0) {
return Type.List(this.get(value[0]));
}
return Type.any;
}
else if (typeof value === 'object') {
const properties = [];
for (const entry of Object.entries(value)) {
properties.push({ name: entry[0], type: this.get(entry[1]) });
}
return Type.Obj(properties);
}
else if (typeof value === 'string') {
// TODO determinar si es fecha.
return Type.string;
}
else if (typeof value === 'number') {
if (Number.isInteger(value)) {
return Type.integer;
}
return Type.decimal;
}
else if (typeof value === 'boolean') {
return Type.boolean;
}
return Type.any;
}
static isList(type) {
if (typeof type === 'string') {
return type.startsWith('[') && type.endsWith(']');
}
return type.primitive === primitive_1.Primitive.list;
}
static isObj(type) {
if (typeof type === 'string') {
return type.startsWith('{') && type.endsWith('}');
}
return type.primitive === primitive_1.Primitive.obj;
}
// Examples:
// sin valor de retorno: (name:string,nro:int)
// sin arguments: (>dateTime)
// simple: (name:string,nro:int>string)
// retorna otra function: (name:string,nro:int>(name:string>int))
// function que recibe function : ((name:string>string),nro:int>int)
static isFunc(type) {
if (typeof type === 'string') {
return type.startsWith('(') && type.endsWith(')');
}
return type.primitive === primitive_1.Primitive.function;
}
static stringify(type) {
if (type === undefined) {
return 'any';
}
if (this.isPrimitive(type)) {
return this.modifier(type) + type.primitive.toString();
}
if (this.isObj(type)) {
const properties = [];
const objectType = type.obj;
for (const propertyType of objectType.properties) {
properties.push(`${propertyType.name}:${this.stringify(propertyType.type)}`);
}
return `${this.modifier(type)}{${properties.join(',')}}`;
}
if (this.isList(type)) {
const arrayType = type.list;
return `${this.modifier(type)}[${this.stringify(arrayType.items)}]`;
}
if (this.isFunc(type)) {
const funcType = type.func;
const params = [];
for (const paramType of funcType.params) {
params.push(`${paramType.name}:${this.stringify(paramType.type)}`);
}
return `(${params.join(',')}>${this.modifier(type)} ${this.stringify(funcType.ret)})`;
}
return 'any';
}
static parse(stringified) {
return new TypeParser(stringified).parse();
}
static serialize(type, indentation) {
if (type === undefined || type === null) {
return undefined;
}
if (indentation) {
return JSON.stringify(type, null, indentation);
}
return JSON.stringify(type);
}
static deserialize(type) {
if (type === undefined || type === null || type.trim() === '') {
return undefined;
}
return JSON.parse(type);
}
static validate(value, type) {
try {
const _type = typeof type === 'string' ? this.parse(type) : type;
this._validate(value, _type);
return [true, ''];
}
catch (error) {
return [false, error.message];
}
}
static type(value, options) {
const type = new Type(primitive_1.Primitive.undefined);
this._type(value, type);
if (options) {
this.info(value, type, options);
}
return type;
}
static modifier(type) {
return (type.undefinable ? '?' : type.nullable ? '!' : type.unique ? '#' : type.async ? '~' : '');
}
static _validate(value, type) {
const stringified = this.stringify(type);
if (type.nullable === undefined && (type.nullable = false) && value === null) {
throw new Error(`${stringified} is null`);
}
if (type.undefinable === undefined && (type.undefinable = false) && value === undefined) {
throw new Error(`${stringified} is undefined`);
}
if (Type.isObj(type) && type.obj) {
if (!helper_1.typeH3lp.val.isObject(value)) {
throw new Error(`${stringified} value: ${value} is not an object`);
}
for (const property of type.obj.properties) {
try {
this._validate(value[property.name], property.type);
}
catch (error) {
throw new Error(`${stringified} property ${property.name} ${error.message}`);
}
}
}
else if (Type.isList(type) && type.list && type.list.items) {
if (!helper_1.typeH3lp.val.isArray(value)) {
throw new Error(`${stringified} value: ${value} is not an array`);
}
for (const item of value) {
try {
this._validate(item, type.list.items);
}
catch (error) {
throw new Error(`${stringified} item ${error.message}`);
}
}
}
else if (Type.isPrimitive(type)) {
const primitive = type.primitive;
switch (primitive) {
case primitive_1.Primitive.string:
if (!helper_1.typeH3lp.val.isString(value)) {
throw new Error(`Value ${value} is not a string`);
}
break;
case primitive_1.Primitive.integer:
if (!helper_1.typeH3lp.val.isInteger(value)) {
throw new Error(`Value ${value} is not an integer`);
}
break;
case primitive_1.Primitive.decimal:
if (!helper_1.typeH3lp.val.isDecimal(value)) {
throw new Error(`Value ${value} is not a number`);
}
break;
case primitive_1.Primitive.number:
if (!helper_1.typeH3lp.val.isNumber(value)) {
throw new Error(`Value ${value} is not a number`);
}
break;
case primitive_1.Primitive.boolean:
if (!helper_1.typeH3lp.val.isBoolean(value)) {
throw new Error(`Value ${value} is not a boolean`);
}
break;
case primitive_1.Primitive.date:
if (!helper_1.typeH3lp.val.isDate(value)) {
throw new Error(`Value ${value} is not a date`);
}
break;
case primitive_1.Primitive.dateTime:
if (!helper_1.typeH3lp.val.isDateTime(value)) {
throw new Error(`Value ${value} is not a date`);
}
break;
case primitive_1.Primitive.time:
if (!helper_1.typeH3lp.val.isTime(value)) {
throw new Error(`Value ${value} is not a date`);
}
break;
case primitive_1.Primitive.void:
if (value !== undefined) {
throw new Error(`Value ${value} is not undefined`);
}
break;
default:
throw new Error(`Primitive ${primitive} not implemented`);
}
}
}
static _type(value, type) {
if (value === undefined || value === null) {
return;
}
if (Array.isArray(value)) {
if (type.primitive === primitive_1.Primitive.undefined) {
type.primitive = primitive_1.Primitive.list;
type.list = { items: new Type(primitive_1.Primitive.undefined) };
}
else if (type.primitive !== primitive_1.Primitive.list && type.primitive !== primitive_1.Primitive.any) {
type.primitive = primitive_1.Primitive.any;
}
if (type.list === undefined || type.list.items === undefined) {
type.list = { items: new Type(primitive_1.Primitive.undefined) };
}
for (const item of value) {
this._type(item, type.list.items);
}
}
else if (typeof value === 'function') {
const str = value.toString();
type.primitive = primitive_1.Primitive.function;
const funcType = new TypeParser(str).parse().func;
type.func = funcType;
console.log(str);
}
else if (typeof value === 'object') {
if (type.primitive === primitive_1.Primitive.undefined) {
type.primitive = primitive_1.Primitive.obj;
type.obj = { properties: [] };
}
else if (type.primitive !== primitive_1.Primitive.obj && type.primitive !== primitive_1.Primitive.any) {
type.primitive = primitive_1.Primitive.any;
}
if (type.obj === undefined || type.obj.properties === undefined) {
type.obj = { properties: [] };
}
for (const entry of Object.entries(value)) {
let property = type.obj.properties.find(p => p.name === entry[0]);
if (property === undefined) {
property = { name: entry[0], type: new Type(primitive_1.Primitive.undefined) };
type.obj.properties.push(property);
}
else if (property.type === undefined) {
property.type = new Type(primitive_1.Primitive.undefined);
}
this._type(entry[1], property.type);
}
}
else if (typeof value === 'string') {
if (type.primitive === primitive_1.Primitive.undefined) {
type.primitive = primitive_1.Primitive.string;
}
else if (type.primitive !== primitive_1.Primitive.string && type.primitive !== primitive_1.Primitive.any) {
type.primitive = primitive_1.Primitive.any;
}
}
else if (typeof value === 'number') {
if (type.primitive === primitive_1.Primitive.undefined) {
if (Number.isInteger(value)) {
type.primitive = primitive_1.Primitive.integer;
}
else {
type.primitive = primitive_1.Primitive.decimal;
}
}
else if (type.primitive === primitive_1.Primitive.integer && !Number.isInteger(value)) {
type.primitive = primitive_1.Primitive.decimal;
}
else if (type.primitive !== primitive_1.Primitive.integer && type.primitive !== primitive_1.Primitive.decimal && type.primitive !== primitive_1.Primitive.any) {
type.primitive = primitive_1.Primitive.any;
}
}
else if (typeof value === 'boolean') {
if (type.primitive === primitive_1.Primitive.undefined) {
type.primitive = primitive_1.Primitive.boolean;
}
else if (type.primitive !== primitive_1.Primitive.boolean && type.primitive !== primitive_1.Primitive.any) {
type.primitive = primitive_1.Primitive.any;
}
}
}
static info(value, type, options) {
if (Type.isList(type) && type.list && Type.isObj(type.list.items) && type.list.items.obj) {
// List of objects
const typeObj = type.list.items;
if (!typeObj.repeated && !typeObj.nullables && !typeObj.indefinite) {
typeObj.repeated = 0;
typeObj.nullables = 0;
typeObj.indefinite = 0;
const listObjects = [];
for (const obj of value) {
if (obj === undefined) {
typeObj.indefinite++;
}
else if (obj === null) {
typeObj.nullables++;
}
else {
const key = this.key(obj, typeObj);
listObjects.push({ key, obj });
}
}
const distinctObjects = [];
for (let i = 0; i < listObjects.length; i++) {
const item = listObjects[i];
if (distinctObjects.find(o => o.key === item.key) === undefined) {
distinctObjects.push(item);
}
for (let j = i + 1; j < listObjects.length; j++) {
const item2 = listObjects[j];
if (item.key === item2.key) {
typeObj.repeated++;
}
}
}
this.onObjectProperties(distinctObjects.map(p => p.obj), typeObj);
this.completeInfoProperties(typeObj, value.length);
}
for (const property of type.list.items.obj.properties) {
this.propertyInfo(value, property, options);
}
}
else if (Type.isObj(type) && type.obj) {
// Object
for (const property of type.obj.properties) {
this.propertyInfo(value, property, options);
}
}
else if (Type.isList(type) && type.list && Type.isPrimitive(type.list.items) && type.list.items.primitive) {
// List of primitives
type.list.items.indefinite = 0;
type.list.items.nullables = 0;
type.list.items.repeated = 0;
const distinct = [];
for (let i = 0; i < value.length; i++) {
const item = value[i];
if (distinct.find(o => o === item) === undefined) {
distinct.push(item);
}
if (item === undefined) {
type.list.items.indefinite++;
}
else if (item === null) {
type.list.items.nullables++;
}
else {
for (let j = i + 1; j < value.length; j++) {
const item2 = value[j];
if (item === item2) {
type.list.items.repeated++;
}
}
}
}
type.distinctCount = distinct.length;
type.list.items.distinctCount = distinct.length;
this.completeInfoProperties(type.list.items, value.length);
if (options.describe) {
this.describe(value, type.list.items);
}
if (options.enums) {
this.enums(value, type);
}
}
}
static propertyInfo(list, property, options) {
if (!property.type) {
return;
}
property.type.indefinite = 0;
property.type.nullables = 0;
property.type.repeated = 0;
if (property.type && Type.isPrimitive(property.type)) {
// Property is a primitive in List of objects
const values = list.map(p => p[property.name]);
for (let i = 0; i < values.length; i++) {
const item = values[i];
if (item === undefined) {
property.type.indefinite++;
}
else if (item === null) {
property.type.nullables++;
}
else {
for (let j = i + 1; j < list.length; j++) {
const item2 = values[j];
if (item === item2) {
property.type.repeated++;
}
}
}
}
this.completeInfoProperties(property.type, list.length);
if (options.describe) {
this.describe(values, property.type);
}
if (options.enums) {
this.enums(values, property.type);
}
}
else if (property.type && Type.isObj(property.type) && property.type.obj) {
// Property is an object in List of objects
const listObjects = [];
for (const objs of list) {
const obj = objs[property.name];
if (obj === undefined) {
property.type.indefinite++;
}
else if (obj === null) {
property.type.nullables++;
}
else {
listObjects.push({ key: JSON.stringify(obj), obj });
}
}
const distinctObjects = [];
for (let i = 0; i < listObjects.length; i++) {
const item = listObjects[i];
if (distinctObjects.find(o => o.key === item.key) === undefined) {
distinctObjects.push(item);
}
for (let j = i + 1; j < listObjects.length; j++) {
const item2 = listObjects[j];
if (item.key === item2.key) {
property.type.repeated++;
}
}
}
this.onObjectProperties(distinctObjects.map(p => p.obj), property.type);
this.completeInfoProperties(property.type, list.length);
this.info(listObjects.map(o => o.obj), property.type, options);
}
else if (property.type && Type.isList(property.type) && property.type.list && property.type.list.items) {
// Property is a list in List of objects
const dictionary = [];
for (const item of list) {
const children = item[property.name];
if (children === undefined) {
property.type.indefinite++;
}
else if (children === null) {
property.type.nullables++;
}
else {
for (const child of children) {
const key = this.key(child, property.type.list.items);
dictionary.push({ key, obj: child });
}
}
}
for (let i = 0; i < dictionary.length; i++) {
const item = dictionary[i];
for (let j = i + 1; j < dictionary.length; j++) {
const item2 = dictionary[j];
if (item.key === item2.key) {
property.type.repeated++;
}
}
}
this.completeInfoProperties(property.type, dictionary.length);
this.info(dictionary.map(p => p.obj), property.type, options);
}
}
static onObjectProperties(list, type) {
if (!type.obj) {
return;
}
type.distinctCount = list.length;
for (const property of type.obj.properties) {
if (property.type && Type.isPrimitive(property.type)) {
property.type.onParentDistinctRepeated = 0;
const distinct = [];
const values = list.map(p => p[property.name]);
for (let i = 0; i < values.length; i++) {
const field = values[i];
if (field === undefined || field === null) {
continue;
}
else {
if (distinct.find(o => o === field) === undefined) {
distinct.push(field);
}
for (let j = i + 1; j < values.length; j++) {
const field2 = values[j];
if (field === field2) {
property.type.onParentDistinctRepeated++;
}
}
}
}
property.type.distinctCount = distinct.length;
property.type.onParentDistinctRepeatedRate = property.type.onParentDistinctRepeated / this.iterations(list.length - 1);
property.type.onParentDistinctUnique = property.type.onParentDistinctRepeated === 0 && list.length > 0;
}
}
}
static completeInfoProperties(type, count) {
type.count = count;
if (type.repeated !== undefined && type.nullables !== undefined && type.indefinite !== undefined) {
type.repeatRate = type.repeated / this.iterations(count - 1);
type.nullable = type.nullables > 0;
type.undefinable = type.indefinite > 0;
type.unique = type.repeated === 0 && type.count > 0;
}
}
static describe(list, type) {
if (!this.isPrimitive(type) || !type.count) {
return null;
}
const primitive = type.primitive;
const sorted = list.sort((a, b) => a - b);
const percentiles = this.percentiles(sorted, [10, 25, 50, 75, 90]);
type.percent10 = percentiles[0];
type.percent25 = percentiles[1];
type.percent50 = percentiles[2];
type.percent75 = percentiles[3];
type.percent90 = percentiles[4];
if (['decimal', 'number', 'integer'].includes(primitive)) {
type.sum = 0;
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (item === undefined || item === null) {
continue;
}
type.sum += item;
if (type.max === undefined || type.max < item) {
type.max = item;
}
if (type.min === undefined || type.min > item) {
type.min = item;
}
}
type.mean = type.sum / list.length;
// calculate standard deviation
const differencesSquared = [];
for (let i = 0; i < list.length; i++) {
const difference = list[i] - type.mean;
const diferenciaCuadrada = Math.pow(difference, 2);
differencesSquared.push(diferenciaCuadrada);
}
const meanSquareDifferences = differencesSquared.reduce((total, value) => total + value, 0) / list.length;
type.std = Math.sqrt(meanSquareDifferences);
}
else if (['dateTime', 'date', 'time'].includes(primitive)) {
let millisecondsTotal = 0;
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (item === undefined || item === null) {
continue;
}
millisecondsTotal += item.getTime();
if (type.max === undefined || type.max < item) {
type.max = item;
}
if (type.min === undefined || type.min > item) {
type.min = item;
}
}
type.mean = new Date(millisecondsTotal / list.length);
}
else if (primitive === 'string') {
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (item === undefined || item === null) {
continue;
}
if (type.max === undefined || type.max < item) {
type.max = item;
}
if (type.min === undefined || type.min > item) {
type.min = item;
}
if (type.maxLen === undefined || type.maxLen < item.length) {
type.maxLen = item.length;
}
if (type.minLen === undefined || type.minLen > item.length) {
type.minLen = item.length;
}
}
}
}
static percentiles(sorted, percentiles) {
const indicesPercentiles = percentiles.map(p => Math.floor((p / 100) * sorted.length));
return indicesPercentiles.map(index => sorted[index]);
}
static enums(list, type) {
if (!this.isPrimitive(type) || !type.count) {
return null;
}
const primitive = type.primitive;
if (['number', 'integer', 'string'].includes(primitive) &&
type.count && type.distinctCount &&
(type.distinctCount === 1 ||
(type.count > 6 && type.distinctCount < 3) ||
(type.count > 12 && type.distinctCount < 4) ||
(type.count > 100 && type.distinctCount / type.count < 0.1) ||
(type.count > 1000 && type.distinctCount / type.count < 0.05))) {
type.enums = [];
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (item === undefined || item === null) {
continue;
}
const enumItem = type.enums.find(e => e.value === item);
if (enumItem === undefined) {
type.enums.push({ value: item, count: 1 });
}
else {
enumItem.count++;
}
}
}
else if (primitive === 'boolean') {
let trueCount = 0;
let falseCount = 0;
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (item === undefined || item === null) {
continue;
}
if (item) {
trueCount++;
}
else {
falseCount++;
}
}
type.enums = [{ value: true, count: trueCount }, { value: false, count: falseCount }];
}
}
static iterations(n) {
if (n === 0) {
return 0;
}
return n + this.iterations(n - 1);
}
static key(value, type) {
if (Type.isList(type) && type.list) {
const keys = [];
for (const item of value) {
keys.push(this.key(item, type.list.items));
}
return '[' + keys.sort().join(',') + ']';
}
else if (Type.isObj(type) && type.obj) {
const keys = [];
for (const property of type.obj.properties.sort((a, b) => a.name.localeCompare(b.name))) {
if (property.type) {
const propertyValue = value[property.name];
if (propertyValue !== undefined && propertyValue !== null) {
keys.push(property.name + ':' + this.key(propertyValue, property.type));
}
}
}
return '{' + keys.join(',') + '}';
}
else if (Type.isPrimitive(type)) {
return value.toString();
}
else {
return value.toString();
}
}
}
exports.Type = Type;
class TypeParser {
constructor(source) {
this.buffer = source.split('');
this.length = this.buffer.length;
this.index = 0;
this.reAlphanumeric = /[a-zA-Z0-9_.]+$/;
}
get end() {
return this.index >= this.length;
}
get current() {
return this.buffer[this.index];
}
chars(count = 1, offset = 0) {
const chars = [];
for (let i = this.index + offset; i < count; i++) {
if (i >= this.length) {
return chars.join('');
}
chars.push(this.buffer[i]);
}
return chars.join('');
}
parse() {
return this.getType();
}
isAlphanumeric(value) {
if (value === null || value === undefined) {
return false;
}
return this.reAlphanumeric.test(value);
}
getType() {
const char = this.current;
if (this.isAlphanumeric(char)) {
const value = this.getValue();
if (Type.isPrimitive(value) || value === primitive_1.Primitive.any) {
return Type.to(value);
}
else {
throw new Error(`Cannot solve ${value}`);
}
}
else if (char === '{') {
this.index += 1;
this.forwardSpaces();
const objectType = this.getObject();
return new Type(primitive_1.Primitive.obj, objectType);
}
else if (char === '[') {
this.index += 1;
this.forwardSpaces();
const listType = this.getList();
return new Type(primitive_1.Primitive.list, undefined, listType);
}
else if (char === '(') {
this.index += 1;
this.forwardSpaces();
const functionType = this.getFunction();
return new Type(primitive_1.Primitive.function, undefined, undefined, functionType);
}
else {
throw new Error('Cannot solve type');
}
}
getValue(increment = true) {
const buff = [];
if (increment) {
while (!this.end && this.isAlphanumeric(this.current)) {
buff.push(this.current);
this.index += 1;
}
}
else {
let index = this.index;
while (!this.end && this.isAlphanumeric(this.buffer[index])) {
buff.push(this.buffer[index]);
index += 1;
}
}
return buff.join('');
}
getObject() {
const properties = [];
while (true) {
const name = this.getValue();
this.forwardSpaces();
if (this.current === ':')
this.index += 1;
else
throw new Error(`attribute ${name} without value`);
const type = this.getType();
properties.push({ name, type });
this.forwardSpaces();
if (this.current === ',') {
this.index += 1;
}
else if (this.current === '}') {
this.index += 1;
break;
}
else {
throw new Error('Object without end');
}
}
return { properties };
}
getList() {
const type = this.getType();
this.forwardSpaces();
if (this.current === ']')
this.index += 1;
else
throw new Error('List without end');
return { items: type };
}
getFunction() {
const params = [];
let ret = Type.void;
while (true) {
const name = this.getValue();
this.forwardSpaces();
let type = Type.any;
if (this.current === ':') {
this.index += 1;
type = this.getType();
}
params.push({ name, type });
this.forwardSpaces();
if (this.current === ',') {
this.index += 1;
continue;
}
if (this.chars(2) === '>') {
this.index += 2;
ret = this.getType();
}
if (this.current === ')') {
this.index += 1;
break;
}
else {
throw new Error('Function without end');
}
}
return { params, ret };
}
forwardSpaces() {
while (!this.end && this.buffer[this.index] === ' ') {
this.index += 1;
}
}
}
//# sourceMappingURL=type.js.map