sdj-esm
Version:
Self Described JSON - ESM
314 lines (308 loc) • 12.4 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 { ESDJ_CLASS, ESDJ_LIMIT, GRAPH_ID, SYS_RESERVED } from "../core/statics.js";
import { genSdKeyProps } from "../util/immutables.js";
import { isArrayWithLen } from "../core/validators.js";
import { restrictCoreSD, restrictLimiter, restrictToAllowedKeys } from "../core/restrict.js";
import { each, isBoolean, isEmpty, isEqual, isFunction, isNull, isNumber, isObject, isString, isUndefined, uniq } from "lodash-es";
import { validIntArray, validSDKey } from "../core/sdj-types.js";
/*
Entities provide the connective information between the data represented
in the SDJ.
sdItems* Items that are assigned to entity, unique pos-int values only
extendIds* Entities extended by current entity
parentIds* Entities that are allowed to contain current entity
childIds* Entities that are allowed to be children of current entity
limiter 4 Avail: (ESDJ_LIMIT)
.REQ Entity is required by parent
.REQ_HIDE Entity is required by parent hidden from user (for only one entity)
.ONE_NONE Entity is singular on parent or not-present
.REQ_ONE At least one Entity is required by parent
.KEY_IDX Child Entities of singular type
sdProps Optional properties for entities and used by lexicons or manually
dataInfo Boolean switch for if Entity stores creation/modification information (sdInfo object)
*All pos-int number[] are uniq'd and sorted before operations
Extending Rules -
- sdProps/limiter and all number[] are considered static during runtime and should be re-validated if changed
- auto-inherent all sdItems from lower entities, auto-remove duplicate items
- Does not affect childIds or parentIds, which are unique per Entity
- sdProps are chained assigned based on entity/order - genJI sdProps will show final values
- limiter value matches last extended item if not provided
Note: in Sdj "$" functions for deliberate class-only implementations which don't appear on IEntitySdj
and should only be used by classes/files that implement "new SdjEntity" in any way.
*/
export class SdjEntity {
_sdId;
_sdKey;
_parentIds;
_sdItems;
_extendIds;
_childIds;
_dataInfo;
_limiter;
_sdProps;
_description;
_childRefs;
_itemRefs;
constructor(inEnt, description) {
this._description = this.confirmDescript(description);
if (this._description) {
this._description.host.checkClassInst(inEnt, ESDJ_CLASS.ENTITY, false);
}
SdjEntity.VerifyJI(inEnt);
this.graphZeroLockout(inEnt);
this._sdId = inEnt.sdId;
this._sdKey = inEnt.sdKey;
if (isArrayWithLen(inEnt.extendIds)) {
this._extendIds = uniq([...inEnt.extendIds]);
}
if (isArrayWithLen(inEnt.parentIds)) {
this._parentIds = uniq([...inEnt.parentIds]);
}
else {
this._parentIds = [0];
}
if (isArrayWithLen(inEnt.childIds)) {
this._childIds = uniq([...inEnt.childIds]);
}
if (isArrayWithLen(inEnt.sdItems)) {
const inEntItems = [...inEnt.sdItems];
this._sdItems = uniq([0, 1].concat(inEntItems));
}
else {
this._sdItems = [0, 1];
}
if (inEnt.dataInfo) {
this._dataInfo = inEnt.dataInfo;
}
if (inEnt.sdProps) {
this._sdProps = genSdKeyProps(inEnt.sdProps);
}
this._limiter = inEnt.limiter || ESDJ_LIMIT.NONE;
}
get sdKey() {
return this._sdKey;
}
get sdId() {
return this._sdId;
}
get parentIds() {
return this._parentIds;
}
get sdItems() {
return this._sdItems;
}
get extendIds() {
return this._extendIds;
}
get childIds() {
return this._childIds;
}
get childRefs() {
return (this._childRefs) ? this._childRefs : {};
}
get itemRefs() {
return (this._itemRefs) ? this._itemRefs : {};
}
get description() {
if (!this._description) {
throw new ReferenceError("[SDJ] Entity has no description;");
}
return this._description;
}
get sdProps() {
return this._sdProps;
}
get limiter() {
return this._limiter;
}
get dataInfo() {
return this._dataInfo;
}
set description(inDesc) {
if (!this._description) {
this._description = this.confirmDescript(inDesc);
if (this._description) {
this.$refreshRefs();
}
else {
throw new Error("[SDJ] Incorrect or missing description/host;");
}
}
else {
throw new Error("[SDJ] an Entity item is prevented from having its description overwritten;");
}
}
validStruct(dataSdj, parentRef, strict = false) {
if (!this._description) {
return false;
}
return this._description.host.searchMgr.validStruct(this, dataSdj, parentRef, strict);
}
validData(dataSdj, strict = false) {
if (!this._description) {
return false;
}
return this._description.host.searchMgr.validData(this, dataSdj, strict);
}
genJI() {
const rtnEntJI = {
sdId: this._sdId,
sdKey: this._sdKey,
};
// parents will always be at least [0] and should only be eliminated when === [0]
if (this._parentIds.length > 1 || this._parentIds[0] !== 0) {
rtnEntJI.parentIds = [...this._parentIds];
}
each(this._sdItems, (itemId) => {
if (itemId !== 0 && itemId !== 1) {
if (!rtnEntJI.sdItems) {
rtnEntJI.sdItems = [];
}
rtnEntJI.sdItems.push(itemId);
}
});
if (isArrayWithLen(this._extendIds)) {
rtnEntJI.extendIds = [...this._extendIds];
}
if (isArrayWithLen(this._childIds)) {
rtnEntJI.childIds = [...this._childIds];
}
if (this._dataInfo) {
rtnEntJI.dataInfo = true;
}
if (this.sdProps) {
let rtnProps;
if (this._description) {
rtnProps = this._description.calcSdKeyProps(this);
if (!isEmpty(rtnProps)) {
rtnEntJI.sdProps = rtnProps;
}
}
else {
rtnEntJI.sdProps = genSdKeyProps(this.sdProps);
}
}
if (this._limiter !== ESDJ_LIMIT.NONE) {
rtnEntJI.limiter = this._limiter;
}
return rtnEntJI;
}
$refreshRefs() {
if (this._description) {
this._itemRefs = {};
this._childRefs = {};
each(this._childIds, (num) => {
const childRef = this._description?.getEntity(num);
if (childRef) {
this.childRefs[num] = childRef;
}
else {
throw new Error("[SDJ] if this happens something else was missed presence of entity refs should be confirmed;");
}
});
each(this._description.getItemsByEntity(this._sdId), (sdItem) => {
this.itemRefs[sdItem.sdKey] = sdItem;
});
}
}
confirmDescript(inDesc) {
let rtnVal = false;
if (!isNull(inDesc) && !isUndefined(inDesc)) {
if (inDesc.host?.checkClassInst && isFunction(inDesc.host.checkClassInst)) {
inDesc.host.checkClassInst(inDesc, ESDJ_CLASS.DESCRIPTION, true);
rtnVal = true;
}
else if (!inDesc.host && isObject(inDesc)) {
throw new Error("[SDJ] Improper Entity init attempt use IDescriptionSdj;");
}
}
return (rtnVal) ? inDesc : undefined;
}
graphZeroLockout(inEnt) {
if (inEnt.sdId === 0 || inEnt.sdKey === GRAPH_ID) {
if (this._description) {
// throw nothing for now
}
else {
throw new Error("[SDJ] Entity 0 is reserved for system requires SdjDescription to build;");
}
}
}
static VerifyJI(inEnt) {
restrictCoreSD(inEnt);
if ((inEnt.sdId === 0 && inEnt?.sdKey !== GRAPH_ID) || inEnt.sdId < 0) {
throw new Error(`[SDJ] Entity: '${inEnt.sdKey}' jixId: ${inEnt.sdId} <= 0;`);
}
else if (inEnt.sdId === 0 && inEnt.sdKey === GRAPH_ID) {
return;
}
SdjEntity.checkArrays(inEnt);
if (inEnt.sdProps && !isObject(inEnt.sdProps)) {
throw new TypeError(`[SDJ] Entity '${inEnt.sdKey}' invalid keyValues;`);
}
else if (inEnt.sdProps) {
each(inEnt.sdProps, (jValue, key) => {
if (SYS_RESERVED.indexOf(key) !== -1 || !validSDKey(key)) {
throw new TypeError(`[SDJ] Entity '${inEnt.sdKey}' has an illegal keyName: ${key};`);
}
else if (!(isString(jValue) || isNumber(jValue) || isBoolean(jValue))) {
throw new TypeError(`[SDJ] Entity '${inEnt.sdKey}' key '${key}' has illegal value, not string|number|boolean;`);
}
});
}
if (inEnt.limiter && !restrictLimiter(inEnt.limiter)) {
throw new TypeError(`[SDJ] Entity '${inEnt.sdKey}' has unrecognized limiter;`);
}
else if (inEnt.limiter && inEnt.limiter === ESDJ_LIMIT.KEY_IDX) {
if (inEnt.parentIds && !isEqual(inEnt.parentIds, [0])) {
throw new TypeError("[SDJ] sdIndexed Entities can only exist on root;");
}
if (!inEnt.childIds) {
throw new TypeError("[SDJ] sdIndexed Entities are required to have children;");
}
}
if (inEnt.dataInfo && !isBoolean(inEnt.dataInfo)) {
throw new TypeError("[SDJ] Entity dataInfo value boolean only if present;");
}
restrictToAllowedKeys("EntityJI: " + inEnt.sdKey, ["sdKey", "sdId", "sdItems", "extendIds", "parentIds", "childIds", "limiter", "sdProps", "dataInfo"], inEnt);
}
static checkArrays(inEnt) {
const nameRefs = ["childIds", "parentIds", "extendIds", "childIds", "sdItems"], valueRefArray = [inEnt.childIds, inEnt.parentIds, inEnt.extendIds, inEnt.childIds, inEnt.sdItems];
each(nameRefs, (keyId, idx) => {
const valueRef = valueRefArray[idx];
if (isArrayWithLen(valueRef)) {
if (!validIntArray(valueRef)) {
throw new TypeError(`[SDJ] Entity '${inEnt.sdKey}' invalid '${keyId}' array;`);
}
else {
switch (keyId) {
case "childIds":
if (valueRef && valueRef.indexOf(0) !== -1) {
throw new Error(`[SDJ] Entity: '${inEnt.sdKey}' GraphZero[0] is not allowed to be a child of an entity;`);
}
break;
case "extendIds":
if (valueRef && valueRef.indexOf(0) !== -1) {
throw new Error(`[SDJ] Entity '${inEnt.sdKey}' cannot extend graphZero [0];`);
}
else if (valueRef && valueRef.indexOf(inEnt.sdId) !== -1) {
throw new Error(`[SDJ] Entity: '${inEnt.sdKey}' cannot extend itself;`);
}
break;
default:
// no default;
}
}
}
if (keyId === "sdItems" && !inEnt.sdItems && !inEnt.extendIds && !inEnt.childIds) {
throw new Error(`[SDJ] Entity '${inEnt.sdKey}' has no items, does not extend, & has no children;`);
}
});
}
}
//# sourceMappingURL=entity.js.map