.do
API Reference

Functions API

Execute business functions with type-safe inputs and outputs

Execute business functions with type-safe inputs and outputs. Functions are the core building blocks of Business-as-Code, representing reusable business logic that can be composed, tested, and executed.

Overview

Business functions are executable units of business logic that:

  • Accept typed inputs and return typed outputs
  • Can be composed into workflows
  • Support metadata and documentation
  • Are versioned and testable
  • Can be executed synchronously or asynchronously
import { createBusinessApi } from 'business-as-code'

const api = createBusinessApi({
  apiKey: process.env.APIS_DO_KEY,
})

// Execute a function
const result = await api.functions.execute('calculate-discount', {
  orderTotal: 150.0,
  customerTier: 'premium',
})

execute()

Execute a business function with the provided input.

Signature

execute<TInput, TOutput>(
  functionId: string,
  input: TInput
): Promise<TOutput>

Parameters

ParameterTypeRequiredDescription
functionIdstringYesUnique identifier of the function to execute
inputTInputYesInput data for the function

Returns

Promise that resolves to the function's output of type TOutput.

Example

// Simple calculation function
const discount = await api.functions.execute<{ orderTotal: number; customerTier: string }, { discountAmount: number; finalTotal: number }>(
  'calculate-discount',
  {
    orderTotal: 150.0,
    customerTier: 'premium',
  }
)

console.log('Discount:', discount.discountAmount) // 15.00
console.log('Final total:', discount.finalTotal) // 135.00

Complex Example: Revenue Calculation

interface RevenueInput {
  transactions: Array<{
    id: string
    amount: number
    currency: string
    date: string
    type: 'sale' | 'refund'
  }>
  period: {
    start: string
    end: string
  }
  aggregateBy?: 'day' | 'week' | 'month'
}

interface RevenueOutput {
  total: number
  currency: string
  breakdown: Record<string, number>
  growth: {
    amount: number
    percentage: number
  }
  metrics: {
    averageTransaction: number
    transactionCount: number
    refundRate: number
  }
}

const revenue = await api.functions.execute<RevenueInput, RevenueOutput>('calculate-revenue', {
  transactions: [
    { id: 'txn-1', amount: 99.99, currency: 'USD', date: '2024-10-01', type: 'sale' },
    { id: 'txn-2', amount: 149.99, currency: 'USD', date: '2024-10-15', type: 'sale' },
    { id: 'txn-3', amount: 49.99, currency: 'USD', date: '2024-10-20', type: 'refund' },
  ],
  period: {
    start: '2024-10-01',
    end: '2024-10-31',
  },
  aggregateBy: 'month',
})

console.log('Total Revenue:', revenue.total)
console.log('Growth:', revenue.growth.percentage + '%')
console.log('Average Transaction:', revenue.metrics.averageTransaction)

Error Handling

import { ApiError, ValidationError } from 'business-as-code'

try {
  const result = await api.functions.execute('calculate-discount', {
    orderTotal: 150.0,
    customerTier: 'premium',
  })
} catch (error) {
  if (error instanceof ValidationError) {
    // Client-side validation failed
    console.error(`Invalid ${error.field}: ${error.message}`)
  } else if (error instanceof ApiError) {
    if (error.statusCode === 404) {
      console.error('Function not found')
    } else if (error.statusCode === 422) {
      console.error('Invalid input:', error.body)
    } else {
      console.error('Execution failed:', error.message)
    }
  }
}

Validation

The execute() method performs the following validations:

  • functionId must be provided and be a string
  • input must be provided
// ❌ Throws ValidationError
await api.functions.execute('', { data: 'value' })
// Error: functionId is required

await api.functions.execute('my-function', null)
// Error: input is required

await api.functions.execute(123, { data: 'value' })
// Error: functionId must be a string

Execution Modes

Functions can execute in different modes:

Synchronous (default):

// Wait for result
const result = await api.functions.execute('quick-calc', { x: 10, y: 20 })
console.log(result) // { sum: 30 }

With timeout:

// Add timeout to API call
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 5000)

try {
  const result = await api.functions.execute('slow-function', input)
} catch (error) {
  if (error.name === 'AbortError') {
    console.error('Function timed out after 5 seconds')
  }
} finally {
  clearTimeout(timeout)
}

list()

Retrieve a list of all available functions.

Signature

list(): Promise<BusinessFunction[]>

Returns

Promise that resolves to an array of BusinessFunction objects.

Example

const functions = await api.functions.list()

console.log(`Found ${functions.length} functions`)

