Skip to content

Latest commit

 

History

History
688 lines (475 loc) · 26.4 KB

File metadata and controls

688 lines (475 loc) · 26.4 KB

API Rules Conditions

Document Hierarchy: API Rules IndexFlattening PatternsConditions ReferenceRule 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.


Core Decision Functions

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

C01: Self-Referential Check

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


C02: Multi-Default Context with Default Export

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


C03: Multi-Default Context Without Default Export

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.helpersdoes 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).


C04: Auto-Flatten Single Named Export Matching Filename

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


C05: Filename Matches Container / Category-Level Flatten

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


C06: Deprecated Condition

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)


C07: Default Fallback - Preserve as Namespace

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"


C08: Auto-Flattening

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


C09: Flatten to Root/Category

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


C09a: Self-Referential Non-Function

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


C09b: Traditional Namespace Preservation

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


C10: Single-File Function - Folder Match

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


C11: Default Export Flattening

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


C12: Object Auto-Flatten

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


C13: Filename / Folder Exact Match Flattening

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


C14: Parent-Level Flattening - Generic Filenames

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


C15: Function Name Matches Folder

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


C16: Function Name Preference

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


C17: Default Function Export Flattening

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


C18: Object Auto-Flatten - Final Check

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


C33: AddApi Special File Detection

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 level

Key details:

  1. Detection: newModules.addapi key check
  2. Extraction: content stored, addapi key deleted from newModules
  3. Flattening: Object.assign(newModules, addapiContent) merges exports flat
  4. Works regardless of autoFlatten setting
  5. Applies only when loading via api.slothlet.api.add() - not initial load

C34: AddApi Path Deduplication

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 hoisted

isDirectChild 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 ✅

Cross-Reference Index

By Rule Number

By Flattening Pattern


Summary

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

Condition Categories

  • 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

Key Architectural Patterns

  1. Self-Referential Protection: C01, C09a prevent circular structures
  2. Multi-Default Coordination: C02, C03 handle mixed export patterns across a folder
  3. Smart Flattening: Auto-detect conditions C04, C08, C12, C18 reduce unnecessary nesting
  4. Filename/Folder Matching: C05, C10, C13 harmonize file and folder naming
  5. Function Name Preference: C15, C16 preserve semantic naming over file structure names
  6. Depth Awareness: Most C10-C18 conditions check currentDepth > 0 to avoid flattening at root
  7. 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.