Documentation API Cheatsheet

← Back to Home

Hooked API Cheatsheet

Quick recipes for common tasks with Hooked API.

Table of Contents

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);
    }
  }
});