MODULAR TYPESCRIPT

Build robust and predictable software

DuploJS gives TypeScript projects a set of composable bricks for shaping data, validating boundaries and keeping application flows explicit.

  • Smart Typing
  • 0 Dependency
  • Functional Programming

Why DuploJS exists

Robustness needs more than types

TypeScript gives structure to JavaScript, but production systems also need pure transformations, explicit contracts, runtime validation and composable patterns. DuploJS brings these missing bricks into one coherent ecosystem.

Pure transformations

DuploJS favors pure, non-mutating functions so a transformation can be read as input, intent, output.

Runtime guarantees

Validation lives at runtime boundaries and stays aligned with the types your code consumes.

Type-driven intent

Business meaning is carried by typed contracts, not left as comments beside generic values.

THE ECOSYSTEM

Many packages. Only one way to do it.

DuploJS brings together a rich ecosystem of focused packages, all shaped by the same functional foundation. From utilities to HTTP, server, form, auth and testing, every package follows the same type-driven logic and consistent development model.

$npm i @duplojs/utils

Designed as one ecosystemEach package extends the same foundation with the same type-driven logic.
Adopt it progressivelyPure functions fit into your stack, one package at a time.
@duplojs/server-utilsSERVER

Server primitives built for predictable backends, covering file handling, commands, environment configuration, typed results and more.

@duplojs/httpCLIENT/SERVER

Declare HTTP contracts once and keep routing, validation, clients, documentation and transport logic aligned by design.

  • 0 External Dependency
  • Only One Way
  • Towards Predictable Code

WITH VS WITHOUT

Same problem. More predictability.

Compare common TypeScript patterns with DuploJS alternatives designed for robustness, explicit intent and predictable behavior.

Fetch

Represent fetch failures explicitly instead of hiding them in thrown errors.

  • Explicit failure path
  • No hidden throw
  • Typed result

Without DuploJS

Classic approach
index.ts
ts
// No obligation to handle errors
const 
response
= await
fetch
("http://example.com/api/users");
const
body
= await
response
.
json
();
try { const
response
= await
fetch
("http://example.com/api/posts");
const
body
= await
response
.
json
();
} catch (
error
: unknown) {
console
.
log
(
error
);
}

With DuploJS

DuploJS approach
index.ts
ts
import { 
createHttpClient
} from "@duplojs/http/client";
import {
E
} from "@duplojs/utils";
const
client
=
createHttpClient
({
baseUrl
: "http://example.com" });
// Covers all cases exhaustively const
result
=
E
.
matchInformation
(
await
client
.
get
("/api/users"),
{
response
: ({
body
}) => {
// Do something with the body }, "request-error": ({
error
}) => {
// Do something with the error }, }, );

From visible differences to clear architecture.

Once the intent is explicit and the behavior is predictable, the structure of your codebase becomes the answer.

Intent Structure Validation

Clean architecture

Explore code by layer

DuploJS helps separate business rules, application flows and infrastructure details into explicit, readable layers. Explore each layer and see how the code stays predictable from domain to runtime.

Connect to external systems and services.
Orchestrate use cases and application flows.
Define business concepts and rules.
Active layer Domain

Define business concepts and rules without depending on external tools.

entities/book.tsDomain
ts
import { C, DPE, E } from "@duplojs/utils";
import { Client } from "./client";

export namespace Book {
	export const Id = C.createNewType("BookId", DPE.string(), C.Uuid);
	export type Id = C.GetNewType<typeof Id>;

	export const Name = C.createNewType("BookName", DPE.string());
	export type Name = C.GetNewType<typeof Name>;

	export const Entity = C.createEntity(
		"Book",
		({ nullable }) => ({
			id: Id,
			name: Name,
			currentBorrowerId: nullable(Client.Id),
		}),
	);
	export type Entity = C.GetEntity<typeof Entity>;

	const Borrow = C.createFlag<
		Entity,
		"Borrow",
		{ currentBorrowerId: Client.Id }
	>("Borrow");
	export const getCurrentBorrower = Borrow.getValue;
	export type Borrow = C.GetFlag<typeof Borrow>;

	const Available = C.createFlag<Entity, "Available">("Available");
	export type Available = C.GetFlag<typeof Available>;

	export function isBorrowed<T extends Entity>(book: T) {
		if (book.currentBorrowerId !== null) {
			return E.result(
				"book-is-borrow",
				Borrow.append(
					book,
					{ currentBorrowerId: book.currentBorrowerId },
				),
			);
		}

		return E.result("book-is-available", Available.append(book));
	}
}