Skip to content

Architecture

Iskra follows a hexagonal architecture (Ports & Adapters) that separates business logic from infrastructure details.

┌─────────────────────────────────────────────────┐
│ Tu Aplicacion │
│ (handlers, servicios, dominio) │
├─────────────────────────────────────────────────┤
│ Capa de Plugins │
│ (extensiones opcionales del framework) │
├─────────────────────────────────────────────────┤
│ Capa de Drivers │
│ WebPlugin │ DbDriver │ SocketDriver │ KV │ … │
├─────────────────────────────────────────────────┤
│ @iskra-bun/core │
│ App │ EventBus │ Logger │ Config │ DI │ Errors│
└─────────────────────────────────────────────────┘

Each framework kit exposes a Driver that registers itself on the App:

interface Driver {
name: string;
init(app: App): Promise<void> | void;
start?(): Promise<void> | void;
stop?(): Promise<void> | void;
}

The lifecycle is: register() → init() → start() → stop().

Plugins are lightweight extensions without a full lifecycle:

interface Plugin {
name: string;
install(app: App): Promise<void> | void;
}

Features are modules of the web Kernel with dependency resolution:

interface Feature {
name: string;
dependencies?: string[];
peerDependencies?: string[];
initialize(kernel: Kernel): Promise<void>;
routes?(app: Hono): void;
shutdown?(): Promise<void>;
}
  • A Driver has a full lifecycle (init/start/stop) and is registered with app.register(driver). Use it when you manage a resource that opens and closes: an HTTP server, a database connection, a WebSocket server.
  • A Plugin is lightweight and is installed immediately with app.use(plugin) via install(app). Use it to extend behavior without managing a long-lived resource.

Iskra uses app.context, a Map whose keys are typed through AppContextRegistry (see Core), as a DI container:

// Un driver se registra en el contexto
app.context.set('db', dbDriver);
// Otro componente lo consume, tipado: DbDriver | undefined
const db = app.context.get('db');

Components communicate through the event bus (based on mitt). Payloads are typed for the events declared in AppEvents:

// Escuchar
app.on('order:created', async (ctx) => {
const order = ctx.payload;
ctx.logger.info({ order }, 'Orden creada');
});
// Emitir
app.emit('order:created', { id: 1, total: 150 });

Every framework error extends IskraError:

IskraError
├── ConfigError
├── DriverError
├── PluginError
├── LifecycleError
├── HttpError (web-kit)
│ ├── ValidationError
│ ├── AuthError
│ ├── ForbiddenError
│ ├── NotFoundError
│ └── ConflictError
├── ConnectionError (db-kit)
├── QueryError (db-kit)
├── MigrationError (db-kit)
├── QueueError (worker-kit)
├── JobError (worker-kit)
├── SocketConnectionError (socket-kit)
└── SocketMessageError (socket-kit)

Each error has a code, a context (metadata) and supports cause (chaining).