functions.forEach((fn) => {
  console.log(`- ${fn.name} (${fn.id})`)
  console.log(`  ${fn.description}`)
})

Response Type

interface BusinessFunction {
  id: string
  name: string
  description?: string
  input?: any
  output?: any
  execute: (input: any) => Promise<any> | any
  metadata?: Record<string, any>
}

Filtering and Searching

// Get all functions
const allFunctions = await api.functions.list()

// Filter by category (client-side)
const calculationFunctions = allFunctions.filter((fn) => fn.metadata?.category === 'calculation')

// Search by name (client-side)
const searchTerm = 'discount'
const matchingFunctions = allFunctions.filter(
  (fn) => fn.name.toLowerCase().includes(searchTerm.toLowerCase()) || fn.description?.toLowerCase().includes(searchTerm.toLowerCase())
)

Complete Example

async function discoverFunctions() {
  const functions = await api.functions.list()

  // Group by category
  const byCategory = functions.reduce(
    (acc, fn) => {
      const category = fn.metadata?.category || 'uncategorized'
      if (!acc[category]) acc[category] = []
      acc[category].push(fn)
      return acc
    },
    {} as Record<string, BusinessFunction[]>
  )

  // Display organized list
  Object.entries(byCategory).forEach(([category, fns]) => {
    console.log(`\n${category.toUpperCase()}:`)
    fns.forEach((fn) => {
      console.log(`  ${fn.name}`)
      console.log(`    ID: ${fn.id}`)
      if (fn.description) {
        console.log(`    Description: ${fn.description}`)
      }
    })
  })

  return byCategory
}

await discoverFunctions()

get()

Retrieve a specific function by its ID.

Signature

get(id: string): Promise<BusinessFunction>

Parameters

ParameterTypeRequiredDescription
idstringYesUnique identifier of the function

Returns

Promise that resolves to a BusinessFunction object.

Example

const fn = await api.functions.get('calculate-discount')

console.log('Function:', fn.name)
console.log('Description:', fn.description)
console.log('Metadata:', fn.metadata)

Error Handling

try {
  const fn = await api.functions.get('non-existent-function')
} catch (error) {
  if (error instanceof ApiError && error.statusCode === 404) {
    console.error('Function not found')
  }
}

Validation

// ❌ Throws ValidationError
await api.functions.get('')
// Error: id is required

await api.functions.get(null)
// Error: id is required

await api.functions.get(123)
// Error: id must be a string

Inspecting Function Details

async function inspectFunction(functionId: string) {
  const fn = await api.functions.get(functionId)

  console.log('='.repeat(50))
  console.log(`Function: ${fn.name}`)
  console.log('='.repeat(50))
  console.log(`ID: ${fn.id}`)
  console.log(`Description: ${fn.description || 'No description'}`)

  // Display input schema
  if (fn.input) {
    console.log('\nInput Schema:')
    console.log(JSON.stringify(fn.input, null, 2))
  }

  // Display output schema
  if (fn.output) {
    console.log('\nOutput Schema:')
    console.log(JSON.stringify(fn.output, null, 2))
  }

  // Display metadata
  if (fn.metadata) {
    console.log('\nMetadata:')
    Object.entries(fn.metadata).forEach(([key, value]) => {
      console.log(`  ${key}: ${value}`)
    })
  }

  return fn
}

await inspectFunction('calculate-discount')

create()

Create a new business function.

Signature

create(fn: Omit<BusinessFunction, 'id'>): Promise<BusinessFunction>

Parameters

ParameterTypeRequiredDescription
fnobjectYesFunction definition without ID

Returns

Promise that resolves to the created BusinessFunction with generated ID.

Example

const discountFunction = await api.functions.create({
  name: 'Calculate Discount',
  description: 'Calculate discount based on order total and customer tier',
  input: {
    orderTotal: 'number',
    customerTier: 'string',
  },
  output: {
    discountAmount: 'number',
    finalTotal: 'number',
  },
  execute: async (input) => {
    const discountRates = {
      basic: 0.05,
      premium: 0.1,
      enterprise: 0.15,
    }

    const rate = discountRates[input.customerTier] || 0
    const discountAmount = input.orderTotal * rate
    const finalTotal = input.orderTotal - discountAmount

    return { discountAmount, finalTotal }
  },
  metadata: {
    category: 'pricing',
    version: '1.0.0',
    author: 'finance-team',
  },
})

console.log('Created function:', discountFunction.id)

Complex Example: Tax Calculation

