@cldmv/slothlet 3.12.2 → 3.13.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.
Files changed (32) hide show
  1. package/README.md +7 -6
  2. package/dist/lib/builders/api-assignment.mjs +1 -1
  3. package/dist/lib/builders/api_builder.mjs +1 -1
  4. package/dist/lib/builders/builder.mjs +1 -1
  5. package/dist/lib/builders/modes-processor.mjs +1 -1
  6. package/dist/lib/errors.mjs +1 -1
  7. package/dist/lib/handlers/api-manager.mjs +1 -1
  8. package/dist/lib/handlers/context-async.mjs +1 -1
  9. package/dist/lib/handlers/context-live.mjs +1 -1
  10. package/dist/lib/handlers/hook-manager.mjs +1 -1
  11. package/dist/lib/handlers/metadata.mjs +1 -1
  12. package/dist/lib/handlers/ownership.mjs +1 -1
  13. package/dist/lib/handlers/permission-manager.mjs +1 -1
  14. package/dist/lib/handlers/unified-wrapper.mjs +1 -1
  15. package/dist/lib/handlers/version-manager.mjs +1 -1
  16. package/dist/lib/helpers/caller-pinning.mjs +17 -0
  17. package/dist/lib/helpers/class-instance-wrapper.mjs +1 -1
  18. package/dist/lib/helpers/config.mjs +1 -1
  19. package/dist/lib/helpers/eventemitter-context.mjs +1 -1
  20. package/dist/lib/helpers/eventtarget-context.mjs +17 -0
  21. package/dist/lib/helpers/platform.mjs +1 -1
  22. package/dist/lib/helpers/scheduler-context.mjs +17 -0
  23. package/dist/lib/i18n/languages/en-us.json +21 -5
  24. package/dist/lib/modes/eager.mjs +1 -1
  25. package/dist/lib/modes/lazy.mjs +1 -1
  26. package/dist/lib/processors/loader.mjs +1 -1
  27. package/dist/lib/runtime/runtime-livebindings.mjs +1 -1
  28. package/dist/slothlet.mjs +1 -1
  29. package/package.json +6 -6
  30. package/types/stub/lib/helpers/caller-pinning.d.mts +3 -0
  31. package/types/stub/lib/helpers/eventtarget-context.d.mts +3 -0
  32. package/types/stub/lib/helpers/scheduler-context.d.mts +3 -0
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{ComponentBase}from"#factories/component-base";import{SlothletError}from"@cldmv/slothlet/errors";import{isNode as IS_NODE}from"@cldmv/slothlet/helpers/platform";function normalizeHookConfig(hook){const hookConfig={enabled:false,pattern:"**",suppressErrors:false,pin:true};if(hook===true||hook===false){hookConfig.enabled=hook;hookConfig.pattern=hook?"**":null}else if(typeof hook==="string"){hookConfig.enabled=true;hookConfig.pattern=hook}else if(hook&&typeof hook==="object"){hookConfig.enabled=hook.enabled!==false;hookConfig.pattern=hook.pattern||"**";hookConfig.suppressErrors=hook.suppressErrors||false;hookConfig.pin=hook.pin!==false}return hookConfig}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"}normalizeHook(hook){return normalizeHookConfig(hook)}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&&config.resolveModuleSpecifier!==null&&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})}}const hookConfig=this.normalizeHook(config.hook);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}}const lifecycleConfig=this.normalizeLifecycle(config.lifecycle);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,lifecycle:lifecycleConfig,debug:this.normalizeDebug(config.debug),diagnostics:config.diagnostics===true,collectLifecycleHooks:config.collectLifecycleHooks===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}normalizeLifecycle(lifecycle){if(lifecycle===void 0||lifecycle===null){return null}if(typeof lifecycle!=="object"||Array.isArray(lifecycle)){throw new this.SlothletError("INVALID_CONFIG",{option:"lifecycle",value:Array.isArray(lifecycle)?"array":typeof lifecycle,expected:"a plain object mapping event names to functions or arrays of functions",hint:"HINT_INVALID_CONFIG",validationError:true},null,{validationError:true})}const lifecycleProto=Object.getPrototypeOf(lifecycle);if(lifecycleProto!==null&&lifecycleProto!==Object.prototype){throw new this.SlothletError("INVALID_CONFIG",{option:"lifecycle",value:lifecycle?.constructor?.name?`${lifecycle.constructor.name} instance`:"non-plain object",expected:"a plain object mapping event names to functions or arrays of functions",hint:"HINT_INVALID_CONFIG",validationError:true},null,{validationError:true})}for(const[event,handler]of Object.entries(lifecycle)){const handlers=Array.isArray(handler)?handler:[handler];for(const fn of handlers){if(typeof fn!=="function"){throw new this.SlothletError("INVALID_CONFIG",{option:`lifecycle["${event}"]`,value:typeof fn,expected:"a function or an array of functions",hint:"HINT_INVALID_CONFIG",validationError:true},null,{validationError:true})}}}return lifecycle}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})}let failOpenOnAbsentCaller;if(permissions.failOpenOnAbsentCaller===true){failOpenOnAbsentCaller=true}else if(permissions.failOpenOnAbsentCaller===false||permissions.failOpenOnAbsentCaller===void 0){failOpenOnAbsentCaller=false}else{throw new SlothletError("INVALID_CONFIG",{option:"permissions.failOpenOnAbsentCaller",value:permissions.failOpenOnAbsentCaller,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,failOpenOnAbsentCaller,rules}}}export{Config,normalizeHookConfig};
17
+ import{ComponentBase}from"#factories/component-base";import{SlothletError}from"@cldmv/slothlet/errors";import{isNode as IS_NODE}from"@cldmv/slothlet/helpers/platform";function normalizeHookConfig(hook){const hookConfig={enabled:false,pattern:"**",suppressErrors:false,pin:true};if(hook===true||hook===false){hookConfig.enabled=hook;hookConfig.pattern=hook?"**":null}else if(typeof hook==="string"){hookConfig.enabled=true;hookConfig.pattern=hook}else if(hook&&typeof hook==="object"){hookConfig.enabled=hook.enabled!==false;hookConfig.pattern=hook.pattern||"**";hookConfig.suppressErrors=hook.suppressErrors||false;hookConfig.pin=hook.pin!==false}return hookConfig}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"}normalizeHook(hook){return normalizeHookConfig(hook)}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&&config.resolveModuleSpecifier!==null&&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("V3_CONFIG_DEPRECATED",{option:"allowMutation",replacement:"api.mutations: { add: false, remove: false, reload: false }"})}}let collision=null;if(config.collision&&!config.api?.collision){collision=this.normalizeCollision(config.collision);if(!config.silent){new this.SlothletWarning("V3_CONFIG_DEPRECATED",{option:"collision",replacement:"api.collision"})}}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})}}const hookConfig=this.normalizeHook(config.hook);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.import!==void 0&&config.import!==null&&typeof config.import!=="function"){throw new this.SlothletError("INVALID_CONFIG_IMPORT",{received:typeof config.import,validationError: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}}const lifecycleConfig=this.normalizeLifecycle(config.lifecycle);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,lifecycle:lifecycleConfig,debug:this.normalizeDebug(config.debug),diagnostics:config.diagnostics===true,collectLifecycleHooks:config.collectLifecycleHooks===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,import:config.import??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}normalizeLifecycle(lifecycle){if(lifecycle===void 0||lifecycle===null){return null}if(typeof lifecycle!=="object"||Array.isArray(lifecycle)){throw new this.SlothletError("INVALID_CONFIG",{option:"lifecycle",value:Array.isArray(lifecycle)?"array":typeof lifecycle,expected:"a plain object mapping event names to functions or arrays of functions",hint:"HINT_INVALID_CONFIG",validationError:true},null,{validationError:true})}const lifecycleProto=Object.getPrototypeOf(lifecycle);if(lifecycleProto!==null&&lifecycleProto!==Object.prototype){throw new this.SlothletError("INVALID_CONFIG",{option:"lifecycle",value:lifecycle?.constructor?.name?`${lifecycle.constructor.name} instance`:"non-plain object",expected:"a plain object mapping event names to functions or arrays of functions",hint:"HINT_INVALID_CONFIG",validationError:true},null,{validationError:true})}for(const[event,handler]of Object.entries(lifecycle)){const handlers=Array.isArray(handler)?handler:[handler];for(const fn of handlers){if(typeof fn!=="function"){throw new this.SlothletError("INVALID_CONFIG",{option:`lifecycle["${event}"]`,value:typeof fn,expected:"a function or an array of functions",hint:"HINT_INVALID_CONFIG",validationError:true},null,{validationError:true})}}}return lifecycle}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})}let failOpenOnAbsentCaller;if(permissions.failOpenOnAbsentCaller===true){failOpenOnAbsentCaller=true}else if(permissions.failOpenOnAbsentCaller===false||permissions.failOpenOnAbsentCaller===void 0){failOpenOnAbsentCaller=false}else{throw new SlothletError("INVALID_CONFIG",{option:"permissions.failOpenOnAbsentCaller",value:permissions.failOpenOnAbsentCaller,expected:"boolean",hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}if(permissions.references!==void 0&&(typeof permissions.references!=="object"||permissions.references===null||Array.isArray(permissions.references))){throw new SlothletError("INVALID_CONFIG",{option:"permissions.references",value:permissions.references,expected:"object",hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}let capture;if(permissions.references?.capture===false){capture=false}else if(permissions.references?.capture===true||permissions.references?.capture===void 0){capture=true}else{throw new SlothletError("INVALID_CONFIG",{option:"permissions.references.capture",value:permissions.references.capture,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:[];if(permissions.private!==void 0&&(typeof permissions.private!=="object"||permissions.private===null||Array.isArray(permissions.private))){throw new SlothletError("INVALID_CONFIG",{option:"permissions.private",value:permissions.private,expected:"object",hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}let privateHost;if(permissions.private?.host==="allow"){privateHost="allow"}else if(permissions.private?.host==="deny"||permissions.private?.host===void 0){privateHost="deny"}else{throw new SlothletError("INVALID_CONFIG",{option:"permissions.private.host",value:permissions.private.host,expected:'"deny" or "allow"',hint:"HINT_INVALID_CONFIG"},null,{validationError:true})}return{defaultPolicy,enabled,audit,readGating,failOpenOnAbsentCaller,references:{capture},private:{host:privateHost},rules}}}export{Config,normalizeHookConfig};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{EventEmitter,AsyncResource}from"@cldmv/slothlet/helpers/platform";let isInApiContext=null;function setApiContextChecker(checker){isInApiContext=checker}const originalMethods=new Map;const wrappedListeners=new Map;const trackedEmitters=new Set;let isPatchingEnabled=false;function runtime_wrapEventListener(listener){const resource=new AsyncResource("slothlet-event-listener");const runtime_wrappedListener=function(...args){return resource.runInAsyncScope(()=>{return listener.apply(this,args)},this)};runtime_wrappedListener._slothletOriginal=listener;runtime_wrappedListener._slothletResource=resource;return runtime_wrappedListener}function runtime_getListenerTracking(emitter){let emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking){emitterTracking=new Map;wrappedListeners.set(emitter,emitterTracking)}return emitterTracking}function runtime_trackListener(emitter,event,originalListener,wrappedListener){const emitterTracking=runtime_getListenerTracking(emitter);let eventTracking=emitterTracking.get(event);if(!eventTracking){eventTracking=new Map;emitterTracking.set(event,eventTracking)}let wrappers=eventTracking.get(originalListener);if(!wrappers){wrappers=[];eventTracking.set(originalListener,wrappers)}wrappers.push(wrappedListener)}function runtime_getWrappedListener(emitter,event,originalListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return void 0;const eventTracking=emitterTracking.get(event);if(!eventTracking)return void 0;const wrappers=eventTracking.get(originalListener);if(!wrappers||wrappers.length===0)return void 0;return wrappers[wrappers.length-1]}function runtime_untrackListener(emitter,event,originalListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return;const eventTracking=emitterTracking.get(event);if(!eventTracking)return;const wrappers=eventTracking.get(originalListener);if(!wrappers||wrappers.length===0)return;const wrappedListener=wrappers.pop();wrappedListener._slothletResource=null;if(wrappers.length===0){eventTracking.delete(originalListener)}if(eventTracking.size===0){emitterTracking.delete(event)}if(emitterTracking.size===0){wrappedListeners.delete(emitter)}}function runtime_untrackSpecificWrapper(emitter,event,originalListener,wrappedListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return;const eventTracking=emitterTracking.get(event);if(!eventTracking)return;const wrappers=eventTracking.get(originalListener);if(!wrappers||wrappers.length===0)return;const idx=wrappers.indexOf(wrappedListener);if(idx===-1)return;wrappers.splice(idx,1);wrappedListener._slothletResource=null;if(wrappers.length===0){eventTracking.delete(originalListener)}if(eventTracking.size===0){emitterTracking.delete(event)}if(emitterTracking.size===0){wrappedListeners.delete(emitter)}}function runtime_shouldWrapListener(listener){if(typeof listener!=="function")return false;if(listener._slothletOriginal)return false;return true}function runtime_maybeTrackEmitter(emitter){if(isInApiContext&&isInApiContext()){trackedEmitters.add(emitter)}}function runtime_patchOn(){const original=EventEmitter.prototype.on;originalMethods.set("on",original);EventEmitter.prototype.on=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);runtime_trackListener(this,event,listener,wrapped);return original.call(this,event,wrapped)};EventEmitter.prototype.addListener=EventEmitter.prototype.on}function runtime_patchOnce(){const original=EventEmitter.prototype.once;originalMethods.set("once",original);const originalOn=originalMethods.get("on")??EventEmitter.prototype.on;const originalRemove=originalMethods.get("removeListener")??EventEmitter.prototype.removeListener;EventEmitter.prototype.once=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);const self=this;const runtime_onceWrapper=function(...args){originalRemove.call(self,event,runtime_onceWrapper);runtime_untrackSpecificWrapper(self,event,listener,runtime_onceWrapper);return wrapped.apply(this,args)};runtime_onceWrapper._slothletOriginal=listener;runtime_onceWrapper._slothletResource=wrapped._slothletResource;runtime_onceWrapper.listener=listener;runtime_trackListener(this,event,listener,runtime_onceWrapper);return originalOn.call(this,event,runtime_onceWrapper)}}function runtime_patchPrependListener(){const original=EventEmitter.prototype.prependListener;originalMethods.set("prependListener",original);EventEmitter.prototype.prependListener=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);runtime_trackListener(this,event,listener,wrapped);return original.call(this,event,wrapped)}}function runtime_patchPrependOnceListener(){const original=EventEmitter.prototype.prependOnceListener;originalMethods.set("prependOnceListener",original);const originalPrepend=originalMethods.get("prependListener")??EventEmitter.prototype.prependListener;const originalRemove=originalMethods.get("removeListener")??EventEmitter.prototype.removeListener;EventEmitter.prototype.prependOnceListener=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);const self=this;const runtime_onceWrapper=function(...args){originalRemove.call(self,event,runtime_onceWrapper);runtime_untrackSpecificWrapper(self,event,listener,runtime_onceWrapper);return wrapped.apply(this,args)};runtime_onceWrapper._slothletOriginal=listener;runtime_onceWrapper._slothletResource=wrapped._slothletResource;runtime_onceWrapper.listener=listener;runtime_trackListener(this,event,listener,runtime_onceWrapper);return originalPrepend.call(this,event,runtime_onceWrapper)}}function runtime_patchRemoveListener(){const original=EventEmitter.prototype.removeListener;originalMethods.set("removeListener",original);EventEmitter.prototype.removeListener=function(event,listener){const wrapped=runtime_getWrappedListener(this,event,listener);if(wrapped){const result=original.call(this,event,wrapped);runtime_untrackListener(this,event,listener);return result}return original.call(this,event,listener)};EventEmitter.prototype.off=EventEmitter.prototype.removeListener}function runtime_patchRemoveAllListeners(){const original=EventEmitter.prototype.removeAllListeners;originalMethods.set("removeAllListeners",original);EventEmitter.prototype.removeAllListeners=function(event){const emitterTracking=wrappedListeners.get(this);if(emitterTracking){if(event===void 0){for(const[____evt,eventTracking]of emitterTracking.entries()){for(const wrappers of eventTracking.values()){for(const wrappedListener of wrappers){wrappedListener._slothletResource=null}}}wrappedListeners.delete(this)}else{const eventTracking=emitterTracking.get(event);if(eventTracking){for(const wrappers of eventTracking.values()){for(const wrappedListener of wrappers){wrappedListener._slothletResource=null}}emitterTracking.delete(event);if(emitterTracking.size===0){wrappedListeners.delete(this)}}}}return original.call(this,event)}}function enableEventEmitterPatching(){if(!EventEmitter)return;if(isPatchingEnabled){return}runtime_patchOn();runtime_patchOnce();runtime_patchPrependListener();runtime_patchPrependOnceListener();runtime_patchRemoveListener();runtime_patchRemoveAllListeners();isPatchingEnabled=true}function disableEventEmitterPatching(){if(!EventEmitter)return;if(!isPatchingEnabled){return}for(const[methodName,originalMethod]of originalMethods.entries()){EventEmitter.prototype[methodName]=originalMethod;if(methodName==="on"){EventEmitter.prototype.addListener=originalMethod}else if(methodName==="removeListener"){EventEmitter.prototype.off=originalMethod}}originalMethods.clear();isPatchingEnabled=false}function cleanupEventEmitterResources(){if(!EventEmitter)return;for(const emitter of trackedEmitters){try{emitter.removeAllListeners()}catch(____error){}}trackedEmitters.clear();wrappedListeners.clear()}export{cleanupEventEmitterResources,disableEventEmitterPatching,enableEventEmitterPatching,setApiContextChecker};
17
+ import{EventEmitter,AsyncResource}from"@cldmv/slothlet/helpers/platform";import{pinToCurrentCaller}from"@cldmv/slothlet/helpers/caller-pinning";let isInApiContext=null;function setApiContextChecker(checker){isInApiContext=checker}const originalMethods=new Map;const wrappedListeners=new Map;const trackedEmitters=new Set;let isPatchingEnabled=false;function runtime_wrapEventListener(listener){const resource=AsyncResource?new AsyncResource("slothlet-event-listener"):null;const bound=pinToCurrentCaller(listener);const runtime_wrappedListener=function(...args){if(!resource)return bound.apply(this,args);return resource.runInAsyncScope(()=>{return bound.apply(this,args)},this)};runtime_wrappedListener._slothletOriginal=listener;runtime_wrappedListener._slothletResource=resource;return runtime_wrappedListener}function runtime_getListenerTracking(emitter){let emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking){emitterTracking=new Map;wrappedListeners.set(emitter,emitterTracking)}return emitterTracking}function runtime_trackListener(emitter,event,originalListener,wrappedListener){const emitterTracking=runtime_getListenerTracking(emitter);let eventTracking=emitterTracking.get(event);if(!eventTracking){eventTracking=new Map;emitterTracking.set(event,eventTracking)}let wrappers=eventTracking.get(originalListener);if(!wrappers){wrappers=[];eventTracking.set(originalListener,wrappers)}wrappers.push(wrappedListener)}function runtime_getWrappedListener(emitter,event,originalListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return void 0;const eventTracking=emitterTracking.get(event);if(!eventTracking)return void 0;const wrappers=eventTracking.get(originalListener);if(!wrappers||wrappers.length===0)return void 0;return wrappers[wrappers.length-1]}function runtime_untrackListener(emitter,event,originalListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return;const eventTracking=emitterTracking.get(event);if(!eventTracking)return;const wrappers=eventTracking.get(originalListener);if(!wrappers||wrappers.length===0)return;const wrappedListener=wrappers.pop();wrappedListener._slothletResource=null;if(wrappers.length===0){eventTracking.delete(originalListener)}if(eventTracking.size===0){emitterTracking.delete(event)}if(emitterTracking.size===0){wrappedListeners.delete(emitter)}}function runtime_untrackSpecificWrapper(emitter,event,originalListener,wrappedListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return;const eventTracking=emitterTracking.get(event);if(!eventTracking)return;const wrappers=eventTracking.get(originalListener);if(!wrappers||wrappers.length===0)return;const idx=wrappers.indexOf(wrappedListener);if(idx===-1)return;wrappers.splice(idx,1);wrappedListener._slothletResource=null;if(wrappers.length===0){eventTracking.delete(originalListener)}if(eventTracking.size===0){emitterTracking.delete(event)}if(emitterTracking.size===0){wrappedListeners.delete(emitter)}}function runtime_shouldWrapListener(listener){if(typeof listener!=="function")return false;if(listener._slothletOriginal)return false;return true}function runtime_maybeTrackEmitter(emitter){if(isInApiContext&&isInApiContext()){trackedEmitters.add(emitter)}}function runtime_patchOn(){const original=EventEmitter.prototype.on;originalMethods.set("on",original);EventEmitter.prototype.on=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);runtime_trackListener(this,event,listener,wrapped);return original.call(this,event,wrapped)};EventEmitter.prototype.addListener=EventEmitter.prototype.on}function runtime_patchOnce(){const original=EventEmitter.prototype.once;originalMethods.set("once",original);const originalOn=originalMethods.get("on")??EventEmitter.prototype.on;const originalRemove=originalMethods.get("removeListener")??EventEmitter.prototype.removeListener;EventEmitter.prototype.once=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);const self=this;const runtime_onceWrapper=function(...args){originalRemove.call(self,event,runtime_onceWrapper);runtime_untrackSpecificWrapper(self,event,listener,runtime_onceWrapper);return wrapped.apply(this,args)};runtime_onceWrapper._slothletOriginal=listener;runtime_onceWrapper._slothletResource=wrapped._slothletResource;runtime_onceWrapper.listener=listener;runtime_trackListener(this,event,listener,runtime_onceWrapper);return originalOn.call(this,event,runtime_onceWrapper)}}function runtime_patchPrependListener(){const original=EventEmitter.prototype.prependListener;originalMethods.set("prependListener",original);EventEmitter.prototype.prependListener=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);runtime_trackListener(this,event,listener,wrapped);return original.call(this,event,wrapped)}}function runtime_patchPrependOnceListener(){const original=EventEmitter.prototype.prependOnceListener;originalMethods.set("prependOnceListener",original);const originalPrepend=originalMethods.get("prependListener")??EventEmitter.prototype.prependListener;const originalRemove=originalMethods.get("removeListener")??EventEmitter.prototype.removeListener;EventEmitter.prototype.prependOnceListener=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);const self=this;const runtime_onceWrapper=function(...args){originalRemove.call(self,event,runtime_onceWrapper);runtime_untrackSpecificWrapper(self,event,listener,runtime_onceWrapper);return wrapped.apply(this,args)};runtime_onceWrapper._slothletOriginal=listener;runtime_onceWrapper._slothletResource=wrapped._slothletResource;runtime_onceWrapper.listener=listener;runtime_trackListener(this,event,listener,runtime_onceWrapper);return originalPrepend.call(this,event,runtime_onceWrapper)}}function runtime_patchRemoveListener(){const original=EventEmitter.prototype.removeListener;originalMethods.set("removeListener",original);EventEmitter.prototype.removeListener=function(event,listener){const wrapped=runtime_getWrappedListener(this,event,listener);if(wrapped){const result=original.call(this,event,wrapped);runtime_untrackListener(this,event,listener);return result}return original.call(this,event,listener)};EventEmitter.prototype.off=EventEmitter.prototype.removeListener}function runtime_patchRemoveAllListeners(){const original=EventEmitter.prototype.removeAllListeners;originalMethods.set("removeAllListeners",original);EventEmitter.prototype.removeAllListeners=function(event){const removeEverything=arguments.length===0;const emitterTracking=wrappedListeners.get(this);if(emitterTracking){if(removeEverything){for(const[____evt,eventTracking]of emitterTracking.entries()){for(const wrappers of eventTracking.values()){for(const wrappedListener of wrappers){wrappedListener._slothletResource=null}}}wrappedListeners.delete(this)}else{const eventTracking=emitterTracking.get(event);if(eventTracking){for(const wrappers of eventTracking.values()){for(const wrappedListener of wrappers){wrappedListener._slothletResource=null}}emitterTracking.delete(event);if(emitterTracking.size===0){wrappedListeners.delete(this)}}}}return removeEverything?original.call(this):original.call(this,event)}}function enableEventEmitterPatching(){if(!EventEmitter)return;if(isPatchingEnabled){return}runtime_patchOn();runtime_patchOnce();runtime_patchPrependListener();runtime_patchPrependOnceListener();runtime_patchRemoveListener();runtime_patchRemoveAllListeners();isPatchingEnabled=true}function disableEventEmitterPatching(){if(!EventEmitter)return;if(!isPatchingEnabled){return}for(const[methodName,originalMethod]of originalMethods.entries()){EventEmitter.prototype[methodName]=originalMethod;if(methodName==="on"){EventEmitter.prototype.addListener=originalMethod}else if(methodName==="removeListener"){EventEmitter.prototype.off=originalMethod}}originalMethods.clear();isPatchingEnabled=false}function cleanupEventEmitterResources(){if(!EventEmitter)return;for(const emitter of trackedEmitters){try{emitter.removeAllListeners()}catch(____error){}}trackedEmitters.clear();wrappedListeners.clear()}export{cleanupEventEmitterResources,disableEventEmitterPatching,enableEventEmitterPatching,setApiContextChecker};
@@ -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{AsyncResource}from"@cldmv/slothlet/helpers/platform";import{pinToCurrentCaller}from"@cldmv/slothlet/helpers/caller-pinning";const wrappers=new WeakMap;const originalMethods=new Map;let isPatchingEnabled=false;function runtime_trackingKey(type,options){const capture=typeof options==="boolean"?options:Boolean(options?.capture);return`${String(type)}\0${capture?"1":"0"}`}function runtime_wrapListener(listener){const resource=AsyncResource?new AsyncResource("slothlet-event-target-listener"):null;const invoke=typeof listener==="function"?pinToCurrentCaller(listener):pinToCurrentCaller(function(event){return listener.handleEvent(event)});const runtime_wrappedListener=function(event){if(!resource)return invoke.call(this,event);return resource.runInAsyncScope(()=>invoke.call(this,event),this)};runtime_wrappedListener._slothletOriginal=listener;return runtime_wrappedListener}function runtime_shouldWrap(listener){if(typeof listener==="function")return!listener._slothletOriginal;if(listener&&typeof listener.handleEvent==="function")return true;return false}function runtime_patchAdd(){const original=EventTarget.prototype.addEventListener;const patch=function(type,listener,options){if(!runtime_shouldWrap(listener))return original.call(this,type,listener,options);const key=runtime_trackingKey(type,options);let byKey=wrappers.get(this);if(!byKey){byKey=new Map;wrappers.set(this,byKey)}let byListener=byKey.get(key);if(!byListener){byListener=new Map;byKey.set(key,byListener)}let wrapper=byListener.get(listener);if(!wrapper){wrapper=runtime_wrapListener(listener);byListener.set(listener,wrapper)}return original.call(this,type,wrapper,options)};EventTarget.prototype.addEventListener=patch;originalMethods.set("addEventListener",{original,patch})}function runtime_patchRemove(){const original=EventTarget.prototype.removeEventListener;const patch=function(type,listener,options){const byKey=wrappers.get(this);const byListener=byKey?.get(runtime_trackingKey(type,options));const wrapper=byListener?.get(listener);if(!wrapper)return original.call(this,type,listener,options);byListener.delete(listener);return original.call(this,type,wrapper,options)};EventTarget.prototype.removeEventListener=patch;originalMethods.set("removeEventListener",{original,patch})}function enableEventTargetPatching(){if(typeof EventTarget!=="function")return;if(isPatchingEnabled)return;runtime_patchAdd();runtime_patchRemove();isPatchingEnabled=true}function disableEventTargetPatching(){if(!isPatchingEnabled)return;for(const[name,{original,patch}]of originalMethods.entries()){if(EventTarget.prototype[name]===patch)EventTarget.prototype[name]=original}originalMethods.clear();isPatchingEnabled=false}export{disableEventTargetPatching,enableEventTargetPatching};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- const isNode=typeof process!=="undefined"&&Boolean(process?.versions?.node);let fs=null;let fsp=null;let path=null;let url=null;let util=null;let EventEmitter=null;let AsyncLocalStorage=null;let AsyncResource=null;let createRequire=null;if(isNode){const[fsMod,fspMod,pathMod,urlMod,utilMod,eventsMod,asyncHooksMod,moduleMod]=await Promise.all([import("node:fs"),import("node:fs/promises"),import("node:path"),import("node:url"),import("node:util"),import("node:events"),import("node:async_hooks"),import("node:module")]);fs=fsMod;fsp=fspMod;path=pathMod;url=urlMod;util=utilMod;({EventEmitter}=eventsMod);({AsyncLocalStorage,AsyncResource}=asyncHooksMod);({createRequire}=moduleMod)}else{util={inspect:Object.assign(value=>value,{custom:Symbol.for("nodejs.util.inspect.custom")}),types:{isProxy:()=>false}}}function loadJson(ref){if(!isNode){return loadJsonBrowser(ref)}try{return JSON.parse(fs.readFileSync(ref,"utf-8"))}catch{return null}}async function loadJsonBrowser(ref){try{const mod=await import(ref,{with:{type:"json"}});return mod.default??null}catch{return null}}export{AsyncLocalStorage,AsyncResource,EventEmitter,createRequire,fs,fsp,isNode,loadJson,path,url,util};
17
+ const isNode=typeof process!=="undefined"&&Boolean(process?.versions?.node);let fs=null;let fsp=null;let path=null;let url=null;let util=null;let EventEmitter=null;let AsyncLocalStorage=null;let AsyncResource=null;let createRequire=null;if(isNode){const[fsMod,fspMod,pathMod,urlMod,utilMod,eventsMod,asyncHooksMod,moduleMod]=await Promise.all([import("node:fs"),import("node:fs/promises"),import("node:path"),import("node:url"),import("node:util"),import("node:events"),import("node:async_hooks"),import("node:module")]);fs=fsMod;fsp=fspMod;path=pathMod;url=urlMod;util=utilMod;({EventEmitter}=eventsMod);({AsyncLocalStorage,AsyncResource}=asyncHooksMod);({createRequire}=moduleMod)}else{util={inspect:Object.assign(value=>value,{custom:Symbol.for("nodejs.util.inspect.custom")}),types:{isProxy:()=>false,isAsyncFunction:fn=>typeof fn==="function"&&fn.constructor?.name==="AsyncFunction"}}}function loadJson(ref){if(!isNode){return loadJsonBrowser(ref)}try{return JSON.parse(fs.readFileSync(ref,"utf-8"))}catch{return null}}async function loadJsonBrowser(ref){try{const mod=await import(ref,{with:{type:"json"}});return mod.default??null}catch{return null}}export{AsyncLocalStorage,AsyncResource,EventEmitter,createRequire,fs,fsp,isNode,loadJson,path,url,util};
@@ -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{pinToCurrentCaller}from"@cldmv/slothlet/helpers/caller-pinning";const patched=[];let isPatchingEnabled=false;function runtime_carryOwnExtras(wrapper,original){for(const key of Object.getOwnPropertySymbols(original)){const descriptor=Object.getOwnPropertyDescriptor(original,key);if(!descriptor)continue;Object.defineProperty(wrapper,key,descriptor)}for(const key of Object.keys(original)){wrapper[key]=original[key]}}function runtime_patchScheduler(host,name){const original=host?.[name];if(typeof original!=="function")return;const wrapper=function(callback,...rest){if(typeof callback!=="function")return original.call(host,callback,...rest);return original.call(host,pinToCurrentCaller(callback),...rest)};runtime_carryOwnExtras(wrapper,original);host[name]=wrapper;patched.push({host,name,original,wrapper})}function enableSchedulerPatching(){if(isPatchingEnabled)return;runtime_patchScheduler(globalThis,"setTimeout");runtime_patchScheduler(globalThis,"setInterval");runtime_patchScheduler(globalThis,"setImmediate");runtime_patchScheduler(globalThis,"queueMicrotask");runtime_patchScheduler(globalThis.process,"nextTick");isPatchingEnabled=true}function disableSchedulerPatching(){if(!isPatchingEnabled)return;for(const{host,name,original,wrapper}of patched){if(host[name]===wrapper)host[name]=original}patched.length=0;isPatchingEnabled=false}export{disableSchedulerPatching,enableSchedulerPatching};
@@ -43,6 +43,8 @@
43
43
  "HINT_COLLISION_DEFAULT_EXPORT_ERROR": "A named export conflicts with a property already present on the default export object at this path. Change the collision mode to 'merge', 'replace', 'warn', or 'skip', or rename the conflicting property.",
