DeesseJS Functions is a powerful TypeScript library for building type-safe APIs with context management.
You will need Node.js 18+ and pnpm installed on your local development machine.
pnpm add @deessejs/functions
# or
npm install @deessejs/functions
# or
yarn add @deessejs/functionsNote: This project uses pnpm for development. See Contributing for development setup.
import { defineContext, success } from "@deessejs/functions";
import { z } from "zod";
// 1. Define your context (single source of truth)
const { t, createAPI } = defineContext<{
userId: string;
database: any;
}>({
userId: "user-123",
database: myDatabase,
});
// 2. Define queries (read operations)
const getUser = t.query({
args: z.object({
id: z.number(),
}),
handler: async (ctx, args) => {
return success({
id: args.id,
requestedBy: ctx.userId,
});
},
});
// 3. Define mutations (write operations)
const createUser = t.mutation({
args: z.object({
name: z.string().min(2),
email: z.string().email(),
}),
handler: async (ctx, args) => {
const user = ctx.database.users.create(args);
return success(user);
},
});
// 4. Create the API
const api = createAPI({ getUser, createUser });
// 5. Call your endpoints
const result = await api.getUser({ id: 123 });
if (result.ok) {
console.log("User:", result.value);
} else {
console.error("Error:", result.error);
}- Native API:
queryandmutationare built-in - no extensions required - Lightning Fast: No more "Type instantiation is excessively deep" errors
- Simple Types: Standard TypeScript generics - no HKT complexity
- Type Safe: Full end-to-end type safety with Zod validation
- Zero Config: Get started in seconds, not hours
- Functional: Pure functions and immutable data structures
- Event-Driven: Built-in lifecycle hooks and cache invalidation streams
Context is the single source of truth for your API - define it once and use it everywhere:
const { t, createAPI } = defineContext<{
userId: string;
database: Database;
logger: Logger;
}>({
userId: "user-123",
database: myDatabase,
logger: myLogger,
});Queries are read-only operations:
const getUser = t.query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
return success({ id: args.id, name: "User" });
},
});Mutations are write operations:
const createUser = t.mutation({
args: z.object({ name: z.string() }),
handler: async (ctx, args) => {
return success({ id: 1, name: args.name });
},
});Organize endpoints logically:
const api = createAPI({
users: t.router({
profile: t.router({
get: t.query({
/* ... */
}),
update: t.mutation({
/* ... */
}),
}),
settings: t.query({
/* ... */
}),
}),
posts: t.router({
get: t.query({
/* ... */
}),
list: t.query({
/* ... */
}),
}),
});
// Usage
await api.users.profile.get({ id: 1 });
await api.users.settings.update({});Attach middleware to your operations:
import { query, success } from "@deessejs/functions";
const getUser = query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => success(args),
})
.beforeInvoke((ctx, args) => {
console.log(`Fetching user ${args.id}`);
})
.onSuccess((ctx, args, data) => {
console.log(`User fetched: ${data.id}`);
})
.onError((ctx, args, error) => {
console.error(`Failed to fetch user: ${error.message}`);
});Better failure handling with causes vs exceptions:
import { query, cause, Causes, successOutcome, failureOutcome } from "@deessejs/functions";
const getUser = query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
const user = await db.find(args.id);
if (!user) {
return failureOutcome(Causes.notFound(args.id, "User"));
}
return successOutcome(user);
},
});Resilient operations with automatic retries:
import { retry, RetryConfigs } from "@deessejs/functions";
const fetchWithRetry = retry(
async (url: string) => {
const response = await fetch(url);
return response.json();
},
RetryConfigs.network // Pre-configured retry settings
);
const data = await fetchWithRetry("https://api.example.com/data");Flexible API naming for backward compatibility:
import { query, aliases } from "@deessejs/functions";
const getUser = query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => success(args),
});
aliases(getUser, ["fetchUser", "retrieveUser", "getUserById"]);
// All work the same
await api.getUser({ id: 1 });
await api.fetchUser({ id: 1 });
await api.retrieveUser({ id: 1 });Real-time cache coordination:
import { createCacheStream } from "@deessejs/functions";
const stream = createCacheStream();
// Subscribe to cache changes
stream.subscribe("users:123", (event) => {
if (event.type === "invalidation") {
// Refetch user data
refetchUser(123);
}
});
// Invalidate cache
stream.invalidate("users:123", {
tags: ["users"],
data: { reason: "User updated" },
});Full end-to-end type safety with automatic inference:
// TypeScript infers all types automatically
const api = createAPI({
getUser: t.query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
return success({
id: args.id, // number
name: "Alice", // string
} as const);
},
}),
});
// Fully typed!
const result = await api.getUser({ id: 123 });
// ^? { ok: true; value: { id: number; name: string } } | { ok: false; error: Error }const result = await api.getUser({ id: 1 });
if (result.ok) {
console.log("Success:", result.value);
} else {
// Handle errors
switch (result.error.name) {
case "ValidationError":
console.error("Invalid input:", result.error.message);
break;
case "NotFound":
console.error("User not found");
break;
default:
console.error("Unexpected error:", result.error.message);
}
}import { defineContext, success } from "@deessejs/functions";
import { z } from "zod";
// Context
interface Context {
userId: string;
database: {
users: {
find: (id: number) => Promise<any>;
create: (data: any) => Promise<any>;
};
};
}
// Create API builder with context
const { t, createAPI } = defineContext<Context>({
userId: "user-123",
database: myDatabase,
});
// Queries
const getUser = t.query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
const user = await ctx.database.users.find(args.id);
if (!user) {
throw new Error(`User ${args.id} not found`);
}
return success(user);
},
});
// Mutations
const createUser = t.mutation({
args: z.object({
name: z.string().min(2),
email: z.string().email(),
}),
handler: async (ctx, args) => {
const user = await ctx.database.users.create(args);
return success({ id: user.id, ...args });
},
});
// Create API
const api = createAPI({
users: t.router({
get: getUser,
create: createUser,
}),
});
// Use it
async function main() {
const created = await api.users.create({
name: "Alice",
email: "[email protected]",
});
if (created.ok) {
console.log("Created user:", created.value);
}
}We've built templates that include DeesseJS Functions integrations for different use cases and frameworks. You can use these templates to get started with your type-safe API.
Available Templates:
- Basic - Simple API with queries and mutations
- Full-Stack - Next.js integration with API routes
- Enterprise - Advanced features with caching and events
Check the /examples directory for complete working examples.
Old API (HKT-based):
import { defineContext, rpc } from "@deessejs/functions";
const { t, createAPI } = defineContext<{ userId: string }>().withExtensions([rpc]);
const getUser = t.query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
return success({ id: args.id, requestedBy: ctx.userId });
},
});
const api = createAPI({
root: { getUser },
runtimeContext: { userId: "123" },
});New Native API:
import { defineContext, success } from "@deessejs/functions";
const { t, createAPI } = defineContext<{ userId: string }>({
userId: "123",
});
const getUser = t.query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
return success({ id: args.id, requestedBy: ctx.userId });
},
});
const api = createAPI({ getUser });Key Changes:
- Context defined ONCE in
defineContext(context)- single source of truth - No
rpcextension needed -queryandmutationare built-in - Handler signature keeps
(ctx, args)- context before arguments createAPI(root)- no context parameter needed!- HKT-based types replaced with standard TypeScript generics
- Faster compilation with simpler, clearer types
- Documentation Site: Full documentation with guides and API reference
- Examples: Check
/examplesdirectory for complete working examples - Type Definitions: See JSDoc comments in your IDE for inline documentation
// Create separate contexts for different domains
const userAPI = defineContext<UserContext>({
/* ... */
});
const postAPI = defineContext<PostContext>({
/* ... */
});const commonFields = z.object({
id: z.number(),
createdAt: z.date(),
});
const userSchema = commonFields.extend({
name: z.string(),
email: z.string().email(),
});const operation = t
.mutation({
args: z.object({
/* ... */
}),
handler: async (ctx, args) => {
try {
const result = await db.create(args);
return success(result);
} catch (error) {
if (error.code === "DUPLICATE") {
return failure(cause({ name: "Conflict", message: "User already exists" }));
}
throw error;
}
},
})
.onError((ctx, args, error) => {
// Log error for monitoring
logger.error("Operation failed", { args, error });
});import { query, aliases } from "@deessejs/functions";
const v1_getUser = query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => success(args),
});
const v2_getUser = query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => success(args),
});
// Provide both
aliases(v2_getUser, ["getUser", "fetchUser", "getUser_v1"]);DeesseJS Functions follows strict architectural principles:
- No Classes - Pure functions and data objects only
- No Interfaces - Type aliases only for structural modeling
- Const Functions - Arrow functions only, no
functionkeyword - Functional Approach - Immutability and referential clarity
Join the DeesseJS Functions community to ask questions, share ideas, and show your projects:
- GitHub: Report issues
- Discussions: Join the conversation
Contributions to DeesseJS Functions are welcome and highly appreciated. Before you jump right into it, please review our Contribution Guidelines to ensure you have a smooth experience contributing.
This project uses pnpm as the package manager.
-
Install pnpm (if not already installed):
npm install -g pnpm
-
Clone and install dependencies:
git clone https://github.com/nesalia-inc/functions.git cd functions pnpm install -
Run the development server:
pnpm dev
-
Build the project:
pnpm build
-
Run tests:
pnpm test -
Run linting:
pnpm lint
When making changes that should be included in the next release, use Changesets:
pnpm changesetThis will guide you through documenting your changes. The CI system will automatically handle versioning and publishing based on these changesets.
We especially welcome:
- Bug reports
- Feature requests
- Pull requests
- Documentation improvements
- Example projects
DeesseJS Functions is created and maintained by the DeesseJS team.
MIT © DeesseJS
| Version | Changes |
|---|---|
| 0.1.0 | Major overhaul - Native API, simplified types, removed HKT complexity |
| 0.0.x | Initial releases with HKT-based architecture |