SDK Full Reference
Exhaustive reference of all BizzOptima Module SDK APIs — types, capabilities, and real implementation examples.
Reference for @bizzoptima/module-sdk v1. Each section includes the full method signatures, required capability, and a complete real-world implementation example.
SDK 2.0 capabilities require at minimum signed_local. Unsigned modules only have
db:read and events:subscribe.Core namespaces — always available
api.db — SQLite databasedb:read / db:write
Local SQLite database scoped to the module. Other modules' tables are not accessible.
db.querydb:read<T>(sql: string, params?: unknown[]) => Promise<T[]>SELECT queries. Positional params with ?.
db.rundb:write(sql: string, params?: unknown[]) => Promise<{ changes: number; lastInsertRowid: number | bigint }>Real example — Contact management module
interface Contact { id: string; name: string; phone: string; created_at: string; }
// In init() — DB migration
async function migrate(api: BizzOptimaModuleAPI) {
await api.db.run(`
CREATE TABLE IF NOT EXISTS contacts (
id TEXT PRIMARY KEY, name TEXT NOT NULL,
phone TEXT, email TEXT, created_at TEXT NOT NULL
)
`);
}
// React component with search, add, delete
function ContactList() {
const api = useModuleAPI();
const toast = useToast();
const [search, setSearch] = React.useState('');
const { data: contacts, loading, refetch } = useDB<Contact>(
'SELECT * FROM contacts WHERE name LIKE ? ORDER BY name ASC',
['%' + search + '%'],
);
async function deleteContact(id: string) {
const ok = await api.ui.confirm('Delete this contact?');
if (!ok) return;
const result = await api.db.run('DELETE FROM contacts WHERE id = ?', [id]);
if (result.changes > 0) { toast('Contact deleted', { type: 'info' }); refetch(); }
}
async function addContact(name: string, phone: string) {
await api.db.run(
'INSERT INTO contacts (id, name, phone, email, created_at) VALUES (?,?,?,?,?)',
[crypto.randomUUID(), name, phone, '', new Date().toISOString()],
);
toast('Contact added', { type: 'success' });
refetch();
}
if (loading) return <div>Loading…</div>;
return (
<div>
<input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search…" />
{contacts.map(c => (
<div key={c.id}>
<strong>{c.name}</strong> — {c.phone}
<button onClick={() => deleteContact(c.id)}>Delete</button>
</div>
))}
<button onClick={() => addContact('New Contact', '+1 555 000 0000')}>Add</button>
</div>
);
}api.events — Inter-module busevents:emit / events:subscribe
Shared event bus across all active modules. Lets a module react to another module's actions without direct coupling.
events.emitevents:emit(name: string, payload: unknown, options?) => Promise<void>events.subscribeevents:subscribe(name: string, callback) => () => voidReturned function unsubscribes the listener.
Real example — Loyalty module reacts to sales
// In init() of loyalty module — subscribe to sales events
function initLoyalty(api: BizzOptimaModuleAPI) {
api.db.run(`
CREATE TABLE IF NOT EXISTS loyalty_points (
customer_id TEXT PRIMARY KEY, points INTEGER NOT NULL DEFAULT 0, updated_at TEXT NOT NULL
)
`);
return api.events.subscribe('sale.completed', async (raw: unknown) => {
const { customerId, total } = raw as { customerId: string | null; total: number };
if (!customerId) return;
const pointsEarned = Math.floor(total / 10); // 1 point per $10
if (!pointsEarned) return;
await api.db.run(`
INSERT INTO loyalty_points (customer_id, points, updated_at) VALUES (?, ?, ?)
ON CONFLICT(customer_id) DO UPDATE SET
points = points + excluded.points, updated_at = excluded.updated_at
`, [customerId, pointsEarned, new Date().toISOString()]);
api.ui.toast('+' + pointsEarned + ' loyalty point(s) awarded!', { type: 'success' });
});
}
// In sales module — emit on each completed sale
async function completeSale(api: BizzOptimaModuleAPI, sale: Sale) {
// ... save to DB ...
await api.events.emit('sale.completed', {
saleId: sale.id, customerId: sale.customerId,
total: sale.total, currency: api.config.getCurrency(),
}, { entityType: 'sale', entityId: sale.id });
}api.config — Business configurationAlways available
Read-only snapshot of the business configuration set up at first launch.
config.getBusiness() => BusinessConfigSnapshot{ businessId, businessName, currency, locale, activeModuleIds… }
config.getCurrency() => stringISO 4217: 'USD', 'EUR', 'XAF'…
config.getLocale() => 'fr' | 'en'config.getInstalledModules() => Promise<InstalledModuleInfo[]>Includes trustLevel per module.
Real example — Multi-currency display + module status
function PriceDisplay({ amount }: { amount: number }) {
const locale = useLocale();
const currency = useCurrency();
const formatted = new Intl.NumberFormat(locale === 'fr' ? 'fr-FR' : 'en-US', {
style: 'currency', currency, minimumFractionDigits: 0,
}).format(amount);
return <span className="font-mono font-semibold">{formatted}</span>;
}
function ModuleStatus() {
const business = useBusinessConfig();
const hasInventory = business.activeModuleIds.includes('inventory');
return (
<div>
<h3>{business.businessName}</h3>
<p>Currency: {business.currency} · Locale: {business.locale}</p>
{!hasInventory && <div className="warning">Inventory module not active.</div>}
</div>
);
}api.ui — UI helpersAlways available
Toasts, confirmations and alerts rendered in BizzOptima's main UI.
ui.toast(message: string, options?) => voidoptions: { type, duration }
ui.confirm(message: string, title?) => Promise<boolean>ui.alert(message: string, title?) => voidReal example — Delete with confirmation
async function deleteInvoice(api: BizzOptimaModuleAPI, invoiceId: string, number: string) {
const confirmed = await api.ui.confirm(
'Delete invoice #' + number + '? This action cannot be undone.',
'Confirm deletion',
);
if (!confirmed) return;
const payments = await api.db.query(
'SELECT id FROM payments WHERE invoice_id = ? AND status = ?',
[invoiceId, 'completed'],
);
if (payments.length > 0) {
await api.ui.alert(payments.length + ' completed payment(s) linked — cannot delete.', 'Blocked');
return;
}
await api.db.run('DELETE FROM invoice_lines WHERE invoice_id = ?', [invoiceId]);
await api.db.run('DELETE FROM invoices WHERE id = ?', [invoiceId]);
await api.events.emit('invoice.deleted', { invoiceId, number });
api.ui.toast('Invoice #' + number + ' deleted', { type: 'success' });
}api.interModule — Secure communicationAlways available
Typed RPC between active modules with caller restriction. The caller has no access to internal implementation.
interModule.expose(method, handler, options?) => () => voidoptions.allowedCallers: moduleId[] restricts access.
interModule.request<T>(targetId, method, params?) => Promise<T>Real example — Inventory exposes stock check to Cashier
// inventory module init():
api.interModule.expose('checkStock', async (params: unknown) => {
const { productId, qty = 1 } = params as { productId: string; qty?: number };
const [row] = await api.db.query<{ available: number; reserved: number }>(
'SELECT available, reserved FROM stock WHERE product_id = ?', [productId],
);
const s = row ?? { available: 0, reserved: 0 };
return { productId, available: s.available, canSell: s.available - s.reserved >= qty };
});
api.interModule.expose('reserveStock',
createSecureHandler(async (params: unknown) => {
const { productId, qty } = params as { productId: string; qty: number };
await api.db.run('UPDATE stock SET reserved = reserved + ? WHERE product_id = ?', [qty, productId]);
return { ok: true };
}, { moduleId: 'inventory', allowedCallers: ['quick_cashier', 'sales'] }),
);
// quick_cashier module — using inventory:
async function addToCart(api: BizzOptimaModuleAPI, productId: string, qty: number) {
const stock = await api.interModule.request<{ canSell: boolean; available: number }>(
'inventory', 'checkStock', { productId, qty },
);
if (!stock.canSell) {
api.ui.toast('Insufficient stock: ' + stock.available + ' available', { type: 'warning' });
return;
}
await api.interModule.request('inventory', 'reserveStock', { productId, qty });
await api.db.run('INSERT INTO cart_items (product_id, qty) VALUES (?,?)', [productId, qty]);
}api.security — Security contextAlways available
Read-only trust level and capabilities, available from initialization.
security.getTrustLevel() => ModuleTrustLevelsecurity.hasCapability(cap: ModuleCapability) => booleansecurity.requireCapability(cap: ModuleCapability) => voidThrows CapabilityError if missing.
Real example — Feature gating by signature level
import { usePermission, PermissionGuard, getMissingCapabilities, CapabilityError } from '@bizzoptima/module-sdk';
function SyncPanel() {
const api = useModuleAPI();
const trustLevel = useTrustLevel();
if (trustLevel === 'unsigned_local') {
return (
<div className="upgrade-banner">
<p>This module is not signed. Cloud sync requires at least signed_local.</p>
<a href="https://bizzoptima.com/docs/module-sdk#signing">Sign my module →</a>
</div>
);
}
return (
<PermissionGuard api={api} requires={['http:request', 'fs:write']}
fallback={
<p>Missing: {getMissingCapabilities(api, ['http:request', 'fs:write']).join(', ')}</p>
}
>
<ExportButton />
</PermissionGuard>
);
}
async function exportAndSync(api: BizzOptimaModuleAPI) {
try {
api.security.requireCapability('fs:write');
api.security.requireCapability('http:request');
} catch (err) {
if (err instanceof CapabilityError) {
api.ui.alert('Missing capability: ' + err.capability + '. Sign the module to unlock.');
return;
}
throw err;
}
const data = await api.db.query('SELECT * FROM sales WHERE synced = 0');
await api.fs.writeText('export/unsynced.json', JSON.stringify(data));
await api.http.post('https://erp.example.com/api/sync', data);
}api.settings — Persisted settingssettings:read / settings:write
Key/value storage scoped to the module. Independent from SQLite.
settings.getsettings:read(key) => Promise<string | null>settings.setsettings:write(key, value) => Promise<void>settings.deletesettings:write(key) => Promise<void>settings.getAllsettings:read() => Promise<Record<string, string>>const [erpUrl, setErpUrl] = useSetting('erp_url', '');
// Imperative: await api.settings.set('last_sync', new Date().toISOString());api.navigatenavigate
navigatenavigate(route: string) => voidconst navigate = useNavigate();
navigate('/modules/inventory');SDK 2.0 namespaces — require signed module
api.http — CORS-free HTTPhttp:request
All requests go through the Electron main process — no CORS, follows system proxy, supports self-signed HTTPS.
http.get<T>(url: string, headers?: Record<string,string>) => Promise<T>http.post<T>(url: string, body: unknown, headers?: Record<string,string>) => Promise<T>http.request(url: string, init?: HttpRequestInit) => Promise<HttpResponse>Real example — ERP sync with error handling
interface ErpProduct { sku: string; name: string; price: number; stock: number; }
async function syncFromErp(api: BizzOptimaModuleAPI, erpUrl: string, apiKey: string) {
api.security.requireCapability('http:request');
const products = await api.http.get<ErpProduct[]>(
erpUrl + '/api/products',
{ 'X-API-Key': apiKey },
);
let imported = 0;
for (const p of products) {
await api.db.run(`
INSERT INTO products (sku, name, price, stock, synced_at) VALUES (?, ?, ?, ?, ?)
ON CONFLICT(sku) DO UPDATE SET
name = excluded.name, price = excluded.price,
stock = excluded.stock, synced_at = excluded.synced_at
`, [p.sku, p.name, p.price, p.stock, new Date().toISOString()]);
imported++;
}
await api.settings.set('last_erp_sync', new Date().toISOString());
return imported;
}
// Hook for simple fetches
function ExchangeRates() {
const { data: rates, loading } = useHttp<{ USD: number }>(
'https://api.exchangerate.host/latest?base=XAF&symbols=USD',
);
if (loading) return <p>Loading rates…</p>;
return <p>1 XAF = {rates?.USD?.toFixed(6)} USD</p>;
}api.fs — Sandboxed file systemfs:read / fs:write / fs:dialog
Access restricted to userData/module-data/{moduleId}/. Module isolation is enforced.
fs.readText / writeText(path: string, content?: string) => Promise<string|null|void>fs.list(directory?: string) => Promise<FsFileInfo[]>fs.exists / delete(path: string) => Promise<boolean|void>fs.pickFilefs:dialog(filters?) => Promise<string|null>Real example — Export sales to CSV
function escCsv(val: string | number): string {
const s = String(val);
return (s.includes(',') || s.includes('"')) ? '"' + s.replace(/"/g, '""') + '"' : s;
}
async function exportSalesToCsv(api: BizzOptimaModuleAPI, from: string, to: string) {
api.security.requireCapability('fs:write');
const sales = await api.db.query<{
id: string; date: string; customer_name: string; total: number;
}>(`
SELECT s.id, s.created_at as date, c.name as customer_name, s.total
FROM sales s LEFT JOIN customers c ON s.customer_id = c.id
WHERE s.created_at BETWEEN ? AND ? ORDER BY s.created_at DESC
`, [from, to]);
if (!sales.length) { api.ui.alert('No sales in selected period.'); return; }
const csv = ['ID,Date,Customer,Total',
...sales.map(s => [escCsv(s.id), escCsv(s.date.slice(0,10)),
escCsv(s.customer_name ?? 'Anonymous'), escCsv(s.total)].join(','))
].join('\n');
await api.fs.writeText('exports/sales_' + from.slice(0,10) + '.csv', csv);
api.ui.toast('Export created: ' + sales.length + ' rows', { type: 'success' });
}api.scheduler — Job schedulerscheduler:manage
SQLite-persisted jobs that survive restarts. Tasks fire even if the module was inactive.
scheduler.once / interval / daily(id, delay/ms/time, callback) => ScheduledJobscheduler.cancel(id) => voidscheduler.onFired(callback) => () => voidReal example — Daily report + stock alert + startup backup
function initScheduler(api: BizzOptimaModuleAPI) {
// Daily sales report at 11 PM
api.scheduler.daily('daily-report', '23:00', async () => {
const today = new Date().toISOString().slice(0, 10);
const [s] = await api.db.query<{ total: number; count: number }>(
'SELECT SUM(total) as total, COUNT(*) as count FROM sales WHERE DATE(created_at) = ?', [today],
);
await api.fs.writeText('reports/' + today + '.json', JSON.stringify(s));
api.notifications.send('Daily report ready', s?.count + ' sale(s) today');
});
// Stock alert every 2 hours
api.scheduler.interval('stock-check', 2 * 60 * 60 * 1000, async () => {
const low = await api.db.query<{ name: string; available: number }>(
'SELECT name, available FROM products WHERE available <= min_stock AND min_stock > 0',
);
if (low.length > 0) {
api.notifications.send(low.length + ' product(s) low',
low.slice(0,3).map(p => p.name + ': ' + p.available + ' left').join('\n'));
}
});
// Auto-backup 5 min after start
api.scheduler.once('startup-backup', 5 * 60 * 1000, async () => {
const data = await api.db.query('SELECT * FROM sales ORDER BY created_at DESC LIMIT 1000');
await api.fs.writeText('backups/latest.json', JSON.stringify(data));
});
}api.print · api.notifications · api.clipboard · api.shell · api.network · api.agentVarious
print — Generate & print an invoice (HTML)
async function printInvoice(api: BizzOptimaModuleAPI, invoiceId: string) {
api.security.requireCapability('print:document');
const [inv] = await api.db.query<{ number: string; customer: string; total: number }>(
'SELECT i.number, c.name as customer, i.total FROM invoices i JOIN customers c ON i.customer_id = c.id WHERE i.id = ?',
[invoiceId],
);
if (!inv) return;
const html = `<!DOCTYPE html><html><head><style>
body{font-family:Arial} .total{color:#059669;font-size:20px;font-weight:bold}
</style></head><body>
<h1>${api.config.getBusiness().businessName}</h1>
<p>Invoice #${inv.number} — ${inv.customer}</p>
<p class="total">Total: ${inv.total.toLocaleString()} ${api.config.getCurrency()}</p>
</body></html>`;
await api.print.html(html);
api.ui.toast('Invoice sent to printer', { type: 'success' });
}agent — AI sales analysis
async function askAI(api: BizzOptimaModuleAPI, question: string) {
if (!api.agent.isAvailable()) return 'Xpress AI not configured.';
const result = await api.agent.run({
systemPrompt: 'You are a sales analyst. Use tools for up-to-date data.',
prompt: question,
maxSteps: 5,
tools: [{
name: 'sales_summary',
description: 'Get sales total and count for a date range',
parameters: { from_date: { type: 'string' }, to_date: { type: 'string' } },
handler: async ({ from_date, to_date }) => api.db.query(
'SELECT COUNT(*) as count, SUM(total) as total FROM sales WHERE DATE(created_at) BETWEEN ? AND ?',
[from_date, to_date],
),
}],
});
return result.output;
}clipboard · shell · network
// Copy reference to clipboard
await api.clipboard.writeText(txId);
// Open generated file in default app
api.shell.openPath('exports/report.csv');
// Offline-aware rendering
function OfflineBanner() {
const { online } = useNetwork();
if (online) return null;
return <div style={{ background: '#fef2f2' }}>⚠️ Offline — cloud sync paused.</div>;
}React Hooks
Available hooks
useModuleAPI() => BizzOptimaModuleAPIAccess the full API instance.
useBusinessConfig() => BusinessConfigSnapshotuseLocale() => 'fr' | 'en'useCurrency() => stringuseDB<T>(sql, params?) => { data, loading, error, refetch }useModuleEvents(eventName, callback) => voidAuto-unsubscribes on unmount.
useToast() => (message, options?) => voiduseNavigate() => (route) => voiduseSecurity() => SecurityContextuseTrustLevel() => ModuleTrustLevelusePermission(cap: ModuleCapability) => booleanusePermissions(caps: ModuleCapability[]) => booleanReturns true if ALL capabilities are available.
useNetwork() => { online: boolean }useHttp<T>(url, init?) => { data, loading, error, refetch }Requires http:request.
useSetting(key, defaultValue?) => [value, setter, loading]Requires settings:read/write.
Security utilities
Guards and helpers
PermissionGuard({ api, requires, fallback?, children }) => JSXConditionally renders based on capability.
createSecureHandler(handler, options) => SecureHandlerWraps an inter-module handler with caller verification.
createPaymentGuard(api, { allowedCallers? }) => (caller?) => voidGuard for sensitive payment operations.
assertCapabilities(api, capabilities[]) => voidThrows CapabilityError on first missing capability.
getMissingCapabilities(api, required[]) => ModuleCapability[]validateManifest(manifest) => { valid, errors[], warnings[] }Capabilities by trust level
| Capability | unsigned | signed_local | signed_registry | trusted_bundled |
|---|---|---|---|---|
| db:read | ✅ | ✅ | ✅ | ✅ |
| db:write | — | ✅ | ✅ | ✅ |
| events:subscribe | ✅ | ✅ | ✅ | ✅ |
| events:emit | — | ✅ | ✅ | ✅ |
| http:request | — | ✅ | ✅ | ✅ |
| fs:read / fs:write | — | ✅ | ✅ | ✅ |
| fs:dialog | — | — | ✅ | ✅ |
| scheduler:manage | — | ✅ | ✅ | ✅ |
| notifications:send | — | ✅ | ✅ | ✅ |
| print:document | — | — | ✅ | ✅ |
| clipboard:write | — | ✅ | ✅ | ✅ |
| clipboard:read | — | — | ✅ | ✅ |
| shell:open | — | ✅ | ✅ | ✅ |
| settings:read | — | — | ✅ | ✅ |
| settings:write | — | — | — | ✅ |