44
44
  "INVALID_ARGUMENT": "Invalid argument '{argument}': expected {expected}, received {received}.",
45
45
  "HINT_INVALID_ARGUMENT": "Path must be a dot-notation string (e.g., 'math.add').",
46
+ "API_LEAVES_UNKNOWN_MODULE": "No module found for '{key}'. Pass a moduleID returned by api.add(), a mount endpoint or owned api path, or '.' for the base load.",
47
+ "HINT_API_LEAVES_UNKNOWN_MODULE": "leaves() resolves its key against the ownership records: a moduleID, the path a module was mounted at, or any path that module owns. List current mounts via api.slothlet.owner.get().",
46
48
  "RUNTIME_NO_ACTIVE_CONTEXT": "No active context found. This operation requires being called from within a slothlet API function.",
47
49
  "HINT_RUNTIME_NO_ACTIVE_CONTEXT": "metadata.self() must be called from within a slothlet API function.",
48
50
  "INVALID_CONFIG_MUTATIONS_DISABLED": "Cannot perform '{operation}' - mutation is disabled. Set allowMutation: true to enable API modification operations (add/remove/reload).",
@@ -57,6 +59,10 @@
57
59
  "HINT_MODULE_LOAD_FAILED": "Check the module file for errors. Ensure it has valid JavaScript syntax and exports.",
