factory-function-composition
Apply factory function patterns to compose clients and services with proper separation of concerns. Use when creating functions that depend on external clients, wrapping resources with domain-specific methods, or refactoring code that mixes client/service/method options together.
Install
mkdir -p .claude/skills/factory-function-composition && curl -L -o skill.zip "https://mcp.directory/api/skills/download/4171" && unzip -o skill.zip -d .claude/skills/factory-function-composition && rm skill.zipInstalls to .claude/skills/factory-function-composition
About this skill
Factory Function Composition
This skill helps you apply factory function patterns for clean dependency injection and function composition in TypeScript.
Related Skills: See
method-shorthand-jsdocfor when to move helpers into the return object. Seerefactoringfor caller counting and inlining single-use extractions.
When to Apply This Skill
Use this pattern when you see:
- A function that takes a client/resource as its first argument
- Options from different layers (client, service, method) mixed together
- Client creation happening inside functions that shouldn't own it
- Functions that are hard to test because they create their own dependencies
The Universal Signature
Every factory function follows this signature:
function createSomething(dependencies, options?) {
return {
/* methods */
};
}
- First argument: Always the resource(s). Either a single client or a destructured object of multiple dependencies.
- Second argument: Optional configuration specific to this factory. Never client config—that belongs at client creation.
Two arguments max. First is resources, second is config. No exceptions.
The Core Pattern
// Single dependency
function createService(client, options = {}) {
return {
method(methodOptions) {
// Uses client, options, and methodOptions
},
};
}
// Multiple dependencies
function createService({ db, cache }, options = {}) {
return {
method(methodOptions) {
// Uses db, cache, options, and methodOptions
},
};
}
// Usage
const client = createClient(clientOptions);
const service = createService(client, serviceOptions);
service.method(methodOptions);
Key Principles
- Client configuration belongs at client creation time — don't pipe clientOptions through your factory
- Each layer has its own options — client, service, and method options stay separate
- Dependencies come first — factory functions take dependencies as the first argument
- Return objects with methods — not standalone functions that need the resource passed in
Recognizing the Anti-Patterns
Anti-Pattern 1: Function takes client as first argument
// Bad
function doSomething(client, options) { ... }
doSomething(client, options);
// Good
const service = createService(client);
service.doSomething(options);
Anti-Pattern 2: Client creation hidden inside
// Bad
function doSomething(clientOptions, methodOptions) {
const client = createClient(clientOptions); // Hidden!
// ...
}
// Good
const client = createClient(clientOptions);
const service = createService(client);
service.doSomething(methodOptions);
Anti-Pattern 3: Mixed options blob
// Bad
doSomething({
timeout: 5000, // Client option
retries: 3, // Client option
endpoint: '/users', // Method option
payload: data, // Method option
});
// Good
const client = createClient({ timeout: 5000, retries: 3 });
const service = createService(client);
service.doSomething({ endpoint: '/users', payload: data });
Anti-Pattern 4: Multiple layers hidden
// Bad
function doSomething(clientOptions, serviceOptions, methodOptions) {
const client = createClient(clientOptions);
const service = createService(client, serviceOptions);
return service.method(methodOptions);
}
// Good — each layer visible and configurable
const client = createClient(clientOptions);
const service = createService(client, serviceOptions);
service.method(methodOptions);
Multiple Dependencies
When your service needs multiple clients:
function createService(
{ db, cache, http }, // Dependencies as destructured object
options = {}, // Service options
) {
return {
method(methodOptions) {
// Uses db, cache, http
},
};
}
// Usage
const db = createDbConnection(dbOptions);
const cache = createCacheClient(cacheOptions);
const http = createHttpClient(httpOptions);
const service = createService({ db, cache, http }, serviceOptions);
service.method(methodOptions);
The Canonical Internal Shape
The previous sections cover the external signature—(deps, options?) → return { methods }. This section covers what goes inside the function body. Every factory function follows a four-zone ordering:
// Option A — destructure in the signature (preferred for small dep lists)
function createSomething({ db, cache }: Deps, options?) {
const maxRetries = options?.maxRetries ?? 3;
// ...
}
// Option B — destructure in zone 1 (fine when you also need the deps object itself)
function createSomething(deps: Deps, options?) {
const { db, cache } = deps;
const maxRetries = options?.maxRetries ?? 3;
// ...
}
Both are valid. The point is that by the time you reach zone 2, all dependencies and config are bound to const names. The four zones:
function createSomething({ db, cache }, options?) {
// Zone 1 — Immutable state (const from deps/options)
const maxRetries = options?.maxRetries ?? 3;
// Zone 2 — Mutable state (let declarations)
let connectionCount = 0;
let lastError: Error | null = null;
// Zone 3 — Private helpers
function resetState() {
connectionCount = 0;
lastError = null;
}
// Zone 4 — Public API (always last)
return {
connect() { ... },
disconnect() { ... },
get errorCount() { return connectionCount; },
};
}
Zones 1 and 2 can merge when there's little state. Zone 3 is empty for small factories. But the return object is always last—it's the complete public API.
The this Decision Rule
Inside the return object, public methods sometimes need to call other public methods. Use this.method() for that—method shorthand gives proper this binding.
If a function is called both by return-object methods and by pre-return initialization logic, it belongs in zone 3 (private helpers). Call it directly by name; no this needed.
| Where the function lives | How to call it |
|---|---|
| Return object (zone 4) | this.method() from sibling methods |
| Private helper (zone 3) | Direct call by name: helperFn() |
| Both zones need it | Keep in zone 3, call by name everywhere |
See Closures Are Better Privacy Than Keywords for the full rationale and real codebase examples.
The Mental Model
Think of it as a chain where each link:
- Receives a resource from the previous link
- Adds its own configuration
- Produces something for the next link
createClient(...) → createService(client, ...) → service.method(...)
↑ ↑ ↑
clientOptions serviceOptions methodOptions
Benefits
- Testability: Inject mock clients easily
- Reusability: Share clients across multiple services
- Flexibility: Configure each layer independently
- Clarity: Clear ownership of configuration at each level
References
See the full articles for more details:
- The Universal Factory Function Signature — signature explained in depth
- Stop Passing Clients as Arguments — practical guide
- The Factory Function Pattern — detailed explanation
- Factory Method Patterns — separating options and method patterns
- Closures Are Better Privacy Than Keywords — internal anatomy and why closures beat class keywords
More by EpicenterHQ
View all skills by EpicenterHQ →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
fastapi-templates
wshobson
Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.
Related MCP Servers
Browse all serversContext Portal: Manage project memory with a database-backed system for decisions, tracking, and semantic search via a k
Create and edit PowerPoint presentations, apply themes, add slides and export to PDF quickly using pptxgenjs and officeg
Extend your developer tools with GitHub MCP Server for advanced automation, supporting GitHub Student and student packag
Chrome extension-based MCP server that exposes browser functionality to AI assistants. Control tabs, capture screenshots
MCP server for Ghidra reverse engineering. Enables AI assistants to decompile binaries, analyze functions, rename variab
Connect Supabase projects to AI with Supabase MCP Server. Standardize LLM communication for secure, efficient developmen
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.