Skip to content

Adapters and drivers

Updated View as Markdown

Pupitre can use the host’s infrastructure without requiring every optional peer. Configure adapters in config/pupitre.ts using a value or () => Promise<adapter>. Installing an adapter package does not configure its database tables, mail transport or queue worker for you.

import { defineConfig } from '@rikology/pupitre'
import { mailDriver } from '@rikology/pupitre/drivers/mail'
import { jobsAdapter } from '@rikology/pupitre/drivers/queue'
import { notificationsAdapter } from '@rikology/pupitre/drivers/notifications'

export default defineConfig({
  mail: mailDriver({ from: 'admin@example.com' }),
  jobs: jobsAdapter(),
  notifications: notificationsAdapter(),
  storage: { disk: 'uploads' },
})

Supported integration points

Concern Configuration and contract Official driver / host dependency
Jobs jobs; dispatch/register/optional initialize jobsAdapter() from drivers/queue, @adonisjs/queue
Notifications notifications; toInbox/inbox/unreadCount/markRead notificationsAdapter() from drivers/notifications, @rikology/adonisjs-notifications
Mail mail; send message mailDriver({ from, mailer? }) from drivers/mail, @adonisjs/mail
Tenancy Panel tenancy.adapter; resolve/scope/fill/list/switch/run Column driver by default, createTenantDriver(definition) from drivers/tenant with @rikology/adonisjs-tenant
Storage storage.disk, field .disk() Host @adonisjs/drive; configure the named disk
Limiter Host limiter manager, action .rateLimit(...) @adonisjs/limiter, lazily used by auth/actions
Social auth Panel .social(...), host Ally providers @adonisjs/ally; no separate social config adapter slot
Realtime realtime; driver/publish/optional initialize(manager) transmitRealtime() from @rikology/pupitre-transmit, @adonisjs/transmit
Audit reads audit; forRecord returning normalized pages auditAdapter() from drivers/audit, @rikology/adonisjs-audit-trail

Driver module paths are @rikology/pupitre/drivers/<name>. Limiter and Ally helpers are runtime integration details, not factories to pass to defineConfig. Storage is a Drive disk setting, not an exported storageAdapter() factory. Optional peers are loaded lazily so unused features do not break host boot; missing required peers report clear integration errors.

Jobs and lifecycle

JobsAdapter provides dispatch(job, payload), register(job, handler) and optional initialize(). Pupitre registers fixed export/import/bulk handlers at boot. Payloads contain identities and storage references; a worker reloads user/tenant/records using scoped queries. Configure the host queue worker and durable storage. Handler context is not a live HTTP request/response.

Without a jobs adapter, async bulk/import falls back to synchronous execution. That does not remove import/export row limits. Set exports: { maxSyncRows, maxAsyncRows, retentionMinutes } intentionally and schedule pupitre:prune.

Mail, inbox and limits

Password reset/verification require mail delivery. mailDriver requires from and optionally mailer. Database notifications require the notifications peer’s persistence/channel configuration; toast feedback does not need inbox storage.

Auth endpoints enforce attempt limits even without the optional limiter, using a bounded in-memory fallback for a single process. At its 10,000-key capacity the fallback evicts the oldest insertion-order key, so identity churn can forget active quotas. Multi-worker or multi-instance deployments must configure a shared-store @adonisjs/limiter; an installed but broken limiter is not ignored. Ordinary action .rateLimit() is still fail-open when the host limiter is absent, so configure it before relying on action quotas. See authentication limits.

Implement custom interfaces using the exact configuration reference and keep application authorization and tenant ownership intact. A driver is an infrastructure bridge, not a reason to trust client state.

Realtime adapters

import { transmitRealtime } from '@rikology/pupitre-transmit'
import { auditAdapter } from '@rikology/pupitre/drivers/audit'

// Merge these keys into your existing defineConfig call:
const infrastructure = { realtime: transmitRealtime(), audit: auditAdapter() }

Both keys accept an adapter or a lazy adapter loader. The server realtime contract is:

interface RealtimeAdapter {
  driver: string
  publish(channel: string, event: RealtimeEvent): Promise<void>
  initialize?(manager: PupitreManager): void | Promise<void>
}

RealtimeEvent is { type: 'pupitre:notification' } or { type: 'pupitre:table', resource: string }. A transport owns channel authorization; never publish sensitive record data. Notification pings go to the user’s channel in every notifications-enabled panel. The browser contract, exported from @rikology/pupitre/catalog, is subscribe(channel, onEvent): () => void, where the returned function unsubscribes. Pass that client to <Pupitre realtime={client} registry={registry} />; server configuration alone does not connect the browser. See Transmit setup and authorization.

Audit read adapters

interface AuditAdapter {
  forRecord(
    model: LucidModel,
    id: string | number,
    options?: { page?: number; perPage?: number; tenantId?: string }
  ): Promise<AuditPage>
}

AuditPage contains items: AuditEntry[] and meta: { total, perPage, currentPage, lastPage } (all numbers). Each entry has id, event, nullable auditableType/auditableId, nullable actor: { type, id, label? }, changes: { field, old, new }[], tags: string[] and an ISO createdAt. See the exact types.

auditAdapter({ auditableType? }) defaults to the model class name and reads the audit-trail peer lazily. It does not capture events. Custom stores must enforce the supplied tenant constraint; an adapter must not leak histories across tenants just because record IDs coincide. AuditAdapter exposes only forRecord, not recent. The Audit plugin uses this adapter for record timelines, but its built-in Activity resource is audit-trail-model specific and reads that peer directly. Disable Activity and supply an authorized host page when using another history store.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close