sdj-esm
Version:
Self Described JSON - ESM
293 lines (292 loc) • 12.2 kB
JavaScript
/*
Copyright (c) 2023-2024 Will Rudolph <@willrudolph.com>
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { checkResetInfo, verifyUniqKeys, } from "../util/verify.js";
import { genEntityJI, genInfoJI, genItemJI } from "../util/immutables.js";
import { BASE_ITEMS_JI, ESDJ_CLASS, GRAPH_ZERO, SYS_RESERVED } from "../core/statics.js";
import { cloneJI, getFromCoreArray } from "../util/func.std.js";
import { isArrayWithLen, isInfo, validLexiconArray } from "../core/validators.js";
import { restrictToAllowedKeys } from "../core/restrict.js";
import { SdjEntity } from "./entity.js";
import { SdjItem } from "./item.js";
import { clone, cloneDeep, each, find, isArray, isEqual, isFunction, times, uniq } from "lodash-es";
import { validSDKey } from "../core/sdj-types.js";
import { getRegEx } from "../util/regex.js";
// Descriptions created outside of new SdJson() or SdjHost.makeDescription
// using 'new SdjDescription(...)' ; will NOT automatically be added to a library.
// After initial creation they can be added as needed via library.storeDesc(...);
export class SdjDescription {
log;
_lexicons = [];
_graph = [];
_sdInfo;
_lang = "en";
_items = [];
_host;
constructor(refDescJI, requiredHost) {
this._host = requiredHost;
if (requiredHost?.checkClassInst && isFunction(requiredHost.checkClassInst)) {
this.host.checkClassInst(requiredHost, ESDJ_CLASS.HOST, true);
this.host.checkClassInst(refDescJI, ESDJ_CLASS.DESCRIPTION, false);
}
else {
throw new Error("[SDJ] Improper required Host - use SdjHost.createDescription for auto provided host;");
}
SdjDescription.VerifyJI(refDescJI);
let inDescJI;
inDescJI = cloneJI(refDescJI);
inDescJI.sdInfo = checkResetInfo(inDescJI.sdInfo);
inDescJI = this._host.fullDescription(inDescJI);
this._sdInfo = inDescJI.sdInfo;
this.lang = (inDescJI.lang) ? String(inDescJI.lang).toLowerCase() : "en";
this.log = this._host.getLogFunc("Desc:" + this._sdInfo.name);
this.log("created");
this.entityItemBuild(inDescJI);
}
// vvv Below are frozen when description/sdJson locked
get graph() {
return this._graph;
}
set graph(inGraph) {
this._graph = inGraph;
}
set items(inItems) {
this._items = inItems;
}
get items() {
return this._items;
}
get name() {
return this._sdInfo.name;
}
set name(name) {
this._sdInfo.name = name;
}
get sdInfo() {
return this._sdInfo;
}
get lang() {
return this._lang;
}
set lang(inStr) {
if (getRegEx("lang").test(inStr)) {
this._lang = String(inStr).toLowerCase();
}
}
// ^^ Above Frozen when locked
get host() {
return this._host;
}
searchEntities(searchEnt) {
return this._host.searchMgr.searchEntities(this, searchEnt);
}
searchItems(searchItem) {
return this._host.searchMgr.searchItems(this, searchItem);
}
getEntity(entKeyNum) {
return (entKeyNum === 0) ? undefined : getFromCoreArray(entKeyNum, this._graph);
}
getEntityRefs(entArray) {
const rtnAry = [];
each(entArray, (theValue) => {
const entRef = getFromCoreArray(theValue, this._graph);
if (entRef && entRef.sdId !== 0) {
rtnAry.push(entRef);
}
else {
this.log(`Unable to find item sdId/sdKey '${theValue}'`, 3);
}
});
return rtnAry;
}
calcSdKeyProps(entity) {
// confirm entity name and sdId, and get from graph instead
const localEnt = (entity && this._graph[entity.sdId]) ? this._graph[entity.sdId] : undefined;
if (!localEnt || entity.sdKey !== localEnt.sdKey || !entity.sdProps) {
this.log(`entity '${entity.sdKey}' does match description '${localEnt?.sdKey}'`);
return (entity.sdProps) ? cloneDeep(entity.sdProps) : {}; // calcSdKeyProps shouldn't be called on entities that don't have sdProps
}
return this._host.lexiconMgr.calcSdKeyProps(entity, this._graph);
}
getItem(itemKeyNum) {
return getFromCoreArray(itemKeyNum, this._items);
}
getItemRefs(itemArray) {
const rtnAry = [];
each(itemArray, (theValue) => {
const itemRef = getFromCoreArray(theValue, this._items);
if (itemRef) {
rtnAry.push(itemRef);
}
else {
this.log(`Unable to find item sdId/sdKey '${theValue}'`, 3);
}
});
return rtnAry;
}
genJI() {
const rtnDescJI = {
sdInfo: genInfoJI(this._sdInfo),
graph: [],
items: [],
lang: this.lang
};
let lexEntRefs = [], lexItemRefs = [];
if (this._lexicons && this._lexicons.length > 0) {
rtnDescJI.lexicons = clone(this._lexicons);
each(this._lexicons, (lexId) => {
const workLex = this._host.lexiconMgr.getByName(lexId);
lexEntRefs = (workLex?.entities) ? lexEntRefs.concat(workLex.entities) : lexEntRefs;
lexItemRefs = (workLex?.items) ? lexItemRefs.concat(workLex.items) : lexItemRefs;
});
}
each(this._items, (sdjItem) => {
const itemInLex = find(lexItemRefs, { sdId: sdjItem.sdId }), itemInBase = find(BASE_ITEMS_JI, { sdId: sdjItem.sdId });
if (!itemInLex && !itemInBase) {
rtnDescJI.items.push(sdjItem.genJI());
}
});
each(this._graph, (sdjEnt) => {
const entInLex = find(lexEntRefs, { sdId: sdjEnt.sdId });
if (sdjEnt.sdId !== 0 && !entInLex) {
rtnDescJI.graph.push(sdjEnt.genJI());
}
});
if (this._lang !== "en") {
rtnDescJI.lang = this._lang;
}
return rtnDescJI;
}
// Use searches for misses; use this function only for Verified Graphs
// Internals should not be calling for things that don't exist
getEntityRefById(entId) {
return (entId > 0) ? this._graph[entId] : undefined;
}
getItemsByEntity(entKeyNum) {
const actualEnt = getFromCoreArray(entKeyNum, this._graph);
if (!actualEnt) {
return [];
}
let extendsList = (actualEnt.extendIds) ? clone(actualEnt.extendIds) : [], fullItemList = cloneDeep(actualEnt.sdItems), getEnts = (entRef) => {
let rtnSdItems = cloneDeep(entRef.sdItems);
if (entRef.extendIds && isArrayWithLen(entRef.extendIds)) {
each(entRef.extendIds, (num) => {
const subEntRef = getFromCoreArray(num, this._graph);
if (subEntRef) {
rtnSdItems = rtnSdItems.concat(getEnts(subEntRef));
}
});
}
return rtnSdItems;
};
each(extendsList, (entNum) => {
const topExtendRef = getFromCoreArray(entNum, this._graph);
if (topExtendRef) {
fullItemList = fullItemList.concat(getEnts(topExtendRef));
}
});
fullItemList = uniq(fullItemList.sort());
return this.getItemRefs(fullItemList);
}
verifyJIbyType(ji, jiType, strict = false) {
return this._host.verifyJIbyType(ji, jiType, strict);
}
entityItemBuild(inDescJI) {
this._items = this.buildBaseItems();
const descriptRef = this;
each(inDescJI.items, (itemJI) => {
this.checkBase(itemJI, true);
this._items.push(new SdjItem(genItemJI(itemJI), descriptRef));
});
each(inDescJI.graph, (entityJI) => {
this.checkBase(entityJI, false);
let defItemIds = times(BASE_ITEMS_JI.length);
if (entityJI.sdItems && Array.isArray(entityJI.sdItems)) {
defItemIds = defItemIds.concat(entityJI.sdItems);
}
entityJI.sdItems = defItemIds;
this._graph.push(new SdjEntity(genEntityJI(entityJI), descriptRef));
});
this._graph.unshift(new SdjEntity(genEntityJI(GRAPH_ZERO), this));
this._lexicons = (inDescJI.lexicons) ? inDescJI.lexicons : [];
each(this._items, (itemJI, idx) => {
// Has been pre-sorted, final confirmation to confirm sdId === _item index alignment
if (idx !== itemJI.sdId) {
throw new Error("[SDJ] Description validity error, missing entity or error;");
}
});
each(this._graph, (entity, idx) => {
// This has been presorted before this, but confirms sdId === _graph index alignment
if (idx !== entity.sdId) {
throw new Error("[SDJ] Description validity error, missing entity or error;");
}
const classRef = entity;
classRef.$refreshRefs();
});
}
buildBaseItems() {
const newBaseItems = this._host.lexiconMgr.newBaseItems(), rtnArray = [];
each(newBaseItems, (itemJI) => {
rtnArray.push(new SdjItem(itemJI, this));
});
return rtnArray;
}
checkBase(itemBase, isItem) {
const checkArray = (isItem) ? this._items : this._graph, errorType = (isItem) ? "Item" : "Entity";
if (SYS_RESERVED.indexOf(itemBase.sdKey) !== -1) {
throw new Error(`[SDJ] Description: ${errorType} sdKey '${itemBase.sdKey}' is reserved;`);
}
if (find(checkArray, { sdKey: itemBase.sdKey })) {
throw new Error(`[SDJ] Description: ${errorType} sdKey '${itemBase.sdKey}' already exists;`);
}
if (find(checkArray, { sdId: itemBase.sdId })) {
throw new Error(`[SDJ] Description:: ${errorType} sdKey '${itemBase.sdKey}' has duplicate sdId [${itemBase.sdId}];`);
}
}
strictLogError(logStr, strict) {
if (strict) {
throw new Error(logStr);
}
else {
this.log(logStr, 3);
}
}
static VerifyJI(inDesc) {
if (!isInfo(inDesc.sdInfo)) {
throw new Error("[SDJ] Description - improperly formatted sdInfo;");
}
else if (isInfo(inDesc.sdInfo) && !validSDKey(inDesc.sdInfo.name)) {
throw new Error(`[SDJ] Description name '${inDesc.sdInfo.name}' must be sdKey format;`);
}
if (inDesc.lexicons && !validLexiconArray(inDesc.lexicons)) {
throw new Error("[SDJ] Description.lexicons improperly formed;");
}
if ((!inDesc.items || !isArray(inDesc.items)) && !inDesc.lexicons) {
throw new Error("[SDJ] Description - items missing or array error;");
}
else if (!inDesc.lexicons && !verifyUniqKeys(inDesc.items)) {
throw new Error("[SDJ] Description - items array must contain unique sdKeys and sdIds;");
}
if (!inDesc.graph) {
inDesc.graph = [];
}
else if ((inDesc.graph && !isArray(inDesc.graph)) && !inDesc.lexicons) {
throw new Error("[SDJ] Description graph is not array;");
}
else if (isArray(inDesc.graph) && !verifyUniqKeys(inDesc.graph)) {
throw new Error("[SDJ] Description graph does not have unique sdKeys/sdIds;");
}
if (inDesc.lang && !getRegEx("lang").test(inDesc.lang)) {
throw new Error(`[SDJ] lang value can be 2-3 lower case chars; error with value:'${inDesc.lang}';`);
}
restrictToAllowedKeys("DescriptionJI:" + inDesc.sdInfo.name, ["sdInfo", "items", "graph", "lexicons", "lang"], inDesc);
}
static IsEqual(alpha, beta) {
const betaJI = (beta instanceof SdjDescription) ? beta.genJI() : beta, alphaJI = (alpha instanceof SdjDescription) ? alpha.genJI() : alpha;
return isEqual(alphaJI, betaJI);
}
}
//# sourceMappingURL=description.js.map