58
60
  "MODULE_NOT_FOUND": "Module not found: {modulePath}. {hint}",
59
61
  "HINT_MODULE_NOT_FOUND": "Ensure the module exists and the path is correct. Check for typos in the import statement.",
62
+ "MODULE_RESERVED_FILENAME": "Module file '{file}' in '{dir}' is named for a framework-reserved key and cannot be loaded.",
63
+ "HINT_MODULE_RESERVED_FILENAME": "Reserved names (INTERNAL_KEYS such as _materialize, _impl) are the framework's own wrapper handles; a module file by that name would overwrite them during child adoption. Rename the file.",
64
+ "MODULE_RESERVED_EXPORT": "Module export '{name}' is named for a framework-reserved key and cannot be loaded.",
65
+ "HINT_MODULE_RESERVED_EXPORT": "Reserved names (_materialize, __impl, ...) are the framework's own wrapper handles — such an export could only ever be shadowed and unreachable. Rename the export.",
60
66
  "MODULE_IMPORT_FAILED": "Failed to import module '{modulePath}': {error}. Check that the file exists and has valid syntax.",
61
67
  "HINT_MODULE_IMPORT_FAILED": "Ensure the module file exists and can be imported. Check for syntax errors or missing dependencies.",
62
68
  "CONTEXT_ALREADY_EXISTS": "Context for instance '{instanceID}' already exists. Cannot initialize twice.",
@@ -119,8 +125,6 @@
119
125
  "HINT_V3_CONFIG_DEPRECATED": "This configuration option has been renamed for clarity. Update your code to use the new option name to ensure forward compatibility with v4.",
120
126
  "CONFIG_SCAN_HIDDEN_FOLDERS_DEPRECATED": "Configuration option '{option}' is deprecated and will be removed in v4. Hidden folders (names starting with '.' or '__') are excluded from the API by default.",
121
127
  "HINT_CONFIG_SCAN_HIDDEN_FOLDERS_DEPRECATED": "This option is a temporary backward-compatibility escape hatch. Move non-API content out of the API directory so hidden folders no longer need to be scanned.",
122
- "V2_CONFIG_UNSUPPORTED": "Configuration option '{option}' from v2 is not supported in v3. {hint} Use '{replacement}' instead.",
123
- "HINT_V2_CONFIG_UNSUPPORTED": "This configuration option from v2 is not supported in v3. Update your configuration to use the v3 equivalent for better control and clarity.",
124
128
  "WARN_SUPPRESS_FIX_ACTIVE": "Rule fix '{rule}' is suppressed via 'suppressFixes'. See {url} for details. This is a temporary override and will be removed in v4. The corrected '{rule}' behavior will be enforced permanently.",
125
129
  "HINT_WARN_SUPPRESS_FIX_ACTIVE": "Remove '{rule}' from 'suppressFixes' and update your API directory structure to accommodate the corrected behavior before upgrading to v4.",
126
130
  "DEBUG_MODE_ROOT_CONTRIBUTOR": "[{mode}] Root contributor detected: {functionName}",
@@ -300,8 +304,12 @@
300
304
  "HINT_INVALID_HOOK_SUBSET": "Subsets control execution order. Use 'before' for pre-processing, 'primary' (default) for main hooks, or 'after' for post-processing.",
301
305
  "INVALID_TYPE_PATTERN": "Invalid type pattern \"{typePattern}\". {expected}",
302
306
  "HINT_INVALID_TYPE_PATTERN": "Hook patterns use the form 'pattern:type', such as 'math.*:before' or '**:error'. The legacy 'type:pattern' form still works but is deprecated.",
303
- "HOOK_BEFORE_RETURNED_PROMISE": "Before hook '{id}' for path '{path}' returned a Promise. Before hooks must be synchronous.",
304
- "HINT_HOOK_BEFORE_RETURNED_PROMISE": "Before hooks execute synchronously before the API call. Remove async/await or Promise usage from this hook, or change it to an 'after' hook if async processing is needed.",
307
+ "HOOK_BEFORE_RETURNED_PROMISE": "Before hook '{id}' for path '{path}' returned a Promise in a synchronous pipeline.",
308
+ "HINT_HOOK_BEFORE_RETURNED_PROMISE": "A 'before' handler was not detected as asynchronous. Use a native async function, or register it with { async: true }, so the call promotes to the asynchronous pipeline.",
309
+ "HOOK_AFTER_RETURNED_PROMISE": "After hook '{id}' for path '{path}' returned a Promise in a synchronous pipeline.",
310
+ "HINT_HOOK_AFTER_RETURNED_PROMISE": "An 'after' handler was not detected as asynchronous. Use a native async function, or register it with { async: true }, so the call promotes to the asynchronous pipeline.",
311
+ "HOOK_PROMOTED_RESULT_NOT_AWAITED": "Path '{path}' returned a Promise because an asynchronous hook is attached — await the result.",
312
+ "HINT_HOOK_PROMOTED_RESULT_NOT_AWAITED": "A synchronous target promotes to asynchronous dispatch while an async before/after hook is attached. Await the call, or remove the async hook to restore synchronous returns.",
305
313
  "HOOK_BRACE_EXPANSION_MAX_DEPTH": "Brace expansion in hook pattern exceeds the maximum nesting depth of {maxDepth}.",
306
314
  "HINT_HOOK_BRACE_EXPANSION_MAX_DEPTH": "Simplify the hook path pattern to reduce brace nesting. Maximum allowed depth is {maxDepth} levels.",
307
315
  "SCOPE_DISABLED": "Per-request context isolation is disabled. Set 'scope: {}' in your slothlet configuration to enable it.",
@@ -385,6 +393,8 @@
385
393
  "UNSUPPORTED_CONTEXT_MANAGER": "Unsupported context manager: '{manager}'. Only AsyncContextManager and LiveContextManager are supported.",
386
394
  "HINT_UNSUPPORTED_CONTEXT_MANAGER": "Use AsyncContextManager for async/ALS-based context isolation or LiveContextManager for synchronous live-binding context.",
387
395
  "INVALID_CONFIG_VERSION_DISPATCHER": "config.versionDispatcher must be a string (metadata key) or a function, received {received}",
396
+ "INVALID_CONFIG_IMPORT": "Invalid 'import' option: expected a function, received {received}.",
397
+ "HINT_INVALID_CONFIG_IMPORT": "The injectable leaf importer replaces the loader's native dynamic import. Pass a function like (url) => import(url) — typically one bound to your test runner so leaf coverage attributes.",
388
398
  "INVALID_CONFIG_VERSION_TAG": "versionConfig.version must be a non-empty string, received {received}",
389
399
  "VERSION_NOT_FOUND": "Version '{version}' is not registered at path '{apiPath}'",
390
400
  "HINT_VERSION_NOT_FOUND": "Call api.slothlet.versioning.list('{apiPath}') to see which version tags are currently registered at that path.",
@@ -467,7 +477,13 @@
467
477
  "CONTEXT_KEY_OWNED": "Context key '{key}' is already owned and cannot be re-claimed by a nested scope.",
468
478
  "SCOPE_INVALID_PROTECT": "'protect' must be an array of string keys. Received: {received}.",
469
479
  "SCOPE_INVALID_OWNERS": "'owners' must be a plain object mapping keys to owner names. Received: {received}.",
470
- "PERMISSION_SEALED": "The permission control surface is sealed; policy can no longer be modified."
480
+ "PERMISSION_SEALED": "The permission control surface is sealed; policy can no longer be modified.",
481
+ "WARNING_COVERAGE_IMPORTER_UNSET": "A coverage run is active but no injectable leaf importer is configured — this externalized slothlet's leaf loads will not attribute to your coverage report.",
482
+ "HINT_WARNING_COVERAGE_IMPORTER_UNSET": "Pass the 'import' option from your test setup — a function like (url) => import(url) — so leaf loads ride your runner's module graph. See docs/TESTING.md.",
483
+ "HOOK_VERSION_UNRESOLVED": "Hook pattern '{pattern}' could not be resolved to a registered version.",
484
+ "HINT_HOOK_VERSION_UNRESOLVED": "Version dispatch needs a versioned mount covering the pattern's path (api.add(path, source, options, { version })). A dispatcher must return a registered tag, an array of them, or nothing to take the default version — or drop the versioned/versionDispatcher option to register the pattern literally.",
485
+ "HOOK_VERSION_UNKNOWN_TAG": "Version dispatch for hook pattern '{pattern}' selected '{version}', which is not a registered version.",
486
+ "HINT_HOOK_VERSION_UNKNOWN_TAG": "The dispatcher must select from the registered tags it was handed in allVersions. Register the mount first (api.add with a version), or fix the dispatcher's return value."
471
487
  },
