Hooked API Cheatsheet
Quick recipes for common tasks with Hooked API.
Quick Links
- API Reference - Detailed API documentation
- Full Documentation - Complete guide and examples
Table of Contents
- Basic Setup
- Adding Methods
- Working with Scopes
- Using Hooks
- Creating Plugins
- Variables and Helpers
- Logging
- Error Handling
- Testing
Basic Setup
Create a simple API
import { Api } from 'hooked-api';
const api = new Api({
name: 'my-api',
version: '1.0.0'
});
Create an API with logging
const api = new Api({
name: 'my-api',
version: '1.0.0',
logging: { level: 'debug' }
});
Adding Methods
Add a simple API method
api.customize({
apiMethods: {
greet: async ({ params }) => `Hello ${params.name}!`
}
});
// Usage
await api.greet({ name: 'World' }); // "Hello World!"
Add method with hooks
api.customize({
apiMethods: {
getData: async ({ params, context, runHooks }) => {
await runHooks('beforeFetch');
context.data = await fetchData(params.id);
await runHooks('afterFetch');
return context.data;
}
}
});
Working with Scopes
Add a basic scope
api.addScope('users', {
schema: { name: 'string', email: 'string' }
});
Add scope with methods
api.customize({
scopeMethods: {
get: async ({ params, scopeName }) => {
return await db.fetch(scopeName, params.id);
}
}
});
api.addScope('users', {});
// Usage: await api.scopes.users.get({ id: 123 });
Add scope with custom hooks
api.addScope('products',
{ schema: { name: 'string', price: 'number' } },
{
hooks: {
afterFetch: ({ context }) => {
context.record.formattedPrice = `$${context.record.price}`;
}
}
}
);
Use scope aliases for better naming
// For database-like APIs
api.setScopeAlias('tables', 'addTable');
api.addTable('orders', { schema: { total: 'number' } });
// Usage: await api.tables.orders.get({ id: 1 });
Using Hooks
Add a simple hook
api.customize({
hooks: {
beforeSave: ({ context }) => {
context.timestamp = new Date();
}
}
});
Add hook with placement
api.customize({
hooks: {
'afterFetch': ({ context, log }) => {
log.debug('Original data:', context.record);
}
}
});
// Later, add a hook that runs before the above
api.customize({
hooks: {
afterFetch: [{
placement: { beforeFunction: 'afterFetch' },
handler: ({ context }) => {
context.record.processed = true;
}
}]
}
});
Stop hook chain execution
api.customize({
hooks: {
validate: ({ context }) => {
if (!context.record.isValid) {
return false; // Stops remaining hooks
}
}
}
});
Creating Plugins
Minimal plugin
const myPlugin = {
name: 'myPlugin',
install: ({ addApiMethod }) => {
addApiMethod('hello', async () => 'Hello from plugin!');
}
};
api.use(myPlugin);
Plugin with dependencies
const enhancedPlugin = {
name: 'enhancedPlugin',
dependencies: ['basePlugin'],
install: ({ addHook, vars }) => {
vars.enhanced = true;
addHook('afterInit', 'enhance', {}, ({ context }) => {
context.enhanced = true;
});
}
};
Plugin with options
const configPlugin = {
name: 'configPlugin',
install: ({ addApiMethod, pluginOptions }) => {
addApiMethod('getConfig', async () => ({
endpoint: pluginOptions.configPlugin?.endpoint || 'default'
}));
}
};
api.use(configPlugin, { endpoint: 'https://api.example.com' });
Variables and Helpers
Set global variables
api.customize({
vars: {
apiKey: 'secret-key',
timeout: 5000
}
});
Add helper functions
api.customize({
helpers: {
formatDate: (date) => date.toISOString(),
delay: (ms) => new Promise(r => setTimeout(r, ms))
}
});
Use vars and helpers in methods
api.customize({
apiMethods: {
fetchData: async ({ vars, helpers }) => {
await helpers.delay(100);
return {
key: vars.apiKey,
date: helpers.formatDate(new Date())
};
}
}
});
Scope-specific vars override globals
api.customize({ vars: { timeout: 5000 } });
api.addScope('slowEndpoint',
{},
{ vars: { timeout: 30000 } } // Override for this scope
);
Logging
Use logger in methods
api.customize({
apiMethods: {
process: async ({ params, log }) => {
log.trace('Starting process');
log.debug('Parameters:', params);
try {
const result = await doWork(params);
log.info('Process completed');
return result;
} catch (error) {
log.error('Process failed:', error);
throw error;
}
}
}
});
Custom logger
const customLogger = {
log: (...args) => console.log('[CUSTOM]', ...args),
error: (...args) => console.error('[ERROR]', ...args),
warn: (...args) => console.warn('[WARN]', ...args)
};
const api = new Api({
name: 'my-api',
version: '1.0.0',
logging: { logger: customLogger }
});
Error Handling
Catch specific errors
import { ValidationError, ScopeError } from 'hooked-api';
try {
api.addScope('123-invalid', {});
} catch (error) {
if (error instanceof ValidationError) {
console.log('Invalid name:', error.value);
}
}
Handle scope not found
try {
await api.scopes.nonexistent.get();
} catch (error) {
if (error instanceof ScopeError) {
console.log('Available scopes:', error.availableScopes);
}
}
Testing
Reset registry between tests
import { resetGlobalRegistryForTesting } from 'hooked-api';
beforeEach(() => {
resetGlobalRegistryForTesting();
});
Test a plugin
test('plugin adds method', async () => {
const api = new Api({ name: 'test', version: '1.0.0' });
api.use({
name: 'testPlugin',
install: ({ addApiMethod }) => {
addApiMethod('getValue', async () => 42);
}
});
const result = await api.getValue();
expect(result).toBe(42);
});
Common Patterns
Authentication pattern
api.customize({
vars: { currentUser: null },
helpers: {
requireAuth: (user) => {
if (!user) throw new Error('Auth required');
}
},
apiMethods: {
secureMethod: async ({ vars, helpers }) => {
helpers.requireAuth(vars.currentUser);
// Proceed with secure operation
}
}
});
Rate limiting pattern
api.customize({
vars: { requests: new Map() },
helpers: {
checkRate: (key, limit = 10) => {
const count = requests.get(key) || 0;
if (count >= limit) throw new Error('Rate limit exceeded');
requests.set(key, count + 1);
}
}
});
Caching pattern
api.customize({
vars: { cache: new Map() },
hooks: {
beforeFetch: ({ context, vars }) => {
const cached = vars.cache.get(context.cacheKey);
if (cached) {
context.result = cached;
return false; // Skip remaining hooks
}
},
afterFetch: ({ context, vars }) => {
vars.cache.set(context.cacheKey, context.result);
}
}
});
Transaction pattern
api.customize({
helpers: {
transaction: async (fn) => {
await db.beginTransaction();
try {
const result = await fn();
await db.commit();
return result;
} catch (error) {
await db.rollback();
throw error;
}
}
},
apiMethods: {
transfer: async ({ params, helpers }) => {
return helpers.transaction(async () => {
await debit(params.from, params.amount);
await credit(params.to, params.amount);
});
}
}
});
Tips and Tricks
1. Use context for hook communication
// Context is shared between all hooks and the method
context.processed = true;
context.metadata = { timestamp: Date.now() };
2. Leverage scope methods for DRY code
// Define once, use for all scopes
api.customize({
scopeMethods: {
validate: async ({ params, scopeOptions }) => {
return validateSchema(params, scopeOptions.schema);
}
}
});
3. Plugin naming for hook ordering
// Name your plugins clearly for hook placement
addHook('process', 'validate', {
beforePlugin: 'DatabasePlugin'
}, handler);
4. Use frozen options for security
// Options are frozen - attempts to modify will fail
apiOptions.name = 'hacked'; // TypeError
5. Combine multiple patterns
// Auth + Rate Limiting + Logging
api.customize({
apiMethods: {
protectedEndpoint: async ({ vars, helpers, log, params }) => {
helpers.requireAuth(vars.currentUser);
helpers.checkRate(vars.currentUser.id);
log.info('Access granted', { user: vars.currentUser.id });
return await processRequest(params);
}
}
});