@fjell/core
Version:
Core Item and Key Framework for Fjell
1,579 lines (1,523 loc) • 80.1 kB
JavaScript
// src/logger.ts
import Logging from "@fjell/logging";
var LibLogger = Logging.getLogger("@fjell/core");
var logger_default = LibLogger;
// src/key/KUtils.ts
var logger = logger_default.get("KUtils");
var isComKey = (key) => {
return key !== null && typeof key === "object" && "kt" in key && "pk" in key && "loc" in key && Array.isArray(key.loc);
};
var isPriKey = (key) => {
return key !== null && typeof key === "object" && "kt" in key && "pk" in key && (!("loc" in key) || key.loc === void 0 || key.loc === null);
};
var normalizeKeyValue = (value) => {
return String(value);
};
var createNormalizedHashFunction = () => {
return (key) => {
if (typeof key === "object" && key !== null) {
const normalizedKey = JSON.parse(JSON.stringify(key));
if ("pk" in normalizedKey && (normalizedKey.pk !== void 0 && normalizedKey.pk !== null)) {
normalizedKey.pk = normalizeKeyValue(normalizedKey.pk);
}
if ("lk" in normalizedKey && (normalizedKey.lk !== void 0 && normalizedKey.lk !== null)) {
normalizedKey.lk = normalizeKeyValue(normalizedKey.lk);
}
if ("loc" in normalizedKey && Array.isArray(normalizedKey.loc)) {
normalizedKey.loc = normalizedKey.loc.map((locItem) => {
if (locItem && "lk" in locItem && (locItem.lk !== void 0 && locItem.lk !== null)) {
return { ...locItem, lk: normalizeKeyValue(locItem.lk) };
}
return locItem;
});
}
return JSON.stringify(normalizedKey);
}
return JSON.stringify(key);
};
};
var isPriKeyEqualNormalized = (a, b) => {
logger.trace("isPriKeyEqualNormalized", { a, b });
return a && b && normalizeKeyValue(a.pk) === normalizeKeyValue(b.pk) && a.kt === b.kt;
};
var isLocKeyEqualNormalized = (a, b) => {
logger.trace("isLocKeyEqualNormalized", { a, b });
return a && b && normalizeKeyValue(a.lk) === normalizeKeyValue(b.lk) && a.kt === b.kt;
};
var isComKeyEqualNormalized = (a, b) => {
logger.trace("isComKeyEqualNormalized", { a, b });
if (a && b && isPriKeyEqualNormalized({ kt: a.kt, pk: a.pk }, { kt: b.kt, pk: b.pk })) {
if (a.loc.length === b.loc.length) {
for (let i = 0; i < a.loc.length; i++) {
if (!isLocKeyEqualNormalized(a.loc[i], b.loc[i])) {
return false;
}
}
return true;
} else {
return false;
}
} else {
return false;
}
};
var isItemKeyEqualNormalized = (a, b) => {
logger.trace("isItemKeyEqualNormalized", { a, b });
if (isComKey(a) && isComKey(b)) {
return isComKeyEqualNormalized(a, b);
} else if (isPriKey(a) && isPriKey(b)) {
if (isComKey(a) || isComKey(b)) {
return false;
} else {
return isPriKeyEqualNormalized(a, b);
}
} else {
return false;
}
};
var isItemKeyEqual = (a, b) => {
logger.trace("isKeyEqual", { a, b });
if (isComKey(a) && isComKey(b)) {
return isComKeyEqual(a, b);
} else if (isPriKey(a) && isPriKey(b)) {
if (isComKey(a) || isComKey(b)) {
return false;
} else {
return isPriKeyEqual(a, b);
}
} else {
return false;
}
};
var isPriKeyEqual = (a, b) => {
logger.trace("isPriKeyEqual", { a, b });
return a && b && a.pk === b.pk && a.kt === b.kt;
};
var isLocKeyEqual = (a, b) => {
logger.trace("isLocKeyEqual", { a, b });
return a && b && a.lk === b.lk && a.kt === b.kt;
};
var isComKeyEqual = (a, b) => {
logger.trace("isComKeyEqual", { a, b });
if (a && b && isPriKeyEqual({ kt: a.kt, pk: a.pk }, { kt: b.kt, pk: b.pk })) {
if (a.loc.length === b.loc.length) {
for (let i = 0; i < a.loc.length; i++) {
if (!isLocKeyEqual(a.loc[i], b.loc[i])) {
return false;
}
}
return true;
} else {
return false;
}
} else {
return false;
}
};
var isItemKey = (key) => {
logger.trace("isItemKey", { key });
return key !== void 0 && (isComKey(key) || isPriKey(key));
};
var isLocKey = (key) => {
logger.trace("isLocKey", { key });
return key !== void 0 && (key.lk !== void 0 && key.kt !== void 0);
};
var generateKeyArray = (key) => {
logger.trace("generateKeyArray", { key });
const keys = [];
if (isComKey(key) || isPriKey(key)) {
if (isComKey(key)) {
const comKey = key;
keys.push({ pk: comKey.pk, kt: comKey.kt });
for (let i = 0; i < comKey.loc.length; i++) {
keys.push(comKey.loc[i]);
}
} else {
keys.push(key);
}
} else {
const locKeys = key;
for (let i = 0; i < locKeys.length; i++) {
keys.push(locKeys[i]);
}
}
return keys;
};
var constructPriKey = (pk, kt) => {
logger.trace("constructPriKey", { pk, kt });
let pri;
if (typeof pk === "string" || typeof pk === "number") {
pri = { kt, pk };
} else {
pri = pk;
}
return pri;
};
var cPK = constructPriKey;
var toKeyTypeArray = (ik) => {
logger.trace("toKeyTypeArray", { ik });
if (isComKey(ik)) {
const ck = ik;
return [ck.kt, ...ck.loc.map((l) => l.kt)];
} else {
return [ik.kt];
}
};
var extractKeyTypeArray = (key) => {
logger.trace("extractKeyTypeArray", { key });
if (isComKey(key)) {
const ck = key;
return [ck.kt, ...ck.loc.map((l) => l.kt)];
} else if (isPriKey(key)) {
return [key.kt];
} else if (Array.isArray(key)) {
return key.map((locKey) => locKey.kt);
} else {
logger.warning("extractKeyTypeArray: Unknown key type", { key });
return [];
}
};
var abbrevIK = (ik) => {
logger.trace("abbrevIK", { ik });
if (ik) {
if (isComKey(ik)) {
const ck = ik;
return `${ck.kt}:${ck.pk}:${ck.loc.map((l) => `${l.kt}:${l.lk}`).join(",")}`;
} else {
return `${ik.kt}:${ik.pk}`;
}
} else {
return "null IK";
}
};
var abbrevLKA = (keyArray) => {
logger.trace("abbrevLKA", { keyArray });
if (keyArray === void 0 || keyArray === null) {
return "null LKA";
} else {
return keyArray.map((key) => {
if (key) {
return `${key.kt}:${key.lk}`;
} else {
return key;
}
}).join(",");
}
};
var primaryType = (ik) => {
logger.trace("primaryType", { ik });
if (isComKey(ik)) {
return ik.kt;
} else {
return ik.kt;
}
};
var itemKeyToLocKeyArray = (ik) => {
logger.trace("itemKeyToLocKeyArray", { ik: abbrevIK(ik) });
let lka = [];
if (isComKey(ik)) {
const ck = ik;
lka = [...ck.loc];
} else {
const pk = ik;
lka = [{ kt: pk.kt, lk: pk.pk }];
}
logger.trace("itemKeyToLocKeyArray Results", { ik: abbrevIK(ik), lka: abbrevLKA(lka) });
return lka;
};
var ikToLKA = itemKeyToLocKeyArray;
var locKeyArrayToItemKey = (lka) => {
logger.trace("locKeyArrayToItemKey", { lka: abbrevLKA(lka) });
if (lka && lka.length === 1) {
const priKey = cPK(lka[0].lk, lka[0].kt);
return priKey;
} else if (lka && lka.length > 1 && lka[0] !== void 0) {
const locs = lka.slice(1);
const priKey = cPK(lka[0].lk, lka[0].kt);
const comKey = { kt: priKey.kt, pk: priKey.pk, loc: locs };
return comKey;
} else {
throw new Error(
"locKeyArrayToItemKey: lka is undefined or empty. Expected non-empty LocKeyArray. Suggestion: Ensure you are passing a valid location key array, not undefined/null/empty array."
);
}
};
var isValidPriKey = (key) => {
const valid = key !== void 0 && key !== null && (key.pk !== void 0 && key.pk !== null && key.pk !== "" && key.pk !== "null") && (key.kt !== void 0 && key.kt !== null && key.kt !== "" && key.kt !== "null");
return valid;
};
var isValidLocKey = (key) => {
const valid = key !== void 0 && key !== null && (key.lk !== void 0 && key.lk !== null && key.lk !== "" && key.lk !== "null") && (key.kt !== void 0 && key.kt !== null && key.kt !== "" && key.kt !== "null");
return valid;
};
var isValidLocKeyArray = (keyArray) => {
return keyArray !== void 0 && keyArray !== null && keyArray.every(isValidLocKey);
};
var isValidComKey = (key) => {
return key !== void 0 && key !== null && isValidPriKey(key) && isValidLocKeyArray(key.loc);
};
var isValidItemKey = (key) => {
if (key === null) throw new Error("Key cannot be null");
if (key === void 0) return false;
return isComKey(key) && isValidComKey(key) || isPriKey(key) && isValidPriKey(key);
};
var lkaToIK = locKeyArrayToItemKey;
// node_modules/@fjell/types/dist/chunk-C37JAZMK.js
var isCondition = (condition) => {
return typeof condition.column === "string" && (Array.isArray(condition.value) && condition.value.every((item) => typeof item === "string") || Array.isArray(condition.value) && condition.value.every((item) => typeof item === "number") || typeof condition.value === "string" || typeof condition.value === "number" || typeof condition.value === "boolean" || condition.value instanceof Date || condition.value === null) && (condition.operator ? typeof condition.operator === "string" : true);
};
// src/item/IQFactory.ts
var IQFactory = class _IQFactory {
query = {};
constructor(query = {}) {
this.query = query;
}
orderBy(field, direction = "asc") {
if (!this.query.orderBy) {
this.query.orderBy = [];
}
this.query.orderBy.push({ field, direction });
return this;
}
agg(name, query) {
if (!this.query.aggs) {
this.query.aggs = {};
}
this.query.aggs[name] = query;
return this;
}
event(name, query) {
if (!this.query.events) {
this.query.events = {};
}
this.query.events[name] = query;
return this;
}
conditions(conditions, compoundType = "AND") {
for (const condition of conditions) {
if (!isCondition(condition)) {
throw new Error(`Invalid condition: ${JSON.stringify(condition)}`);
}
}
if (!this.query.compoundCondition) {
this.query.compoundCondition = {
compoundType,
conditions
};
} else {
const compoundCondition = {
compoundType,
conditions
};
this.query.compoundCondition.conditions.push(compoundCondition);
}
return this;
}
limit(limit) {
this.query.limit = limit;
return this;
}
offset(offset) {
this.query.offset = offset;
return this;
}
// TODO: right now, we're only supporting PK refs for queries. Should add support for CKs
pk(kt, pk, name) {
if (!this.query.refs) {
this.query.refs = {};
}
const refName = name || kt;
this.query.refs[refName] = { key: cPK(pk, kt) };
return this;
}
condition(column, value, operator = "==") {
const condition = { column, value, operator };
if (isCondition(condition)) {
if (!this.query.compoundCondition) {
this.query.compoundCondition = {
compoundType: "AND",
conditions: []
};
}
this.query.compoundCondition.conditions.push(condition);
return this;
} else {
throw new Error(`Invalid condition: ${JSON.stringify(condition)}`);
}
}
static all() {
const iqFactory = new _IQFactory();
return iqFactory;
}
static orderBy(field, direction = "asc") {
const iqFactory = new _IQFactory();
return iqFactory.orderBy(field, direction);
}
static agg(name, query) {
const iqFactory = new _IQFactory();
return iqFactory.agg(name, query);
}
static event(name, query) {
const iqFactory = new _IQFactory();
return iqFactory.event(name, query);
}
static limit(limit) {
const iqFactory = new _IQFactory();
return iqFactory.limit(limit);
}
static offset(offset) {
const iqFactory = new _IQFactory();
return iqFactory.offset(offset);
}
static pk(kt, pk, name) {
const iqFactory = new _IQFactory();
return iqFactory.pk(kt, pk, name);
}
static condition(column, value, operator = "==") {
const iqFactory = new _IQFactory();
return iqFactory.condition(column, value, operator);
}
static conditions(conditions, compoundType = "AND") {
const iqFactory = new _IQFactory();
return iqFactory.conditions(conditions, compoundType);
}
toQuery() {
return this.query;
}
};
// src/item/IFactory.ts
import deepmerge from "deepmerge";
var IFactory = class _IFactory {
item = {};
constructor(props = {}) {
this.item = deepmerge(this.item, props);
}
addRef(i, name) {
const ik = i.key;
const refName = name || primaryType(ik);
if (!this.item.refs) {
this.item.refs = {};
}
this.item.refs[refName] = ik;
return this;
}
static addRef(i, name) {
return new _IFactory().addRef(i, name);
}
addDefaultEvents() {
if (!this.item.events) {
this.item.events = {};
}
const now = /* @__PURE__ */ new Date();
if (!this.item.events.created) {
this.item.events.created = { at: now };
}
if (!this.item.events.updated) {
this.item.events.updated = { at: now };
}
if (!this.item.events.deleted) {
this.item.events.deleted = { at: null };
}
return this;
}
addEvent(name, at, by) {
if (!this.item.events) {
this.item.events = {};
}
this.item.events[name] = { at, by };
return this;
}
static addEvent(name, at, by) {
return new _IFactory().addEvent(name, at, by);
}
addProp(name, value) {
this.item[name] = value;
return this;
}
static addProp(name, value) {
return new _IFactory().addProp(name, value);
}
addProps(props) {
this.item = deepmerge(this.item, props);
return this;
}
static addProps(props) {
return new _IFactory().addProps(props);
}
toItem() {
return this.item;
}
};
// src/AItemService.ts
var AItemService = class {
pkType;
parentService = null;
constructor(pkType, parentService) {
this.pkType = pkType;
if (parentService) {
this.parentService = parentService;
}
}
getPkType = () => {
return this.pkType;
};
getKeyTypes = () => {
let keyTypes = [this.getPkType()];
if (this.parentService) {
keyTypes = keyTypes.concat(this.parentService.getKeyTypes());
}
return keyTypes;
};
};
// src/Coordinate.ts
var logger2 = logger_default.get("Coordinate");
var createCoordinate = (kta, scopes = []) => {
const ktArray = Array.isArray(kta) ? kta : [kta];
const toString = () => {
logger2.debug("toString", { kta, scopes });
return `${ktArray.join(", ")} - ${scopes.join(", ")}`;
};
logger2.debug("createCoordinate", { kta: ktArray, scopes, toString });
return { kta: ktArray, scopes, toString };
};
// src/dictionary.ts
var logger3 = logger_default.get("Dictionary");
var Dictionary = class _Dictionary {
map = {};
hashFunction = (key) => JSON.stringify(key);
constructor(map, hashFunction) {
if (hashFunction) {
this.hashFunction = hashFunction;
}
if (map) {
Object.entries(map).forEach(([hashedKey, value]) => {
try {
const originalKey = JSON.parse(hashedKey);
this.map[hashedKey] = { originalKey, value };
} catch {
logger3.warning("Cannot recover original key from legacy map entry", { hashedKey });
}
});
}
}
set(key, item) {
logger3.trace("set", { key, item });
const hashedKey = this.hashFunction(key);
this.map[hashedKey] = { originalKey: key, value: item };
}
get(key) {
logger3.trace("get", { key });
const hashedKey = this.hashFunction(key);
const entry = this.map[hashedKey];
return entry && this.keysEqual(entry.originalKey, key) ? entry.value : null;
}
keysEqual(key1, key2) {
return key1 === key2;
}
delete(key) {
logger3.trace("delete", { key });
const hashedKey = this.hashFunction(key);
delete this.map[hashedKey];
}
keys() {
return Object.values(this.map).map((entry) => entry.originalKey);
}
values() {
return Object.values(this.map).map((entry) => entry.value);
}
includesKey(key) {
const hashedKey = this.hashFunction(key);
const entry = this.map[hashedKey];
return entry ? this.keysEqual(entry.originalKey, key) : false;
}
clone() {
const clonedMap = {};
Object.entries(this.map).forEach(([hashedKey, entry]) => {
clonedMap[hashedKey] = entry.value;
});
const clone = new _Dictionary(clonedMap, this.hashFunction);
clone.map = Object.assign({}, this.map);
return clone;
}
};
// src/item/IQUtils.ts
import * as luxon from "luxon";
var logger4 = logger_default.get("IQUtils");
var queryToParams = (query) => {
const params = {};
if (query.compoundCondition) {
params.compoundCondition = JSON.stringify(query.compoundCondition);
}
if (query.refs) {
params.refs = JSON.stringify(query.refs);
}
if (query.limit) {
params.limit = query.limit;
}
if (query.offset) {
params.offset = query.offset;
}
if (query.aggs) {
params.aggs = JSON.stringify(query.aggs);
}
if (query.events) {
params.events = JSON.stringify(query.events);
}
if (query.orderBy) {
params.orderBy = JSON.stringify(query.orderBy);
}
return params;
};
var dateTimeReviver = function(key, value) {
if (typeof value === "string") {
const parsedDate = luxon.DateTime.fromISO(value);
if (parsedDate.isValid) {
return parsedDate.toJSDate();
}
}
return value;
};
var paramsToQuery = (params) => {
const query = {};
if (params.compoundCondition) {
query.compoundCondition = JSON.parse(params.compoundCondition);
}
if (params.refs) {
query.refs = JSON.parse(params.refs);
}
if (params.limit) {
query.limit = Number(params.limit);
}
if (params.offset) {
query.offset = Number(params.offset);
}
if (params.aggs) {
query.aggs = JSON.parse(params.aggs);
}
if (params.events) {
query.events = JSON.parse(params.events, dateTimeReviver);
}
if (params.orderBy) {
query.orderBy = JSON.parse(params.orderBy);
}
return query;
};
var isRefQueryMatch = (refKey, queryRef, references) => {
logger4.trace("doesRefMatch", { queryRef, references });
logger4.debug("Comparing Ref", { refKey, itemRef: references[refKey], queryRef });
if (!references[refKey]) {
return false;
}
return isItemKeyEqual(queryRef, references[refKey].key);
};
var isCompoundConditionQueryMatch = (queryCondition, item) => {
if (queryCondition.compoundType === "AND") {
return queryCondition.conditions.every(
(condition) => isCondition(condition) ? isConditionQueryMatch(condition, item) : isCompoundConditionQueryMatch(condition, item)
);
} else {
return queryCondition.conditions.some(
(condition) => isCondition(condition) ? isConditionQueryMatch(condition, item) : isCompoundConditionQueryMatch(condition, item)
);
}
};
var isConditionQueryMatch = (queryCondition, item) => {
const propKey = queryCondition.column;
logger4.trace("doesConditionMatch", { propKey, queryCondition, item });
if (item[propKey] === void 0) {
logger4.debug("Item does not contain prop under key", { propKey, item });
return false;
}
logger4.debug("Comparing Condition", { propKey, itemProp: item[propKey], queryCondition });
if (queryCondition.value === null) {
if (queryCondition.operator === "==") {
return item[propKey] === null;
} else if (queryCondition.operator === "!=") {
return item[propKey] !== null;
} else {
throw new Error(
`Operator ${queryCondition.operator} cannot be used with null value. Use '==' for null checks or '!=' for not-null checks.`
);
}
}
let result = false;
switch (queryCondition.operator) {
case "==":
result = item[propKey] === queryCondition.value;
break;
case "!=":
result = item[propKey] !== queryCondition.value;
break;
case ">":
result = item[propKey] > queryCondition.value;
break;
case ">=":
result = item[propKey] >= queryCondition.value;
break;
case "<":
result = item[propKey] < queryCondition.value;
break;
case "<=":
result = item[propKey] <= queryCondition.value;
break;
case "in":
result = queryCondition.value.includes(item[propKey]);
break;
case "not-in":
result = !queryCondition.value.includes(item[propKey]);
break;
case "array-contains":
result = item[propKey].includes(queryCondition.value);
break;
case "array-contains-any":
result = queryCondition.value.some((value) => item[propKey].includes(value));
break;
}
return result;
};
var isAggQueryMatch = (aggKey, aggQuery, agg) => {
const aggItem = agg.item;
logger4.debug("Comparing Agg", { aggKey, aggItem, aggQuery });
if (!aggItem) {
return false;
}
return isQueryMatch(aggItem, aggQuery);
};
var isEventQueryMatch = (eventKey, eventQuery, item) => {
if (!item.events[eventKey]) {
logger4.debug("Item does not contain event under key", { eventKey, events: item.events });
return false;
} else {
const itemEvent = item.events[eventKey];
if (itemEvent.at !== null) {
if (eventQuery.start && !(eventQuery.start.getTime() <= itemEvent.at.getTime())) {
logger4.debug("Item date before event start query", { eventQuery, itemEvent });
return false;
}
if (eventQuery.end && !(eventQuery.end.getTime() > itemEvent.at.getTime())) {
logger4.debug("Item date after event end query", { eventQuery, itemEvent });
return false;
}
} else {
logger4.debug("Item event does contains a null at", { itemEvent });
return false;
}
return true;
}
};
var isQueryMatch = (item, query) => {
logger4.trace("isMatch", { item, query });
if (query.refs && item.refs) {
for (const key in query.refs) {
const queryRef = query.refs[key];
if (!isRefQueryMatch(key, queryRef.key, item.refs)) return false;
}
} else if (query.refs && !item.refs) {
logger4.debug("Query contains refs but item does not have refs", { query, item });
return false;
}
if (query.compoundCondition && item) {
if (!isCompoundConditionQueryMatch(query.compoundCondition, item)) return false;
}
if (query.events && item.events) {
for (const key in query.events) {
const queryEvent = query.events[key];
if (!isEventQueryMatch(key, queryEvent, item)) return false;
}
return true;
}
if (query.aggs && item.aggs) {
for (const key in query.aggs) {
const aggQuery = query.aggs[key];
if (item.aggs[key]) {
let hasMatch = false;
for (const aggRecord of item.aggs[key]) {
if (isAggQueryMatch(key, aggQuery, aggRecord)) {
hasMatch = true;
break;
}
}
if (!hasMatch) return false;
} else {
return false;
}
}
} else if (query.aggs && !item.aggs) {
logger4.debug("Query contains aggs but item does not have aggs", { query, item });
return false;
}
return true;
};
var abbrevQuery = (query) => {
const abbrev = ["IQ"];
if (query) {
if (query.refs) {
for (const key in query.refs) {
const ref = abbrevRef(key, query.refs[key]);
abbrev.push(ref);
}
}
if (query.compoundCondition) {
const props = abbrevCompoundCondition(query.compoundCondition);
abbrev.push(props);
}
if (query.aggs) {
for (const key in query.aggs) {
const agg = abbrevAgg(key, query.aggs[key]);
abbrev.push(agg);
}
}
if (query.events) {
const events = `(E${Object.keys(query.events).join(",")})`;
abbrev.push(events);
}
if (query.limit) {
abbrev.push(`L${query.limit}`);
}
if (query.offset) {
abbrev.push(`O${query.offset}`);
}
} else {
abbrev.push("(empty)");
}
return abbrev.join(" ");
};
var abbrevRef = (key, ref) => {
if (isPriKey(ref.key)) {
const priKey = ref.key;
return `R(${key},${priKey.kt},${priKey.pk})`;
} else {
const comKey = ref.key;
return `R(${key},${JSON.stringify(comKey)})`;
}
};
var abbrevAgg = (key, agg) => {
return `A(${key},${abbrevQuery(agg)})`;
};
var abbrevCompoundCondition = (compoundCondition) => {
return `CC(${compoundCondition.compoundType},${compoundCondition.conditions ? compoundCondition.conditions.map(abbrevCondition).join(",") : "No Conditions"})`;
};
var abbrevCondition = (condition) => {
if (isCondition(condition)) {
return `(${condition.column},${condition.value},${condition.operator})`;
} else {
return abbrevCompoundCondition(condition);
}
};
// src/item/IUtils.ts
var isPriItem = (item) => {
return !!(item && item.key && isPriKey(item.key));
};
var isComItem = (item) => {
return !!(item && item.key && isComKey(item.key));
};
// src/errors/ActionError.ts
var ActionError = class extends Error {
constructor(errorInfo, cause) {
super(errorInfo.message);
this.errorInfo = errorInfo;
this.name = "ActionError";
this.cause = cause;
if (!this.errorInfo.technical) {
this.errorInfo.technical = { timestamp: (/* @__PURE__ */ new Date()).toISOString() };
}
if (!this.errorInfo.technical.timestamp) {
this.errorInfo.technical.timestamp = (/* @__PURE__ */ new Date()).toISOString();
}
}
toJSON() {
return this.errorInfo;
}
};
// src/errors/ValidationError.ts
var ValidationError = class extends ActionError {
fieldErrors;
constructor(message, validOptions, suggestedAction, conflictingValue, fieldErrors) {
super({
code: "VALIDATION_ERROR",
message,
operation: { type: "create", name: "", params: {} },
// Will be filled by wrapper
context: { itemType: "" },
// Will be filled by wrapper
details: {
validOptions,
suggestedAction: suggestedAction || (validOptions && validOptions.length > 0 ? `Valid options are: ${validOptions.join(", ")}. Please use one of these values.` : "Check the validation requirements and ensure all fields meet the required format, type, and constraints."),
retryable: true,
conflictingValue,
fieldErrors
},
technical: {
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}
});
this.fieldErrors = fieldErrors;
if (fieldErrors) {
if (!this.errorInfo.details) this.errorInfo.details = {};
this.errorInfo.details.fieldErrors = fieldErrors;
}
}
};
// src/errors/NotFoundError.ts
var NotFoundError = class extends ActionError {
constructor(message, itemType, key) {
super({
code: "NOT_FOUND",
message,
operation: { type: "get", name: "", params: {} },
context: {
itemType,
key: typeof key === "object" ? key : { primary: key }
},
details: {
suggestedAction: "Verify the item ID/key is correct, check if the item was deleted, or create the item if it should exist.",
retryable: false
},
technical: {
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}
});
this.name = "NotFoundError";
}
};
// src/errors/BusinessLogicError.ts
var BusinessLogicError = class extends ActionError {
constructor(message, suggestedAction, retryable = false) {
super({
code: "BUSINESS_LOGIC_ERROR",
message,
operation: { type: "action", name: "", params: {} },
context: { itemType: "" },
details: {
suggestedAction: suggestedAction || "Review the business logic requirements and ensure all conditions are met before retrying.",
retryable
},
technical: {
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}
});
}
};
// src/errors/PermissionError.ts
var PermissionError = class extends ActionError {
constructor(message, requiredPermission, currentPermissions) {
super({
code: "PERMISSION_DENIED",
message,
operation: { type: "action", name: "", params: {} },
context: {
itemType: "",
...requiredPermission && { requiredPermission }
},
details: {
...requiredPermission && {
suggestedAction: `Required permission: ${requiredPermission}`,
expectedValue: requiredPermission
},
...currentPermissions && { conflictingValue: currentPermissions },
retryable: false
},
technical: {
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}
});
}
};
// src/errors/DuplicateError.ts
var DuplicateError = class extends ActionError {
constructor(message, existingItemIdOrKey, duplicateField) {
let existingItemId = null;
let keyInfo = null;
if (typeof existingItemIdOrKey === "object" && existingItemIdOrKey !== null) {
existingItemId = existingItemIdOrKey.pk || existingItemIdOrKey.id || existingItemIdOrKey.primary || null;
if (existingItemId !== null) {
keyInfo = {
primary: existingItemId,
...existingItemIdOrKey
};
} else {
keyInfo = existingItemIdOrKey;
}
} else if (typeof existingItemIdOrKey !== "undefined") {
existingItemId = existingItemIdOrKey;
keyInfo = { primary: existingItemId };
}
super({
code: "DUPLICATE_ERROR",
message,
operation: { type: "create", name: "", params: {} },
context: {
itemType: "",
...keyInfo && { key: keyInfo },
...existingItemId && {
affectedItems: [{
id: existingItemId,
type: "",
displayName: `Existing item with ${duplicateField || "key"}`
}]
}
},
details: {
suggestedAction: duplicateField ? `An item with this ${duplicateField} already exists. Use a different ${duplicateField} value or update the existing item.` : "An item with this key already exists. Use a different key or update the existing item using upsert.",
retryable: false,
conflictingValue: duplicateField
},
technical: {
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}
});
}
};
// src/operations/errorEnhancer.ts
async function executeWithContext(operation, context) {
try {
return await operation();
} catch (error) {
throw enhanceError(error, context);
}
}
function executeWithContextSync(operation, context) {
try {
return operation();
} catch (error) {
throw enhanceError(error, context);
}
}
function enhanceError(error, context) {
if (!(error instanceof ActionError)) {
return error;
}
error.errorInfo.operation = {
type: context.operationType,
name: context.operationName,
params: context.params
};
if (!error.errorInfo.context.itemType) {
error.errorInfo.context.itemType = context.itemType;
}
if (context.key) {
const existingKey = error.errorInfo.context.key;
const hasNoPrimaryKey = !existingKey || typeof existingKey.primary === "undefined";
const hasNoCompositeKey = !existingKey || !existingKey.composite;
const shouldOverride = hasNoPrimaryKey && hasNoCompositeKey;
if (shouldOverride) {
error.errorInfo.context.key = extractKeyInfo(context.key);
}
}
if (context.locations && context.locations.length > 0 && !error.errorInfo.context.parentLocation) {
error.errorInfo.context.parentLocation = {
id: context.locations[0].lk,
type: context.locations[0].kt
};
}
return error;
}
function extractKeyInfo(key) {
if ("loc" in key) {
const ktaArray = Array.isArray(key.kt) ? key.kt : [key.kt];
const locations = Array.isArray(key.loc) ? key.loc : [];
return {
composite: {
sk: key.pk,
kta: ktaArray,
locations: locations.map((loc) => ({
lk: loc.lk,
kt: loc.kt
}))
}
};
} else if ("pk" in key) {
return { primary: key.pk };
}
return { primary: JSON.stringify(key) };
}
function isActionError(error) {
return error instanceof ActionError;
}
function getErrorInfo(error) {
if (isActionError(error)) {
return error.toJSON();
}
if (error instanceof Error) {
return {
code: "UNKNOWN_ERROR",
message: error.message,
technical: {
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
stackTrace: error.stack
}
};
}
return {
code: "UNKNOWN_ERROR",
message: String(error),
technical: {
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}
};
}
// node_modules/@fjell/validation/dist/index.js
import Logging2 from "@fjell/logging";
import Logging22 from "@fjell/logging";
import Logging3 from "@fjell/logging";
import Logging4 from "@fjell/logging";
var logger5 = Logging2.getLogger("validation.LocationValidator");
var validateLocations = (locations, coordinate, operation) => {
if (!locations || locations.length === 0) {
return;
}
const keyTypeArray = coordinate.kta;
const expectedLocationTypes = keyTypeArray.slice(1);
const actualLocationTypes = locations.map((loc) => loc.kt);
logger5.debug(`Validating locations for ${operation}`, {
expected: expectedLocationTypes,
actual: actualLocationTypes,
coordinate: keyTypeArray
});
if (actualLocationTypes.length > expectedLocationTypes.length) {
logger5.error("Location key array has too many elements", {
expected: expectedLocationTypes.length,
actual: actualLocationTypes.length,
expectedTypes: expectedLocationTypes,
actualTypes: actualLocationTypes,
coordinate,
operation
});
throw new Error(
`Invalid location key array for ${operation}: Expected at most ${expectedLocationTypes.length} location keys (hierarchy: [${expectedLocationTypes.join(", ")}]), but received ${actualLocationTypes.length} (types: [${actualLocationTypes.join(", ")}])`
);
}
for (let i = 0; i < actualLocationTypes.length; i++) {
if (expectedLocationTypes[i] !== actualLocationTypes[i]) {
logger5.error("Location key array order mismatch", {
position: i,
expected: expectedLocationTypes[i],
actual: actualLocationTypes[i],
expectedHierarchy: expectedLocationTypes,
actualOrder: actualLocationTypes,
coordinate,
operation
});
throw new Error(
`Invalid location key array order for ${operation}: At position ${i}, expected key type "${expectedLocationTypes[i]}" but received "${actualLocationTypes[i]}". Location keys must be ordered according to the hierarchy: [${expectedLocationTypes.join(", ")}]. Received order: [${actualLocationTypes.join(", ")}]`
);
}
}
logger5.debug(`Location key array validation passed for ${operation}`, { locations });
};
var isComKey3 = (key) => {
return typeof key !== "undefined" && (typeof key.pk !== "undefined" && typeof key.kt !== "undefined") && (typeof key.loc !== "undefined" && Array.isArray(key.loc));
};
var isPriKey3 = (key) => {
return typeof key !== "undefined" && (typeof key.pk !== "undefined" && typeof key.kt !== "undefined") && typeof key.loc === "undefined";
};
var toKeyTypeArray2 = (ik) => {
if (isComKey3(ik)) {
const ck = ik;
return [ck.kt, ...ck.loc.map((l) => l.kt)];
} else {
return [ik.kt];
}
};
var logger22 = Logging22.getLogger("validation.KeyValidator");
var validateLocationKeyOrder = (key, coordinate, operation) => {
const keyTypeArray = coordinate.kta;
const expectedLocationTypes = keyTypeArray.slice(1);
const actualLocationTypes = key.loc.map((loc) => loc.kt);
if (expectedLocationTypes.length !== actualLocationTypes.length) {
logger22.error("Location key array length mismatch", {
expected: expectedLocationTypes.length,
actual: actualLocationTypes.length,
key,
coordinate,
operation
});
const expectedOrder = expectedLocationTypes.map(
(kt, i) => ` [${i}] { kt: '${kt}', lk: <value> }`
).join("\n");
const actualOrder = key.loc.map(
(loc, i) => ` [${i}] { kt: '${loc.kt}', lk: ${JSON.stringify(loc.lk)} }`
).join("\n");
throw new Error(
`Location key array length mismatch for ${operation} operation.
Expected ${expectedLocationTypes.length} location keys but received ${actualLocationTypes.length}.
Expected location key order for '${keyTypeArray[0]}':
${expectedOrder}
Received location key order:
${actualOrder}`
);
}
for (let i = 0; i < expectedLocationTypes.length; i++) {
if (expectedLocationTypes[i] !== actualLocationTypes[i]) {
logger22.error("Location key array order mismatch", {
position: i,
expected: expectedLocationTypes[i],
actual: actualLocationTypes[i],
key,
coordinate,
operation
});
const expectedOrder = expectedLocationTypes.map(
(kt, i2) => ` [${i2}] { kt: '${kt}', lk: <value> }`
).join("\n");
const actualOrder = key.loc.map(
(loc, i2) => ` [${i2}] { kt: '${loc.kt}', lk: ${JSON.stringify(loc.lk)} }`
).join("\n");
throw new Error(
`Location key array order mismatch for ${operation} operation.
At position ${i}, expected key type "${expectedLocationTypes[i]}" but received "${actualLocationTypes[i]}".
Expected location key order for '${keyTypeArray[0]}':
${expectedOrder}
Received location key order:
${actualOrder}
Tip: Location keys must be ordered according to the hierarchy: [${expectedLocationTypes.join(", ")}]`
);
}
}
};
var validateKey = (key, coordinate, operation) => {
logger22.debug(`Validating key for ${operation}`, { key, coordinate: coordinate.kta });
if (!key || key === null) {
throw new Error(
`Invalid key structure for ${operation} operation.
The provided key is null or undefined.
Valid key formats:
PriKey: { kt: string, pk: string|number }
ComKey: { kt: string, pk: string|number, loc: Array<{ kt: string, lk: string|number }> }`
);
}
const isCompositeLibrary = coordinate.kta.length > 1;
const keyIsComposite = isComKey3(key);
const keyIsPrimary = isPriKey3(key);
if (isCompositeLibrary && !keyIsComposite) {
logger22.error(`Composite library received primary key in ${operation}`, { key, coordinate });
const keyTypeArray = coordinate.kta;
throw new Error(
`Invalid key type for ${operation} operation.
This is a composite item library. You must provide a ComKey with location keys.
Expected: ComKey with format:
{
kt: '${keyTypeArray[0]}',
pk: string|number,
loc: [
` + keyTypeArray.slice(1).map((kt) => ` { kt: '${kt}', lk: string|number }`).join(",\n") + `
]
}
Received: PriKey with format:
${JSON.stringify(key, null, 2)}
Example correct usage:
library.operations.${operation}({
kt: '${keyTypeArray[0]}',
pk: 'item-id',
loc: [${keyTypeArray.slice(1).map((kt) => `{ kt: '${kt}', lk: 'parent-id' }`).join(", ")}]
})`
);
}
if (!isCompositeLibrary && keyIsComposite) {
logger22.error(`Primary library received composite key in ${operation}`, { key, coordinate });
const keyTypeArray = coordinate.kta;
throw new Error(
`Invalid key type for ${operation} operation.
This is a primary item library. You should provide a PriKey without location keys.
Expected: PriKey with format:
{ kt: '${keyTypeArray[0]}', pk: string|number }
Received: ComKey with format:
${JSON.stringify(key, null, 2)}
Example correct usage:
library.operations.${operation}({ kt: '${keyTypeArray[0]}', pk: 'item-id' })`
);
}
if (!keyIsPrimary && !keyIsComposite) {
logger22.error(`Invalid key structure in ${operation}`, { key, coordinate });
throw new Error(
`Invalid key structure for ${operation} operation.
The provided key does not match PriKey or ComKey format.
Received:
${JSON.stringify(key, null, 2)}
Valid key formats:
PriKey: { kt: string, pk: string|number }
ComKey: { kt: string, pk: string|number, loc: Array<{ kt: string, lk: string|number }> }`
);
}
const expectedKeyType = coordinate.kta[0];
if (key.kt !== expectedKeyType) {
logger22.error(`Key type mismatch in ${operation}`, {
expected: expectedKeyType,
received: key.kt,
key,
coordinate
});
throw new Error(
`Invalid key type for ${operation} operation.
Expected key type: '${expectedKeyType}'
Received key type: '${key.kt}'
Example correct usage:
library.operations.${operation}({ kt: '${expectedKeyType}', pk: 'item-id', ... })`
);
}
if (keyIsComposite) {
const comKey = key;
if (comKey.loc.length === 0) {
logger22.debug(`Empty loc array detected in ${operation} - will search across all locations`, { key });
} else {
validateLocationKeyOrder(comKey, coordinate, operation);
}
}
logger22.debug(`Key validation passed for ${operation}`, { key });
};
var logger32 = Logging3.getLogger("validation.ItemValidator");
var validatePKForItem = (item, pkType) => {
if (!item) {
logger32.error("Item validation failed - item is undefined", {
component: "core",
operation: "validatePK",
expectedType: pkType,
item,
suggestion: "Ensure the operation returns a valid item object, not undefined/null"
});
throw new Error(
`Item validation failed: item is undefined. Expected item of type '${pkType}'. This usually indicates a database operation returned null/undefined unexpectedly.`
);
}
if (!item.key) {
logger32.error("Item validation failed - item missing key", {
component: "core",
operation: "validatePK",
expectedType: pkType,
item,
suggestion: "Ensure the item has a valid key property with kt and pk fields"
});
throw new Error(
`Item validation failed: item does not have a key property. Expected key with type '${pkType}'. Item: ${JSON.stringify(item)}. This indicates a database processing error.`
);
}
const keyTypeArray = toKeyTypeArray2(item.key);
if (keyTypeArray[0] !== pkType) {
logger32.error("Key type mismatch during validation", {
component: "core",
operation: "validatePK",
expectedType: pkType,
actualType: keyTypeArray[0],
keyTypeArray,
itemKey: item.key,
suggestion: `Ensure the item key has kt: '${pkType}', not '${keyTypeArray[0]}'`
});
throw new Error(
`Item has incorrect primary key type. Expected '${pkType}', got '${keyTypeArray[0]}'. Key: ${JSON.stringify(item.key)}. This indicates a data model mismatch.`
);
}
return item;
};
var validatePK = (input, pkType) => {
logger32.trace("Checking Return Type", { input });
if (Array.isArray(input)) {
return input.map((item) => validatePKForItem(item, pkType));
}
return validatePKForItem(input, pkType);
};
var logger42 = Logging4.getLogger("validation.QueryValidator");
var validateQuery = (query, operation) => {
if (typeof query === "undefined" || query === null) {
return;
}
if (typeof query !== "object") {
logger42.error(`Invalid query type for ${operation}`, { query, type: typeof query });
throw new Error(
`[${operation}] Invalid query parameter.
Expected: object or undefined
Received: ${typeof query}
Example valid queries:
{}
{ filter: { status: 'active' } }
{ limit: 10, sort: { field: 'name', order: 'asc' } }`
);
}
if (Array.isArray(query)) {
logger42.error(`Query cannot be an array for ${operation}`, { query });
throw new Error(
`[${operation}] Invalid query parameter.
Query cannot be an array.
Received: ${JSON.stringify(query)}`
);
}
logger42.debug(`Query validation passed for ${operation}`, { query });
};
var validateOperationParams = (params, operation) => {
if (typeof params === "undefined") {
return;
}
if (params === null) {
logger42.error(`Params cannot be null for ${operation}`, { params });
throw new Error(
`[${operation}] Invalid operation parameters.
Parameters cannot be null.
Expected: object or undefined
Received: null
Example valid parameters:
{}
{ email: 'user@example.com' }
{ status: 'active', limit: 10 }`
);
}
if (typeof params !== "object") {
logger42.error(`Invalid params type for ${operation}`, { params, type: typeof params });
throw new Error(
`[${operation}] Invalid operation parameters.
Expected: object or undefined
Received: ${typeof params}
Example valid parameters:
{}
{ email: 'user@example.com' }
{ status: 'active', limit: 10 }`
);
}
if (Array.isArray(params)) {
logger42.error(`Params cannot be an array for ${operation}`, { params });
throw new Error(
`[${operation}] Invalid operation parameters.
Parameters cannot be an array.
Received: ${JSON.stringify(params)}`
);
}
for (const [key, value] of Object.entries(params)) {
const valueType = typeof value;
const isValidType = valueType === "string" || valueType === "number" || valueType === "boolean" || value instanceof Date || Array.isArray(value) && value.every(
(v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v instanceof Date
);
if (!isValidType) {
logger42.error(`Invalid param value type for ${operation}`, { key, value, valueType });
throw new Error(
`[${operation}] Invalid value type for parameter "${key}".
Allowed types: string, number, boolean, Date, or arrays of these types
Received: ${valueType}
Value: ${JSON.stringify(value)}`
);
}
}
logger42.debug(`Operation params validation passed for ${operation}`, { params });
};
var validateFinderName = (finder, operation) => {
if (!finder || typeof finder !== "string") {
logger42.error(`Invalid finder name for ${operation}`, { finder, type: typeof finder });
throw new Error(
`[${operation}] Finder name must be a non-empty string.
Received: ${JSON.stringify(finder)}`
);
}
if (finder.trim().length === 0) {
logger42.error(`Empty finder name for ${operation}`, { finder });
throw new Error(
`[${operation}] Finder name cannot be empty or whitespace only.
Received: "${finder}"`
);
}
logger42.debug(`Finder name validation passed for ${operation}`, { finder });
};
var validateActionName = (action, operation) => {
if (!action || typeof action !== "string") {
logger42.error(`Invalid action name for ${operation}`, { action, type: typeof action });
throw new Error(
`[${operation}] Action name must be a non-empty string.
Received: ${JSON.stringify(action)}`
);
}
if (action.trim().length === 0) {
logger42.error(`Empty action name for ${operation}`, { action });
throw new Error(
`[${operation}] Action name cannot be empty or whitespace only.
Received: "${action}"`
);
}
logger42.debug(`Action name validation passed for ${operation}`, { action });
};
var validateFacetName = (facet, operation) => {
if (!facet || typeof facet !== "string") {
logger42.error(`Invalid facet name for ${operation}`, { facet, type: typeof facet });
throw new Error(
`[${operation}] Facet name must be a non-empty string.
Received: ${JSON.stringify(facet)}`
);
}
if (facet.trim().length === 0) {
logger42.error(`Empty facet name for ${operation}`, { facet });
throw new Error(
`[${operation}] Facet name cannot be empty or whitespace only.
Received: "${facet}"`
);
}
logger42.debug(`Facet name validation passed for ${operation}`, { facet });
};
// src/operations/wrappers/createOneWrapper.ts
var logger6 = logger_default.get("operations", "wrappers", "one");
function createOneWrapper(coordinate, implementation, options = {}) {
const operationName = options.operationName || "one";
return async (query, locations) => {
if (options.debug) {
logger6.debug(`[${operationName}] Called with:`, { query, locations });
}
if (!options.skipValidation) {
try {
validateQuery(query, operationName);
validateLocations(locations, coordinate, operationName);
} catch (error) {
throw error;
}
}
const normalizedQuery = query ?? {};
const normalizedLocations = locations ?? [];
try {
const result = await implementation(normalizedQuery, normalizedLocations);
if (options.debug) {
logger6.debug(`[${operationName}] Result:`, result ? "found" : "not found");
}
if (result && !options.skipValidation) {
return validatePK(result, coordinate.kta[0]);
}
return result;
} catch (error) {
if (options.onError) {
const context = {
operationName,
params: [query, locations],
coordinate
};
throw options.onError(error, context);
}
const enhanced = new Error(
`[${operationName}] Operation failed: ${error.message}`,
{ cause: error }
);
if (error.stack) {
enhanced.stack = error.stack;
}
throw enhanced;
}
};
}
// src/operations/wrappers/createAllWrapper.ts
var logger7 = logger_default.get("operations", "wrappers", "all");
function createAllWrapper(coordinate, implementation, wrapperOptions = {}) {
const operationName = wrapperOptions.operationName || "all";
return async (query, locations, allOptions) => {
if (wrapperOptions.debug) {
logger7.debug(`[${operationName}] Called with:`, { query, locations, allOptions });
}
if (!wrapperOptions.skipValidation) {
validateQuery(query, operationName);
validateLocations(locations, coordinate, operationName);
if (allOptions && "limit" in allOptions && allOptions.limit != null) {
if (!Number.isInteger(allOptions.limit) || allOptions.limit < 1) {
logger7.error(`Invalid limit parameter in ${operationName}`, {
component: "core",
wrapper: "createAllWrapper",
operation: operationName,
limit: allOptions.limit,
limitType: typeof allOptions.limit,
suggestion: "Use a positive integer (1, 2, 3, ...) for limit parameter"
});
throw new Error(
`[${operationName}] limit must be a positive integer, got: ${allOptions.limit} (${typeof allOptions.limit}). Suggestion: Use limit: 10 or similar positive integer value.`
);
}
}
if (allOptions && "offset" in allOptions && allOpti