const taxFunction = await api.functions.create({
  name: 'Calculate Sales Tax',
  description: 'Calculate sales tax based on location and product category',

  input: {
    type: 'object',
    properties: {
      subtotal: { type: 'number', minimum: 0 },
      location: {
        type: 'object',
        properties: {
          country: { type: 'string' },
          state: { type: 'string' },
          zipCode: { type: 'string' },
        },
        required: ['country', 'state'],
      },
      category: {
        type: 'string',
        enum: ['physical', 'digital', 'service'],
      },
    },
    required: ['subtotal', 'location', 'category'],
  },

  output: {
    type: 'object',
    properties: {
      taxAmount: { type: 'number' },
      taxRate: { type: 'number' },
      total: { type: 'number' },
      breakdown: {
        type: 'object',
        properties: {
          state: { type: 'number' },
          county: { type: 'number' },
          city: { type: 'number' },
        },
      },
    },
  },

  execute: async (input) => {
    // Tax calculation logic
    const taxRates = await getTaxRates(input.location)
    const applicableRates = filterByCategory(taxRates, input.category)

    const breakdown = {
      state: input.subtotal * applicableRates.state,
      county: input.subtotal * applicableRates.county,
      city: input.subtotal * applicableRates.city,
    }

    const taxAmount = Object.values(breakdown).reduce((a, b) => a + b, 0)
    const total = input.subtotal + taxAmount

    return {
      taxAmount,
      taxRate: taxAmount / input.subtotal,
      total,
      breakdown,
    }
  },

  metadata: {
    category: 'tax',
    version: '2.1.0',
    lastUpdated: new Date().toISOString(),
    dependencies: ['tax-rates-service'],
    rateLimit: 100,
    cacheTTL: 3600,
  },
})

console.log('Tax function created:', taxFunction.id)

Validation

// ❌ Throws ValidationError
await api.functions.create(null)
// Error: function is required

await api.functions.create({} as any)
// Error: function must be an object

await api.functions.create({ name: 'My Function' } as any)
// Error: execute function is required

Function Composition

Create functions that call other functions:

// Create base functions
const priceFunction = await api.functions.create({
  name: 'Calculate Base Price',
  execute: async (input) => {
    return { basePrice: input.quantity * input.unitPrice }
  },
})

const discountFunction = await api.functions.create({
  name: 'Apply Discount',
  execute: async (input) => {
    return {
      discountedPrice: input.basePrice * (1 - input.discountRate),
    }
  },
})

// Create composed function
const finalPriceFunction = await api.functions.create({
  name: 'Calculate Final Price',
  description: 'Compose pricing with discount and tax',

  execute: async (input) => {
    // Call base price function
    const { basePrice } = await api.functions.execute(priceFunction.id, { quantity: input.quantity, unitPrice: input.unitPrice })

    // Apply discount
    const { discountedPrice } = await api.functions.execute(discountFunction.id, { basePrice, discountRate: input.discountRate })

    // Apply tax
    const { total } = await api.functions.execute('calculate-sales-tax', { subtotal: discountedPrice, location: input.location, category: input.category })

    return { finalPrice: total }
  },

  metadata: {
    category: 'pricing',
    composedFrom: [priceFunction.id, discountFunction.id, 'calculate-sales-tax'],
  },
})

Type Safety

Input/Output Types

Define strict types for function inputs and outputs:

// Define types
interface ProductPricingInput {
  productId: string
  quantity: number
  customerId: string
  promoCode?: string
}

interface ProductPricingOutput {
  unitPrice: number
  subtotal: number
  discount: number
  tax: number
  total: number
  savings: number
}

// Create function with types
const pricingFunction = await api.functions.create({
  name: 'Product Pricing',

  input: {
    type: 'object',
    properties: {
      productId: { type: 'string' },
      quantity: { type: 'number', minimum: 1 },
      customerId: { type: 'string' },
      promoCode: { type: 'string', optional: true },
    },
    required: ['productId', 'quantity', 'customerId'],
  },

  output: {
    type: 'object',
    properties: {
      unitPrice: { type: 'number' },
      subtotal: { type: 'number' },
      discount: { type: 'number' },
      tax: { type: 'number' },
      total: { type: 'number' },
      savings: { type: 'number' },
    },
  },

  execute: async (input: ProductPricingInput): Promise<ProductPricingOutput> => {
    // Implementation
    return {
      unitPrice: 29.99,
      subtotal: 29.99 * input.quantity,
      discount: 0,
      tax: 0,
      total: 29.99 * input.quantity,
      savings: 0,
    }
  },
})

