@majikah/majik-buwiz-client
v0.9.0
Published
A TypeScript invoice management library that combines structured accounting logic with cryptographic envelope for signing, verification, encryption, and secure document workflows.
Maintainers
Readme
Majik Buwiz Client
Majik Buwiz Client is a high-level TypeScript client for the MajikBuwiz business and invoicing ecosystem.
Built on top of MajikKeyClient, it combines cryptographic account management with business-domain functionality for invoices, expenses, contacts, contact groups, recurring expenses, backups, application preferences, and audit/activity logs.
Instead of requiring application code to coordinate MajikKey, MajikInvoice, MajikInvoiceContactManager, storage adapters, signing keys, recipients, expected signers, and persistence independently, MajikBuwizClient provides a single orchestration layer for the complete workflow.
Build invoices. Track expenses. Manage identities. Keep the data under your control.
- Majik Buwiz Client
- Installation
- Initialization
- Persistence Model
- Hydration
- Account Management
- Explicit Account Selection
- Invoice Management
- Creating an Invoice
- Finalizing an Invoice
- Invoice Modes
- Signing an Invoice
- File Signing
- Sealing an Invoice
- Verifying Invoice Signatures
- Verifying an Invoice Seal
- Invoice Validation
- Decrypting an Invoice
- Batch Decryption
- Checking Decryption Capability
- Checking Signing Capability
- Checking Sealing Capability
- Reissuing an Invoice
- Restarting an Invoice
- Switching Invoice Modes
- Reissue + Sign + Store
- Invoice Queries
- Invoice Filters
- Invoice Ownership Queries
- Invoice Statistics
- Invoice Public Information
- Importing an Invoice
- Duplicate an Invoice
- CSV Export
- Contact Directory
- Adding Contacts
- Getting Contacts
- Checking Contacts
- Listing Contacts
- Updating Contacts
- Contact Import and Export
- Compressed Contact Exchange
- Contact Groups
- Listing Groups
- Managing Group Membership
- Querying Group Membership
- Favorites and Blocked Contacts
- Resolving Invoice Recipients and Signers
- Expected Signers
- Signer Labels
- Expense Management
- Creating an Expense
- Retrieving Expenses
- Expense Queries
- Expense Filters
- Expense Ownership Queries
- Duplicating Expenses
- Recurring Expenses
- Application Defaults
- Invoice Table Columns
- Expense Table Columns
- User Application Preferences
- Preference Helpers
- Audit and Activity Logs
- History Logs
- Activity Logs
- Recording Activity Manually
- Clearing Logs
- Key Data Reset
- Backup and Restore
- Backup Invoices
- Backup Expenses
- Backup Contacts
- Full Application Backup
- Reading a Backup Without Restoring
- Probing Backup Type
- Reading Invoice Backups
- Restoring Invoice Backups
- Reading Contact Backups
- Restoring Contacts
- Restoring Full Application Data
- Selective Restore
- Event System
- Available Events
- Error Handling
- Signing-Key Requirements
- Account Identity Resolution
- In-Memory Development Mode
- Custom Managers
- Custom Storage Adapters
- Typical Desktop Application Flow
- Typical Invoice Workflow
- Typical Expense Workflow
- Typical Backup Workflow
- Security-Oriented Design
- Important Design Principles
- API Surface
- TypeScript
- Related Majikah Packages
- License
- Author
- Majikah Solutions
- Contact
Overview
MajikBuwizClient is designed for applications that need to manage business records while preserving the cryptographic identity model of the Majikah ecosystem.
It provides:
- MajikKey account management through
MajikKeyClient - Multi-account support with active-account tracking
- Invoice creation, signing, sealing, verification, encryption, and decryption
- Invoice querying and filtering
- Invoice reissuing and mode switching
- Invoice ownership-aware queries
- Expense creation, duplication, querying, and removal
- Recurring expense actualization
- Contact directory management
- Contact groups, favorites, and blocked contacts
- Public-key/contact resolution for invoice recipients and expected signers
- Encrypted invoice workflows
- Application defaults and user preferences
- History/audit logs
- User activity logs
- Portable compressed application backups
- Selective backup inspection and restoration
- Pluggable persistence adapters
- Application event notifications
The client is intentionally storage-agnostic. Persistent domains can be backed by IndexedDB, SQLite, filesystem-backed storage, or other implementations of the provided adapter interfaces.
Architecture
At a high level, the client coordinates several domains:
┌─────────────────────┐
│ MajikBuwizClient │
└──────────┬──────────┘
│
┌───────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
MajikKeyClient MajikInvoiceManager ExpenseManager
│ │ │
│ │ └── RecurringExpenseManager
│ │
▼ ▼
MajikKey MajikInvoice
│ │
└───────────┬───────────┘
│
▼
MajikInvoiceContactManager
│
┌─────────┴─────────┐
▼ ▼
Contacts GroupsAdditional managers provide application state and operational history:
ClientStateManager
HistoryLogManager
UserActivityLogManagerEach domain can have its own storage adapter.
There is no requirement for a single monolithic application-state blob.
Installation
npm install @majikah/majik-buwiz-clientThe client is designed to work with the Majikah package ecosystem, including:
@majikah/majik-key
@majikah/majik-key-client
@majikah/majik-envelope
@majikah/majik-invoice
@majikah/majik-signature
@majikah/majik-cjson
@majikah/majik-fileInitialization
The recommended initialization path is MajikBuwizClient.create().
create() constructs the client and immediately hydrates all configured domains.
import { MajikBuwizClient } from "@majikah/majik-buwiz-client";
const client = await MajikBuwizClient.create({
adapters: {
// Provide your persistent adapters here.
// clientState, keys, contacts, invoices, expenses, etc.
},
});You can also instantiate the client directly and hydrate it manually:
const client = new MajikBuwizClient({
adapters: {
// persistent adapters
},
});
await client.hydrate();The two patterns are equivalent:
const client = await MajikBuwizClient.create(config);is effectively:
const client = new MajikBuwizClient(config);
await client.hydrate();Persistence Model
MajikBuwizClient uses a domain-oriented persistence model.
Instead of saving the entire client into one large application object, each domain owns its own storage adapter.
Typical domains include:
Keys
Client state
Contacts
Invoices
Expenses
Recurring expenses
History logs
User activity logsThis makes it possible to use different persistence mechanisms for different data sets.
For example:
const client = await MajikBuwizClient.create({
adapters: {
keys: myKeyAdapter,
clientState: myClientStateAdapter,
contacts: myContactsAdapter,
invoices: myInvoiceAdapter,
expenses: myExpenseAdapter,
recurringExpenses: myRecurringExpenseAdapter,
historyLogs: myHistoryAdapter,
userActivityLogs: myActivityAdapter,
},
});When a domain-specific manager is not supplied, the client creates a manager using the corresponding configured adapter.
For development and testing, the client can therefore operate with in-memory storage.
Hydration
Call hydrate() once when restoring application state from persistent storage.
Hydration occurs in an intentional order:
Keys
↓
Contacts
↓
Invoices
↓
Expenses
↓
Recurring Expenses
↓
History Logs
↓
Activity Logs
↓
Client State
↓
Owned Accounts
↓
Account OrderThis is important because account registration synchronizes owned identities into the contact directory.
await client.hydrate();For most applications, prefer:
const client = await MajikBuwizClient.create(config);Account Management
Because MajikBuwizClient extends MajikKeyClient, it inherits the underlying MajikKey account lifecycle and active-account functionality.
This includes concepts such as:
- Multiple owned accounts
- Account creation/import
- Lock and unlock states
- Active account selection
- Account key management
- Account ordering
- Identity lookup
MajikBuwiz adds business-specific synchronization between owned accounts and its contact directory.
For example, when an owned account is registered, a corresponding contact representation is automatically added to the shared directory.
Active account
Many operations default to the active account:
client.setActiveAccount(accountId);Operations that require a signing identity will fail when no suitable account is available.
For example:
await client.finalizeInvoice(
draft,
"signed-only",
[],
);uses the active account unless an explicit accountId is provided.
Explicit Account Selection
Operations can generally target a specific account using accountId.
await client.signInvoice(invoice, {
accountId: "account-id",
});This is particularly useful for applications that operate several businesses, brands, legal entities, or signing identities from the same installation.
Invoice Management
MajikBuwizClient provides the complete high-level invoice lifecycle.
Core operations include:
Create
Store
Sign
Seal
Decrypt
Verify
Validate
Duplicate
Reissue
Restart
Switch mode
Query
Export
Backup
RestoreCreating an Invoice
For low-level invoice creation:
const invoice = await client.createInvoice(
{
// MajikInvoiceInput fields
// except signerKey and recipients
...
},
{
recipientContacts: contacts,
expectedSigners,
},
);The client resolves the signing account automatically.
For application-level invoice finalization, finalizeInvoice() is usually more convenient.
Finalizing an Invoice
finalizeInvoice() provides the full application workflow:
GeneralInvoice
↓
Resolve signer
↓
Resolve contacts
↓
Build recipients
↓
Build expected signers
↓
Create MajikInvoice
↓
Sign
↓
Persist
↓
Increment invoice numberExample:
const invoice = await client.finalizeInvoice(
draft,
"signed-only",
[],
);For encrypted invoices:
const invoice = await client.finalizeInvoice(
draft,
"encrypted-and-signed",
[
customerContact.id,
],
);Encrypted-and-signed invoices require at least one recipient contact.
You can also explicitly select the signing account and invoice status:
const invoice = await client.finalizeInvoice(
draft,
"encrypted-and-signed",
[customerContact.id],
{
accountId: accountId,
status: "issued",
},
);Invoice Modes
MajikBuwiz supports the invoice modes provided by MajikInvoice.
The client explicitly handles:
signed-only
encrypted-and-signedSigned-only
The invoice is cryptographically signed without encrypting the invoice payload for recipients.
await client.finalizeInvoice(
draft,
"signed-only",
[],
);Encrypted and signed
The invoice is encrypted for resolved recipients and signed by the issuing account.
await client.finalizeInvoice(
draft,
"encrypted-and-signed",
[recipientContactId],
);Signing an Invoice
Sign an existing invoice by ID:
const signed = await client.signInvoice(invoiceId);Or pass an invoice instance:
const signed = await client.signInvoice(invoice);An explicit account can be selected:
const signed = await client.signInvoice(invoice, {
accountId,
});You can also provide an explicit signing timestamp:
const signed = await client.signInvoice(invoice, {
timestamp: new Date().toISOString(),
});Expected signers can also be supplied:
const signed = await client.signInvoice(invoice, {
expectedSigners,
});File Signing
The client also exposes file signing through MajikSignature.
const result = await client.signFile(fileBlob, {
contentType: "application/pdf",
});
console.log(result.blob);
console.log(result.signature);
console.log(result.handler);
console.log(result.mimeType);The implementation delegates file signing to MajikSignature.signFile().
The active account is automatically unlocked when needed.
A specific account can also be selected:
const result = await client.signFile(fileBlob, {
accountId,
contentType: "application/pdf",
});Expected signers can be attached to the operation:
const result = await client.signFile(fileBlob, {
expectedSigners,
});Sealing an Invoice
A signed invoice can be sealed to establish a final integrity state.
const sealed = await client.sealInvoice(invoice);The operation can also target an explicit account:
const sealed = await client.sealInvoice(invoice, {
accountId,
});Seal information can later be retrieved:
const sealInfo = await client.getInvoiceSealInfo(invoice.id);Verifying Invoice Signatures
Verify all invoice signatures:
const results = await client.verifyInvoiceSignatures(invoice);Verify the signature of one signer:
const result = await client.verifyInvoiceSignature(
invoice,
signerId,
);Verification results are returned from the underlying MajikInvoice instance.
Verifying an Invoice Seal
const result = await client.verifyInvoiceSeal(invoice);This verifies the invoice's seal state independently from signature verification.
Invoice Validation
Run invoice validation using the stored invoice:
const result = await client.validateInvoice(invoice.id);
console.log(result);The method resolves the invoice and delegates validation to MajikInvoice.
Decrypting an Invoice
Decrypt a single invoice:
const result = await client.decryptInvoice(invoice);The returned result contains both the decrypted invoice and the updated invoice instance.
console.log(result.invoice);
console.log(result.instance);The updated instance is saved back into the invoice manager.
Batch Decryption
Decrypt every cached invoice accessible by an account:
const result = await client.decryptCachedInvoices();
console.log(result);Or target a specific account:
const result = await client.decryptCachedInvoices(accountId);You can also decrypt an explicitly supplied list:
const result = await client.decryptInvoices(
invoices,
accountId,
);Successful decrypted instances are written back to the invoice store.
Checking Decryption Capability
Before attempting decryption:
const canDecrypt = await client.canDecryptInvoice(
invoice,
accountId,
);
if (canDecrypt) {
// safe to offer decrypt action
}Returns false when the invoice, account, or key is unavailable.
Checking Signing Capability
Use canSignInvoice() to determine whether an account can sign an invoice.
const result = await client.canSignInvoice(
invoice,
accountId,
);
if (!result.permitted) {
console.log(result.reason);
}The method also prevents signing an invoice that the same account has already signed.
Checking Sealing Capability
const result = await client.canSealInvoice(
invoice,
accountId,
);
if (!result.permitted) {
console.log(result.reason);
}Reissuing an Invoice
Reissue an invoice with an updated GeneralInvoice representation.
const reissued = await client.reissueInvoice(
invoice,
updatedInvoice,
{
recipientContactIds: [
customerContact.id,
],
},
);The client automatically resolves:
- Recipients
- Public keys
- Expected signers
- Signing identity
For encrypted invoices, the updated invoice cache is refreshed so consumers do not immediately need to perform another decryption pass.
Restarting an Invoice
restartInvoice() creates a new invoice lineage from an existing invoice.
const restarted = await client.restartInvoice(invoice);The implementation handles encrypted invoices specially so the restarted invoice can continue through the appropriate signed/encrypted workflow.
Switching Invoice Modes
Switch a finalized invoice between:
signed-only
encrypted-and-signedExample:
const updated = await client.switchInvoiceMode(
invoice,
"encrypted-and-signed",
[customerContact.id],
);Switching back:
const updated = await client.switchInvoiceMode(
invoice,
"signed-only",
);Signatures can optionally be dropped and replaced during the operation:
const updated = await client.switchInvoiceMode(
invoice,
"signed-only",
[],
{
dropSignatures: true,
},
);When switching into encrypted-and-signed mode, at least one recipient is required.
Reissue + Sign + Store
For application interfaces such as invoice editors, reissueSignAndStore() provides the complete update pipeline.
const updated = await client.reissueSignAndStore(
invoice,
updatedDraft,
recipientContactIds,
);This method:
- Resolves the signing account.
- Resolves recipient contacts.
- Resolves expected signers.
- Reissues the invoice.
- Signs the reissued invoice.
- Refreshes decrypted cache data when needed.
- Stores the result.
- Emits
invoice-updated.
This is useful when an application wants one high-level method for "save changes".
Invoice Queries
Retrieve one invoice:
const invoice = await client.getInvoice(invoiceId);Throw when missing:
const invoice = await client.getInvoiceOrThrow(invoiceId);List invoices:
const invoices = client.listInvoices();Query with options:
const result = client.queryInvoices({
sortBy: "createdAt",
sortDir: "desc",
});Advanced querying:
const result = await client.queryInvoicesAdvanced({
...
});Invoice Filters
Convenience methods are provided for frequently used queries.
By active account
const invoices =
await client.listInvoicesByActiveAccount();By issue date
const invoices =
await client.listInvoicesByIssuedAtRange({
from: startDate,
to: endDate,
});By creation date
const invoices =
await client.listInvoicesByCreatedAtRange({
from: startDate,
to: endDate,
});By mode
const invoices =
await client.listInvoicesByMode("signed-only");By issuer
const invoices =
client.listInvoicesByIssuer("Acme Corporation");By recipient
const invoices =
client.listInvoicesByRecipient("Customer Name");By status
const invoices =
client.listInvoicesByStatus("paid");Multiple statuses can be supplied:
const invoices =
client.listInvoicesByStatus([
"issued",
"sent",
]);Invoice Ownership Queries
A local store can contain invoices belonging to more than one account.
Use:
const foreignInvoices =
await client.getInvoicesNotOwnedByActiveAccount();For a lightweight count:
const count =
await client.countInvoicesNotOwnedByActiveAccount();When backed by a SQL-based storage adapter, the count operation can be performed at the database level without loading every invoice.
Invoice Statistics
const stats = client.getInvoiceStats();Returns:
{
total: number;
draft: number;
issued: number;
paid: number;
overdue: number;
void: number;
}The issued count includes invoices with status:
issued
sentInvoice Public Information
Retrieve the public invoice summary:
const summary =
await client.getInvoicePublicSummary(invoice.id);Retrieve seal information:
const sealInfo =
await client.getInvoiceSealInfo(invoice.id);Importing an Invoice
Import from JSON:
const invoice = client.importInvoice(json);The JSON may be either a serialized JSON string or a MajikInvoiceJSON object.
Duplicate an Invoice
const duplicated =
await client.duplicateInvoice(invoice);A specific account/key may be supplied:
const duplicated =
await client.duplicateInvoice(invoice, {
account: accountId,
});CSV Export
Export multiple invoices to CSV:
const result =
await client.batchExportInvoicesToCSV(invoices);Custom columns:
const result =
await client.batchExportInvoicesToCSV(
invoices,
{
columns,
},
);A decryption key is resolved automatically from the selected account.
Contact Directory
MajikBuwizClient exposes the shared contact directory through high-level convenience methods.
Contacts can be:
Added
Removed
Updated
Imported
Exported
Compressed
Resolved by ID
Resolved by public key
Grouped
Favorited
BlockedAdding Contacts
await client.addContact(contact);Adding a contact also records a user activity entry and emits:
new-contactGetting Contacts
By ID:
const contact =
client.getContactByID(contactId);By address:
const contact =
await client.getContactByAddress(publicKeyAddress);By public key:
const contact =
await client.getContactByPublicKey(publicKeyBase64);Multiple IDs:
const contacts =
client.getContactsByID(ids);Multiple public keys:
const contacts =
await client.getContactsByPublicKey(publicKeys);Checking Contacts
const exists =
client.hasContact(contactId);Or by public-key address:
const exists =
await client.hasContactByAddress(address);Listing Contacts
By default, owned accounts are excluded from listContacts().
const contacts = client.listContacts();Include owned accounts:
const contacts =
client.listContacts(true);Restrict to Majikah contacts:
const contacts =
client.listContacts(false, true);Updating Contacts
await client.updateContactMeta(
contactId,
{
label: "Main Customer",
},
);Owned account metadata can also be updated:
await client.updateOwnAccountMeta(
accountId,
{
label: "Business Account",
},
);The active account has a dedicated helper:
await client.updateActiveAccountMeta({
label: "My Business",
});Contact Import and Export
Export JSON:
const json =
await client.exportContactAsJSON(contactId);Export the manager's string representation:
const encoded =
await client.exportContactAsString(contactId);Import JSON:
await client.importContactFromJSON(json);Import encoded contact data:
await client.importContactFromString(encoded);Compressed Contact Exchange
Contacts can also be exchanged using a compressed base64 representation.
Export:
const compressed =
await client.exportContactCompressed(contact);Import:
const imported =
await client.importContactCompressed(compressed);This is useful for compact contact-card sharing.
Contact Groups
Create a group:
const group = await client.createGroup(
"customers",
"Customers",
);Add an existing group:
await client.addGroup(group);Get a group:
const group =
client.getContactGroup("customers");Get-or-throw:
const group =
client.getGroupOrThrow("customers");Check existence:
const exists =
client.hasGroup("customers");Listing Groups
List all groups:
const groups =
client.listContactGroups();List only user-created groups:
const groups =
client.listUserGroups();List system-managed groups:
const groups =
client.listSystemGroups();Groups can optionally be sorted by name:
const groups =
client.listContactGroups(true, true);Managing Group Membership
Add one contact:
await client.addContactToGroup(
groupId,
contactId,
);Add multiple contacts:
await client.addContactsToGroup(
groupId,
contactIds,
);Remove a contact:
await client.removeContactFromGroup(
groupId,
contactId,
);Move a contact between groups:
await client.moveContactBetweenGroups(
contactId,
fromGroupId,
toGroupId,
);Querying Group Membership
Get contacts:
const contacts =
client.getContactsInGroup(groupId);Sorted:
const contacts =
client.getContactsInGroupSorted(groupId);Check membership:
const included =
client.isContactInGroup(
groupId,
contactId,
);Get groups for a contact:
const groups =
client.getGroupsForContact(contactId);Or only their IDs:
const groupIds =
client.getGroupIdsForContact(contactId);Favorites and Blocked Contacts
The contact directory includes built-in system groups for:
Favorites
BlockedAdd to favorites:
await client.addContactToFavorites(contactId);Remove from favorites:
await client.removeContactFromFavorites(contactId);Check favorite state:
const favorite =
client.isContactFavorite(contactId);Check blocked state:
const blocked =
client.isContactBlocked(contactId);Retrieve the built-in groups:
const favorites =
client.getFavoritesGroup();
const blocked =
client.getBlockedGroup();Retrieve contacts:
const favoriteContacts =
client.getFavoriteContacts();
const blockedContacts =
client.getBlockedContacts();System groups are protected from being overwritten by backup restoration.
Resolving Invoice Recipients and Signers
The client can convert contacts into structures required by MajikInvoice and MajikSignature.
By public key:
const recipients =
await client.getMajikRecipientsByPublicKey(
publicKeys,
);
const signers =
await client.getExpectedSignersByPublicKey(
publicKeys,
);By contact ID:
const recipients =
await client.getMajikRecipientsByIDs(
contactIds,
);
const signers =
await client.getExpectedSignersByIDs(
contactIds,
);For workflows requiring all resolved data:
const data =
await client.getMajikahInvoiceDataByID(
contactIds,
);The returned structure contains:
{
recipients: MajikRecipient[];
signers: ExpectedSigner[];
publicKeys: MajikMessagePublicKey[];
}A public-key version is also available:
const data =
await client.getMajikahInvoiceDataByPublicKey(
publicKeys,
);Expected Signers
Create an expected signer directly from a MajikKey:
const signer =
MajikBuwizClient.expectedSignerFromKey(key);Or resolve an expected signer from a contact:
const signer =
client.expectedSignerFromContact(contactId);The contact must contain the signing public-key material required by the signature system.
Signer Labels
Resolve a human-readable signer label:
const label =
client.resolveSignerLabel(signerId);The client checks:
- Owned accounts
- Shared contact directory
- Fallback truncated identifier
This is useful for UI rendering of signer histories and verification results.
Expense Management
Majik Buwiz also provides a complete expense domain alongside invoices.
Core operations include:
Create
Store
Retrieve
Duplicate
Query
Filter
Remove
Actualize recurring expenses
Backup
RestoreCreating an Expense
const expense = await client.createExpense({
// ExpenseRecordInput
...
});The expense is associated with the selected account's fingerprint.
An explicit account can be used:
const expense = await client.createExpense(
input,
{
accountId,
},
);To create without immediately storing it:
const expense = await client.createExpense(
input,
{
skipStore: true,
},
);Retrieving Expenses
const expense =
await client.getExpense(expenseId);Or throw when missing:
const expense =
await client.getExpenseOrThrow(expenseId);List expenses:
const expenses =
client.listExpenses();Expense Queries
Basic queries:
const result =
client.queryExpenses({
...
});Advanced queries:
const result =
await client.queryExpensesAdvanced({
...
});Expense Filters
By active account:
const expenses =
await client.listExpensesByActiveAccount();By creation date:
const expenses =
await client.listExpensesByCreatedAtRange({
from: startDate,
to: endDate,
});By category:
const expenses =
await client.listExpensesByCategory(category);By status:
const expenses =
client.listExpensesByStatus("...");Expense Ownership Queries
List expenses belonging to other accounts:
const expenses =
await client.getExpensesNotOwnedByActiveAccount();Get only the count:
const count =
await client.countExpensesNotOwnedByActiveAccount();This is useful for multi-account dashboards where the current account should only display its own business records.
Duplicating Expenses
const duplicate =
await client.duplicateExpense(expense);A different account/key can be selected:
const duplicate =
await client.duplicateExpense(
expense,
{
account: accountId,
},
);Recurring Expenses
Recurring expense definitions can be actualized into normal expense records.
Actualize one item:
const result =
await client.actualizeRecurringExpense(
recurringExpenseId,
);With options:
const result =
await client.actualizeRecurringExpense(
recurringExpenseId,
{
...
},
);Actualize all recurring expenses:
const results =
await client.actualizeAllRecurringExpenses();The client automatically coordinates the recurring expense manager and expense manager so duplicate actualizations can be prevented.
Application Defaults
Invoice defaults are persisted through ClientStateManager.
Retrieve:
const defaults =
await client.getInvoiceDefaults();Save:
await client.setInvoiceDefaults({
...
});Remove:
await client.removeInvoiceDefaults();These defaults are intended to help application interfaces pre-fill invoice creation forms. The client does not automatically inject them into every newly created invoice.
Invoice Table Columns
Persist invoice table configuration:
const columns =
await client.getInvoiceTableColumns();Save:
await client.setInvoiceTableColumns(
columns,
);Reset:
await client.resetInvoiceTableColumns();Expense Table Columns
Retrieve:
const columns =
await client.getExpenseTableColumns();Save:
await client.setExpenseTableColumns(
columns,
);Reset:
await client.resetExpenseTableColumns();User Application Preferences
Retrieve preferences:
const preferences =
await client.getUserAppPreferences();Persist preferences:
await client.setUserAppPreferences(
preferences,
);Remove preferences:
await client.removeUserAppPreferences();Reset to defaults:
await client.resetUserAppPreferences();Preference Helpers
Analytics preference:
const enabled =
await client.isAnalyticsEnabled();Auto-decryption preference:
const enabled =
await client.isAutoDecryptInvoicesEnabled();Audit and Activity Logs
Majik Buwiz separates operational logging into two concepts:
History logs
User activity logsThe managers are exposed directly:
const history =
client.historyManager;
const activity =
client.activityManager;History Logs
History records are designed for persisted operational history.
Retrieve history for the active account:
const logs =
client.listHistoryForActiveAccount();History recording is deliberately non-blocking from the perspective of the primary business operation.
A failure to record a history entry does not fail the invoice or account operation it accompanies.
History retention respects the user's configured history limit.
Activity Logs
Retrieve activity for the active account:
const logs =
client.listActivityForActiveAccount();Activity recording is also non-blocking.
The client maintains a hard limit of 5,000 activity records per fingerprint and removes the oldest records when the limit is exceeded.
Recording Activity Manually
Applications can record their own activity:
await client.recordActivity(
accountFingerprint,
{
reference_id: "invoice-123",
action: "...",
metadata: {
...
},
},
);The method returns either the created log entry or null.
Clearing Logs
Clear the active account's history:
await client.clearHistoryLogsForActiveAccount();Clear activity:
await client.clearActivityLogsForActiveAccount();Clear both and start a new activity trail:
await client.restartLogsForActiveAccount();Log hydration for an unlocked active account is also available:
await client.hydrateLogsForActiveAccount();Key Data Reset
When key-derived application data is reset, the client clears:
Contacts
Invoices
Expenses
Recurring expensesHistory and activity logs are intentionally preserved.
This makes it possible to retain an audit trail across a cryptographic/account data reset.
Backup and Restore
MajikBuwiz supports portable backups for individual domains as well as application-wide state.
Backup payloads are:
- Serialized
- Compressed using
MajikCompressedJSON - Prefixed with a domain-specific magic-byte header
The magic header allows the client to identify the backup type before attempting full parsing.
Supported backup domains include:
Invoices
Expenses
Contacts
Application dataBackup Invoices
const blob =
client.backupInvoices();Backup Expenses
const blob =
client.backupExpenses();Backup Contacts
const blob =
await client.backupContacts();Contact backups include the contact directory and user-defined groups.
Built-in system groups such as Favorites and Blocked are handled as system-managed data and are not treated as ordinary user groups during restore.
Full Application Backup
const blob =
await client.backupAppData();The application backup can contain:
Contacts
Contact groups
Invoices
Expenses
Invoice defaults
User application preferencesReading a Backup Without Restoring
A major design goal of the backup system is to separate inspection from mutation.
For example:
const snapshot =
await client.readAppDataBackup(blob);Nothing is written to the live store.
This allows a UI to:
- Inspect a backup
- Show record counts
- Preview data
- Ask the user what should be restored
- Perform a selective restore
Probing Backup Type
Before fully parsing a backup:
const type =
await MajikBuwizClient.probeBackupType(blob);Possible results are:
"invoices"
"contacts"
"expenses"
"appData"
"unknown"This is useful for validating files selected through a file picker.
Reading Invoice Backups
const invoices =
await client.readInvoicesBackup(blob);This parses the backup but does not write invoices to the store.
Restoring Invoice Backups
const result =
await client.restoreInvoices(blob);
console.log(result.restored);Reading Contact Backups
const snapshot =
await client.readContactsBackup(blob);The returned snapshot contains hydrated contact and group instances as well as the underlying manager representation used by the restore pipeline.
Restoring Contacts
const result =
await client.restoreContacts(blob);Options are available:
const result =
await client.restoreContacts(
blob,
{
overwriteContacts: false,
includeGroups: true,
},
);When overwriteContacts is false, existing matching contacts are left untouched.
System-managed groups are never overwritten.
Restoring Full Application Data
For a complete restore:
const result =
await client.restoreAppData(blob);
console.log(result);
/*
{
contacts: number;
groups: number;
invoices: number;
}
*/Contacts and groups are restored before invoices so contact references can be resolved correctly.
Selective Restore
Applications can inspect the backup first:
const snapshot =
await client.readAppDataBackup(blob);Then restore only selected domains:
const result =
await client.restoreAppDataSelective(
snapshot,
{
invoices: true,
expenses: true,
contacts: false,
groups: false,
invoiceDefaults: true,
preferences: false,
},
);This is useful for restore/merge interfaces where the user should be able to choose exactly which parts of a backup are imported.
Event System
MajikBuwizClient exposes an event system for application integration.
Register a listener:
client.on(
"invoice-created",
(invoice) => {
console.log("Invoice created", invoice);
},
);Remove a listener:
client.off(
"invoice-created",
callback,
);Remove all listeners for an event:
client.off("invoice-created");Available Events
The client exposes the following domain events in addition to events inherited from MajikKeyClient:
Contact events
new-contact
updated-contact
removed-contact
new-contact-group
removed-contact-group
contact-group-changeInvoice events
invoice-created
invoice-updated
invoice-removed
invoice-signed
invoice-sealed
invoice-verified
invoice-decrypted
invoice-reissued
invoice-decrypted-batch
invoice-export-csv
invoice-export-pdf
invoice-export-mjki
invoice-clearExpense events
expense-created
expense-updated
expense-removed
expense-actualized
expense-clearLogging events
history-log
activity-logError events
The client also uses the inherited error event mechanism when an operation fails.
Example:
client.on(
"error",
(error, context) => {
console.error(
"Majik Buwiz error",
error,
context,
);
},
);Error Handling
High-level client methods generally follow one of two patterns.
Validation and account errors
Operations requiring an account, key, or valid identifier may throw immediately.
For example:
await client.signInvoice(invoice);can fail when no active account exists or when the resolved account cannot sign.
Domain-operation errors
Invoice operations catch failures, emit an error event, and rethrow the original error.
Example:
try {
await client.signInvoice(invoice);
} catch (error) {
console.error(error);
}This means applications can handle errors normally while also subscribing to the central client event stream.
Signing-Key Requirements
Signing operations require an account with the necessary signing key material.
When a selected account has no signing keys, the client reports that the account needs to be re-imported using the appropriate MajikKey backup/import workflow.
Locked accounts must also be unlocked before signing.
For example:
await client.setActiveAccount(accountId);
await client.unlockAccount(accountId);
await client.signInvoice(invoice);Account Identity Resolution
The client differentiates between:
Account ID
Fingerprint
Public-key address
Public key
MajikKey instanceHigh-level methods resolve these internally wherever possible.
For example:
await client.getContactByAddress(address);or:
await client.getInvoice(
invoiceId,
);The caller does not need to manually locate the corresponding manager.
In-Memory Development Mode
Every major domain can operate without persistent storage.
This makes the client useful for:
Unit tests
Integration tests
Prototypes
Storybook environments
Temporary sessions
CLI utilitiesFor example:
const client = new MajikBuwizClient({});or:
const client =
await MajikBuwizClient.create({});The default managers use in-memory adapters where applicable.
Custom Managers
Advanced applications can inject fully configured managers rather than relying on automatic construction.
const client =
new MajikBuwizClient({
contactManager,
invoiceManager,
expenseManager,
recurringExpenseManager,
historyManager,
activityManager,
});This allows applications to customize the lower layers while retaining the high-level orchestration API.
Custom Storage Adapters
Storage adapters can be injected independently:
const client =
new MajikBuwizClient({
adapters: {
contacts: contactAdapter,
invoices: invoiceAdapter,
expenses: expenseAdapter,
recurringExpenses: recurringExpenseAdapter,
historyLogs: historyAdapter,
userActivityLogs: activityAdapter,
},
});This architecture allows Majik Buwiz applications to choose storage based on platform requirements rather than forcing a single persistence implementation.
Typical Desktop Application Flow
A desktop application can follow this lifecycle:
import { MajikBuwizClient } from "@majikah/majik-buwiz-client";
async function bootstrap() {
const client =
await MajikBuwizClient.create({
adapters: {
// persistent adapters
},
});
const accounts =
client.listOwnAccounts();
if (accounts.length > 0) {
client.setActiveAccount(accounts[0].id);
}
return client;
}After initialization, application panels can operate entirely through the client:
const invoices =
client.listInvoices();
const expenses =
client.listExpenses();
const contacts =
client.listContacts();
const stats =
client.getInvoiceStats();Typical Invoice Workflow
A complete UI workflow can look like this:
// 1. Load or select active account
client.setActiveAccount(accountId);
// 2. Ensure account is unlocked
await client.unlockAccount(accountId);
// 3. Resolve contacts from the directory
const customer =
client.getContactByID(customerContactId);
// 4. Finalize the invoice
const invoice =
await client.finalizeInvoice(
draft,
"encrypted-and-signed",
customer ? [customer.id] : [],
);
// 5. Verify
const signatures =
await client.verifyInvoiceSignatures(invoice);
// 6. Optionally seal
const sealed =
await client.sealInvoice(invoice);The application layer therefore does not need to manually construct:
MajikRecipient[]
ExpectedSigner[]
signerKey
MajikInvoice
storage writes
invoice numberingfor the common workflow.
Typical Expense Workflow
const expense =
await client.createExpense({
...
});
const currentExpenses =
await client.listExpensesByActiveAccount();
const monthlyExpenses =
await client.listExpensesByCreatedAtRange({
from: startDate,
to: endDate,
});Recurring expenses can then be actualized:
await client.actualizeAllRecurringExpenses();Typical Backup Workflow
A backup UI can be implemented in two phases.
Export
const backup =
await client.backupAppData();Save the returned Blob using the platform's file APIs.
Restore
First inspect:
const snapshot =
await client.readAppDataBackup(
backupBlob,
);Then selectively restore:
await client.restoreAppDataSelective(
snapshot,
{
contacts: true,
groups: true,
invoices: true,
expenses: true,
invoiceDefaults: true,
preferences: true,
},
);This avoids forcing every restore operation to be all-or-nothing.
Security-Oriented Design
MajikBuwizClient is designed to preserve the security boundaries established by the underlying Majikah cryptographic packages.
Important characteristics include:
Local key ownership
Signing operations resolve MajikKey instances from the local key manager.
Explicit unlocking
Operations requiring protected key material validate account/key availability and lock state.
Cryptographic signing
Invoices are signed through the underlying MajikInvoice / MajikSignature implementations rather than relying on application-level identifiers alone.
Recipient-aware encryption
Encrypted invoices resolve recipient cryptographic material from the contact directory.
Integrity sealing
Invoice seals provide an additional integrity state for finalized records.
Scoped logging
History and activity entries can be associated with the fingerprint of the account that actually performed the operation.
Backup type validation
Backup blobs contain domain-specific magic headers so arbitrary binary input can be rejected before attempting to parse it as a supported backup type.
Important Design Principles
MajikBuwizClient follows several architectural principles.
One high-level client
Application code should not need to coordinate every Majikah manager directly for common business workflows.
Account-aware by default
Operations use the active account unless another account is explicitly supplied.
Explicit persistence boundaries
Invoices, contacts, expenses, recurring expenses, client state, and logs are independently persisted.
Read-before-write backups
Backup contents can be parsed into snapshots before anything is written to the active store.
Non-blocking logging
History and activity logging failures do not intentionally interrupt the primary business operation.
Domain orchestration
The client translates application-level concepts such as:
recipient contact IDs
signer contact IDs
account IDs
invoice draftsinto the cryptographic/domain structures required by the underlying Majikah libraries.
API Surface
The following is a condensed view of the primary public API.
Accounts
Inherited MajikKeyClient account APIs
updateOwnAccountMeta()
updateActiveAccountMeta()
hasOwnIdentity()
exportActiveAccountKey()Contacts
getContactByID()
hasContact()
hasContactByAddress()
getContactByAddress()
getContactByPublicKey()
getContactsByID()
getContactsByPublicKey()
listContacts()
addContact()
removeContact()
updateContactMeta()
exportContactAsJSON()
exportContactAsString()
importContactFromJSON()
importContactFromString()
exportContactCompressed()
importContactCompressed()
clearDirectory()Groups
createGroup()
addGroup()
removeGroup()
getContactGroup()
getGroupOrThrow()
hasGroup()
listContactGroups()
listUserGroups()
listSystemGroups()
updateGroupMeta()
addContactToGroup()
addContactsToGroup()
removeContactFromGroup()
moveContactBetweenGroups()
getContactsInGroup()
getContactsInGroupSorted()
isContactInGroup()
getGroupsForContact()
getGroupIdsForContact()Favorites / Blocked
addContactToFavorites()
removeContactFromFavorites()
isContactFavorite()
isContactBlocked()
getFavoritesGroup()
getBlockedGroup()
getFavoriteContacts()
getBlockedContacts()Invoice lifecycle
createInvoice()
storeInvoice()
getInvoice()
getInvoiceOrThrow()
removeInvoice()
clearInvoices()
signInvoice()
signExternalInvoice()
sealInvoice()
decryptInvoice()
unlockInvoice()
decryptCachedInvoices()
decryptInvoices()
duplicateInvoice()
verifyInvoiceSignatures()
verifyInvoiceSignature()
verifyInvoiceSeal()
validateInvoice()
reissueInvoice()
restartInvoice()
setInvoiceMode()
switchInvoiceMode()
reissueSignAndStore()
finalizeInvoice()Invoice queries
listInvoices()
queryInvoices()
queryInvoicesAdvanced()
listInvoicesByActiveAccount()
listInvoicesByIssuedAtRange()
listInvoicesByCreatedAtRange()
listInvoicesByMode()
listInvoicesByIssuer()
listInvoicesByRecipient()
listInvoicesByStatus()
hasInvoice()
getInvoicesNotOwnedByActiveAccount()
countInvoicesNotOwnedByActiveAccount()
getInvoiceStats()
getInvoicePublicSummary()
getInvoiceSealInfo()Invoice utilities
batchExportInvoicesToCSV()
importInvoice()
expectedSignerFromKey()
expectedSignerFromContact()Expenses
createExpense()
duplicateExpense()
getExpense()
getExpenseOrThrow()
storeExpense()
removeExpense()
clearExpenses()
listExpenses()
queryExpenses()
queryExpensesAdvanced()
listExpensesByActiveAccount()
listExpensesByCreatedAtRange()
listExpensesByCategory()
listExpensesByStatus()
getExpensesNotOwnedByActiveAccount()
countExpensesNotOwnedByActiveAccount()Recurring expenses
actualizeRecurringExpense()
actualizeAllRecurringExpenses()Application state
getInvoiceDefaults()
setInvoiceDefaults()
removeInvoiceDefaults()
getInvoiceTableColumns()
setInvoiceTableColumns()
resetInvoiceTableColumns()
getExpenseTableColumns()
setExpenseTableColumns()
resetExpenseTableColumns()
getUserAppPreferences()
setUserAppPreferences()
removeUserAppPreferences()
resetUserAppPreferences()
isAnalyticsEnabled()
isAutoDecryptInvoicesEnabled()Logging
historyManager
activityManager
listHistoryForActiveAccount()
listActivityForActiveAccount()
recordActivity()
clearHistoryLogsForActiveAccount()
clearActivityLogsForActiveAccount()
restartLogsForActiveAccount()
hydrateLogsForActiveAccount()Backups
backupInvoices()
backupExpenses()
backupContacts()
backupAppData()
readInvoicesBackup()
readContactsBackup()
readAppDataBackup()
restoreInvoices()
restoreContacts()
restoreAppData()
restoreAppDataSelective()
probeBackupType()Lifecycle
hydrate()
MajikBuwizClient.create()Events
on()
off()TypeScript
The client is written in TypeScript and exposes strongly typed domain models from the Majikah ecosystem.
Examples:
import type {
MajikInvoice,
MajikInvoiceMode,
MajikInvoiceContact,
ExpenseRecord,
} from "@majikah/majik-buwiz-client";The exact exported type surface depends on the package entry point and package build configuration.
Related Majikah Packages
Majik Buwiz Client is intentionally built as an orchestration layer around the wider Majikah ecosystem.
Relevant packages include:
@majikah/majik-key— cryptographic identity and key management@majikah/majik-key-client— account/client lifecycle foundation@majikah/majik-invoice— invoice model and cryptographic invoice operations@majikah/majik-envelope— recipient/envelope construction@majikah/majik-signature— signing and verification primitives@majikah/majik-cjson— compressed JSON serialization@majikah/majik-file— file identity support
License
Apache-2.0 — free for personal and commercial use.
Author
Developed by Josef Elijah Fabian (Zelijah) | Majikah Solutions OPC
About the Developer
Josef Elijah Fabian is the founder and lead developer of Majikah Solutions OPC, building software across cryptography, document trust, business automation, and creative technology.
- Developer: Josef Elijah Fabian
- GitHub: @thezelijah
- Organization: Majikah
Majikah Solutions
Your vision, my magic.
Majik Buwiz is part of the broader Majikah ecosystem of developer tools and applications built around cryptographic identity, trustworthy digital documents, and practical business software.
- Website: https://majikah.solutions
- GitHub: https://github.com/Majikah
Contact
- Business: [email protected]
- Website: https://majikah.solutions
- GitHub: https://github.com/Majikah
Contributing
Contributions, bug reports, security reports, and improvements are welcome.
For security-sensitive issues, please follow the repository's security policy rather than publishing potentially exploitable details in a public issue.
Summary
MajikBuwizClient is the application-facing orchestration layer for Majik Buwiz.
It brings together:
MajikKey
+
Accounts
+
Contacts
+
Invoices
+
Expenses
+
Recurring Expenses
+
Signing / Verification
+
Encryption / Decryption
+
Audit / Activity Logs
+
Backup / Restore
+
Pluggable Storageinto a single client designed for real-world business applications.
The result is an API where common operations can stay simple:
const client =
await MajikBuwizClient.create(config);
client.setActiveAccount(accountId);
const invoice =
await client.finalizeInvoice(
draft,
"encrypted-and-signed",
[customerContactId],
);
const verified =
await client.verifyInvoiceSignatures(
invoice,
);
const backup =
await client.backupAppData();Majik Buwiz Client — business data, cryptographic identity, and application state in one high-level client.
