Registers
What is a Register?
A register is simply a plain JavaScript object that holds your keys and values. Think of it as a structured blueprint of your data. It can contain:
- Objects (nested structures)
- Functions (API calls, utilities)
- Primitives (strings, numbers, booleans)
The register is the source of truth that Builder uses to generate type-safe keys.
Simple Example
Here's the most basic register:
const register = {
user: {
name: 'John Doe',
email: 'john.doe@example.com'
}
};
const builder = createBuilder(register);
// Access values
builder.$use.user.name; // 'John Doe'
// Generate keys
builder.user.name.$use(); // ['user', 'name']Key insight: Whatever structure you define in the register, Builder mirrors it with smart key-generation methods.
Practical Example: API Endpoints
The real power shows when you store functions, like API calls:
// Before: API calls defined in multiple places
// File 1: users/api.ts
export const fetchUser = (id: number) => fetch(`/api/users/${id}`);
// File 2: users/list.tsx
const key = ['users', 'detail', userId]; // Manual key
const data = await fetchUser(userId);
// After: Everything in one place
const api = createBuilder({
users: {
list: async () => {
return fetch('/api/users').then(r => r.json());
},
detail: async (id: number) => {
return fetch(`/api/users/${id}`).then(r => r.json());
},
create: async (data: { name: string; email: string }) => {
return fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(r => r.json());
}
}
});
// Now use anywhere:
api.users.detail.$use(5); // ['users', 'detail', 5]
api.$use.users.detail(5); // Calls the APIBenefits:
- ✅ API structure is self-documenting
- ✅ Keys and functions stay synchronized
- ✅ Easy to refactor (change once, update everywhere)
Nested Structures
You can nest objects as deeply as needed to organize related functionality:
const register = {
products: {
electronics: {
list: () => fetch('/api/products/electronics').then(r => r.json()),
featured: () => fetch('/api/products/electronics/featured').then(r => r.json())
},
clothing: {
list: () => fetch('/api/products/clothing').then(r => r.json())
}
},
cart: {
items: () => fetch('/api/cart').then(r => r.json()),
add: (productId: number) => fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId })
}).then(r => r.json())
}
};
const store = createBuilder(register);
// Access nested endpoints
store.products.electronics.featured.$use(); // ['products', 'electronics', 'featured']
store.cart.add.$use(123); // ['cart', 'add', 123]Organizing Large Registers
For larger applications, break your register into smaller, focused modules:
// auth.register.ts
export const authRegister = {
login: async (credentials: Credentials) => { /* ... */ },
logout: async () => { /* ... */ },
refresh: async () => { /* ... */ }
};
// products.register.ts
export const productsRegister = {
list: async (category?: string) => { /* ... */ },
detail: async (id: number) => { /* ... */ },
search: async (query: string) => { /* ... */ }
};
// main.register.ts
import { authRegister } from './auth.register';
import { productsRegister } from './products.register';
const register = {
auth: authRegister,
products: productsRegister
};
export const api = createBuilder(register);Why this is better:
- Keeps code organized by domain
- Easier to find and maintain
- Multiple team members can work on different modules
- Registers can be reused across projects
Best Practices
✅ DO: Keep it Flat When Possible
// Good: Easy to navigate
const register = {
users: { list, detail, create },
posts: { list, detail, create }
};❌ AVOID: Unnecessary Deep Nesting
// Harder to work with
const register = {
api: {
v1: {
resources: {
users: {
operations: {
list: () => { /* ... */ }
}
}
}
}
}
};
// Results in long chains:
builder.api.v1.resources.users.operations.list.$use(); // Too verbose✅ DO: Group Related Functions
const register = {
users: {
// All user-related operations together
list: async () => { /* ... */ },
detail: async (id: number) => { /* ... */ },
create: async (data: User) => { /* ... */ },
update: async (id: number, data: Partial<User>) => { /* ... */ },
delete: async (id: number) => { /* ... */ }
}
};✅ DO: Mix Functions and Values
const config = createBuilder({
api: {
baseUrl: 'https://api.example.com',
timeout: 5000,
fetchUser: async (id: number) => { /* ... */ }
}
});
// Access both
config.$use.api.baseUrl; // 'https://api.example.com'
config.$use.api.fetchUser(5); // Calls the function
config.api.baseUrl.$use(); // ['api', 'baseUrl']Real-World Example
Here's a complete register for a blog application:
import { createBuilder } from '@ibnlanre/builder';
type Post = { id: number; title: string; content: string; authorId: number };
type Comment = { id: number; postId: number; text: string };
const blog = createBuilder({
posts: {
list: async (page: number = 1) => {
return fetch(`/api/posts?page=${page}`).then(r => r.json());
},
detail: async (id: number) => {
return fetch(`/api/posts/${id}`).then(r => r.json());
},
create: async (data: Omit<Post, 'id'>) => {
return fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(r => r.json());
},
comments: {
list: async (postId: number) => {
return fetch(`/api/posts/${postId}/comments`).then(r => r.json());
},
create: async (postId: number, text: string) => {
return fetch(`/api/posts/${postId}/comments`, {
method: 'POST',
body: JSON.stringify({ text })
}).then(r => r.json());
}
}
},
authors: {
detail: async (id: number) => {
return fetch(`/api/authors/${id}`).then(r => r.json());
},
posts: async (authorId: number) => {
return fetch(`/api/authors/${authorId}/posts`).then(r => r.json());
}
}
});
// Usage in components:
blog.posts.list.$use(1); // ['posts', 'list', 1]
blog.posts.comments.list.$use(42); // ['posts', 'comments', 'list', 42]
blog.authors.posts.$use(7); // ['authors', 'posts', 7]Summary
The register is your single source of truth:
- Define your structure once
- Builder generates consistent keys
- Functions and values stay together
- TypeScript ensures everything is type-safe
Think of it as: A table of contents for your entire application's data layer.
Next Steps
- Learn how to create a builder from your register
- Understand the builder object and its special methods
- See real-world usage examples with data fetching libraries