receta
Object

Patterns & Recipes

Copy-paste ready patterns for common real-world scenarios. All examples are production-ready and based on actual API structures.

Table of Contents

  1. API Integration
  2. Form Handling
  3. Configuration Management
  4. Security & Sanitization
  5. Database Operations

API Integration

Pattern: GitHub API Normalization

import { map } from '@ontologyio/receta/result'
import * as Obj from '@ontologyio/receta/object'
import { pipe } from 'remeda'

// GitHub repository response → Your schema
const normalizeGitHubRepo = (apiResponse: unknown) =>
  pipe(
    apiResponse,
    Obj.validateShape({
      name: (v: unknown): v is string => typeof v === 'string',
      full_name: (v: unknown): v is string => typeof v === 'string',
      owner: {
        login: (v: unknown): v is string => typeof v === 'string',
        avatar_url: (v: unknown): v is string => typeof v === 'string'
      },
      stargazers_count: (v: unknown): v is number => typeof v === 'number',
      created_at: (v: unknown): v is string => typeof v === 'string'
    }),
    map(Obj.rename({
      full_name: 'fullName',
      stargazers_count: 'stars',
      created_at: 'createdAt'
    })),
    map((repo) => Obj.flatten(repo)),
    map((flat) => Obj.rename(flat, {
      'owner.login': 'ownerUsername',
      'owner.avatar_url': 'ownerAvatar'
    })),
    map((renamed) => Obj.unflatten(renamed))
  )

Pattern: Stripe Payment Intent Processing

import { toResult } from '@ontologyio/receta/option'
import { map as mapResult } from '@ontologyio/receta/result'

const cardBrandKey = 'charges.data.0.payment_method_details.card.brand'

// Stripe API → Your database schema
const processPaymentIntent = (stripeResponse: Obj.PlainObject) =>
  pipe(
    stripeResponse,
    Obj.getPath(['id']),
    toResult({ code: 'MISSING_ID' }),
    mapResult(() =>
      pipe(
        Obj.flatten(stripeResponse, { flattenArrays: true }),
        Obj.filterKeys((key) =>
          ['id', 'amount', 'currency', 'status', 'customer.email', cardBrandKey].some(
            (allowed) => key.startsWith(allowed)
          )
        ),
        Obj.rename({
          'customer.email': 'customerEmail',
          [cardBrandKey]: 'cardBrand'
        })
      )
    )
  )

Form Handling

Pattern: React Form State Manager

import { type Option } from '@ontologyio/receta/option'
import { type Result } from '@ontologyio/receta/result'

interface FormState<T> {
  data: T
  errors: Record<string, string>
  touched: Record<string, boolean>
}

class Form<T extends Obj.PlainObject> {
  constructor(private state: FormState<T>) {}

  // Get field value
  getValue<V>(path: Obj.ObjectPath): Option<V> {
    return Obj.getPath<V>(path, this.state.data)
  }

  // Update field
  updateField(path: Obj.ObjectPath, value: unknown): Form<T> {
    return new Form({
      ...this.state,
      data: Obj.setPath(path, value, this.state.data),
      touched: Obj.setPath(path, true, this.state.touched)
    })
  }

  // Validate and submit
  submit(schema: Obj.ObjectSchema<T>): Result<T, Obj.ObjectError> {
    return Obj.validateShape(Obj.compact(this.state.data), schema)
  }
}

// Usage
const userForm = new Form({
  data: { name: '', email: '', age: undefined },
  errors: {},
  touched: {}
})

userForm
  .updateField(['name'], 'Alice')
  .updateField(['email'], 'alice@example.com')
  .submit(userSchema)

Pattern: Multi-Step Form with Partial Validation

import { flatMap } from '@ontologyio/receta/result'
const multiStepForm = {
  // Step 1: Personal Info
  validateStep1: (data: Obj.PlainObject) =>
    pipe(
      data,
      Obj.mask(['name', 'email', 'phone']),
      Obj.stripUndefined,
      (partial) => Obj.validateShape(partial, step1Schema)
    ),

  // Step 2: Address
  validateStep2: (data: Obj.PlainObject) =>
    pipe(
      data,
      Obj.mask(['street', 'city', 'state', 'zip']),
      Obj.stripUndefined,
      (partial) => Obj.validateShape(partial, step2Schema)
    ),

  // Final: Combine and validate all
  submitAll: (formData: Obj.PlainObject) =>
    pipe(
      formData,
      Obj.compact,
      (data) => Obj.validateShape(data, fullSchema),
      flatMap(submitToAPI)
    )
}

Configuration Management

Pattern: Environment-Specific Config Merge

import { unwrapOr } from '@ontologyio/receta/result'
// Next.js / Vite style config merging
const loadConfig = () => {
  const base = {
    api: {
      url: 'https://api.example.com',
      timeout: 30000,
      retries: 3
    },
    features: {
      analytics: false,
      debugMode: false
    }
  }

  const envConfig = pipe(
    process.env,
    Obj.filterKeys((key) => key.startsWith('APP_')),
    Obj.mapKeys((key) => key.replace(/^APP_/, '').toLowerCase()),
    (flat) => Obj.unflatten(flat, { separator: '__' })
  )

  const userConfig = loadUserConfig()  // From file

  return pipe(
    [base, envConfig, userConfig],
    Obj.deepMerge({ arrayStrategy: 'concat' }),
    (merged) => Obj.validateShape<typeof base>(merged, configSchema),
    unwrapOr(base)
  )
}

Pattern: Feature Flags with Fallbacks

