UNPKG

@fjell/core

Version:

Core Item and Key Framework for Fjell

1,579 lines (1,523 loc) 80.1 kB
// 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