472
488
  "metadata": {
473
489
  "code": "en-us",
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{ComponentBase}from"#factories/component-base";class EagerMode extends ComponentBase{static slothletProperty="eager";constructor(slothlet){super(slothlet)}async buildAPI({dir,apiPathPrefix="",collisionContext="initial",moduleID,apiDepth=Infinity,cacheBust=null,fileFilter=null,hidden=null,scanHiddenFolders=false,preloadedStructure=null}){const api={};const{modesProcessor}=this.slothlet.builders;const{loader}=this.slothlet.processors;if(preloadedStructure!=null&&(!Array.isArray(preloadedStructure.files)||!Array.isArray(preloadedStructure.directories))){const filesType=Array.isArray(preloadedStructure.files)?"array":typeof preloadedStructure.files;const directoriesType=Array.isArray(preloadedStructure.directories)?"array":typeof preloadedStructure.directories;const received=`{ files: ${filesType}, directories: ${directoriesType} }`;throw new this.SlothletError("INVALID_CONFIG_PRELOADED_STRUCTURE",{received},null,{validationError:true})}const structure=preloadedStructure??await loader.scanDirectory(dir,{maxDepth:apiDepth,fileFilter,hidden,scanHiddenFolders});const rootDirectory={name:".",path:dir,children:{files:structure.files,directories:structure.directories}};const rootDefaultFunction=await modesProcessor.processFiles(api,structure.files,rootDirectory,0,"eager",true,true,false,apiPathPrefix,collisionContext,moduleID,dir,cacheBust);return modesProcessor.applyRootContributor(api,rootDefaultFunction,"eager")}}export{EagerMode};
17
+ import{ComponentBase}from"#factories/component-base";class EagerMode extends ComponentBase{static slothletProperty="eager";constructor(slothlet){super(slothlet)}async buildAPI({dir,apiPathPrefix="",collisionContext="initial",moduleID,apiDepth=Infinity,cacheBust=null,fileFilter=null,hidden=null,scanHiddenFolders=false,preloadedStructure=null,rootUnwrap=false}){const api={};const{modesProcessor}=this.slothlet.builders;const{loader}=this.slothlet.processors;if(preloadedStructure!=null&&(!Array.isArray(preloadedStructure.files)||!Array.isArray(preloadedStructure.directories))){const filesType=Array.isArray(preloadedStructure.files)?"array":typeof preloadedStructure.files;const directoriesType=Array.isArray(preloadedStructure.directories)?"array":typeof preloadedStructure.directories;const received=`{ files: ${filesType}, directories: ${directoriesType} }`;throw new this.SlothletError("INVALID_CONFIG_PRELOADED_STRUCTURE",{received},null,{validationError:true})}const structure=preloadedStructure??await loader.scanDirectory(dir,{maxDepth:apiDepth,fileFilter,hidden,scanHiddenFolders});const rootDirectory={name:".",path:dir,children:{files:structure.files,directories:structure.directories}};const rootDefaultFunction=await modesProcessor.processFiles(api,structure.files,rootDirectory,0,"eager",true,true,false,apiPathPrefix,collisionContext,moduleID,dir,cacheBust,null,rootUnwrap);return modesProcessor.applyRootContributor(api,rootDefaultFunction,"eager")}}export{EagerMode};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{ComponentBase}from"#factories/component-base";class LazyMode extends ComponentBase{static slothletProperty="lazy";constructor(slothlet){super(slothlet)}createNamedMaterializeFunc(apiPath,handler){const safePath=String(apiPath||"api").replace(/\./g,"__").replace(/[^A-Za-z0-9_$]/g,"_");const normalized=/^[A-Za-z_$]/.test(safePath[0])?safePath:`_${safePath}`;const funcName=`${normalized}__lazy_materializeFunc`;return{[funcName]:async function(...args){return handler(...args)}}[funcName]}async buildAPI({dir,apiPathPrefix="",collisionContext="initial",collisionMode=null,moduleID,apiDepth=Infinity,cacheBust=null,fileFilter=null,hidden=null,scanHiddenFolders=false,preloadedStructure=null}){this.slothlet.debug("modes",{key:"DEBUG_MODE_BUILD_LAZY_API_CALLED",apiPathPrefix,collisionMode,collisionContext});const api={};const{modesProcessor}=this.slothlet.builders;const{loader}=this.slothlet.processors;if(preloadedStructure!=null&&(!Array.isArray(preloadedStructure.files)||!Array.isArray(preloadedStructure.directories))){const filesType=Array.isArray(preloadedStructure.files)?"array":typeof preloadedStructure.files;const directoriesType=Array.isArray(preloadedStructure.directories)?"array":typeof preloadedStructure.directories;const received=`{ files: ${filesType}, directories: ${directoriesType} }`;throw new this.SlothletError("INVALID_CONFIG_PRELOADED_STRUCTURE",{received},null,{validationError:true})}const structure=preloadedStructure??await loader.scanDirectory(dir,{maxDepth:apiDepth,fileFilter,hidden,scanHiddenFolders});const rootDirectory={name:".",path:dir,children:{files:structure.files,directories:structure.directories}};const rootDefaultFunction=await modesProcessor.processFiles(api,structure.files,rootDirectory,0,"lazy",true,false,false,apiPathPrefix,collisionContext,moduleID,dir,cacheBust,collisionMode);return modesProcessor.applyRootContributor(api,rootDefaultFunction,"lazy")}}export{LazyMode};
17
+ import{ComponentBase}from"#factories/component-base";class LazyMode extends ComponentBase{static slothletProperty="lazy";constructor(slothlet){super(slothlet)}createNamedMaterializeFunc(apiPath,handler){const safePath=String(apiPath||"api").replace(/\./g,"__").replace(/[^A-Za-z0-9_$]/g,"_");const normalized=/^[A-Za-z_$]/.test(safePath[0])?safePath:`_${safePath}`;const funcName=`${normalized}__lazy_materializeFunc`;return{[funcName]:async function(...args){return handler(...args)}}[funcName]}async buildAPI({dir,apiPathPrefix="",collisionContext="initial",collisionMode=null,moduleID,apiDepth=Infinity,cacheBust=null,fileFilter=null,hidden=null,scanHiddenFolders=false,preloadedStructure=null,rootUnwrap=false}){this.slothlet.debug("modes",{key:"DEBUG_MODE_BUILD_LAZY_API_CALLED",apiPathPrefix,collisionMode,collisionContext});const api={};const{modesProcessor}=this.slothlet.builders;const{loader}=this.slothlet.processors;if(preloadedStructure!=null&&(!Array.isArray(preloadedStructure.files)||!Array.isArray(preloadedStructure.directories))){const filesType=Array.isArray(preloadedStructure.files)?"array":typeof preloadedStructure.files;const directoriesType=Array.isArray(preloadedStructure.directories)?"array":typeof preloadedStructure.directories;const received=`{ files: ${filesType}, directories: ${directoriesType} }`;throw new this.SlothletError("INVALID_CONFIG_PRELOADED_STRUCTURE",{received},null,{validationError:true})}const structure=preloadedStructure??await loader.scanDirectory(dir,{maxDepth:apiDepth,fileFilter,hidden,scanHiddenFolders});const rootDirectory={name:".",path:dir,children:{files:structure.files,directories:structure.directories}};const rootDefaultFunction=await modesProcessor.processFiles(api,structure.files,rootDirectory,0,"lazy",true,false,false,apiPathPrefix,collisionContext,moduleID,dir,cacheBust,collisionMode,rootUnwrap);return modesProcessor.applyRootContributor(api,rootDefaultFunction,"lazy")}}export{LazyMode};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{ComponentBase}from"#factories/component-base";import{fsp,path,url,createRequire}from"@cldmv/slothlet/helpers/platform";import{compilePattern}from"@cldmv/slothlet/helpers/pattern-matcher";function compileHidden(globs){if(!globs)return null;const list=Array.isArray(globs)?globs:[globs];const rules=[];for(const g of list){if(typeof g!=="string"||g.length===0)continue;const negated=g.startsWith("!");const body=(negated?g.slice(1):g).replace(/\//g,".");if(body.length===0)continue;rules.push({negated,match:compilePattern(body)})}if(!rules.length)return null;return relDotted=>{let hidden=false;for(const rule of rules){if(rule.match(relDotted))hidden=!rule.negated}return hidden}}class Loader extends ComponentBase{static slothletProperty="loader";constructor(slothlet){super(slothlet)}async loadModule(filePath,instanceID,moduleID,cacheBust=null){try{if(this.slothlet.envTarget==="browser"){return this.#loadModuleBrowser(filePath)}if(filePath.endsWith(".cjs")){return this.#loadCJSIsolated(filePath)}const isTypeScript=filePath.endsWith(".ts")||filePath.endsWith(".mts");const typescriptConfig=this.slothlet.config?.typescript;let moduleUrl;if(isTypeScript&&typescriptConfig?.enabled){const mode=typescriptConfig.mode;if(mode==="strict"){if(!typescriptConfig.types?.output){throw new this.SlothletError("TS_STRICT_REQUIRES_OUTPUT",{},null,{validationError:true})}if(!typescriptConfig.types?.interfaceName){throw new this.SlothletError("TS_STRICT_REQUIRES_INTERFACE_NAME",{},null,{validationError:true})}if(!this.slothlet._typesGenerated){const{fork}=await import("child_process");const path2=await import("path");const{fileURLToPath}=await import("url");const __dirname=path2.dirname(fileURLToPath(import.meta.url));const scriptPath=path2.resolve(__dirname,"../../../tools/build/generate-types-worker.mjs");const childConfig=JSON.stringify({dir:this.slothlet.config.root||this.slothlet.config.dir,mode:"eager",typescript:{enabled:true,mode:"fast"},types:typescriptConfig.types});await new Promise((resolve,reject)=>{const child=fork(scriptPath,[],{stdio:["pipe","pipe","pipe","ipc"],env:{...process.env,SLOTHLET_CONFIG:childConfig}});let errorOutput="";child.stderr?.on("data",data=>{errorOutput+=data.toString()});child.on("message",msg=>{if(msg.type==="success"){this.slothlet._typesGenerated=true;resolve()}else if(msg.type==="error"){reject(new this.SlothletError("TS_TYPE_GENERATION_FAILED",{},{message:msg.error}))}});child.on("error",error=>{reject(new this.SlothletError("TS_TYPE_GENERATION_FORK_FAILED",{},error))});child.on("exit",code=>{if(code!==0&&!this.slothlet._typesGenerated){reject(new this.SlothletError("TS_TYPE_GENERATION_PROCESS_EXITED",{code,output:errorOutput},null,{validationError:true}))}})})}const{transformTypeScriptStrict,writeTransformedToCache,formatDiagnostics}=await import("@cldmv/slothlet/processors/typescript");const strictTransform=async tsPath=>{const result=await transformTypeScriptStrict(tsPath,{target:typescriptConfig.target,module:typescriptConfig.module,strict:typescriptConfig.strict,typeDefinitionPath:typescriptConfig.types.output,compilerOptions:typescriptConfig.compilerOptions});if(result.diagnostics&&result.diagnostics.length>0){const ts=await import("typescript");const errors=formatDiagnostics(result.diagnostics,ts.default);const error=new this.SlothletError("TS_TYPE_CHECK_ERRORS",{filePath:tsPath,errors:errors.join("\n")},null,{validationError:true});error.diagnostics=result.diagnostics;throw error}return result.code};const entryCode=await strictTransform(filePath);moduleUrl=await this.#buildTypescriptModuleUrl(writeTransformedToCache,filePath,entryCode,instanceID,moduleID,cacheBust,strictTransform)}else{const{transformTypeScript,writeTransformedToCache}=await import("@cldmv/slothlet/processors/typescript");const transformOptions={target:typescriptConfig.target,sourcemap:typescriptConfig.sourcemap};const transformedCode=await transformTypeScript(filePath,transformOptions);const transform=tsPath=>transformTypeScript(tsPath,transformOptions);moduleUrl=await this.#buildTypescriptModuleUrl(writeTransformedToCache,filePath,transformedCode,instanceID,moduleID,cacheBust,transform)}}else{const fileUrl=url.pathToFileURL(filePath).href;moduleUrl=`${fileUrl}?slothlet_instance=${instanceID}`;if(moduleID){moduleUrl+=`&module=${moduleID}`}if(cacheBust){moduleUrl+=`&_reload=${cacheBust}`}}const module=await import(moduleUrl);return module}catch(error){throw new this.SlothletError("MODULE_IMPORT_FAILED",{modulePath:filePath},error)}}#loadCJSIsolated(filePath){const requireFn=createRequire(filePath);const resolved=requireFn.resolve(filePath);delete requireFn.cache[resolved];const exports=requireFn(resolved);delete requireFn.cache[resolved];const namespace={default:exports};if(exports!==null&&typeof exports==="object"){for(const key of Object.keys(exports)){if(key!=="default"){namespace[key]=exports[key]}}}return namespace}async#buildTypescriptModuleUrl(writeTransformedToCache,filePath,code,instanceID,moduleID,cacheBust,transform){const{url:url2,cacheDir}=await writeTransformedToCache(filePath,code,instanceID,transform);(this.slothlet._typescriptCacheDirs??=new Set).add(cacheDir);let moduleUrl=`${url2}?slothlet_instance=${instanceID}`;if(moduleID){moduleUrl+=`&module=${moduleID}`}if(cacheBust){moduleUrl+=`&_reload=${cacheBust}`}return moduleUrl}async scanDirectory(dir,options={}){if(this.slothlet.envTarget==="browser"){return this.#scanDirectoryBrowser(dir,options)}const typescriptConfig=this.slothlet.config?.typescript;const defaultExtensions=[".mjs",".cjs",".js"];const typescriptExtensions=typescriptConfig?.enabled?[".ts",".mts"]:[];const allExtensions=[...defaultExtensions,...typescriptExtensions];const{recursive=true,extensions=allExtensions,isRootScan=true,currentDepth=0,maxDepth=Infinity,fileFilter=null,hidden=null,scanHiddenFolders=false,rootDir=dir}=options;const hiddenMatcher=typeof hidden==="function"?hidden:compileHidden(hidden);const apiRel=p=>path.relative(rootDir,p).split(path.sep).join(".");const hasHiddenPrefix=name=>name.startsWith(".")||name.startsWith("__");try{await fsp.stat(dir)}catch(error){throw new this.SlothletError("INVALID_DIRECTORY",{dir},error)}const structure={files:[],directories:[]};const entries=await fsp.readdir(dir,{withFileTypes:true});for(const entry of entries){const fullPath=path.join(dir,entry.name);if(entry.isDirectory()){if(fileFilter){continue}if(!scanHiddenFolders&&hasHiddenPrefix(entry.name)){continue}if(hiddenMatcher&&hiddenMatcher(apiRel(fullPath))){continue}if(recursive&&currentDepth<maxDepth){const subStructure=await this.scanDirectory(fullPath,{...options,isRootScan:false,currentDepth:currentDepth+1,hidden:hiddenMatcher,scanHiddenFolders,rootDir});if(subStructure.files.length===0&&subStructure.directories.length===0){continue}structure.directories.push({path:fullPath,name:entry.name,children:subStructure})}}else if(entry.isFile()){const ext=path.extname(entry.name);if(extensions.includes(ext)){if(hasHiddenPrefix(entry.name)){continue}if(fileFilter&&!fileFilter(entry.name)){continue}const nameWithoutExt=path.basename(entry.name,ext);if(hiddenMatcher&&hiddenMatcher(apiRel(path.join(dir,nameWithoutExt)))){continue}structure.files.push({path:fullPath,name:nameWithoutExt,fullName:entry.name,moduleID:this.slothlet.helpers.sanitize.getModuleId(fullPath,dir)})}}}if(isRootScan&&structure.files.length===0&&structure.directories.length===0&&!this.____config?.silent){new this.SlothletWarning("WARN_DIRECTORY_EMPTY",{dir,resolvedPath:path.resolve(dir)})}return structure}#scanDirectoryBrowser(dir,options={}){const manifest=this.slothlet.config?.manifest;if(!manifest){throw new this.SlothletError("INVALID_CONFIG_BROWSER_REQUIRES_MANIFEST",{},null,{validationError:true})}const configBase=(this.slothlet.config?.dir||"").replace(/\/$/,"");let relativePath=(dir||"").replace(/\/$/,"");if(configBase&&relativePath.startsWith(configBase)){relativePath=relativePath.slice(configBase.length).replace(/^\/|\/$/g,"")}const isRoot=!relativePath||relativePath==="/"||relativePath===".";const node=isRoot?manifest:this.#findManifestNode(manifest,relativePath);if(!node){throw new this.SlothletError("INVALID_DIRECTORY",{dir},null)}return this.#manifestNodeToStructure(node,dir,options)}#findManifestNode(node,targetPath){const normalised=targetPath.replace(/\\/g,"/").replace(/^\/|\/$/g,"");for(const dir of node.directories||[]){const dirPath=(dir.path||dir.name||"").replace(/\\/g,"/").replace(/^\/|\/$/g,"");if(dirPath===normalised)return dir.children||dir;const found=this.#findManifestNode(dir.children||dir,normalised);if(found)return found}return null}#manifestNodeToStructure(node,rootPath,options={}){const{fileFilter=null}=options;const ALLOWED_EXTS=[".mjs",".cjs",".js"];const structure={files:[],directories:[]};for(const file of node.files||[]){const filePath=file.path||file.relativePath||"";const fullName=file.fullName||filePath.split("/").pop();const lastDot=fullName.lastIndexOf(".");const ext=lastDot>=0?fullName.slice(lastDot):"";if(!ALLOWED_EXTS.includes(ext))continue;if(fullName.startsWith("__"))continue;if(fileFilter&&!fileFilter(fullName))continue;const name=file.name||(lastDot>=0?fullName.slice(0,lastDot):fullName);structure.files.push({path:filePath,name,fullName,moduleID:this.slothlet.helpers.sanitize.getModuleId(filePath,rootPath)})}if(!fileFilter){for(const dir of node.directories||[]){const dirPath=dir.path||dir.name||"";structure.directories.push({path:dirPath,name:dir.name||dirPath.split("/").pop(),children:this.#manifestNodeToStructure(dir.children||dir,dirPath,options)})}}return structure}async#loadModuleBrowser(filePath){const resolveModuleSpecifier=this.slothlet.config?.resolveModuleSpecifier??(({path:p})=>{const base=this.slothlet.config?.base??this.slothlet.config?.dir??"";const leadingSlash=base.startsWith("/")?"":"/";const baseUrl=/^[a-zA-Z][\w+\-.]*:\/\//.test(base)?base.endsWith("/")?base:base+"/":"file://"+leadingSlash+base.replace(/\/?$/,"/");return new URL(p,baseUrl).href});const fullName=filePath.split("/").pop();const lastDot=fullName.lastIndexOf(".");const name=lastDot>=0?fullName.slice(0,lastDot):fullName;const specifier=resolveModuleSpecifier({path:filePath,name,fullName});const module=await import(specifier);return module}extractExports(module){const exports={};if(module.default!==void 0){exports.default=module.default}for(const key of Object.keys(module)){if(key!=="default"&&key!=="module.exports"&&typeof key==="string"){exports[key]=module[key]}}if(exports.default&&typeof exports.default==="object"&&exports.default!==null&&"default"in exports.default){const rootNamedKeys=Object.keys(exports).filter(k=>k!=="default"&&k!=="module.exports");const defaultKeys=Object.keys(exports.default).filter(k=>k!=="default");const isCJSPattern=rootNamedKeys.every(k=>k in exports.default);if(isCJSPattern&&defaultKeys.length>0){exports.default=exports.default.default}}return exports}}export{Loader};
17
+ import{ComponentBase}from"#factories/component-base";import{fsp,path,url,createRequire}from"@cldmv/slothlet/helpers/platform";import{compilePattern}from"@cldmv/slothlet/helpers/pattern-matcher";import{isFrameworkReservedKey}from"#handlers/unified-wrapper";import{SlothletWarning}from"@cldmv/slothlet/errors";const RUNTIME_EXTERNALIZED=!("env"in import.meta);function warnIfCoverageWithoutImporter(config,{worker=globalThis.__vitest_worker__,externalized=RUNTIME_EXTERNALIZED}={}){if(worker?.config?.coverage?.enabled!==true)return false;if(!externalized)return false;if(typeof config?.import==="function")return false;if(config?.silent)return false;new SlothletWarning("WARNING_COVERAGE_IMPORTER_UNSET",{});return true}function compileHidden(globs){if(!globs)return null;const list=Array.isArray(globs)?globs:[globs];const rules=[];for(const g of list){if(typeof g!=="string"||g.length===0)continue;const negated=g.startsWith("!");const body=(negated?g.slice(1):g).replace(/\//g,".");if(body.length===0)continue;rules.push({negated,match:compilePattern(body)})}if(!rules.length)return null;return relDotted=>{let hidden=false;for(const rule of rules){if(rule.match(relDotted))hidden=!rule.negated}return hidden}}class Loader extends ComponentBase{static slothletProperty="loader";constructor(slothlet){super(slothlet)}async loadModule(filePath,instanceID,moduleID,cacheBust=null){try{if(this.slothlet.envTarget==="browser"){return this.#loadModuleBrowser(filePath)}if(filePath.endsWith(".cjs")){return this.#loadCJSIsolated(filePath)}const isTypeScript=filePath.endsWith(".ts")||filePath.endsWith(".mts");const typescriptConfig=this.slothlet.config?.typescript;let moduleUrl;if(isTypeScript&&typescriptConfig?.enabled){const mode=typescriptConfig.mode;if(mode==="strict"){if(!typescriptConfig.types?.output){throw new this.SlothletError("TS_STRICT_REQUIRES_OUTPUT",{},null,{validationError:true})}if(!typescriptConfig.types?.interfaceName){throw new this.SlothletError("TS_STRICT_REQUIRES_INTERFACE_NAME",{},null,{validationError:true})}if(!this.slothlet._typesGenerated){const{fork}=await import("child_process");const path2=await import("path");const{fileURLToPath}=await import("url");const __dirname=path2.dirname(fileURLToPath(import.meta.url));const scriptPath=path2.resolve(__dirname,"../../../tools/build/generate-types-worker.mjs");const childConfig=JSON.stringify({dir:this.slothlet.config.root||this.slothlet.config.dir,mode:"eager",typescript:{enabled:true,mode:"fast"},types:typescriptConfig.types});await new Promise((resolve,reject)=>{const child=fork(scriptPath,[],{stdio:["pipe","pipe","pipe","ipc"],env:{...process.env,SLOTHLET_CONFIG:childConfig}});let errorOutput="";child.stderr?.on("data",data=>{errorOutput+=data.toString()});child.on("message",msg=>{if(msg.type==="success"){this.slothlet._typesGenerated=true;resolve()}else if(msg.type==="error"){reject(new this.SlothletError("TS_TYPE_GENERATION_FAILED",{},{message:msg.error}))}});child.on("error",error=>{reject(new this.SlothletError("TS_TYPE_GENERATION_FORK_FAILED",{},error))});child.on("exit",code=>{if(code!==0&&!this.slothlet._typesGenerated){reject(new this.SlothletError("TS_TYPE_GENERATION_PROCESS_EXITED",{code,output:errorOutput},null,{validationError:true}))}})})}const{transformTypeScriptStrict,writeTransformedToCache,formatDiagnostics}=await import("@cldmv/slothlet/processors/typescript");const strictTransform=async tsPath=>{const result=await transformTypeScriptStrict(tsPath,{target:typescriptConfig.target,module:typescriptConfig.module,strict:typescriptConfig.strict,typeDefinitionPath:typescriptConfig.types.output,compilerOptions:typescriptConfig.compilerOptions});if(result.diagnostics&&result.diagnostics.length>0){const ts=await import("typescript");const errors=formatDiagnostics(result.diagnostics,ts.default);const error=new this.SlothletError("TS_TYPE_CHECK_ERRORS",{filePath:tsPath,errors:errors.join("\n")},null,{validationError:true});error.diagnostics=result.diagnostics;throw error}return result.code};const entryCode=await strictTransform(filePath);moduleUrl=await this.#buildTypescriptModuleUrl(writeTransformedToCache,filePath,entryCode,instanceID,moduleID,cacheBust,strictTransform)}else{const{transformTypeScript,writeTransformedToCache}=await import("@cldmv/slothlet/processors/typescript");const transformOptions={target:typescriptConfig.target,sourcemap:typescriptConfig.sourcemap};const transformedCode=await transformTypeScript(filePath,transformOptions);const transform=tsPath=>transformTypeScript(tsPath,transformOptions);moduleUrl=await this.#buildTypescriptModuleUrl(writeTransformedToCache,filePath,transformedCode,instanceID,moduleID,cacheBust,transform)}}else{const fileUrl=url.pathToFileURL(filePath).href;moduleUrl=`${fileUrl}?slothlet_instance=${instanceID}`;if(moduleID){moduleUrl+=`&module=${moduleID}`}if(cacheBust){moduleUrl+=`&_reload=${cacheBust}`}}const customImport=this.slothlet?.config?.import;const module=customImport?await customImport(moduleUrl):await import(moduleUrl);return module}catch(error){throw new this.SlothletError("MODULE_IMPORT_FAILED",{modulePath:filePath},error)}}#loadCJSIsolated(filePath){const requireFn=createRequire(filePath);const resolved=requireFn.resolve(filePath);delete requireFn.cache[resolved];const exports=requireFn(resolved);delete requireFn.cache[resolved];const namespace={default:exports};if(exports!==null&&typeof exports==="object"){for(const key of Object.keys(exports)){if(key!=="default"){namespace[key]=exports[key]}}}return namespace}async#buildTypescriptModuleUrl(writeTransformedToCache,filePath,code,instanceID,moduleID,cacheBust,transform){const{url:url2,cacheDir}=await writeTransformedToCache(filePath,code,instanceID,transform);(this.slothlet._typescriptCacheDirs??=new Set).add(cacheDir);let moduleUrl=`${url2}?slothlet_instance=${instanceID}`;if(moduleID){moduleUrl+=`&module=${moduleID}`}if(cacheBust){moduleUrl+=`&_reload=${cacheBust}`}return moduleUrl}async scanDirectory(dir,options={}){if(this.slothlet.envTarget==="browser"){return this.#scanDirectoryBrowser(dir,options)}const typescriptConfig=this.slothlet.config?.typescript;const defaultExtensions=[".mjs",".cjs",".js"];const typescriptExtensions=typescriptConfig?.enabled?[".ts",".mts"]:[];const allExtensions=[...defaultExtensions,...typescriptExtensions];const{recursive=true,extensions=allExtensions,isRootScan=true,currentDepth=0,maxDepth=Infinity,fileFilter=null,hidden=null,scanHiddenFolders=false,rootDir=dir}=options;const hiddenMatcher=typeof hidden==="function"?hidden:compileHidden(hidden);const apiRel=p=>path.relative(rootDir,p).split(path.sep).join(".");const hasHiddenPrefix=name=>name.startsWith(".")||name.startsWith("__");try{await fsp.stat(dir)}catch(error){throw new this.SlothletError("INVALID_DIRECTORY",{dir},error)}const structure={files:[],directories:[]};const entries=await fsp.readdir(dir,{withFileTypes:true});for(const entry of entries){const fullPath=path.join(dir,entry.name);if(entry.isDirectory()){if(fileFilter){continue}if(!scanHiddenFolders&&hasHiddenPrefix(entry.name)){continue}if(hiddenMatcher&&hiddenMatcher(apiRel(fullPath))){continue}if(recursive&&currentDepth<maxDepth){const subStructure=await this.scanDirectory(fullPath,{...options,isRootScan:false,currentDepth:currentDepth+1,hidden:hiddenMatcher,scanHiddenFolders,rootDir});if(subStructure.files.length===0&&subStructure.directories.length===0){continue}structure.directories.push({path:fullPath,name:entry.name,children:subStructure})}}else if(entry.isFile()){const ext=path.extname(entry.name);if(extensions.includes(ext)){if(hasHiddenPrefix(entry.name)){continue}if(fileFilter&&!fileFilter(entry.name)){continue}const nameWithoutExt=path.basename(entry.name,ext);if(hiddenMatcher&&hiddenMatcher(apiRel(path.join(dir,nameWithoutExt)))){continue}if(isFrameworkReservedKey(nameWithoutExt)){throw new this.SlothletError("MODULE_RESERVED_FILENAME",{file:entry.name,dir},null,{validationError:true})}structure.files.push({path:fullPath,name:nameWithoutExt,fullName:entry.name,moduleID:this.slothlet.helpers.sanitize.getModuleId(fullPath,dir)})}}}if(isRootScan&&structure.files.length===0&&structure.directories.length===0&&!this.____config?.silent){new this.SlothletWarning("WARN_DIRECTORY_EMPTY",{dir,resolvedPath:path.resolve(dir)})}return structure}#scanDirectoryBrowser(dir,options={}){const manifest=this.slothlet.config?.manifest;if(!manifest){throw new this.SlothletError("INVALID_CONFIG_BROWSER_REQUIRES_MANIFEST",{},null,{validationError:true})}const configBase=(this.slothlet.config?.dir||"").replace(/\/$/,"");let relativePath=(dir||"").replace(/\/$/,"");if(configBase&&relativePath.startsWith(configBase)){relativePath=relativePath.slice(configBase.length).replace(/^\/|\/$/g,"")}const isRoot=!relativePath||relativePath==="/"||relativePath===".";const node=isRoot?manifest:this.#findManifestNode(manifest,relativePath);if(!node){throw new this.SlothletError("INVALID_DIRECTORY",{dir},null)}return this.#manifestNodeToStructure(node,dir,options)}#findManifestNode(node,targetPath){const normalised=targetPath.replace(/\\/g,"/").replace(/^\/|\/$/g,"");for(const dir of node.directories||[]){const dirPath=(dir.path||dir.name||"").replace(/\\/g,"/").replace(/^\/|\/$/g,"");if(dirPath===normalised)return dir.children||dir;const found=this.#findManifestNode(dir.children||dir,normalised);if(found)return found}return null}#manifestNodeToStructure(node,rootPath,options={}){const{fileFilter=null}=options;const ALLOWED_EXTS=[".mjs",".cjs",".js"];const structure={files:[],directories:[]};for(const file of node.files||[]){const filePath=file.path||file.relativePath||"";const fullName=file.fullName||filePath.split("/").pop();const lastDot=fullName.lastIndexOf(".");const ext=lastDot>=0?fullName.slice(lastDot):"";if(!ALLOWED_EXTS.includes(ext))continue;if(fullName.startsWith("__"))continue;if(fileFilter&&!fileFilter(fullName))continue;const name=file.name||(lastDot>=0?fullName.slice(0,lastDot):fullName);if(isFrameworkReservedKey(name)){throw new this.SlothletError("MODULE_RESERVED_FILENAME",{file:fullName,dir:rootPath},null,{validationError:true})}structure.files.push({path:filePath,name,fullName,moduleID:this.slothlet.helpers.sanitize.getModuleId(filePath,rootPath)})}if(!fileFilter){for(const dir of node.directories||[]){const dirPath=dir.path||dir.name||"";structure.directories.push({path:dirPath,name:dir.name||dirPath.split("/").pop(),children:this.#manifestNodeToStructure(dir.children||dir,dirPath,options)})}}return structure}async#loadModuleBrowser(filePath){const resolveModuleSpecifier=this.slothlet.config?.resolveModuleSpecifier??(({path:p})=>{const base=this.slothlet.config?.base??this.slothlet.config?.dir??"";const leadingSlash=base.startsWith("/")?"":"/";const baseUrl=/^[a-zA-Z][\w+\-.]*:\/\//.test(base)?base.endsWith("/")?base:base+"/":"file://"+leadingSlash+base.replace(/\/?$/,"/");return new URL(p,baseUrl).href});const fullName=filePath.split("/").pop();const lastDot=fullName.lastIndexOf(".");const name=lastDot>=0?fullName.slice(0,lastDot):fullName;const specifier=resolveModuleSpecifier({path:filePath,name,fullName});const module=await import(specifier);return module}extractExports(module){const exports={};if(module.default!==void 0){exports.default=module.default}for(const key of Object.keys(module)){if(key!=="default"&&key!=="module.exports"&&typeof key==="string"){if(isFrameworkReservedKey(key)){throw new this.SlothletError("MODULE_RESERVED_EXPORT",{name:key},null,{validationError:true})}exports[key]=module[key]}}if(exports.default&&typeof exports.default==="object"&&exports.default!==null&&"default"in exports.default){const rootNamedKeys=Object.keys(exports).filter(k=>k!=="default"&&k!=="module.exports");const defaultKeys=Object.keys(exports.default).filter(k=>k!=="default");const isCJSPattern=rootNamedKeys.every(k=>k in exports.default);if(isCJSPattern&&defaultKeys.length>0){exports.default=exports.default.default}}return exports}}export{Loader,warnIfCoverageWithoutImporter};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{liveRuntime}from"#factories/context";import{SlothletError}from"@cldmv/slothlet/errors";import{enforceContextKeyWrite,readProtectedContextValue}from"#handlers/trusted-root";const resolveActiveContext=()=>liveRuntime.getContext();const self=new Proxy({},{get(_,prop){const ctx=liveRuntime.getContext();if(!ctx||!ctx.self){throw new SlothletError("RUNTIME_NO_ACTIVE_CONTEXT_SELF",{},null,{validationError:true})}return ctx.self[prop]},ownKeys(){const ctx=liveRuntime.getContext();if(!ctx||!ctx.self)return[];return Reflect.ownKeys(ctx.self)},has(_,prop){const ctx=liveRuntime.getContext();if(!ctx||!ctx.self)return false;return prop in ctx.self},getOwnPropertyDescriptor(_,prop){const ctx=liveRuntime.getContext();if(!ctx||!ctx.self)return void 0;const desc=Reflect.getOwnPropertyDescriptor(ctx.self,prop);if(desc){return{...desc,configurable:true}}return void 0},set(_,prop,value){const ctx=liveRuntime.getContext();if(!ctx||!ctx.self){throw new SlothletError("RUNTIME_NO_ACTIVE_CONTEXT_SELF",{},null,{validationError:true})}if(typeof prop==="symbol"){ctx.self[prop]=value;return true}if(ctx.slothlet?.boundApi&&ctx.self!==ctx.slothlet.boundApi){ctx.self[prop]=value;return true}const apiManager=ctx.slothlet?.handlers?.apiManager;if(apiManager&&typeof apiManager.setOwnedProperty==="function"){apiManager.setOwnedProperty(String(prop),value,ctx.currentWrapper??null)}else{ctx.self[prop]=value}return true}});const context=new Proxy({},{get(_,prop){const ctx=liveRuntime.getContext();if(!ctx||!ctx.context){return void 0}return readProtectedContextValue(ctx,prop,resolveActiveContext)},set(_,prop,value){const ctx=liveRuntime.getContext();if(!ctx||!ctx.context){throw new SlothletError("RUNTIME_NO_ACTIVE_CONTEXT_CONTEXT",{},null,{validationError:true})}enforceContextKeyWrite(ctx,prop);ctx.context[prop]=value;return true},ownKeys(){const ctx=liveRuntime.getContext();if(!ctx||!ctx.context)return[];return Reflect.ownKeys(ctx.context)},has(_,prop){const ctx=liveRuntime.getContext();if(!ctx||!ctx.context)return false;return prop in ctx.context},getOwnPropertyDescriptor(_,prop){const ctx=liveRuntime.getContext();if(!ctx||!ctx.context)return void 0;return Reflect.getOwnPropertyDescriptor(ctx.context,prop)}});export{context,self};
17
+ import{liveRuntime}from"#factories/context";import{SlothletError}from"@cldmv/slothlet/errors";import{enforceContextKeyWrite,readProtectedContextValue,TRUSTED_ROOT}from"#handlers/trusted-root";const resolveActiveContext=()=>liveRuntime.getContext();function runtime_resolveExecutingContext(){const ctx=liveRuntime.getContext();if(!ctx||!ctx.self)return null;const identity=liveRuntime.getCallerIdentity?.();const executing=identity?identity.currentWrapper:ctx.currentWrapper;if(executing)return ctx;if(ctx.parentInstanceID&&ctx[TRUSTED_ROOT]===true)return ctx;return null}const self=new Proxy({},{get(_,prop){const ctx=runtime_resolveExecutingContext();if(!ctx){throw new SlothletError("RUNTIME_NO_ACTIVE_CONTEXT_SELF",{},null,{validationError:true})}return ctx.self[prop]},ownKeys(){const ctx=runtime_resolveExecutingContext();if(!ctx)return[];return Reflect.ownKeys(ctx.self)},has(_,prop){const ctx=runtime_resolveExecutingContext();if(!ctx)return false;return prop in ctx.self},getOwnPropertyDescriptor(_,prop){const ctx=runtime_resolveExecutingContext();if(!ctx)return void 0;const desc=Reflect.getOwnPropertyDescriptor(ctx.self,prop);if(desc){return{...desc,configurable:true}}return void 0},set(_,prop,value){const ctx=runtime_resolveExecutingContext();if(!ctx){throw new SlothletError("RUNTIME_NO_ACTIVE_CONTEXT_SELF",{},null,{validationError:true})}if(typeof prop==="symbol"){ctx.self[prop]=value;return true}if(ctx.slothlet?.boundApi&&ctx.self!==ctx.slothlet.boundApi){ctx.self[prop]=value;return true}const apiManager=ctx.slothlet?.handlers?.apiManager;if(apiManager&&typeof apiManager.setOwnedProperty==="function"){apiManager.setOwnedProperty(String(prop),value,liveRuntime.getCallerIdentity?.()?.currentWrapper??ctx.currentWrapper??null)}else{ctx.self[prop]=value}return true}});const context=new Proxy({},{get(_,prop){const ctx=liveRuntime.getContext();if(!ctx||!ctx.context){return void 0}return readProtectedContextValue(ctx,prop,resolveActiveContext)},set(_,prop,value){const ctx=liveRuntime.getContext();if(!ctx||!ctx.context){throw new SlothletError("RUNTIME_NO_ACTIVE_CONTEXT_CONTEXT",{},null,{validationError:true})}enforceContextKeyWrite(ctx,prop);ctx.context[prop]=value;return true},ownKeys(){const ctx=liveRuntime.getContext();if(!ctx||!ctx.context)return[];return Reflect.ownKeys(ctx.context)},has(_,prop){const ctx=liveRuntime.getContext();if(!ctx||!ctx.context)return false;return prop in ctx.context},getOwnPropertyDescriptor(_,prop){const ctx=liveRuntime.getContext();if(!ctx||!ctx.context)return void 0;return Reflect.getOwnPropertyDescriptor(ctx.context,prop)}});export{context,self};
package/dist/slothlet.mjs CHANGED
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{isNode,fs,fsp,path,url,createRequire}from"@cldmv/slothlet/helpers/platform";import{getContextManager}from"#factories/context";import{SlothletError,SlothletWarning,SlothletDebug}from"@cldmv/slothlet/errors";import{registerInstance}from"#handlers/lifecycle-token";import{resolveWrapper}from"#handlers/unified-wrapper";import{TRUSTED_ROOT}from"#handlers/trusted-root";import{initI18n}from"@cldmv/slothlet/i18n";import{enableEventEmitterPatching,disableEventEmitterPatching,cleanupEventEmitterResources,setApiContextChecker}from"@cldmv/slothlet/helpers/eventemitter-context";class Slothlet{static RESERVED_ROOT_KEYS=["slothlet","shutdown","destroy"];static SKIP_PROPS=["__metadata","__type","_materialize","_impl","____slothletInternal"];constructor(){this.SlothletError=SlothletError;this.SlothletWarning=SlothletWarning;this.debugLogger=null;this.instanceID=null;this.config=null;this.envTarget="node";this.api=null;this.boundApi=null;this.contextManager=null;this.isLoaded=false;this.reference=null;this.context=null;this.envSnapshot=null;this._totalLazyCount=0;this._unmaterializedLazyCount=0;this._materializationComplete=false;this._materializationWaiters=[];this._materializationCompleteEmitted=false;this.componentCategories=["helpers","handlers","builders","processors","modes"];for(const category of this.componentCategories){this[category]={}}}async _initializeComponents(){if(this.envTarget==="browser"){await this._initializeComponentsBrowser();return}const baseDir=path.join(path.dirname(url.fileURLToPath(import.meta.url)),"lib");for(const category of this.componentCategories){const categoryDir=path.join(baseDir,category);const files=fs.readdirSync(categoryDir).filter(f=>f.endsWith(".mjs"));for(const file of files){const filePath=path.join(categoryDir,file);try{const module=await import(url.pathToFileURL(filePath).href);const classExports=Object.values(module).filter(exp=>typeof exp==="function"&&exp.slothletProperty);for(const ClassExport of classExports){const propName=ClassExport.slothletProperty;this[category][propName]=new ClassExport(this);if(this.config?.debug?.initialization){this.debug("initialization",{key:"DEBUG_MODE_COMPONENT_INITIALIZED",component:ClassExport.name,category,propertyName:propName})}}}catch(error){throw new this.SlothletError("MODULE_IMPORT_FAILED",{modulePath:filePath},error)}}}}async _initializeComponentsBrowser(){const BROWSER_COMPONENT_SPECIFIERS=["@cldmv/slothlet/builders/api-assignment","@cldmv/slothlet/builders/api_builder","@cldmv/slothlet/builders/builder","@cldmv/slothlet/builders/modes-processor","#handlers/api-cache-manager","#handlers/api-manager","#handlers/hook-manager","#handlers/lifecycle","#handlers/materialize-manager","#handlers/metadata","#handlers/module-manager","#handlers/ownership","#handlers/permission-manager","#handlers/version-manager","@cldmv/slothlet/helpers/config","@cldmv/slothlet/helpers/hint-detector","@cldmv/slothlet/helpers/modes-utils","@cldmv/slothlet/helpers/resolve-from-caller","@cldmv/slothlet/helpers/sanitize","@cldmv/slothlet/helpers/utilities","@cldmv/slothlet/modes/eager","@cldmv/slothlet/modes/lazy","@cldmv/slothlet/processors/flatten","@cldmv/slothlet/processors/loader"];for(const specifier of BROWSER_COMPONENT_SPECIFIERS){const parts=specifier.split("/");const category=specifier.startsWith("#")?parts[0].slice(1):parts[2];const module=await import(specifier);const classExports=Object.values(module).filter(exp=>typeof exp==="function"&&exp.slothletProperty);for(const ClassExport of classExports){const propName=ClassExport.slothletProperty;this[category][propName]=new ClassExport(this)}}}_setupConfigLifecycleSubscribers(){const map=this.config.lifecycle;if(!map){return}const lifecycle=this.handlers.lifecycle;for(const[event,handler]of Object.entries(map)){const handlers=Array.isArray(handler)?handler:[handler];for(const fn of handlers){lifecycle.subscribe(event,fn)}}}_setupLifecycleSubscribers(){if(!this.handlers.lifecycle){return}if(this.handlers.metadata){this.handlers.lifecycle.subscribe("impl:created",(data,token)=>{this.handlers.metadata.tagSystemMetadata(data.impl,{filePath:data.filePath,apiPath:data.apiPath,moduleID:data.moduleID,sourceFolder:data.sourceFolder},token)});this.handlers.lifecycle.subscribe("impl:changed",(data,token)=>{this.handlers.metadata.tagSystemMetadata(data.impl,{filePath:data.filePath,apiPath:data.apiPath,moduleID:data.moduleID,sourceFolder:data.sourceFolder},token)});this.handlers.lifecycle.subscribe("impl:removed",data=>{if(data.apiPath){const rootSegment=data.apiPath.split(".")[0];this.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}})}if(this.handlers.ownership){this.handlers.lifecycle.subscribe("impl:created",data=>{const collisionMode=this.config?.collision?.api||"merge";const implValue=data.wrapper?.__impl??data.impl;this.handlers.ownership.register({moduleID:data.moduleID,apiPath:data.apiPath,value:implValue,source:data.source,filePath:data.filePath,collisionMode})});this.handlers.lifecycle.subscribe("impl:changed",data=>{const collisionMode=this.config?.collision?.api||"merge";const implValue=data.wrapper?.__impl??data.impl;const currentOwner=this.handlers.ownership.getCurrentOwner(data.apiPath);if(currentOwner?.moduleID!==data.moduleID){this.handlers.ownership.register({moduleID:data.moduleID,apiPath:data.apiPath,value:implValue,source:data.source,filePath:data.filePath,collisionMode})}})}}_registerLazyWrapper(){this._totalLazyCount++;this._unmaterializedLazyCount++;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_LAZY_WRAPPER_REGISTERED",total:this._totalLazyCount,unmaterialized:this._unmaterializedLazyCount})}}_onWrapperMaterialized(){this._unmaterializedLazyCount--;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_LAZY_WRAPPER_MATERIALIZED",total:this._totalLazyCount,unmaterialized:this._unmaterializedLazyCount,percentage:this._totalLazyCount>0?(this._totalLazyCount-this._unmaterializedLazyCount)/this._totalLazyCount*100:100})}if(this._unmaterializedLazyCount===0&&!this._materializationComplete){this._materializationComplete=true;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_ALL_LAZY_WRAPPERS_MATERIALIZED",total:this._totalLazyCount})}const waiters=this._materializationWaiters.splice(0);for(const resolve of waiters){resolve()}if(this.config?.tracking?.materialization&&!this._materializationCompleteEmitted){this._materializationCompleteEmitted=true;if(this.handlers?.lifecycle){this.handlers.lifecycle.emit("materialized:complete",{total:this._totalLazyCount,timestamp:Date.now()})}}}}_captureEnvSnapshot(envConfig){const rawInclude=envConfig?.include;const include=Array.isArray(rawInclude)?rawInclude.filter(key=>typeof key==="string"):null;const useAllowlist=include!==null&&include.length>0;const raw=useAllowlist?include.reduce((acc,key)=>{if(Object.prototype.hasOwnProperty.call(process.env,key)){acc[key]=process.env[key]}return acc},Object.create(null)):{...process.env};return Object.freeze(raw)}async load(config={},preservedInstanceID=null){this.config=config;this.envTarget=config.platform==="browser"||config.platform!=="node"&&config.manifest!=null?"browser":"node";if(!this.envSnapshot){this.envSnapshot=this.envTarget==="browser"?Object.freeze(Object.create(null)):this._captureEnvSnapshot(config.env)}this.debugLogger=new SlothletDebug(config);await this._initializeComponents();registerInstance(this);this._setupLifecycleSubscribers();this.config=this.helpers.config.transformConfig(config);this._setupConfigLifecycleSubscribers();if(this.config?.i18n?.language){initI18n({language:this.config.i18n.language})}this.debugLogger=new SlothletDebug(this.config);this.instanceID=preservedInstanceID||this.helpers.utilities.generateId();this.reference=this.config.reference;this.context=this.config.context;this.contextManager=getContextManager(this.envTarget==="browser"?"live":this.config.runtime);setApiContextChecker(()=>{const ctx=this.contextManager.tryGetContext();return!!(ctx&&ctx.self)});let store;if(preservedInstanceID&&this.contextManager.instances.has(preservedInstanceID)){this.contextManager.cleanup(preservedInstanceID);store=this.contextManager.initialize(this.instanceID,this.config)}else{store=this.contextManager.initialize(this.instanceID,this.config)}Object.defineProperty(store,TRUSTED_ROOT,{value:true,configurable:true});enableEventEmitterPatching();if(typeof this.contextManager.registerEventEmitterContextChecker==="function"){this.contextManager.registerEventEmitterContextChecker()}const baseModuleId=`base_${this.helpers.utilities.generateId().substring(0,8)}`;if(this.config.scanHiddenFolders!==void 0&&!this.config.silent){new this.SlothletWarning("CONFIG_SCAN_HIDDEN_FOLDERS_DEPRECATED",{option:"scanHiddenFolders"})}const baseApi=await this.builders.builder.buildAPI({dir:this.config.dir,mode:this.config.mode,moduleID:baseModuleId,hidden:this.config.hidden??null,scanHiddenFolders:this.config.scanHiddenFolders===true});this.api=baseApi;const apiWithBuiltins=await this.buildFinalAPI(this.api);if(this.handlers.apiCacheManager){this.handlers.apiCacheManager.set(baseModuleId,{endpoint:".",moduleID:baseModuleId,api:this.api,folderPath:this.config.dir,mode:this.config.mode,sanitizeOptions:this.config.sanitize||{},collisionMode:this.config.collision?.api||"merge",config:{...this.config},timestamp:Date.now()})}this.injectRuntimeMetadataFunctions(apiWithBuiltins);if(this.config.metadata&&typeof this.config.metadata==="object"){for(const[key,value]of Object.entries(this.config.metadata)){this.handlers.metadata.setGlobalMetadata(key,value)}this.handlers.metadata.registerUserMetadata(baseModuleId,this.config.metadata)}if(this.handlers.ownership){this.handlers.ownership.registerSubtree(apiWithBuiltins,baseModuleId,"");this.handlers.ownership.setModuleEndpoint(baseModuleId,".")}if(!this.boundApi){const isCallable=typeof this.api==="function"||this.api&&typeof this.api.default==="function";const proxyTarget=isCallable?function(){}:{};this.boundApi=new Proxy(proxyTarget,{get:(target,prop)=>this.api?this.api[prop]:void 0,set:(target,prop,value)=>{if(this.api){this.api[prop]=value}return true},has:(target,prop)=>this.api?prop in this.api:false,ownKeys:____target=>this.api?Reflect.ownKeys(this.api):[],deleteProperty:(target,prop)=>this.api?delete this.api[prop]:true,apply:(target,thisArg,args)=>this.api?Reflect.apply(this.api,thisArg,args):void 0,construct:(target,args)=>this.api?Reflect.construct(this.api,args):{},getOwnPropertyDescriptor:(target,prop)=>{if(isCallable&&prop==="prototype"){return Object.getOwnPropertyDescriptor(target,prop)}if(this.api&&prop in this.api){const desc=Object.getOwnPropertyDescriptor(this.api,prop);if(desc){return{...desc,configurable:true}}}return void 0}})}store.self=this.boundApi;store.context=this.context||{};store.slothlet=this;if(this.reference&&typeof this.reference==="object"){Object.assign(this.boundApi,this.reference)}this.isLoaded=true;return this.boundApi}async reload(options={}){const{keepInstanceID=false}=options;if(!this.config?.dir){throw new SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"reload",validationError:true})}const operationHistory=this.handlers.apiManager?.state?.operationHistory?[...this.handlers.apiManager.state.operationHistory]:[];await this._clearModuleCaches();const oldInstanceID=this.instanceID;if(!keepInstanceID){this.instanceID=`${oldInstanceID}_reload_${Date.now()}`;if(isNode&&this._typescriptCacheDirs?.size){await Promise.allSettled([...this._typescriptCacheDirs].map(dir=>fsp.rm(dir,{recursive:true,force:true})));this._typescriptCacheDirs.clear()}}const savedMetadataState=this.handlers.metadata?.exportUserState?.();const savedHooks=this.handlers.hookManager?.exportHooks?.();const wasSealed=this.handlers.permissionManager?.isSealed?.()===true;await this.load(this.config,this.instanceID);if(savedMetadataState&&this.handlers.metadata){this.handlers.metadata.importUserState(savedMetadataState)}if(savedHooks?.length&&this.handlers.hookManager){this.handlers.hookManager.importHooks(savedHooks)}for(const[,store]of this.contextManager.instances){if(store.parentInstanceID===oldInstanceID){store.parentInstanceID=this.instanceID}}if(oldInstanceID&&oldInstanceID!==this.instanceID&&this.contextManager.instances?.has(oldInstanceID)){this.contextManager.cleanup(oldInstanceID)}for(const operation of operationHistory){if(operation.type==="add"){await this.handlers.apiManager.addApiComponent({apiPath:operation.apiPath,folderPath:operation.folderPath,options:{...operation.options||{},recordHistory:false},moduleID:`replay_${this.helpers.utilities.generateId().substring(0,8)}`,versionConfig:operation.versionConfig||null})}else if(operation.type==="remove"){await this.handlers.apiManager.removeApiComponent(operation.apiPath,{recordHistory:false})}else if(operation.type==="addPermissionRule"){if(this.handlers.permissionManager){this.handlers.permissionManager.addRule(operation.rule,operation.ownerModuleID,operation.ruleId)}}else{if(operation.type==="removePermissionRule"){if(this.handlers.permissionManager){this.handlers.permissionManager.removeRule(operation.ruleId,operation.callerModuleID)}}}}if(wasSealed)this.handlers.permissionManager?.seal?.();return this.boundApi}async _clearModuleCaches(){if(!isNode)return;const targetDir=this.config.dir;const require2=createRequire(import.meta.url);const absoluteTargetDir=path.resolve(targetDir);for(const key of Object.keys(require2.cache)){if(key.startsWith(absoluteTargetDir)){delete require2.cache[key]}}}injectRuntimeMetadataFunctions(api){if(!api.slothlet?.metadata){return}const metadataHandler=this.handlers.metadata;api.slothlet.metadata.get=async function slothlet_metadata_get_runtime(path2){return metadataHandler.get(path2)};api.slothlet.metadata.self=function slothlet_metadata_self_runtime(){return metadataHandler.self()};api.slothlet.metadata.caller=function slothlet_metadata_caller_runtime(){return metadataHandler.caller()}}async _drainInFlightLoads(){if(!this.api)return;const pending=[];const seen=new Set;const collect=(obj,depth=0)=>{const objType=typeof obj;if(!obj||objType!=="object"&&objType!=="function"||depth>15||seen.has(obj))return;seen.add(obj);try{if(obj.__isVersionDispatcher===true)return;const wrapper=resolveWrapper(obj);if(wrapper){const mat=wrapper.____slothletInternal.materializationPromise;if(mat)pending.push(mat);for(const key of Object.keys(wrapper)){if(!key.startsWith("____"))collect(wrapper[key],depth+1)}return}for(const key of Object.keys(obj)){collect(obj[key],depth+1)}}catch{}};for(const key of Object.keys(this.api)){collect(this.api[key])}if(pending.length>0){await Promise.allSettled(pending)}}async _collectLifecycleHooks(kind){if(!this.api)return[];const hooks=[];const seen=new Set;const collect=async(obj,depth=0)=>{const objType=typeof obj;if(!obj||objType!=="object"&&objType!=="function"||depth>15||seen.has(obj))return;seen.add(obj);try{if(obj.__isVersionDispatcher===true)return;const wrapper=resolveWrapper(obj);if(wrapper){if(wrapper.____slothletInternal.mode==="lazy"&&!wrapper.____slothletInternal.state.materialized){try{await wrapper._materialize()}catch{return}}if(wrapper.____slothletInternal.mode!=="lazy"||wrapper.____slothletInternal.state.materialized){const hook=obj[kind];if(typeof hook==="function"){hooks.push({apiPath:wrapper.____slothletInternal.apiPath,fn:hook,receiver:obj})}}for(const key of Object.keys(wrapper)){if(!key.startsWith("____"))await collect(wrapper[key],depth+1)}return}for(const key of Object.keys(obj)){await collect(obj[key],depth+1)}}catch{}};for(const key of Object.keys(this.api)){if(key==="slothlet"||key==="shutdown"||key==="destroy"||key.startsWith("____"))continue;await collect(this.api[key])}return hooks}async shutdown(){if(!this.isLoaded){return}await this._drainInFlightLoads();disableEventEmitterPatching();cleanupEventEmitterResources();if(this.instanceID&&this.contextManager){this.contextManager.cleanup(this.instanceID)}if(this.handlers.ownership){this.handlers.ownership.clear()}this.handlers.versionManager?.shutdown();await this.handlers.permissionManager?.shutdown();if(isNode&&this._typescriptCacheDirs?.size){await Promise.allSettled([...this._typescriptCacheDirs].map(dir=>fsp.rm(dir,{recursive:true,force:true})));this._typescriptCacheDirs.clear()}this.isLoaded=false}debug(code,context={}){if(this.debugLogger){this.debugLogger.log(code,context)}}getAPI(){if(!this.isLoaded){throw new SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"getAPI"},null,{validationError:true})}return this.boundApi}getDiagnostics(){return{instanceID:this.instanceID,isLoaded:this.isLoaded,config:this.config,context:this.contextManager?.getDiagnostics()||null,ownership:this.handlers.ownership?.getDiagnostics()||null}}getOwnership(){if(!this.handlers.ownership){return null}return this.handlers.ownership.getDiagnostics()}buildFinalAPI(userApi){return this.builders.apiBuilder.buildFinalAPI(userApi)}}async function slothlet(config){const instance=new Slothlet;const api=await instance.load(config);return api}var stdin_default=slothlet;export{stdin_default as default,slothlet};
17
+ import{isNode,fs,fsp,path,url,createRequire}from"@cldmv/slothlet/helpers/platform";import{getContextManager}from"#factories/context";import{warnIfCoverageWithoutImporter}from"@cldmv/slothlet/processors/loader";import{SlothletError,SlothletWarning,SlothletDebug}from"@cldmv/slothlet/errors";import{registerInstance}from"#handlers/lifecycle-token";import{resolveWrapper}from"#handlers/unified-wrapper";import{TRUSTED_ROOT}from"#handlers/trusted-root";import{initI18n}from"@cldmv/slothlet/i18n";import{enableEventEmitterPatching,disableEventEmitterPatching,cleanupEventEmitterResources,setApiContextChecker}from"@cldmv/slothlet/helpers/eventemitter-context";import{enableSchedulerPatching,disableSchedulerPatching}from"@cldmv/slothlet/helpers/scheduler-context";import{enableEventTargetPatching,disableEventTargetPatching}from"@cldmv/slothlet/helpers/eventtarget-context";const boundaryPatchHolders=new Set;class Slothlet{static RESERVED_ROOT_KEYS=["slothlet","shutdown","destroy"];static SKIP_PROPS=["__metadata","__type","_materialize","_impl","____slothletInternal"];constructor(){this.SlothletError=SlothletError;this.SlothletWarning=SlothletWarning;this.debugLogger=null;this.instanceID=null;this.config=null;this.envTarget="node";this.api=null;this.boundApi=null;this.contextManager=null;this.isLoaded=false;this.reference=null;this.context=null;this.envSnapshot=null;this._totalLazyCount=0;this._unmaterializedLazyCount=0;this._materializationComplete=false;this._materializationWaiters=[];this._materializationCompleteEmitted=false;this.componentCategories=["helpers","handlers","builders","processors","modes"];for(const category of this.componentCategories){this[category]={}}}async _initializeComponents(){if(this.envTarget==="browser"){await this._initializeComponentsBrowser();return}const baseDir=path.join(path.dirname(url.fileURLToPath(import.meta.url)),"lib");for(const category of this.componentCategories){const categoryDir=path.join(baseDir,category);const files=fs.readdirSync(categoryDir).filter(f=>f.endsWith(".mjs"));for(const file of files){const filePath=path.join(categoryDir,file);try{const module=await import(url.pathToFileURL(filePath).href);const classExports=Object.values(module).filter(exp=>typeof exp==="function"&&exp.slothletProperty);for(const ClassExport of classExports){const propName=ClassExport.slothletProperty;this[category][propName]=new ClassExport(this);if(this.config?.debug?.initialization){this.debug("initialization",{key:"DEBUG_MODE_COMPONENT_INITIALIZED",component:ClassExport.name,category,propertyName:propName})}}}catch(error){throw new this.SlothletError("MODULE_IMPORT_FAILED",{modulePath:filePath},error)}}}}async _initializeComponentsBrowser(){const BROWSER_COMPONENT_SPECIFIERS=["@cldmv/slothlet/builders/api-assignment","@cldmv/slothlet/builders/api_builder","@cldmv/slothlet/builders/builder","@cldmv/slothlet/builders/modes-processor","#handlers/api-cache-manager","#handlers/api-manager","#handlers/hook-manager","#handlers/lifecycle","#handlers/materialize-manager","#handlers/metadata","#handlers/module-manager","#handlers/ownership","#handlers/permission-manager","#handlers/version-manager","@cldmv/slothlet/helpers/config","@cldmv/slothlet/helpers/hint-detector","@cldmv/slothlet/helpers/modes-utils","@cldmv/slothlet/helpers/resolve-from-caller","@cldmv/slothlet/helpers/sanitize","@cldmv/slothlet/helpers/utilities","@cldmv/slothlet/modes/eager","@cldmv/slothlet/modes/lazy","@cldmv/slothlet/processors/flatten","@cldmv/slothlet/processors/loader"];for(const specifier of BROWSER_COMPONENT_SPECIFIERS){const parts=specifier.split("/");const category=specifier.startsWith("#")?parts[0].slice(1):parts[2];const module=await import(specifier);const classExports=Object.values(module).filter(exp=>typeof exp==="function"&&exp.slothletProperty);for(const ClassExport of classExports){const propName=ClassExport.slothletProperty;this[category][propName]=new ClassExport(this)}}}_setupConfigLifecycleSubscribers(){const map=this.config.lifecycle;if(!map){return}const lifecycle=this.handlers.lifecycle;for(const[event,handler]of Object.entries(map)){const handlers=Array.isArray(handler)?handler:[handler];for(const fn of handlers){lifecycle.subscribe(event,fn)}}}_setupLifecycleSubscribers(){if(!this.handlers.lifecycle){return}if(this.handlers.metadata){this.handlers.lifecycle.subscribe("impl:created",(data,token)=>{this.handlers.metadata.tagSystemMetadata(data.impl,{filePath:data.filePath,apiPath:data.apiPath,moduleID:data.moduleID,sourceFolder:data.sourceFolder},token)});this.handlers.lifecycle.subscribe("impl:changed",(data,token)=>{this.handlers.metadata.tagSystemMetadata(data.impl,{filePath:data.filePath,apiPath:data.apiPath,moduleID:data.moduleID,sourceFolder:data.sourceFolder},token)});this.handlers.lifecycle.subscribe("impl:removed",data=>{if(data.apiPath){const rootSegment=data.apiPath.split(".")[0];this.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}})}if(this.handlers.ownership){this.handlers.lifecycle.subscribe("impl:created",data=>{const collisionMode=this.config?.collision?.api||"merge";const implValue=data.wrapper?.__impl??data.impl;this.handlers.ownership.register({moduleID:data.moduleID,apiPath:data.apiPath,value:implValue,source:data.source,filePath:data.filePath,collisionMode})});this.handlers.lifecycle.subscribe("impl:changed",data=>{const collisionMode=this.config?.collision?.api||"merge";const implValue=data.wrapper?.__impl??data.impl;const currentOwner=this.handlers.ownership.getCurrentOwner(data.apiPath);if(currentOwner?.moduleID!==data.moduleID){this.handlers.ownership.register({moduleID:data.moduleID,apiPath:data.apiPath,value:implValue,source:data.source,filePath:data.filePath,collisionMode})}})}}_registerLazyWrapper(){this._totalLazyCount++;this._unmaterializedLazyCount++;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_LAZY_WRAPPER_REGISTERED",total:this._totalLazyCount,unmaterialized:this._unmaterializedLazyCount})}}_onWrapperMaterialized(){this._unmaterializedLazyCount--;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_LAZY_WRAPPER_MATERIALIZED",total:this._totalLazyCount,unmaterialized:this._unmaterializedLazyCount,percentage:this._totalLazyCount>0?(this._totalLazyCount-this._unmaterializedLazyCount)/this._totalLazyCount*100:100})}if(this._unmaterializedLazyCount===0&&!this._materializationComplete){this._materializationComplete=true;if(this.config?.debug?.materialize){this.debug("materialize",{key:"DEBUG_MODE_ALL_LAZY_WRAPPERS_MATERIALIZED",total:this._totalLazyCount})}const waiters=this._materializationWaiters.splice(0);for(const resolve of waiters){resolve()}if(this.config?.tracking?.materialization&&!this._materializationCompleteEmitted){this._materializationCompleteEmitted=true;if(this.handlers?.lifecycle){this.handlers.lifecycle.emit("materialized:complete",{total:this._totalLazyCount,timestamp:Date.now()})}}}}_captureEnvSnapshot(envConfig){const rawInclude=envConfig?.include;const include=Array.isArray(rawInclude)?rawInclude.filter(key=>typeof key==="string"):null;const useAllowlist=include!==null&&include.length>0;const raw=useAllowlist?include.reduce((acc,key)=>{if(Object.prototype.hasOwnProperty.call(process.env,key)){acc[key]=process.env[key]}return acc},Object.create(null)):{...process.env};return Object.freeze(raw)}async load(config={},preservedInstanceID=null){this.config=config;this.envTarget=config.platform==="browser"||config.platform!=="node"&&config.manifest!=null?"browser":"node";if(!this.envSnapshot){this.envSnapshot=this.envTarget==="browser"?Object.freeze(Object.create(null)):this._captureEnvSnapshot(config.env)}this.debugLogger=new SlothletDebug(config);await this._initializeComponents();registerInstance(this);this._setupLifecycleSubscribers();this.config=this.helpers.config.transformConfig(config);if(this.envTarget==="node"){warnIfCoverageWithoutImporter(this.config)}this._setupConfigLifecycleSubscribers();if(this.config?.i18n?.language){initI18n({language:this.config.i18n.language})}this.debugLogger=new SlothletDebug(this.config);this.instanceID=preservedInstanceID||this.helpers.utilities.generateId();this.reference=this.config.reference;this.context=this.config.context;this.contextManager=getContextManager(this.envTarget==="browser"?"live":this.config.runtime);setApiContextChecker(()=>{const ctx=this.contextManager.tryGetContext();return!!(ctx&&ctx.self)});let store;if(preservedInstanceID&&this.contextManager.instances.has(preservedInstanceID)){this.contextManager.cleanup(preservedInstanceID);store=this.contextManager.initialize(this.instanceID,this.config)}else{store=this.contextManager.initialize(this.instanceID,this.config)}Object.defineProperty(store,TRUSTED_ROOT,{value:true,configurable:true});enableEventEmitterPatching();enableSchedulerPatching();enableEventTargetPatching();boundaryPatchHolders.add(this.instanceID);if(typeof this.contextManager.registerEventEmitterContextChecker==="function"){this.contextManager.registerEventEmitterContextChecker()}const baseModuleId=`base_${this.helpers.utilities.generateId().substring(0,8)}`;if(this.config.scanHiddenFolders!==void 0&&!this.config.silent){new this.SlothletWarning("CONFIG_SCAN_HIDDEN_FOLDERS_DEPRECATED",{option:"scanHiddenFolders"})}const baseApi=await this.builders.builder.buildAPI({dir:this.config.dir,mode:this.config.mode,moduleID:baseModuleId,hidden:this.config.hidden??null,scanHiddenFolders:this.config.scanHiddenFolders===true});this.api=baseApi;const apiWithBuiltins=await this.buildFinalAPI(this.api);if(this.handlers.apiCacheManager){this.handlers.apiCacheManager.set(baseModuleId,{endpoint:".",moduleID:baseModuleId,api:this.api,folderPath:this.config.dir,mode:this.config.mode,sanitizeOptions:this.config.sanitize||{},collisionMode:this.config.collision?.api||"merge",config:{...this.config},timestamp:Date.now()})}this.injectRuntimeMetadataFunctions(apiWithBuiltins);if(this.config.metadata&&typeof this.config.metadata==="object"){for(const[key,value]of Object.entries(this.config.metadata)){this.handlers.metadata.setGlobalMetadata(key,value)}this.handlers.metadata.registerUserMetadata(baseModuleId,this.config.metadata)}if(this.handlers.ownership){this.handlers.ownership.registerSubtree(apiWithBuiltins,baseModuleId,"");this.handlers.ownership.setModuleEndpoint(baseModuleId,".")}if(!this.boundApi){const isCallable=typeof this.api==="function"||this.api&&typeof this.api.default==="function";const proxyTarget=isCallable?function(){}:{};this.boundApi=new Proxy(proxyTarget,{get:(target,prop)=>this.api?this.api[prop]:void 0,set:(target,prop,value)=>{if(this.api){this.api[prop]=value}return true},has:(target,prop)=>this.api?prop in this.api:false,ownKeys:____target=>this.api?Reflect.ownKeys(this.api):[],deleteProperty:(target,prop)=>this.api?delete this.api[prop]:true,apply:(target,thisArg,args)=>this.api?Reflect.apply(this.api,thisArg,args):void 0,construct:(target,args)=>this.api?Reflect.construct(this.api,args):{},getOwnPropertyDescriptor:(target,prop)=>{if(isCallable&&prop==="prototype"){return Object.getOwnPropertyDescriptor(target,prop)}if(this.api&&prop in this.api){const desc=Object.getOwnPropertyDescriptor(this.api,prop);if(desc){return{...desc,configurable:true}}}return void 0}})}store.self=this.boundApi;store.context=this.context||{};store.slothlet=this;if(this.reference&&typeof this.reference==="object"){Object.assign(this.boundApi,this.reference)}this.isLoaded=true;return this.boundApi}async reload(options={}){const{keepInstanceID=false}=options;if(!this.config?.dir){throw new SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"reload",validationError:true})}const operationHistory=this.handlers.apiManager?.state?.operationHistory?[...this.handlers.apiManager.state.operationHistory]:[];await this._clearModuleCaches();const oldInstanceID=this.instanceID;if(!keepInstanceID){this.instanceID=`${oldInstanceID}_reload_${Date.now()}`;if(isNode&&this._typescriptCacheDirs?.size){await Promise.allSettled([...this._typescriptCacheDirs].map(dir=>fsp.rm(dir,{recursive:true,force:true})));this._typescriptCacheDirs.clear()}}const savedMetadataState=this.handlers.metadata?.exportUserState?.();const savedHooks=this.handlers.hookManager?.exportHooks?.();const wasSealed=this.handlers.permissionManager?.isSealed?.()===true;await this.load(this.config,this.instanceID);if(savedMetadataState&&this.handlers.metadata){this.handlers.metadata.importUserState(savedMetadataState)}if(savedHooks?.length&&this.handlers.hookManager){this.handlers.hookManager.importHooks(savedHooks)}for(const[,store]of this.contextManager.instances){if(store.parentInstanceID===oldInstanceID){store.parentInstanceID=this.instanceID}}if(oldInstanceID&&oldInstanceID!==this.instanceID&&this.contextManager.instances?.has(oldInstanceID)){this.contextManager.cleanup(oldInstanceID)}for(const operation of operationHistory){if(operation.type==="add"){await this.handlers.apiManager.addApiComponent({apiPath:operation.apiPath,folderPath:operation.folderPath,options:{...operation.options||{},recordHistory:false},moduleID:`replay_${this.helpers.utilities.generateId().substring(0,8)}`,versionConfig:operation.versionConfig||null})}else if(operation.type==="remove"){await this.handlers.apiManager.removeApiComponent(operation.apiPath,{recordHistory:false})}else if(operation.type==="addPermissionRule"){if(this.handlers.permissionManager){this.handlers.permissionManager.addRule(operation.rule,operation.ownerModuleID,operation.ruleId)}}else{if(operation.type==="removePermissionRule"){if(this.handlers.permissionManager){this.handlers.permissionManager.removeRule(operation.ruleId,operation.callerModuleID)}}}}if(wasSealed)this.handlers.permissionManager?.seal?.();return this.boundApi}async _clearModuleCaches(){if(!isNode)return;const targetDir=this.config.dir;const require2=createRequire(import.meta.url);const absoluteTargetDir=path.resolve(targetDir);for(const key of Object.keys(require2.cache)){if(key.startsWith(absoluteTargetDir)){delete require2.cache[key]}}}injectRuntimeMetadataFunctions(api){if(!api.slothlet?.metadata){return}const metadataHandler=this.handlers.metadata;api.slothlet.metadata.get=async function slothlet_metadata_get_runtime(path2){return metadataHandler.get(path2)};api.slothlet.metadata.self=function slothlet_metadata_self_runtime(){return metadataHandler.self()};api.slothlet.metadata.caller=function slothlet_metadata_caller_runtime(){return metadataHandler.caller()}}async _drainInFlightLoads(){if(!this.api)return;const pending=[];const seen=new Set;const collect=(obj,depth=0)=>{const objType=typeof obj;if(!obj||objType!=="object"&&objType!=="function"||depth>15||seen.has(obj))return;seen.add(obj);try{if(obj.__isVersionDispatcher===true)return;const wrapper=resolveWrapper(obj);if(wrapper){const mat=wrapper.____slothletInternal.materializationPromise;if(mat)pending.push(mat);for(const key of Object.keys(wrapper)){if(!key.startsWith("____"))collect(wrapper[key],depth+1)}return}for(const key of Object.keys(obj)){collect(obj[key],depth+1)}}catch{}};for(const key of Object.keys(this.api)){collect(this.api[key])}if(pending.length>0){await Promise.allSettled(pending)}}async _collectLifecycleHooks(kind){if(!this.api)return[];const hooks=[];const seen=new Set;const collect=async(obj,depth=0)=>{const objType=typeof obj;if(!obj||objType!=="object"&&objType!=="function"||depth>15||seen.has(obj))return;seen.add(obj);try{if(obj.__isVersionDispatcher===true)return;const wrapper=resolveWrapper(obj);if(wrapper){if(wrapper.____slothletInternal.mode==="lazy"&&!wrapper.____slothletInternal.state.materialized){try{await wrapper._materialize()}catch{return}}if(wrapper.____slothletInternal.mode!=="lazy"||wrapper.____slothletInternal.state.materialized){const hook=obj[kind];if(typeof hook==="function"){hooks.push({apiPath:wrapper.____slothletInternal.apiPath,fn:hook,receiver:obj})}}for(const key of Object.keys(wrapper)){if(!key.startsWith("____"))await collect(wrapper[key],depth+1)}return}for(const key of Object.keys(obj)){await collect(obj[key],depth+1)}}catch{}};for(const key of Object.keys(this.api)){if(key==="slothlet"||key==="shutdown"||key==="destroy"||key.startsWith("____"))continue;await collect(this.api[key])}return hooks}async shutdown(){if(!this.isLoaded){return}await this._drainInFlightLoads();boundaryPatchHolders.delete(this.instanceID);if(boundaryPatchHolders.size===0){disableEventEmitterPatching();disableSchedulerPatching();disableEventTargetPatching();cleanupEventEmitterResources()}if(this.instanceID&&this.contextManager){this.contextManager.cleanup(this.instanceID)}if(this.handlers.ownership){this.handlers.ownership.clear()}this.handlers.versionManager?.shutdown();await this.handlers.permissionManager?.shutdown();if(isNode&&this._typescriptCacheDirs?.size){await Promise.allSettled([...this._typescriptCacheDirs].map(dir=>fsp.rm(dir,{recursive:true,force:true})));this._typescriptCacheDirs.clear()}this.isLoaded=false}debug(code,context={}){if(this.debugLogger){this.debugLogger.log(code,context)}}getAPI(){if(!this.isLoaded){throw new SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"getAPI"},null,{validationError:true})}return this.boundApi}getDiagnostics(){return{instanceID:this.instanceID,isLoaded:this.isLoaded,config:this.config,context:this.contextManager?.getDiagnostics()||null,ownership:this.handlers.ownership?.getDiagnostics()||null}}getOwnership(){if(!this.handlers.ownership){return null}return this.handlers.ownership.getDiagnostics()}buildFinalAPI(userApi){return this.builders.apiBuilder.buildFinalAPI(userApi)}}async function slothlet(config){const instance=new Slothlet;const api=await instance.load(config);return api}var stdin_default=slothlet;export{stdin_default as default,slothlet};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cldmv/slothlet",
3
- "version": "3.12.2",
3
+ "version": "3.13.0",
4
4
  "moduleVersions": {
5
5
  "lazy": "3.0.0",
6
6
  "eager": "3.0.0",
@@ -312,7 +312,7 @@
312
312
  }
