@cldmv/slothlet 3.14.0 → 3.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -56
- package/dist/lib/builders/api_builder.mjs +1 -1
- package/dist/lib/handlers/api-manager.mjs +1 -1
- package/dist/lib/handlers/metadata.mjs +1 -1
- package/dist/lib/handlers/ownership.mjs +1 -1
- package/dist/lib/handlers/unified-wrapper.mjs +1 -1
- package/dist/lib/i18n/languages/en-us.json +4 -0
- package/dist/slothlet.mjs +1 -1
- package/package.json +1 -1
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import{translate}from"@cldmv/slothlet/i18n";import{ComponentBase}from"#factories/component-base";import{UnifiedWrapper,resolveWrapper}from"#handlers/unified-wrapper";import{isFrameworkInternal}from"#handlers/framework-internals";import{fsp,path}from"@cldmv/slothlet/helpers/platform";class ApiManager extends ComponentBase{static slothletProperty="apiManager";constructor(slothlet){super(slothlet);this.state={addHistory:[],initialConfig:slothlet?.config||null,operationHistory:[],replaceShadows:new Map}}normalizeApiPath(apiPath){if(apiPath===""||apiPath===null||apiPath===void 0){return{apiPath:"",parts:[]}}if(Array.isArray(apiPath)){if(apiPath.length===0){return{apiPath:"",parts:[]}}for(let i=0;i<apiPath.length;i++){if(typeof apiPath[i]!=="string"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,segment:apiPath[i],index:i,reason:translate("API_PATH_REASON_ARRAY_ELEMENTS"),validationError:true})}if(apiPath[i].trim()===""){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,segment:apiPath[i],index:i,reason:translate("API_PATH_REASON_ARRAY_EMPTY_SEGMENTS"),validationError:true})}}if(apiPath[0]==="slothlet"||apiPath.length===1&&(apiPath[0]==="shutdown"||apiPath[0]==="destroy")){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_RESERVED_NAME"),index:void 0,segment:void 0,validationError:true})}return{apiPath:apiPath.join("."),parts:apiPath}}if(typeof apiPath!=="string"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_INVALID_TYPE"),index:void 0,segment:void 0,validationError:true})}const normalized=apiPath.trim();if(normalized===""){return{apiPath:"",parts:[]}}const parts=normalized.split(".");if(parts.length===0||parts.some(part=>part.trim()==="")){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:normalized,reason:translate("API_PATH_REASON_EMPTY_SEGMENTS"),index:void 0,segment:void 0,validationError:true})}if(parts[0]==="slothlet"||normalized==="shutdown"||normalized==="destroy"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:normalized,reason:translate("API_PATH_REASON_RESERVED_NAME"),index:void 0,segment:void 0,validationError:true})}return{apiPath:normalized,parts}}async resolvePath(inputPath){if(!inputPath||typeof inputPath!=="string"){throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:inputPath,validationError:true})}const resolvedPath=this.slothlet.helpers.resolver.resolvePathFromCaller(inputPath);if(this.slothlet.envTarget==="browser"){const lastSegment=resolvedPath.split("/").pop();const isFile=lastSegment.includes(".");return{resolvedPath,isDirectory:!isFile,isFile}}try{const stats=await fsp.stat(resolvedPath);return{resolvedPath,isDirectory:stats.isDirectory(),isFile:stats.isFile()}}catch(error){if(error instanceof this.SlothletError){throw error}throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:resolvedPath,validationError:true})}}async resolveFolderPath(folderPath){if(!folderPath||typeof folderPath!=="string"){throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:folderPath,validationError:true})}const resolvedPath=this.slothlet.helpers.resolver.resolvePathFromCaller(folderPath);try{const stats=await fsp.stat(resolvedPath);if(!stats.isDirectory()){throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:resolvedPath,validationError:true})}}catch(error){if(error instanceof this.SlothletError){throw error}throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:resolvedPath,validationError:true})}return resolvedPath}buildDefaultModuleId(apiPath,____resolvedFolderPath){const randomSuffix=Math.random().toString(36).substring(2,8);const prefix=apiPath||"auto";return`${prefix}_${randomSuffix}`}getValueAtPath(root,parts){let current=root;for(const part of parts){if(!current||typeof current!=="object"&&typeof current!=="function"){return void 0}current=current[part]}return current}ensureParentPath(root,parts,options={}){const{moduleID,sourceFolder}=options;let current=root;for(let i=0;i<parts.length-1;i+=1){const part=parts[i];const next=current[part];if(next===void 0){const containerPath=parts.slice(0,i+1).join(".");const containerWrapper=new UnifiedWrapper(this.slothlet,{mode:this.____config.mode,apiPath:containerPath,moduleID,sourceFolder});containerWrapper.___setImpl({},moduleID);current[part]=containerWrapper.createProxy();current=current[part];continue}if(next&&(typeof next==="object"||typeof next==="function")){current=next;continue}throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:parts.slice(0,i+1).join("."),reason:translate("API_PATH_REASON_NOT_TRAVERSABLE"),index:void 0,segment:void 0,validationError:true})}return current}setOwnedProperty(apiPath,value,callerWrapper){const{parts}=this.normalizeApiPath(apiPath);if(parts.length===0){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:String(apiPath??""),reason:translate("API_PATH_REASON_EMPTY_SEGMENTS"),index:void 0,segment:void 0,validationError:true})}const RESERVED=new Set(["__proto__","prototype","constructor"]);for(const segment of parts){if(RESERVED.has(segment)){throw new this.SlothletError("LOOSE_SET_RESERVED_KEY",{apiPath:parts.join("."),segment,validationError:true})}}const callerModuleID=callerWrapper?.____slothletInternal?.moduleID??null;const callerWrapperPath=callerWrapper?.____slothletInternal?.apiPath??null;let ownedRoot=null;if(callerModuleID&&this.slothlet.handlers?.ownership?.getModuleEndpoint){const endpoint=this.slothlet.handlers.ownership.getModuleEndpoint(callerModuleID);if(endpoint!=null){ownedRoot=endpoint}}if(ownedRoot===null)ownedRoot=".";if(ownedRoot&&ownedRoot!=="."&&ownedRoot!==""){const ownedParts=String(ownedRoot).split(".").filter(Boolean);const isUnderOwn=ownedParts.length<=parts.length&&ownedParts.every((seg,i)=>parts[i]===seg);if(!isUnderOwn){const callerApiPath=callerWrapperPath??ownedRoot;throw new this.SlothletError("LOOSE_SET_NOT_OWNED",{apiPath:parts.join("."),callerApiPath,validationError:true})}}const callerSourceFolder=callerWrapper?.____slothletInternal?.sourceFolder??null;const root=this.slothlet.boundApi;const parent=this.ensureParentPath(root,parts,{moduleID:callerModuleID,sourceFolder:callerSourceFolder});const finalKey=parts[parts.length-1];const isWrappable=typeof value==="function"||value!==null&&typeof value==="object";if(isWrappable){const wrapper=new UnifiedWrapper(this.slothlet,{mode:this.____config.mode,apiPath:parts.join("."),moduleID:callerModuleID,isCallable:typeof value==="function",sourceFolder:callerSourceFolder});wrapper.___setImpl(value,callerModuleID);parent[finalKey]=wrapper.createProxy()}else{parent[finalKey]=value}}isWrapperProxy(value){return!!(value&&(typeof value==="object"||typeof value==="function")&&resolveWrapper(value)!==null)}async syncWrapper(existingProxy,nextProxy,config,collisionMode="replace",moduleID=null){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_ENTRY_EXISTING",apiPath:resolveWrapper(existingProxy)?.apiPath});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_ENTRY_NEXT",apiPath:resolveWrapper(nextProxy)?.apiPath})}if(!this.isWrapperProxy(existingProxy)||!this.isWrapperProxy(nextProxy)){return false}const existingWrapper=resolveWrapper(existingProxy)??existingProxy;const nextWrapper=resolveWrapper(nextProxy)??nextProxy;if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_EXISTING",apiPath:existingWrapper.apiPath});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_NEXT",apiPath:nextWrapper.apiPath})}if(nextWrapper.____slothletInternal.materializeFunc&&collisionMode!=="merge"){existingWrapper.____slothletInternal.materializeFunc=nextWrapper.____slothletInternal.materializeFunc}const existingChildKeys=Object.keys(existingWrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));const nextChildKeys=Object.keys(nextWrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_BEFORE_MERGE",existingCacheSize:existingChildKeys.length,nextCacheSize:nextChildKeys.length});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_NEXT_IMPL_KEYS",implKeys:Object.keys(nextWrapper.____slothletInternal.impl||{})});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_NEXT_CHILDCACHE_KEYS",childCacheKeys:nextChildKeys})}if(collisionMode==="replace"){this._recordReplaceShadows(existingWrapper,existingChildKeys,nextChildKeys,moduleID);if(existingWrapper.___setImpl&&nextWrapper.____slothletInternal.impl!==void 0){existingWrapper.___setImpl(nextWrapper.____slothletInternal.impl,moduleID)}else if(nextWrapper.____slothletInternal.impl===void 0){existingWrapper.____slothletInternal.impl=null}else{if(nextWrapper.____slothletInternal.impl!==void 0){existingWrapper.____slothletInternal.impl=nextWrapper.____slothletInternal.impl;if(typeof nextWrapper.____slothletInternal.impl==="function"||nextWrapper.____slothletInternal.impl&&typeof nextWrapper.____slothletInternal.impl.default==="function"){existingWrapper.isCallable=true}}}for(const key of existingChildKeys){delete existingWrapper[key]}existingWrapper.___adoptImplChildren();for(const key of nextChildKeys){const childValue=nextWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}else if(collisionMode==="merge"){for(const key of nextChildKeys){const isInternal=typeof key==="string"&&(key.startsWith("_")||key.startsWith("__"));if(!isInternal&&!Object.prototype.hasOwnProperty.call(existingWrapper,key)){const childValue=nextWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}else if(!isInternal){const existingChild=existingWrapper[key];const nextChild=nextWrapper[key];if(this.isWrapperProxy(existingChild)&&this.isWrapperProxy(nextChild)){const syncWrapper_nextChildWrapper=resolveWrapper(nextChild)??nextChild;const syncWrapper_hasGrandChildren=Object.keys(syncWrapper_nextChildWrapper).some(k=>!k.startsWith("_")&&!k.startsWith("__"));if(syncWrapper_hasGrandChildren){await this.syncWrapper(existingChild,nextChild,config,collisionMode,moduleID)}}}}}else{for(const key of nextChildKeys){const childValue=nextWrapper[key];if(Object.prototype.hasOwnProperty.call(existingWrapper,key)){const existingChild=existingWrapper[key];if(this.isWrapperProxy(existingChild)&&this.isWrapperProxy(childValue)){const nextChildWrapper=resolveWrapper(childValue)??childValue;const hasGrandChildren=Object.keys(nextChildWrapper).some(k=>!k.startsWith("_")&&!k.startsWith("__"));if(hasGrandChildren){await this.syncWrapper(existingChild,childValue,config,collisionMode,moduleID)}else{delete existingWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}else{delete existingWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}else{Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}}if(existingWrapper.____slothletInternal.state){const isActuallyMaterialized=existingWrapper.____slothletInternal.impl&&typeof existingWrapper.____slothletInternal.impl!=="function";existingWrapper.____slothletInternal.state.materialized=isActuallyMaterialized;existingWrapper.____slothletInternal.state.inFlight=false}return true}_recordReplaceShadows(existingWrapper,existingChildKeys,nextChildKeys,moduleID){const ownership=this.slothlet.handlers.ownership;if(!ownership||!moduleID)return;const moduleIDKey=String(moduleID);const containerPath=existingWrapper.____slothletInternal.apiPath;const nextSet=new Set(nextChildKeys);for(const key of existingChildKeys){if(nextSet.has(key))continue;const owner=ownership.getCurrentOwner(`${containerPath}.${key}`);if(!owner)continue;if(String(owner.moduleID)===moduleIDKey)continue;const child=existingWrapper[key];if(!this.isWrapperProxy(child))continue;let list=this.state.replaceShadows.get(moduleIDKey);if(!list){list=[];this.state.replaceShadows.set(moduleIDKey,list)}list.push({container:existingWrapper,key,child,ownerModuleID:String(owner.moduleID)})}}async mutateApiValue(existingValue,nextValue,options,config){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_CALLED",existingType:typeof existingValue,nextType:typeof nextValue});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_WRAPPER_STATUS",existingIsWrapper:this.isWrapperProxy(existingValue),nextIsWrapper:this.isWrapperProxy(nextValue)});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_NEXT_VALUE",nextValue});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_NEXT_VALUE_KEYS",nextValueKeys:nextValue?Object.keys(nextValue):[]})}if(existingValue===nextValue){return}if(this.isWrapperProxy(existingValue)&&this.isWrapperProxy(nextValue)){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_SYNC_WRAPPERS"})}await this.syncWrapper(existingValue,nextValue,config,options.collisionMode,options.moduleID);return}if(this.isWrapperProxy(existingValue)&&!this.isWrapperProxy(nextValue)){const nextIsObjectLike=nextValue&&(typeof nextValue==="object"||typeof nextValue==="function");const nextHasKeys=nextIsObjectLike&&Object.keys(nextValue).length>0;if(nextHasKeys){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_MERGE_INTO_WRAPPER"});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_MERGE_KEYS",keys:Object.keys(nextValue)})}await this.slothlet.builders.apiAssignment.mergeApiObjects(existingValue,nextValue,{removeMissing:options.removeMissing,mutateExisting:true,allowOverwrite:true,syncWrapper:this.syncWrapper.bind(this),collisionMode:options.collisionMode,moduleID:options.moduleID});return}const existingValueRaw=resolveWrapper(existingValue);if(existingValueRaw!==null){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_SETIMPL_FALLBACK"})}existingValueRaw.___setImpl(resolveWrapper(nextValue)?.__impl??nextValue);return}}if(existingValue&&typeof existingValue==="object"&&nextValue&&typeof nextValue==="object"){await this.slothlet.builders.apiAssignment.mergeApiObjects(existingValue,nextValue,{removeMissing:options.removeMissing,mutateExisting:true,allowOverwrite:true,syncWrapper:this.syncWrapper.bind(this),collisionMode:options.collisionMode,moduleID:options.moduleID});return existingValue}return nextValue}async setValueAtPath(root,parts,value,options){const parent=this.ensureParentPath(root,parts,{moduleID:options.moduleID,sourceFolder:options.sourceFolder});const finalKey=parts[parts.length-1];const parentWrapper=resolveWrapper(parent);if(parentWrapper&&parentWrapper.____slothletInternal.mode==="lazy"&&!parentWrapper.____slothletInternal.state.materialized){await parentWrapper._materialize()}const existing=parent?parent[finalKey]:void 0;const collisionMode=options.collisionMode||"merge";const moduleID=options.moduleID;this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH",finalKey,existingType:typeof existing,valueType:typeof value,collisionMode,options});if(existing!==void 0){if(collisionMode==="error"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:parts.join("."),reason:translate("API_PATH_REASON_COLLISION_ERROR"),index:void 0,segment:void 0,validationError:true})}if(collisionMode==="skip"){this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_SKIP_COLLISION",path:parts.join("."),mode:"skip"});return false}if(collisionMode==="warn"){if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARNING_HOT_RELOAD_PATH_COLLISION",{apiPath:parts.join(".")})}return false}if(collisionMode==="replace"){const existingIsObject=typeof existing==="object"||typeof existing==="function";const valueIsObject=typeof value==="object"||typeof value==="function";if(existingIsObject&&valueIsObject){this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_REPLACE_MERGE",path:parts.join("."),mode:"replace"});await this.mutateApiValue(existing,value,{removeMissing:false,allowOverwrite:true,collisionMode:"replace",moduleID},this.____config);return true}else{parent[finalKey]=value;return true}}if(collisionMode==="merge"||collisionMode==="merge-replace"){const existingIsObject=typeof existing==="object"||typeof existing==="function";const valueIsObject=typeof value==="object"||typeof value==="function";if(existingIsObject&&valueIsObject){this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_MERGE_PROPS",mode:collisionMode});await this.mutateApiValue(existing,value,{removeMissing:false,allowOverwrite:true,collisionMode},this.____config);return true}else{const apiPath=parts.join(".");if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARNING_HOT_RELOAD_MERGE_PRIMITIVES",{apiPath})}await this.emitImplDiagnostic("error",{apiPath,code:"WARNING_HOT_RELOAD_MERGE_PRIMITIVES",context:{apiPath},source:"addApi",moduleID,error:new this.SlothletError("WARNING_HOT_RELOAD_MERGE_PRIMITIVES",{apiPath})});return false}}}this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_ASSIGN",finalKey});parent[finalKey]=value;return true}async deletePath(root,parts){let current=root;const stack=[];for(const part of parts.slice(0,-1)){if(!current||typeof current!=="object"&&typeof current!=="function"){return false}stack.push({parent:current,key:part});current=current[part]}const finalKey=parts[parts.length-1];if(!current||typeof current!=="object"&&typeof current!=="function"){return false}if(!Object.prototype.hasOwnProperty.call(current,finalKey)){return false}const removedImpl=current[finalKey];const apiPath=parts.join(".");if(removedImpl&&this.slothlet.handlers?.lifecycle){const metadata=this.slothlet.handlers.metadata?.getMetadata?.(removedImpl);await this.slothlet.handlers.lifecycle.emit("impl:removed",{apiPath,impl:removedImpl,source:"removal",moduleID:metadata?.moduleID,filePath:metadata?.filePath,sourceFolder:metadata?.sourceFolder})}if(resolveWrapper(current)){const wrapper=resolveWrapper(current);const isInternal=typeof finalKey==="string"&&(finalKey.startsWith("_")||finalKey.startsWith("__"));if(!isInternal&&finalKey in wrapper){delete wrapper[finalKey]}if(wrapper.____slothletInternal.impl&&typeof wrapper.____slothletInternal.impl==="object"){delete wrapper.____slothletInternal.impl[finalKey]}}delete current[finalKey];if(removedImpl&&(typeof removedImpl==="object"||typeof removedImpl==="function")){if(resolveWrapper(removedImpl)){const wrapper=resolveWrapper(removedImpl);if(wrapper.____slothletInternal.impl!==void 0){wrapper.____slothletInternal.impl=null}const childKeys=Object.keys(wrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));for(const key of childKeys){delete wrapper[key]}if(wrapper.____slothletInternal.state){wrapper.____slothletInternal.state.materialized=false;wrapper.____slothletInternal.state.inFlight=false}}}if(this.slothlet.handlers?.metadata){const rootSegment=apiPath.split(".")[0];this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}for(let i=stack.length-1;i>=0;i-=1){const{parent,key}=stack[i];const value=parent[key];if(value&&(typeof value==="object"||typeof value==="function")&&Object.keys(value).length===0){delete parent[key]}}return true}async restoreApiPath(apiPath,moduleID){const normalizedModuleId=moduleID||null;const historyEntry=this.state.addHistory.slice().reverse().find(entry=>entry.apiPath===apiPath&&(normalizedModuleId?entry.moduleID===normalizedModuleId:true));if(historyEntry){await this.addApiComponent({apiPath:historyEntry.apiPath,folderPath:historyEntry.folderPath,options:{...historyEntry.options,metadata:historyEntry.metadata,mutateExisting:true,forceOverwrite:true,collisionMode:"replace",recordHistory:false}});return}if(normalizedModuleId==="base"||normalizedModuleId==="core"){const baseApi=await this.slothlet.builders.builder.buildAPI({dir:this.____config.dir,mode:this.____config.mode,moduleID:"base"});const{parts}=this.normalizeApiPath(apiPath);let baseValue=this.getValueAtPath(baseApi,parts);if(baseValue===void 0){await this.deletePath(this.slothlet.api,parts);await this.deletePath(this.slothlet.boundApi,parts);return}const baseValueRaw=resolveWrapper(baseValue);if(baseValue&&baseValueRaw!==null){baseValue=baseValueRaw.__impl}await this.setValueAtPath(this.slothlet.api,parts,baseValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:normalizedModuleId});await this.setValueAtPath(this.slothlet.boundApi,parts,baseValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:normalizedModuleId})}}#normalizePermissionShorthandEntry(entry){const getValueType=value=>{if(value===null)return"null";if(Array.isArray(value))return"array";return typeof value};if(typeof entry==="string"){return{target:entry,condition:void 0}}if(!entry||typeof entry!=="object"||Array.isArray(entry)){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_NOT_OBJECT"),received:getValueType(entry),validationError:true})}if(typeof entry.target!=="string"||!entry.target){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_TARGET_REQUIRED"),received:typeof entry.target,validationError:true})}return{target:entry.target,condition:entry.condition}}async addApiComponent(params){const{apiPath,folderPath,options={},versionConfig=null}=params||{};if(Array.isArray(folderPath)){const moduleIDs=[];for(const singlePath of folderPath){const moduleID2=await this.addApiComponent({apiPath,folderPath:singlePath,options,versionConfig});moduleIDs.push(moduleID2)}return moduleIDs}let effectiveOptions=options;if(folderPath&&typeof folderPath==="object"&&!Array.isArray(folderPath)&&Object.prototype.hasOwnProperty.call(folderPath,"exports")){const siblingOptions={...folderPath};delete siblingOptions.exports;if(Object.keys(siblingOptions).length>0)effectiveOptions={...siblingOptions,...options}}const{metadata={},...restOptions}=effectiveOptions;if(!this.slothlet||!this.slothlet.isLoaded){throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"addApi",validationError:true})}const{apiPath:normalizedPath,parts}=this.normalizeApiPath(apiPath);let effectivePath=normalizedPath;let effectiveParts=parts;if(versionConfig?.version!==void 0&&versionConfig?.version!==null){if(normalizedPath===""){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_VERSIONED_ROOT"),index:void 0,segment:void 0,validationError:true})}if(typeof versionConfig.version!=="string"||!String(versionConfig.version).trim()){throw new this.SlothletError("INVALID_CONFIG_VERSION_TAG",{received:versionConfig.version,validationError:true})}const versionTag=String(versionConfig.version).trim();effectiveParts=[versionTag,...parts];effectivePath=effectiveParts.join(".")}const isSynthetic=typeof folderPath==="function"||folderPath!==null&&typeof folderPath==="object"&&!Array.isArray(folderPath);let syntheticExports=null;let resolvedPath,isDirectory,isFile;if(isSynthetic){const isPlainObject=value=>{if(value===null||typeof value!=="object")return false;const proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null};const describeNonPlain=value=>value===null?"null":Array.isArray(value)?"array":typeof value==="object"?`${value.constructor?.name??"non-plain object"} instance`:typeof value;if(typeof folderPath==="function"){syntheticExports={default:folderPath}}else if(Object.prototype.hasOwnProperty.call(folderPath,"exports")){const inner=folderPath.exports;if(!isPlainObject(inner)){throw new this.SlothletError("INVALID_CONFIG_SYNTHETIC_EXPORTS_SHAPE",{received:describeNonPlain(inner),validationError:true})}syntheticExports=inner}else if(isPlainObject(folderPath)){syntheticExports=folderPath}else{throw new this.SlothletError("INVALID_CONFIG_SYNTHETIC_INPUT",{received:describeNonPlain(folderPath),validationError:true})}if(parts.length===0&&typeof syntheticExports.default==="function"){const def=syntheticExports.default;const{default:___default,...named}=syntheticExports;if(!def.name||def.name==="default"){if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARN_SYNTHETIC_ROOT_UNNAMED",{})}await this.emitImplDiagnostic("warning",{apiPath:normalizedPath,code:"WARN_SYNTHETIC_ROOT_UNNAMED",context:{},source:"addApi",moduleID:restOptions.moduleID});syntheticExports=named}else{if(Object.prototype.hasOwnProperty.call(named,def.name)){if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARN_SYNTHETIC_ROOT_COLLISION",{name:def.name})}await this.emitImplDiagnostic("warning",{apiPath:normalizedPath,code:"WARN_SYNTHETIC_ROOT_COLLISION",context:{name:def.name},source:"addApi",moduleID:restOptions.moduleID})}syntheticExports={[def.name]:def,...named}}}isFile=false;resolvedPath=`synthetic:${normalizedPath||"root"}`}else{({resolvedPath,isDirectory,isFile}=await this.resolvePath(folderPath));if(!isDirectory&&!isFile){throw new this.SlothletError("INVALID_CONFIG_PATH_TYPE",{path:resolvedPath,validationError:true})}if(isFile){const ext=path.extname(resolvedPath);if(![".mjs",".cjs",".js"].includes(ext)){throw new this.SlothletError("INVALID_CONFIG_FILE_TYPE",{path:resolvedPath,extension:ext,validationError:true})}}}const resolvedFolderPath=resolvedPath;let collisionMode;if(restOptions.forceOverwrite){collisionMode="replace"}else{collisionMode=restOptions.collisionMode||this.____config.api?.collision?.api||"error"}const mutateExisting=!!(restOptions.mutateExisting||collisionMode==="merge");const scanHiddenFolders=(restOptions.scanHiddenFolders??this.____config.scanHiddenFolders)===true;if(restOptions.scanHiddenFolders!==void 0&&!this.____config?.silent){new this.SlothletWarning("CONFIG_SCAN_HIDDEN_FOLDERS_DEPRECATED",{option:"scanHiddenFolders"})}const moduleID=restOptions.moduleID?String(restOptions.moduleID):this.buildDefaultModuleId(normalizedPath,resolvedFolderPath);if(restOptions.forceOverwrite&&!moduleID){throw new this.SlothletError("INVALID_CONFIG_FORCE_OVERWRITE_REQUIRES_MODULE_ID",{apiPath:normalizedPath,validationError:true})}let dirForBuild=resolvedFolderPath;let fileFilter=null;if(isFile){dirForBuild=path.dirname(resolvedFolderPath);const fileName=path.basename(resolvedFolderPath);fileFilter=file=>file===fileName}const newApi=await this.slothlet.builders.builder.buildAPI({dir:dirForBuild,mode:this.____config.mode,apiPathPrefix:effectivePath,collisionContext:"addApi",moduleID,collisionMode,fileFilter,rootUnwrap:isFile||isSynthetic,hidden:restOptions.hidden??null,scanHiddenFolders,...isSynthetic?{syntheticExports,syntheticName:parts.length?parts[parts.length-1]:"synthetic"}:{}});if(this.slothlet.handlers.apiCacheManager){this.slothlet.handlers.apiCacheManager.set(moduleID,{endpoint:effectivePath,moduleID,api:newApi,folderPath:resolvedFolderPath,syntheticExports:isSynthetic?syntheticExports:null,mode:this.____config.mode,sanitizeOptions:this.____config.sanitize||{},hidden:restOptions.hidden??null,scanHiddenFolders,collisionMode,config:{...this.____config},timestamp:Date.now()})}this.slothlet.debug("api",{key:"DEBUG_MODE_ADD_API_COMPONENT_BUILD_RETURN",topLevelKeys:Object.keys(newApi),dottedKeys:Object.keys(newApi).filter(k=>k.includes(".")),wrappers:Object.keys(newApi).filter(k=>resolveWrapper(newApi[k])!==null).map(k=>{const _w=resolveWrapper(newApi[k]);return{key:k,apiPath:_w.apiPath,implKeys:Object.keys(_w.____slothletInternal.impl||{}),childCacheSize:Object.keys(_w).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__")).length,childCacheKeys:Object.keys(_w).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__"))}}),nonWrappers:Object.keys(newApi).filter(k=>resolveWrapper(newApi[k])===null).map(k=>({key:k,type:typeof newApi[k]}))});let apiToMerge=newApi;if((isFile||isSynthetic)&&Object.keys(newApi).length===1){const fileName=Object.keys(newApi)[0];apiToMerge=newApi[fileName]}if(!isFile&&normalizedPath){const lastPart=normalizedPath.includes(".")?normalizedPath.split(".").pop():normalizedPath;if(lastPart&&Object.prototype.hasOwnProperty.call(apiToMerge,lastPart)){const dupValue=apiToMerge[lastPart];const dupType=typeof dupValue;if(dupValue!==null&&(dupType==="object"||dupType==="function")){const dupWrapper=resolveWrapper(dupValue);const dupFilePath=dupWrapper?.____slothletInternal?.filePath;const dupFileDir=dupFilePath?dupFilePath.replace(/\\/g,"/").split("/").slice(0,-1).join("/"):null;const normalizedFolderPath=resolvedFolderPath.replace(/\\/g,"/").replace(/\/$/,"");const expectedDir=normalizedFolderPath+"/"+lastPart;const isDirectChild=dupFileDir===expectedDir||dupFileDir===normalizedFolderPath;if(isDirectChild){const hoisted={};for(const k of Object.keys(apiToMerge)){if(k!==lastPart)hoisted[k]=apiToMerge[k]}if(dupWrapper){for(const k of Object.keys(dupWrapper).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__"))){hoisted[k]=dupWrapper[k]}}else{for(const k of Object.keys(dupValue)){hoisted[k]=dupValue[k]}}apiToMerge=hoisted;this.slothlet.debug("api",{key:"DEBUG_MODE_RULE_13_DEDUP_HOISTED_KEY",lastPart,newKeys:Object.keys(apiToMerge)})}}}}if(this.____config.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_ADD_API_COMPONENT_MERGE_KEYS",keys:Object.keys(apiToMerge),isRootLevel:parts.length===0})}let anyAssignmentSucceeded=false;let rootKeys=[];if(parts.length===0){const rootSource=isSynthetic?apiToMerge:newApi;rootKeys=Object.keys(rootSource);if(rootKeys.length===0&&isSynthetic){if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARN_SYNTHETIC_ROOT_EMPTY",{apiPath:normalizedPath||"(root)"})}await this.emitImplDiagnostic("warning",{apiPath:normalizedPath,code:"WARN_SYNTHETIC_ROOT_EMPTY",context:{apiPath:normalizedPath||"(root)"},source:"addApi",moduleID})}for(const key of rootKeys){const result1=await this.setValueAtPath(this.slothlet.api,[key],rootSource[key],{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});const result2=await this.setValueAtPath(this.slothlet.boundApi,[key],rootSource[key],{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(result1||result2){anyAssignmentSucceeded=true}}}else{if(resolveWrapper(apiToMerge)===null){const isCallableNamespace=typeof apiToMerge==="function";const containerWrapper=new UnifiedWrapper(this.slothlet,{apiPath:effectivePath,mode:this.____config.mode,isCallable:isCallableNamespace,moduleID,filePath:resolvedFolderPath,sourceFolder:resolvedFolderPath});containerWrapper.___setImpl(apiToMerge,moduleID);apiToMerge=containerWrapper.createProxy()}const result1=await this.setValueAtPath(this.slothlet.api,effectiveParts,apiToMerge,{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});const result2=await this.setValueAtPath(this.slothlet.boundApi,effectiveParts,apiToMerge,{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(result1||result2){anyAssignmentSucceeded=true}}if(anyAssignmentSucceeded){const pendingMaterializations=[];const seenWrappers=new Set;const collectPendingMaterializations=(obj,depth=0)=>{const objType=typeof obj;if(!obj||objType!=="object"&&objType!=="function"||depth>10)return;if(isFrameworkInternal(obj))return;const wrapper=resolveWrapper(obj);if(wrapper){if(seenWrappers.has(wrapper))return;seenWrappers.add(wrapper);if(wrapper.____slothletInternal.materializationPromise){pendingMaterializations.push(wrapper.____slothletInternal.materializationPromise)}const childKeys=Object.keys(wrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));for(const key of childKeys){collectPendingMaterializations(wrapper[key],depth+1)}return}for(const key of Object.keys(obj)){if(key!=="____slothletInternal"){collectPendingMaterializations(obj[key],depth+1)}}};if(effectiveParts.length===0){for(const key of rootKeys){if(this.slothlet.api[key]){collectPendingMaterializations(this.slothlet.api[key])}}}else{let current=this.slothlet.api;for(const part of effectiveParts){if(current&¤t[part]){current=current[part]}else{break}}if(current){collectPendingMaterializations(current)}}if(pendingMaterializations.length>0){if(this.____config.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_AWAITING_PENDING_MATERIALIZATIONS",count:pendingMaterializations.length,apiPath:normalizedPath})}await Promise.all(pendingMaterializations)}}if(anyAssignmentSucceeded&&metadata&&Object.keys(metadata).length>0&&this.slothlet.handlers.metadata){if(parts.length===0){for(const key of Object.keys(newApi)){this.slothlet.handlers.metadata.registerUserMetadata(key,metadata)}}else{const rootSegment=effectiveParts[0];this.slothlet.handlers.metadata.registerUserMetadata(rootSegment,metadata)}}if(this.slothlet.handlers.ownership&&moduleID){this.slothlet.handlers.ownership.registerSubtree(apiToMerge,moduleID,effectivePath);this.slothlet.handlers.ownership.setModuleEndpoint(moduleID,effectivePath)}if(this.slothlet.handlers.ownership){if(restOptions.recordHistory!==false){this.state.addHistory.push({apiPath:normalizedPath,folderPath:resolvedFolderPath,options:{...restOptions,metadata,moduleID},moduleID,versionConfig:versionConfig||null});this.state.operationHistory.push({type:"add",apiPath:normalizedPath,folderPath:resolvedFolderPath,options:{...restOptions,metadata,moduleID},moduleID,versionConfig:versionConfig||null})}}if(versionConfig?.version&&this.slothlet.handlers.versionManager){const versionTag=String(versionConfig.version).trim();try{this.slothlet.handlers.versionManager.registerVersion(normalizedPath,versionTag,moduleID,versionConfig.metadata??{},versionConfig.default??false)}catch(error){await this._rollbackFailedVersionedAdd({moduleID,effectivePath,normalizedPath});throw error}}if(restOptions.permissions&&this.slothlet.handlers?.permissionManager){const perms=restOptions.permissions;const callerPattern=`${normalizedPath}.**`;if(Array.isArray(perms.deny)){for(const entry of perms.deny){const{target,condition}=this.#normalizePermissionShorthandEntry(entry);this.slothlet.handlers.permissionManager.addRule({caller:callerPattern,target,effect:"deny",condition},moduleID)}}if(Array.isArray(perms.allow)){for(const entry of perms.allow){const{target,condition}=this.#normalizePermissionShorthandEntry(entry);this.slothlet.handlers.permissionManager.addRule({caller:callerPattern,target,effect:"allow",condition},moduleID)}}}return moduleID}async _rollbackFailedVersionedAdd({moduleID,effectivePath,normalizedPath}){let addIndex=-1;for(let i=this.state.operationHistory.length-1;i>=0;i--){const entry=this.state.operationHistory[i];if(entry?.type==="add"&&entry?.apiPath===normalizedPath&&entry?.moduleID===moduleID){addIndex=i;break}}if(addIndex!==-1){this.state.operationHistory.splice(addIndex,1)}this.state.addHistory=this.state.addHistory.filter(entry=>entry?.moduleID!==moduleID);try{await this.removeApiComponent(moduleID||effectivePath,{recordHistory:false})}catch{}}#sweepOrphanedCaches(){const cacheManager=this.slothlet.handlers?.apiCacheManager;if(!cacheManager)return;const root=this.slothlet.boundApi;for(const moduleID of cacheManager.getAllModuleIDs()){const entry=cacheManager.get(moduleID);if(!entry||entry.endpoint==="."||entry.endpoint==="")continue;const parts=String(entry.endpoint).split(".").filter(Boolean);if(this.getValueAtPath(root,parts)===void 0){cacheManager.delete(moduleID)}}}_hasForeignOwnedDescendant(apiPath,moduleIDKey){const ownership=this.slothlet.handlers.ownership;if(!ownership||!apiPath)return false;const prefix=apiPath+".";for(const p of ownership.pathToModule.keys()){if(!p.startsWith(prefix))continue;const owner=ownership.getCurrentOwner(p);if(owner&&owner.moduleID!==moduleIDKey)return true}return false}async removeApiComponent(pathOrModuleId,options={}){const recordHistory=options.recordHistory!==false;if(typeof pathOrModuleId!=="string"||!pathOrModuleId){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"non-empty string",received:typeof pathOrModuleId,validationError:true})}let apiPath=null;let moduleID;if(this.slothlet.handlers.ownership){const candidateModuleID=pathOrModuleId.split(":")[0];const registeredModules=Array.from(this.slothlet.handlers.ownership.moduleToPath.keys());let matchingModule=null;for(let i=registeredModules.length-1;i>=0;i--){const candidate=registeredModules[i];if(candidate===candidateModuleID||candidate.startsWith(`${candidateModuleID}_`)){matchingModule=candidate;break}}if(matchingModule){moduleID=matchingModule}else{const owner=this.slothlet.handlers.ownership.getCurrentOwner(pathOrModuleId);if(owner){apiPath=pathOrModuleId;moduleID=owner.moduleID}else{return false}}}else{const isModuleId=!pathOrModuleId.includes(".");apiPath=isModuleId?null:pathOrModuleId;moduleID=isModuleId?pathOrModuleId.split(":")[0]:null}if(!this.slothlet||!this.slothlet.isLoaded){throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"removeApi",validationError:true})}if(apiPath&&moduleID){const normalizedPath2=this.normalizeApiPath(apiPath).apiPath;const moduleIDKey=String(moduleID);const history=this.slothlet.handlers.ownership?.getPathHistory?.(normalizedPath2)||[];const ownershipResult2=this.slothlet.handlers.ownership?.removePath?.(normalizedPath2,moduleIDKey)||{action:"none",removedModuleId:null,restoreModuleId:null};const pathParts=this.normalizeApiPath(apiPath).parts;if(ownershipResult2.action==="delete"){await this.deletePath(this.slothlet.api,pathParts);await this.deletePath(this.slothlet.boundApi,pathParts);if(this.slothlet.handlers.metadata){const rootSegment=normalizedPath2.split(".")[0];this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}if(this.slothlet.handlers.versionManager){const versionKey=this.slothlet.handlers.versionManager.getVersionKeyForModule(moduleIDKey);if(versionKey){this.slothlet.handlers.versionManager.unregisterVersion(versionKey.logicalPath,versionKey.versionTag)}if(this.slothlet.handlers.versionManager.hasDispatcher(normalizedPath2)){this.slothlet.handlers.versionManager.teardownDispatcher(normalizedPath2)}}this.#sweepOrphanedCaches();this.state.operationHistory.push({type:"remove",apiPath:normalizedPath2});return true}if(ownershipResult2.action==="restore"){const restoredValue=this.slothlet.handlers.ownership?.getCurrentValue?.(normalizedPath2);const restoredModuleId=this.slothlet.handlers.ownership?.getCurrentOwner?.(normalizedPath2)?.moduleID;if(restoredValue!==void 0&&restoredModuleId){await this.setValueAtPath(this.slothlet.api,pathParts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});await this.setValueAtPath(this.slothlet.boundApi,pathParts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});this.state.operationHistory.push({type:"remove",apiPath:normalizedPath2});return true}await this.restoreApiPath(normalizedPath2,ownershipResult2.restoreModuleId);this.state.operationHistory.push({type:"remove",apiPath:normalizedPath2});return true}if(ownershipResult2.action==="none"&&history.length===0){await this.deletePath(this.slothlet.api,pathParts);await this.deletePath(this.slothlet.boundApi,pathParts);return true}return false}if(moduleID){const moduleIDKey=String(moduleID);const mountEndpoint=this.slothlet.handlers.ownership?.getModuleEndpoint?.(moduleIDKey);const isRootMount=!mountEndpoint||mountEndpoint===".";let mountRoot=isRootMount?"":mountEndpoint;const result=this.slothlet.handlers.ownership?.unregister?.(moduleIDKey)||{removed:[],rolledBack:[]};if(this.slothlet.handlers.versionManager){const versionKey=this.slothlet.handlers.versionManager.getVersionKeyForModule(moduleIDKey);if(versionKey){this.slothlet.handlers.versionManager.unregisterVersion(versionKey.logicalPath,versionKey.versionTag)}}const allPaths=[...result.removed,...result.rolledBack.map(r=>r.apiPath)];const uniquePaths=[...new Set(allPaths)];const pathsToDelete=[];const pathsToRollback=[];for(const path2 of uniquePaths){const currentOwner=this.slothlet.handlers.ownership?.getCurrentOwner?.(path2);const hasChildrenWithOtherOwners=isRootMount?this._hasForeignOwnedDescendant(path2,moduleIDKey):uniquePaths.some(p=>{if(p===path2||!p.startsWith(path2+"."))return false;const childOwner=this.slothlet.handlers.ownership?.getCurrentOwner?.(p);return childOwner&&childOwner.moduleID!==moduleIDKey});if(currentOwner&¤tOwner.moduleID!==moduleIDKey){pathsToRollback.push({apiPath:path2,restoredTo:currentOwner.moduleID})}else if(!hasChildrenWithOtherOwners){pathsToDelete.push(path2)}}pathsToDelete.sort((a,b)=>{const depthA=(a.match(/\./g)||[]).length;const depthB=(b.match(/\./g)||[]).length;return depthB-depthA});for(const removedPath of pathsToDelete){const{parts:parts2}=this.normalizeApiPath(removedPath);await this.deletePath(this.slothlet.api,parts2);await this.deletePath(this.slothlet.boundApi,parts2);if(this.slothlet.handlers.metadata){const rootSegment=removedPath.split(".")[0];this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}}if(pathsToDelete.length>0){if(isRootMount){const segs=pathsToDelete.map(p=>p.split("."));const minLen=Math.min(...segs.map(s=>s.length));const common=[];for(let i=0;i<minLen;i++){const seg=segs[0][i];if(segs.every(s=>s[i]===seg))common.push(seg);else break}mountRoot=common.join(".")}const rootParts=this.normalizeApiPath(mountRoot).parts;const mountRootDot=mountRoot===""?null:`${mountRoot}.`;const hasRollbackSurvivor=pathsToRollback.some(r=>r.apiPath===mountRoot||mountRootDot!==null&&r.apiPath.startsWith(mountRootDot));if(!hasRollbackSurvivor&&(!isRootMount||!this._hasForeignOwnedDescendant(mountRoot,moduleIDKey))){await this.deletePath(this.slothlet.api,rootParts);await this.deletePath(this.slothlet.boundApi,rootParts)}}for(const rollback of pathsToRollback){const{parts:parts2}=this.normalizeApiPath(rollback.apiPath);const previousImpl=this.slothlet.handlers.ownership?.getCurrentValue?.(rollback.apiPath);if(previousImpl!==void 0){const existingWrapper=this.getValueAtPath(this.slothlet.api,parts2);const existingWrapperRaw=resolveWrapper(existingWrapper);if(existingWrapperRaw){existingWrapperRaw.___setImpl(previousImpl,rollback.restoredTo)}const existingBoundWrapper=this.getValueAtPath(this.slothlet.boundApi,parts2);const existingBoundWrapperRaw=resolveWrapper(existingBoundWrapper);if(existingBoundWrapperRaw){existingBoundWrapperRaw.___setImpl(previousImpl,rollback.restoredTo)}}}const shadows=this.state.replaceShadows.get(moduleIDKey);if(shadows){for(const shadow of shadows){if(Object.prototype.hasOwnProperty.call(shadow.container,shadow.key))continue;Object.defineProperty(shadow.container,shadow.key,{value:shadow.child,writable:false,enumerable:true,configurable:true})}this.state.replaceShadows.delete(moduleIDKey)}this.state.addHistory=this.state.addHistory.filter(entry=>String(entry.moduleID)!==moduleIDKey);if(this.slothlet.handlers.apiCacheManager){const deleted=this.slothlet.handlers.apiCacheManager.delete(moduleIDKey);if(deleted){this.slothlet.debug("cache",{key:"DEBUG_MODE_CACHE_DELETED_MODULE_REMOVED",moduleID:moduleIDKey})}}this.#sweepOrphanedCaches();if(recordHistory&&pathsToDelete.length>0){this.state.operationHistory.push({type:"remove",apiPath:mountRoot||pathsToDelete[0].split(".")[0]})}return pathsToDelete.length>0||pathsToRollback.length>0}if(!apiPath){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_REQUIRED"),index:void 0,segment:void 0,validationError:true})}const{apiPath:normalizedPath,parts}=this.normalizeApiPath(apiPath);const ownershipResult=this.slothlet.handlers.ownership?.removePath?.(normalizedPath,null)||{action:"none",removedModuleId:null,restoreModuleId:null};const pathExists=this.getValueAtPath(this.slothlet.api,parts)!==void 0;if(ownershipResult.action==="none"){if(pathExists){await this.deletePath(this.slothlet.api,parts);await this.deletePath(this.slothlet.boundApi,parts);if(this.slothlet.handlers.metadata){this.slothlet.handlers.metadata.removeUserMetadataByApiPath(normalizedPath)}if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}return false}if(ownershipResult.action==="delete"){await this.deletePath(this.slothlet.api,parts);await this.deletePath(this.slothlet.boundApi,parts);if(this.slothlet.handlers.metadata){this.slothlet.handlers.metadata.removeUserMetadataByApiPath(normalizedPath)}if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}if(ownershipResult.action==="restore"){const restoredValue=this.slothlet.handlers.ownership?.getCurrentValue?.(normalizedPath);const restoredModuleId=this.slothlet.handlers.ownership?.getCurrentOwner?.(normalizedPath)?.moduleID;if(restoredValue!==void 0&&restoredModuleId){await this.setValueAtPath(this.slothlet.api,parts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});await this.setValueAtPath(this.slothlet.boundApi,parts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}await this.restoreApiPath(normalizedPath,ownershipResult.restoreModuleId);if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}return false}async reloadApiComponent(params){const{apiPath,moduleID,options}=params||{};if(!this.slothlet||!this.slothlet.isLoaded){throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"reloadApi",validationError:true})}if(moduleID){await this._reloadByModuleID(moduleID);return}if(apiPath){await this._reloadByApiPath(apiPath,options);return}throw new this.SlothletError("INVALID_ARGUMENT",{argument:"params",expected:"{ moduleID } or { apiPath }",received:params,validationError:true})}async _reloadByModuleID(moduleID,{forceReplace=true}={}){const cacheManager=this.slothlet.handlers.apiCacheManager;if(!cacheManager){throw new this.SlothletError("CACHE_MANAGER_NOT_AVAILABLE",{operation:"reload",validationError:true})}if(!cacheManager.has(moduleID)){throw new this.SlothletError("CACHE_NOT_FOUND",{moduleID,operation:"reload",validationError:true})}const oldEntry=cacheManager.get(moduleID);this.slothlet.debug("reload",{key:"DEBUG_MODE_RELOADING_MODULE_BY_ID",moduleID,endpoint:oldEntry.endpoint,folderPath:oldEntry.folderPath});const freshApi=await cacheManager.rebuildCache(moduleID);cacheManager.set(moduleID,{...oldEntry,api:freshApi,timestamp:Date.now()});this.slothlet.debug("reload",{key:"DEBUG_MODE_FRESH_API_KEYS_BEFORE_RESTORE",moduleID,endpoint:oldEntry.endpoint,freshApiKeys:Object.keys(freshApi||{})});await this._restoreApiTree(freshApi,oldEntry.endpoint,moduleID,oldEntry.collisionMode,forceReplace);this.slothlet.debug("reload",{key:"DEBUG_MODE_FRESH_API_KEYS_AFTER_RESTORE",moduleID,endpoint:oldEntry.endpoint,freshApiKeys:Object.keys(freshApi||{})});this.slothlet.debug("reload",{key:"DEBUG_MODE_MODULE_RELOAD_COMPLETE",moduleID});if(this.slothlet.handlers.versionManager){this.slothlet.handlers.versionManager.onVersionedModuleReload(moduleID)}}async _reloadByApiPath(apiPath,options={}){this.slothlet.debug("reload",{key:"DEBUG_MODE_RELOADING_BY_API_PATH",apiPath});const moduleIDsToReload=this._findAffectedCaches(apiPath);if(moduleIDsToReload.length===0){this.slothlet.debug("reload",{key:"DEBUG_MODE_NO_CACHES_ATTEMPTING_RESTORE",apiPath});if(apiPath!=="."&&apiPath!==""){await this.restoreApiPath(apiPath,"base")}return}const cacheManager=this.slothlet.handlers.apiCacheManager;moduleIDsToReload.sort((a,b)=>{const entryA=cacheManager.get(a);const entryB=cacheManager.get(b);if(entryA?.endpoint==="."&&entryB?.endpoint!==".")return-1;if(entryB?.endpoint==="."&&entryA?.endpoint!==".")return 1;const indexA=this.state.addHistory.findIndex(h=>h.moduleID===a);const indexB=this.state.addHistory.findIndex(h=>h.moduleID===b);return indexA-indexB});const endpointOrder=new Map;for(const moduleID of moduleIDsToReload){const entry=cacheManager.get(moduleID);const ep=entry?.endpoint??".";if(!endpointOrder.has(ep))endpointOrder.set(ep,[]);endpointOrder.get(ep).push(moduleID)}for(const[,moduleIDs]of endpointOrder){for(let i=0;i<moduleIDs.length;i++){await this._reloadByModuleID(moduleIDs[i],{forceReplace:i===0})}}const reloadMetadata=options?.metadata;if(reloadMetadata&&typeof reloadMetadata==="object"&&Object.keys(reloadMetadata).length>0){if(this.slothlet.handlers.metadata){const targetPath=apiPath==="."?null:apiPath.split(".")[0];if(targetPath){this.slothlet.handlers.metadata.registerUserMetadata(targetPath,reloadMetadata)}}}this.slothlet.debug("reload",{key:"DEBUG_MODE_API_PATH_RELOAD_COMPLETE",apiPath,reloadedModules:moduleIDsToReload.length,loadOrder:moduleIDsToReload})}_findAffectedCaches(apiPath){const cacheManager=this.slothlet.handlers.apiCacheManager;if(!cacheManager)return[];const allModuleIDs=cacheManager.getAllModuleIDs();if(apiPath==="."||apiPath===""||apiPath==null){const baseModules=[];for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(entry&&entry.endpoint==="."){baseModules.push(moduleID)}}return baseModules}const exactMatches=[];for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(entry&&entry.endpoint===apiPath){exactMatches.push(moduleID)}}if(exactMatches.length>0)return exactMatches;const children=[];const pathPrefix=apiPath+".";for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(entry?.endpoint?.startsWith(pathPrefix)){children.push(moduleID)}}if(children.length>0)return children;const ownership=this.slothlet.handlers.ownership;const history=ownership?.getPathHistory?.(apiPath);if(history&&history.length>0){const owned=[];for(const{moduleID}of history){if(cacheManager.has(moduleID)){owned.push(moduleID)}}if(owned.length>0)return owned}let bestMatch=null;let bestLength=-1;for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(!entry?.endpoint)continue;const ep=entry.endpoint;if(ep==="."||apiPath.startsWith(ep+".")){if(ep.length>bestLength){bestLength=ep.length;bestMatch=moduleID}}}if(bestMatch)return[bestMatch];return[]}_collectCustomProperties(existingProxy,freshApi){const customProps={};if(!existingProxy||typeof existingProxy!=="object"&&typeof existingProxy!=="function"){return customProps}const wrapper=resolveWrapper(existingProxy);if(!wrapper){return customProps}const freshKeys=new Set(freshApi?Object.keys(freshApi):[]);const ownKeys=Object.keys(wrapper).filter(k=>!ComponentBase.INTERNAL_KEYS.has(k));for(const key of ownKeys){try{const val=wrapper[key];if(val&&(typeof val==="object"||typeof val==="function")&&resolveWrapper(val)){continue}if(!freshKeys.has(key)){customProps[key]=val}else{customProps[key]=val}}catch{}}return customProps}_restoreCustomProperties(proxy,customProps){if(!proxy||!customProps||typeof customProps!=="object"){return}for(const[key,value]of Object.entries(customProps)){try{proxy[key]=value}catch{}}}async _restoreApiTree(freshApi,endpoint,moduleID,collisionMode,forceReplace=true){if(!freshApi||typeof freshApi!=="object"&&typeof freshApi!=="function"){return}const parts=endpoint==="."?[]:endpoint.split(".");if(parts.length===0){for(const key of Object.keys(freshApi)){if(typeof key==="string"&&(key.startsWith("_")||key.startsWith("__")))continue;if(key==="slothlet"||key==="shutdown"||key==="destroy")continue;const existingAtKey=this.slothlet.api[key];const freshValue=freshApi[key];if(existingAtKey&&resolveWrapper(existingAtKey)!==null){const customProps=this._collectCustomProperties(existingAtKey,freshValue);const freshWrapper=resolveWrapper(freshValue);const isLazyFresh=freshWrapper&&freshWrapper.____slothletInternal.mode==="lazy"&&!freshWrapper.____slothletInternal.state.materialized&&typeof freshWrapper.____slothletInternal.materializeFunc==="function";this.slothlet.debug("reload",{key:"DEBUG_MODE_RESTORE_ROOT_KEY_INSPECT",rootKey:key,hasFreshWrapper:!!freshWrapper,freshMode:freshWrapper?.____slothletInternal.mode,freshMaterialized:freshWrapper?.____slothletInternal.state?.materialized,hasMaterializeFunc:typeof freshWrapper?.____slothletInternal.materializeFunc==="function",isLazyFresh,existingMaterialized:resolveWrapper(existingAtKey)?.____slothletInternal?.state?.materialized});if(isLazyFresh){resolveWrapper(existingAtKey).___resetLazy(freshWrapper.____slothletInternal.materializeFunc);this._restoreCustomProperties(existingAtKey,customProps);this.slothlet.debug("reload",{key:"DEBUG_MODE_ROOT_KEY_RESET_LAZY",rootKey:key,restoredCustomProps:Object.keys(customProps)})}else{let implForReload;if(freshValue&&resolveWrapper(freshValue)!==null){implForReload=freshWrapper?UnifiedWrapper._extractFullImpl(freshWrapper):freshValue}else{implForReload=freshValue}resolveWrapper(existingAtKey).___setImpl(implForReload,moduleID);this._restoreCustomProperties(existingAtKey,customProps);this.slothlet.debug("reload",{key:"DEBUG_MODE_ROOT_KEY_UPDATED_SETIMPL",rootKey:key,restoredCustomProps:Object.keys(customProps)})}}else if(existingAtKey===void 0){const cacheManager=this.slothlet.handlers.apiCacheManager;const cacheEntry=cacheManager.get(moduleID);const resolvedFolderPath=cacheEntry?.folderPath||"";await this.setValueAtPath(this.slothlet.api,[key],freshValue,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(this.slothlet.boundApi){await this.setValueAtPath(this.slothlet.boundApi,[key],freshValue,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath})}}}}else{const existing=this.getValueAtPath(this.slothlet.api,parts);this.slothlet.debug("reload",{key:"DEBUG_MODE_RESTORE_NESTED_PATH",endpoint,moduleID,partsPath:parts.join("."),existingFound:!!existing,hasSetImpl:existing?resolveWrapper(existing)!==null:false,freshApiKeys:Object.keys(freshApi)});if(existing&&resolveWrapper(existing)!==null){const customProps=this._collectCustomProperties(existing,freshApi);const wrapper=resolveWrapper(existing);const originalCollisionMode=wrapper?wrapper.____slothletInternal.state.collisionMode:null;if(forceReplace&&wrapper){wrapper.____slothletInternal.state.collisionMode="replace";this.slothlet.debug("reload",{key:"DEBUG_MODE_RESTORE_FORCING_REPLACE",endpoint,originalCollisionMode,wrapperApiPath:wrapper.____slothletInternal.apiPath})}let implForReload;if(resolveWrapper(freshApi)!==null){const freshWrapper=resolveWrapper(freshApi);implForReload=freshWrapper?UnifiedWrapper._extractFullImpl(freshWrapper):freshApi}else{implForReload=freshApi}if(parts.length>0&&implForReload&&typeof implForReload==="object"){const lastEndpointPart=parts[parts.length-1];if(lastEndpointPart&&Object.prototype.hasOwnProperty.call(implForReload,lastEndpointPart)){const dupValue=implForReload[lastEndpointPart];const dupWrapperForDedup=resolveWrapper(dupValue);if(dupWrapperForDedup){const hoisted={};for(const k of Object.keys(implForReload)){if(k!==lastEndpointPart)hoisted[k]=implForReload[k]}for(const k of Object.keys(dupWrapperForDedup).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__"))){hoisted[k]=dupWrapperForDedup[k]}implForReload=hoisted}}}if(implForReload&&typeof implForReload==="object"){for(const key of Object.keys(implForReload)){const val=implForReload[key];if(resolveWrapper(val)!==null){const childWrapper=resolveWrapper(val);if(childWrapper.____slothletInternal.state.materialized){implForReload[key]=UnifiedWrapper._extractFullImpl(childWrapper)}}}}resolveWrapper(existing).___setImpl(implForReload,moduleID);if(wrapper&&originalCollisionMode!==null){wrapper.____slothletInternal.state.collisionMode=originalCollisionMode}this._restoreCustomProperties(existing,customProps);this.slothlet.debug("reload",{key:"DEBUG_MODE_UPDATED_WRAPPER_IMPL",endpoint,moduleID,forcedReplaceMode:true,restoredCustomProps:Object.keys(customProps)})}else{const cacheManager=this.slothlet.handlers.apiCacheManager;const cacheEntry=cacheManager.get(moduleID);const resolvedFolderPath=cacheEntry?.folderPath||"";let implForContainer=freshApi;if(typeof freshApi==="function"){implForContainer={};for(const key of Object.keys(freshApi)){implForContainer[key]=freshApi[key]}}const containerWrapper=new UnifiedWrapper(this.slothlet,{apiPath:endpoint,mode:this.____config.mode,moduleID,filePath:resolvedFolderPath,sourceFolder:resolvedFolderPath});containerWrapper.___setImpl(implForContainer,moduleID);const apiToSet=containerWrapper.createProxy();await this.setValueAtPath(this.slothlet.api,parts,apiToSet,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(this.slothlet.boundApi){await this.setValueAtPath(this.slothlet.boundApi,parts,apiToSet,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath})}this.slothlet.debug("reload",{key:"DEBUG_MODE_CREATED_NEW_WRAPPER_UNEXPECTED",endpoint,moduleID})}}}}export{ApiManager};
|
|
17
|
+
import{translate}from"@cldmv/slothlet/i18n";import{ComponentBase}from"#factories/component-base";import{UnifiedWrapper,resolveWrapper}from"#handlers/unified-wrapper";import{isFrameworkInternal}from"#handlers/framework-internals";import{MODULE_ID_SEPARATOR}from"#handlers/metadata";import{fsp,path}from"@cldmv/slothlet/helpers/platform";const UNSAFE_PATH_SEGMENTS=new Set(["__proto__","constructor","prototype"]);class ApiManager extends ComponentBase{static slothletProperty="apiManager";constructor(slothlet){super(slothlet);this.state={addHistory:[],initialConfig:slothlet?.config||null,operationHistory:[],replaceShadows:new Map}}normalizeApiPath(apiPath){if(apiPath===""||apiPath===null||apiPath===void 0){return{apiPath:"",parts:[]}}if(Array.isArray(apiPath)){if(apiPath.length===0){return{apiPath:"",parts:[]}}for(let i=0;i<apiPath.length;i++){if(typeof apiPath[i]!=="string"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,segment:apiPath[i],index:i,reason:translate("API_PATH_REASON_ARRAY_ELEMENTS"),validationError:true})}if(apiPath[i].trim()===""){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,segment:apiPath[i],index:i,reason:translate("API_PATH_REASON_ARRAY_EMPTY_SEGMENTS"),validationError:true})}}if(apiPath[0]==="slothlet"||apiPath.length===1&&(apiPath[0]==="shutdown"||apiPath[0]==="destroy")){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_RESERVED_NAME"),index:void 0,segment:void 0,validationError:true})}const unsafeArrayIndex=apiPath.findIndex(segment=>UNSAFE_PATH_SEGMENTS.has(segment));if(unsafeArrayIndex!==-1){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_UNSAFE_SEGMENT"),index:unsafeArrayIndex,segment:apiPath[unsafeArrayIndex],validationError:true})}return{apiPath:apiPath.join("."),parts:apiPath}}if(typeof apiPath!=="string"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_INVALID_TYPE"),index:void 0,segment:void 0,validationError:true})}const normalized=apiPath.trim();if(normalized===""){return{apiPath:"",parts:[]}}const parts=normalized.split(".");if(parts.length===0||parts.some(part=>part.trim()==="")){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:normalized,reason:translate("API_PATH_REASON_EMPTY_SEGMENTS"),index:void 0,segment:void 0,validationError:true})}if(parts[0]==="slothlet"||normalized==="shutdown"||normalized==="destroy"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:normalized,reason:translate("API_PATH_REASON_RESERVED_NAME"),index:void 0,segment:void 0,validationError:true})}const unsafeIndex=parts.findIndex(segment=>UNSAFE_PATH_SEGMENTS.has(segment));if(unsafeIndex!==-1){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:normalized,reason:translate("API_PATH_REASON_UNSAFE_SEGMENT"),index:unsafeIndex,segment:parts[unsafeIndex],validationError:true})}return{apiPath:normalized,parts}}async resolvePath(inputPath){if(!inputPath||typeof inputPath!=="string"){throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:inputPath,validationError:true})}const resolvedPath=this.slothlet.helpers.resolver.resolvePathFromCaller(inputPath);if(this.slothlet.envTarget==="browser"){const lastSegment=resolvedPath.split("/").pop();const isFile=lastSegment.includes(".");return{resolvedPath,isDirectory:!isFile,isFile}}try{const stats=await fsp.stat(resolvedPath);return{resolvedPath,isDirectory:stats.isDirectory(),isFile:stats.isFile()}}catch(error){if(error instanceof this.SlothletError){throw error}throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:resolvedPath,validationError:true})}}async resolveFolderPath(folderPath){if(!folderPath||typeof folderPath!=="string"){throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:folderPath,validationError:true})}const resolvedPath=this.slothlet.helpers.resolver.resolvePathFromCaller(folderPath);try{const stats=await fsp.stat(resolvedPath);if(!stats.isDirectory()){throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:resolvedPath,validationError:true})}}catch(error){if(error instanceof this.SlothletError){throw error}throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:resolvedPath,validationError:true})}return resolvedPath}buildDefaultModuleId(apiPath,____resolvedFolderPath){const randomSuffix=Math.random().toString(36).substring(2,8);const prefix=apiPath||"auto";return`${prefix}_${randomSuffix}`}getValueAtPath(root,parts){let current=root;for(const part of parts){if(!current||typeof current!=="object"&&typeof current!=="function"){return void 0}current=current[part]}return current}ensureParentPath(root,parts,options={}){const{moduleID,sourceFolder}=options;let current=root;for(let i=0;i<parts.length-1;i+=1){const part=parts[i];const next=current[part];if(next===void 0){const containerPath=parts.slice(0,i+1).join(".");const containerWrapper=new UnifiedWrapper(this.slothlet,{mode:this.____config.mode,apiPath:containerPath,moduleID,sourceFolder});containerWrapper.___setImpl({},moduleID);current[part]=containerWrapper.createProxy();current=current[part];continue}if(next&&(typeof next==="object"||typeof next==="function")){current=next;continue}throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:parts.slice(0,i+1).join("."),reason:translate("API_PATH_REASON_NOT_TRAVERSABLE"),index:void 0,segment:void 0,validationError:true})}return current}setOwnedProperty(apiPath,value,callerWrapper){const coercedPath=String(apiPath);for(const segment of coercedPath.split(".")){if(UNSAFE_PATH_SEGMENTS.has(segment)){throw new this.SlothletError("LOOSE_SET_RESERVED_KEY",{apiPath:coercedPath,segment,validationError:true})}}const{parts}=this.normalizeApiPath(apiPath);if(parts.length===0){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:String(apiPath??""),reason:translate("API_PATH_REASON_EMPTY_SEGMENTS"),index:void 0,segment:void 0,validationError:true})}const callerModuleID=callerWrapper?.____slothletInternal?.moduleID??null;const callerWrapperPath=callerWrapper?.____slothletInternal?.apiPath??null;let ownedRoot=null;if(callerModuleID&&this.slothlet.handlers?.ownership?.getModuleEndpoint){const endpoint=this.slothlet.handlers.ownership.getModuleEndpoint(callerModuleID);if(endpoint!=null){ownedRoot=endpoint}}if(ownedRoot===null)ownedRoot=".";if(ownedRoot&&ownedRoot!=="."&&ownedRoot!==""){const ownedParts=String(ownedRoot).split(".").filter(Boolean);const isUnderOwn=ownedParts.length<=parts.length&&ownedParts.every((seg,i)=>parts[i]===seg);if(!isUnderOwn){const callerApiPath=callerWrapperPath??ownedRoot;throw new this.SlothletError("LOOSE_SET_NOT_OWNED",{apiPath:parts.join("."),callerApiPath,validationError:true})}}const callerSourceFolder=callerWrapper?.____slothletInternal?.sourceFolder??null;const root=this.slothlet.boundApi;const parent=this.ensureParentPath(root,parts,{moduleID:callerModuleID,sourceFolder:callerSourceFolder});const finalKey=parts[parts.length-1];const isWrappable=typeof value==="function"||value!==null&&typeof value==="object";if(isWrappable){const wrapper=new UnifiedWrapper(this.slothlet,{mode:this.____config.mode,apiPath:parts.join("."),moduleID:callerModuleID,isCallable:typeof value==="function",sourceFolder:callerSourceFolder});wrapper.___setImpl(value,callerModuleID);parent[finalKey]=wrapper.createProxy()}else{parent[finalKey]=value}}isWrapperProxy(value){return!!(value&&(typeof value==="object"||typeof value==="function")&&resolveWrapper(value)!==null)}async syncWrapper(existingProxy,nextProxy,config,collisionMode="replace",moduleID=null){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_ENTRY_EXISTING",apiPath:resolveWrapper(existingProxy)?.apiPath});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_ENTRY_NEXT",apiPath:resolveWrapper(nextProxy)?.apiPath})}if(!this.isWrapperProxy(existingProxy)||!this.isWrapperProxy(nextProxy)){return false}const existingWrapper=resolveWrapper(existingProxy)??existingProxy;const nextWrapper=resolveWrapper(nextProxy)??nextProxy;if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_EXISTING",apiPath:existingWrapper.apiPath});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_NEXT",apiPath:nextWrapper.apiPath})}if(nextWrapper.____slothletInternal.materializeFunc&&collisionMode!=="merge"){existingWrapper.____slothletInternal.materializeFunc=nextWrapper.____slothletInternal.materializeFunc}const existingChildKeys=Object.keys(existingWrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));const nextChildKeys=Object.keys(nextWrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_BEFORE_MERGE",existingCacheSize:existingChildKeys.length,nextCacheSize:nextChildKeys.length});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_NEXT_IMPL_KEYS",implKeys:Object.keys(nextWrapper.____slothletInternal.impl||{})});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_NEXT_CHILDCACHE_KEYS",childCacheKeys:nextChildKeys})}if(collisionMode==="replace"){this._recordReplaceShadows(existingWrapper,existingChildKeys,nextChildKeys,moduleID);if(existingWrapper.___setImpl&&nextWrapper.____slothletInternal.impl!==void 0){existingWrapper.___setImpl(nextWrapper.____slothletInternal.impl,moduleID)}else if(nextWrapper.____slothletInternal.impl===void 0){existingWrapper.____slothletInternal.impl=null}else{if(nextWrapper.____slothletInternal.impl!==void 0){existingWrapper.____slothletInternal.impl=nextWrapper.____slothletInternal.impl;if(typeof nextWrapper.____slothletInternal.impl==="function"||nextWrapper.____slothletInternal.impl&&typeof nextWrapper.____slothletInternal.impl.default==="function"){existingWrapper.isCallable=true}}}for(const key of existingChildKeys){delete existingWrapper[key]}existingWrapper.___adoptImplChildren();for(const key of nextChildKeys){const childValue=nextWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}else if(collisionMode==="merge"){for(const key of nextChildKeys){const isInternal=typeof key==="string"&&(key.startsWith("_")||key.startsWith("__"));if(!isInternal&&!Object.prototype.hasOwnProperty.call(existingWrapper,key)){const childValue=nextWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}else if(!isInternal){const existingChild=existingWrapper[key];const nextChild=nextWrapper[key];if(this.isWrapperProxy(existingChild)&&this.isWrapperProxy(nextChild)){const syncWrapper_nextChildWrapper=resolveWrapper(nextChild)??nextChild;const syncWrapper_hasGrandChildren=Object.keys(syncWrapper_nextChildWrapper).some(k=>!k.startsWith("_")&&!k.startsWith("__"));if(syncWrapper_hasGrandChildren){await this.syncWrapper(existingChild,nextChild,config,collisionMode,moduleID)}}}}}else{for(const key of nextChildKeys){const childValue=nextWrapper[key];if(Object.prototype.hasOwnProperty.call(existingWrapper,key)){const existingChild=existingWrapper[key];if(this.isWrapperProxy(existingChild)&&this.isWrapperProxy(childValue)){const nextChildWrapper=resolveWrapper(childValue)??childValue;const hasGrandChildren=Object.keys(nextChildWrapper).some(k=>!k.startsWith("_")&&!k.startsWith("__"));if(hasGrandChildren){await this.syncWrapper(existingChild,childValue,config,collisionMode,moduleID)}else{delete existingWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}else{delete existingWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}else{Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}}if(existingWrapper.____slothletInternal.state){const isActuallyMaterialized=existingWrapper.____slothletInternal.impl&&typeof existingWrapper.____slothletInternal.impl!=="function";existingWrapper.____slothletInternal.state.materialized=isActuallyMaterialized;existingWrapper.____slothletInternal.state.inFlight=false}return true}_recordReplaceShadows(existingWrapper,existingChildKeys,nextChildKeys,moduleID){const ownership=this.slothlet.handlers.ownership;if(!ownership||!moduleID)return;const moduleIDKey=String(moduleID);const containerPath=existingWrapper.____slothletInternal.apiPath;const nextSet=new Set(nextChildKeys);for(const key of existingChildKeys){if(nextSet.has(key))continue;const owner=ownership.getCurrentOwner(`${containerPath}.${key}`);if(!owner)continue;if(String(owner.moduleID)===moduleIDKey)continue;const child=existingWrapper[key];if(!this.isWrapperProxy(child))continue;let list=this.state.replaceShadows.get(moduleIDKey);if(!list){list=[];this.state.replaceShadows.set(moduleIDKey,list)}list.push({container:existingWrapper,key,child,ownerModuleID:String(owner.moduleID)})}}async mutateApiValue(existingValue,nextValue,options,config){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_CALLED",existingType:typeof existingValue,nextType:typeof nextValue});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_WRAPPER_STATUS",existingIsWrapper:this.isWrapperProxy(existingValue),nextIsWrapper:this.isWrapperProxy(nextValue)});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_NEXT_VALUE",nextValue});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_NEXT_VALUE_KEYS",nextValueKeys:nextValue?Object.keys(nextValue):[]})}if(existingValue===nextValue){return}if(this.isWrapperProxy(existingValue)&&this.isWrapperProxy(nextValue)){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_SYNC_WRAPPERS"})}await this.syncWrapper(existingValue,nextValue,config,options.collisionMode,options.moduleID);return}if(this.isWrapperProxy(existingValue)&&!this.isWrapperProxy(nextValue)){const nextIsObjectLike=nextValue&&(typeof nextValue==="object"||typeof nextValue==="function");const nextHasKeys=nextIsObjectLike&&Object.keys(nextValue).length>0;if(nextHasKeys){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_MERGE_INTO_WRAPPER"});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_MERGE_KEYS",keys:Object.keys(nextValue)})}await this.slothlet.builders.apiAssignment.mergeApiObjects(existingValue,nextValue,{removeMissing:options.removeMissing,mutateExisting:true,allowOverwrite:true,syncWrapper:this.syncWrapper.bind(this),collisionMode:options.collisionMode,moduleID:options.moduleID});return}const existingValueRaw=resolveWrapper(existingValue);if(existingValueRaw!==null){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_SETIMPL_FALLBACK"})}existingValueRaw.___setImpl(resolveWrapper(nextValue)?.__impl??nextValue);return}}if(existingValue&&typeof existingValue==="object"&&nextValue&&typeof nextValue==="object"){await this.slothlet.builders.apiAssignment.mergeApiObjects(existingValue,nextValue,{removeMissing:options.removeMissing,mutateExisting:true,allowOverwrite:true,syncWrapper:this.syncWrapper.bind(this),collisionMode:options.collisionMode,moduleID:options.moduleID});return existingValue}return nextValue}async setValueAtPath(root,parts,value,options){const parent=this.ensureParentPath(root,parts,{moduleID:options.moduleID,sourceFolder:options.sourceFolder});const finalKey=parts[parts.length-1];const parentWrapper=resolveWrapper(parent);if(parentWrapper&&parentWrapper.____slothletInternal.mode==="lazy"&&!parentWrapper.____slothletInternal.state.materialized){await parentWrapper._materialize()}const existing=parent?parent[finalKey]:void 0;const collisionMode=options.collisionMode||"merge";const moduleID=options.moduleID;this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH",finalKey,existingType:typeof existing,valueType:typeof value,collisionMode,options});if(existing!==void 0){if(collisionMode==="error"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:parts.join("."),reason:translate("API_PATH_REASON_COLLISION_ERROR"),index:void 0,segment:void 0,validationError:true})}if(collisionMode==="skip"){this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_SKIP_COLLISION",path:parts.join("."),mode:"skip"});return false}if(collisionMode==="warn"){if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARNING_HOT_RELOAD_PATH_COLLISION",{apiPath:parts.join(".")})}return false}if(collisionMode==="replace"){const existingIsObject=typeof existing==="object"||typeof existing==="function";const valueIsObject=typeof value==="object"||typeof value==="function";if(existingIsObject&&valueIsObject){this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_REPLACE_MERGE",path:parts.join("."),mode:"replace"});await this.mutateApiValue(existing,value,{removeMissing:false,allowOverwrite:true,collisionMode:"replace",moduleID},this.____config);return true}else{parent[finalKey]=value;return true}}if(collisionMode==="merge"||collisionMode==="merge-replace"){const existingIsObject=typeof existing==="object"||typeof existing==="function";const valueIsObject=typeof value==="object"||typeof value==="function";if(existingIsObject&&valueIsObject){this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_MERGE_PROPS",mode:collisionMode});await this.mutateApiValue(existing,value,{removeMissing:false,allowOverwrite:true,collisionMode},this.____config);return true}else{const apiPath=parts.join(".");if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARNING_HOT_RELOAD_MERGE_PRIMITIVES",{apiPath})}await this.emitImplDiagnostic("error",{apiPath,code:"WARNING_HOT_RELOAD_MERGE_PRIMITIVES",context:{apiPath},source:"addApi",moduleID,error:new this.SlothletError("WARNING_HOT_RELOAD_MERGE_PRIMITIVES",{apiPath})});return false}}}this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_ASSIGN",finalKey});parent[finalKey]=value;return true}async deletePath(root,parts){let current=root;const stack=[];for(const part of parts.slice(0,-1)){if(!current||typeof current!=="object"&&typeof current!=="function"){return false}stack.push({parent:current,key:part});current=current[part]}const finalKey=parts[parts.length-1];if(!current||typeof current!=="object"&&typeof current!=="function"){return false}if(!Object.prototype.hasOwnProperty.call(current,finalKey)){return false}const removedImpl=current[finalKey];const apiPath=parts.join(".");if(removedImpl&&this.slothlet.handlers?.lifecycle){const metadata=this.slothlet.handlers.metadata?.getMetadata?.(removedImpl);await this.slothlet.handlers.lifecycle.emit("impl:removed",{apiPath,impl:removedImpl,source:"removal",moduleID:metadata?.moduleID,filePath:metadata?.filePath,sourceFolder:metadata?.sourceFolder})}if(resolveWrapper(current)){const wrapper=resolveWrapper(current);const isInternal=typeof finalKey==="string"&&(finalKey.startsWith("_")||finalKey.startsWith("__"));if(!isInternal&&finalKey in wrapper){delete wrapper[finalKey]}if(wrapper.____slothletInternal.impl&&typeof wrapper.____slothletInternal.impl==="object"){delete wrapper.____slothletInternal.impl[finalKey]}}delete current[finalKey];if(removedImpl&&(typeof removedImpl==="object"||typeof removedImpl==="function")){if(resolveWrapper(removedImpl)){const wrapper=resolveWrapper(removedImpl);if(wrapper.____slothletInternal.impl!==void 0){wrapper.____slothletInternal.impl=null}const childKeys=Object.keys(wrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));for(const key of childKeys){delete wrapper[key]}if(wrapper.____slothletInternal.state){wrapper.____slothletInternal.state.materialized=false;wrapper.____slothletInternal.state.inFlight=false}}}if(this.slothlet.handlers?.metadata){const rootSegment=apiPath.split(".")[0];this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}for(let i=stack.length-1;i>=0;i-=1){const{parent,key}=stack[i];const value=parent[key];if(value&&(typeof value==="object"||typeof value==="function")&&Object.keys(value).length===0){delete parent[key]}}return true}async restoreApiPath(apiPath,moduleID){const normalizedModuleId=moduleID||null;const historyEntry=this.state.addHistory.slice().reverse().find(entry=>entry.apiPath===apiPath&&(normalizedModuleId?entry.moduleID===normalizedModuleId:true));if(historyEntry){await this.addApiComponent({apiPath:historyEntry.apiPath,folderPath:historyEntry.folderPath,options:{...historyEntry.options,metadata:historyEntry.metadata,mutateExisting:true,forceOverwrite:true,collisionMode:"replace",recordHistory:false}});return}if(normalizedModuleId==="base"||normalizedModuleId==="core"){const baseApi=await this.slothlet.builders.builder.buildAPI({dir:this.____config.dir,mode:this.____config.mode,moduleID:"base"});const{parts}=this.normalizeApiPath(apiPath);let baseValue=this.getValueAtPath(baseApi,parts);if(baseValue===void 0){await this.deletePath(this.slothlet.api,parts);await this.deletePath(this.slothlet.boundApi,parts);return}const baseValueRaw=resolveWrapper(baseValue);if(baseValue&&baseValueRaw!==null){baseValue=baseValueRaw.__impl}await this.setValueAtPath(this.slothlet.api,parts,baseValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:normalizedModuleId});await this.setValueAtPath(this.slothlet.boundApi,parts,baseValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:normalizedModuleId})}}#normalizePermissionShorthandEntry(entry){const getValueType=value=>{if(value===null)return"null";if(Array.isArray(value))return"array";return typeof value};if(typeof entry==="string"){return{target:entry,condition:void 0}}if(!entry||typeof entry!=="object"||Array.isArray(entry)){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_NOT_OBJECT"),received:getValueType(entry),validationError:true})}if(typeof entry.target!=="string"||!entry.target){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_TARGET_REQUIRED"),received:typeof entry.target,validationError:true})}return{target:entry.target,condition:entry.condition}}async addApiComponent(params){const{apiPath,folderPath,options={},versionConfig=null}=params||{};if(Array.isArray(folderPath)){const moduleIDs=[];for(const singlePath of folderPath){const moduleID2=await this.addApiComponent({apiPath,folderPath:singlePath,options,versionConfig});moduleIDs.push(moduleID2)}return moduleIDs}let effectiveOptions=options;if(folderPath&&typeof folderPath==="object"&&!Array.isArray(folderPath)&&Object.prototype.hasOwnProperty.call(folderPath,"exports")){const siblingOptions={...folderPath};delete siblingOptions.exports;if(Object.keys(siblingOptions).length>0)effectiveOptions={...siblingOptions,...options}}const{metadata={},...restOptions}=effectiveOptions;if(!this.slothlet||!this.slothlet.isLoaded){throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"addApi",validationError:true})}if(restOptions.moduleID&&String(restOptions.moduleID).includes(MODULE_ID_SEPARATOR)){throw new this.SlothletError("MODULE_ID_RESERVED_SEPARATOR",{moduleID:String(restOptions.moduleID),separator:MODULE_ID_SEPARATOR,validationError:true})}const{apiPath:normalizedPath,parts}=this.normalizeApiPath(apiPath);if(restOptions.moduleID&&normalizedPath.includes(MODULE_ID_SEPARATOR)){const segIndex=parts.findIndex(p=>p.includes(MODULE_ID_SEPARATOR));throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:normalizedPath,segment:parts[segIndex],index:segIndex,reason:translate("API_PATH_REASON_RESERVED_SEPARATOR"),validationError:true})}let effectivePath=normalizedPath;let effectiveParts=parts;if(versionConfig?.version!==void 0&&versionConfig?.version!==null){if(normalizedPath===""){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_VERSIONED_ROOT"),index:void 0,segment:void 0,validationError:true})}if(typeof versionConfig.version!=="string"||!String(versionConfig.version).trim()){throw new this.SlothletError("INVALID_CONFIG_VERSION_TAG",{received:versionConfig.version,validationError:true})}const versionTag=String(versionConfig.version).trim();effectiveParts=[versionTag,...parts];effectivePath=effectiveParts.join(".")}const isSynthetic=typeof folderPath==="function"||folderPath!==null&&typeof folderPath==="object"&&!Array.isArray(folderPath);let syntheticExports=null;let resolvedPath,isDirectory,isFile;if(isSynthetic){const isPlainObject=value=>{if(value===null||typeof value!=="object")return false;const proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null};const describeNonPlain=value=>value===null?"null":Array.isArray(value)?"array":typeof value==="object"?`${value.constructor?.name??"non-plain object"} instance`:typeof value;if(typeof folderPath==="function"){syntheticExports={default:folderPath}}else if(Object.prototype.hasOwnProperty.call(folderPath,"exports")){const inner=folderPath.exports;if(!isPlainObject(inner)){throw new this.SlothletError("INVALID_CONFIG_SYNTHETIC_EXPORTS_SHAPE",{received:describeNonPlain(inner),validationError:true})}syntheticExports=inner}else if(isPlainObject(folderPath)){syntheticExports=folderPath}else{throw new this.SlothletError("INVALID_CONFIG_SYNTHETIC_INPUT",{received:describeNonPlain(folderPath),validationError:true})}if(parts.length===0&&typeof syntheticExports.default==="function"){const def=syntheticExports.default;const{default:___default,...named}=syntheticExports;if(!def.name||def.name==="default"){if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARN_SYNTHETIC_ROOT_UNNAMED",{})}await this.emitImplDiagnostic("warning",{apiPath:normalizedPath,code:"WARN_SYNTHETIC_ROOT_UNNAMED",context:{},source:"addApi",moduleID:restOptions.moduleID});syntheticExports=named}else{if(Object.prototype.hasOwnProperty.call(named,def.name)){if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARN_SYNTHETIC_ROOT_COLLISION",{name:def.name})}await this.emitImplDiagnostic("warning",{apiPath:normalizedPath,code:"WARN_SYNTHETIC_ROOT_COLLISION",context:{name:def.name},source:"addApi",moduleID:restOptions.moduleID})}syntheticExports={[def.name]:def,...named}}}isFile=false;resolvedPath=`synthetic:${normalizedPath||"root"}`}else{({resolvedPath,isDirectory,isFile}=await this.resolvePath(folderPath));if(!isDirectory&&!isFile){throw new this.SlothletError("INVALID_CONFIG_PATH_TYPE",{path:resolvedPath,validationError:true})}if(isFile){const ext=path.extname(resolvedPath);if(![".mjs",".cjs",".js"].includes(ext)){throw new this.SlothletError("INVALID_CONFIG_FILE_TYPE",{path:resolvedPath,extension:ext,validationError:true})}}}const resolvedFolderPath=resolvedPath;let collisionMode;if(restOptions.forceOverwrite){collisionMode="replace"}else{collisionMode=restOptions.collisionMode||this.____config.api?.collision?.api||"error"}const mutateExisting=!!(restOptions.mutateExisting||collisionMode==="merge");const scanHiddenFolders=(restOptions.scanHiddenFolders??this.____config.scanHiddenFolders)===true;if(restOptions.scanHiddenFolders!==void 0&&!this.____config?.silent){new this.SlothletWarning("CONFIG_SCAN_HIDDEN_FOLDERS_DEPRECATED",{option:"scanHiddenFolders"})}const moduleID=restOptions.moduleID?String(restOptions.moduleID):this.buildDefaultModuleId(normalizedPath,resolvedFolderPath);if(moduleID.includes(MODULE_ID_SEPARATOR)){throw new this.SlothletError("MODULE_ID_RESERVED_SEPARATOR",{moduleID,separator:MODULE_ID_SEPARATOR,validationError:true})}if(restOptions.forceOverwrite&&!moduleID){throw new this.SlothletError("INVALID_CONFIG_FORCE_OVERWRITE_REQUIRES_MODULE_ID",{apiPath:normalizedPath,validationError:true})}let dirForBuild=resolvedFolderPath;let fileFilter=null;if(isFile){dirForBuild=path.dirname(resolvedFolderPath);const fileName=path.basename(resolvedFolderPath);fileFilter=file=>file===fileName}const newApi=await this.slothlet.builders.builder.buildAPI({dir:dirForBuild,mode:this.____config.mode,apiPathPrefix:effectivePath,collisionContext:"addApi",moduleID,collisionMode,fileFilter,rootUnwrap:isFile||isSynthetic,hidden:restOptions.hidden??null,scanHiddenFolders,...isSynthetic?{syntheticExports,syntheticName:parts.length?parts[parts.length-1]:"synthetic"}:{}});if(this.slothlet.handlers.apiCacheManager){this.slothlet.handlers.apiCacheManager.set(moduleID,{endpoint:effectivePath,moduleID,api:newApi,folderPath:resolvedFolderPath,syntheticExports:isSynthetic?syntheticExports:null,mode:this.____config.mode,sanitizeOptions:this.____config.sanitize||{},hidden:restOptions.hidden??null,scanHiddenFolders,collisionMode,config:{...this.____config},timestamp:Date.now()})}this.slothlet.debug("api",{key:"DEBUG_MODE_ADD_API_COMPONENT_BUILD_RETURN",topLevelKeys:Object.keys(newApi),dottedKeys:Object.keys(newApi).filter(k=>k.includes(".")),wrappers:Object.keys(newApi).filter(k=>resolveWrapper(newApi[k])!==null).map(k=>{const _w=resolveWrapper(newApi[k]);return{key:k,apiPath:_w.apiPath,implKeys:Object.keys(_w.____slothletInternal.impl||{}),childCacheSize:Object.keys(_w).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__")).length,childCacheKeys:Object.keys(_w).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__"))}}),nonWrappers:Object.keys(newApi).filter(k=>resolveWrapper(newApi[k])===null).map(k=>({key:k,type:typeof newApi[k]}))});let apiToMerge=newApi;if((isFile||isSynthetic)&&Object.keys(newApi).length===1){const fileName=Object.keys(newApi)[0];apiToMerge=newApi[fileName]}if(!isFile&&normalizedPath){const lastPart=normalizedPath.includes(".")?normalizedPath.split(".").pop():normalizedPath;if(lastPart&&Object.prototype.hasOwnProperty.call(apiToMerge,lastPart)){const dupValue=apiToMerge[lastPart];const dupType=typeof dupValue;if(dupValue!==null&&(dupType==="object"||dupType==="function")){const dupWrapper=resolveWrapper(dupValue);const dupFilePath=dupWrapper?.____slothletInternal?.filePath;const dupFileDir=dupFilePath?dupFilePath.replace(/\\/g,"/").split("/").slice(0,-1).join("/"):null;const normalizedFolderPath=resolvedFolderPath.replace(/\\/g,"/").replace(/\/$/,"");const expectedDir=normalizedFolderPath+"/"+lastPart;const isDirectChild=dupFileDir===expectedDir||dupFileDir===normalizedFolderPath;if(isDirectChild){const hoisted={};for(const k of Object.keys(apiToMerge)){if(k!==lastPart)hoisted[k]=apiToMerge[k]}if(dupWrapper){for(const k of Object.keys(dupWrapper).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__"))){hoisted[k]=dupWrapper[k]}}else{for(const k of Object.keys(dupValue)){hoisted[k]=dupValue[k]}}apiToMerge=hoisted;this.slothlet.debug("api",{key:"DEBUG_MODE_RULE_13_DEDUP_HOISTED_KEY",lastPart,newKeys:Object.keys(apiToMerge)})}}}}if(this.____config.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_ADD_API_COMPONENT_MERGE_KEYS",keys:Object.keys(apiToMerge),isRootLevel:parts.length===0})}let anyAssignmentSucceeded=false;let rootKeys=[];if(parts.length===0){const rootSource=isSynthetic?apiToMerge:newApi;rootKeys=Object.keys(rootSource);if(rootKeys.length===0&&isSynthetic){if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARN_SYNTHETIC_ROOT_EMPTY",{apiPath:normalizedPath||"(root)"})}await this.emitImplDiagnostic("warning",{apiPath:normalizedPath,code:"WARN_SYNTHETIC_ROOT_EMPTY",context:{apiPath:normalizedPath||"(root)"},source:"addApi",moduleID})}for(const key of rootKeys){const result1=await this.setValueAtPath(this.slothlet.api,[key],rootSource[key],{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});const result2=await this.setValueAtPath(this.slothlet.boundApi,[key],rootSource[key],{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(result1||result2){anyAssignmentSucceeded=true}}}else{if(resolveWrapper(apiToMerge)===null){const isCallableNamespace=typeof apiToMerge==="function";const containerWrapper=new UnifiedWrapper(this.slothlet,{apiPath:effectivePath,mode:this.____config.mode,isCallable:isCallableNamespace,moduleID,filePath:resolvedFolderPath,sourceFolder:resolvedFolderPath});containerWrapper.___setImpl(apiToMerge,moduleID);apiToMerge=containerWrapper.createProxy()}const result1=await this.setValueAtPath(this.slothlet.api,effectiveParts,apiToMerge,{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});const result2=await this.setValueAtPath(this.slothlet.boundApi,effectiveParts,apiToMerge,{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(result1||result2){anyAssignmentSucceeded=true}}if(anyAssignmentSucceeded){const pendingMaterializations=[];const seenWrappers=new Set;const collectPendingMaterializations=(obj,depth=0)=>{const objType=typeof obj;if(!obj||objType!=="object"&&objType!=="function"||depth>10)return;if(isFrameworkInternal(obj))return;const wrapper=resolveWrapper(obj);if(wrapper){if(seenWrappers.has(wrapper))return;seenWrappers.add(wrapper);if(wrapper.____slothletInternal.materializationPromise){pendingMaterializations.push(wrapper.____slothletInternal.materializationPromise)}const childKeys=Object.keys(wrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));for(const key of childKeys){collectPendingMaterializations(wrapper[key],depth+1)}return}for(const key of Object.keys(obj)){if(key!=="____slothletInternal"){collectPendingMaterializations(obj[key],depth+1)}}};if(effectiveParts.length===0){for(const key of rootKeys){if(this.slothlet.api[key]){collectPendingMaterializations(this.slothlet.api[key])}}}else{let current=this.slothlet.api;for(const part of effectiveParts){if(current&¤t[part]){current=current[part]}else{break}}if(current){collectPendingMaterializations(current)}}if(pendingMaterializations.length>0){if(this.____config.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_AWAITING_PENDING_MATERIALIZATIONS",count:pendingMaterializations.length,apiPath:normalizedPath})}await Promise.all(pendingMaterializations)}}if(anyAssignmentSucceeded&&metadata&&Object.keys(metadata).length>0&&this.slothlet.handlers.metadata){if(parts.length===0){for(const key of Object.keys(newApi)){this.slothlet.handlers.metadata.registerUserMetadata(key,metadata)}}else{const rootSegment=effectiveParts[0];this.slothlet.handlers.metadata.registerUserMetadata(rootSegment,metadata)}}if(this.slothlet.handlers.ownership&&moduleID){this.slothlet.handlers.ownership.registerSubtree(apiToMerge,moduleID,effectivePath);this.slothlet.handlers.ownership.setModuleEndpoint(moduleID,effectivePath)}if(this.slothlet.handlers.ownership){if(restOptions.recordHistory!==false){const historyFolderPath=isSynthetic?folderPath:resolvedFolderPath;this.state.addHistory.push({apiPath:normalizedPath,folderPath:historyFolderPath,options:{...restOptions,metadata,moduleID},moduleID,versionConfig:versionConfig||null});this.state.operationHistory.push({type:"add",apiPath:normalizedPath,folderPath:historyFolderPath,options:{...restOptions,metadata,moduleID},moduleID,versionConfig:versionConfig||null})}}if(versionConfig?.version&&this.slothlet.handlers.versionManager){const versionTag=String(versionConfig.version).trim();try{this.slothlet.handlers.versionManager.registerVersion(normalizedPath,versionTag,moduleID,versionConfig.metadata??{},versionConfig.default??false)}catch(error){await this._rollbackFailedVersionedAdd({moduleID,effectivePath,normalizedPath});throw error}}if(restOptions.permissions&&this.slothlet.handlers?.permissionManager){const perms=restOptions.permissions;const callerPattern=`${normalizedPath}.**`;if(Array.isArray(perms.deny)){for(const entry of perms.deny){const{target,condition}=this.#normalizePermissionShorthandEntry(entry);this.slothlet.handlers.permissionManager.addRule({caller:callerPattern,target,effect:"deny",condition},moduleID)}}if(Array.isArray(perms.allow)){for(const entry of perms.allow){const{target,condition}=this.#normalizePermissionShorthandEntry(entry);this.slothlet.handlers.permissionManager.addRule({caller:callerPattern,target,effect:"allow",condition},moduleID)}}}return moduleID}async _rollbackFailedVersionedAdd({moduleID,effectivePath,normalizedPath}){let addIndex=-1;for(let i=this.state.operationHistory.length-1;i>=0;i--){const entry=this.state.operationHistory[i];if(entry?.type==="add"&&entry?.apiPath===normalizedPath&&entry?.moduleID===moduleID){addIndex=i;break}}if(addIndex!==-1){this.state.operationHistory.splice(addIndex,1)}this.state.addHistory=this.state.addHistory.filter(entry=>entry?.moduleID!==moduleID);try{await this.removeApiComponent(moduleID||effectivePath,{recordHistory:false})}catch{}}#sweepOrphanedCaches(){const cacheManager=this.slothlet.handlers?.apiCacheManager;if(!cacheManager)return;const root=this.slothlet.boundApi;for(const moduleID of cacheManager.getAllModuleIDs()){const entry=cacheManager.get(moduleID);if(!entry||entry.endpoint==="."||entry.endpoint==="")continue;const parts=String(entry.endpoint).split(".").filter(Boolean);if(this.getValueAtPath(root,parts)===void 0){cacheManager.delete(moduleID)}}}_hasForeignOwnedDescendant(apiPath,moduleIDKey){const ownership=this.slothlet.handlers.ownership;if(!ownership||!apiPath)return false;const prefix=apiPath+".";for(const p of ownership.pathToModule.keys()){if(!p.startsWith(prefix))continue;const owner=ownership.getCurrentOwner(p);if(owner&&owner.moduleID!==moduleIDKey)return true}return false}async removeApiComponent(pathOrModuleId,options={}){const recordHistory=options.recordHistory!==false;if(typeof pathOrModuleId!=="string"||!pathOrModuleId){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"non-empty string",received:typeof pathOrModuleId,validationError:true})}const scopedApiPath=typeof options.scopedApiPath==="string"?options.scopedApiPath:null;let apiPath=null;let moduleID;if(this.slothlet.handlers.ownership){const registeredModules=Array.from(this.slothlet.handlers.ownership.moduleToPath.keys());let matchingModule=null;const candidateModuleID=pathOrModuleId.split(MODULE_ID_SEPARATOR)[0];for(let i=registeredModules.length-1;i>=0;i--){const candidate=registeredModules[i];if(candidate===candidateModuleID||candidate.startsWith(`${candidateModuleID}_`)){matchingModule=candidate;break}}if(matchingModule){moduleID=matchingModule}else if(scopedApiPath!==null){return false}else{const owner=this.slothlet.handlers.ownership.getCurrentOwner(pathOrModuleId);if(owner){apiPath=pathOrModuleId;moduleID=owner.moduleID}else{return false}}}else{const isModuleId=!pathOrModuleId.includes(".");apiPath=isModuleId?null:pathOrModuleId;moduleID=isModuleId?pathOrModuleId.split(MODULE_ID_SEPARATOR)[0]:null}if(!this.slothlet||!this.slothlet.isLoaded){throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"removeApi",validationError:true})}if(scopedApiPath!==null){const ownership=this.slothlet.handlers.ownership;const normalizedScoped=this.normalizeApiPath(scopedApiPath).apiPath;const ownedPaths=ownership?.moduleToPath?.get(moduleID);if(!ownedPaths||!ownedPaths.has(normalizedScoped)){return false}const scopedModuleIDKey=String(moduleID);const scopedPrefix=`${normalizedScoped}.`;const targets=[...ownedPaths].filter(p=>p===normalizedScoped||p.startsWith(scopedPrefix)).sort((a,b)=>b.length-a.length);const isFullRemoval=[...ownedPaths].every(p=>p===normalizedScoped||p.startsWith(scopedPrefix));if(isFullRemoval){ownership?.markUnregistered?.(scopedModuleIDKey)}for(const target of targets){const targetParts=this.normalizeApiPath(target).parts;const scopedResult=ownership.removePath(target,scopedModuleIDKey);if(scopedResult.action==="restore"){const revertValue=ownership.getCurrentValue?.(target);const revertOwner=ownership.getCurrentOwner?.(target)?.moduleID;if(revertValue!==void 0&&revertOwner){const revertOptions={mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:revertOwner};await this.setValueAtPath(this.slothlet.api,targetParts,revertValue,revertOptions);await this.setValueAtPath(this.slothlet.boundApi,targetParts,revertValue,revertOptions)}else{await this.restoreApiPath(target,scopedResult.restoreModuleId)}}else{const stillHasChild=[...ownership.moduleToPath.values()].some(set=>{for(const owned of set)if(owned.startsWith(`${target}.`))return true;return false});if(!stillHasChild){await this.deletePath(this.slothlet.api,targetParts);await this.deletePath(this.slothlet.boundApi,targetParts)}}}this.#sweepOrphanedCaches();if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedScoped,scopedModuleID:scopedModuleIDKey})}return true}if(apiPath&&moduleID){const normalizedPath2=this.normalizeApiPath(apiPath).apiPath;const moduleIDKey=String(moduleID);const history=this.slothlet.handlers.ownership?.getPathHistory?.(normalizedPath2)||[];const ownershipResult2=this.slothlet.handlers.ownership?.removePath?.(normalizedPath2,moduleIDKey)||{action:"none",removedModuleId:null,restoreModuleId:null};const pathParts=this.normalizeApiPath(apiPath).parts;if(ownershipResult2.action==="delete"){await this.deletePath(this.slothlet.api,pathParts);await this.deletePath(this.slothlet.boundApi,pathParts);if(this.slothlet.handlers.metadata){const rootSegment=normalizedPath2.split(".")[0];this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}if(this.slothlet.handlers.versionManager){const versionKey=this.slothlet.handlers.versionManager.getVersionKeyForModule(moduleIDKey);if(versionKey){this.slothlet.handlers.versionManager.unregisterVersion(versionKey.logicalPath,versionKey.versionTag)}if(this.slothlet.handlers.versionManager.hasDispatcher(normalizedPath2)){this.slothlet.handlers.versionManager.teardownDispatcher(normalizedPath2)}}this.#sweepOrphanedCaches();this.state.operationHistory.push({type:"remove",apiPath:normalizedPath2});return true}if(ownershipResult2.action==="restore"){const restoredValue=this.slothlet.handlers.ownership?.getCurrentValue?.(normalizedPath2);const restoredModuleId=this.slothlet.handlers.ownership?.getCurrentOwner?.(normalizedPath2)?.moduleID;if(restoredValue!==void 0&&restoredModuleId){await this.setValueAtPath(this.slothlet.api,pathParts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});await this.setValueAtPath(this.slothlet.boundApi,pathParts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});this.state.operationHistory.push({type:"remove",apiPath:normalizedPath2});return true}else{await this.restoreApiPath(normalizedPath2,ownershipResult2.restoreModuleId);this.state.operationHistory.push({type:"remove",apiPath:normalizedPath2});return true}}if(ownershipResult2.action==="none"&&history.length===0){await this.deletePath(this.slothlet.api,pathParts);await this.deletePath(this.slothlet.boundApi,pathParts);return true}return false}if(moduleID){const moduleIDKey=String(moduleID);const mountEndpoint=this.slothlet.handlers.ownership?.getModuleEndpoint?.(moduleIDKey);const isRootMount=!mountEndpoint||mountEndpoint===".";let mountRoot=isRootMount?"":mountEndpoint;const result=this.slothlet.handlers.ownership?.unregister?.(moduleIDKey)||{removed:[],rolledBack:[]};if(this.slothlet.handlers.versionManager){const versionKey=this.slothlet.handlers.versionManager.getVersionKeyForModule(moduleIDKey);if(versionKey){this.slothlet.handlers.versionManager.unregisterVersion(versionKey.logicalPath,versionKey.versionTag)}}const allPaths=[...result.removed,...result.rolledBack.map(r=>r.apiPath)];const uniquePaths=[...new Set(allPaths)];const pathsToDelete=[];const pathsToRollback=[];for(const path2 of uniquePaths){const currentOwner=this.slothlet.handlers.ownership?.getCurrentOwner?.(path2);const hasChildrenWithOtherOwners=isRootMount?this._hasForeignOwnedDescendant(path2,moduleIDKey):uniquePaths.some(p=>{if(p===path2||!p.startsWith(path2+"."))return false;const childOwner=this.slothlet.handlers.ownership?.getCurrentOwner?.(p);return childOwner&&childOwner.moduleID!==moduleIDKey});if(currentOwner&¤tOwner.moduleID!==moduleIDKey){pathsToRollback.push({apiPath:path2,restoredTo:currentOwner.moduleID})}else if(!hasChildrenWithOtherOwners){pathsToDelete.push(path2)}}pathsToDelete.sort((a,b)=>{const depthA=(a.match(/\./g)||[]).length;const depthB=(b.match(/\./g)||[]).length;return depthB-depthA});for(const removedPath of pathsToDelete){const{parts:parts2}=this.normalizeApiPath(removedPath);await this.deletePath(this.slothlet.api,parts2);await this.deletePath(this.slothlet.boundApi,parts2);if(this.slothlet.handlers.metadata){const rootSegment=removedPath.split(".")[0];this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}}if(pathsToDelete.length>0){if(isRootMount){const segs=pathsToDelete.map(p=>p.split("."));const minLen=Math.min(...segs.map(s=>s.length));const common=[];for(let i=0;i<minLen;i++){const seg=segs[0][i];if(segs.every(s=>s[i]===seg))common.push(seg);else break}mountRoot=common.join(".")}const rootParts=this.normalizeApiPath(mountRoot).parts;const mountRootDot=mountRoot===""?null:`${mountRoot}.`;const hasRollbackSurvivor=pathsToRollback.some(r=>r.apiPath===mountRoot||mountRootDot!==null&&r.apiPath.startsWith(mountRootDot));if(!hasRollbackSurvivor&&(!isRootMount||!this._hasForeignOwnedDescendant(mountRoot,moduleIDKey))){await this.deletePath(this.slothlet.api,rootParts);await this.deletePath(this.slothlet.boundApi,rootParts)}}for(const rollback of pathsToRollback){const{parts:parts2}=this.normalizeApiPath(rollback.apiPath);const previousImpl=this.slothlet.handlers.ownership?.getCurrentValue?.(rollback.apiPath);if(previousImpl!==void 0){const existingWrapper=this.getValueAtPath(this.slothlet.api,parts2);const existingWrapperRaw=resolveWrapper(existingWrapper);if(existingWrapperRaw){existingWrapperRaw.___setImpl(previousImpl,rollback.restoredTo)}const existingBoundWrapper=this.getValueAtPath(this.slothlet.boundApi,parts2);const existingBoundWrapperRaw=resolveWrapper(existingBoundWrapper);if(existingBoundWrapperRaw){existingBoundWrapperRaw.___setImpl(previousImpl,rollback.restoredTo)}}}const shadows=this.state.replaceShadows.get(moduleIDKey);if(shadows){for(const shadow of shadows){if(Object.prototype.hasOwnProperty.call(shadow.container,shadow.key))continue;Object.defineProperty(shadow.container,shadow.key,{value:shadow.child,writable:false,enumerable:true,configurable:true})}this.state.replaceShadows.delete(moduleIDKey)}this.state.addHistory=this.state.addHistory.filter(entry=>String(entry.moduleID)!==moduleIDKey);if(this.slothlet.handlers.apiCacheManager){const deleted=this.slothlet.handlers.apiCacheManager.delete(moduleIDKey);if(deleted){this.slothlet.debug("cache",{key:"DEBUG_MODE_CACHE_DELETED_MODULE_REMOVED",moduleID:moduleIDKey})}}this.#sweepOrphanedCaches();if(recordHistory&&pathsToDelete.length>0){this.state.operationHistory.push({type:"remove",apiPath:mountRoot||pathsToDelete[0].split(".")[0]})}return pathsToDelete.length>0||pathsToRollback.length>0}if(!apiPath){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_REQUIRED"),index:void 0,segment:void 0,validationError:true})}const{apiPath:normalizedPath,parts}=this.normalizeApiPath(apiPath);const ownershipResult=this.slothlet.handlers.ownership?.removePath?.(normalizedPath,null)||{action:"none",removedModuleId:null,restoreModuleId:null};const pathExists=this.getValueAtPath(this.slothlet.api,parts)!==void 0;if(ownershipResult.action==="none"){if(pathExists){await this.deletePath(this.slothlet.api,parts);await this.deletePath(this.slothlet.boundApi,parts);if(this.slothlet.handlers.metadata){this.slothlet.handlers.metadata.removeUserMetadataByApiPath(normalizedPath)}if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}return false}if(ownershipResult.action==="delete"){await this.deletePath(this.slothlet.api,parts);await this.deletePath(this.slothlet.boundApi,parts);if(this.slothlet.handlers.metadata){this.slothlet.handlers.metadata.removeUserMetadataByApiPath(normalizedPath)}if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}if(ownershipResult.action==="restore"){const restoredValue=this.slothlet.handlers.ownership?.getCurrentValue?.(normalizedPath);const restoredModuleId=this.slothlet.handlers.ownership?.getCurrentOwner?.(normalizedPath)?.moduleID;if(restoredValue!==void 0&&restoredModuleId){await this.setValueAtPath(this.slothlet.api,parts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});await this.setValueAtPath(this.slothlet.boundApi,parts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}await this.restoreApiPath(normalizedPath,ownershipResult.restoreModuleId);if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}return false}async reloadApiComponent(params){const{apiPath,moduleID,options}=params||{};if(!this.slothlet||!this.slothlet.isLoaded){throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"reloadApi",validationError:true})}if(moduleID){await this._reloadByModuleID(moduleID);return}if(apiPath){await this._reloadByApiPath(apiPath,options);return}throw new this.SlothletError("INVALID_ARGUMENT",{argument:"params",expected:"{ moduleID } or { apiPath }",received:params,validationError:true})}async _reloadByModuleID(moduleID,{forceReplace=true}={}){const cacheManager=this.slothlet.handlers.apiCacheManager;if(!cacheManager){throw new this.SlothletError("CACHE_MANAGER_NOT_AVAILABLE",{operation:"reload",validationError:true})}if(!cacheManager.has(moduleID)){throw new this.SlothletError("CACHE_NOT_FOUND",{moduleID,operation:"reload",validationError:true})}const oldEntry=cacheManager.get(moduleID);this.slothlet.debug("reload",{key:"DEBUG_MODE_RELOADING_MODULE_BY_ID",moduleID,endpoint:oldEntry.endpoint,folderPath:oldEntry.folderPath});const freshApi=await cacheManager.rebuildCache(moduleID);cacheManager.set(moduleID,{...oldEntry,api:freshApi,timestamp:Date.now()});this.slothlet.debug("reload",{key:"DEBUG_MODE_FRESH_API_KEYS_BEFORE_RESTORE",moduleID,endpoint:oldEntry.endpoint,freshApiKeys:Object.keys(freshApi||{})});await this._restoreApiTree(freshApi,oldEntry.endpoint,moduleID,oldEntry.collisionMode,forceReplace);this.slothlet.debug("reload",{key:"DEBUG_MODE_FRESH_API_KEYS_AFTER_RESTORE",moduleID,endpoint:oldEntry.endpoint,freshApiKeys:Object.keys(freshApi||{})});this.slothlet.debug("reload",{key:"DEBUG_MODE_MODULE_RELOAD_COMPLETE",moduleID});if(this.slothlet.handlers.versionManager){this.slothlet.handlers.versionManager.onVersionedModuleReload(moduleID)}}async _reloadByApiPath(apiPath,options={}){this.slothlet.debug("reload",{key:"DEBUG_MODE_RELOADING_BY_API_PATH",apiPath});const moduleIDsToReload=this._findAffectedCaches(apiPath);if(moduleIDsToReload.length===0){this.slothlet.debug("reload",{key:"DEBUG_MODE_NO_CACHES_ATTEMPTING_RESTORE",apiPath});if(apiPath!=="."&&apiPath!==""){await this.restoreApiPath(apiPath,"base")}return}const cacheManager=this.slothlet.handlers.apiCacheManager;moduleIDsToReload.sort((a,b)=>{const entryA=cacheManager.get(a);const entryB=cacheManager.get(b);if(entryA?.endpoint==="."&&entryB?.endpoint!==".")return-1;if(entryB?.endpoint==="."&&entryA?.endpoint!==".")return 1;const indexA=this.state.addHistory.findIndex(h=>h.moduleID===a);const indexB=this.state.addHistory.findIndex(h=>h.moduleID===b);return indexA-indexB});const endpointOrder=new Map;for(const moduleID of moduleIDsToReload){const entry=cacheManager.get(moduleID);const ep=entry?.endpoint??".";if(!endpointOrder.has(ep))endpointOrder.set(ep,[]);endpointOrder.get(ep).push(moduleID)}for(const[,moduleIDs]of endpointOrder){for(let i=0;i<moduleIDs.length;i++){await this._reloadByModuleID(moduleIDs[i],{forceReplace:i===0})}}const reloadMetadata=options?.metadata;if(reloadMetadata&&typeof reloadMetadata==="object"&&Object.keys(reloadMetadata).length>0){if(this.slothlet.handlers.metadata){const targetPath=apiPath==="."?null:apiPath.split(".")[0];if(targetPath){this.slothlet.handlers.metadata.registerUserMetadata(targetPath,reloadMetadata)}}}this.slothlet.debug("reload",{key:"DEBUG_MODE_API_PATH_RELOAD_COMPLETE",apiPath,reloadedModules:moduleIDsToReload.length,loadOrder:moduleIDsToReload})}_findAffectedCaches(apiPath){const cacheManager=this.slothlet.handlers.apiCacheManager;if(!cacheManager)return[];const allModuleIDs=cacheManager.getAllModuleIDs();if(apiPath==="."||apiPath===""||apiPath==null){const baseModules=[];for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(entry&&entry.endpoint==="."){baseModules.push(moduleID)}}return baseModules}const exactMatches=[];for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(entry&&entry.endpoint===apiPath){exactMatches.push(moduleID)}}if(exactMatches.length>0)return exactMatches;const children=[];const pathPrefix=apiPath+".";for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(entry?.endpoint?.startsWith(pathPrefix)){children.push(moduleID)}}if(children.length>0)return children;const ownership=this.slothlet.handlers.ownership;const history=ownership?.getPathHistory?.(apiPath);if(history&&history.length>0){const owned=[];for(const{moduleID}of history){if(cacheManager.has(moduleID)){owned.push(moduleID)}}if(owned.length>0)return owned}let bestMatch=null;let bestLength=-1;for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(!entry?.endpoint)continue;const ep=entry.endpoint;if(ep==="."||apiPath.startsWith(ep+".")){if(ep.length>bestLength){bestLength=ep.length;bestMatch=moduleID}}}if(bestMatch)return[bestMatch];return[]}_collectCustomProperties(existingProxy,freshApi){const customProps={};if(!existingProxy||typeof existingProxy!=="object"&&typeof existingProxy!=="function"){return customProps}const wrapper=resolveWrapper(existingProxy);if(!wrapper){return customProps}const freshKeys=new Set(freshApi?Object.keys(freshApi):[]);const ownKeys=Object.keys(wrapper).filter(k=>!ComponentBase.INTERNAL_KEYS.has(k));for(const key of ownKeys){try{const val=wrapper[key];if(val&&(typeof val==="object"||typeof val==="function")&&resolveWrapper(val)){continue}if(!freshKeys.has(key)){customProps[key]=val}else{customProps[key]=val}}catch{}}return customProps}_restoreCustomProperties(proxy,customProps){if(!proxy||!customProps||typeof customProps!=="object"){return}for(const[key,value]of Object.entries(customProps)){try{proxy[key]=value}catch{}}}async _restoreApiTree(freshApi,endpoint,moduleID,collisionMode,forceReplace=true){if(!freshApi||typeof freshApi!=="object"&&typeof freshApi!=="function"){return}const parts=endpoint==="."?[]:endpoint.split(".");if(parts.length===0){for(const key of Object.keys(freshApi)){if(typeof key==="string"&&(key.startsWith("_")||key.startsWith("__")))continue;if(key==="slothlet"||key==="shutdown"||key==="destroy")continue;const existingAtKey=this.slothlet.api[key];const freshValue=freshApi[key];if(existingAtKey&&resolveWrapper(existingAtKey)!==null){const customProps=this._collectCustomProperties(existingAtKey,freshValue);const freshWrapper=resolveWrapper(freshValue);const isLazyFresh=freshWrapper&&freshWrapper.____slothletInternal.mode==="lazy"&&!freshWrapper.____slothletInternal.state.materialized&&typeof freshWrapper.____slothletInternal.materializeFunc==="function";this.slothlet.debug("reload",{key:"DEBUG_MODE_RESTORE_ROOT_KEY_INSPECT",rootKey:key,hasFreshWrapper:!!freshWrapper,freshMode:freshWrapper?.____slothletInternal.mode,freshMaterialized:freshWrapper?.____slothletInternal.state?.materialized,hasMaterializeFunc:typeof freshWrapper?.____slothletInternal.materializeFunc==="function",isLazyFresh,existingMaterialized:resolveWrapper(existingAtKey)?.____slothletInternal?.state?.materialized});if(isLazyFresh){resolveWrapper(existingAtKey).___resetLazy(freshWrapper.____slothletInternal.materializeFunc);this._restoreCustomProperties(existingAtKey,customProps);this.slothlet.debug("reload",{key:"DEBUG_MODE_ROOT_KEY_RESET_LAZY",rootKey:key,restoredCustomProps:Object.keys(customProps)})}else{let implForReload;if(freshValue&&resolveWrapper(freshValue)!==null){implForReload=freshWrapper?UnifiedWrapper._extractFullImpl(freshWrapper):freshValue}else{implForReload=freshValue}resolveWrapper(existingAtKey).___setImpl(implForReload,moduleID);this._restoreCustomProperties(existingAtKey,customProps);this.slothlet.debug("reload",{key:"DEBUG_MODE_ROOT_KEY_UPDATED_SETIMPL",rootKey:key,restoredCustomProps:Object.keys(customProps)})}}else if(existingAtKey===void 0){const cacheManager=this.slothlet.handlers.apiCacheManager;const cacheEntry=cacheManager.get(moduleID);const resolvedFolderPath=cacheEntry?.folderPath||"";await this.setValueAtPath(this.slothlet.api,[key],freshValue,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(this.slothlet.boundApi){await this.setValueAtPath(this.slothlet.boundApi,[key],freshValue,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath})}}}}else{const existing=this.getValueAtPath(this.slothlet.api,parts);this.slothlet.debug("reload",{key:"DEBUG_MODE_RESTORE_NESTED_PATH",endpoint,moduleID,partsPath:parts.join("."),existingFound:!!existing,hasSetImpl:existing?resolveWrapper(existing)!==null:false,freshApiKeys:Object.keys(freshApi)});if(existing&&resolveWrapper(existing)!==null){const customProps=this._collectCustomProperties(existing,freshApi);const wrapper=resolveWrapper(existing);const originalCollisionMode=wrapper?wrapper.____slothletInternal.state.collisionMode:null;if(forceReplace&&wrapper){wrapper.____slothletInternal.state.collisionMode="replace";this.slothlet.debug("reload",{key:"DEBUG_MODE_RESTORE_FORCING_REPLACE",endpoint,originalCollisionMode,wrapperApiPath:wrapper.____slothletInternal.apiPath})}let implForReload;if(resolveWrapper(freshApi)!==null){const freshWrapper=resolveWrapper(freshApi);implForReload=freshWrapper?UnifiedWrapper._extractFullImpl(freshWrapper):freshApi}else{implForReload=freshApi}if(parts.length>0&&implForReload&&typeof implForReload==="object"){const lastEndpointPart=parts[parts.length-1];if(lastEndpointPart&&Object.prototype.hasOwnProperty.call(implForReload,lastEndpointPart)){const dupValue=implForReload[lastEndpointPart];const dupWrapperForDedup=resolveWrapper(dupValue);if(dupWrapperForDedup){const hoisted={};for(const k of Object.keys(implForReload)){if(k!==lastEndpointPart)hoisted[k]=implForReload[k]}for(const k of Object.keys(dupWrapperForDedup).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__"))){hoisted[k]=dupWrapperForDedup[k]}implForReload=hoisted}}}if(implForReload&&typeof implForReload==="object"){for(const key of Object.keys(implForReload)){const val=implForReload[key];if(resolveWrapper(val)!==null){const childWrapper=resolveWrapper(val);if(childWrapper.____slothletInternal.state.materialized){implForReload[key]=UnifiedWrapper._extractFullImpl(childWrapper)}}}}resolveWrapper(existing).___setImpl(implForReload,moduleID);if(wrapper&&originalCollisionMode!==null){wrapper.____slothletInternal.state.collisionMode=originalCollisionMode}this._restoreCustomProperties(existing,customProps);this.slothlet.debug("reload",{key:"DEBUG_MODE_UPDATED_WRAPPER_IMPL",endpoint,moduleID,forcedReplaceMode:true,restoredCustomProps:Object.keys(customProps)})}else{const cacheManager=this.slothlet.handlers.apiCacheManager;const cacheEntry=cacheManager.get(moduleID);const resolvedFolderPath=cacheEntry?.folderPath||"";let implForContainer=freshApi;if(typeof freshApi==="function"){implForContainer={};for(const key of Object.keys(freshApi)){implForContainer[key]=freshApi[key]}}const containerWrapper=new UnifiedWrapper(this.slothlet,{apiPath:endpoint,mode:this.____config.mode,moduleID,filePath:resolvedFolderPath,sourceFolder:resolvedFolderPath});containerWrapper.___setImpl(implForContainer,moduleID);const apiToSet=containerWrapper.createProxy();await this.setValueAtPath(this.slothlet.api,parts,apiToSet,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(this.slothlet.boundApi){await this.setValueAtPath(this.slothlet.boundApi,parts,apiToSet,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath})}this.slothlet.debug("reload",{key:"DEBUG_MODE_CREATED_NEW_WRAPPER_UNEXPECTED",endpoint,moduleID})}}}}export{ApiManager};
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import{ComponentBase}from"#factories/component-base";import{resolveWrapper}from"#handlers/unified-wrapper";import{verifyToken}from"#handlers/lifecycle-token";class Metadata extends ComponentBase{static slothletProperty="metadata";_instanceId=null;constructor(slothlet){super(slothlet);this._instanceId=`metadata_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}#secureMetadata=new WeakMap;#userMetadataStore=new Map;#globalUserMetadata=Object.create(null);#mergeMetadataValue(currentValue,nextValue,argumentName="metadata",metadataKey=null){const utilities=this.slothlet.helpers?.utilities;if(metadataKey!=null){this.#assertSafeMetadataKeySegment(metadataKey)}if(utilities?.isPlainObject(currentValue)){this.#assertAcyclicPlainObject(currentValue,argumentName,metadataKey);this.#assertNoReservedMetadataKeys(currentValue,metadataKey)}if(utilities?.isPlainObject(nextValue)){this.#assertAcyclicPlainObject(nextValue,argumentName,metadataKey);this.#assertNoReservedMetadataKeys(nextValue,metadataKey)}if(utilities?.isPlainObject(currentValue)&&utilities?.isPlainObject(nextValue)){return utilities.deepMerge(currentValue,nextValue)}return nextValue}#assertAcyclicPlainObject(value,argumentName,metadataKey=null){const utilities=this.slothlet.helpers?.utilities;if(!utilities?.isPlainObject(value))return;const validate=(candidate,ancestors=new WeakSet)=>{if(ancestors.has(candidate)){throw new this.SlothletError("INVALID_ARGUMENT",{argument:metadataKey?`${argumentName}.${metadataKey}`:argumentName,expected:"acyclic object",received:"circular reference",validationError:true})}ancestors.add(candidate);try{for(const nestedValue of Object.values(candidate)){if(utilities.isPlainObject(nestedValue)){validate(nestedValue,ancestors)}}}finally{ancestors.delete(candidate)}};validate(value)}#assertSafeMetadataKeySegment(key){const blocked=new Set(["__proto__","prototype","constructor"]);if(typeof key!=="string"||key.length===0)return;for(const segment of key.split(".")){if(blocked.has(segment)){throw new this.SlothletError("INVALID_METADATA_KEY",{key,type:"string",expected:"safe dot-notation key without reserved segments"})}}}#assertNoReservedMetadataKeys(value,metadataKey=null){const blocked=new Set(["__proto__","prototype","constructor"]);const utilities=this.slothlet.helpers?.utilities;const walk=(candidate,path="")=>{for(const[key,nestedValue]of Object.entries(candidate)){if(blocked.has(key)){const fullPath=path?`${path}.${key}`:key;const keyPath=metadataKey?`${metadataKey}.${fullPath}`:fullPath;throw new this.SlothletError("INVALID_METADATA_KEY",{key:keyPath,type:"string",expected:"safe dot-notation key without reserved segments"})}if(utilities?.isPlainObject(nestedValue)){walk(nestedValue,path?`${path}.${key}`:key)}}};walk(value)}#deepFreeze(obj){if(Object.isFrozen(obj))return obj;Object.freeze(obj);Object.getOwnPropertyNames(obj).forEach(prop=>{if(obj[prop]!==null&&typeof obj[prop]==="object"){this.#deepFreeze(obj[prop])}});return obj}tagSystemMetadata(target,systemData,token){if(!verifyToken(this.slothlet,token)){throw new this.SlothletError("METADATA_LIFECYCLE_BYPASS",{},null,{validationError:true})}if(!target)return;if(typeof target!=="object"&&typeof target!=="function"){return}let fullModuleID=systemData.moduleID;if(systemData.apiPath&&systemData.moduleID){const apiPathSlashes=systemData.apiPath.replace(/\./g,"/");fullModuleID=`${systemData.moduleID}:${apiPathSlashes}`}let sourceFolder=systemData.sourceFolder;if(!sourceFolder&&systemData.filePath){const pathModule=this.slothlet.helpers.resolver.path;sourceFolder=pathModule.dirname(systemData.filePath)}const frozenSystem=Object.freeze({filePath:systemData.filePath,sourceFolder,apiPath:systemData.apiPath,moduleID:fullModuleID,taggedAt:Date.now()});this.#secureMetadata.set(target,frozenSystem)}getSystemMetadata(target){if(!target)return null;const actualTarget=resolveWrapper(target)??target;const systemData=this.#secureMetadata.get(actualTarget.____slothletInternal?.impl||actualTarget);return systemData||null}getMetadata(target){if(!target)return{};const actualTarget=resolveWrapper(target)??target;const systemData=this.#secureMetadata.get(actualTarget)||this.#secureMetadata.get(actualTarget.____slothletInternal?.impl)||{};const moduleID=systemData.moduleID;const apiPath=systemData.apiPath;const collectMetadataFromParents=path=>{const parts=path.split(".");const collected={};for(let i=1;i<=parts.length;i++){const parentPath=parts.slice(0,i).join(".");const parentMeta=this.#userMetadataStore.get(parentPath);if(parentMeta?.metadata){Object.assign(collected,parentMeta.metadata)}}return collected};const userMetadataByModule=moduleID?this.#userMetadataStore.get(moduleID):null;const userMetadataByPath=apiPath?collectMetadataFromParents(apiPath):{};const userData={...userMetadataByPath,...userMetadataByModule?.metadata||{}};const combined={...this.#globalUserMetadata,...userData,...systemData};if(combined.metadata&&typeof combined.metadata==="object"){const{metadata,...rest}=combined;return this.#deepFreeze({...rest,...metadata})}return this.#deepFreeze(combined)}setGlobalMetadata(key,value){this.#globalUserMetadata[key]=this.#mergeMetadataValue(this.#globalUserMetadata[key],value,"metadata",key)}setUserMetadata(target,key,value){if(typeof target!=="function"&&typeof target!=="object"){throw new this.SlothletError("INVALID_METADATA_TARGET",{target:typeof target,expected:"function or object"})}const actualTarget=resolveWrapper(target)??target;const systemData=this.#secureMetadata.get(actualTarget)||this.#secureMetadata.get(actualTarget.____slothletInternal?.impl)||{};const moduleID=systemData.moduleID;if(!moduleID){throw new this.SlothletError("METADATA_NO_MODULE_ID",{},null,{validationError:true})}let entry=this.#userMetadataStore.get(moduleID);if(!entry){entry={metadata:{},apiPaths:new Set};this.#userMetadataStore.set(moduleID,entry)}entry.metadata[key]=this.#mergeMetadataValue(entry.metadata[key],value,"metadata",key);const apiPath=systemData.apiPath;if(apiPath){let pathEntry=this.#userMetadataStore.get(apiPath);if(!pathEntry){pathEntry={metadata:{},apiPaths:new Set};this.#userMetadataStore.set(apiPath,pathEntry)}pathEntry.metadata[key]=this.#mergeMetadataValue(pathEntry.metadata[key],value,"metadata",key);pathEntry.apiPaths.add(apiPath)}}removeUserMetadata(target,key){if(typeof target!=="function"&&typeof target!=="object"){throw new this.SlothletError("INVALID_METADATA_TARGET",{target:typeof target,expected:"function or object"})}const actualTarget=resolveWrapper(target)??target;const systemData=this.#secureMetadata.get(actualTarget)||this.#secureMetadata.get(actualTarget.____slothletInternal?.impl)||{};const moduleID=systemData.moduleID;const apiPath=systemData.apiPath;if(!moduleID)return;const applyRemoval=storeKey=>{const storeEntry=this.#userMetadataStore.get(storeKey);if(!storeEntry)return;if(key===void 0){this.#userMetadataStore.delete(storeKey)}else if(Array.isArray(key)){for(const k of key){if(typeof k!=="string"){throw new this.SlothletError("INVALID_METADATA_KEY",{key:k,type:typeof k,expected:"string"})}delete storeEntry.metadata[k]}}else if(typeof key==="object"&&key!==null){for(const[metadataKey,nestedKeys]of Object.entries(key)){if(!Array.isArray(nestedKeys)){throw new this.SlothletError("INVALID_METADATA_KEY",{key:metadataKey,type:typeof nestedKeys,expected:"array"})}const metadataValue=storeEntry.metadata[metadataKey];if(metadataValue&&typeof metadataValue==="object"){for(const nestedKey of nestedKeys){if(typeof nestedKey!=="string"){throw new this.SlothletError("INVALID_METADATA_KEY",{key:nestedKey,type:typeof nestedKey,expected:"string"})}delete metadataValue[nestedKey]}}}}else if(typeof key==="string"){delete storeEntry.metadata[key]}else{throw new this.SlothletError("INVALID_METADATA_KEY",{key,type:typeof key,expected:"string, string[], or object"})}};applyRemoval(moduleID);if(apiPath&&apiPath!==moduleID){applyRemoval(apiPath)}}registerUserMetadata(identifier,metadata){if(!identifier||typeof identifier!=="string"){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"identifier",expected:"non-empty string",received:typeof identifier},null,{validationError:true})}let entry=this.#userMetadataStore.get(identifier);if(!entry){entry={metadata:{},apiPaths:new Set};this.#userMetadataStore.set(identifier,entry)}entry.metadata=this.#mergeMetadataValue(entry.metadata,metadata,"metadata");entry.apiPaths.add(identifier)}removeUserMetadataByApiPath(apiPath){if(!apiPath)return;this.#userMetadataStore.delete(apiPath)}setPathMetadata(apiPath,keyOrObj,value){if(typeof apiPath!=="string"||!apiPath){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"apiPath",expected:"non-empty string",received:typeof apiPath},null,{validationError:true})}const metadataObj=typeof keyOrObj==="string"?{[keyOrObj]:value}:keyOrObj;if(!metadataObj||typeof metadataObj!=="object"||Array.isArray(metadataObj)){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"keyOrObj",expected:"string key or plain object",received:typeof keyOrObj},null,{validationError:true})}this.registerUserMetadata(apiPath,metadataObj)}getPathMetadata(apiPath){if(!apiPath||typeof apiPath!=="string")return{};const parts=apiPath.split(".");const collected={};for(let i=1;i<=parts.length;i++){const parentPath=parts.slice(0,i).join(".");const parentMeta=this.#userMetadataStore.get(parentPath);if(parentMeta?.metadata){Object.assign(collected,parentMeta.metadata)}}return{...this.#globalUserMetadata,...collected}}removePathMetadata(apiPath,key){if(!apiPath||typeof apiPath!=="string")return;const entry=this.#userMetadataStore.get(apiPath);if(!entry)return;if(key===void 0){this.#userMetadataStore.delete(apiPath)}else if(Array.isArray(key)){for(const k of key){if(typeof k!=="string"){throw new this.SlothletError("INVALID_METADATA_KEY",{key:k,type:typeof k,expected:"string"})}delete entry.metadata[k]}}else if(typeof key==="string"){delete entry.metadata[key]}else{throw new this.SlothletError("INVALID_METADATA_KEY",{key,type:typeof key,expected:"string or string[]"})}}exportUserState(){const storeCopy=new Map;for(const[key,entry]of this.#userMetadataStore){storeCopy.set(key,{metadata:{...entry.metadata},apiPaths:new Set(entry.apiPaths)})}return{globalMetadata:{...this.#globalUserMetadata},userMetadataStore:storeCopy}}importUserState(state){if(!state)return;if(state.globalMetadata){for(const[k,v]of Object.entries(state.globalMetadata)){if(!(k in this.#globalUserMetadata)){this.#globalUserMetadata[k]=v}}}if(state.userMetadataStore){for(const[key,savedEntry]of state.userMetadataStore){const existing=this.#userMetadataStore.get(key);if(!existing){this.#userMetadataStore.set(key,{metadata:{...savedEntry.metadata},apiPaths:new Set(savedEntry.apiPaths)})}else{existing.metadata={...savedEntry.metadata,...existing.metadata};for(const p of savedEntry.apiPaths)existing.apiPaths.add(p)}}}}async get(path){if(typeof path!=="string"){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"path",expected:"string",received:typeof path})}const apiRoot=this.slothlet.api;if(!apiRoot)return null;const parts=path.split(".");let target=apiRoot;for(const part of parts){if(!target||typeof target!=="object"&&typeof target!=="function"){return null}target=target[part]}if(target&&typeof target._materialize==="function"){await target._materialize()}if(typeof target==="function"||target&&resolveWrapper(target)?.____slothletInternal?.impl){return this.getMetadata(target)}return null}self(){const ctx=this.slothlet.contextManager?.tryGetContext();const currentWrapper=this.slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper;if(!ctx||!currentWrapper){throw new this.SlothletError("RUNTIME_NO_ACTIVE_CONTEXT",{},null,{validationError:true})}return this.getMetadata(currentWrapper)}caller(){const ctx=this.slothlet.contextManager?.tryGetContext();if(!ctx||!ctx.callerWrapper)return null;return this.getMetadata(ctx.callerWrapper)}}export{Metadata};
|
|
17
|
+
import{ComponentBase}from"#factories/component-base";import{resolveWrapper}from"#handlers/unified-wrapper";import{verifyToken}from"#handlers/lifecycle-token";const MODULE_ID_SEPARATOR="__slothlet_sep__";class Metadata extends ComponentBase{static slothletProperty="metadata";_instanceId=null;constructor(slothlet){super(slothlet);this._instanceId=`metadata_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}#secureMetadata=new WeakMap;#userMetadataStore=new Map;#globalUserMetadata=Object.create(null);#mergeMetadataValue(currentValue,nextValue,argumentName="metadata",metadataKey=null){const utilities=this.slothlet.helpers?.utilities;if(metadataKey!=null){this.#assertSafeMetadataKeySegment(metadataKey)}if(utilities?.isPlainObject(currentValue)){this.#assertAcyclicPlainObject(currentValue,argumentName,metadataKey);this.#assertNoReservedMetadataKeys(currentValue,metadataKey)}if(utilities?.isPlainObject(nextValue)){this.#assertAcyclicPlainObject(nextValue,argumentName,metadataKey);this.#assertNoReservedMetadataKeys(nextValue,metadataKey)}if(utilities?.isPlainObject(currentValue)&&utilities?.isPlainObject(nextValue)){return utilities.deepMerge(currentValue,nextValue)}return nextValue}#assertAcyclicPlainObject(value,argumentName,metadataKey=null){const utilities=this.slothlet.helpers?.utilities;if(!utilities?.isPlainObject(value))return;const validate=(candidate,ancestors=new WeakSet)=>{if(ancestors.has(candidate)){throw new this.SlothletError("INVALID_ARGUMENT",{argument:metadataKey?`${argumentName}.${metadataKey}`:argumentName,expected:"acyclic object",received:"circular reference",validationError:true})}ancestors.add(candidate);try{for(const nestedValue of Object.values(candidate)){if(utilities.isPlainObject(nestedValue)){validate(nestedValue,ancestors)}}}finally{ancestors.delete(candidate)}};validate(value)}#assertSafeMetadataKeySegment(key){const blocked=new Set(["__proto__","prototype","constructor"]);if(typeof key!=="string"||key.length===0)return;for(const segment of key.split(".")){if(blocked.has(segment)){throw new this.SlothletError("INVALID_METADATA_KEY",{key,type:"string",expected:"safe dot-notation key without reserved segments"})}}}#assertNoReservedMetadataKeys(value,metadataKey=null){const blocked=new Set(["__proto__","prototype","constructor"]);const utilities=this.slothlet.helpers?.utilities;const walk=(candidate,path="")=>{for(const[key,nestedValue]of Object.entries(candidate)){if(blocked.has(key)){const fullPath=path?`${path}.${key}`:key;const keyPath=metadataKey?`${metadataKey}.${fullPath}`:fullPath;throw new this.SlothletError("INVALID_METADATA_KEY",{key:keyPath,type:"string",expected:"safe dot-notation key without reserved segments"})}if(utilities?.isPlainObject(nestedValue)){walk(nestedValue,path?`${path}.${key}`:key)}}};walk(value)}#deepFreeze(obj){if(Object.isFrozen(obj))return obj;Object.freeze(obj);Object.getOwnPropertyNames(obj).forEach(prop=>{if(obj[prop]!==null&&typeof obj[prop]==="object"){this.#deepFreeze(obj[prop])}});return obj}tagSystemMetadata(target,systemData,token){if(!verifyToken(this.slothlet,token)){throw new this.SlothletError("METADATA_LIFECYCLE_BYPASS",{},null,{validationError:true})}if(!target)return;if(typeof target!=="object"&&typeof target!=="function"){return}let fullModuleID=systemData.moduleID;if(systemData.apiPath&&systemData.moduleID){const apiPathSlashes=systemData.apiPath.replace(/\./g,"/");fullModuleID=`${systemData.moduleID}${MODULE_ID_SEPARATOR}${apiPathSlashes}`}let sourceFolder=systemData.sourceFolder;if(!sourceFolder&&systemData.filePath){const pathModule=this.slothlet.helpers.resolver.path;sourceFolder=pathModule.dirname(systemData.filePath)}const frozenSystem=Object.freeze({filePath:systemData.filePath,sourceFolder,apiPath:systemData.apiPath,moduleID:fullModuleID,baseModuleID:systemData.moduleID,taggedAt:Date.now()});this.#secureMetadata.set(target,frozenSystem)}getSystemMetadata(target){if(!target)return null;const actualTarget=resolveWrapper(target)??target;const systemData=this.#secureMetadata.get(actualTarget.____slothletInternal?.impl||actualTarget);return systemData||null}getMetadata(target){if(!target)return{};const actualTarget=resolveWrapper(target)??target;const systemData=this.#secureMetadata.get(actualTarget)||this.#secureMetadata.get(actualTarget.____slothletInternal?.impl)||{};const moduleID=systemData.moduleID;const apiPath=systemData.apiPath;const collectMetadataFromParents=path=>{const parts=path.split(".");const collected={};for(let i=1;i<=parts.length;i++){const parentPath=parts.slice(0,i).join(".");const parentMeta=this.#userMetadataStore.get(parentPath);if(parentMeta?.metadata){Object.assign(collected,parentMeta.metadata)}}return collected};const userMetadataByModule=moduleID?this.#userMetadataStore.get(moduleID):null;const userMetadataByPath=apiPath?collectMetadataFromParents(apiPath):{};const userData={...userMetadataByPath,...userMetadataByModule?.metadata||{}};const combined={...this.#globalUserMetadata,...userData,...systemData};if(combined.metadata&&typeof combined.metadata==="object"){const{metadata,...rest}=combined;return this.#deepFreeze({...rest,...metadata})}return this.#deepFreeze(combined)}setGlobalMetadata(key,value){this.#globalUserMetadata[key]=this.#mergeMetadataValue(this.#globalUserMetadata[key],value,"metadata",key)}setUserMetadata(target,key,value){if(typeof target!=="function"&&typeof target!=="object"){throw new this.SlothletError("INVALID_METADATA_TARGET",{target:typeof target,expected:"function or object"})}const actualTarget=resolveWrapper(target)??target;const systemData=this.#secureMetadata.get(actualTarget)||this.#secureMetadata.get(actualTarget.____slothletInternal?.impl)||{};const moduleID=systemData.moduleID;if(!moduleID){throw new this.SlothletError("METADATA_NO_MODULE_ID",{},null,{validationError:true})}let entry=this.#userMetadataStore.get(moduleID);if(!entry){entry={metadata:{},apiPaths:new Set};this.#userMetadataStore.set(moduleID,entry)}entry.metadata[key]=this.#mergeMetadataValue(entry.metadata[key],value,"metadata",key);const apiPath=systemData.apiPath;if(apiPath){let pathEntry=this.#userMetadataStore.get(apiPath);if(!pathEntry){pathEntry={metadata:{},apiPaths:new Set};this.#userMetadataStore.set(apiPath,pathEntry)}pathEntry.metadata[key]=this.#mergeMetadataValue(pathEntry.metadata[key],value,"metadata",key);pathEntry.apiPaths.add(apiPath)}}removeUserMetadata(target,key){if(typeof target!=="function"&&typeof target!=="object"){throw new this.SlothletError("INVALID_METADATA_TARGET",{target:typeof target,expected:"function or object"})}const actualTarget=resolveWrapper(target)??target;const systemData=this.#secureMetadata.get(actualTarget)||this.#secureMetadata.get(actualTarget.____slothletInternal?.impl)||{};const moduleID=systemData.moduleID;const apiPath=systemData.apiPath;if(!moduleID)return;const applyRemoval=storeKey=>{const storeEntry=this.#userMetadataStore.get(storeKey);if(!storeEntry)return;if(key===void 0){this.#userMetadataStore.delete(storeKey)}else if(Array.isArray(key)){for(const k of key){if(typeof k!=="string"){throw new this.SlothletError("INVALID_METADATA_KEY",{key:k,type:typeof k,expected:"string"})}delete storeEntry.metadata[k]}}else if(typeof key==="object"&&key!==null){for(const[metadataKey,nestedKeys]of Object.entries(key)){if(!Array.isArray(nestedKeys)){throw new this.SlothletError("INVALID_METADATA_KEY",{key:metadataKey,type:typeof nestedKeys,expected:"array"})}const metadataValue=storeEntry.metadata[metadataKey];if(metadataValue&&typeof metadataValue==="object"){for(const nestedKey of nestedKeys){if(typeof nestedKey!=="string"){throw new this.SlothletError("INVALID_METADATA_KEY",{key:nestedKey,type:typeof nestedKey,expected:"string"})}delete metadataValue[nestedKey]}}}}else if(typeof key==="string"){delete storeEntry.metadata[key]}else{throw new this.SlothletError("INVALID_METADATA_KEY",{key,type:typeof key,expected:"string, string[], or object"})}};applyRemoval(moduleID);if(apiPath&&apiPath!==moduleID){applyRemoval(apiPath)}}registerUserMetadata(identifier,metadata){if(!identifier||typeof identifier!=="string"){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"identifier",expected:"non-empty string",received:typeof identifier},null,{validationError:true})}let entry=this.#userMetadataStore.get(identifier);if(!entry){entry={metadata:{},apiPaths:new Set};this.#userMetadataStore.set(identifier,entry)}entry.metadata=this.#mergeMetadataValue(entry.metadata,metadata,"metadata");entry.apiPaths.add(identifier)}removeUserMetadataByApiPath(apiPath){if(!apiPath)return;this.#userMetadataStore.delete(apiPath)}setPathMetadata(apiPath,keyOrObj,value){if(typeof apiPath!=="string"||!apiPath){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"apiPath",expected:"non-empty string",received:typeof apiPath},null,{validationError:true})}const metadataObj=typeof keyOrObj==="string"?{[keyOrObj]:value}:keyOrObj;if(!metadataObj||typeof metadataObj!=="object"||Array.isArray(metadataObj)){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"keyOrObj",expected:"string key or plain object",received:typeof keyOrObj},null,{validationError:true})}this.registerUserMetadata(apiPath,metadataObj)}getPathMetadata(apiPath){if(!apiPath||typeof apiPath!=="string")return{};const parts=apiPath.split(".");const collected={};for(let i=1;i<=parts.length;i++){const parentPath=parts.slice(0,i).join(".");const parentMeta=this.#userMetadataStore.get(parentPath);if(parentMeta?.metadata){Object.assign(collected,parentMeta.metadata)}}return{...this.#globalUserMetadata,...collected}}removePathMetadata(apiPath,key){if(!apiPath||typeof apiPath!=="string")return;const entry=this.#userMetadataStore.get(apiPath);if(!entry)return;if(key===void 0){this.#userMetadataStore.delete(apiPath)}else if(Array.isArray(key)){for(const k of key){if(typeof k!=="string"){throw new this.SlothletError("INVALID_METADATA_KEY",{key:k,type:typeof k,expected:"string"})}delete entry.metadata[k]}}else if(typeof key==="string"){delete entry.metadata[key]}else{throw new this.SlothletError("INVALID_METADATA_KEY",{key,type:typeof key,expected:"string or string[]"})}}exportUserState(){const storeCopy=new Map;for(const[key,entry]of this.#userMetadataStore){storeCopy.set(key,{metadata:{...entry.metadata},apiPaths:new Set(entry.apiPaths)})}return{globalMetadata:{...this.#globalUserMetadata},userMetadataStore:storeCopy}}importUserState(state){if(!state)return;if(state.globalMetadata){for(const[k,v]of Object.entries(state.globalMetadata)){if(!(k in this.#globalUserMetadata)){this.#globalUserMetadata[k]=v}}}if(state.userMetadataStore){for(const[key,savedEntry]of state.userMetadataStore){const existing=this.#userMetadataStore.get(key);if(!existing){this.#userMetadataStore.set(key,{metadata:{...savedEntry.metadata},apiPaths:new Set(savedEntry.apiPaths)})}else{existing.metadata={...savedEntry.metadata,...existing.metadata};for(const p of savedEntry.apiPaths)existing.apiPaths.add(p)}}}}async get(path){if(typeof path!=="string"){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"path",expected:"string",received:typeof path})}const apiRoot=this.slothlet.api;if(!apiRoot)return null;const parts=path.split(".");let target=apiRoot;for(const part of parts){if(!target||typeof target!=="object"&&typeof target!=="function"){return null}target=target[part]}if(target&&typeof target._materialize==="function"){await target._materialize()}if(typeof target==="function"||target&&resolveWrapper(target)?.____slothletInternal?.impl){return this.getMetadata(target)}return null}self(){const ctx=this.slothlet.contextManager?.tryGetContext();const currentWrapper=this.slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper;if(!ctx||!currentWrapper){throw new this.SlothletError("RUNTIME_NO_ACTIVE_CONTEXT",{},null,{validationError:true})}return this.getMetadata(currentWrapper)}caller(){const ctx=this.slothlet.contextManager?.tryGetContext();if(!ctx||!ctx.callerWrapper)return null;return this.getMetadata(ctx.callerWrapper)}}export{MODULE_ID_SEPARATOR,Metadata};
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import{ComponentBase}from"#factories/component-base";import{resolveWrapper}from"#handlers/unified-wrapper";class OwnershipManager extends ComponentBase{static slothletProperty="ownership";constructor(slothlet){super(slothlet);this.moduleToPath=new Map;this.pathToModule=new Map;this._unregisteredModules=new Set;this.moduleEndpoints=new Map}setModuleEndpoint(moduleID,endpoint){if(typeof moduleID==="string"&&moduleID){this.moduleEndpoints.set(moduleID,endpoint)}}getModuleEndpoint(moduleID){return this.moduleEndpoints.get(moduleID)}register({moduleID,apiPath,value,source="core",collisionMode="error",config=null,filePath=null}){if(!moduleID||typeof moduleID!=="string"){throw new this.SlothletError("OWNERSHIP_INVALID_MODULE_ID",{moduleID},null,{validationError:true})}if(apiPath!==""&&(!apiPath||typeof apiPath!=="string")){throw new this.SlothletError("OWNERSHIP_INVALID_API_PATH",{apiPath},null,{validationError:true})}if(this._unregisteredModules.has(moduleID)){return null}const currentOwner=this.getCurrentOwner(apiPath);if(currentOwner&¤tOwner.moduleID!==moduleID){if(collisionMode==="merge"||collisionMode==="replace"||collisionMode==="merge-replace"){}else if(collisionMode==="skip"){return null}else if(collisionMode==="warn"){if(!config?.silent){new this.SlothletWarning("WARNING_OWNERSHIP_CONFLICT",{apiPath,existingModuleId:currentOwner.moduleID,newModuleId:moduleID})}return null}else{throw new this.SlothletError("OWNERSHIP_CONFLICT",{apiPath,existingModuleId:currentOwner.moduleID,newModuleId:moduleID,validationError:true})}}if(!this.moduleToPath.has(moduleID)){this.moduleToPath.set(moduleID,new Set)}this.moduleToPath.get(moduleID).add(apiPath);if(!this.pathToModule.has(apiPath)){this.pathToModule.set(apiPath,[])}const stack=this.pathToModule.get(apiPath);const existingEntry=stack.find(entry2=>entry2.moduleID===moduleID);if(existingEntry){existingEntry.source=source;existingEntry.timestamp=Date.now();if(value!==void 0){existingEntry.value=value}if(filePath!==null){existingEntry.filePath=filePath}return existingEntry}const entry={moduleID,source,timestamp:Date.now(),value,filePath};this.pathToModule.get(apiPath).push(entry);return entry}unregister(moduleID){const paths=this.moduleToPath.get(moduleID);if(!paths){return{removed:[],rolledBack:[]}}this._unregisteredModules.add(moduleID);const removed=[];const rolledBack=[];for(const apiPath of paths){const result=this.removePath(apiPath,moduleID);if(result.action==="delete"){removed.push(apiPath)}else if(result.action==="restore"){rolledBack.push({apiPath,restoredTo:result.restoreModuleId})}}this.moduleToPath.delete(moduleID);this.moduleEndpoints.delete(moduleID);return{removed,rolledBack}}removePath(apiPath,moduleID=null){const stack=this.pathToModule.get(apiPath);if(!stack){return{action:"none",removedModuleId:null,restoreModuleId:null}}const index=moduleID?stack.findIndex(entry=>entry.moduleID===moduleID):stack.length-1;if(index===-1){return{action:"none",removedModuleId:null,restoreModuleId:null}}const[removed]=stack.splice(index,1);const removedModuleId=removed.moduleID;if(removedModuleId&&this.moduleToPath.has(removedModuleId)){const pathSet=this.moduleToPath.get(removedModuleId);pathSet.delete(apiPath);if(pathSet.size===0){this.moduleToPath.delete(removedModuleId)}}if(stack.length===0){this.pathToModule.delete(apiPath);return{action:"delete",removedModuleId,restoreModuleId:null}}const previous=stack[stack.length-1];return{action:"restore",removedModuleId,restoreModuleId:previous.moduleID}}getCurrentOwner(apiPath){const stack=this.pathToModule.get(apiPath);if(!stack||stack.length===0)return null;return stack[stack.length-1]}getCurrentValue(apiPath){const owner=this.getCurrentOwner(apiPath);if(!owner)return void 0;const value=owner.value;const rawWrapper=resolveWrapper(value);if(rawWrapper){return rawWrapper.__impl}return value}getModulePaths(moduleID){return Array.from(this.moduleToPath.get(moduleID)||[])}getPathHistory(apiPath){return this.pathToModule.get(apiPath)||[]}ownsPath(moduleID,apiPath){const owner=this.getCurrentOwner(apiPath);return owner&&owner.moduleID===moduleID}getDiagnostics(){return{totalModules:this.moduleToPath.size,totalPaths:this.pathToModule.size,modules:Array.from(this.moduleToPath.entries()).map(([id,paths])=>({moduleID:id,pathCount:paths.size})),conflictedPaths:Array.from(this.pathToModule.entries()).filter(([_,stack])=>stack.length>1).map(([path,stack])=>({apiPath:path,ownerStack:stack.map(e=>e.moduleID)}))}}getPathOwnership(apiPath){const stack=this.pathToModule.get(apiPath);if(!stack||stack.length===0){return null}return new Set(stack.map(entry=>entry.moduleID))}registerSubtree(api,moduleID,path,visited=new WeakSet){if(!api||typeof api!=="object")return;if(visited.has(api)){return}visited.add(api);if(path){this.register({moduleID,apiPath:path,value:api,source:"core",collisionMode:"merge",filePath:null})}for(const[key,value]of Object.entries(api)){const skipProps=["__metadata","__type","_materialize","_impl","____slothletInternal"];if(skipProps.includes(key)){continue}const childPath=path?`${path}.${key}`:key;if(typeof value==="function"||value&&typeof value==="object"){this.register({moduleID,apiPath:childPath,value,source:"core",collisionMode:"merge",filePath:null});if(typeof value==="object"&&!Array.isArray(value)){this.registerSubtree(value,moduleID,childPath,visited)}}}}clear(){this.moduleToPath.clear();this.pathToModule.clear();this._unregisteredModules.clear();this.moduleEndpoints.clear()}exportState(){return{moduleToPath:Array.from(this.moduleToPath.entries()).map(([id,paths])=>[id,Array.from(paths)]),pathToModule:Array.from(this.pathToModule.entries())}}importState(state){this.clear();for(const[id,paths]of state.moduleToPath){this.moduleToPath.set(id,new Set(paths))}for(const[path,stack]of state.pathToModule){this.pathToModule.set(path,stack)}}}export{OwnershipManager};
|
|
17
|
+
import{ComponentBase}from"#factories/component-base";import{resolveWrapper}from"#handlers/unified-wrapper";class OwnershipManager extends ComponentBase{static slothletProperty="ownership";constructor(slothlet){super(slothlet);this.moduleToPath=new Map;this.pathToModule=new Map;this._unregisteredModules=new Set;this.moduleEndpoints=new Map}setModuleEndpoint(moduleID,endpoint){if(typeof moduleID==="string"&&moduleID){this.moduleEndpoints.set(moduleID,endpoint)}}getModuleEndpoint(moduleID){return this.moduleEndpoints.get(moduleID)}register({moduleID,apiPath,value,source="core",collisionMode="error",config=null,filePath=null}){if(!moduleID||typeof moduleID!=="string"){throw new this.SlothletError("OWNERSHIP_INVALID_MODULE_ID",{moduleID},null,{validationError:true})}if(apiPath!==""&&(!apiPath||typeof apiPath!=="string")){throw new this.SlothletError("OWNERSHIP_INVALID_API_PATH",{apiPath},null,{validationError:true})}if(this._unregisteredModules.has(moduleID)){return null}const currentOwner=this.getCurrentOwner(apiPath);if(currentOwner&¤tOwner.moduleID!==moduleID){if(collisionMode==="merge"||collisionMode==="replace"||collisionMode==="merge-replace"){}else if(collisionMode==="skip"){return null}else if(collisionMode==="warn"){if(!config?.silent){new this.SlothletWarning("WARNING_OWNERSHIP_CONFLICT",{apiPath,existingModuleId:currentOwner.moduleID,newModuleId:moduleID})}return null}else{throw new this.SlothletError("OWNERSHIP_CONFLICT",{apiPath,existingModuleId:currentOwner.moduleID,newModuleId:moduleID,validationError:true})}}if(!this.moduleToPath.has(moduleID)){this.moduleToPath.set(moduleID,new Set)}this.moduleToPath.get(moduleID).add(apiPath);if(!this.pathToModule.has(apiPath)){this.pathToModule.set(apiPath,[])}const stack=this.pathToModule.get(apiPath);const existingEntry=stack.find(entry2=>entry2.moduleID===moduleID);if(existingEntry){existingEntry.source=source;existingEntry.timestamp=Date.now();if(value!==void 0){existingEntry.value=value}if(filePath!==null){existingEntry.filePath=filePath}return existingEntry}const entry={moduleID,source,timestamp:Date.now(),value,filePath};this.pathToModule.get(apiPath).push(entry);return entry}unregister(moduleID){const paths=this.moduleToPath.get(moduleID);if(!paths){return{removed:[],rolledBack:[]}}this._unregisteredModules.add(moduleID);const removed=[];const rolledBack=[];for(const apiPath of paths){const result=this.removePath(apiPath,moduleID);if(result.action==="delete"){removed.push(apiPath)}else if(result.action==="restore"){rolledBack.push({apiPath,restoredTo:result.restoreModuleId})}}this.moduleToPath.delete(moduleID);this.moduleEndpoints.delete(moduleID);return{removed,rolledBack}}markUnregistered(moduleID){this._unregisteredModules.add(moduleID);this.moduleEndpoints.delete(moduleID)}removePath(apiPath,moduleID=null){const stack=this.pathToModule.get(apiPath);if(!stack){return{action:"none",removedModuleId:null,restoreModuleId:null}}const index=moduleID?stack.findIndex(entry=>entry.moduleID===moduleID):stack.length-1;if(index===-1){return{action:"none",removedModuleId:null,restoreModuleId:null}}const[removed]=stack.splice(index,1);const removedModuleId=removed.moduleID;if(removedModuleId&&this.moduleToPath.has(removedModuleId)){const pathSet=this.moduleToPath.get(removedModuleId);pathSet.delete(apiPath);if(pathSet.size===0){this.moduleToPath.delete(removedModuleId)}}if(stack.length===0){this.pathToModule.delete(apiPath);return{action:"delete",removedModuleId,restoreModuleId:null}}const previous=stack[stack.length-1];return{action:"restore",removedModuleId,restoreModuleId:previous.moduleID}}getCurrentOwner(apiPath){const stack=this.pathToModule.get(apiPath);if(!stack||stack.length===0)return null;return stack[stack.length-1]}getCurrentValue(apiPath){const owner=this.getCurrentOwner(apiPath);if(!owner)return void 0;const value=owner.value;const rawWrapper=resolveWrapper(value);if(rawWrapper){return rawWrapper.__impl}return value}getModulePaths(moduleID){return Array.from(this.moduleToPath.get(moduleID)||[])}getPathHistory(apiPath){return this.pathToModule.get(apiPath)||[]}ownsPath(moduleID,apiPath){const owner=this.getCurrentOwner(apiPath);return owner&&owner.moduleID===moduleID}getDiagnostics(){return{totalModules:this.moduleToPath.size,totalPaths:this.pathToModule.size,modules:Array.from(this.moduleToPath.entries()).map(([id,paths])=>({moduleID:id,pathCount:paths.size})),conflictedPaths:Array.from(this.pathToModule.entries()).filter(([_,stack])=>stack.length>1).map(([path,stack])=>({apiPath:path,ownerStack:stack.map(e=>e.moduleID)}))}}getPathOwnership(apiPath){const stack=this.pathToModule.get(apiPath);if(!stack||stack.length===0){return null}return new Set(stack.map(entry=>entry.moduleID))}registerSubtree(api,moduleID,path,visited=new WeakSet){if(!api||typeof api!=="object")return;if(visited.has(api)){return}visited.add(api);if(path){this.register({moduleID,apiPath:path,value:api,source:"core",collisionMode:"merge",filePath:null})}for(const[key,value]of Object.entries(api)){const skipProps=["__metadata","__type","_materialize","_impl","____slothletInternal"];if(skipProps.includes(key)){continue}const childPath=path?`${path}.${key}`:key;if(typeof value==="function"||value&&typeof value==="object"){this.register({moduleID,apiPath:childPath,value,source:"core",collisionMode:"merge",filePath:null});if(typeof value==="object"&&!Array.isArray(value)){this.registerSubtree(value,moduleID,childPath,visited)}}}}clear(){this.moduleToPath.clear();this.pathToModule.clear();this._unregisteredModules.clear();this.moduleEndpoints.clear()}exportState(){return{moduleToPath:Array.from(this.moduleToPath.entries()).map(([id,paths])=>[id,Array.from(paths)]),pathToModule:Array.from(this.pathToModule.entries())}}importState(state){this.clear();for(const[id,paths]of state.moduleToPath){this.moduleToPath.set(id,new Set(paths))}for(const[path,stack]of state.pathToModule){this.pathToModule.set(path,stack)}}}export{OwnershipManager};
|