Document Hierarchy: API Rules Index → Flattening Patterns → Conditions Reference → Rule Mapping
Purpose: Complete reference for all source-level conditions that implement flattening behavior. Each entry maps to the rule and flattening pattern it enforces, and links directly to the source function and line range.
The flattening logic lives in three functions across two source files:
| Function | File | Description |
|---|---|---|
getFlatteningDecision() |
src/lib/processors/flatten.mjs |
Per-module single-file flattening verdict |
processModuleForAPI() |
src/lib/processors/flatten.mjs |
Applies flattening decision during module processing |
buildCategoryDecisions() |
src/lib/processors/flatten.mjs |
Directory-level flattening for multi-file folders |
Category: Basic Flattening Related Rule: Rule 6 Flattening Guide: F01: Basic Flattening Rules Status: ✅ Active
Pattern: A module that exports itself as a named export is self-referential and must not be flattened into itself.
Function: getFlatteningDecision()
Source: src/lib/helpers/api_builder/decisions.mjs ~L100
Condition Check:
if (isSelfReferential) {
return { shouldFlatten: false, flattenType: "self-referential" };
}Triggers: isSelfReferential === true
Result: shouldFlatten: false, flattenType: "self-referential"
Used By: API-RULES Rule 6
Category: Basic Flattening Related Rule: Rule 5 Status: ✅ Active
Pattern: In a multi-default context, if the current module has a default export, preserve it as its own namespace. Each default-exporting file maintains a distinct callable namespace alongside its peers.
Function: getFlatteningDecision()
Source: src/lib/processors/flatten.mjs — #checkMultiDefault()
Condition Check:
if (hasMultipleDefaults) {
if (analysis.hasDefault) {
return { preserveAsNamespace: true, reason: ... };
}
}Triggers: hasMultipleDefaults === true && analysis.hasDefault === true
Result: { preserveAsNamespace: true }
Used By: API-RULES Rule 5
Category: Basic Flattening
Related Rule: Rule 5
Status: ✅ Active
Bug Fix: PR #116 — corrected behavior enforced in v3.8+; opt-out via suppressFixes: ["C03_116"] (removed in v4)
Pattern: In a multi-default context, if the current module does NOT have a default export, its named exports are hoisted ("flattened to root") directly into the parent folder namespace. The file's own intermediate namespace is dissolved — there is no api.folder.filename level. This is the complement of C02: default-exporting files are preserved as named namespaces, while named-only files are merged into the folder.
Function: getFlatteningDecision()
Source: src/lib/processors/flatten.mjs — #checkMultiDefault()
Condition Check:
if (hasMultipleDefaults) {
// C02 (hasDefault) handled above; falls through to:
return { flattenToRoot: true, reason: ... };
}Triggers: hasMultipleDefaults === true && analysis.hasDefault === false
Result: { flattenToRoot: true }
Consumer: modes-processor.mjs — iterates moduleContent keys and assigns each directly into targetApi
Used By: API-RULES Rule 5
Example: Given notifications/email.mjs (default), notifications/sms.mjs (default), notifications/helpers.mjs (no default — formatPhone, RETRY_LIMIT):
api.notifications.email(...)— callable (C02, preserved)api.notifications.sms(...)— callable (C02, preserved)api.notifications.formatPhone(...)— hoisted (C03, dissolved)api.notifications.RETRY_LIMIT— hoisted (C03, dissolved)api.notifications.helpers— does not exist
Pre-Fix Behavior (before PR #116): C03 fell through to standard namespace wrapping, so named-only files were NOT dissolved. helpers.mjs would have produced api.notifications.helpers.formatPhone and api.notifications.helpers.RETRY_LIMIT — the intermediate helpers namespace was retained. To temporarily restore this pre-fix behavior while migrating, use suppressFixes: ["C03_116"] (removed in v4).
Category: Basic Flattening Related Rule: Rule 7 Flattening Guide: F03: Auto-Flatten Status: ✅ Active
Pattern: A module with exactly one named export, and that export key matches the file's API path key, is an auto-flatten candidate - no intermediate namespace is needed.
Function: getFlatteningDecision()
Source: src/lib/helpers/api_builder/decisions.mjs ~L138
Condition Check:
if (moduleKeys.length === 1 && moduleKeys[0] === apiPathKey) {
return { shouldFlatten: true, flattenType: "auto-flatten", preferredName: moduleKeys[0] };
}Triggers: moduleKeys.length === 1 && moduleKeys[0] === apiPathKey
Result: shouldFlatten: true, flattenType: "auto-flatten"
Used By: API-RULES Rule 7
Category: Basic Flattening Related Rule: Rule 1 Flattening Guide: F01: Basic Flattening Rules Status: ✅ Active
Pattern: When a file at the category level has the same name as its containing folder (and exports named members but no default), flatten those exports directly into the category namespace.
Function: getFlatteningDecision()
Source: src/lib/helpers/api_builder/decisions.mjs ~L150
Condition Check:
if (categoryName && fileName === categoryName && !moduleHasDefault && moduleKeys.length > 0) {
return { shouldFlatten: true, flattenType: "category", preferredName: categoryName };
}Triggers: categoryName != null && fileName === categoryName && !moduleHasDefault && moduleKeys.length > 0
Result: shouldFlatten: true, flattenType: "category"
Used By: API-RULES Rule 1
Category: Basic Flattening Status: ⛔ Intentionally disabled / commented out
Note: This condition was removed from the active decision path. The code block remains in source as a historical reference but is not evaluated during normal execution. No rules depend on this condition.
Source: src/lib/helpers/api_builder/decisions.mjs ~L162-L170 (commented out)
Category: Basic Flattening Related Rule: All rules (fallback) Status: ✅ Active
Pattern: When no other condition matches, preserve the module under its own namespace key. This is the safe default.
Function: getFlatteningDecision()
Source: src/lib/helpers/api_builder/decisions.mjs ~L174
Condition Check:
else {
return { shouldFlatten: false, flattenType: "namespace" };
}Triggers: No prior condition matched
Result: shouldFlatten: false, flattenType: "namespace"
Category: Module Processing Related Rule: Rule 7 Flattening Guide: F03: Auto-Flatten Status: ✅ Active
Pattern: If the flattening decision verdict is useAutoFlattening, apply auto-flatten processing to the module during processModuleForAPI().
Function: processModuleForAPI()
Source: src/lib/helpers/api_builder/decisions.mjs ~L424
Condition Check:
if (decision.useAutoFlattening) {
// apply auto-flatten processing
}Triggers: decision.useAutoFlattening === true
Used By: API-RULES Rule 7
Category: Module Processing Related Rules: Rule 1, Rule 5 Status: ✅ Active
Pattern: If the decision specifies flattenToRoot or flattenToCategory, merge the module's exports into the target namespace level directly.
Function: processModuleForAPI()
Source: src/lib/helpers/api_builder/decisions.mjs ~L430
Condition Check:
else if (decision.flattenToRoot || decision.flattenToCategory) {
// merge exports into target namespace
}Triggers: decision.flattenToRoot === true || decision.flattenToCategory === true
Used By: API-RULES Rule 1, Rule 5
Category: Module Processing Related Rule: Rule 6 Status: ✅ Active
Pattern: During module processing, if a self-referential condition is present but the export is not a function, bypass flattening and preserve as a namespace.
Function: processModuleForAPI()
Source: src/lib/helpers/api_builder/decisions.mjs ~L440
Condition Check:
else if (isSelfReferential) {
// preserve as namespace, no flatten
}Triggers: isSelfReferential === true (non-function case)
Used By: API-RULES Rule 6
Category: Module Processing Status: ✅ Active
Pattern: Final fallback in processModuleForAPI() - if no processing branch matches, preserve the module under its namespace key.
Function: processModuleForAPI()
Source: src/lib/helpers/api_builder/decisions.mjs ~L444
Condition Check:
else {
// standard namespace preservation
}Triggers: No prior processing branch matched Result: Module placed under its own namespace key
Category: Category Decisions Related Rule: Rule 2 Status: ✅ Active
Pattern: In a directory with a single .mjs file, if the file exports a function and the module name matches the category name, promote the function directly to the category key (no intermediate namespace).
Function: buildCategoryDecisions()
Source: src/lib/helpers/api_builder/decisions.mjs ~L580
Condition Check:
if (moduleName === categoryName && typeof mod === "function" && currentDepth > 0) {
// promote function to category level
}Triggers: moduleName === categoryName && typeof mod === "function" && currentDepth > 0
Used By: API-RULES Rule 2
Category: Category Decisions Related Rule: Rule 4 Flattening Guide: F04: Default Export Object Flattening Status: ✅ Active
Pattern: When a module has a default export that is an object and its name matches the category name, flatten the object's properties into the category namespace.
Function: buildCategoryDecisions()
Source: src/lib/helpers/api_builder/decisions.mjs ~L588
Condition Check:
if (analysis.hasDefault && analysis.defaultExportType === "object" && moduleName === categoryName && currentDepth > 0) {
// flatten default object into category
}Triggers: Has default object export + module name matches category name + nested context Used By: API-RULES Rule 4
Category: Category Decisions Related Rule: Rule 7 Flattening Guide: F04: Default Export Object Flattening Status: ✅ Active
Pattern: When a module exports a plain object (not array, not function) and its name matches the category name, auto-flatten the object's properties into the category level.
Function: buildCategoryDecisions()
Source: src/lib/helpers/api_builder/decisions.mjs ~L596
Condition Check:
if (moduleName === categoryName && mod && typeof mod === "object" && !Array.isArray(mod) && currentDepth > 0) {
// auto-flatten object
}Triggers: Module is plain object + name matches category + nested context Used By: API-RULES Rule 7
Category: Category Decisions Related Rules: Rule 1, Rule 2 Status: ✅ Active
Pattern: When the file's base name matches the category name and the module has at least one export, flatten those exports up to the category level.
Function: buildCategoryDecisions()
Source: src/lib/helpers/api_builder/decisions.mjs ~L611
Condition Check:
if (fileBaseName === categoryName && moduleKeys.length > 0) {
// flatten to category
}Triggers: fileBaseName === categoryName && moduleKeys.length > 0
Used By: API-RULES Rule 1, Rule 2
Category: Category Decisions Related Rule: Rule 10 Flattening Guide: F02: Function Folder Matching Status: ✅ Active
Pattern: When a folder contains exactly one file, that file has a generic name (e.g. index, main, helpers), and there is nested depth, flatten the module's exports to the parent level to avoid pointless nesting.
Function: buildCategoryDecisions()
Source: src/lib/helpers/api_builder/decisions.mjs ~L653-L661
Condition Check:
if (
moduleFiles.length === 1 &&
currentDepth > 0 &&
/* ... various generic-name checks ... */ &&
isGenericFilename
) {
// flatten to parent
}Triggers: Single file in folder + nested context + filename is generic Used By: API-RULES Rule 10
Category: Category Decisions Related Rule: Rule 9 Status: ✅ Active
Pattern: When a module exports a function whose name matches the containing folder name, that function name takes precedence over the file name as the API key.
Function: buildCategoryDecisions()
Source: src/lib/helpers/api_builder/decisions.mjs ~L670
Condition Check:
if (functionNameMatchesFolder && currentDepth > 0) {
// use function name as API key
}Triggers: functionNameMatchesFolder === true && currentDepth > 0
Used By: API-RULES Rule 9
Category: Category Decisions Related Rule: Rule 9 Status: ✅ Active
Pattern: When a module exports a function whose name matches the filename (even without folder match), the function's own name is used as the API key rather than the file's name.
Function: buildCategoryDecisions()
Source: src/lib/helpers/api_builder/decisions.mjs ~L678
Condition Check:
if (functionNameMatchesFilename) {
// use function name as preferred key
}Triggers: functionNameMatchesFilename === true
Used By: API-RULES Rule 9
Category: Category Decisions Related Rule: Rule 4 Status: ✅ Active
Pattern: When a module exports a function as its default and has no explicit name (or is explicitly marked as a slothlet default), promote the function to the parent category level.
Function: buildCategoryDecisions()
Source: src/lib/helpers/api_builder/decisions.mjs ~L687
Condition Check:
if (typeof mod === "function" && (!mod.name || mod.name === "default" || mod.__slothletDefault === true) && currentDepth > 0) {
// promote anonymous default function
}Triggers: Module is an anonymous/unnamed function or explicit default + nested context Used By: API-RULES Rule 4
Category: Category Decisions Related Rule: Rule 7 Status: ✅ Active
Pattern: Final single-file auto-flatten check: when a module has exactly one named export and that export's name matches the module name, flatten it. This catches cases not resolved by C04/C12.
Function: buildCategoryDecisions()
Source: src/lib/helpers/api_builder/decisions.mjs ~L704
Condition Check:
if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
return { shouldFlatten: true, flattenType: "object-auto-flatten", preferredName: moduleName };
}Triggers: moduleKeys.length === 1 && moduleKeys[0] === moduleName
Result: shouldFlatten: true, flattenType: "object-auto-flatten"
Used By: API-RULES Rule 7
Category: AddApi Related Rule: Rule 11 Flattening Guide: F06: AddApi Special File Pattern Status: ✅ Active
Pattern: Files named addapi.mjs loaded via api.slothlet.api.add() always flatten regardless of the autoFlatten setting. The file is designed for seamless namespace extensions - it should never create an intermediate .addapi. level.
Function: getFlatteningDecision() (detection) / modes-processor execution
Source: src/lib/processors/flatten.mjs ~L119-L133, L332-L347; src/lib/builders/modes-processor.mjs ~L207-L215, L699-L710
Condition Check:
if (newModules && typeof newModules === "object" && newModules.addapi) {
const addapiContent = newModules.addapi;
delete newModules.addapi;
if (typeof addapiContent === "object") {
Object.assign(newModules, addapiContent);
} else if (typeof addapiContent === "function") {
Object.assign(newModules, addapiContent);
}
}Triggers: After modules are loaded, newModules.addapi key is present
Result: addapi.mjs exports are merged directly into the mount path namespace
Used By: API-RULES Rule 11
Example:
plugin-folder/
└── addapi.mjs ← exports: initializePlugin, cleanup, configure
await api.slothlet.api.add("plugins", "./plugin-folder");
api.plugins.initializePlugin(); // ✅ Direct extension
api.plugins.cleanup(); // ✅ No .addapi. intermediate levelKey details:
- Detection:
newModules.addapikey check - Extraction: content stored,
addapikey deleted fromnewModules - Flattening:
Object.assign(newModules, addapiContent)merges exports flat - Works regardless of
autoFlattensetting - Applies only when loading via
api.slothlet.api.add()- not initial load
Category: AddApi Related Rule: Rule 13 Flattening Guide: F08: AddApi Path Deduplication Flattening Status: ✅ Active (New in v3)
Pattern: After buildAPI returns for an api.slothlet.api.add() call, if the result contains a key matching the last segment of the mount path, AND that key's value originated from a direct subfolder of the mounted directory (isDirectChild guard), hoist that key's exports one level up and remove the duplicate key.
Purpose: Prevents double-nesting when a mounted folder contains a same-named subfolder. api.slothlet.api.add("config", folder) should produce api.config.*, not api.config.config.*.
Function: addApiComponent()
Source: src/lib/handlers/api-manager.mjs - immediately after single-file unwrap block
Guard - isDirectChild:
// Ensures the matching key came from mountDir/lastPart/, not a deeper nested folder
const isDirectChild = dirname(filePath) === path.join(resolvedFolderPath, lastPart);This guard prevents incorrect hoisting when a deeply nested folder happens to share the mount path's last segment name.
Condition Check:
if (newApi[lastPart] !== undefined && isDirectChild) {
// hoist exports from newApi[lastPart] up to newApi level
Object.assign(newApi, newApi[lastPart]);
delete newApi[lastPart];
}Triggers: newApi has key matching mount path's last segment + isDirectChild === true
Result: Exports hoisted one level; duplicate key removed
Used By: API-RULES Rule 13
Example:
api_smart_flatten_folder_config/
└── config/
└── config.mjs ← exports: getNestedConfig
await api.slothlet.api.add("config", "./api_smart_flatten_folder_config");
// Without C34: api.config.config.getNestedConfig() ← double-nested
// With C34: api.config.getNestedConfig() ← correctly hoistedisDirectChild examples:
| Mount path | Folder structure | isDirectChild |
Action |
|---|---|---|---|
"config" |
folder/config/config.mjs |
true |
Hoist ✅ |
"services" |
folder/services/services.mjs |
true |
Hoist ✅ |
"api" |
folder/api/nested/api.mjs |
false |
No hoist ✅ |
- Rule 1: C05, C09, C13
- Rule 2: C10, C13
- Rule 4: C11, C17
- Rule 5: C02, C03
- Rule 6: C01, C09a
- Rule 7: C04, C08, C12, C18
- Rule 9: C15, C16
- Rule 10: C14
- Rule 11: C33
- Rule 13: C34
Total Active Conditions: 20 (C01-C05, C07-C18, C33, C34)
Deprecated Conditions: 1 (C06 - intentionally disabled)
Primary Source Files: src/lib/helpers/api_builder/decisions.mjs, src/lib/processors/flatten.mjs, src/lib/builders/modes-processor.mjs, src/lib/handlers/api-manager.mjs
- Basic Flattening (C01-C07): Core per-module flattening verdict from
getFlatteningDecision() - Module Processing (C08-C09b): Module handling during
processModuleForAPI() - Category Decisions (C10-C18): Directory-level coordination in
buildCategoryDecisions() - AddApi Special Cases (C33-C34): Always-flatten and deduplication for
api.slothlet.api.add()calls
- Self-Referential Protection: C01, C09a prevent circular structures
- Multi-Default Coordination: C02, C03 handle mixed export patterns across a folder
- Smart Flattening: Auto-detect conditions C04, C08, C12, C18 reduce unnecessary nesting
- Filename/Folder Matching: C05, C10, C13 harmonize file and folder naming
- Function Name Preference: C15, C16 preserve semantic naming over file structure names
- Depth Awareness: Most C10-C18 conditions check
currentDepth > 0to avoid flattening at root - AddApi Extension: C33, C34 ensure
api.slothlet.api.add()produces clean, non-doubled namespaces
See API-RULE-MAPPING.md for the complete traceability matrix: Rule ↔ F## ↔ C## ↔ implementation file.