@giansandoval/clean-arch
v0.1.0
Published
Small TypeScript primitives for Clean Architecture, DDD and scalable application layers.
Maintainers
Readme
@giansandoval/clean-arch
Small TypeScript primitives for building applications with Clean Architecture, basic DDD patterns and scalable boundaries across TypeScript, React Native, React, Node.js and frontend projects.
This package has no runtime dependencies and is designed to stay framework-agnostic.
Installation
npm install @giansandoval/clean-archCLI Scaffold
The package includes a small CLI to generate a Clean Architecture folder structure in an existing project.
Run it interactively:
npx @giansandoval/clean-arch initOr pass options directly:
npx @giansandoval/clean-arch init --path src --project react --features auth,users,products --yesAvailable project types:
nodereactreact-nativefrontend
The CLI generates feature-first folders with domain, application, infrastructure and presentation layers:
src/
core/
domain/
errors/
AppDomainError.ts
features/
users/
domain/
entities/
errors/
repositories/
application/
use-cases/
infrastructure/
repositories/
presentation/
view-models/ # react, react-native, frontend
controllers/ # nodeExisting files are skipped by default. Use --force only when you want generated files to be overwritten.
Result Pattern
Use Result<T, E> to return expected success or failure states without throwing for normal application flow.
import { Result } from "@giansandoval/clean-arch";
function parseUserId(value: string): Result<number> {
const id = Number(value);
if (Number.isNaN(id)) {
return Result.fail("Invalid user id");
}
return Result.ok(id);
}
const result = parseUserId("42");
if (result.isSuccess) {
console.log(result.value);
}
const doubled = result.map((id) => id * 2);
const value = doubled.unwrapOr(0);unwrap() is available when you intentionally want to fail fast on an invalid access. For application flow, prefer isSuccess, isFailure, unwrapOr, map or asyncMap.
UseCase Example
import { Result, type UseCase } from "@giansandoval/clean-arch";
type CreateUserInput = {
name: string;
};
type UserOutput = {
id: string;
name: string;
};
class CreateUserUseCase implements UseCase<CreateUserInput, UserOutput> {
execute(input: CreateUserInput): Result<UserOutput> {
if (!input.name.trim()) {
return Result.fail("Name is required");
}
return Result.ok({
id: crypto.randomUUID(),
name: input.name,
});
}
}Repository Example
import type { Repository } from "@giansandoval/clean-arch";
import { User } from "./User";
class InMemoryUserRepository implements Repository<User, string> {
private readonly users = new Map<string, User>();
findById(id: string): User | null {
return this.users.get(id) ?? null;
}
save(user: User): User {
this.users.set(user.id, user);
return user;
}
delete(id: string): void {
this.users.delete(id);
}
}Entity Example
import { Entity } from "@giansandoval/clean-arch";
type UserProps = {
name: string;
email: string;
};
class User extends Entity<UserProps, string> {
constructor(id: string, props: UserProps) {
super(id, props);
}
get email(): string {
return this.props.email;
}
}
const user = new User("user-1", {
name: "Gian",
email: "[email protected]",
});
console.log(user.toJSON());ValueObject Example
import { ValueObject } from "@giansandoval/clean-arch";
type EmailProps = {
value: string;
};
class Email extends ValueObject<EmailProps> {
constructor(value: string) {
super({ value: value.trim().toLowerCase() });
}
get value(): string {
return this.props.value;
}
}
const first = new Email("[email protected]");
const second = new Email("[email protected]");
console.log(first.equals(second)); // trueDomainError Example
import { DomainError } from "@giansandoval/clean-arch";
class UserAlreadyExistsError extends DomainError {
constructor(email: string) {
super("User already exists", {
code: "USER_ALREADY_EXISTS",
details: { email },
});
}
}Publishing
npm install
npm test
npm run build
npm login
npm publish --access publicThe package is configured with:
- ESM:
dist/index.js - CommonJS:
dist/index.cjs - Type definitions:
dist/index.d.ts - CLI:
clean-arch - Public scoped publishing through
publishConfig.access
