@cldmv/slothlet 3.8.0 → 3.9.0
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/dist/lib/builders/modes-processor.mjs +1 -1
- package/dist/lib/handlers/api-manager.mjs +1 -1
- package/dist/lib/handlers/module-manager.mjs +1 -1
- package/dist/lib/helpers/config.mjs +1 -1
- package/dist/lib/helpers/generate-manifest.mjs +17 -0
- package/dist/lib/helpers/manifest-resolver.mjs +17 -0
- package/dist/lib/helpers/module-discovery.mjs +1 -1
- package/dist/lib/helpers/module-manifest-validator.mjs +1 -1
- package/dist/lib/helpers/resolve-from-caller.mjs +1 -1
- package/dist/lib/i18n/languages/de-de.json +53 -46
- package/dist/lib/i18n/languages/en-gb.json +53 -46
- package/dist/lib/i18n/languages/en-us.json +53 -46
- package/dist/lib/i18n/languages/es-es.json +53 -46
- package/dist/lib/i18n/languages/es-mx.json +53 -46
- package/dist/lib/i18n/languages/fr-fr.json +7 -0
- package/dist/lib/i18n/languages/hi-in.json +49 -42
- package/dist/lib/i18n/languages/ja-jp.json +9 -2
- package/dist/lib/i18n/languages/ko-kr.json +9 -2
- package/dist/lib/i18n/languages/pt-br.json +53 -46
- package/dist/lib/i18n/languages/ru-ru.json +45 -38
- package/dist/lib/i18n/languages/zh-cn.json +14 -7
- package/dist/lib/processors/loader.mjs +1 -1
- package/dist/lib/processors/type-generator.mjs +1 -1
- package/dist/lib/processors/typescript.mjs +1 -1
- package/dist/lib/typegen/typegen.mjs +1 -1
- package/dist/slothlet.mjs +1 -1
- package/index.cjs +4 -14
- package/index.mjs +2 -12
- package/package.json +1 -1
- package/types/dist/lib/builders/modes-processor.d.mts.map +1 -1
- package/types/dist/lib/handlers/api-manager.d.mts.map +1 -1
- package/types/dist/lib/handlers/module-manager.d.mts.map +1 -1
- package/types/dist/lib/helpers/config.d.mts +7 -0
- package/types/dist/lib/helpers/config.d.mts.map +1 -1
- package/types/dist/lib/helpers/generate-manifest.d.mts +2 -0
- package/types/dist/lib/helpers/generate-manifest.d.mts.map +1 -0
- package/types/dist/lib/helpers/manifest-resolver.d.mts +2 -0
- package/types/dist/lib/helpers/manifest-resolver.d.mts.map +1 -0
- package/types/dist/lib/helpers/module-discovery.d.mts.map +1 -1
- package/types/dist/lib/helpers/module-manifest-validator.d.mts.map +1 -1
- package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -1
- package/types/dist/lib/processors/loader.d.mts.map +1 -1
- package/types/dist/lib/processors/type-generator.d.mts.map +1 -1
- package/types/dist/lib/processors/typescript.d.mts.map +1 -1
- package/types/dist/lib/typegen/typegen.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts.map +1 -1
- package/types/index.d.mts +3 -24
- package/types/index.d.mts.map +1 -1
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{t}from"@cldmv/slothlet/i18n";import{UnifiedWrapper,resolveWrapper}from"@cldmv/slothlet/handlers/unified-wrapper";import{getInstanceToken}from"@cldmv/slothlet/handlers/lifecycle-token";class ModesProcessor extends ComponentBase{static slothletProperty="modesProcessor";constructor(slothlet){super(slothlet)}async processFiles(api,files,directory,currentDepth,mode,isRoot,recursive,populateDirectly=false,apiPathPrefix="",collisionContext="initial",moduleID=null,sourceFolder=null,cacheBust=null,collisionModeOverride=null){const buildApiPath=path=>{if(!apiPathPrefix)return path;if(path.startsWith(`${apiPathPrefix}.`)){return path}return`${apiPathPrefix}.${path}`};let rootDefaultFunction=null;const rootContributors=[];const categoryName=isRoot&&!populateDirectly?null:this.slothlet.helpers.sanitize.sanitizePropertyName(directory.name);let targetApi=isRoot&&!populateDirectly?api:populateDirectly?api:api[categoryName]=api[categoryName]||{};const isRootFile=currentDepth===0&&!populateDirectly;const effectiveMode=mode==="lazy"&&isRootFile?"eager":mode;const shouldWrap=!(effectiveMode==="lazy"&&populateDirectly);if(!isRoot&&shouldWrap&&!populateDirectly){const existingTarget=api[categoryName];if(existingTarget&&resolveWrapper(existingTarget)){if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_CATEGORY_REUSE_EXISTING_WRAPPER",categoryName,apiPath:resolveWrapper(existingTarget)?.apiPath})}targetApi=existingTarget}else if(existingTarget===void 0||typeof existingTarget==="object"&&existingTarget!==null){const initialImpl=resolveWrapper(existingTarget)?{}:this.slothlet.helpers.modesUtils.cloneWrapperImpl(existingTarget||{},mode);if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_CATEGORY_WRAPPER_CREATED",categoryName,apiPath:buildApiPath(categoryName)})}const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(categoryName),initialImpl,filePath:directory.path,moduleID:moduleID||categoryName,sourceFolder});api[categoryName]=wrapper.createProxy();if(this.slothlet.handlers?.metadata){this.slothlet.handlers.metadata.tagSystemMetadata(wrapper,{filePath:directory.path,apiPath:buildApiPath(categoryName),moduleID:moduleID||"base",sourceFolder:sourceFolder||directory.path},getInstanceToken(this.slothlet))}if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_CATEGORY_WRAPPER_ASSIGNED",categoryName})}targetApi=api[categoryName];if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_CATEGORY_CREATED",categoryName,apiPath:wrapper.apiPath});this.slothlet.debug("modes",{key:"DEBUG_MODE_CATEGORY_TARGET_API_STATUS",isWrapper:!!resolveWrapper(targetApi),targetApiKeys:Object.keys(targetApi)})}}}if(!isRoot&&this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{message:await t("DEBUG_MODE_PROCESSING_DIRECTORY",{mode,categoryName,currentDepth})})}const loadedModules=[];for(const file of files){if(this.slothlet.config.debug?.modes&&categoryName==="string"){this.slothlet.debug("modes",{key:"DEBUG_MODE_PROCESSING_FILE",categoryName,file:file.name,isRoot,populateDirectly,mode})}try{const mod=await this.slothlet.processors.loader.loadModule(file.path,this.slothlet.instanceID,moduleID,cacheBust);const exports=this.slothlet.processors.loader.extractExports(mod);const moduleName=this.slothlet.helpers.sanitize.sanitizePropertyName(file.name);const moduleKeys=Object.keys(exports).filter(k=>k!=="default");const analysis={hasDefault:exports.default!==void 0,hasNamed:moduleKeys.length>0,defaultExportType:exports.default?typeof exports.default:null};loadedModules.push({file,mod:exports,moduleName,moduleKeys,analysis})}catch(error){if(error.name==="SlothletError")throw error;throw new this.SlothletError("MODULE_LOAD_FAILED",{modulePath:file.path,moduleID:moduleID||file.moduleID},error)}}const hasMultipleDefaults=loadedModules.filter(m=>m.analysis.hasDefault).length>1;for(const{file,mod,moduleName,moduleKeys,analysis}of loadedModules){if(this.slothlet.config.debug?.modes&&categoryName==="logger"){this.slothlet.debug("modes",{key:"DEBUG_MODE_PROCESSING_MODULE",categoryName,moduleName,hasDefault:analysis.hasDefault,moduleKeys,targetApiType:typeof targetApi,targetApiCallable:typeof targetApi==="function"})}const isAddapiFile=moduleName==="addapi"||file.name==="addapi"||file.fullName&&["addapi.mjs","addapi.cjs","addapi.js","addapi.ts"].includes(file.fullName.toLowerCase());const isAddapiObjectDefault=isAddapiFile&&analysis.hasDefault&&typeof mod.default!=="function";const isRootContributor=isRoot&&analysis.hasDefault&&typeof mod.default==="function"&&!isAddapiObjectDefault;if(moduleName==="config"||moduleKeys.some(k=>k.includes("Config")||k.includes("config"))){if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FILE_PROCESSING",module:moduleName,category:categoryName||"(none)",isRoot,hasDefault:analysis.hasDefault,moduleKeys})}}if(isRootContributor){const defaultFunc=this.slothlet.helpers.modesUtils.ensureNamedExportFunction(mod.default,moduleName);for(const key of moduleKeys){if(!this.slothlet.processors.flatten.shouldAttachNamedExport(key,mod[key],defaultFunc,mod.default)){continue}defaultFunc[key]=mod[key]}rootContributors.push({moduleName,file,defaultFunc});continue}else{const decision=await this.slothlet.processors.flatten.getFlatteningDecision({mod,moduleName,categoryName:categoryName||moduleName,analysis,hasMultipleDefaults,moduleKeys,t});if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{message:await t("DEBUG_MODE_MODULE_DECISION",{mode,moduleName,reason:decision.reason})})}const propertyName=decision.preferredName||moduleName;const effectiveCategoryName=categoryName||moduleName;let{moduleContent}=this.slothlet.processors.flatten.processModuleForAPI({mod,decision,moduleName,propertyName,moduleKeys,analysis,file,collisionContext,apiPathPrefix:apiPathPrefix||""});if(!isRoot&&!apiPathPrefix&&moduleName===categoryName){if(moduleKeys.length===1&&moduleKeys[0]===moduleName&&!analysis.hasDefault){const exportedValue=mod[moduleName];if(typeof exportedValue==="object"&&exportedValue!==null){if(this.slothlet.config.debug?.modes&&categoryName==="string"){this.slothlet.debug("modes",{key:"DEBUG_MODE_SINGLE_FILE_FOLDER_DETECTED",categoryName,populateDirectly,isRoot,mode,exportKeys:Object.keys(exportedValue)})}if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(categoryName),initialImpl:exportedValue,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});api[categoryName]=wrapper.createProxy();targetApi=api[categoryName]}else{this.slothlet.debug("modes",{key:"DEBUG_MODE_SINGLE_FILE_FOLDER_WRAPPED",categoryName,implKeys:Object.keys(exportedValue)})}for(const key of Object.keys(exportedValue)){if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${key}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}continue}}else if(analysis.hasDefault){const namedKeys=moduleKeys.length>0?moduleKeys:Object.keys(mod).filter(key=>key!=="default");const callableModule=typeof mod.default==="function"?this.slothlet.helpers.modesUtils.ensureNamedExportFunction(mod.default,categoryName):moduleContent;if(namedKeys.length>0){for(const key of namedKeys){if(key in callableModule){continue}if(!this.slothlet.processors.flatten.shouldAttachNamedExport(key,mod[key],callableModule,mod.default)){continue}callableModule[key]=mod[key]}}moduleContent=callableModule;if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(categoryName),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(callableModule,mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});api[categoryName]=wrapper.createProxy();targetApi=api[categoryName]}else{api[categoryName]=moduleContent;targetApi=api[categoryName]}const needsSeparateNamedExports=typeof mod.default==="function";if(needsSeparateNamedExports&&namedKeys.length>0){for(const key of namedKeys){if(shouldWrap){const namedWrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(`${categoryName}.${key}`),initialImpl:mod[key],materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,namedWrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,mod[key],{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${key}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:categoryName,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}continue}else if(moduleKeys.length>0){const hasMatchingObject=moduleKeys.some(key=>key===moduleName&&typeof mod[key]==="object"&&mod[key]!==null&&!Array.isArray(mod[key]));if(hasMatchingObject){const matchingObj=mod[moduleName];for(const[propKey,propValue]of Object.entries(matchingObj)){if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(`${categoryName}.${propKey}`),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(propValue,mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,propKey,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,propKey,propValue,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${propKey}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}for(const key of moduleKeys){if(key!==moduleName){if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(`${categoryName}.${key}`),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(mod[key],mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,mod[key],{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${key}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}}}else{if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FLATTEN_MULTI_EXPORT_FILE",moduleName,categoryName,exportCount:moduleKeys.length});this.slothlet.debug("modes",{key:"DEBUG_MODE_FLATTEN_MULTI_EXPORT_TARGET_STATUS",isWrapper:!!resolveWrapper(targetApi),keysBefore:Object.keys(targetApi)})}for(const key of moduleKeys){if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FLATTEN_MULTI_EXPORT_ASSIGNING",propKey:key})}if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(`${categoryName}.${key}`),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(mod[key],mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});const assigned=this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext});if(assigned){this.slothlet.debug("modes",{key:"DEBUG_MODE_FLATTEN_MULTI_EXPORT_ASSIGNED",propKey:key,keysAfter:Object.keys(targetApi)})}else{this.slothlet.debug("modes",{key:"DEBUG_MODE_FLATTEN_MULTI_EXPORT_BLOCKED",propKey:key})}}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,mod[key],{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${key}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}}continue}}if(!analysis.hasDefault&&moduleKeys.length===1&&!isRoot){const key=moduleKeys[0];const keyValue=mod[key];const isMatchingObject=key===moduleName&&typeof keyValue==="object"&&keyValue!==null&&!Array.isArray(keyValue);if(!isMatchingObject){const normalizedKey=key.toLowerCase().replace(/[-_]/g,"");const normalizedModuleName=moduleName.toLowerCase().replace(/[-_]/g,"");if(normalizedKey===normalizedModuleName){const preferredName=key;if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(`${categoryName}.${preferredName}`),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(mod[key],mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,preferredName,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,preferredName,mod[key],{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${preferredName}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}continue}}}if(decision.flattenToCategory&&moduleContent&&effectiveCategoryName){const isAddapiFile2=decision.flattenType==="addapi-metadata-default"||decision.flattenType==="addapi-special-file";if(isAddapiFile2&&typeof moduleContent==="object"&&!Array.isArray(moduleContent)&&typeof moduleContent!=="function"){for(const key of Object.keys(moduleContent)){const value=moduleContent[key];const keyPath=isRoot?key:`${apiPathPrefix?apiPathPrefix+".":""}${key}`;if(shouldWrap&&typeof value==="function"){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(keyPath),initialImpl:value,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,value,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}}if(this.slothlet.handlers.ownership){for(const key of Object.keys(moduleContent)){const apiPath=isRoot?key:apiPathPrefix?`${apiPathPrefix}.${key}`:key;this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),config:this.slothlet.config})}}}else{const localPath=isRoot?effectiveCategoryName:`${apiPathPrefix?apiPathPrefix+".":""}${effectiveCategoryName}`;if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(localPath),initialImpl:moduleContent,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder,isCallable:typeof moduleContent==="function"});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,effectiveCategoryName,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,effectiveCategoryName,moduleContent,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){const apiPath=isRoot?effectiveCategoryName:apiPathPrefix?`${apiPathPrefix}.${effectiveCategoryName}`:effectiveCategoryName;this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),config:this.slothlet.config})}}continue}if(shouldWrap){const localPath=isRoot?propertyName:`${categoryName}.${propertyName}`;const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(localPath),initialImpl:moduleContent,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.debug("modes",{key:"DEBUG_MODE_FILE_WRAPPER_ASSIGNMENT",propertyName,apiPath:buildApiPath(localPath),overwriting:propertyName in targetApi?resolveWrapper(targetApi[propertyName])?"wrapper":"value":"nothing"});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,propertyName,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,propertyName,moduleContent,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.config.debug?.modes&&categoryName==="logger"){this.slothlet.debug("modes",{key:"DEBUG_MODE_AFTER_ASSIGNMENT_STATUS",targetApiType:typeof targetApi,propertyName,hasProperty:propertyName in targetApi,implType:typeof resolveWrapper(targetApi)?.____slothletInternal.impl,implHasProperty:!!resolveWrapper(targetApi)?.____slothletInternal.impl?.utils})}if(this.slothlet.handlers.ownership){const apiPath=isRoot?propertyName:`${categoryName}.${propertyName}`;this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),config:this.slothlet.config})}}}if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_SUBDIRECTORY_CHECK",isRoot,categoryName,hasDirectory:!!directory,hasChildren:!!directory?.children,directoryCount:directory?.children?.directories?.length||0})}this.slothlet.debug("modes",{key:"DEBUG_MODE_DIRECTORY_CHECK",hasChildren:!!directory?.children,hasDirectories:!!directory?.children?.directories,length:directory?.children?.directories?.length||0});if(directory?.children?.directories){this.slothlet.debug("modes",{key:"DEBUG_MODE_DIRECTORY_CHECK_PASSED",recursive});if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_SUBDIRECTORIES_FOUND",subdirectoryCount:directory.children.directories.length,recursive})}if(recursive){this.slothlet.debug("modes",{key:"DEBUG_MODE_SUBDIRECTORY_LOOP_START",count:directory.children.directories.length});for(const subDir of directory.children.directories){this.slothlet.debug("modes",{key:"DEBUG_MODE_PROCESSING_SUBDIRECTORY",name:subDir.name,fileCount:subDir.children.files.length,subdirCount:subDir.children.directories.length});const subDirName=this.slothlet.helpers.sanitize.sanitizePropertyName(subDir.name);if(subDir.children.files.length===1&&subDir.children.directories.length===0){const file=subDir.children.files[0];const moduleName=this.slothlet.helpers.sanitize.sanitizePropertyName(file.name);const genericFilenames=["singlefile","index","main","default"];const isGeneric=genericFilenames.includes(moduleName.toLowerCase());const filenameMatchesFolder=moduleName===subDirName;if(isGeneric||filenameMatchesFolder){this.slothlet.debug("modes",{key:"DEBUG_MODE_FOLDER_LEVEL_FLATTEN_CHECK",subDir:subDirName,file:moduleName,isGeneric,filenameMatches:filenameMatchesFolder});const mod=await this.slothlet.processors.loader.loadModule(file.path,this.slothlet.instanceID,moduleID,cacheBust);const exports=this.slothlet.processors.loader.extractExports(mod);const moduleKeys=Object.keys(exports).filter(k=>k!=="default");const analysis={hasDefault:exports.default!==void 0,hasNamed:moduleKeys.length>0,defaultExportType:exports.default?typeof exports.default:null};const modContent=exports.default!==void 0?exports.default:exports;const categoryDecision=await this.slothlet.processors.flatten.buildCategoryDecisions({categoryName:subDirName,mod:modContent,moduleName,fileBaseName:file.name,analysis,moduleKeys,currentDepth:currentDepth+1,moduleFiles:subDir.children.files,t});if(categoryDecision.shouldFlatten){this.slothlet.debug("modes",{key:"DEBUG_MODE_FOLDER_LEVEL_FLATTEN_SKIP_RECURSION",subDir:subDirName});let implToWrap;if(categoryDecision.flattenType==="addapi-metadata-default"){implToWrap={};for(const key of moduleKeys){if(key!=="default"){implToWrap[key]=exports[key]}}}else if(moduleName===subDirName&&moduleKeys.includes(subDirName)){implToWrap=exports[subDirName]}else if(exports.default!==void 0){implToWrap=exports.default;if(moduleKeys.length>0){if(typeof implToWrap==="function"){const collisionConfig=this.slothlet.config.api?.collision||this.slothlet.config.collision;const collisionMode=(collisionContext==="initial"?collisionConfig?.initial:collisionConfig?.api)||"merge";for(const key of moduleKeys){if(key!=="default"){const hasExisting=implToWrap[key]!==void 0;if(hasExisting){if(collisionMode==="merge"||collisionMode==="skip"){continue}else if(collisionMode==="error"){throw new this.slothlet.SlothletError("COLLISION_DEFAULT_EXPORT_ERROR",{key,apiPath:`${apiPathPrefix}.${subDirName}`},null,{validationError:true})}else if(collisionMode==="warn"){new this.slothlet.SlothletWarning("WARNING_COLLISION_DEFAULT_EXPORT_OVERWRITE",{key,apiPath:`${apiPathPrefix}.${subDirName}`})}}implToWrap[key]=exports[key]}}}else if(typeof implToWrap==="object"&&implToWrap!==null){for(const key of moduleKeys){if(key!=="default"&&!(key in implToWrap)){implToWrap[key]=exports[key]}}}}}else{implToWrap=modContent}const modes_eagerCollisionConfig=this.slothlet.config.api?.collision||this.slothlet.config.collision;const modes_eagerCollisionMode=(collisionContext==="initial"?modes_eagerCollisionConfig?.initial:modes_eagerCollisionConfig?.api)||"merge";const modes_existingAtKey=targetApi[subDirName];if(modes_existingAtKey!==void 0&&modes_eagerCollisionMode!=="replace"&&modes_eagerCollisionMode!=="skip"){const modes_existingWrapper=resolveWrapper(modes_existingAtKey);if(modes_existingWrapper){if(modes_existingWrapper.____slothletInternal?.materializeFunc&&!modes_existingWrapper.____slothletInternal?.state?.materialized){await modes_existingWrapper._materialize()}if(modes_existingWrapper.____slothletInternal?.impl&&!modes_existingWrapper.____slothletInternal?.state?.childrenAdopted){modes_existingWrapper.___adoptImplChildren()}const modes_existingImpl=modes_existingWrapper.__impl;if(modes_existingImpl&&typeof modes_existingImpl==="object"&&!Array.isArray(modes_existingImpl)){if(typeof implToWrap==="object"&&implToWrap!==null){for(const[k,v]of Object.entries(modes_existingImpl)){if(!(k in implToWrap)){implToWrap[k]=v}}}else if(typeof implToWrap==="function"){for(const[k,v]of Object.entries(modes_existingImpl)){if(implToWrap[k]===void 0){implToWrap[k]=v}}}}const modes_existingChildKeys=Object.keys(modes_existingWrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));for(const ck of modes_existingChildKeys){if(typeof implToWrap==="object"&&implToWrap!==null&&!(ck in implToWrap)){implToWrap[ck]=modes_existingWrapper[ck]}else if(typeof implToWrap==="function"&&implToWrap[ck]===void 0){implToWrap[ck]=modes_existingWrapper[ck]}}this.slothlet.debug("modes",{key:"DEBUG_MODE_FILE_FOLDER_COLLISION_MERGED",subDir:subDirName,mergedKeys:Object.keys(implToWrap)})}}const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(categoryName?`${categoryName}.${subDirName}`:subDirName),initialImpl:implToWrap,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,subDirName,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext});if(this.slothlet.handlers.ownership){const apiPath=buildApiPath(categoryName?`${categoryName}.${subDirName}`:subDirName);this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),config:this.slothlet.config})}continue}}}const currentCategoryName=apiPathPrefix?apiPathPrefix.split(".").pop():categoryName;if(subDirName===currentCategoryName&¤tCategoryName!==null){await this.processFiles(targetApi,subDir.children.files,subDir,currentDepth+1,mode,false,recursive,true,apiPathPrefix,collisionContext,moduleID,sourceFolder,cacheBust);continue}await this.processFiles(targetApi,subDir.children.files,{name:subDirName,path:subDir.path,children:subDir.children},currentDepth+1,mode,false,recursive,false,apiPathPrefix,collisionContext,moduleID,sourceFolder,cacheBust)}}else{for(const subDir of directory.children.directories){const subDirName=this.slothlet.helpers.sanitize.sanitizePropertyName(subDir.name);const lazy_currentCategoryName=apiPathPrefix?apiPathPrefix.split(".").pop():categoryName;if(subDirName===lazy_currentCategoryName&&lazy_currentCategoryName!==null&&!populateDirectly){await this.processFiles(targetApi,subDir.children.files,subDir,currentDepth+1,"eager",false,true,true,apiPathPrefix,collisionContext,moduleID,sourceFolder,cacheBust);continue}const apiPath=categoryName?`${categoryName}.${subDirName}`:apiPathPrefix?`${apiPathPrefix}.${subDirName}`:subDirName;if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_CREATING_LAZY_SUBDIRECTORY",apiPath,fileCount:subDir.children.files.length})}const collisionConfig=this.slothlet.config.api?.collision;const modes_initialCollisionMode=collisionModeOverride||(collisionContext==="initial"?collisionConfig?.initial:collisionConfig?.api)||"replace";let modes_fileFolderImpl=null;const modes_lazyExisting=targetApi[subDirName];if(modes_initialCollisionMode!=="replace"&&resolveWrapper(modes_lazyExisting)){const modes_lazyExistingW=resolveWrapper(modes_lazyExisting);const existImpl=modes_lazyExistingW.__impl;if(existImpl&&typeof existImpl==="object"&&!Array.isArray(existImpl)){modes_fileFolderImpl={...existImpl}}const existChildKeys=Object.keys(modes_lazyExistingW).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));for(const ck of existChildKeys){if(!modes_fileFolderImpl)modes_fileFolderImpl={};if(!(ck in modes_fileFolderImpl)){modes_fileFolderImpl[ck]=modes_lazyExistingW[ck]}}}this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,subDirName,this.createLazySubdirectoryWrapper(subDir,apiPath,moduleID,sourceFolder,cacheBust,modes_fileFolderImpl,modes_initialCollisionMode),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}}}if(isRoot&&rootContributors.length>0){if(rootContributors.length===1){const{moduleName,file,defaultFunc}=rootContributors[0];rootDefaultFunction=defaultFunc;if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{message:await t("DEBUG_MODE_ROOT_CONTRIBUTOR",{mode,functionName:defaultFunc.name||"anonymous"})})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:moduleName,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}else{new this.SlothletWarning("WARNING_MULTIPLE_ROOT_CONTRIBUTORS",{rootContributors:rootContributors.map(rc=>rc.moduleName).join(", "),firstContributor:rootContributors[0].moduleName});for(const{moduleName,file,defaultFunc}of rootContributors){if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(moduleName),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(defaultFunc,mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,moduleName,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,moduleName,defaultFunc,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:moduleName,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}}}return rootDefaultFunction}createLazySubdirectoryWrapper(dir,apiPath,moduleID=null,sourceFolder=null,cacheBust=null,fileFolderCollisionImpl=null,collisionMode="merge"){const lazy_materializeFunc=this.slothlet.modes.lazy.createNamedMaterializeFunc(apiPath,async()=>{if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_MATERIALIZE_FUNCTION_STARTING",dir:dir.name,fileCount:dir.children.files?.length||0})}const categoryName=this.slothlet.helpers.sanitize.sanitizePropertyName(dir.name);const materialized={};const actualSourceFolder=sourceFolder?`${sourceFolder}/${dir.name}`.replace(/\\/g,"/"):`${this.slothlet.config?.dir}/${dir.name}`.replace(/\\/g,"/");const parentPrefix=apiPath.includes(".")?apiPath.split(".").slice(0,-1).join("."):"";const subDirs=dir.children.directories||[];if(dir.children.files.length===1&&subDirs.length===0){const file=dir.children.files[0];const moduleName=this.slothlet.helpers.sanitize.sanitizePropertyName(file.name);const genericFilenames=["singlefile","index","main","default"];const isGeneric=genericFilenames.includes(moduleName.toLowerCase());const filenameMatchesFolder=moduleName===categoryName;if(isGeneric||filenameMatchesFolder){const mod=await this.slothlet.processors.loader.loadModule(file.path,this.slothlet.instanceID,moduleID,cacheBust);const exports=this.slothlet.processors.loader.extractExports(mod);const moduleKeys=Object.keys(exports).filter(k=>k!=="default");const analysis={hasDefault:exports.default!==void 0,hasNamed:moduleKeys.length>0,defaultExportType:exports.default?typeof exports.default:null};const modContent=exports.default!==void 0?exports.default:exports;const categoryDecision=await this.slothlet.processors.flatten.buildCategoryDecisions({categoryName,mod:modContent,moduleName,fileBaseName:file.name,analysis,moduleKeys,currentDepth:apiPath.split(".").length,moduleFiles:dir.children.files,t});if(categoryDecision.shouldFlatten){let implToWrap;if(categoryDecision.flattenType==="addapi-metadata-default"){implToWrap=exports.default;for(const key of moduleKeys){if(key!=="default"){implToWrap[key]=exports[key]}}}else if(moduleName===categoryName&&moduleKeys.includes(categoryName)){implToWrap=exports[categoryName]}else if(exports.default!==void 0){implToWrap=exports.default;if(moduleKeys.length>0&&(typeof implToWrap==="function"||typeof implToWrap==="object"&&implToWrap!==null)){const collisionMode2=this.slothlet.config?.collision?.initial||"merge";for(const key of moduleKeys){if(!this.slothlet.processors.flatten.shouldAttachNamedExport(key,exports[key],implToWrap,exports.default)){continue}const hasExisting=Object.prototype.hasOwnProperty.call(implToWrap,key);if(hasExisting){if(collisionMode2==="merge"||collisionMode2==="skip"){continue}else if(collisionMode2==="error"){throw new this.slothlet.SlothletError("COLLISION_DEFAULT_EXPORT_ERROR",{key,apiPath},null,{validationError:true})}else if(collisionMode2==="warn"){new this.slothlet.SlothletWarning("WARNING_COLLISION_DEFAULT_EXPORT_OVERWRITE",{key,apiPath})}}implToWrap[key]=exports[key]}}}else{implToWrap=modContent}if(implToWrap&&typeof implToWrap==="object"&&this.slothlet.handlers?.lifecycle){for(const key of Object.keys(implToWrap)){const value=implToWrap[key];if(typeof value==="function"){this.slothlet.handlers.lifecycle.emit("impl:created",{apiPath:`${apiPath}.${key}`,impl:value,source:"lazy-materialization",moduleID,filePath:file.path,sourceFolder:sourceFolder||this.slothlet.config?.dir})}}}if(implToWrap&&typeof implToWrap==="object"){const childPaths={};for(const key of Object.keys(implToWrap)){if(typeof key!=="symbol"&&key!=="__childFilePaths"&&key!=="__filePath"){childPaths[key]=file.path}}implToWrap.__childFilePaths=childPaths}if(fileFolderCollisionImpl&&typeof implToWrap==="object"&&implToWrap!==null){for(const[k,v]of Object.entries(fileFolderCollisionImpl)){if(!(k in implToWrap)){implToWrap[k]=v}}}else if(fileFolderCollisionImpl&&typeof implToWrap==="function"){for(const[k,v]of Object.entries(fileFolderCollisionImpl)){if(implToWrap[k]===void 0){implToWrap[k]=v}}}return implToWrap}}}await this.processFiles(materialized,dir.children.files,{name:dir.name,children:dir.children},0,"eager",false,false,true,parentPrefix,"initial",moduleID,actualSourceFolder,cacheBust,collisionMode);if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_MATERIALIZE_FUNCTION_RETURNING_IMPL",dir:dir.name,keys:Object.keys(materialized)})}if(fileFolderCollisionImpl){for(const[k,v]of Object.entries(fileFolderCollisionImpl)){if(!(k in materialized)){materialized[k]=v}}}const materializedKeys=Object.keys(materialized);const _hasCategoryFile=dir.children.files.some(f=>this.slothlet.helpers.sanitize.sanitizePropertyName(f.name)===categoryName);if(_hasCategoryFile&&materializedKeys.includes(categoryName)&&materializedKeys.length>1){if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FOLDER_PATTERN_MATCH",dir:dir.name,categoryName,keys:materializedKeys})}const mainValue=materialized[categoryName];for(const key of materializedKeys){if(key!==categoryName){if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FOLDER_PATTERN_ATTACH_PROPERTY",categoryName,propKey:key,valueType:typeof materialized[key]})}mainValue[key]=materialized[key]}}if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FOLDER_PATTERN_RETURN",categoryName,keys:Object.keys(mainValue).filter(k=>!k.startsWith("__"))})}return mainValue}if(materializedKeys.length===1&&materializedKeys[0]===categoryName){const nestedValue=materialized[categoryName];if(nestedValue&&resolveWrapper(nestedValue)!==null){const attachedKeys=Object.keys(nestedValue).filter(key=>key!=="____slothletInternal");if(attachedKeys.length>0){return nestedValue}return nestedValue.__impl??nestedValue}else{return nestedValue}}return materialized});const wrapper=new UnifiedWrapper(this.slothlet,{mode:"lazy",apiPath,materializeFunc:lazy_materializeFunc,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:dir.path,moduleID,sourceFolder});if(collisionMode){wrapper.____slothletInternal.state.collisionMode=collisionMode}const shouldPrePopulate=collisionMode==="merge"||collisionMode==="warn";if(fileFolderCollisionImpl&&shouldPrePopulate){for(const[k,v]of Object.entries(fileFolderCollisionImpl)){if(typeof k==="string"&&!k.startsWith("_")&&!k.startsWith("__")){Object.defineProperty(wrapper,k,{value:v,writable:false,enumerable:true,configurable:true})}}}return wrapper.createProxy()}async applyRootContributor(api,rootFunction,mode){if(rootFunction){Object.assign(rootFunction,api);if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{message:await t("DEBUG_MODE_ROOT_CONTRIBUTOR_APPLIED",{mode,properties:Object.keys(api).length})})}return rootFunction}return api}}export{ModesProcessor};
|
|
17
|
+
import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{t}from"@cldmv/slothlet/i18n";import{UnifiedWrapper,resolveWrapper}from"@cldmv/slothlet/handlers/unified-wrapper";import{getInstanceToken}from"@cldmv/slothlet/handlers/lifecycle-token";class ModesProcessor extends ComponentBase{static slothletProperty="modesProcessor";constructor(slothlet){super(slothlet)}async processFiles(api,files,directory,currentDepth,mode,isRoot,recursive,populateDirectly=false,apiPathPrefix="",collisionContext="initial",moduleID=null,sourceFolder=null,cacheBust=null,collisionModeOverride=null){const buildApiPath=path=>{if(!apiPathPrefix)return path;if(path.startsWith(`${apiPathPrefix}.`)){return path}return`${apiPathPrefix}.${path}`};let rootDefaultFunction=null;const rootContributors=[];const categoryName=isRoot&&!populateDirectly?null:this.slothlet.helpers.sanitize.sanitizePropertyName(directory.name);let targetApi=isRoot&&!populateDirectly?api:populateDirectly?api:api[categoryName]=api[categoryName]||{};const isRootFile=currentDepth===0&&!populateDirectly;const effectiveMode=mode==="lazy"&&isRootFile?"eager":mode;const shouldWrap=!(effectiveMode==="lazy"&&populateDirectly);if(!isRoot&&shouldWrap&&!populateDirectly){const existingTarget=api[categoryName];if(existingTarget&&resolveWrapper(existingTarget)){if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_CATEGORY_REUSE_EXISTING_WRAPPER",categoryName,apiPath:resolveWrapper(existingTarget)?.apiPath})}targetApi=existingTarget}else if(existingTarget===void 0||typeof existingTarget==="object"&&existingTarget!==null){const initialImpl=resolveWrapper(existingTarget)?{}:this.slothlet.helpers.modesUtils.cloneWrapperImpl(existingTarget||{},mode);if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_CATEGORY_WRAPPER_CREATED",categoryName,apiPath:buildApiPath(categoryName)})}const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(categoryName),initialImpl,filePath:directory.path,moduleID:moduleID||categoryName,sourceFolder});api[categoryName]=wrapper.createProxy();if(this.slothlet.handlers?.metadata){this.slothlet.handlers.metadata.tagSystemMetadata(wrapper,{filePath:directory.path,apiPath:buildApiPath(categoryName),moduleID:moduleID||"base",sourceFolder:sourceFolder||directory.path},getInstanceToken(this.slothlet))}if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_CATEGORY_WRAPPER_ASSIGNED",categoryName})}targetApi=api[categoryName];if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_CATEGORY_CREATED",categoryName,apiPath:wrapper.apiPath});this.slothlet.debug("modes",{key:"DEBUG_MODE_CATEGORY_TARGET_API_STATUS",isWrapper:!!resolveWrapper(targetApi),targetApiKeys:Object.keys(targetApi)})}}}if(!isRoot&&this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{message:await t("DEBUG_MODE_PROCESSING_DIRECTORY",{mode,categoryName,currentDepth})})}const loadedModules=[];for(const file of files){if(this.slothlet.config.debug?.modes&&categoryName==="string"){this.slothlet.debug("modes",{key:"DEBUG_MODE_PROCESSING_FILE",categoryName,file:file.name,isRoot,populateDirectly,mode})}try{const mod=await this.slothlet.processors.loader.loadModule(file.path,this.slothlet.instanceID,moduleID,cacheBust);const exports=this.slothlet.processors.loader.extractExports(mod);const moduleName=this.slothlet.helpers.sanitize.sanitizePropertyName(file.name);const moduleKeys=Object.keys(exports).filter(k=>k!=="default");const analysis={hasDefault:exports.default!==void 0,hasNamed:moduleKeys.length>0,defaultExportType:exports.default?typeof exports.default:null};loadedModules.push({file,mod:exports,moduleName,moduleKeys,analysis})}catch(error){if(error.name==="SlothletError")throw error;throw new this.SlothletError("MODULE_LOAD_FAILED",{modulePath:file.path,moduleID:moduleID||file.moduleID},error)}}const hasMultipleDefaults=loadedModules.filter(m=>m.analysis.hasDefault).length>1;for(const{file,mod,moduleName,moduleKeys,analysis}of loadedModules){if(this.slothlet.config.debug?.modes&&categoryName==="logger"){this.slothlet.debug("modes",{key:"DEBUG_MODE_PROCESSING_MODULE",categoryName,moduleName,hasDefault:analysis.hasDefault,moduleKeys,targetApiType:typeof targetApi,targetApiCallable:typeof targetApi==="function"})}const isAddapiFile=moduleName==="addapi"||file.name==="addapi"||file.fullName&&["addapi.mjs","addapi.cjs","addapi.js","addapi.ts"].includes(file.fullName.toLowerCase());const isAddapiObjectDefault=isAddapiFile&&analysis.hasDefault&&typeof mod.default!=="function";const isRootContributor=isRoot&&analysis.hasDefault&&typeof mod.default==="function"&&!isAddapiObjectDefault;if(moduleName==="config"||moduleKeys.some(k=>k.includes("Config")||k.includes("config"))){if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FILE_PROCESSING",module:moduleName,category:categoryName||"(none)",isRoot,hasDefault:analysis.hasDefault,moduleKeys})}}if(isRootContributor){const defaultFunc=this.slothlet.helpers.modesUtils.ensureNamedExportFunction(mod.default,moduleName);for(const key of moduleKeys){if(!this.slothlet.processors.flatten.shouldAttachNamedExport(key,mod[key],defaultFunc,mod.default)){continue}defaultFunc[key]=mod[key]}rootContributors.push({moduleName,file,defaultFunc});continue}else{const decision=await this.slothlet.processors.flatten.getFlatteningDecision({mod,moduleName,categoryName:categoryName||moduleName,analysis,hasMultipleDefaults,moduleKeys,t});if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{message:await t("DEBUG_MODE_MODULE_DECISION",{mode,moduleName,reason:decision.reason})})}const propertyName=decision.preferredName||moduleName;const effectiveCategoryName=categoryName||moduleName;let{moduleContent}=this.slothlet.processors.flatten.processModuleForAPI({mod,decision,moduleName,propertyName,moduleKeys,analysis,file,collisionContext,apiPathPrefix:apiPathPrefix||""});if(!isRoot&&!apiPathPrefix&&moduleName===categoryName){if(moduleKeys.length===1&&moduleKeys[0]===moduleName&&!analysis.hasDefault){const exportedValue=mod[moduleName];if(typeof exportedValue==="object"&&exportedValue!==null){if(this.slothlet.config.debug?.modes&&categoryName==="string"){this.slothlet.debug("modes",{key:"DEBUG_MODE_SINGLE_FILE_FOLDER_DETECTED",categoryName,populateDirectly,isRoot,mode,exportKeys:Object.keys(exportedValue)})}if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(categoryName),initialImpl:exportedValue,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});api[categoryName]=wrapper.createProxy();targetApi=api[categoryName]}else{this.slothlet.debug("modes",{key:"DEBUG_MODE_SINGLE_FILE_FOLDER_WRAPPED",categoryName,implKeys:Object.keys(exportedValue)})}for(const key of Object.keys(exportedValue)){if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${key}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}continue}}else if(analysis.hasDefault){const namedKeys=moduleKeys.length>0?moduleKeys:Object.keys(mod).filter(key=>key!=="default");const callableModule=typeof mod.default==="function"?this.slothlet.helpers.modesUtils.ensureNamedExportFunction(mod.default,categoryName):moduleContent;if(namedKeys.length>0){for(const key of namedKeys){if(key in callableModule){continue}if(!this.slothlet.processors.flatten.shouldAttachNamedExport(key,mod[key],callableModule,mod.default)){continue}callableModule[key]=mod[key]}}moduleContent=callableModule;if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(categoryName),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(callableModule,mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});api[categoryName]=wrapper.createProxy();targetApi=api[categoryName]}else{api[categoryName]=moduleContent;targetApi=api[categoryName]}const needsSeparateNamedExports=typeof mod.default==="function";if(needsSeparateNamedExports&&namedKeys.length>0){for(const key of namedKeys){if(shouldWrap){const namedWrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(`${categoryName}.${key}`),initialImpl:mod[key],materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,namedWrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,mod[key],{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${key}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:categoryName,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}continue}else if(moduleKeys.length>0){const hasMatchingObject=moduleKeys.some(key=>key===moduleName&&typeof mod[key]==="object"&&mod[key]!==null&&!Array.isArray(mod[key]));if(hasMatchingObject){const matchingObj=mod[moduleName];for(const[propKey,propValue]of Object.entries(matchingObj)){if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(`${categoryName}.${propKey}`),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(propValue,mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,propKey,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,propKey,propValue,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${propKey}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}for(const key of moduleKeys){if(key!==moduleName){if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(`${categoryName}.${key}`),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(mod[key],mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,mod[key],{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${key}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}}}else{if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FLATTEN_MULTI_EXPORT_FILE",moduleName,categoryName,exportCount:moduleKeys.length});this.slothlet.debug("modes",{key:"DEBUG_MODE_FLATTEN_MULTI_EXPORT_TARGET_STATUS",isWrapper:!!resolveWrapper(targetApi),keysBefore:Object.keys(targetApi)})}for(const key of moduleKeys){if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FLATTEN_MULTI_EXPORT_ASSIGNING",propKey:key})}if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(`${categoryName}.${key}`),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(mod[key],mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});const assigned=this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext});if(assigned){this.slothlet.debug("modes",{key:"DEBUG_MODE_FLATTEN_MULTI_EXPORT_ASSIGNED",propKey:key,keysAfter:Object.keys(targetApi)})}else{this.slothlet.debug("modes",{key:"DEBUG_MODE_FLATTEN_MULTI_EXPORT_BLOCKED",propKey:key})}}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,mod[key],{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${key}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}}continue}}if(!analysis.hasDefault&&moduleKeys.length===1&&!isRoot){const key=moduleKeys[0];const keyValue=mod[key];const isMatchingObject=key===moduleName&&typeof keyValue==="object"&&keyValue!==null&&!Array.isArray(keyValue);if(!isMatchingObject){const normalizedKey=key.toLowerCase().replace(/[-_]/g,"");const normalizedModuleName=moduleName.toLowerCase().replace(/[-_]/g,"");if(normalizedKey===normalizedModuleName){const preferredName=key;if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(`${categoryName}.${preferredName}`),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(mod[key],mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,preferredName,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,preferredName,mod[key],{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:`${categoryName}.${preferredName}`,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}continue}}}if(decision.flattenToRoot&&moduleContent&&!isRoot&&!this.slothlet.config.suppressFixes?.has("C03_116")){for(const key of Object.keys(moduleContent)){const value=moduleContent[key];const keyPath=apiPathPrefix?`${apiPathPrefix}.${key}`:`${categoryName}.${key}`;if(shouldWrap&&typeof value==="function"){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(keyPath),initialImpl:value,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,value,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}}if(this.slothlet.handlers.ownership){for(const key of Object.keys(moduleContent)){const apiPath=apiPathPrefix?`${apiPathPrefix}.${key}`:`${categoryName}.${key}`;this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}continue}if(decision.flattenToCategory&&moduleContent&&effectiveCategoryName){const isAddapiFile2=decision.flattenType==="addapi-metadata-default"||decision.flattenType==="addapi-special-file";if(isAddapiFile2&&typeof moduleContent==="object"&&!Array.isArray(moduleContent)&&typeof moduleContent!=="function"){for(const key of Object.keys(moduleContent)){const value=moduleContent[key];const keyPath=isRoot?key:`${apiPathPrefix?apiPathPrefix+".":""}${key}`;if(shouldWrap&&typeof value==="function"){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(keyPath),initialImpl:value,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,key,value,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}}if(this.slothlet.handlers.ownership){for(const key of Object.keys(moduleContent)){const apiPath=isRoot?key:apiPathPrefix?`${apiPathPrefix}.${key}`:key;this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),config:this.slothlet.config})}}}else{const localPath=isRoot?effectiveCategoryName:`${apiPathPrefix?apiPathPrefix+".":""}${effectiveCategoryName}`;if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(localPath),initialImpl:moduleContent,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder,isCallable:typeof moduleContent==="function"});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,effectiveCategoryName,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,effectiveCategoryName,moduleContent,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){const apiPath=isRoot?effectiveCategoryName:apiPathPrefix?`${apiPathPrefix}.${effectiveCategoryName}`:effectiveCategoryName;this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),config:this.slothlet.config})}}continue}if(shouldWrap){const localPath=isRoot?propertyName:`${categoryName}.${propertyName}`;const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(localPath),initialImpl:moduleContent,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.debug("modes",{key:"DEBUG_MODE_FILE_WRAPPER_ASSIGNMENT",propertyName,apiPath:buildApiPath(localPath),overwriting:propertyName in targetApi?resolveWrapper(targetApi[propertyName])?"wrapper":"value":"nothing"});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,propertyName,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,propertyName,moduleContent,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.config.debug?.modes&&categoryName==="logger"){this.slothlet.debug("modes",{key:"DEBUG_MODE_AFTER_ASSIGNMENT_STATUS",targetApiType:typeof targetApi,propertyName,hasProperty:propertyName in targetApi,implType:typeof resolveWrapper(targetApi)?.____slothletInternal.impl,implHasProperty:!!resolveWrapper(targetApi)?.____slothletInternal.impl?.utils})}if(this.slothlet.handlers.ownership){const apiPath=isRoot?propertyName:`${categoryName}.${propertyName}`;this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),config:this.slothlet.config})}}}if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_SUBDIRECTORY_CHECK",isRoot,categoryName,hasDirectory:!!directory,hasChildren:!!directory?.children,directoryCount:directory?.children?.directories?.length||0})}this.slothlet.debug("modes",{key:"DEBUG_MODE_DIRECTORY_CHECK",hasChildren:!!directory?.children,hasDirectories:!!directory?.children?.directories,length:directory?.children?.directories?.length||0});if(directory?.children?.directories){this.slothlet.debug("modes",{key:"DEBUG_MODE_DIRECTORY_CHECK_PASSED",recursive});if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_SUBDIRECTORIES_FOUND",subdirectoryCount:directory.children.directories.length,recursive})}if(recursive){this.slothlet.debug("modes",{key:"DEBUG_MODE_SUBDIRECTORY_LOOP_START",count:directory.children.directories.length});for(const subDir of directory.children.directories){this.slothlet.debug("modes",{key:"DEBUG_MODE_PROCESSING_SUBDIRECTORY",name:subDir.name,fileCount:subDir.children.files.length,subdirCount:subDir.children.directories.length});const subDirName=this.slothlet.helpers.sanitize.sanitizePropertyName(subDir.name);if(subDir.children.files.length===1&&subDir.children.directories.length===0){const file=subDir.children.files[0];const moduleName=this.slothlet.helpers.sanitize.sanitizePropertyName(file.name);const genericFilenames=["singlefile","index","main","default"];const isGeneric=genericFilenames.includes(moduleName.toLowerCase());const filenameMatchesFolder=moduleName===subDirName;if(isGeneric||filenameMatchesFolder){this.slothlet.debug("modes",{key:"DEBUG_MODE_FOLDER_LEVEL_FLATTEN_CHECK",subDir:subDirName,file:moduleName,isGeneric,filenameMatches:filenameMatchesFolder});const mod=await this.slothlet.processors.loader.loadModule(file.path,this.slothlet.instanceID,moduleID,cacheBust);const exports=this.slothlet.processors.loader.extractExports(mod);const moduleKeys=Object.keys(exports).filter(k=>k!=="default");const analysis={hasDefault:exports.default!==void 0,hasNamed:moduleKeys.length>0,defaultExportType:exports.default?typeof exports.default:null};const modContent=exports.default!==void 0?exports.default:exports;const categoryDecision=await this.slothlet.processors.flatten.buildCategoryDecisions({categoryName:subDirName,mod:modContent,moduleName,fileBaseName:file.name,analysis,moduleKeys,currentDepth:currentDepth+1,moduleFiles:subDir.children.files,t});if(categoryDecision.shouldFlatten){this.slothlet.debug("modes",{key:"DEBUG_MODE_FOLDER_LEVEL_FLATTEN_SKIP_RECURSION",subDir:subDirName});let implToWrap;if(categoryDecision.flattenType==="addapi-metadata-default"){implToWrap={};for(const key of moduleKeys){if(key!=="default"){implToWrap[key]=exports[key]}}}else if(moduleName===subDirName&&moduleKeys.includes(subDirName)){implToWrap=exports[subDirName]}else if(exports.default!==void 0){implToWrap=exports.default;if(moduleKeys.length>0){if(typeof implToWrap==="function"){const collisionConfig=this.slothlet.config.api?.collision||this.slothlet.config.collision;const collisionMode=(collisionContext==="initial"?collisionConfig?.initial:collisionConfig?.api)||"merge";for(const key of moduleKeys){if(key!=="default"){const hasExisting=implToWrap[key]!==void 0;if(hasExisting){if(collisionMode==="merge"||collisionMode==="skip"){continue}else if(collisionMode==="error"){throw new this.slothlet.SlothletError("COLLISION_DEFAULT_EXPORT_ERROR",{key,apiPath:`${apiPathPrefix}.${subDirName}`},null,{validationError:true})}else if(collisionMode==="warn"){new this.slothlet.SlothletWarning("WARNING_COLLISION_DEFAULT_EXPORT_OVERWRITE",{key,apiPath:`${apiPathPrefix}.${subDirName}`})}}implToWrap[key]=exports[key]}}}else if(typeof implToWrap==="object"&&implToWrap!==null){for(const key of moduleKeys){if(key!=="default"&&!(key in implToWrap)){implToWrap[key]=exports[key]}}}}}else{implToWrap=modContent}const modes_eagerCollisionConfig=this.slothlet.config.api?.collision||this.slothlet.config.collision;const modes_eagerCollisionMode=(collisionContext==="initial"?modes_eagerCollisionConfig?.initial:modes_eagerCollisionConfig?.api)||"merge";const modes_existingAtKey=targetApi[subDirName];if(modes_existingAtKey!==void 0&&modes_eagerCollisionMode!=="replace"&&modes_eagerCollisionMode!=="skip"){const modes_existingWrapper=resolveWrapper(modes_existingAtKey);if(modes_existingWrapper){if(modes_existingWrapper.____slothletInternal?.materializeFunc&&!modes_existingWrapper.____slothletInternal?.state?.materialized){await modes_existingWrapper._materialize()}if(modes_existingWrapper.____slothletInternal?.impl&&!modes_existingWrapper.____slothletInternal?.state?.childrenAdopted){modes_existingWrapper.___adoptImplChildren()}const modes_existingImpl=modes_existingWrapper.__impl;if(modes_existingImpl&&typeof modes_existingImpl==="object"&&!Array.isArray(modes_existingImpl)){if(typeof implToWrap==="object"&&implToWrap!==null){for(const[k,v]of Object.entries(modes_existingImpl)){if(!(k in implToWrap)){implToWrap[k]=v}}}else if(typeof implToWrap==="function"){for(const[k,v]of Object.entries(modes_existingImpl)){if(implToWrap[k]===void 0){implToWrap[k]=v}}}}const modes_existingChildKeys=Object.keys(modes_existingWrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));for(const ck of modes_existingChildKeys){if(typeof implToWrap==="object"&&implToWrap!==null&&!(ck in implToWrap)){implToWrap[ck]=modes_existingWrapper[ck]}else if(typeof implToWrap==="function"&&implToWrap[ck]===void 0){implToWrap[ck]=modes_existingWrapper[ck]}}this.slothlet.debug("modes",{key:"DEBUG_MODE_FILE_FOLDER_COLLISION_MERGED",subDir:subDirName,mergedKeys:Object.keys(implToWrap)})}}const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(categoryName?`${categoryName}.${subDirName}`:subDirName),initialImpl:implToWrap,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,subDirName,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext});if(this.slothlet.handlers.ownership){const apiPath=buildApiPath(categoryName?`${categoryName}.${subDirName}`:subDirName);this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),config:this.slothlet.config})}continue}}}const currentCategoryName=apiPathPrefix?apiPathPrefix.split(".").pop():categoryName;if(subDirName===currentCategoryName&¤tCategoryName!==null){await this.processFiles(targetApi,subDir.children.files,subDir,currentDepth+1,mode,false,recursive,true,apiPathPrefix,collisionContext,moduleID,sourceFolder,cacheBust);continue}await this.processFiles(targetApi,subDir.children.files,{name:subDirName,path:subDir.path,children:subDir.children},currentDepth+1,mode,false,recursive,false,apiPathPrefix,collisionContext,moduleID,sourceFolder,cacheBust)}}else{for(const subDir of directory.children.directories){const subDirName=this.slothlet.helpers.sanitize.sanitizePropertyName(subDir.name);const lazy_currentCategoryName=apiPathPrefix?apiPathPrefix.split(".").pop():categoryName;if(subDirName===lazy_currentCategoryName&&lazy_currentCategoryName!==null&&!populateDirectly){await this.processFiles(targetApi,subDir.children.files,subDir,currentDepth+1,"eager",false,true,true,apiPathPrefix,collisionContext,moduleID,sourceFolder,cacheBust);continue}const apiPath=categoryName?`${categoryName}.${subDirName}`:apiPathPrefix?`${apiPathPrefix}.${subDirName}`:subDirName;if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_CREATING_LAZY_SUBDIRECTORY",apiPath,fileCount:subDir.children.files.length})}const collisionConfig=this.slothlet.config.api?.collision;const modes_initialCollisionMode=collisionModeOverride||(collisionContext==="initial"?collisionConfig?.initial:collisionConfig?.api)||"replace";let modes_fileFolderImpl=null;const modes_lazyExisting=targetApi[subDirName];if(modes_initialCollisionMode!=="replace"&&resolveWrapper(modes_lazyExisting)){const modes_lazyExistingW=resolveWrapper(modes_lazyExisting);const existImpl=modes_lazyExistingW.__impl;if(existImpl&&typeof existImpl==="object"&&!Array.isArray(existImpl)){modes_fileFolderImpl={...existImpl}}const existChildKeys=Object.keys(modes_lazyExistingW).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));for(const ck of existChildKeys){if(!modes_fileFolderImpl)modes_fileFolderImpl={};if(!(ck in modes_fileFolderImpl)){modes_fileFolderImpl[ck]=modes_lazyExistingW[ck]}}}this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,subDirName,this.createLazySubdirectoryWrapper(subDir,apiPath,moduleID,sourceFolder,cacheBust,modes_fileFolderImpl,modes_initialCollisionMode),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}}}if(isRoot&&rootContributors.length>0){if(rootContributors.length===1){const{moduleName,file,defaultFunc}=rootContributors[0];rootDefaultFunction=defaultFunc;if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{message:await t("DEBUG_MODE_ROOT_CONTRIBUTOR",{mode,functionName:defaultFunc.name||"anonymous"})})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:moduleName,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}else{new this.SlothletWarning("WARNING_MULTIPLE_ROOT_CONTRIBUTORS",{rootContributors:rootContributors.map(rc=>rc.moduleName).join(", "),firstContributor:rootContributors[0].moduleName});for(const{moduleName,file,defaultFunc}of rootContributors){if(shouldWrap){const wrapper=new UnifiedWrapper(this.slothlet,{mode:effectiveMode,apiPath:buildApiPath(moduleName),initialImpl:this.slothlet.helpers.modesUtils.cloneWrapperImpl(defaultFunc,mode),materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:file.path,moduleID:moduleID||file.moduleID,sourceFolder});this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,moduleName,wrapper.createProxy(),{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}else{this.slothlet.builders.apiAssignment.assignToApiPath(targetApi,moduleName,defaultFunc,{useCollisionDetection:true,config:this.slothlet.config,collisionContext})}if(this.slothlet.handlers.ownership){this.slothlet.handlers.ownership.register({moduleID:moduleID||file.moduleID,apiPath:moduleName,source:"core",collisionMode:this.slothlet.helpers.modesUtils.getOwnershipCollisionMode(this.slothlet.config,collisionContext),filePath:file.path})}}}}return rootDefaultFunction}createLazySubdirectoryWrapper(dir,apiPath,moduleID=null,sourceFolder=null,cacheBust=null,fileFolderCollisionImpl=null,collisionMode="merge"){const lazy_materializeFunc=this.slothlet.modes.lazy.createNamedMaterializeFunc(apiPath,async()=>{if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_MATERIALIZE_FUNCTION_STARTING",dir:dir.name,fileCount:dir.children.files?.length||0})}const categoryName=this.slothlet.helpers.sanitize.sanitizePropertyName(dir.name);const materialized={};const actualSourceFolder=sourceFolder?`${sourceFolder}/${dir.name}`.replace(/\\/g,"/"):`${this.slothlet.config?.dir}/${dir.name}`.replace(/\\/g,"/");const parentPrefix=apiPath.includes(".")?apiPath.split(".").slice(0,-1).join("."):"";const subDirs=dir.children.directories||[];if(dir.children.files.length===1&&subDirs.length===0){const file=dir.children.files[0];const moduleName=this.slothlet.helpers.sanitize.sanitizePropertyName(file.name);const genericFilenames=["singlefile","index","main","default"];const isGeneric=genericFilenames.includes(moduleName.toLowerCase());const filenameMatchesFolder=moduleName===categoryName;if(isGeneric||filenameMatchesFolder){const mod=await this.slothlet.processors.loader.loadModule(file.path,this.slothlet.instanceID,moduleID,cacheBust);const exports=this.slothlet.processors.loader.extractExports(mod);const moduleKeys=Object.keys(exports).filter(k=>k!=="default");const analysis={hasDefault:exports.default!==void 0,hasNamed:moduleKeys.length>0,defaultExportType:exports.default?typeof exports.default:null};const modContent=exports.default!==void 0?exports.default:exports;const categoryDecision=await this.slothlet.processors.flatten.buildCategoryDecisions({categoryName,mod:modContent,moduleName,fileBaseName:file.name,analysis,moduleKeys,currentDepth:apiPath.split(".").length,moduleFiles:dir.children.files,t});if(categoryDecision.shouldFlatten){let implToWrap;if(categoryDecision.flattenType==="addapi-metadata-default"){implToWrap=exports.default;for(const key of moduleKeys){if(key!=="default"){implToWrap[key]=exports[key]}}}else if(moduleName===categoryName&&moduleKeys.includes(categoryName)){implToWrap=exports[categoryName]}else if(exports.default!==void 0){implToWrap=exports.default;if(moduleKeys.length>0&&(typeof implToWrap==="function"||typeof implToWrap==="object"&&implToWrap!==null)){const collisionMode2=this.slothlet.config?.collision?.initial||"merge";for(const key of moduleKeys){if(!this.slothlet.processors.flatten.shouldAttachNamedExport(key,exports[key],implToWrap,exports.default)){continue}const hasExisting=Object.prototype.hasOwnProperty.call(implToWrap,key);if(hasExisting){if(collisionMode2==="merge"||collisionMode2==="skip"){continue}else if(collisionMode2==="error"){throw new this.slothlet.SlothletError("COLLISION_DEFAULT_EXPORT_ERROR",{key,apiPath},null,{validationError:true})}else if(collisionMode2==="warn"){new this.slothlet.SlothletWarning("WARNING_COLLISION_DEFAULT_EXPORT_OVERWRITE",{key,apiPath})}}implToWrap[key]=exports[key]}}}else{implToWrap=modContent}if(implToWrap&&typeof implToWrap==="object"&&this.slothlet.handlers?.lifecycle){for(const key of Object.keys(implToWrap)){const value=implToWrap[key];if(typeof value==="function"){this.slothlet.handlers.lifecycle.emit("impl:created",{apiPath:`${apiPath}.${key}`,impl:value,source:"lazy-materialization",moduleID,filePath:file.path,sourceFolder:sourceFolder||this.slothlet.config?.dir})}}}if(implToWrap&&typeof implToWrap==="object"){const childPaths={};for(const key of Object.keys(implToWrap)){if(typeof key!=="symbol"&&key!=="__childFilePaths"&&key!=="__filePath"){childPaths[key]=file.path}}implToWrap.__childFilePaths=childPaths}if(fileFolderCollisionImpl&&typeof implToWrap==="object"&&implToWrap!==null){for(const[k,v]of Object.entries(fileFolderCollisionImpl)){if(!(k in implToWrap)){implToWrap[k]=v}}}else if(fileFolderCollisionImpl&&typeof implToWrap==="function"){for(const[k,v]of Object.entries(fileFolderCollisionImpl)){if(implToWrap[k]===void 0){implToWrap[k]=v}}}return implToWrap}}}await this.processFiles(materialized,dir.children.files,{name:dir.name,children:dir.children},0,"eager",false,false,true,parentPrefix,"initial",moduleID,actualSourceFolder,cacheBust,collisionMode);if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_MATERIALIZE_FUNCTION_RETURNING_IMPL",dir:dir.name,keys:Object.keys(materialized)})}if(fileFolderCollisionImpl){for(const[k,v]of Object.entries(fileFolderCollisionImpl)){if(!(k in materialized)){materialized[k]=v}}}const materializedKeys=Object.keys(materialized);const _hasCategoryFile=dir.children.files.some(f=>this.slothlet.helpers.sanitize.sanitizePropertyName(f.name)===categoryName);if(_hasCategoryFile&&materializedKeys.includes(categoryName)&&materializedKeys.length>1){if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FOLDER_PATTERN_MATCH",dir:dir.name,categoryName,keys:materializedKeys})}const mainValue=materialized[categoryName];for(const key of materializedKeys){if(key!==categoryName){if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FOLDER_PATTERN_ATTACH_PROPERTY",categoryName,propKey:key,valueType:typeof materialized[key]})}mainValue[key]=materialized[key]}}if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{key:"DEBUG_MODE_FOLDER_PATTERN_RETURN",categoryName,keys:Object.keys(mainValue).filter(k=>!k.startsWith("__"))})}return mainValue}if(materializedKeys.length===1&&materializedKeys[0]===categoryName){const nestedValue=materialized[categoryName];if(nestedValue&&resolveWrapper(nestedValue)!==null){const attachedKeys=Object.keys(nestedValue).filter(key=>key!=="____slothletInternal");if(attachedKeys.length>0){return nestedValue}return nestedValue.__impl??nestedValue}else{return nestedValue}}return materialized});const wrapper=new UnifiedWrapper(this.slothlet,{mode:"lazy",apiPath,materializeFunc:lazy_materializeFunc,materializeOnCreate:this.slothlet.config.backgroundMaterialize,filePath:dir.path,moduleID,sourceFolder});if(collisionMode){wrapper.____slothletInternal.state.collisionMode=collisionMode}const shouldPrePopulate=collisionMode==="merge"||collisionMode==="warn";if(fileFolderCollisionImpl&&shouldPrePopulate){for(const[k,v]of Object.entries(fileFolderCollisionImpl)){if(typeof k==="string"&&!k.startsWith("_")&&!k.startsWith("__")){Object.defineProperty(wrapper,k,{value:v,writable:false,enumerable:true,configurable:true})}}}return wrapper.createProxy()}async applyRootContributor(api,rootFunction,mode){if(rootFunction){Object.assign(rootFunction,api);if(this.slothlet.config.debug?.modes){this.slothlet.debug("modes",{message:await t("DEBUG_MODE_ROOT_CONTRIBUTOR_APPLIED",{mode,properties:Object.keys(api).length})})}return rootFunction}return api}}export{ModesProcessor};
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import fs from"node:fs/promises";import path from"node:path";import{translate}from"@cldmv/slothlet/i18n";import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{UnifiedWrapper,resolveWrapper}from"@cldmv/slothlet/handlers/unified-wrapper";class ApiManager extends ComponentBase{static slothletProperty="apiManager";constructor(slothlet){super(slothlet);this.state={addHistory:[],initialConfig:slothlet?.config||null,operationHistory:[]}}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);try{const stats=await fs.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 fs.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"){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 if(collisionMode==="merge-replace"){for(const key of nextChildKeys){const childValue=nextWrapper[key];const isInternal=typeof key==="string"&&(key.startsWith("_")||key.startsWith("__"));if(!isInternal&&Object.prototype.hasOwnProperty.call(existingWrapper,key)){const existingChild=existingWrapper[key];if(this.isWrapperProxy(existingChild)&&this.isWrapperProxy(childValue)){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 if(!isInternal){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}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 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{if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARNING_HOT_RELOAD_MERGE_PRIMITIVES",{apiPath:parts.join(".")})}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}const{metadata={},...restOptions}=options;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{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 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});if(this.slothlet.handlers.apiCacheManager){this.slothlet.handlers.apiCacheManager.set(moduleID,{endpoint:effectivePath,moduleID,api:newApi,folderPath:resolvedFolderPath,mode:this.____config.mode,sanitizeOptions:this.____config.sanitize||{},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&&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;if(parts.length===0){for(const key of Object.keys(newApi)){const result1=await this.setValueAtPath(this.slothlet.api,[key],newApi[key],{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});const result2=await this.setValueAtPath(this.slothlet.boundApi,[key],newApi[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)=>{if(!obj||typeof obj!=="object"||depth>10)return;if(obj.__isVersionDispatcher===true)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)}}for(const key of Object.keys(obj)){if(key!=="____slothletInternal"){collectPendingMaterializations(obj[key],depth+1)}}};if(effectiveParts.length===0){for(const key of Object.keys(newApi)){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)}}}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=null;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 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=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){const rootSegment=pathsToDelete[0].split(".")[0];if(rootSegment in this.slothlet.api){delete this.slothlet.api[rootSegment]}if(rootSegment in this.slothlet.boundApi){delete this.slothlet.boundApi[rootSegment]}}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)}}}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){const rootSegment=pathsToDelete[0].split(".")[0];this.state.operationHistory.push({type:"remove",apiPath:rootSegment})}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}if(typeof implForReload==="function"){const extracted={};for(const k of Object.keys(implForReload)){extracted[k]=implForReload[k]}implForReload=extracted}const wrapper=resolveWrapper(existingAtKey);const originalCollisionMode=wrapper?wrapper.____slothletInternal.state.collisionMode:null;if(forceReplace&&wrapper){wrapper.____slothletInternal.state.collisionMode="replace"}if(wrapper&&originalCollisionMode!==null){wrapper.____slothletInternal.state.collisionMode=originalCollisionMode}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 if(typeof freshApi==="function"){implForReload={};for(const key of Object.keys(freshApi)){implForReload[key]=freshApi[key]}}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"@cldmv/slothlet/factories/component-base";import{UnifiedWrapper,resolveWrapper}from"@cldmv/slothlet/handlers/unified-wrapper";const IS_NODE=typeof process!=="undefined"&&Boolean(process.versions?.node);const[fs,path]=IS_NODE?await Promise.all([import("node:fs/promises").then(m=>m.default),import("node:path").then(m=>m.default)]):[null,null];class ApiManager extends ComponentBase{static slothletProperty="apiManager";constructor(slothlet){super(slothlet);this.state={addHistory:[],initialConfig:slothlet?.config||null,operationHistory:[]}}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 fs.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 fs.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"){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 if(collisionMode==="merge-replace"){for(const key of nextChildKeys){const childValue=nextWrapper[key];const isInternal=typeof key==="string"&&(key.startsWith("_")||key.startsWith("__"));if(!isInternal&&Object.prototype.hasOwnProperty.call(existingWrapper,key)){const existingChild=existingWrapper[key];if(this.isWrapperProxy(existingChild)&&this.isWrapperProxy(childValue)){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 if(!isInternal){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}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 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{if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARNING_HOT_RELOAD_MERGE_PRIMITIVES",{apiPath:parts.join(".")})}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}const{metadata={},...restOptions}=options;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{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 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});if(this.slothlet.handlers.apiCacheManager){this.slothlet.handlers.apiCacheManager.set(moduleID,{endpoint:effectivePath,moduleID,api:newApi,folderPath:resolvedFolderPath,mode:this.____config.mode,sanitizeOptions:this.____config.sanitize||{},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&&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;if(parts.length===0){for(const key of Object.keys(newApi)){const result1=await this.setValueAtPath(this.slothlet.api,[key],newApi[key],{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});const result2=await this.setValueAtPath(this.slothlet.boundApi,[key],newApi[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)=>{if(!obj||typeof obj!=="object"||depth>10)return;if(obj.__isVersionDispatcher===true)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)}}for(const key of Object.keys(obj)){if(key!=="____slothletInternal"){collectPendingMaterializations(obj[key],depth+1)}}};if(effectiveParts.length===0){for(const key of Object.keys(newApi)){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)}}}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=null;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 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=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){const rootSegment=pathsToDelete[0].split(".")[0];if(rootSegment in this.slothlet.api){delete this.slothlet.api[rootSegment]}if(rootSegment in this.slothlet.boundApi){delete this.slothlet.boundApi[rootSegment]}}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)}}}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){const rootSegment=pathsToDelete[0].split(".")[0];this.state.operationHistory.push({type:"remove",apiPath:rootSegment})}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}if(typeof implForReload==="function"){const extracted={};for(const k of Object.keys(implForReload)){extracted[k]=implForReload[k]}implForReload=extracted}const wrapper=resolveWrapper(existingAtKey);const originalCollisionMode=wrapper?wrapper.____slothletInternal.state.collisionMode:null;if(forceReplace&&wrapper){wrapper.____slothletInternal.state.collisionMode="replace"}if(wrapper&&originalCollisionMode!==null){wrapper.____slothletInternal.state.collisionMode=originalCollisionMode}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 if(typeof freshApi==="function"){implForReload={};for(const key of Object.keys(freshApi)){implForReload[key]=freshApi[key]}}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"@cldmv/slothlet/factories/component-base";import{SlothletError}from"@cldmv/slothlet/errors";import{
|
|
17
|
+
import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{SlothletError}from"@cldmv/slothlet/errors";import{sortModules}from"@cldmv/slothlet/helpers/module-sort";const DEFAULT_MODULE_COLLISION_MODE="merge";class ModuleManager extends ComponentBase{static slothletProperty="moduleManager";#cache=new Map;#mounted=new Map;constructor(slothlet){super(slothlet)}async discover(options={}){await this.#emit("modules:discover-start",{scanRoot:options.scanRoot,options});const{discoverModules}=await import("@cldmv/slothlet/helpers/module-discovery");const found=await discoverModules(options);this.#cache.clear();for(const result of found){const key=`${result.packageName}@${result.manifest.version}`;this.#cache.set(key,result)}await this.#emit("modules:discover-complete",{found,stale:this.getStaleMounts()});return found}sort(results,comparator){return sortModules(results,comparator)}getDiscoveryCache(){return[...this.#cache.values()]}clearDiscoveryCache(){this.#cache.clear()}getStaleMounts(){const stale=[];for(const[key,mountResult]of this.#mounted){if(!this.#cache.has(key)){stale.push(mountResult)}}return stale}async addModule(nameOrResult,options={}){const collisionMode=options.collisionMode??DEFAULT_MODULE_COLLISION_MODE;const discoverResult=await this.#resolveToDiscoverResult(nameOrResult,options);await this.#emit("modules:mount-start",{items:[nameOrResult],options});const mountResult=await this.#mountSingle(discoverResult,collisionMode,null);await this.#emit("modules:loaded",{mounted:[mountResult]});return mountResult}async addModules(items,options={}){if(!Array.isArray(items)){throw new SlothletError("INVALID_ARGUMENT",{argument:"items",expected:"array of string|DiscoverResult",received:typeof items},null,{validationError:true})}const onFailure=options.onFailure??"throw";const concurrency=Math.max(1,Number(options.concurrency)||1);const collisionMode=options.collisionMode??DEFAULT_MODULE_COLLISION_MODE;if(!["throw","rollback","best-effort"].includes(onFailure)){throw new SlothletError("INVALID_ARGUMENT",{argument:"onFailure",expected:"throw|rollback|best-effort",received:String(onFailure)},null,{validationError:true})}const resolved=[];for(const item of items){resolved.push(await this.#resolveToDiscoverResult(item,options))}const versionConfigs=this.#buildVersionConfigs(resolved);await this.#emit("modules:mount-start",{items,options});let outcome;if(concurrency===1){outcome=await this.#mountSerial(resolved,collisionMode,onFailure,versionConfigs)}else{outcome=await this.#mountParallel(resolved,collisionMode,onFailure,concurrency,versionConfigs)}const loadedPayload=Array.isArray(outcome)?{mounted:outcome}:{mounted:outcome.mounted,failed:outcome.failed};await this.#emit("modules:loaded",loadedPayload);return outcome}async removeModule(name,opts={}){const matches=[];for(const[key,mountResult]of this.#mounted){if(mountResult.packageName!==name)continue;if(opts.version!==void 0&&mountResult.discoverResult.manifest.version!==opts.version)continue;matches.push({key,mountResult})}if(matches.length===0)return false;for(const{key,mountResult}of matches){await this.slothlet.handlers.apiManager.removeApiComponent(mountResult.moduleID);this.#mounted.delete(key)}return true}async#resolveToDiscoverResult(arg,options){if(arg&&typeof arg==="object"&&typeof arg.packageName==="string"){return arg}if(typeof arg!=="string"){throw new SlothletError("INVALID_ARGUMENT",{argument:"module name or DiscoverResult",expected:"string or DiscoverResult object",received:typeof arg},null,{validationError:true})}if(this.#cache.size===0){await this.discover(options.discover??{})}const candidates=[];for(const result of this.#cache.values()){if(result.packageName!==arg)continue;if(options.version!==void 0&&result.manifest.version!==options.version)continue;candidates.push(result)}if(candidates.length===0){throw new SlothletError("MODULE_PACKAGE_NOT_FOUND",{packageName:arg,hint:"addModule(name) requires the package to be installed under one of the scanned roots. Run discover() first, or pass a DiscoverResult object directly."},null,{validationError:true})}if(candidates.length>1){throw new SlothletError("INVALID_ARGUMENT",{argument:"version",expected:"version disambiguator (multi-version cache hit)",received:"undefined"},null,{validationError:true})}return candidates[0]}async#mountSingle(discoverResult,collisionMode,versionConfig){const mountPathDotted=discoverResult.mountPath.join(".");const effectiveMountPath=versionConfig?.version?`${versionConfig.version}.${mountPathDotted}`:mountPathDotted;if(collisionMode==="error"){const existing=this.#findExactMountAt(effectiveMountPath);if(existing){throw new SlothletError("MODULE_MOUNT_COLLISION",{packageName:discoverResult.packageName,mountPath:effectiveMountPath,existingModuleID:existing.moduleID,collisionMode},null,{validationError:true})}}const version=discoverResult.manifest.version;const underlyingCollisionMode=collisionMode==="error"?"merge":collisionMode;const moduleID=await this.slothlet.handlers.apiManager.addApiComponent({apiPath:mountPathDotted,folderPath:discoverResult.apiDir,options:{collisionMode:underlyingCollisionMode,metadata:{_module:{manifest:discoverResult.manifest}}},versionConfig:versionConfig??null});const result={packageName:discoverResult.packageName,mountPath:effectiveMountPath,moduleID,discoverResult,versionConfig:versionConfig??null};this.#mounted.set(`${discoverResult.packageName}@${version}`,result);await this.#emit("modules:mount-complete",{name:discoverResult.packageName,version,mountPath:effectiveMountPath,moduleID});return result}#buildVersionConfigs(resolved){const byName=new Map;for(const r of resolved){const list=byName.get(r.packageName)??[];list.push(r);byName.set(r.packageName,list)}const highestByName=new Map;for(const[name,group]of byName){if(group.length<2)continue;const versions=group.map(r=>r.manifest.version);highestByName.set(name,pickHighestSemver(versions))}const configs=new Map;for(let i=0;i<resolved.length;i++){const r=resolved[i];const group=byName.get(r.packageName);if(!group||group.length<2){configs.set(i,null);continue}const versionTag=semverToTag(r.manifest.version);const isDefault=r.manifest.version===highestByName.get(r.packageName);configs.set(i,{version:versionTag,default:isDefault})}return configs}async#emit(event,payload){const lifecycle=this.slothlet?.handlers?.lifecycle;if(lifecycle&&typeof lifecycle.emit==="function"){await lifecycle.emit(event,payload)}}#findExactMountAt(dottedPath){const history=this.slothlet?.handlers?.apiManager?.state?.addHistory;if(!Array.isArray(history))return null;for(let i=history.length-1;i>=0;i--){const entry=history[i];if(entry?.apiPath===dottedPath)return entry}return null}async#mountSerial(resolved,collisionMode,onFailure,versionConfigs){const mounted=[];const failed=[];for(let i=0;i<resolved.length;i++){const item=resolved[i];try{const mountResult=await this.#mountSingle(item,collisionMode,versionConfigs?.get(i)??null);mounted.push(mountResult)}catch(err){if(onFailure==="throw"){throw err}if(onFailure==="rollback"){await this.#rollback(mounted);throw err}failed.push({item,error:err})}}if(onFailure==="best-effort"){return{mounted,failed}}return mounted}async#mountParallel(resolved,collisionMode,onFailure,concurrency,versionConfigs){const mounted=[];const failed=[];let firstError=null;let nextIndex=0;const worker=async()=>{while(true){if(firstError&&onFailure!=="best-effort")return;const i=nextIndex++;if(i>=resolved.length)return;const item=resolved[i];try{const mountResult=await this.#mountSingle(item,collisionMode,versionConfigs?.get(i)??null);mounted.push(mountResult)}catch(err){if(onFailure==="best-effort"){failed.push({item,error:err})}else if(!firstError){firstError=err}}}};const workers=Array.from({length:Math.min(concurrency,resolved.length)},()=>worker());await Promise.all(workers);if(firstError){if(onFailure==="rollback"){await this.#rollback(mounted)}throw firstError}if(onFailure==="best-effort"){return{mounted,failed}}return mounted}async#rollback(mounted){for(const m of mounted){try{await this.slothlet.handlers.apiManager.removeApiComponent(m.moduleID);this.#mounted.delete(`${m.packageName}@${m.discoverResult.manifest.version}`)}catch{}}}}function semverToTag(version){const m=/^(\d+)/.exec(String(version));return m?`v${m[1]}`:`v${version}`}function pickHighestSemver(versions){if(versions.length===1)return versions[0];const segs=v=>String(v).split(/[.\-+]/).map(p=>{const n=Number.parseInt(p,10);return Number.isFinite(n)?n:0});let best=versions[0];let bestSegs=segs(best);for(let i=1;i<versions.length;i++){const candidate=versions[i];const candidateSegs=segs(candidate);const len=Math.max(candidateSegs.length,bestSegs.length);let candidateWins=false;for(let j=0;j<len;j++){const a=candidateSegs[j]??0;const b=bestSegs[j]??0;if(a>b){candidateWins=true;break}if(a<b)break}if(candidateWins){best=candidate;bestSegs=candidateSegs}}return best}export{ModuleManager};
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{SlothletError}from"@cldmv/slothlet/errors";class Config extends ComponentBase{static slothletProperty="config";normalizeCollision(collision){const validModes=["skip","warn","replace","merge","merge-replace","error"];const defaultMode="merge";if(typeof collision==="string"){const normalized=collision.toLowerCase();const mode=validModes.includes(normalized)?normalized:defaultMode;return{initial:mode,api:mode}}if(collision&&typeof collision==="object"){const validateMode=m=>{if(!m)return defaultMode;const normalized=String(m).toLowerCase();return validModes.includes(normalized)?normalized:defaultMode};return{initial:validateMode(collision.initial),api:validateMode(collision.api)}}return{initial:defaultMode,api:defaultMode}}normalizeRuntime(runtime){if(!runtime||typeof runtime!=="string"){return"async"}const normalized=runtime.toLowerCase().trim();if(normalized==="async"||normalized==="asynclocal"||normalized==="asynclocalstorage"){return"async"}if(normalized==="live"||normalized==="livebindings"||normalized==="experimental"){return"live"}return"async"}normalizeMode(mode){if(!mode||typeof mode!=="string"){return"eager"}const normalized=mode.toLowerCase().trim();if(normalized==="lazy"||normalized==="deferred"||normalized==="proxy"){return"lazy"}if(normalized==="eager"||normalized==="immediate"||normalized==="preload"){return"eager"}return"eager"}normalizeMutations(mutations){const defaults={add:true,remove:true,reload:true,permissions:true};if(!mutations||typeof mutations!=="object"){return defaults}return{add:mutations.add===false?false:true,remove:mutations.remove===false?false:true,reload:mutations.reload===false?false:true,permissions:mutations.permissions===false?false:true}}normalizeDebug(debug){if(!debug){return{builder:false,api:false,index:false,modes:false,wrapper:false,ownership:false,context:false,initialization:false,materialize:false,versioning:false,permissions:false}}if(debug===true){return{builder:true,api:true,index:true,modes:true,wrapper:true,ownership:true,context:true,initialization:true,materialize:true,versioning:true,permissions:true}}if(typeof debug==="object"){return{builder:debug.builder||false,api:debug.api||false,index:debug.index||false,modes:debug.modes||false,wrapper:debug.wrapper||false,ownership:debug.ownership||false,context:debug.context||false,initialization:debug.initialization||false,materialize:debug.materialize||false,versioning:debug.versioning||false,permissions:debug.permissions||false}}return{builder:false,api:false,index:false,modes:false,wrapper:false,ownership:false,context:false,initialization:false,materialize:false,versioning:false,permissions:false}}transformConfig(config={}){if(!config.dir){throw new this.SlothletError("INVALID_CONFIG_DIR_MISSING",{},null,{validationError:true})}const resolvedDir=this.slothlet.helpers.resolver.resolvePathFromCaller(config.dir);let mutations=null;if(config.allowMutation===false){mutations={add:false,remove:false,reload:false};if(!config.silent){new this.SlothletWarning("V2_CONFIG_UNSUPPORTED",{option:"allowMutation",replacement:"api.mutations: { add: false, remove: false, reload: false }",hint:"The allowMutation config option was part of v2. Use api.mutations for granular control in v3."})}}let collision=null;if(config.collision&&!config.api?.collision){collision=this.normalizeCollision(config.collision);if(!config.silent){new this.SlothletWarning("V2_CONFIG_UNSUPPORTED",{option:"collision",replacement:"api.collision",hint:"Root-level collision config was part of v2. Use api.collision in v3."})}}const apiConfig=config.api||{};const finalCollision=apiConfig.collision?this.normalizeCollision(apiConfig.collision):collision||this.normalizeCollision(null);const finalMutations=apiConfig.mutations?this.normalizeMutations(apiConfig.mutations):mutations||this.normalizeMutations(null);let scopeConfig=config.scope;if(scopeConfig&&typeof scopeConfig==="object"&&scopeConfig.merge){const validMergeStrategies=["shallow","deep"];if(!validMergeStrategies.includes(scopeConfig.merge)){throw new this.SlothletError("INVALID_CONFIG",{option:"scope.merge",value:scopeConfig.merge,expected:validMergeStrategies.join(" or "),hint:`Invalid merge strategy: "${scopeConfig.merge}". Must be "shallow" or "deep".`,validationError:true},null,{validationError:true})}}let hookConfig={enabled:false,pattern:"**",suppressErrors:false};if(config.hook===true||config.hook===false){hookConfig.enabled=config.hook;hookConfig.pattern=config.hook?"**":null}else if(typeof config.hook==="string"){hookConfig.enabled=true;hookConfig.pattern=config.hook}else if(config.hook&&typeof config.hook==="object"){hookConfig.enabled=config.hook.enabled!==false;hookConfig.pattern=config.hook.pattern||"**";hookConfig.suppressErrors=config.hook.suppressErrors||false}let trackingConfig={materialization:false};if(config.tracking===true||config.tracking===false){trackingConfig.materialization=config.tracking}else if(config.tracking&&typeof config.tracking==="object"){trackingConfig.materialization=config.tracking.materialization===true}if(config.backgroundMaterialize===true){trackingConfig.materialization=true}if(config.versionDispatcher!==void 0&&config.versionDispatcher!==null){if(typeof config.versionDispatcher!=="string"&&typeof config.versionDispatcher!=="function"){throw new this.SlothletError("INVALID_CONFIG_VERSION_DISPATCHER",{received:typeof config.versionDispatcher,validationError:true})}}const permissionsConfig=this.normalizePermissions(config.permissions);let i18nConfig=null;if(config.i18n&&typeof config.i18n==="object"){i18nConfig={language:typeof config.i18n.language==="string"?config.i18n.language:void 0}}return{...config,dir:resolvedDir,mode:this.normalizeMode(config.mode),runtime:this.normalizeRuntime(config.runtime),apiDepth:config.apiDepth!==void 0?config.apiDepth:Infinity,reference:config.reference||null,context:config.context||null,i18n:i18nConfig,debug:this.normalizeDebug(config.debug),diagnostics:config.diagnostics===true,hook:hookConfig,collision:finalCollision,api:{collision:finalCollision,mutations:finalMutations},scope:scopeConfig,tracking:trackingConfig,backgroundMaterialize:config.backgroundMaterialize===true,silent:config.silent===true,typescript:this.normalizeTypeScript(config.typescript),env:this.normalizeEnv(config.env),versionDispatcher:config.versionDispatcher??null,permissions:permissionsConfig}}normalizeTypeScript(typescript){if(!typescript){return null}if(typescript===true){return{enabled:true,mode:"fast"}}if(typeof typescript==="string"){const mode=typescript.toLowerCase();if(mode==="fast"||mode==="strict"){return{enabled:true,mode}}return{enabled:true,mode:"fast"}}if(typeof typescript==="object"){const mode=typescript.mode==="strict"?"strict":"fast";return{enabled:true,mode,types:typescript.types||null,target:typescript.target||"es2020",sourcemap:typescript.sourcemap||false}}return null}normalizeEnv(env){if(!env||typeof env!=="object"){return null}const include=Array.isArray(env.include)?env.include.filter(k=>typeof k==="string"):null;if(include&&include.length>0){return{include}}return null}normalizePermissions(permissions){if(!permissions||typeof permissions!=="object"){return null}let defaultPolicy;if(permissions.defaultPolicy==="deny"){defaultPolicy="deny"}else if(permissions.defaultPolicy==="allow"||permissions.defaultPolicy===void 0){defaultPolicy="allow"}else{throw new SlothletError("INVALID_CONFIG",{option:"permissions.defaultPolicy",value:permissions.defaultPolicy,expected:'"allow" or "deny"',hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}const enabled=permissions.enabled!==false;let audit;if(permissions.audit==="verbose"){audit="verbose"}else if(permissions.audit==="default"||permissions.audit===void 0){audit="default"}else if(permissions.audit===true){audit="default"}else if(permissions.audit===false){audit="default"}else{throw new SlothletError("INVALID_CONFIG",{option:"permissions.audit",value:permissions.audit,expected:'"default" or "verbose"',hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}let readGating;if(permissions.readGating===false){readGating=false}else if(permissions.readGating===true||permissions.readGating===void 0){readGating=true}else{throw new SlothletError("INVALID_CONFIG",{option:"permissions.readGating",value:permissions.readGating,expected:"boolean",hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}if(permissions.rules!==void 0&&!Array.isArray(permissions.rules)){throw new SlothletError("INVALID_CONFIG",{option:"permissions.rules",value:permissions.rules,expected:"array",hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}const rules=Array.isArray(permissions.rules)?permissions.rules:[];return{defaultPolicy,enabled,audit,readGating,rules}}}export{Config};
|
|
17
|
+
import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{SlothletError}from"@cldmv/slothlet/errors";const IS_NODE=typeof process!=="undefined"&&typeof process.versions?.node==="string";class Config extends ComponentBase{static slothletProperty="config";normalizeCollision(collision){const validModes=["skip","warn","replace","merge","merge-replace","error"];const defaultMode="merge";if(typeof collision==="string"){const normalized=collision.toLowerCase();const mode=validModes.includes(normalized)?normalized:defaultMode;return{initial:mode,api:mode}}if(collision&&typeof collision==="object"){const validateMode=m=>{if(!m)return defaultMode;const normalized=String(m).toLowerCase();return validModes.includes(normalized)?normalized:defaultMode};return{initial:validateMode(collision.initial),api:validateMode(collision.api)}}return{initial:defaultMode,api:defaultMode}}normalizeRuntime(runtime){if(!runtime||typeof runtime!=="string"){return"async"}const normalized=runtime.toLowerCase().trim();if(normalized==="async"||normalized==="asynclocal"||normalized==="asynclocalstorage"){return"async"}if(normalized==="live"||normalized==="livebindings"||normalized==="experimental"){return"live"}return"async"}normalizeMode(mode){if(!mode||typeof mode!=="string"){return"eager"}const normalized=mode.toLowerCase().trim();if(normalized==="lazy"||normalized==="deferred"||normalized==="proxy"){return"lazy"}if(normalized==="eager"||normalized==="immediate"||normalized==="preload"){return"eager"}return"eager"}normalizeMutations(mutations){const defaults={add:true,remove:true,reload:true,permissions:true};if(!mutations||typeof mutations!=="object"){return defaults}return{add:mutations.add===false?false:true,remove:mutations.remove===false?false:true,reload:mutations.reload===false?false:true,permissions:mutations.permissions===false?false:true}}normalizeDebug(debug){if(!debug){return{builder:false,api:false,index:false,modes:false,wrapper:false,ownership:false,context:false,initialization:false,materialize:false,versioning:false,permissions:false}}if(debug===true){return{builder:true,api:true,index:true,modes:true,wrapper:true,ownership:true,context:true,initialization:true,materialize:true,versioning:true,permissions:true}}if(typeof debug==="object"){return{builder:debug.builder||false,api:debug.api||false,index:debug.index||false,modes:debug.modes||false,wrapper:debug.wrapper||false,ownership:debug.ownership||false,context:debug.context||false,initialization:debug.initialization||false,materialize:debug.materialize||false,versioning:debug.versioning||false,permissions:debug.permissions||false}}return{builder:false,api:false,index:false,modes:false,wrapper:false,ownership:false,context:false,initialization:false,materialize:false,versioning:false,permissions:false}}normalizeEnvTarget(platform,hasManifest=false){if(platform==="browser")return"browser";if(platform==="node")return"node";if(hasManifest)return"browser";return IS_NODE?"node":"browser"}transformConfig(config={}){const hasManifest=config.manifest!=null;const envTarget=this.normalizeEnvTarget(config.platform,hasManifest);const rawBase=config.base??config.dir;if(config.dir!==void 0&&config.base===void 0&&!config.silent){new this.SlothletWarning("V3_CONFIG_DEPRECATED",{option:"dir",replacement:"base"})}if(!rawBase){throw new this.SlothletError("INVALID_CONFIG_DIR_MISSING",{},null,{validationError:true})}if(envTarget==="browser"){if(!config.manifest||typeof config.manifest!=="object"||Array.isArray(config.manifest)){throw new this.SlothletError("INVALID_CONFIG_BROWSER_REQUIRES_MANIFEST",{},null,{validationError:true})}if(!Array.isArray(config.manifest.files)||!Array.isArray(config.manifest.directories)){throw new this.SlothletError("INVALID_CONFIG_BROWSER_MANIFEST_INVALID",{received:typeof config.manifest},null,{validationError:true})}if(config.resolveModuleSpecifier!==void 0&&typeof config.resolveModuleSpecifier!=="function"){throw new this.SlothletError("INVALID_CONFIG_BROWSER_RESOLVE_SPECIFIER_INVALID",{received:typeof config.resolveModuleSpecifier},null,{validationError:true})}}const resolvedDir=envTarget==="browser"?rawBase:this.slothlet.helpers.resolver.resolvePathFromCaller(rawBase);let mutations=null;if(config.allowMutation===false){mutations={add:false,remove:false,reload:false};if(!config.silent){new this.SlothletWarning("V2_CONFIG_UNSUPPORTED",{option:"allowMutation",replacement:"api.mutations: { add: false, remove: false, reload: false }",hint:"The allowMutation config option was part of v2. Use api.mutations for granular control in v3."})}}let collision=null;if(config.collision&&!config.api?.collision){collision=this.normalizeCollision(config.collision);if(!config.silent){new this.SlothletWarning("V2_CONFIG_UNSUPPORTED",{option:"collision",replacement:"api.collision",hint:"Root-level collision config was part of v2. Use api.collision in v3."})}}const apiConfig=config.api||{};const finalCollision=apiConfig.collision?this.normalizeCollision(apiConfig.collision):collision||this.normalizeCollision(null);const finalMutations=apiConfig.mutations?this.normalizeMutations(apiConfig.mutations):mutations||this.normalizeMutations(null);let scopeConfig=config.scope;if(scopeConfig&&typeof scopeConfig==="object"&&scopeConfig.merge){const validMergeStrategies=["shallow","deep"];if(!validMergeStrategies.includes(scopeConfig.merge)){throw new this.SlothletError("INVALID_CONFIG",{option:"scope.merge",value:scopeConfig.merge,expected:validMergeStrategies.join(" or "),hint:`Invalid merge strategy: "${scopeConfig.merge}". Must be "shallow" or "deep".`,validationError:true},null,{validationError:true})}}let hookConfig={enabled:false,pattern:"**",suppressErrors:false};if(config.hook===true||config.hook===false){hookConfig.enabled=config.hook;hookConfig.pattern=config.hook?"**":null}else if(typeof config.hook==="string"){hookConfig.enabled=true;hookConfig.pattern=config.hook}else if(config.hook&&typeof config.hook==="object"){hookConfig.enabled=config.hook.enabled!==false;hookConfig.pattern=config.hook.pattern||"**";hookConfig.suppressErrors=config.hook.suppressErrors||false}let trackingConfig={materialization:false};if(config.tracking===true||config.tracking===false){trackingConfig.materialization=config.tracking}else if(config.tracking&&typeof config.tracking==="object"){trackingConfig.materialization=config.tracking.materialization===true}if(config.backgroundMaterialize===true){trackingConfig.materialization=true}if(config.versionDispatcher!==void 0&&config.versionDispatcher!==null){if(typeof config.versionDispatcher!=="string"&&typeof config.versionDispatcher!=="function"){throw new this.SlothletError("INVALID_CONFIG_VERSION_DISPATCHER",{received:typeof config.versionDispatcher,validationError:true})}}const permissionsConfig=this.normalizePermissions(config.permissions);const suppressFixes=this.normalizeSuppressFixes(config.suppressFixes,config.silent);let i18nConfig=null;if(config.i18n&&typeof config.i18n==="object"){i18nConfig={language:typeof config.i18n.language==="string"?config.i18n.language:void 0}}return{...config,base:resolvedDir,dir:resolvedDir,manifest:config.manifest??null,resolveModuleSpecifier:config.resolveModuleSpecifier??null,envTarget,mode:this.normalizeMode(config.mode),runtime:this.normalizeRuntime(config.runtime),apiDepth:config.apiDepth!==void 0?config.apiDepth:Infinity,reference:config.reference||null,context:config.context||null,i18n:i18nConfig,debug:this.normalizeDebug(config.debug),diagnostics:config.diagnostics===true,hook:hookConfig,collision:finalCollision,api:{collision:finalCollision,mutations:finalMutations},scope:scopeConfig,tracking:trackingConfig,backgroundMaterialize:config.backgroundMaterialize===true,silent:config.silent===true,typescript:this.normalizeTypeScript(config.typescript),env:this.normalizeEnv(config.env),versionDispatcher:config.versionDispatcher??null,permissions:permissionsConfig,suppressFixes}}normalizeSuppressFixes(suppressFixes,silent){const KNOWN_FIX_IDS=new Set(["C03_116"]);const REPO_PR_BASE="https://github.com/CLDMV/slothlet/pull/";if(!Array.isArray(suppressFixes)||suppressFixes.length===0){return new Set}const result=new Set;for(const rule of suppressFixes){if(typeof rule!=="string"||!KNOWN_FIX_IDS.has(rule)){continue}result.add(rule);if(!silent){const prNumber=rule.split("_").pop();const url=`${REPO_PR_BASE}${prNumber}`;new this.SlothletWarning("WARN_SUPPRESS_FIX_ACTIVE",{rule,url})}}return result}normalizeTypeScript(typescript){if(!typescript){return null}if(typescript===true){return{enabled:true,mode:"fast"}}if(typeof typescript==="string"){const mode=typescript.toLowerCase();if(mode==="fast"||mode==="strict"){return{enabled:true,mode}}return{enabled:true,mode:"fast"}}if(typeof typescript==="object"){const mode=typescript.mode==="strict"?"strict":"fast";return{enabled:true,mode,types:typescript.types||null,target:typescript.target||"es2020",sourcemap:typescript.sourcemap||false}}return null}normalizeEnv(env){if(!env||typeof env!=="object"){return null}const include=Array.isArray(env.include)?env.include.filter(k=>typeof k==="string"):null;if(include&&include.length>0){return{include}}return null}normalizePermissions(permissions){if(!permissions||typeof permissions!=="object"){return null}let defaultPolicy;if(permissions.defaultPolicy==="deny"){defaultPolicy="deny"}else if(permissions.defaultPolicy==="allow"||permissions.defaultPolicy===void 0){defaultPolicy="allow"}else{throw new SlothletError("INVALID_CONFIG",{option:"permissions.defaultPolicy",value:permissions.defaultPolicy,expected:'"allow" or "deny"',hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}const enabled=permissions.enabled!==false;let audit;if(permissions.audit==="verbose"){audit="verbose"}else if(permissions.audit==="default"||permissions.audit===void 0){audit="default"}else if(permissions.audit===true){audit="default"}else if(permissions.audit===false){audit="default"}else{throw new SlothletError("INVALID_CONFIG",{option:"permissions.audit",value:permissions.audit,expected:'"default" or "verbose"',hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}let readGating;if(permissions.readGating===false){readGating=false}else if(permissions.readGating===true||permissions.readGating===void 0){readGating=true}else{throw new SlothletError("INVALID_CONFIG",{option:"permissions.readGating",value:permissions.readGating,expected:"boolean",hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}if(permissions.rules!==void 0&&!Array.isArray(permissions.rules)){throw new SlothletError("INVALID_CONFIG",{option:"permissions.rules",value:permissions.rules,expected:"array",hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}const rules=Array.isArray(permissions.rules)?permissions.rules:[];return{defaultPolicy,enabled,audit,readGating,rules}}}export{Config};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from"node:fs/promises";import path from"node:path";const LOADABLE_EXTENSIONS=new Set([".mjs",".cjs",".js",".ts",".mts",".cts"]);const SKIP_PREFIXES=["__","."];function isApiFile(filename){const ext=path.extname(filename);return LOADABLE_EXTENSIONS.has(ext)}function makeFileEntry(relativePath){const fullName=path.basename(relativePath);const name=path.basename(relativePath,path.extname(relativePath));return{path:relativePath,name,fullName}}async function scanDir(absDir,rootDir){let entries;try{entries=await fs.readdir(absDir,{withFileTypes:true})}catch{return{files:[],directories:[]}}const files=[];const directories=[];for(const entry of entries){if(SKIP_PREFIXES.some(p=>entry.name.startsWith(p)))continue;if(entry.isFile()){if(isApiFile(entry.name)){const absPath=path.join(absDir,entry.name);const rel=path.relative(rootDir,absPath).replace(/\\/g,"/");files.push(makeFileEntry(rel))}}else if(entry.isDirectory()){const absSubDir=path.join(absDir,entry.name);const relDir=path.relative(rootDir,absSubDir).replace(/\\/g,"/");const children=await scanDir(absSubDir,rootDir);if(children.files.length>0||children.directories.length>0){directories.push({name:entry.name,path:relDir,children})}}}return{files,directories}}async function generateManifest(dir){if(!dir||typeof dir!=="string"){throw new Error(`generateManifest: dir must be a non-empty string, received ${typeof dir}`)}const absDir=path.resolve(dir);let stat;try{stat=await fs.stat(absDir)}catch(err){throw new Error(`generateManifest: cannot read directory "${absDir}": ${err.message}`)}if(!stat.isDirectory()){throw new Error(`generateManifest: "${absDir}" is not a directory`)}return scanDir(absDir,absDir)}export{generateManifest};
|