Functions

What is Deesse Functions?

Introduction to DeesseJS Functions library

Deesse Functions is a TypeScript utility library for building type-safe APIs with powerful context management. It combines simplicity with advanced features to help you create robust, maintainable backends.

Core Philosophy

Deesse Functions is built on three principles:

1. Type Safety First

Catch Errors at Compile Time

Every part of your API is fully typed - from arguments to return values to context. Catch errors at compile time, not runtime.

// Args are validated and typed
const getUser = t.query({
  args: z.object({ id: z.number() }),
  handler: async (ctx, args) => {
    // args.id is guaranteed to be a number
    return success({ id: args.id, name: "Alice" });
  },
});

// Usage is fully type-checked
const result = await api.getUser({ id: 1 });
const result = await api.getUser({ id: "wrong" }); // ❌ Type error!

2. Context as Single Source of Truth

Define Once, Use Everywhere

Define your dependencies once. Context flows automatically to all your queries and mutations.

const { t, createAPI } = defineContext({
  database: myDatabase,
  userId: "user-123",
  emailService: myMailer,
});

// Context is automatically available in all handlers
const createUser = t.mutation({
  handler: async (ctx, args) => {
    // Access ctx.database, ctx.userId, ctx.emailService
    // No need to pass them around manually
  },
});

3. Batteries Included

No Plugins Required

Essential features are built-in. No need to install plugins or extensions for core functionality.

Queries & Mutations

Native read and write operations - no extensions needed

Intelligent Caching

Built-in caching with automatic revalidation

Event-Driven Architecture

Emit and listen to events across your application

Authorization Checks

Protect your endpoints with built-in authorization

Lifecycle Hooks

Add middleware and side effects at any stage

Automatic Retry

Resilient operations with built-in retry logic

Input Validation

Type-safe validation powered by Zod

Key Features

Type-Safe Context Management

Single Source of Truth

Context provides a single source of truth for your API. Define it once, use it everywhere with full type safety.

const { t, createAPI } = defineContext<{
  userId: string;
  database: Database;
  emailService: EmailService;
}>();

// TypeScript knows exactly what's in context
const sendEmail = t.mutation({
  handler: async (ctx, args) => {
    ctx.userId;        // ✅ Type: string
    ctx.database;      // ✅ Type: Database
    ctx.emailService;  // ✅ Type: EmailService
    ctx.unknown;       // ❌ Type error
  },
});

Native Queries & Mutations

Built-in support for read and write operations. No extensions required.

// Queries for reading data
const getUser = t.query({
  cacheKey: ['users', '{id}'],
  staleTime: 60000,
  args: z.object({ id: z.number() }),
  handler: async (ctx, args) => {
    return success(await ctx.database.find(args.id));
  },
});

// Mutations for writing data
const updateUser = t.mutation({
  invalidate: [['users', '{id}']],
  args: z.object({ id: z.number(), name: z.string() }),
  handler: async (ctx, args) => {
    return success(await ctx.database.update(args.id, args));
  },
});

Intelligent Caching

Built-in Caching with Invalidation

Built-in caching with automatic invalidation and revalidation. Configure cache keys and let the system handle the rest.

const getUser = t.query({
  cacheKey: ['users', '{id}'],
  staleTime: 60000,     // Cache for 1 minute
  gcTime: 300000,       // Remove from memory after 5 minutes
  handler: async (ctx, args) => {
    // Only called if cache is stale or missing
    return success(await fetchUser(args.id));
  },
});

Result Types

Explicit Error Handling

Handle success and failure cases explicitly and safely with built-in Result types.

import { success, failure } from '@deessejs/functions';

const getUser = t.query({
  handler: async (ctx, args) => {
    const user = await ctx.database.find(args.id);

    if (!user) {
      return failure(new Error("User not found"));
    }

    return success(user);
  },
});

// Usage
const result = await api.getUser({ id: 1 });

if (result.success) {
  console.log(result.value);  // User data
} else {
  console.error(result.error); // Error information
}

Authorization Checks

Protect your endpoints with built-in authorization.

import { hasPermission } from '@deessejs/functions';

const deleteUser = t.mutation({
  authorize: hasPermission('admin'),
  handler: async (ctx, args) => {
    // Only runs if user has 'admin' permission
    return success(await ctx.database.delete(args.id));
  },
});

Lifecycle Hooks

Middleware at Any Stage

Add middleware and side effects at any stage of request execution.

const createUser = t.mutation({
  beforeInvoke: async (ctx, args) => {
    console.log('Creating user:', args);
  },
  onSuccess: async (ctx, args, result) => {
    await ctx.events.emit('user:created', result.value);
  },
  onError: async (ctx, args, error) => {
    await ctx.logger.error('Failed to create user', error);
  },
  handler: async (ctx, args) => {
    return success(await ctx.database.create(args));
  },
});

Event-Driven Architecture

Emit and listen to events across your application.

const createPost = t.mutation({
  handler: async (ctx, args) => {
    const post = await ctx.database.create(args);
    await ctx.events.emit('post:created', post);
    return success(post);
  },
});

// Listen to events
ctx.events.on('post:created', async (post) => {
  await ctx.emailService.sendNotification(post.authorId);
});

What Deesse Functions is NOT

Understanding What Deesse Functions is NOT

Deesse Functions is a focused library with specific goals. Understanding what it's not helps you use it effectively.

Not an ORM

Works Alongside Your Database

