@cldmv/slothlet 3.16.2 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/REFERENCE.md +7 -0
- package/dist/lib/builders/api_builder.mjs +1 -1
- package/dist/lib/builders/modes-processor.mjs +1 -1
- package/dist/lib/handlers/event-manager.mjs +17 -0
- package/dist/lib/handlers/lifecycle.mjs +1 -1
- package/dist/lib/handlers/module-manager.mjs +1 -1
- package/dist/lib/handlers/permission-manager.mjs +1 -1
- package/dist/lib/handlers/routine-manager.mjs +1 -1
- package/dist/lib/handlers/unified-wrapper.mjs +1 -1
- package/dist/lib/helpers/config.mjs +1 -1
- package/dist/lib/helpers/module-manifest-validator.mjs +1 -1
- package/dist/lib/helpers/pattern-matcher.mjs +1 -1
- package/dist/lib/helpers/sanitize.mjs +1 -1
- package/dist/lib/helpers/utilities.mjs +1 -1
- package/dist/lib/i18n/languages/en-us.json +8 -0
- package/dist/lib/processors/flatten.mjs +1 -1
- package/dist/lib/processors/loader.mjs +1 -1
- package/dist/slothlet.mjs +1 -1
- package/package.json +2 -1
- package/types/stub/lib/helpers/config.d.mts +11 -8
- package/types/stub/lib/helpers/pattern-matcher.d.mts +3 -1
- package/types/stub/lib/helpers/utilities.d.mts +4 -2
- package/types/stub/lib/processors/flatten.d.mts +1 -1
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import{ComponentBase}from"#factories/component-base";class Sanitize extends ComponentBase{static slothletProperty="sanitize";#compileGlobPattern(pattern,caseSensitive=true){if(pattern.startsWith("**")&&pattern.endsWith("**")&&pattern.length>4){const innerString=pattern.slice(2,-2);const escapedString=innerString.replace(/[.+^${}()|[\]\\*?]/g,"\\$&");const flags2=caseSensitive?"":"i";return new RegExp(`(?<=.)${escapedString}(?=.)`,flags2)}const regexPattern=pattern.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".");const flags=caseSensitive?"":"i";return new RegExp(`^${regexPattern}$`,flags)}#matchesAnyPattern(input,patterns,caseSensitive=false){for(const pattern of patterns){if(pattern.includes("*")||pattern.includes("?")){const regex=this.#compileGlobPattern(pattern,caseSensitive);if(regex&®ex.test(input))return true}else{const match=caseSensitive?input===pattern:input.toLowerCase()===pattern.toLowerCase();if(match)return true}}return false}#extractPatternLiterals(pattern){return pattern.split(/[*?]+/).filter(Boolean)}#applySegmentRules(segment,index,originalString,config){const{preserveAllUpper,preserveAllLower,leaveRules,leaveInsensitiveRules,upperRules,lowerRules}=config;if(this.#matchesAnyPattern(segment,leaveRules,true)){return segment}if(this.#matchesAnyPattern(segment,leaveInsensitiveRules,false)){return segment}if(preserveAllUpper&&segment===segment.toUpperCase()&&segment!==segment.toLowerCase()&&/[A-Z]/.test(segment)){return segment}if(preserveAllLower&&segment===segment.toLowerCase()&&segment!==segment.toUpperCase()&&/[a-z]/.test(segment)){return segment}for(const pattern of[...upperRules,...lowerRules]){if(pattern.includes("*")||pattern.includes("?")){const regex=this.#compileGlobPattern(pattern,false);if(regex&®ex.test(originalString)){const literals=this.#extractPatternLiterals(pattern);for(const literal of literals){const cleanLiteral=literal.replace(/[^A-Za-z0-9_$]/g,"").replace(/^_+|_+$/g,"");if(cleanLiteral&&segment.toLowerCase()===cleanLiteral.toLowerCase()){return upperRules.includes(pattern)?segment.toUpperCase():segment.toLowerCase()}}}}else{if(segment.toLowerCase()===pattern.toLowerCase()){return upperRules.includes(pattern)?segment.toUpperCase():segment.toLowerCase()}}}let transformed=this.#applyWithinSegmentPatterns(segment,upperRules,lowerRules);if(transformed!==segment){return transformed}return segment}#applyWithinSegmentPatterns(segment,upperRules,lowerRules){let result=segment;const applyBoundaryPattern=(pattern,toUpper)=>{if(pattern.startsWith("**")&&pattern.endsWith("**")&&pattern.length>4){const innerString=pattern.slice(2,-2);const innerRegex=new RegExp(innerString.replace(/[.+^${}()|[\]\\*?]/g,"\\$&"),"gi");const matches=[...result.matchAll(innerRegex)];for(const match of matches){const startPos=match.index;const endPos=startPos+match[0].length;const hasCharBefore=startPos>0;const hasCharAfter=endPos<result.length;if(hasCharBefore&&hasCharAfter){const replacement=toUpper?innerString.toUpperCase():innerString.toLowerCase();result=result.substring(0,startPos)+replacement+result.substring(endPos);break}}}else if(pattern.includes("*")&&!pattern.startsWith("**")){const literalParts=pattern.split("*").filter(Boolean);for(const literal of literalParts){const literalRegex=new RegExp(literal.replace(/[.+^${}()|[\]\\]/g,"\\$&"),"gi");const replacement=toUpper?literal.toUpperCase():literal.toLowerCase();result=result.replace(literalRegex,replacement)}}};upperRules.forEach(pattern=>applyBoundaryPattern(pattern,true));lowerRules.forEach(pattern=>applyBoundaryPattern(pattern,false));return result}sanitizePropertyName(input,options={}){const{lowerFirst=true,preserveAllUpper=false,preserveAllLower=false,rules={}}=options;const leaveRules=(rules.leave||[]).map(s=>String(s));const leaveInsensitiveRules=(rules.leaveInsensitive||[]).map(s=>String(s));const upperRules=(rules.upper||[]).map(s=>String(s));const lowerRules=(rules.lower||[]).map(s=>String(s));const originalString=String(input).trim();const isAllUpper=originalString===originalString.toUpperCase()&&originalString!==originalString.toLowerCase()&&/[A-Z]/.test(originalString);const isAllLower=originalString===originalString.toLowerCase()&&originalString!==originalString.toUpperCase()&&/[a-z]/.test(originalString);if(preserveAllUpper&&isAllUpper){return originalString}if(preserveAllLower&&isAllLower&&!/-/.test(originalString)){return originalString}let primarySegments=originalString.split(/[-]+|[^A-Za-z0-9_$]+/).filter(Boolean);if(primarySegments.length===0)return"_";while(primarySegments.length&&!/^[A-Za-z_$]/.test(primarySegments[0][0])){primarySegments[0]=primarySegments[0].replace(/^[^A-Za-z_$]+/,"");if(!primarySegments[0])primarySegments.shift()}if(primarySegments.length===0)return"_";const lowerRuleApplied=[];const processedPrimarySegments=primarySegments.map((primarySeg,primaryIdx)=>{const parts=primarySeg.split(/(_+)/);const processedParts=parts.map((part,partIdx)=>{if(partIdx%2===1)return part;if(!part)return part;const cleanSeg=part.replace(/[^A-Za-z0-9_$]/g,"");const config={preserveAllUpper,preserveAllLower,leaveRules,leaveInsensitiveRules,upperRules,lowerRules};const result2=this.#applySegmentRules(cleanSeg,0,originalString,config);const matchesLower=lowerRules.some(pattern=>{if(pattern.includes("*")||pattern.includes("?")){const regex=this.#compileGlobPattern(pattern,false);if(regex&®ex.test(originalString)){const literals=this.#extractPatternLiterals(pattern);for(const literal of literals){const cleanLiteral=literal.replace(/[^A-Za-z0-9_$]/g,"").replace(/^_+|_+$/g,"");if(cleanLiteral&&cleanSeg.toLowerCase()===cleanLiteral.toLowerCase()){return true}}}}else{if(cleanSeg.toLowerCase()===pattern.toLowerCase()){return true}}return false});if(matchesLower&&result2===cleanSeg.toLowerCase()){lowerRuleApplied[primaryIdx]=true}return result2});return processedParts.join("")});const camelCasedSegments=processedPrimarySegments.map((seg,idx)=>{const matchesLeave=this.#matchesAnyPattern(seg,leaveRules,true);const matchesLeaveInsensitive=this.#matchesAnyPattern(seg,leaveInsensitiveRules,false);const matchesUpper=this.#matchesAnyPattern(seg,upperRules,false);const hasUnderscores=seg.includes("_");const isAllUpper2=!hasUnderscores&&preserveAllUpper&&seg===seg.toUpperCase()&&seg!==seg.toLowerCase()&&/[A-Z]/.test(seg);const isAllLower2=!hasUnderscores&&preserveAllLower&&seg===seg.toLowerCase()&&seg!==seg.toUpperCase()&&/[a-z]/.test(seg);if(matchesLeave||matchesLeaveInsensitive||matchesUpper||isAllUpper2||isAllLower2){return seg}let transformed;if(idx===0){transformed=lowerFirst?seg[0].toLowerCase()+seg.slice(1):seg}else{if(lowerRuleApplied[idx]){transformed=seg}else{transformed=seg[0].toUpperCase()+seg.slice(1)}}return transformed});let result=camelCasedSegments.join("");result=result.replace(/[^A-Za-z0-9_$]/g,"");return result}getModuleId(filePath,baseDir){let relative=filePath.replace(baseDir,"").replace(/\\/g,"/");relative=relative.replace(/^\//,"");relative=relative.replace(/\.(mjs|cjs|js)$/,"");return relative}shouldPreserveFunctionCase(name){const preservePatterns=[/^[A-Z]{2,}$/,/[A-Z]{2,}/];return preservePatterns.some(pattern=>pattern.test(name))}}function sanitizePropertyName(input,options={}){const sanitizer=new Sanitize(null);return sanitizer.sanitizePropertyName(input,options)}export{Sanitize,sanitizePropertyName};
|
|
17
|
+
import{ComponentBase}from"#factories/component-base";class Sanitize extends ComponentBase{static slothletProperty="sanitize";#compileGlobPattern(pattern,caseSensitive=true){if(pattern.startsWith("**")&&pattern.endsWith("**")&&pattern.length>4){const innerString=pattern.slice(2,-2);const escapedString=innerString.replace(/[.+^${}()|[\]\\*?]/g,"\\$&");const flags2=caseSensitive?"":"i";return new RegExp(`(?<=.)${escapedString}(?=.)`,flags2)}const regexPattern=pattern.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".");const flags=caseSensitive?"":"i";return new RegExp(`^${regexPattern}$`,flags)}#matchesAnyPattern(input,patterns,caseSensitive=false){for(const pattern of patterns){if(pattern.includes("*")||pattern.includes("?")){const regex=this.#compileGlobPattern(pattern,caseSensitive);if(regex&®ex.test(input))return true}else{const match=caseSensitive?input===pattern:input.toLowerCase()===pattern.toLowerCase();if(match)return true}}return false}#extractPatternLiterals(pattern){return pattern.split(/[*?]+/).filter(Boolean)}#applySegmentRules(segment,index,originalString,config){const{preserveAllUpper,preserveAllLower,leaveRules,leaveInsensitiveRules,upperRules,lowerRules}=config;if(this.#matchesAnyPattern(segment,leaveRules,true)){return segment}if(this.#matchesAnyPattern(segment,leaveInsensitiveRules,false)){return segment}if(preserveAllUpper&&segment===segment.toUpperCase()&&segment!==segment.toLowerCase()&&/[A-Z]/.test(segment)){return segment}if(preserveAllLower&&segment===segment.toLowerCase()&&segment!==segment.toUpperCase()&&/[a-z]/.test(segment)){return segment}for(const pattern of[...upperRules,...lowerRules]){if(pattern.includes("*")||pattern.includes("?")){const regex=this.#compileGlobPattern(pattern,false);if(regex&®ex.test(originalString)){const literals=this.#extractPatternLiterals(pattern);for(const literal of literals){const cleanLiteral=literal.replace(/[^A-Za-z0-9_$]/g,"").replace(/^_+|_+$/g,"");if(cleanLiteral&&segment.toLowerCase()===cleanLiteral.toLowerCase()){return upperRules.includes(pattern)?segment.toUpperCase():segment.toLowerCase()}}}}else{if(segment.toLowerCase()===pattern.toLowerCase()){return upperRules.includes(pattern)?segment.toUpperCase():segment.toLowerCase()}}}let transformed=this.#applyWithinSegmentPatterns(segment,upperRules,lowerRules);if(transformed!==segment){return transformed}return segment}#applyWithinSegmentPatterns(segment,upperRules,lowerRules){let result=segment;const applyBoundaryPattern=(pattern,toUpper)=>{if(pattern.startsWith("**")&&pattern.endsWith("**")&&pattern.length>4){const innerString=pattern.slice(2,-2);const innerRegex=new RegExp(innerString.replace(/[.+^${}()|[\]\\*?]/g,"\\$&"),"gi");const matches=[...result.matchAll(innerRegex)];for(const match of matches){const startPos=match.index;const endPos=startPos+match[0].length;const hasCharBefore=startPos>0;const hasCharAfter=endPos<result.length;if(hasCharBefore&&hasCharAfter){const replacement=toUpper?innerString.toUpperCase():innerString.toLowerCase();result=result.substring(0,startPos)+replacement+result.substring(endPos);break}}}else if(pattern.includes("*")&&!pattern.startsWith("**")){const literalParts=pattern.split("*").filter(Boolean);for(const literal of literalParts){const literalRegex=new RegExp(literal.replace(/[.+^${}()|[\]\\]/g,"\\$&"),"gi");const replacement=toUpper?literal.toUpperCase():literal.toLowerCase();result=result.replace(literalRegex,replacement)}}};upperRules.forEach(pattern=>applyBoundaryPattern(pattern,true));lowerRules.forEach(pattern=>applyBoundaryPattern(pattern,false));return result}sanitizePropertyName(input,options={}){const{lowerFirst=true,preserveAllUpper=false,preserveAllLower=false,rules={}}=options;const leaveRules=(rules.leave||[]).map(s=>String(s));const leaveInsensitiveRules=(rules.leaveInsensitive||[]).map(s=>String(s));const upperRules=(rules.upper||[]).map(s=>String(s));const lowerRules=(rules.lower||[]).map(s=>String(s));const originalString=String(input).trim();const isAllUpper=originalString===originalString.toUpperCase()&&originalString!==originalString.toLowerCase()&&/[A-Z]/.test(originalString);const isAllLower=originalString===originalString.toLowerCase()&&originalString!==originalString.toUpperCase()&&/[a-z]/.test(originalString);if(preserveAllUpper&&isAllUpper&&!/-/.test(originalString)){return originalString}if(preserveAllLower&&isAllLower&&!/-/.test(originalString)){return originalString}let primarySegments=originalString.split(/[-]+|[^A-Za-z0-9_$]+/).filter(Boolean);if(primarySegments.length===0)return"_";while(primarySegments.length&&!/^[A-Za-z_$]/.test(primarySegments[0][0])){primarySegments[0]=primarySegments[0].replace(/^[^A-Za-z_$]+/,"");if(!primarySegments[0])primarySegments.shift()}if(primarySegments.length===0)return"_";const lowerRuleApplied=[];const processedPrimarySegments=primarySegments.map((primarySeg,primaryIdx)=>{const parts=primarySeg.split(/(_+)/);const processedParts=parts.map((part,partIdx)=>{if(partIdx%2===1)return part;if(!part)return part;const cleanSeg=part.replace(/[^A-Za-z0-9_$]/g,"");const config={preserveAllUpper,preserveAllLower,leaveRules,leaveInsensitiveRules,upperRules,lowerRules};const result2=this.#applySegmentRules(cleanSeg,0,originalString,config);const matchesLower=lowerRules.some(pattern=>{if(pattern.includes("*")||pattern.includes("?")){const regex=this.#compileGlobPattern(pattern,false);if(regex&®ex.test(originalString)){const literals=this.#extractPatternLiterals(pattern);for(const literal of literals){const cleanLiteral=literal.replace(/[^A-Za-z0-9_$]/g,"").replace(/^_+|_+$/g,"");if(cleanLiteral&&cleanSeg.toLowerCase()===cleanLiteral.toLowerCase()){return true}}}}else{if(cleanSeg.toLowerCase()===pattern.toLowerCase()){return true}}return false});if(matchesLower&&result2===cleanSeg.toLowerCase()){lowerRuleApplied[primaryIdx]=true}return result2});return processedParts.join("")});const camelCasedSegments=processedPrimarySegments.map((seg,idx)=>{const matchesLeave=this.#matchesAnyPattern(seg,leaveRules,true);const matchesLeaveInsensitive=this.#matchesAnyPattern(seg,leaveInsensitiveRules,false);const matchesUpper=this.#matchesAnyPattern(seg,upperRules,false);const hasUnderscores=seg.includes("_");const isAllUpper2=!hasUnderscores&&preserveAllUpper&&seg===seg.toUpperCase()&&seg!==seg.toLowerCase()&&/[A-Z]/.test(seg);const isAllLower2=!hasUnderscores&&preserveAllLower&&seg===seg.toLowerCase()&&seg!==seg.toUpperCase()&&/[a-z]/.test(seg);if(matchesLeave||matchesLeaveInsensitive||matchesUpper||isAllUpper2||isAllLower2){return seg}let transformed;if(idx===0){transformed=lowerFirst?seg[0].toLowerCase()+seg.slice(1):seg}else{if(lowerRuleApplied[idx]){transformed=seg}else{transformed=seg[0].toUpperCase()+seg.slice(1)}}return transformed});let result=camelCasedSegments.join("");result=result.replace(/[^A-Za-z0-9_$]/g,"");return result}getModuleId(filePath,baseDir){let relative=filePath.replace(baseDir,"").replace(/\\/g,"/");relative=relative.replace(/^\//,"");relative=relative.replace(/\.(mjs|cjs|js)$/,"");return relative}shouldPreserveFunctionCase(name){const preservePatterns=[/^[A-Z]{2,}$/,/[A-Z]{2,}/];return preservePatterns.some(pattern=>pattern.test(name))}}function sanitizePropertyName(input,options={}){const sanitizer=new Sanitize(null);return sanitizer.sanitizePropertyName(input,options)}export{Sanitize,sanitizePropertyName};
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import{ComponentBase}from"#factories/component-base";class Utilities extends ComponentBase{static slothletProperty="utilities";isPlainObject(obj){if(typeof obj!=="object"||obj===null)return false;const proto=Object.getPrototypeOf(obj);return proto===null||proto===Object.prototype}deepMerge(target,source){if(!this.isPlainObject(target)||!this.isPlainObject(source)){return source}const result={...target};for(const key in source){if(Object.prototype.hasOwnProperty.call(source,key)){if(this.isPlainObject(source[key])){result[key]=this.deepMerge(this.isPlainObject(target[key])?target[key]:{},source[key])}else{result[key]=source[key]}}}return result}deepClone(obj){try{return structuredClone(obj)}catch{const objType=obj?.__type||typeof obj;if(obj===null||objType!=="object"&&objType!=="function")return obj;if(obj instanceof Date)return new Date(obj.getTime());if(Array.isArray(obj))return obj.map(item=>this.deepClone(item));const cloned={};for(const key in obj){try{cloned[key]=this.deepClone(obj[key])}catch{cloned[key]=obj[key]}}return cloned}}generateId(){return`slothlet_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}}export{Utilities};
|
|
17
|
+
import{ComponentBase}from"#factories/component-base";class Utilities extends ComponentBase{static slothletProperty="utilities";isPlainObject(obj){if(typeof obj!=="object"||obj===null)return false;const proto=Object.getPrototypeOf(obj);return proto===null||proto===Object.prototype}deepMerge(target,source){if(!this.isPlainObject(target)||!this.isPlainObject(source)){return source}const result={...target};for(const key in source){if(Object.prototype.hasOwnProperty.call(source,key)){if(this.isPlainObject(source[key])){result[key]=this.deepMerge(this.isPlainObject(target[key])?target[key]:{},source[key])}else{result[key]=source[key]}}}return result}deepClone(obj){try{return structuredClone(obj)}catch{const objType=obj?.__type||typeof obj;if(obj===null||objType!=="object"&&objType!=="function")return obj;if(typeof obj==="function")return obj;if(obj instanceof Date)return new Date(obj.getTime());if(Array.isArray(obj))return obj.map(item=>this.deepClone(item));const cloned={};for(const key in obj){try{cloned[key]=this.deepClone(obj[key])}catch{cloned[key]=obj[key]}}return cloned}}generateId(){return`slothlet_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}}export{Utilities};
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
"HINT_RUNTIME_NO_ACTIVE_CONTEXT": "metadata.self() must be called from within a slothlet API function.",
|
|
50
50
|
"INVALID_CONFIG_MUTATIONS_DISABLED": "Cannot perform '{operation}' - mutation is disabled. Set allowMutation: true to enable API modification operations (add/remove/reload).",
|
|
51
51
|
"HINT_INVALID_CONFIG_MUTATIONS_DISABLED": "API mutation operations require allowMutation: true in the configuration. Use diagnostics: true to access these methods for testing without enabling actual mutations.",
|
|
52
|
+
"WARNING_API_ADD_OPTION_LOCKED": "'{option}' passed to api.add() was ignored — collision / internal policy is set at init, not per call. Use the `collision` config at slothlet() init, `forceOverwrite` for a targeted replace, or set api.mutations.allowCollisionOverride to true to honor per-call overrides.",
|
|
53
|
+
"HINT_WARNING_API_ADD_OPTION_LOCKED": "collisionMode, mutateExisting and recordHistory are locked so a runtime mount cannot bypass the instance's collision policy; forceOverwrite stays available for a targeted replace.",
|
|
52
54
|
"CACHE_MODULEID_MISMATCH": "Cache entry moduleID mismatch: expected '{cacheKey}', but entry has '{entryModuleID}'. This indicates an internal cache inconsistency.",
|
|
53
55
|
"HINT_CACHE_MODULEID_MISMATCH": "This is an internal error indicating cache corruption. Please report this issue with steps to reproduce.",
|
|
54
56
|
"CACHE_NOT_FOUND": "Cache entry not found for moduleID '{moduleID}' during '{operation}' operation. The module may not be loaded or the cache may have been cleared.",
|
|
@@ -121,6 +123,8 @@
|
|
|
121
123
|
"HINT_WARNING_COLLISION_TRIGGER_MATERIALIZE_ERROR": "This is a non-critical background error during collision handling. The lazy folder at the given path failed to materialize. Check that the module at that path is valid and can be loaded.",
|
|
122
124
|
"WARNING_LIFECYCLE_HANDLER_ERROR": "Lifecycle event handler threw an error for event '{event}'. Other handlers for this event continued executing.",
|
|
123
125
|
"HINT_WARNING_LIFECYCLE_HANDLER_ERROR": "A lifecycle handler registered for the '{event}' event threw an error. Check your lifecycle.on('{event}', ...) handler for bugs. Other handlers in the same event are not affected.",
|
|
126
|
+
"WARNING_EVENT_HANDLER_ERROR": "Event listener threw an error for event '{event}'. Other listeners for this event continued executing.",
|
|
127
|
+
"HINT_WARNING_EVENT_HANDLER_ERROR": "An event listener registered for the '{event}' event threw an error. Check your event.on('{event}', ...) listener for bugs. Other listeners in the same event are not affected.",
|
|
124
128
|
"WARNING_MULTIPLE_ROOT_CONTRIBUTORS": "Multiple root-level default function exports detected: {rootContributors}. Each has been namespaced by filename (e.g., api.{firstContributor}()). Consider using a single root-level default export or moving files to subdirectories.",
|
|
125
129
|
"HINT_WARNING_MULTIPLE_ROOT_CONTRIBUTORS": "Multiple files export default functions at the root level. Each has been namespaced (e.g., api.filename()). Consider consolidating into one root export or moving files to subdirectories for clearer organization.",
|
|
126
130
|
"V3_CONFIG_DEPRECATED": "Configuration option '{option}' is deprecated and will be removed in v4. Use '{replacement}' instead.",
|
|
@@ -427,6 +431,8 @@
|
|
|
427
431
|
"PERM_RULE_CALLER_REQUIRED": "rule.caller must be a non-empty string",
|
|
428
432
|
"PERM_RULE_TARGET_REQUIRED": "rule.target must be a non-empty string",
|
|
429
433
|
"PERM_RULE_EFFECT_INVALID": "rule.effect must be 'allow' or 'deny'",
|
|
434
|
+
"PERM_EVENT_RULE_EVENT_REQUIRED": "rule.event must be a non-empty string",
|
|
435
|
+
"PERM_EVENT_RULE_EFFECT_INVALID": "rule.effect must be 'deny', 'notify', or 'allow'",
|
|
430
436
|
"PERM_RULE_CONDITION_INVALID": "rule.condition must be a plain object, a function, or an array where each entry is a plain object or function",
|
|
431
437
|
"BRACE_EXPANSION_MAX_DEPTH": "Brace expansion exceeded maximum depth of {maxDepth}",
|
|
432
438
|
"HINT_BRACE_EXPANSION_MAX_DEPTH": "Reduce nesting in your brace patterns or increase the maxDepth option.",
|
|
@@ -450,6 +456,8 @@
|
|
|
450
456
|
"MODULE_MANIFEST_REASON_PERMISSION_CALLER": "permissions[{index}].caller must be a non-empty string",
|
|
451
457
|
"MODULE_MANIFEST_REASON_PERMISSION_TARGET": "permissions[{index}].target must be a non-empty string",
|
|
452
458
|
"MODULE_MANIFEST_REASON_PERMISSION_EFFECT": "permissions[{index}].effect must be \"allow\" or \"deny\"",
|
|
459
|
+
"MODULE_MANIFEST_REASON_EVENT_NAME": "events[{index}].event must be a non-empty string",
|
|
460
|
+
"MODULE_MANIFEST_REASON_EVENT_EFFECT": "events[{index}].effect must be \"deny\", \"notify\", or \"allow\"",
|
|
453
461
|
"MODULE_MANIFEST_REASON_JSON_PARSE": "JSON parse error: {error}",
|
|
454
462
|
"GENERATE_MANIFEST_DIR_INVALID": "generateManifest: dir must be a non-empty string, received {received}",
|
|
455
463
|
"GENERATE_MANIFEST_DIR_UNREADABLE": "generateManifest: cannot read directory \"{dir}\": {reason}",
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import{ComponentBase}from"#factories/component-base";class Flatten extends ComponentBase{static slothletProperty="flatten";constructor(slothlet){super(slothlet)}#checkSelfReferential(mod,moduleName){if(!mod||typeof mod!=="object")return false;return mod[moduleName]===mod}async#checkMultiDefault(analysis,hasMultipleDefaults,t){if(!hasMultipleDefaults)return null;if(analysis.hasDefault){return{preserveAsNamespace:true,reason:await t("FLATTEN_REASON_MULTI_DEFAULT_WITH_DEFAULT")}}return{flattenToRoot:true,reason:await t("FLATTEN_REASON_MULTI_DEFAULT_WITHOUT_DEFAULT")}}#checkAutoFlatten(mod,moduleName,moduleKeys){if(moduleKeys.length!==1)return false;return moduleKeys[0]===moduleName}async getFlatteningDecision(options){const{mod,moduleName,categoryName,analysis,hasMultipleDefaults,moduleKeys,t}=options;const isAddapiFile=moduleName==="addapi";if(isAddapiFile){if(analysis.hasDefault&&analysis.defaultExportType==="object"&&moduleKeys.length>0){return{flattenToCategory:true,flattenType:"addapi-metadata-default",reason:await t("FLATTEN_REASON_ADDAPI_METADATA_DEFAULT")}}return{flattenToCategory:true,flattenType:"addapi-special-file",reason:await t("FLATTEN_REASON_ADDAPI_SPECIAL_FILE")}}if(this.#checkSelfReferential(mod,moduleName)){return{preserveAsNamespace:true,reason:await t("FLATTEN_REASON_SELF_REFERENTIAL")}}const multiDefaultDecision=await this.#checkMultiDefault(analysis,hasMultipleDefaults,t);if(multiDefaultDecision){if(multiDefaultDecision.preserveAsNamespace){const exportToCheck2=typeof mod==="function"?mod:mod?.default&&typeof mod.default==="function"?mod.default:null;if(exportToCheck2&&exportToCheck2.name&&exportToCheck2.name!=="default"){const normalizedFunctionName=exportToCheck2.name.toLowerCase().replace(/[-_]/g,"");const normalizedModuleName=moduleName.toLowerCase().replace(/[-_]/g,"");if(normalizedFunctionName===normalizedModuleName){multiDefaultDecision.preferredName=exportToCheck2.name}}}return multiDefaultDecision}if(this.#checkAutoFlatten(mod,moduleName,moduleKeys)){return{useAutoFlattening:true,reason:await t("FLATTEN_REASON_SINGLE_EXPORT_MATCHES_FILENAME")}}if(moduleName===categoryName){return{flattenToCategory:true,reason:await t("FLATTEN_REASON_FILENAME_MATCHES_CATEGORY")}}const exportToCheck=typeof mod==="function"?mod:mod?.default&&typeof mod.default==="function"?mod.default:null;if(exportToCheck&&exportToCheck.name&&exportToCheck.name!=="default"){const normalizedFunctionName=exportToCheck.name.toLowerCase().replace(/[-_]/g,"");const normalizedModuleName=moduleName.toLowerCase().replace(/[-_]/g,"");const functionNameMatchesFilename=normalizedFunctionName===normalizedModuleName;if(functionNameMatchesFilename){return{preserveAsNamespace:true,preferredName:exportToCheck.name,reason:await t("FLATTEN_REASON_PRESERVING_FUNCTION_NAME")}}}return{preserveAsNamespace:true,reason:await t("FLATTEN_REASON_DEFAULT_PRESERVE_NAMESPACE")}}processModuleForAPI(options){const{mod,decision,moduleName,propertyName,moduleKeys,analysis,file=null,collisionContext="initial",apiPathPrefix="",isSelfReferential=false,collisionModeOverride=null}=options;const isAddapiFile=moduleName==="addapi"||file&&file.name==="addapi"||file&&file.fullName&&["addapi.mjs","addapi.cjs","addapi.js","addapi.ts"].includes(file.fullName.toLowerCase());if(isAddapiFile&&analysis.hasDefault&&moduleKeys.length>0){const moduleContent2=mod.default;for(const key of moduleKeys){moduleContent2[key]=mod[key]}return{moduleContent:moduleContent2}}if(decision.useAutoFlattening){return{moduleContent:mod[moduleName]}}if(decision.flattenToRoot||decision.flattenToCategory){if(mod.default&&moduleKeys.length===0){return{moduleContent:mod.default}}if(mod.default&&moduleKeys.length>0){const moduleContent3=typeof mod.default==="function"?mod.default:{...mod.default};for(const key of moduleKeys){moduleContent3[key]=mod[key]}return{moduleContent:moduleContent3}}const moduleContent2={};for(const key of moduleKeys){moduleContent2[key]=mod[key]}return{moduleContent:moduleContent2}}if(isSelfReferential){return{moduleContent:mod[moduleName]||mod}}if(mod.default&&moduleKeys.length>0){if(typeof mod.default==="function"){const moduleContent3=this.slothlet.helpers.modesUtils.ensureNamedExportFunction(mod.default,propertyName);const collisionConfig=this.slothlet.config.api?.collision||this.slothlet.config.collision;const collisionMode=collisionModeOverride||(collisionContext==="initial"?collisionConfig.initial:collisionConfig.api);for(const key of moduleKeys){if(!this.shouldAttachNamedExport(key,mod[key],moduleContent3,mod.default)){continue}const hasExisting=Object.prototype.hasOwnProperty.call(moduleContent3,key);if(hasExisting){if(collisionMode==="merge"||collisionMode==="skip"){continue}else if(collisionMode==="error"){throw new this.slothlet.SlothletError("COLLISION_DEFAULT_EXPORT_ERROR",{key,apiPath:`${apiPathPrefix}.${propertyName}`},null,{validationError:true})}else if(collisionMode==="warn"){new this.slothlet.SlothletWarning("WARNING_COLLISION_DEFAULT_EXPORT_OVERWRITE",{key,apiPath:`${apiPathPrefix}.${propertyName}`})}}moduleContent3[key]=mod[key]}return{moduleContent:moduleContent3}}if(typeof mod.default==="object"&&mod.default!==null){const moduleContent3=mod.default;for(const key of moduleKeys){if(key in mod.default){continue}if(!this.shouldAttachNamedExport(key,mod[key],moduleContent3,mod.default)){continue}moduleContent3[key]=mod[key]}return{moduleContent:moduleContent3}}const moduleContent2={default:mod.default};for(const key of moduleKeys){moduleContent2[key]=mod[key]}return{moduleContent:moduleContent2}}if(mod.default&&moduleKeys.length===0){return{moduleContent:this.slothlet.helpers.modesUtils.ensureNamedExportFunction(mod.default,propertyName)}}const moduleContent={};for(const key of moduleKeys){moduleContent[key]=mod[key]}return{moduleContent}}async buildCategoryDecisions(options){const{categoryName,mod,moduleName,fileBaseName,analysis,moduleKeys,currentDepth,moduleFiles=[],t}=options;const decision={shouldFlatten:false,flattenType:"preserve",preferredName:null,reason:await t("FLATTEN_REASON_NO_CONDITIONS_MET")};const isAddapiFile=moduleName==="addapi"||fileBaseName==="addapi"||fileBaseName&&["addapi.mjs","addapi.cjs","addapi.js","addapi.ts"].includes(fileBaseName.toLowerCase());if(isAddapiFile){if(analysis.hasDefault&&analysis.defaultExportType==="object"&&moduleKeys.length>0){return{shouldFlatten:true,flattenType:"addapi-metadata-default",reason:await t("FLATTEN_REASON_ADDAPI_SPECIAL_FILE_PARENT")}}return{shouldFlatten:true,flattenType:"addapi-special-file",reason:await t("FLATTEN_REASON_ADDAPI_SPECIAL_FILE")}}if(moduleName===categoryName&&typeof mod==="function"&¤tDepth>0){return{shouldFlatten:true,flattenType:"function-folder-match",reason:await t("FLATTEN_REASON_FUNCTION_FOLDER_MATCH")}}if(analysis.hasDefault&&analysis.defaultExportType==="object"&&moduleName===categoryName&¤tDepth>0){return{shouldFlatten:true,flattenType:"default-export-flatten",reason:await t("FLATTEN_REASON_DEFAULT_OBJECT_EXPORT_FLATTEN")}}if(moduleName===categoryName&&mod&&typeof mod==="object"&&!Array.isArray(mod)&¤tDepth>0){if(moduleKeys.length===1&&moduleKeys[0]===moduleName){return{shouldFlatten:true,flattenType:"object-auto-flatten",reason:await t("FLATTEN_REASON_SINGLE_EXPORT_MATCHES_FILENAME")}}}if(fileBaseName===categoryName&&moduleKeys.length>0){return{shouldFlatten:true,flattenType:"filename-folder-match-flatten",reason:await t("FLATTEN_REASON_BASENAME_MATCHES_CATEGORY")}}if(moduleFiles.length===1&¤tDepth>0&&mod&&typeof mod==="object"&&!Array.isArray(mod)){const genericFilenames=["singlefile","index","main","default"];const isGenericFilename=genericFilenames.includes(moduleName.toLowerCase());if(moduleKeys.length===1&&isGenericFilename){return{shouldFlatten:true,flattenType:"parent-level-flatten",reason:await t("FLATTEN_REASON_GENERIC_FILENAME_SINGLE_EXPORT")}}}if(typeof mod==="function"&&mod.name&¤tDepth>0){const functionNameMatchesFolder=mod.name.toLowerCase()===categoryName.toLowerCase()||mod.name.toLowerCase().replace(/[-_]/g,"")===categoryName.toLowerCase().replace(/[-_]/g,"");if(functionNameMatchesFolder){return{shouldFlatten:true,flattenType:"function-folder-match",preferredName:mod.name,reason:await t("FLATTEN_REASON_FUNCTION_FOLDER_MATCH")}}}const exportToCheck=typeof mod==="function"?mod:mod?.default&&typeof mod.default==="function"?mod.default:null;if(exportToCheck&&exportToCheck.name&&exportToCheck.name!=="default"){const normalizedFunctionName=exportToCheck.name.toLowerCase().replace(/[-_]/g,"");const normalizedModuleName=moduleName.toLowerCase().replace(/[-_]/g,"");const functionNameMatchesFilename=normalizedFunctionName===normalizedModuleName;if(functionNameMatchesFilename){return{shouldFlatten:false,preferredName:exportToCheck.name,reason:await t("FLATTEN_REASON_PRESERVING_FUNCTION_NAME")}}}if(typeof mod==="function"&&(!mod.name||mod.name==="default"||mod.__slothletDefault===true)&¤tDepth>0){return{shouldFlatten:true,flattenType:"default-function",preferredName:categoryName,reason:await t("FLATTEN_REASON_DEFAULT_FUNCTION_EXPORT")}}if(moduleKeys.length===1&&moduleKeys[0]===moduleName){return{shouldFlatten:true,flattenType:"object-auto-flatten",preferredName:moduleName,reason:await t("FLATTEN_REASON_SINGLE_EXPORT_MATCHES_MODULE")}}return decision}shouldAttachNamedExport(key,value,defaultFunc,originalDefault){if(!key||key==="default"){return false}if(value===defaultFunc||value===originalDefault){return false}if(typeof defaultFunc==="function"&&key===defaultFunc.name){return false}if(typeof originalDefault==="function"&&key===originalDefault.name){return false}return true}}export{Flatten};
|
|
17
|
+
import{ComponentBase}from"#factories/component-base";class Flatten extends ComponentBase{static slothletProperty="flatten";constructor(slothlet){super(slothlet)}#checkSelfReferential(mod,moduleName){if(!mod||typeof mod!=="object")return false;return mod[moduleName]===mod}async#checkMultiDefault(analysis,hasMultipleDefaults,t){if(!hasMultipleDefaults)return null;if(analysis.hasDefault){return{preserveAsNamespace:true,reason:await t("FLATTEN_REASON_MULTI_DEFAULT_WITH_DEFAULT")}}return{flattenToRoot:true,reason:await t("FLATTEN_REASON_MULTI_DEFAULT_WITHOUT_DEFAULT")}}#checkAutoFlatten(mod,moduleName,moduleKeys){if(moduleKeys.length!==1)return false;return moduleKeys[0]===moduleName}async getFlatteningDecision(options){const{mod,moduleName,categoryName,analysis,hasMultipleDefaults,moduleKeys,t}=options;const isAddapiFile=moduleName==="addapi";if(isAddapiFile){if(analysis.hasDefault&&analysis.defaultExportType==="object"&&moduleKeys.length>0){return{flattenToCategory:true,flattenType:"addapi-metadata-default",reason:await t("FLATTEN_REASON_ADDAPI_METADATA_DEFAULT")}}return{flattenToCategory:true,flattenType:"addapi-special-file",reason:await t("FLATTEN_REASON_ADDAPI_SPECIAL_FILE")}}if(this.#checkSelfReferential(mod,moduleName)){return{preserveAsNamespace:true,reason:await t("FLATTEN_REASON_SELF_REFERENTIAL")}}const multiDefaultDecision=await this.#checkMultiDefault(analysis,hasMultipleDefaults,t);if(multiDefaultDecision){if(multiDefaultDecision.preserveAsNamespace){const exportToCheck2=typeof mod==="function"?mod:mod?.default&&typeof mod.default==="function"?mod.default:null;if(exportToCheck2&&exportToCheck2.name&&exportToCheck2.name!=="default"){const normalizedFunctionName=exportToCheck2.name.toLowerCase().replace(/[-_]/g,"");const normalizedModuleName=moduleName.toLowerCase().replace(/[-_]/g,"");if(normalizedFunctionName===normalizedModuleName){multiDefaultDecision.preferredName=exportToCheck2.name}}}return multiDefaultDecision}if(this.#checkAutoFlatten(mod,moduleName,moduleKeys)){return{useAutoFlattening:true,reason:await t("FLATTEN_REASON_SINGLE_EXPORT_MATCHES_FILENAME")}}if(moduleName===categoryName){return{flattenToCategory:true,reason:await t("FLATTEN_REASON_FILENAME_MATCHES_CATEGORY")}}const exportToCheck=typeof mod==="function"?mod:mod?.default&&typeof mod.default==="function"?mod.default:null;if(exportToCheck&&exportToCheck.name&&exportToCheck.name!=="default"){const normalizedFunctionName=exportToCheck.name.toLowerCase().replace(/[-_]/g,"");const normalizedModuleName=moduleName.toLowerCase().replace(/[-_]/g,"");const functionNameMatchesFilename=normalizedFunctionName===normalizedModuleName;if(functionNameMatchesFilename){return{preserveAsNamespace:true,preferredName:exportToCheck.name,reason:await t("FLATTEN_REASON_PRESERVING_FUNCTION_NAME")}}}return{preserveAsNamespace:true,reason:await t("FLATTEN_REASON_DEFAULT_PRESERVE_NAMESPACE")}}processModuleForAPI(options){const{mod,decision,moduleName,propertyName,moduleKeys,analysis,file=null,collisionContext="initial",apiPathPrefix="",isSelfReferential=false,collisionModeOverride=null}=options;const isAddapiFile=moduleName==="addapi"||file&&file.name==="addapi"||file&&file.fullName&&["addapi.mjs","addapi.cjs","addapi.js","addapi.ts"].includes(file.fullName.toLowerCase());if(isAddapiFile&&analysis.hasDefault&&moduleKeys.length>0){const moduleContent2=mod.default;for(const key of moduleKeys){moduleContent2[key]=mod[key]}return{moduleContent:moduleContent2}}if(decision.useAutoFlattening){return{moduleContent:mod[moduleName]}}if(decision.flattenToRoot||decision.flattenToCategory){if(mod.default&&moduleKeys.length===0){return{moduleContent:mod.default}}if(mod.default&&moduleKeys.length>0){const moduleContent3=typeof mod.default==="function"?mod.default:{...mod.default};for(const key of moduleKeys){moduleContent3[key]=mod[key]}return{moduleContent:moduleContent3}}const moduleContent2={};for(const key of moduleKeys){moduleContent2[key]=mod[key]}return{moduleContent:moduleContent2}}if(isSelfReferential){return{moduleContent:mod[moduleName]||mod}}if(mod.default&&moduleKeys.length>0){if(typeof mod.default==="function"){const moduleContent3=this.slothlet.helpers.modesUtils.ensureNamedExportFunction(mod.default,propertyName);const collisionConfig=this.slothlet.config.api?.collision||this.slothlet.config.collision;const collisionMode=collisionModeOverride||(collisionContext==="initial"?collisionConfig.initial:collisionConfig.api);for(const key of moduleKeys){if(!this.shouldAttachNamedExport(key,mod[key],moduleContent3,mod.default)){continue}const hasExisting=Object.prototype.hasOwnProperty.call(moduleContent3,key);if(hasExisting){if(collisionMode==="merge"||collisionMode==="skip"){continue}else if(collisionMode==="error"){throw new this.slothlet.SlothletError("COLLISION_DEFAULT_EXPORT_ERROR",{key,apiPath:`${apiPathPrefix}.${propertyName}`},null,{validationError:true})}else if(collisionMode==="warn"){new this.slothlet.SlothletWarning("WARNING_COLLISION_DEFAULT_EXPORT_OVERWRITE",{key,apiPath:`${apiPathPrefix}.${propertyName}`})}}moduleContent3[key]=mod[key]}return{moduleContent:moduleContent3}}if(typeof mod.default==="object"&&mod.default!==null){const moduleContent3=mod.default;const collisionConfig=this.slothlet.config.api?.collision||this.slothlet.config.collision;const collisionMode=collisionModeOverride||(collisionContext==="initial"?collisionConfig.initial:collisionConfig.api);for(const key of moduleKeys){if(!this.shouldAttachNamedExport(key,mod[key],moduleContent3,mod.default)){continue}const hasExisting=key in mod.default;if(hasExisting){if(collisionMode==="merge"||collisionMode==="skip"){continue}else if(collisionMode==="error"){throw new this.slothlet.SlothletError("COLLISION_DEFAULT_EXPORT_ERROR",{key,apiPath:`${apiPathPrefix}.${propertyName}`},null,{validationError:true})}else if(collisionMode==="warn"){new this.slothlet.SlothletWarning("WARNING_COLLISION_DEFAULT_EXPORT_OVERWRITE",{key,apiPath:`${apiPathPrefix}.${propertyName}`})}}moduleContent3[key]=mod[key]}return{moduleContent:moduleContent3}}const moduleContent2={default:mod.default};for(const key of moduleKeys){moduleContent2[key]=mod[key]}return{moduleContent:moduleContent2}}if(mod.default&&moduleKeys.length===0){return{moduleContent:this.slothlet.helpers.modesUtils.ensureNamedExportFunction(mod.default,propertyName)}}const moduleContent={};for(const key of moduleKeys){moduleContent[key]=mod[key]}return{moduleContent}}async buildCategoryDecisions(options){const{categoryName,mod,moduleName,fileBaseName,analysis,moduleKeys,currentDepth,moduleFiles=[],t}=options;const decision={shouldFlatten:false,flattenType:"preserve",preferredName:null,reason:await t("FLATTEN_REASON_NO_CONDITIONS_MET")};const isAddapiFile=moduleName==="addapi"||fileBaseName==="addapi"||fileBaseName&&["addapi.mjs","addapi.cjs","addapi.js","addapi.ts"].includes(fileBaseName.toLowerCase());if(isAddapiFile){if(analysis.hasDefault&&analysis.defaultExportType==="object"&&moduleKeys.length>0){return{shouldFlatten:true,flattenType:"addapi-metadata-default",reason:await t("FLATTEN_REASON_ADDAPI_SPECIAL_FILE_PARENT")}}return{shouldFlatten:true,flattenType:"addapi-special-file",reason:await t("FLATTEN_REASON_ADDAPI_SPECIAL_FILE")}}if(moduleName===categoryName&&typeof mod==="function"&¤tDepth>0){return{shouldFlatten:true,flattenType:"function-folder-match",reason:await t("FLATTEN_REASON_FUNCTION_FOLDER_MATCH")}}if(analysis.hasDefault&&analysis.defaultExportType==="object"&&moduleName===categoryName&¤tDepth>0){return{shouldFlatten:true,flattenType:"default-export-flatten",reason:await t("FLATTEN_REASON_DEFAULT_OBJECT_EXPORT_FLATTEN")}}if(moduleName===categoryName&&mod&&typeof mod==="object"&&!Array.isArray(mod)&¤tDepth>0){if(moduleKeys.length===1&&moduleKeys[0]===moduleName){return{shouldFlatten:true,flattenType:"object-auto-flatten",reason:await t("FLATTEN_REASON_SINGLE_EXPORT_MATCHES_FILENAME")}}}if(fileBaseName===categoryName&&moduleKeys.length>0){return{shouldFlatten:true,flattenType:"filename-folder-match-flatten",reason:await t("FLATTEN_REASON_BASENAME_MATCHES_CATEGORY")}}if(moduleFiles.length===1&¤tDepth>0&&mod&&typeof mod==="object"&&!Array.isArray(mod)){const genericFilenames=["singlefile","index","main","default"];const isGenericFilename=genericFilenames.includes(moduleName.toLowerCase());if(moduleKeys.length===1&&isGenericFilename){return{shouldFlatten:true,flattenType:"parent-level-flatten",reason:await t("FLATTEN_REASON_GENERIC_FILENAME_SINGLE_EXPORT")}}}if(typeof mod==="function"&&mod.name&¤tDepth>0){const functionNameMatchesFolder=mod.name.toLowerCase()===categoryName.toLowerCase()||mod.name.toLowerCase().replace(/[-_]/g,"")===categoryName.toLowerCase().replace(/[-_]/g,"");if(functionNameMatchesFolder){return{shouldFlatten:true,flattenType:"function-folder-match",preferredName:mod.name,reason:await t("FLATTEN_REASON_FUNCTION_FOLDER_MATCH")}}}const exportToCheck=typeof mod==="function"?mod:mod?.default&&typeof mod.default==="function"?mod.default:null;if(exportToCheck&&exportToCheck.name&&exportToCheck.name!=="default"){const normalizedFunctionName=exportToCheck.name.toLowerCase().replace(/[-_]/g,"");const normalizedModuleName=moduleName.toLowerCase().replace(/[-_]/g,"");const functionNameMatchesFilename=normalizedFunctionName===normalizedModuleName;if(functionNameMatchesFilename){return{shouldFlatten:false,preferredName:exportToCheck.name,reason:await t("FLATTEN_REASON_PRESERVING_FUNCTION_NAME")}}}if(typeof mod==="function"&&(!mod.name||mod.name==="default"||mod.__slothletDefault===true)&¤tDepth>0){return{shouldFlatten:true,flattenType:"default-function",preferredName:categoryName,reason:await t("FLATTEN_REASON_DEFAULT_FUNCTION_EXPORT")}}if(moduleKeys.length===1&&moduleKeys[0]===moduleName){return{shouldFlatten:true,flattenType:"object-auto-flatten",preferredName:moduleName,reason:await t("FLATTEN_REASON_SINGLE_EXPORT_MATCHES_MODULE")}}return decision}shouldAttachNamedExport(key,value,defaultFunc,originalDefault){if(!key||key==="default"){return false}if(value===defaultFunc||value===originalDefault){return false}if(typeof defaultFunc==="function"&&key===defaultFunc.name){return false}if(typeof originalDefault==="function"&&key===originalDefault.name){return false}return true}}export{Flatten};
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import{ComponentBase}from"#factories/component-base";import{DEFAULT_API_DEPTH}from"@cldmv/slothlet/helpers/defaults";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=DEFAULT_API_DEPTH,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&¤tDepth<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};
|
|
17
|
+
import{ComponentBase}from"#factories/component-base";import{DEFAULT_API_DEPTH}from"@cldmv/slothlet/helpers/defaults";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=DEFAULT_API_DEPTH,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&¤tDepth<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,hidden=null,scanHiddenFolders=false,maxDepth=DEFAULT_API_DEPTH,currentDepth=0,apiPrefix=""}=options;const ALLOWED_EXTS=[".mjs",".cjs",".js"];const hiddenMatcher=typeof hidden==="function"?hidden:compileHidden(hidden);const hasHiddenPrefix=n=>n.startsWith(".")||n.startsWith("__");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(hasHiddenPrefix(fullName))continue;if(fileFilter&&!fileFilter(fullName))continue;const name=file.name||(lastDot>=0?fullName.slice(0,lastDot):fullName);if(hiddenMatcher&&hiddenMatcher(apiPrefix?`${apiPrefix}.${name}`:name))continue;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||"";const dirName=dir.name||dirPath.split("/").pop();if(!scanHiddenFolders&&hasHiddenPrefix(dirName))continue;const dirApiRel=apiPrefix?`${apiPrefix}.${dirName}`:dirName;if(hiddenMatcher&&hiddenMatcher(dirApiRel))continue;if(currentDepth>=maxDepth)continue;const children=this.#manifestNodeToStructure(dir.children||dir,dirPath,{...options,hidden:hiddenMatcher,currentDepth:currentDepth+1,apiPrefix:dirApiRel});if(children.files.length===0&&children.directories.length===0)continue;structure.directories.push({path:dirPath,name:dirName,children})}}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};
|
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{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{isFrameworkInternal}from"#handlers/framework-internals";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";import{enableEventTargetPropertyPatching,disableEventTargetPropertyPatching}from"@cldmv/slothlet/helpers/eventtarget-property-context";import{enableObserverPatching,disableObserverPatching}from"@cldmv/slothlet/helpers/observer-context";import{DEFAULT_ROUTINES,RESERVED_EXPORTS}from"@cldmv/slothlet/helpers/defaults";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/routine-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.routineManager){this.handlers.lifecycle.subscribe("impl:created",data=>{this.handlers.routineManager.onImplCreated(data)});this.handlers.lifecycle.subscribe("impl:changed",data=>{this.handlers.routineManager.onImplCreated(data)});this.handlers.lifecycle.subscribe("impl:removed",data=>{this.handlers.routineManager.onImplRemoved(data)})}if(this.handlers.ownership){this.handlers.lifecycle.subscribe("impl:created",data=>{const configCollisionMode=this.config?.collision?.api||"merge";const collisionMode=configCollisionMode==="replace"||configCollisionMode==="merge-replace"?configCollisionMode:"merge";const implValue=data.wrapper?.__impl??data.impl;if(typeof implValue==="function"&&implValue.__slothletRoutineStack===true){return}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 configCollisionMode=this.config?.collision?.api||"merge";const collisionMode=configCollisionMode==="replace"||configCollisionMode==="merge-replace"?configCollisionMode:"merge";const implValue=data.wrapper?.__impl??data.impl;if(typeof implValue==="function"&&implValue.__slothletRoutineStack===true){return}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);if(this.handlers.routineManager){this.handlers.routineManager.reset()}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();enableEventTargetPropertyPatching();enableObserverPatching();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?.initial||"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)}if(this.handlers.routineManager){await this.handlers.routineManager.rebuildStacks(this.boundApi);await this.handlers.routineManager.runStartupModeRoutines()}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},versionConfig:operation.versionConfig||null})}else if(operation.type==="remove"){if(operation.scopedModuleID){await this.handlers.apiManager.removeApiComponent(operation.scopedModuleID,{scopedApiPath:operation.apiPath,recordHistory:false})}else{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(isFrameworkInternal(obj))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 shutdown(){if(!this.isLoaded){return}await this._drainInFlightLoads();boundaryPatchHolders.delete(this.instanceID);if(boundaryPatchHolders.size===0){disableEventEmitterPatching();disableSchedulerPatching();disableEventTargetPatching();disableEventTargetPropertyPatching();disableObserverPatching();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}slothlet.defaults=Object.freeze({routines:DEFAULT_ROUTINES,reservedExports:RESERVED_EXPORTS});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{isFrameworkInternal}from"#handlers/framework-internals";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";import{enableEventTargetPropertyPatching,disableEventTargetPropertyPatching}from"@cldmv/slothlet/helpers/eventtarget-property-context";import{enableObserverPatching,disableObserverPatching}from"@cldmv/slothlet/helpers/observer-context";import{DEFAULT_ROUTINES,RESERVED_EXPORTS}from"@cldmv/slothlet/helpers/defaults";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/event-manager","#handlers/hook-manager","#handlers/lifecycle","#handlers/materialize-manager","#handlers/metadata","#handlers/module-manager","#handlers/ownership","#handlers/permission-manager","#handlers/routine-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.subscribeInternal("impl:created",(data,token)=>{this.handlers.metadata.tagSystemMetadata(data.__wrapperRef??data.wrapper?.__impl,{filePath:data.filePath,apiPath:data.apiPath,moduleID:data.moduleID,sourceFolder:data.sourceFolder},token)});this.handlers.lifecycle.subscribeInternal("impl:changed",(data,token)=>{this.handlers.metadata.tagSystemMetadata(data.wrapper?.__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.routineManager){this.handlers.lifecycle.subscribeInternal("impl:created",data=>{this.handlers.routineManager.onImplCreated(data)});this.handlers.lifecycle.subscribeInternal("impl:changed",data=>{this.handlers.routineManager.onImplCreated(data)});this.handlers.lifecycle.subscribe("impl:removed",data=>{this.handlers.routineManager.onImplRemoved(data)})}if(this.handlers.ownership){this.handlers.lifecycle.subscribeInternal("impl:created",data=>{const configCollisionMode=this.config?.collision?.api||"merge";const collisionMode=configCollisionMode==="replace"||configCollisionMode==="merge-replace"?configCollisionMode:"merge";const implValue=data.wrapper?.__impl??data.__wrapperRef;if(typeof implValue==="function"&&implValue.__slothletRoutineStack===true){return}this.handlers.ownership.register({moduleID:data.moduleID,apiPath:data.apiPath,value:implValue,source:data.source,filePath:data.filePath,collisionMode});if(this.handlers.ownership.getCurrentOwner(data.apiPath)?.moduleID===data.moduleID){void this.handlers.lifecycle.emit("impl:created",{apiPath:data.apiPath,wrapper:data.wrapper,source:data.source,moduleID:data.moduleID,filePath:data.filePath,sourceFolder:data.sourceFolder})}});this.handlers.lifecycle.subscribeInternal("impl:changed",data=>{const configCollisionMode=this.config?.collision?.api||"merge";const collisionMode=configCollisionMode==="replace"||configCollisionMode==="merge-replace"?configCollisionMode:"merge";const implValue=data.wrapper?.__impl??data.__wrapperRef;if(typeof implValue==="function"&&implValue.__slothletRoutineStack===true){return}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})}if(this.handlers.ownership.getCurrentOwner(data.apiPath)?.moduleID===data.moduleID){void this.handlers.lifecycle.emit("impl:changed",{apiPath:data.apiPath,wrapper:data.wrapper,source:data.source,moduleID:data.moduleID,filePath:data.filePath,sourceFolder:data.sourceFolder})}})}}_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);if(this.handlers.routineManager){this.handlers.routineManager.reset()}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();enableEventTargetPropertyPatching();enableObserverPatching();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?.initial||"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)}if(this.handlers.routineManager){await this.handlers.routineManager.rebuildStacks(this.boundApi);await this.handlers.routineManager.runStartupModeRoutines()}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},versionConfig:operation.versionConfig||null})}else if(operation.type==="remove"){if(operation.scopedModuleID){await this.handlers.apiManager.removeApiComponent(operation.scopedModuleID,{scopedApiPath:operation.apiPath,recordHistory:false})}else{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)}}else if(operation.type==="addEventRule"){if(this.handlers.permissionManager){this.handlers.permissionManager.addEventRule(operation.rule,operation.ownerModuleID,operation.ruleId)}}else{if(operation.type==="removeEventRule"){if(this.handlers.permissionManager){this.handlers.permissionManager.removeEventRule(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(isFrameworkInternal(obj))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 shutdown(){if(!this.isLoaded){return}await this._drainInFlightLoads();boundaryPatchHolders.delete(this.instanceID);if(boundaryPatchHolders.size===0){disableEventEmitterPatching();disableSchedulerPatching();disableEventTargetPatching();disableEventTargetPropertyPatching();disableObserverPatching();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();this.handlers.eventManager?.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}slothlet.defaults=Object.freeze({routines:DEFAULT_ROUTINES,reservedExports:RESERVED_EXPORTS});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.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"moduleVersions": {
|
|
5
5
|
"lazy": "3.0.0",
|
|
6
6
|
"eager": "3.0.0",
|
|
@@ -148,6 +148,7 @@
|
|
|
148
148
|
"types": "./types/stub/index.d.mts",
|
|
149
149
|
"scripts": {
|
|
150
150
|
"precommit": "node tools/dev/precommit-validation.mjs",
|
|
151
|
+
"prepare": "node -e \"import('./.githooks/install.mjs').catch(()=>{})\"",
|
|
151
152
|
"debug": "node tests/debug-slothlet.mjs",
|
|
152
153
|
"i18n:check": "node tools/ci/check-i18n-languages.mjs",
|
|
153
154
|
"vitest": "node tests/vitests/run-all-vitest.mjs --workers 8",
|
|
@@ -244,12 +244,13 @@ export class Config extends ComponentBase {
|
|
|
244
244
|
* a root cascade runs every matching contribution anywhere, ordered per the entry's `order`.
|
|
245
245
|
* See `docs/LIFECYCLE.md` ("Routines") for the full contract.
|
|
246
246
|
*
|
|
247
|
-
* Each entry normalizes to `{ name, mode, recursive, order }` — `recursive
|
|
248
|
-
* always present on the normalized output, even when the raw entry omitted them:
|
|
249
|
-
* - `"name"` (string, no `:`) → `{ name, mode: "manual", recursive: false, order: "mount" }`.
|
|
250
|
-
* - `"name:mode"` (string, split once on the first `:`) → `{ name, mode, recursive: false, order: <mode-defaulted
|
|
251
|
-
* - `{ name, mode?, recursive?, order? }` (object) → `mode` defaults to `"manual"`, `recursive`
|
|
252
|
-
* `false`,
|
|
247
|
+
* Each entry normalizes to `{ name, mode, recursive, order, cascade }` — `recursive`, `order` and
|
|
248
|
+
* `cascade` are always present on the normalized output, even when the raw entry omitted them:
|
|
249
|
+
* - `"name"` (string, no `:`) → `{ name, mode: "manual", recursive: false, order: "mount", cascade: true }`.
|
|
250
|
+
* - `"name:mode"` (string, split once on the first `:`) → `{ name, mode, recursive: false, order: <mode-defaulted>, cascade: true }`.
|
|
251
|
+
* - `{ name, mode?, recursive?, order?, cascade? }` (object) → `mode` defaults to `"manual"`, `recursive`
|
|
252
|
+
* to `false`, `order` to {@link DEFAULT_ROUTINE_ORDER_BY_MODE}`[mode]`, and `cascade` to `true` when each
|
|
253
|
+
* is omitted. `cascade: false` (#400) suppresses the root `api.<name>()` run-all cascade for that routine.
|
|
253
254
|
*
|
|
254
255
|
* Providing `routines` at all REPLACES {@link DEFAULT_ROUTINES} — that is the off-switch
|
|
255
256
|
* (`routines: []` disables every routine). Omitting the option keeps the built-in defaults.
|
|
@@ -260,8 +261,8 @@ export class Config extends ComponentBase {
|
|
|
260
261
|
* normalizes to an equivalent list — same values, always freshly-built objects (never the same
|
|
261
262
|
* references) — so `reload()` can safely re-feed it.
|
|
262
263
|
*
|
|
263
|
-
* @param {undefined|null|Array<string|{name: string, mode?: string, recursive?: boolean, order?: string}>} routines - Raw `routines` option.
|
|
264
|
-
* @returns {Array<{name: string, mode: "manual"|"startup"|"shutdown"|"destroy", recursive: boolean, order: "mount"|"depth"}>} Normalized routines list.
|
|
264
|
+
* @param {undefined|null|Array<string|{name: string, mode?: string, recursive?: boolean, order?: string, cascade?: boolean}>} routines - Raw `routines` option.
|
|
265
|
+
* @returns {Array<{name: string, mode: "manual"|"startup"|"shutdown"|"destroy", recursive: boolean, order: "mount"|"depth", cascade: boolean}>} Normalized routines list.
|
|
265
266
|
* @throws {SlothletError} INVALID_CONFIG when the shape is invalid, a name is empty/reserved/an invalid glob, or a mode/order is unrecognized.
|
|
266
267
|
* @public
|
|
267
268
|
*
|
|
@@ -285,11 +286,13 @@ export class Config extends ComponentBase {
|
|
|
285
286
|
mode?: string;
|
|
286
287
|
recursive?: boolean;
|
|
287
288
|
order?: string;
|
|
289
|
+
cascade?: boolean;
|
|
288
290
|
}>): Array<{
|
|
289
291
|
name: string;
|
|
290
292
|
mode: "manual" | "startup" | "shutdown" | "destroy";
|
|
291
293
|
recursive: boolean;
|
|
292
294
|
order: "mount" | "depth";
|
|
295
|
+
cascade: boolean;
|
|
293
296
|
}>;
|
|
294
297
|
/**
|
|
295
298
|
* Normalize permissions configuration.
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Compile a glob pattern into a matcher function.
|
|
5
5
|
* Supports: * (any chars except .), ** (any chars including .), ? (single char),
|
|
6
|
-
* {a,b} brace expansion, !pattern negation
|
|
6
|
+
* {a,b} brace expansion, !pattern whole-pattern negation, and !(a|b) scoped exclusion —
|
|
7
|
+
* a per-segment complement matching any single segment except the listed literal alternatives
|
|
8
|
+
* (e.g. `admin.!(initialize)` matches `admin.start` but not `admin.initialize`).
|
|
7
9
|
*
|
|
8
10
|
* @param {string} pattern - Glob pattern
|
|
9
11
|
* @param {object} [options={}] - Options
|
|
@@ -40,8 +40,10 @@ export class Utilities extends ComponentBase {
|
|
|
40
40
|
* Strategy:
|
|
41
41
|
* 1. Try `structuredClone` — fast and spec-correct for plain data.
|
|
42
42
|
* 2. Fall back to a manual recursive copy for Proxies, callables, and other
|
|
43
|
-
* non-serialisable objects
|
|
44
|
-
*
|
|
43
|
+
* non-serialisable objects. Callables (functions / callable Proxies) are kept
|
|
44
|
+
* BY REFERENCE — they cannot be reconstructed from a property copy — while the
|
|
45
|
+
* surrounding data is still deep-cloned; errors on individual property clones are
|
|
46
|
+
* swallowed and the original reference is retained for that key.
|
|
45
47
|
*
|
|
46
48
|
* @param {unknown} obj - Value to clone.
|
|
47
49
|
* @returns {unknown} Deep clone of `obj`.
|
|
@@ -80,7 +80,7 @@ export class Flatten extends ComponentBase {
|
|
|
80
80
|
};
|
|
81
81
|
/**
|
|
82
82
|
* Build category-level flattening decisions.
|
|
83
|
-
* Implements conditions C10-
|
|
83
|
+
* Implements conditions C10-C24 from buildCategoryDecisions().
|
|
84
84
|
* @param {object} options - Category options
|
|
85
85
|
* @param {string} options.categoryName - Category name
|
|
86
86
|
* @param {object} options.mod - Module exports
|