Getting Started
Basic Usage

Basic Usage

The Problem: Manual Key Management

When working with cache-based state management or data fetching libraries, you typically need to create array-based keys to identify your data:

// ❌ The old way: Manually constructing keys everywhere
useQuery({
  queryKey: ['users', 'list', { page: 1 }],
  queryFn: fetchUsers
});
 
useQuery({
  queryKey: ['users', 'detail', userId],
  queryFn: () => fetchUser(userId)
});
 
useMutation({
  mutationKey: ['users', 'create'],
  mutationFn: createUser
});
 
// Problems:
// 1. String literals are fragile and error-prone
// 2. Inconsistent structure across your app
// 3. Hard to refactor when you change key names
// 4. No autocomplete or type-safety
// 5. Function and key definitions are separated

The Solution: Builder

With @ibnlanre/builder, you define your keys and functions together in a structured object (called a "register"), then let Builder handle key generation:

import { createBuilder } from '@ibnlanre/builder';
 
// ✅ Define once
const api = createBuilder({
  users: {
    list: (params: { page: number }) => fetchUsers(params),
    detail: (id: number) => fetchUser(id),
    create: (data: User) => createUser(data)
  }
});
 
// ✅ Use anywhere with autocomplete
useQuery({
  queryKey: api.users.list.$use({ page: 1 }),  // ['users', 'list', { page: 1 }]
  queryFn: () => api.$use.users.list({ page: 1 })
});

How It Works

Builder transforms your register into a smart object with two special properties at every level:

1. $use - Dual Purpose Property

On the root node, $use gives you access to your original values:

const register = {
  users: {
    getName: (id: number) => `User ${id}`,
    count: 42
  }
};
 
const builder = createBuilder(register);
 
// Access values through the root $use
builder.$use.users.getName(10);  // "User 10"
builder.$use.users.count;         // 42

On nested nodes, $use() generates keys following the function signature:

// If the value is a function with parameters
builder.users.getName.$use(10);  // ['users', 'getName', 10]
 
// If the value is a primitive or object
builder.users.count.$use();      // ['users', 'count']

2. $get() - Flexible Key Generation

The $get() method gives you more control, accepting arbitrary arguments:

// Get the key without following function signature
builder.users.getName.$get();              // ['users', 'getName']
builder.users.getName.$get(10, 'active');  // ['users', 'getName', 10, 'active']

Step-by-Step Example

Let's build a complete example:

Step 1: Create Your Register

Define your keys and values in a nested structure:

const register = {
  products: {
    list: async (category?: string) => {
      const url = category ? `/api/products?cat=${category}` : '/api/products';
      return fetch(url).then(r => r.json());
    },
    detail: async (id: number) => fetch(`/api/products/${id}`).then(r => r.json()),
    price: 29.99  // Can also store plain values
  }
};

Step 2: Create the Builder

const api = createBuilder(register, {
  prefix: ['api', 'v1']  // Optional: add prefixes to all keys
});

Step 3: Generate Keys

// Using $use - follows function signature
api.products.list.$use();           // ['api', 'v1', 'products', 'list']
api.products.list.$use('electronics');  // ['api', 'v1', 'products', 'list', 'electronics']
api.products.detail.$use(42);       // ['api', 'v1', 'products', 'detail', 42]
 
// Using $get - flexible arguments
api.products.list.$get('sale', true);  // ['api', 'v1', 'products', 'list', 'sale', true]

Step 4: Access Values

// Call the actual functions
await api.$use.products.list('electronics');  // Fetches electronics
await api.$use.products.detail(42);           // Fetches product 42
 
// Access plain values
api.$use.products.price;  // 29.99

Step 5: Use in Your Application

import { useQuery } from '@tanstack/react-query';
 
function ProductList({ category }: { category?: string }) {
  const { data } = useQuery({
    queryKey: api.products.list.$use(category),
    queryFn: () => api.$use.products.list(category)
  });
 
  return <div>{/* render products */}</div>;
}

Understanding Prefixes

Prefixes act as a namespace for your keys and appear at the beginning of every generated key:

const api = createBuilder(register, { prefix: ['app', 'cache'] });
 
api.products.list.$use();  // ['app', 'cache', 'products', 'list']
api.$get();                 // ['app', 'cache'] - just the prefix

Use prefixes to:

  • Version your cache keys
  • Separate concerns (e.g., ['api'] vs ['ui'])
  • Namespace multiple builders in the same app

Key Takeaways

  • Register: Your nested object containing keys and values
  • Builder: The enhanced object created by createBuilder()
  • $use: Property on root for values, method on nodes for keys (follows function signature)
  • $get(): Method for flexible key generation with arbitrary parameters
  • Type-safe: TypeScript enforces correct parameters throughout

Next Steps