Deesse Functions doesn't replace your database layer. It works alongside your existing data access code.

// You still use your favorite ORM or query builder
const getUser = t.query({
  handler: async (ctx, args) => {
    // Works with Prisma, Drizzle, TypeORM, Knex, raw SQL, etc.
    return success(await prisma.user.findUnique({ where: { id: args.id } }));
  },
});

Not a Framework

Library, Not Framework

Deesse Functions is a library, not a framework. It doesn't dictate your application structure.

// Use it however you want
const api = createAPI({
  // Organize however makes sense for your project
  users: t.router({ /* ... */ }),
  posts: t.router({ /* ... */ }),
  admin: t.router({ /* ... */ }),
});

Not Tied to Any Transport

Protocol Agnostic

Use with HTTP, WebSockets, RPC, or any protocol you need.

// Works with any transport layer
const api = createAPI({ /* ... */ });

// Use with Express, Fastify, Hono, Next.js, etc.
app.post('/api/users/create', async (req, res) => {
  const result = await api.users.create(req.body);
  res.json(result);
});

Design Principles

Functional Approach

Functional Programming Principles

Deesse Functions follows functional programming principles for cleaner, more maintainable code.

  • No classes - Pure functions and data objects only
  • No interfaces - Type aliases for structural modeling
  • Const functions - Arrow functions only
  • Immutability - Data never changes unexpectedly
// ✅ Functional style
const getUser = t.query({
  handler: async (ctx, args) => {
    return success(immutableData);
  },
});

// ❌ Not used in Deesse Functions
class UserService {
  async getUser() { /* ... */ }
}

Simplicity Over Complexity

Major Refactor for Simplicity

The library recently underwent a major refactor to remove unnecessary complexity (HKT) while maintaining full type safety. The result is a simpler, more approachable API.

Before (complex HKT-based approach):

// Complex type-level programming
interface Query<T, C, Args> { /* ... */ }

After (simple, standard TypeScript):

// Standard TypeScript generics
const query = t.query<Args, Return>({ /* ... */ });

When to Use Deesse Functions

Deesse Functions is ideal for:

Real-Time Applications

Live Data Updates

Applications with live data updates benefit from intelligent caching and automatic revalidation.

const getMessages = t.query({
  cacheKey: ['messages', '{channelId}'],
  staleTime: 5000, // Revalidate every 5 seconds
  handler: async (ctx, args) => {
    return success(await ctx.db.getMessages(args.channelId));
  },
});

Collaborative Systems

Multi-user applications with complex authorization requirements.

const updateDocument = t.mutation({
  authorize: async (ctx, args) => {
    const doc = await ctx.db.find(args.documentId);
    return doc.ownerId === ctx.userId || doc.collaborators.includes(ctx.userId);
  },
  handler: async (ctx, args) => {
    return success(await ctx.db.update(args.documentId, args));
  },
});

Enterprise Applications

Large applications with complex business logic and multiple services.

const { t, createAPI } = defineContext({
  database: db,
  cache: redis,
  email: mailer,
  sms: twilio,
  analytics: segment,
  logger: winston,
});

const createUser = t.mutation({
  handler: async (ctx, args) => {
    const user = await ctx.database.create(args);
    await ctx.email.sendWelcome(user.email);
    await ctx.analytics.track('user:created', { userId: user.id });
    ctx.logger.info('User created', { userId: user.id });
    return success(user);
  },
});

Performance-Critical Applications

Excellent Caching Strategies

Applications that need excellent caching strategies and minimal redundant operations.

const getProduct = t.query({
  cacheKey: ['products', '{id}'],
  staleTime: 300000, // 5 minutes
  gcTime: 900000,    // 15 minutes
  handler: async (ctx, args) => {
    // Expensive operation only runs when cache is stale
    return success(await ctx.db.getProductWithReviews(args.id));
  },
});

When NOT to Use Deesse Functions

Consider Alternatives When:

  • Simple scripts or CLIs - Overhead isn't justified for one-off scripts
  • Static site generators - No dynamic API needed
  • Purely serverless functions - If you have no shared state, context might be overkill

Comparison to Traditional Approaches

Express Routes

// Traditional Express
app.get('/users/:id', async (req, res) => {
  // No type safety for req.params
  // Manual error handling
  // No built-in caching
  // No authorization helpers
  const user = await db.find(req.params.id);
  res.json(user);
});

// Deesse Functions
const getUser = t.query({
  args: z.object({ id: z.number() }), // Type-safe args
  handler: async (ctx, args) => {     // Automatic error handling
    return success(await ctx.db.find(args.id)); // Built-in Result type
  },
});

tRPC

// tRPC requires routers
export const appRouter = router({
  users: router({
    get: procedure.input(z.object({ id: z.number() })).query(async ({ input }) => {
      return await db.find(input.id);
    }),
  }),
});

// Deesse Functions - simpler API
const getUser = t.query({
  args: z.object({ id: z.number() }),
  handler: async (ctx, args) => {
    return success(await ctx.db.find(args.id));
  },
});

const api = createAPI({
  users: t.router({ get: getUser }),
});

Ecosystem

TypeScript

First-class TypeScript support with full type inference

Zod

Schema validation powered by Zod

Vitest

Testing framework integration

Next.js

Full-stack framework compatibility

Any ORM

Works with Prisma, Drizzle, TypeORM, and more

License

MIT - freely use in personal and commercial projects.

Next Steps

On this page