// Execute with full type safety
const pricing = await api.functions.execute<ProductPricingInput, ProductPricingOutput>(pricingFunction.id, {
  productId: 'prod-123',
  quantity: 2,
  customerId: 'cust-456',
})

// TypeScript knows the exact shape
console.log(pricing.total) // ✅ number
console.log(pricing.invalid) // ❌ Property 'invalid' does not exist

Function Metadata

Standard Metadata Fields

interface FunctionMetadata {
  category?: string
  version?: string
  author?: string
  team?: string
  lastUpdated?: string
  tags?: string[]
  dependencies?: string[]
  rateLimit?: number
  cacheTTL?: number
  timeout?: number
  retries?: number
  visibility?: 'public' | 'private' | 'internal'
  deprecated?: boolean
  deprecationMessage?: string
  successors?: string[]
}

const fn = await api.functions.create({
  name: 'Legacy Calculator',
  execute: async (input) => ({ result: input.x + input.y }),
  metadata: {
    category: 'math',
    version: '1.0.0',
    author: 'engineering-team',
    lastUpdated: '2024-10-27T00:00:00Z',
    tags: ['math', 'calculator', 'deprecated'],
    deprecated: true,
    deprecationMessage: 'Use calculate-v2 instead',
    successors: ['calculate-v2'],
    visibility: 'internal',
  },
})

Querying by Metadata

async function findFunctionsByMetadata(predicate: (metadata: Record<string, any>) => boolean) {
  const functions = await api.functions.list()
  return functions.filter((fn) => fn.metadata && predicate(fn.metadata))
}

// Find all deprecated functions
const deprecated = await findFunctionsByMetadata((meta) => meta.deprecated === true)

// Find functions by team
const financeTeam = await findFunctionsByMetadata((meta) => meta.team === 'finance')

// Find recent functions
const recentFunctions = await findFunctionsByMetadata((meta) => {
  if (!meta.lastUpdated) return false
  const updated = new Date(meta.lastUpdated)
  const thirtyDaysAgo = new Date()
  thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
  return updated > thirtyDaysAgo
})

Best Practices

1. Use Descriptive Names

// ❌ Bad
await api.functions.create({
  name: 'calc',
  execute: async (input) => input.x + input.y,
})

// ✅ Good
await api.functions.create({
  name: 'Calculate Order Total',
  description: 'Calculate order total including discounts, tax, and shipping',
  execute: async (input) => {
    // Implementation
  },
})

2. Document Input/Output

// ✅ Good - Clear schema documentation
await api.functions.create({
  name: 'Process Payment',
  description: 'Process a payment transaction through the payment gateway',

  input: {
    type: 'object',
    properties: {
      amount: {
        type: 'number',
        description: 'Payment amount in cents',
        minimum: 1,
      },
      currency: {
        type: 'string',
        description: 'ISO 4217 currency code',
        pattern: '^[A-Z]{3}$',
      },
      paymentMethod: {
        type: 'string',
        description: 'Payment method ID from payment gateway',
      },
    },
    required: ['amount', 'currency', 'paymentMethod'],
  },

  execute: async (input) => {
    // Implementation
  },
})

3. Handle Errors Gracefully

await api.functions.create({
  name: 'Send Email',

  execute: async (input) => {
    try {
      // Send email
      const result = await emailService.send(input)
      return { success: true, messageId: result.id }
    } catch (error) {
      // Log error
      console.error('Failed to send email:', error)

      // Return error response
      return {
        success: false,
        error: error.message,
        retryable: error.code === 'RATE_LIMIT',
      }
    }
  },
})

4. Implement Idempotency

await api.functions.create({
  name: 'Create Order',

  execute: async (input) => {
    // Use idempotency key
    const idempotencyKey = input.idempotencyKey || crypto.randomUUID()

    // Check if already processed
    const existing = await db.query('orders').where('idempotencyKey', idempotencyKey).first()

    if (existing) {
      return existing // Return existing result
    }

    // Process order
    const order = await createOrder(input)

    // Store with idempotency key
    await db.insert('orders', {
      ...order,
      idempotencyKey,
    })

    return order
  },
})

5. Use Versioning

// Version 1
await api.functions.create({
  name: 'Calculate Shipping v1',
  metadata: { version: '1.0.0' },
  execute: async (input) => {
    // Simple calculation
    return { cost: input.weight * 0.5 }
  },
})

