Files
accounted/.claude/skills/create-extension/references/services-patterns.md
T
2026-02-27 11:08:53 +01:00

2.3 KiB

Service Integration Patterns

Two patterns for extensions to provide services to core. Both ensure core compiles without the extension.

Pattern A: Interface Registration

Best for single-implementation services (e.g., email). Core defines interface + noop default; extension registers at load time.

// Core: lib/email/service.ts
let emailService: EmailService = new NoopEmailService()
export function getEmailService(): EmailService { return emailService }
export function registerEmailService(svc: EmailService): void { emailService = svc }

// Extension: extensions/general/email/index.ts
import { registerEmailService } from '@/lib/email/service'
registerEmailService(new ResendEmailService())  // Registers at module load
export const emailExtension: Extension = { id: 'email', name: 'Email', version: '1.0.0' }

// Core consumption:
const svc = getEmailService()
if (svc.isConfigured()) await svc.sendEmail({ to, subject, html })

Pattern B: Services Record

Best for multiple named functions (e.g., AI categorization). Extension exposes via services; core discovers via registry.

// Extension:
services: {
  findSimilarTemplates: async (...args: unknown[]) => {
    const { findSimilarTemplates } = await import('./lib/template-embeddings')
    return findSimilarTemplates(args[0] as Transaction, args[1] as EntityType)
  },
},

// Core facade (lib/bookkeeping/template-embeddings.ts):
const aiExt = extensionRegistry.get('ai-categorization')
if (aiExt?.services?.findSimilarTemplates) {
  return aiExt.services.findSimilarTemplates(transaction, entityType)
}
return findMatchingTemplates(transaction, entityType)  // Fallback

Pattern C: Core Services for Extensions

Extensions consume core services via ctx.services:

await ctx?.services.ingestTransactions(ctx.supabase, ctx.userId, rawTransactions)

Comparison

Aspect Interface Registration Services Record
Defined in Core (lib/) Extension (index.ts)
Discovery get*() getter extensionRegistry.get().services
Functions Single interface Multiple named
Lazy imports No Yes (inside service fns)
Fallback Noop default Core provides fallback
Example Email AI categorization