import { map, unwrapOr } from '@ontologyio/receta/option'

const getFeatureFlags = (remoteConfig: Obj.PlainObject) => {
  const defaults = {
    newDashboard: false,
    betaFeatures: false,
    experimentalApi: false
  }

  return pipe(
    remoteConfig,
    Obj.getPath<Record<string, boolean>>(['features']),
    map(Obj.compact),
    map((flags) => ({ ...defaults, ...flags })),
    unwrapOr(defaults)
  )
}

Security & Sanitization

Pattern: Sanitize Before Logging

const sanitizeForLogging = (
  obj: Obj.PlainObject,
  sensitiveFields: string[] = ['password', 'apiKey', 'creditCard', 'ssn']
) =>
  pipe(
    Obj.flatten(obj, { flattenArrays: true }),
    Obj.filterKeys((key) =>
      !sensitiveFields.some((sensitive) => key.toLowerCase().includes(sensitive.toLowerCase()))
    ),
    (safe) => Obj.unflatten(safe)
  )

// Usage
logger.info('User login', sanitizeForLogging(request))
// Removes every key that contains a sensitive name, at any depth

Pattern: GDPR-Compliant Data Minimization

const gdprMinimize = {
  // Only collect necessary analytics
  analytics: (event: UserEvent) =>
    pipe(
      event,
      Obj.mask(['action', 'timestamp', 'category']),
      Obj.stripUndefined
      // No PII stored
    ),

  // Anonymize user data for support
  supportTicket: (user: User) =>
    pipe(
      user,
      Obj.mask(['id', 'createdAt', 'subscription']),
      Obj.updatePath(['id'], (id: string) => hashUserId(id))
      // Email/name removed, ID hashed
    ),

  // Export user data (GDPR right to access)
  exportUserData: (userId: string) =>
    fetchAllUserData(userId).then((data) =>
      pipe(
        Obj.flatten(data),
        Obj.filterKeys((key) => !key.includes('_internal')),
        (kept) => Obj.unflatten(kept)
      )
    )
}

Pattern: API Response Filtering by Role

const filterByRole = (user: User, role: UserRole) => {
  const publicFields = ['id', 'username', 'avatar']
  const authenticatedFields = [...publicFields, 'email', 'createdAt']
  const adminFields = [...authenticatedFields, 'lastLogin', 'ipAddress', 'internalNotes']

  const allowedFields = {
    public: publicFields,
    user: authenticatedFields,
    admin: adminFields
  }[role]

  return Obj.mask(user, allowedFields)
}

Database Operations

Pattern: Query Builder

// A sketch of the data flow. Use parameterized queries with real user input.
const buildWhereClause = (filters: Record<string, unknown>) =>
  pipe(
    filters,
    Obj.compact,
    (active) => Obj.flatten(active),
    Obj.mapKeys((key) => `table.${key}`),
    Obj.mapValues((value) =>
      Array.isArray(value) ? `IN (${value.join(',')})` : `= '${value}'`
    ),
    Object.entries,
    (entries) => entries.map(([key, value]) => `${key} ${value}`).join(' AND ')
  )

// Usage
const sql = `SELECT * FROM users WHERE ${buildWhereClause({
  status: 'active',
  role: ['admin', 'moderator'],
  'profile.verified': true
})}`
// => "SELECT * FROM users WHERE table.status = 'active' AND table.role IN (admin,moderator) AND table.profile.verified = 'true'"

Pattern: Prisma-Style Nested Updates

const updateNestedUser = (userId: string, updates: DeepPartial<User>) =>
  pipe(
    updates,
    (partial) => Obj.flatten(partial),
    Obj.filterKeys((key) => !key.includes('id')),  // Protect ID fields
    Obj.stripUndefined,
    (flat) =>
      Object.entries(flat).reduce(
        (query, [path, value]) => query.update({ where: { id: userId }, data: { [path]: value } }),
        prisma.user
      )
  )

Complete Real-World Example: Payment Processing

import { isErr, isOk, mapErr, tapErr } from '@ontologyio/receta/result'

// Complete payment processing pipeline
const processPayment = async (stripeWebhook: unknown) => {
  // 1. Validate webhook structure
  const validated = pipe(
    stripeWebhook,
    Obj.validateShape(stripeWebhookSchema),
    mapErr((error) => ({
      code: 'INVALID_WEBHOOK',
      message: 'Webhook validation failed',
      details: error
    }))
  )

  if (isErr(validated)) {
    logger.error('Invalid webhook', validated.error)
    return validated
  }

  // 2. Extract payment data
  const paymentData = pipe(
    Obj.flatten(validated.value),
    Obj.filterKeys((key) =>
      ['data.object.id', 'data.object.amount', 'data.object.customer'].some((prefix) =>
        key.startsWith(prefix)
      )
    ),
    Obj.rename({
      'data.object.id': 'paymentId',
      'data.object.amount': 'amount',
      'data.object.customer': 'customerId'
    }),
    Obj.compact
  )

  // 3. Update database. flatMap can't await, so the async save sits outside the pipe.
  const payment = Obj.validateShape(paymentData, dbPaymentSchema)
  const dbResult = isOk(payment) ? await savePaymentToDb(payment.value) : payment

  tapErr(dbResult, (error) =>
    logger.error('DB save failed', { paymentData: sanitizeForLogging(paymentData), error })
  )

  // 4. Send notification
  if (isOk(dbResult)) {
    await sendPaymentConfirmation(dbResult.value.customerId)
  }

  return dbResult
}

Next Steps

On this page