# Banking plugin factory ## Purpose `createBankingPlugin(options?)` is the package's primary entry point — it Zod-validates the supplied options, builds the `BankingService` inside the descriptor's `onInit`, bootstraps the default bank inside `onReady`, mounts the OpenAPIHono route tree on the host `app` under the configured `routePrefix`, and drops every cached reference inside `onClose`. The descriptor is shaped against the canonical `@suaveplan/api-plugin` `PluginDescriptor` so any host that consumes that contract can register the banking plugin without bespoke wiring. ## Features - Zod boundary at `createBankingPlugin(options)` — invalid input rejects with `BankingError(BANKING.INVALID_OPTIONS)` carrying the `ZodError` as `cause`. - Declared dependency on the `database` plugin so the topological loader runs the database plugin first and the `db` slot is populated before `onInit` resolves it. - Optional dependencies on `auth`, `audit`, `rate-limit` so a host that registers those plugins gets them loaded before banking (matching the enterprise audit's future-work additions); when absent, the plugin still works (the optional dep contract). - Idempotent `initializeDefaultBank` invoked from `onReady` — safe on every host start. - `decommissionBankingTenant(service, tenantId)` helper exported for hosts that orchestrate tenant lifecycle externally (since the current `PluginDescriptor` does not yet declare tenant hooks). ## Usage: Basic ```ts import { createBankingPlugin } from "@suaveplan/api-plugin-banking"; const plugin = createBankingPlugin(); registry.register(plugin); ``` ## Usage: Advanced Customise the configuration: ```ts import { createBankingPlugin } from "@suaveplan/api-plugin-banking"; const plugin = createBankingPlugin({ routePrefix: "/api/v1/banking", maxAccountsPerUser: 25, enableOverdraft: true, enableTransfers: true, defaultBankName: "Acme Trust", defaultSortCode: 234567, }); await registry.register(plugin); await registry.initAll(); await registry.readyAll(); ``` Tenant decommission: ```ts import { decommissionBankingTenant, type BankingService, } from "@suaveplan/api-plugin-banking"; const service = ctx.getService("bankingService") as BankingService; const { banks, accounts } = await decommissionBankingTenant( service, tenantId ); ``` ## API Reference ### `createBankingPlugin(options?: BankingPluginOptionsInput): PluginDescriptor` Returns a frozen descriptor. The descriptor exposes: - `name: "banking"` - `version: "0.1.0"` - `dependencies: ["database"]` - `optionalDependencies: ["auth", "audit", "rate-limit"]` - `tags: ["banking", "accounts", "finance"]` - `defaults`: the parsed `BankingPluginConfig` - `onInit`, `onReady`, `onClose` ### `decommissionBankingTenant(service, tenantId): Promise<{ banks; accounts }>` Convenience wrapper that delegates to `BankingService.deleteTenantData`. Returns the count of soft-deleted banks and hard-deleted accounts. The deletes are not atomic — if the underlying SurQL throws between the two, `BANKING.TENANT_CLEANUP_FAILED` surfaces with the partial state visible to the next call. ## Implementation Notes The plugin uses `dependencies: ["database"]`, not `optionalDependencies` — banking is meaningless without a database, and surfacing a typed `BANKING.SERVICE_NOT_AVAILABLE` for the missing-slot case at `onInit` time is the canonical genesis pattern (instead of silently no-oping). The `emit` helper carries a typed-payload signature constrained by `BankingEventMap`. It runtime-casts to the loose `EventBusLike.emit` shape because `@suaveplan/types`' `EventBusLike` is intentionally untyped — strong typing lives on the per-plugin event map, not the bus. The cast is at one place and narrow. `onTenantCreate` is intentionally absent because the upstream package's version was a no-op stub. Hosts that need per-tenant default-bank provisioning call `service.createBank({ ... site_id })` from their own tenant lifecycle hook.