// Version 2 with improvements
await api.functions.create({
  name: 'Calculate Shipping v2',
  metadata: {
    version: '2.0.0',
    predecessors: ['calculate-shipping-v1'],
  },
  execute: async (input) => {
    // Complex calculation with zones and carriers
    return {
      cost: calculateByZone(input),
      estimatedDays: estimateDelivery(input),
      carrier: selectBestCarrier(input),
    }
  },
})

6. Implement Caching

const cache = new Map<string, { data: any; timestamp: number }>()

await api.functions.create({
  name: 'Get Exchange Rate',

  execute: async (input) => {
    const cacheKey = `${input.from}-${input.to}`
    const cached = cache.get(cacheKey)

    // Return cached if fresh (5 minutes)
    if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) {
      return cached.data
    }

    // Fetch fresh data
    const rate = await exchangeRateService.get(input.from, input.to)

    // Cache result
    cache.set(cacheKey, { data: rate, timestamp: Date.now() })

    return rate
  },

  metadata: {
    cacheTTL: 300, // 5 minutes
  },
})

Examples

E-commerce: Price Calculator

const priceCalculator = await api.functions.create({
  name: 'E-commerce Price Calculator',
  description: 'Calculate final price with discounts, tax, and shipping',

  input: {
    type: 'object',
    properties: {
      items: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            productId: { type: 'string' },
            quantity: { type: 'number' },
            unitPrice: { type: 'number' },
          },
        },
      },
      customerId: { type: 'string' },
      promoCode: { type: 'string', optional: true },
      shippingAddress: { type: 'object' },
    },
  },

  execute: async (input) => {
    // Calculate subtotal
    const subtotal = input.items.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0)

    // Apply customer discount
    const customer = await api.resources.get('Customer', input.customerId)
    const customerDiscount = subtotal * (customer.discountRate || 0)

    // Apply promo code
    let promoDiscount = 0
    if (input.promoCode) {
      const promo = await api.resources.get('PromoCode', input.promoCode)
      promoDiscount = promo.type === 'percentage' ? subtotal * promo.value : promo.value
    }

    // Calculate tax
    const taxableAmount = subtotal - customerDiscount - promoDiscount
    const { taxAmount } = await api.functions.execute('calculate-sales-tax', {
      subtotal: taxableAmount,
      location: input.shippingAddress,
      category: 'physical',
    })

    // Calculate shipping
    const { cost: shippingCost } = await api.functions.execute('calculate-shipping', {
      items: input.items,
      destination: input.shippingAddress,
    })

    // Calculate total
    const total = taxableAmount + taxAmount + shippingCost

    return {
      subtotal,
      discounts: {
        customer: customerDiscount,
        promo: promoDiscount,
        total: customerDiscount + promoDiscount,
      },
      taxAmount,
      shippingCost,
      total,
      savings: customerDiscount + promoDiscount,
    }
  },
})

// Use it
const pricing = await api.functions.execute(priceCalculator.id, {
  items: [
    { productId: 'prod-1', quantity: 2, unitPrice: 29.99 },
    { productId: 'prod-2', quantity: 1, unitPrice: 49.99 },
  ],
  customerId: 'cust-123',
  promoCode: 'SAVE10',
  shippingAddress: {
    country: 'US',
    state: 'CA',
    zipCode: '94102',
  },
})

SaaS: Usage Calculator

const usageCalculator = await api.functions.create({
  name: 'SaaS Usage Calculator',
  description: 'Calculate monthly bill based on usage metrics',

  execute: async (input) => {
    const { customerId, period } = input

    // Get customer plan
    const customer = await api.resources.get('Customer', customerId)
    const plan = await api.resources.get('Plan', customer.planId)

    // Get usage metrics
    const usage = await api.metrics.list({
      customerId,
      period,
    })

    // Calculate base fee
    const baseFee = plan.basePrice

    // Calculate usage fees
    const usageFees = plan.metrics.map((metric) => {
      const used = usage.find((u) => u.name === metric.name)?.value || 0
      const included = metric.included || 0
      const overage = Math.max(0, used - included)

      return {
        metric: metric.name,
        used,
        included,
        overage,
        fee: overage * metric.pricePerUnit,
      }
    })

    const totalUsageFees = usageFees.reduce((sum, u) => sum + u.fee, 0)

    // Apply any credits
    const credits = customer.credits || 0
    const subtotal = baseFee + totalUsageFees
    const creditsApplied = Math.min(credits, subtotal)
    const total = subtotal - creditsApplied

    return {
      period,
      baseFee,
      usageFees,
      subtotal,
      creditsApplied,
      total,
      nextBillingDate: calculateNextBillingDate(period),
    }
  },
})

Next Steps