4.0 KiB
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 withBankingError(BANKING.INVALID_OPTIONS)carrying theZodErrorascause. - Declared dependency on the
databaseplugin so the topological loader runs the database plugin first and thedbslot is populated beforeonInitresolves it. - Optional dependencies on
auth,audit,rate-limitso 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
initializeDefaultBankinvoked fromonReady— safe on every host start. decommissionBankingTenant(service, tenantId)helper exported for hosts that orchestrate tenant lifecycle externally (since the currentPluginDescriptordoes not yet declare tenant hooks).
Usage: Basic
import { createBankingPlugin } from "@suaveplan/api-plugin-banking";
const plugin = createBankingPlugin();
registry.register(plugin);
Usage: Advanced
Customise the configuration:
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:
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<BankingPluginConfig>
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 parsedBankingPluginConfigonInit,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.