313
313
  },
314
314
  "devDependencies": {
315
- "@cldmv/fix-headers": "^1.3.0",
315
+ "@cldmv/fix-headers": "^1.3.7",
316
316
  "@cldmv/vitest-runner": "^1.2.0",
317
317
  "@eslint/css": "^1.4.0",
318
318
  "@eslint/js": "^10.0.1",
@@ -322,8 +322,8 @@
322
322
  "@vitest/browser": "^4.1.10",
323
323
  "@vitest/browser-playwright": "^4.1.10",
324
324
  "@vitest/coverage-v8": "^4.1.10",
325
- "acorn": "^8.17.0",
326
- "chalk": "^5.6.2",
325
+ "acorn": "^8.18.0",
326
+ "chalk": "^6.0.0",
327
327
  "chokidar": "^5.0.0",
328
328
  "dmd": "^7.1.1",
329
329
  "esbuild": "^0.28.1",
@@ -334,14 +334,14 @@
334
334
  "jsdoc-to-markdown": "^9.1.3",
335
335
  "jsdoc2md": "^1.0.0",
336
336
  "jsonc-parser": "^3.3.1",
337
- "playwright": "^1.61.1",
337
+ "playwright": "^1.62.1",
338
338
  "prettier": "^3.9.5",
339
339
  "shx": "^0.4.0",
340
340
  "typescript": "^6.0.3",
341
341
  "vitest": "^4.1.10"
342
342
  },
343
343
  "optionalDependencies": {
344
- "@rolldown/binding-linux-x64-gnu": "1.0.3"
344
+ "@rolldown/binding-linux-x64-gnu": "1.1.3"
345
345
  },
346
346
  "repository": {
347
347
  "type": "git",
@@ -0,0 +1,3 @@
1
+ // AUTO-GENERATED by tools/build/build-typestubs.mjs — do not edit.
2
+ // Re-exports the real declarations from @cldmv/slothlet-types (install it for TypeScript support).
3
+ export * from "@cldmv/slothlet-types/helpers/caller-pinning";
@@ -0,0 +1,3 @@
1
+ // AUTO-GENERATED by tools/build/build-typestubs.mjs — do not edit.
2
+ // Re-exports the real declarations from @cldmv/slothlet-types (install it for TypeScript support).
3
+ export * from "@cldmv/slothlet-types/helpers/eventtarget-context";
@@ -0,0 +1,3 @@
1
+ // AUTO-GENERATED by tools/build/build-typestubs.mjs — do not edit.
2
+ // Re-exports the real declarations from @cldmv/slothlet-types (install it for TypeScript support).
3
+ export * from "@cldmv/slothlet-types/helpers/scheduler-context";