Files
accounted/lib/extensions/types.ts
T
Jakob Wennberg 03b569d708 refactor: consolidate extension system to general-only with manifest-driven architecture
- Remove all sector-specific extensions (construction, ecommerce, export,
  hotel, restaurant, tech) — only general-purpose extensions remain
- Move NE-bilaga and SRU export from extensions to core reports (lib/reports/)
- Move moms-box-mapping from extensions/export/shared to lib/vat/
- Replace per-extension API routes with catch-all dispatcher
  (app/api/extensions/ext/[...path]/route.ts)
- Add manifest.json for each extension with metadata, env vars, and deps
- Add api-routes.ts pattern for extension-defined API endpoints
- Add code generation scripts (generate-extension-registry, create-extension)
- Add extensions.config.json for opt-in extension loading
- Add extensions.schema.json for config validation
- Add email service interface with noop default (lib/email/service.ts)
- Add CI workflow (core-build.yml) to verify core builds with zero extensions
- Add migration 045: expand account_type CHECK for untaxed_reserves
- Update CLAUDE.md with comprehensive extension system documentation
- Update all report engines and bookkeeping services for new imports
- Clean up extensions.schema.json to only list existing extensions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 14:32:56 +01:00

200 lines
5.6 KiB
TypeScript

import type { SupabaseClient } from '@supabase/supabase-js'
import type { CoreEvent, CoreEventType } from '@/lib/events/types'
import type { EntityType, RawTransaction, IngestResult } from '@/types'
// ============================================================
// Extension Marketplace Types
// ============================================================
/** Extension category for marketplace grouping */
export type ExtensionCategory = 'accounting' | 'reports' | 'import' | 'operations'
/** Sector slugs for extension organization */
export type SectorSlug = 'general'
/** How an extension gets its data */
export type ExtensionDataPattern = 'core' | 'manual' | 'both'
/** Dashboard quick action declared by an extension */
export interface QuickActionDefinition {
label: string
description: string
icon: string
href?: string
event?: string
order?: number
}
/** Extension metadata for the marketplace and workspace routing */
export interface ExtensionDefinition {
slug: string
name: string
sector: SectorSlug
category: ExtensionCategory
description: string
longDescription: string
icon: string
entityTypes?: EntityType[]
dataPattern: ExtensionDataPattern
readsCoreTables?: string[]
hasOwnData?: boolean
quickAction?: QuickActionDefinition
/** Notice shown when enabling — e.g. external subscription requirement */
subscriptionNotice?: string
}
/** Sector definition with its extensions */
export interface Sector {
slug: SectorSlug
name: string
icon: string
description: string
extensions: ExtensionDefinition[]
}
/** Database row for extension toggle state */
export interface ExtensionToggle {
id: string
user_id: string
sector_slug: string
extension_slug: string
enabled: boolean
created_at: string
updated_at: string
}
// ============================================================
// Extension Interface & Supporting Types
// ============================================================
/** A route exposed by an extension (page route) */
export interface RouteDefinition {
path: string
label: string
}
/** An API route exposed by an extension */
export interface ApiRouteDefinition {
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
path: string
handler: (request: Request, ctx?: ExtensionContext) => Promise<Response>
}
/** Sidebar navigation item added by an extension */
export interface SidebarItem {
label: string
icon?: string
path: string
order?: number
}
/** Report type added by an extension */
export interface ReportDefinition {
id: string
name: string
description: string
}
/** Settings panel exposed by an extension */
export interface SettingsPanelDefinition {
label: string
path: string
}
/** Tax code definition added by an extension */
export interface TaxCodeDefinition {
code: string
rate: number
description: string
}
/** Dimension type definition added by an extension */
export interface DimensionDefinition {
id: string
name: string
description: string
}
/** Mapping rule type added by an extension */
export interface MappingRuleTypeDefinition {
id: string
name: string
description: string
}
/** Event handler registration for an extension */
export interface ExtensionEventHandler {
eventType: CoreEventType
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler: (payload: any, ctx?: ExtensionContext) => Promise<void> | void
}
/** Logger interface for extensions */
export interface ExtensionLogger {
info(message: string, ...args: unknown[]): void
warn(message: string, ...args: unknown[]): void
error(message: string, ...args: unknown[]): void
}
/** Settings accessor for extension-scoped key-value data */
export interface ExtensionSettings {
get<T>(key?: string): Promise<T | null>
set<T>(key: string, value: T): Promise<void>
}
/** Storage accessor wrapping Supabase storage */
export interface ExtensionStorage {
download(bucket: string, path: string): Promise<{ data: Blob | null; error?: string }>
upload(bucket: string, path: string, data: ArrayBuffer, options?: { contentType?: string }): Promise<{ path: string; error?: string }>
getPublicUrl(bucket: string, path: string): string
}
/** Core services exposed to extensions */
export interface ExtensionServices {
ingestTransactions(supabase: SupabaseClient, userId: string, raw: RawTransaction[]): Promise<IngestResult>
}
/** Context passed to extension lifecycle hooks and event handlers */
export interface ExtensionContext {
userId: string
extensionId: string
supabase: SupabaseClient
emit(event: CoreEvent): Promise<void>
settings: ExtensionSettings
storage: ExtensionStorage
log: ExtensionLogger
services: ExtensionServices
}
/**
* Extension interface — the contract for all add-ons.
*
* Extensions declare what they provide (routes, event handlers, sidebar items, etc.)
* and the registry wires them into the system.
*/
export interface Extension {
id: string
name: string
version: string
sector?: SectorSlug
// Surfaces
routes?: RouteDefinition[]
apiRoutes?: ApiRouteDefinition[]
sidebarItems?: SidebarItem[]
eventHandlers?: ExtensionEventHandler[]
mappingRuleTypes?: MappingRuleTypeDefinition[]
reportTypes?: ReportDefinition[]
settingsPanel?: SettingsPanelDefinition
taxCodes?: TaxCodeDefinition[]
dimensionTypes?: DimensionDefinition[]
/** Named services this extension provides to core via registry lookup */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
services?: Record<string, (...args: any[]) => Promise<any>>
// Lifecycle hooks
onInstall?(ctx: ExtensionContext): Promise<void>
onUninstall?(ctx: ExtensionContext): Promise<void>
}