@barchart/common-js
Version:
Library of common JavaScript utilities
325 lines (319 loc) • 9.16 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var Schema_exports = {};
__export(Schema_exports, {
default: () => Schema
});
module.exports = __toCommonJS(Schema_exports);
var attributes = __toESM(require("./../../lang/attributes.js"));
var functions = __toESM(require("./../../lang/functions.js"));
var is = __toESM(require("./../../lang/is.js"));
var import_LinkedList = __toESM(require("./../../collections/LinkedList.js"));
var import_Tree = __toESM(require("./../../collections/Tree.js"));
class Schema {
#name;
#fields;
#components;
#strict;
#revivers;
/**
* @param {string} name - The name of the schema
* @param {Field[]=} fields
* @param {Component[]=} components
* @param {boolean=} strict
*/
constructor(name, fields, components, strict) {
this.#name = name;
this.#fields = fields || [];
this.#components = components || [];
this.#strict = is.boolean(strict) && strict;
this.#revivers = getReviverItems(this.#fields, this.#components);
}
/**
* Accepts data and returns a new object which (should) conform to
* the schema.
*
* @public
* @param {object} data
* @returns {object}
*/
format(data) {
const returnRef = {};
this.#fields.forEach((field) => {
formatField(returnRef, field, data);
});
this.#components.forEach((component) => {
component.fields.forEach((field) => {
formatField(returnRef, field, data);
});
});
return returnRef;
}
/**
* Name of the table.
*
* @public
* @returns {string}
*/
get name() {
return this.#name;
}
/**
* The fields of the table.
*
* @public
* @returns {Array<Field>}
*/
get fields() {
return [...this.#fields];
}
/**
* The components of the table.
*
* @public
* @returns {Array<Component>}
*/
get components() {
return [...this.#components];
}
/**
* If true, only the explicitly defined fields and components will
* be serialized.
*
* @public
* @returns {boolean}
*/
get strict() {
return this.#strict;
}
/**
* Returns true, if an object complies with the schema.
*
* @public
* @param {*} candidate
* @returns {boolean}
*/
validate(candidate) {
return !getCandidateIsInvalid(candidate) && this.getInvalidFields(candidate).length === 0;
}
/**
* Returns an array of {@link Field} objects from the schema for which the
* candidate object does not comply with.
*
* @public
* @param {*} candidate
* @returns {Field[]}
*/
getInvalidFields(candidate) {
if (getCandidateIsInvalid(candidate)) {
return this.fields.filter((f) => !f.optional);
}
return this.fields.reduce((problems, field) => {
let check = !field.optional || attributes.has(candidate, field.name);
if (check) {
const valid = field.dataType.validator.call(this, attributes.read(candidate, field.name));
if (!valid) {
problems.push(field);
}
}
return problems;
}, []);
}
/**
* Generates a function suitable for use by {@link JSON.parse}.
*
* @public
* @returns {Function}
*/
getReviver() {
let head = this.#revivers;
let node = null;
const advance = (key) => {
const previous = node;
if (node === null) {
node = head;
} else {
node = node.getNext();
}
const item = node.getValue();
if (key === item.name) {
return item;
} else if (item.reset || key === "" && node === head) {
node = null;
return item;
} else if (item.array && is.integer(parseInt(key))) {
node = previous;
return item;
} else if (item.optional) {
return advance(key);
} else {
throw new SchemaError(key, item.name, `Schema parsing is using strict mode, unexpected key found [ found: ${key}, expected: ${item.name} ]`);
}
};
return (key, value) => {
const item = advance(key);
if (key === "" || item.array && key === item.name) {
return value;
} else {
return item.reviver(value);
}
};
}
/**
* Returns a function that will generate a *new* reviver function
* (see {@link Schema#getReviver}).
*
* @public
* @returns {Function}
*/
getReviverFactory() {
return () => this.getReviver();
}
/**
* Returns a string representation.
*
* @public
* @returns {string}
*/
toString() {
return `[Schema (name=${this.#name})]`;
}
}
class SchemaError extends Error {
constructor(key, name, message) {
super(message);
this.key = key;
this.name = name;
}
toString() {
return `[SchemaError]`;
}
}
class ReviverItem {
#name;
#reviver;
#optional;
#reset;
#array;
constructor(name, reviver, optional, reset, array) {
this.#name = name;
this.#reviver = reviver || functions.getTautology();
this.#optional = is.boolean(optional) && optional;
this.#reset = is.boolean(reset) && reset;
this.#array = is.boolean(array) && array;
}
get name() {
return this.#name;
}
get reviver() {
return this.#reviver;
}
get optional() {
return this.#optional;
}
get reset() {
return this.#reset;
}
get array() {
return this.#array;
}
}
function getReviverItems(fields, components) {
const root = new import_Tree.default(new ReviverItem(null, null, false, true));
fields.forEach((field) => {
const names = field.name.split(".");
let node = root;
names.forEach((name, i) => {
if (names.length === i + 1) {
node.addChild(new ReviverItem(name, field.dataType.reviver, field.optional, false, field.array));
} else {
let child = node.findChild((n) => n.name === name);
if (!child) {
child = node.addChild(new ReviverItem(name));
}
node = child;
}
});
});
components.forEach((component) => {
let node = root;
const names = component.name.split(".");
names.forEach((name, i) => {
if (names.length === i + 1) {
node = node.addChild(new ReviverItem(name, component.reviver));
} else {
let child = node.findChild((n) => n.name === name);
if (!child) {
child = node.addChild(new ReviverItem(name));
}
node = child;
}
});
component.fields.forEach((f) => node.addChild(new ReviverItem(f.name, f.dataType.reviver)));
});
let head = null;
let current = null;
const addItemToList = (item, node) => {
let itemToUse = item;
if (!node.getIsLeaf()) {
const required = node.search((i, n) => n.getIsLeaf() && !i.optional, true, false) !== null;
if (!required) {
itemToUse = new ReviverItem(item.name, item.reviver, true, item.reset, item.array);
}
} else {
itemToUse = item;
}
if (current === null) {
current = head = new import_LinkedList.default(itemToUse);
} else {
current = current.insert(itemToUse);
}
};
root.walk(addItemToList, false, true);
return head;
}
function formatField(target, field, data) {
if (attributes.has(data, field.name)) {
attributes.write(target, field.name, field.dataType.convert(attributes.read(data, field.name)));
}
}
function getCandidateIsInvalid(candidate) {
return is.undef(candidate) || is.nil(candidate) || !is.object(candidate);
}
{
const cjsExports = module.exports;
const cjsDefaultExport = cjsExports && cjsExports.__esModule ? cjsExports.default : cjsExports;
if (cjsDefaultExport && (typeof cjsDefaultExport === 'function' || typeof cjsDefaultExport === 'object')) {
Object.keys(cjsExports).forEach((key) => {
if (key !== 'default' && key !== '__esModule') {
cjsDefaultExport[key] = cjsExports[key];
}
});
}
module.exports = cjsDefaultExport;
}