Utilities
Helper functions for common patterns in event-sourced systems.
Prerequisites: core-concepts.md
Overview
Section titled “Overview”The z0 SDK includes utility functions for common operations in distributed, event-sourced systems:
calculateTieredCharge()- Graduated tier pricing math for usage-based billingThresholdMonitor- Threshold crossing detection with debounce for alertscascade()- Multi-DO coordination with automatic rollback
These utilities are generic and don’t depend on specific z0 primitives, making them reusable across different parts of your application.
Tiered Pricing
Section titled “Tiered Pricing”calculateTieredCharge()
Section titled “calculateTieredCharge()”Calculate charges using graduated tier pricing where different quantity ranges are charged at different rates.
Use cases:
- Usage-based billing (API calls, storage, bandwidth)
- Volume discounts (more usage = lower per-unit price)
- Freemium tiers (first N units free, then graduated pricing)
Signature
Section titled “Signature”import { calculateTieredCharge } from '@z0-app/sdk';import type { Tier, TieredChargeResult } from '@z0-app/sdk';
function calculateTieredCharge( quantity: number, tiers: Tier[]): TieredChargeResultinterface Tier { ending_quantity: number | null; // Last unit in tier (null = unbounded) unit_price_cents: number; // Price per unit in cents}
interface TieredChargeResult { total_charge_cents: number; tier_breakdown: TierBreakdownItem[];}
interface TierBreakdownItem { tier_index: number; quantity: number; unit_price_cents: number; charge_cents: number;}How Graduated Pricing Works
Section titled “How Graduated Pricing Works”Unlike flat-rate pricing where all units cost the same, graduated pricing charges different rates for different ranges:
const tiers: Tier[] = [ { ending_quantity: 100, unit_price_cents: 200 }, // 0-100 @ $2.00 { ending_quantity: 1000, unit_price_cents: 100 }, // 101-1000 @ $1.00 { ending_quantity: null, unit_price_cents: 50 }, // 1001+ @ $0.50];
// For 1500 units:// - First 100 units @ $2.00 = $200.00// - Next 900 units @ $1.00 = $900.00// - Remaining 500 units @ $0.50 = $250.00// Total: $1,350.00
const result = calculateTieredCharge(1500, tiers);// result.total_charge_cents === 135000// result.tier_breakdown shows the calculation per tierExamples
Section titled “Examples”Basic tiered pricing:
const apiTiers: Tier[] = [ { ending_quantity: 1000, unit_price_cents: 0 }, // First 1K free { ending_quantity: 10000, unit_price_cents: 5 }, // 1K-10K @ $0.05 { ending_quantity: null, unit_price_cents: 2 }, // 10K+ @ $0.02];
const result = calculateTieredCharge(15000, apiTiers);console.log(`Total: $${result.total_charge_cents / 100}`);// Total: $460.00
// Breakdown:// - Tier 0: 1,000 units @ $0.00 = $0.00// - Tier 1: 9,000 units @ $0.05 = $450.00// - Tier 2: 5,000 units @ $0.02 = $10.00Flat rate (single unbounded tier):
const flatRate: Tier[] = [ { ending_quantity: null, unit_price_cents: 10 }, // All units @ $0.10];
const result = calculateTieredCharge(1000, flatRate);// result.total_charge_cents === 10000 ($100.00)Integration with MeterEngine:
class BillingLedger extends EntityLedger { async calculateMonthlyBill(entityId: string): Promise<number> { const meter = new MeterEngine(this.db);
// Get usage for the month const usage = await meter.getUsage(entityId, 'api_calls', 'month');
// Calculate charge using tiers const result = calculateTieredCharge(usage.count, apiTiers);
return result.total_charge_cents; }}Threshold Monitoring
Section titled “Threshold Monitoring”ThresholdMonitor
Section titled “ThresholdMonitor”Detect when numeric values cross thresholds (fall below) or recover (rise back above) with built-in debounce and fact emission.
Use cases:
- Low balance alerts
- Budget warnings
- Capacity alerts (CPU, memory, storage)
- SLA threshold violations
v0.10.0: Added fact emission for threshold events via THRESHOLD_MONITOR_FACT_TYPES.
Constructor
Section titled “Constructor”import { ThresholdMonitor, THRESHOLD_MONITOR_FACT_TYPES } from '@z0-app/sdk';
// Basic monitor (no fact emission)const monitor = new ThresholdMonitor();
// With fact emissionconst monitor = new ThresholdMonitor({ factManager, monitorId: 'balance_monitor', configVersion: 1, tenantId: 'tnt_acme',});check() Method
Section titled “check() Method”check( oldValue: number, newValue: number, threshold: number | null | undefined): ThresholdCheckResult
type ThresholdCheckResult = 'crossed' | 'recovered' | 'none';Returns:
'crossed'- Value dropped below threshold (alert trigger)'recovered'- Value rose back to or above threshold (recovery notification)'none'- No state change (staying above, staying below, or debounced)
Key Behaviors
Section titled “Key Behaviors”- Crossing detection:
oldValue >= threshold && newValue < threshold→'crossed' - Recovery detection:
oldValue < threshold && newValue >= threshold→'recovered' - Exact threshold: Values exactly at threshold are treated as “at or above”
- Debounce: Once triggered, subsequent crossings return
'none'until recovery - Null threshold: Returns
'none'(no monitoring without threshold)
Debounce Logic
Section titled “Debounce Logic”The monitor prevents alert spam by tracking triggered state:
const monitor = new ThresholdMonitor();
// First drop below thresholdmonitor.check(1000, 500, 750); // 'crossed' - alert fired
// Still below threshold - debounced (no repeated alerts)monitor.check(500, 400, 750); // 'none' - debouncedmonitor.check(400, 300, 750); // 'none' - debounced
// Recover back above thresholdmonitor.check(300, 800, 750); // 'recovered' - reset state
// Can alert again after recoverymonitor.check(800, 600, 750); // 'crossed' - alert fired againExamples
Section titled “Examples”Low balance alert:
import { ThresholdMonitor, THRESHOLD_MONITOR_FACT_TYPES } from '@z0-app/sdk';
class AccountLedger extends EntityLedger { private balanceMonitor = new ThresholdMonitor({ factManager: this.factManager, monitorId: 'balance_monitor', tenantId: this.tenantId, });
async handleDeposit(fact: Fact): Promise<void> { const oldBalance = this.getBalance(); const newBalance = oldBalance + fact.data.amount;
// Check for low balance recovery const result = this.balanceMonitor.check( oldBalance, newBalance, 100_00 // $100 threshold in cents );
if (result === 'recovered') { // Fact automatically emitted: // { // type: 'threshold', // subtype: 'recovered', // data: { // monitor_id: 'balance_monitor', // threshold_value: 10000, // old_value: oldBalance, // new_value: newBalance // } // }
// Send notification: "Your balance is back above $100" await this.sendNotification('balance_recovered', { balance: newBalance, threshold: 100_00, }); }
this.setBalance(newBalance); }
async handleWithdrawal(fact: Fact): Promise<void> { const oldBalance = this.getBalance(); const newBalance = oldBalance - fact.data.amount;
// Check for low balance alert const result = this.balanceMonitor.check( oldBalance, newBalance, 100_00 // $100 threshold in cents );
if (result === 'crossed') { // Fact automatically emitted: // { // type: 'threshold', // subtype: 'crossed', // data: { // monitor_id: 'balance_monitor', // threshold_value: 10000, // old_value: oldBalance, // new_value: newBalance // } // }
// Send alert: "Warning: Your balance is below $100" await this.sendNotification('low_balance_alert', { balance: newBalance, threshold: 100_00, }); }
this.setBalance(newBalance); }}Budget enforcement with warnings:
class MeterLedger extends EntityLedger { private budgetMonitor = new ThresholdMonitor();
async trackUsage(entityId: string, amount: number): Promise<void> { const budget = await this.getBudget(entityId); const oldUsage = await this.getUsage(entityId); const newUsage = oldUsage + amount;
// Check against warning threshold (80% of budget) const warningThreshold = budget * 0.8; const result = this.budgetMonitor.check( budget - oldUsage, budget - newUsage, warningThreshold );
if (result === 'crossed') { // Alert: "You've used 80% of your budget" await this.sendWarning(entityId, { usage: newUsage, budget, percent: (newUsage / budget) * 100, }); }
await this.setUsage(entityId, newUsage); }}Multiple thresholds:
class StorageLedger extends EntityLedger { // Separate monitors for different alert levels private warningMonitor = new ThresholdMonitor(); private criticalMonitor = new ThresholdMonitor();
async updateStorageUsage(used: number, total: number): Promise<void> { const oldRemaining = this.getState('remaining') ?? total; const newRemaining = total - used;
// Warning at 20% remaining const warning = this.warningMonitor.check( oldRemaining, newRemaining, total * 0.2 );
// Critical at 5% remaining const critical = this.criticalMonitor.check( oldRemaining, newRemaining, total * 0.05 );
if (critical === 'crossed') { await this.sendAlert('critical', { remaining: newRemaining, total }); } else if (warning === 'crossed') { await this.sendAlert('warning', { remaining: newRemaining, total }); }
this.setState('remaining', newRemaining); }}Reset state:
const monitor = new ThresholdMonitor();
// After some alerts...monitor.check(100, 50, 75); // 'crossed'
// Reset all tracked state (useful for testing)monitor.reset();
// Check if specific threshold is triggeredif (monitor.isTriggered(75)) { console.log('Alert is active');}Multi-DO Coordination
Section titled “Multi-DO Coordination”cascade()
Section titled “cascade()”Execute a sequence of operations across multiple Durable Objects with automatic rollback if any operation fails.
Use cases:
- Atomic multi-entity creation (parent + children)
- Cross-DO state changes that need to be all-or-nothing
- Distributed transactions with compensation logic
Signature
Section titled “Signature”import { cascade } from '@z0-app/sdk';import type { CascadeOperation, CascadeResult } from '@z0-app/sdk';
async function cascade<T = unknown>( operations: CascadeOperation<T>[]): Promise<CascadeResult<T>>interface CascadeOperation<T = unknown> { execute: () => Promise<T>; // Execute the operation rollback: () => Promise<void>; // Undo the operation}
interface CascadeResult<T = unknown> { results: T[]; // Results from each execute() in order}
interface PartialRollback { succeeded: number[]; // Indices of successful rollbacks failed: number[]; // Indices of failed rollbacks}
class CascadeError extends Error { originalError: Error; partialRollback?: PartialRollback;}How It Works
Section titled “How It Works”- Sequential execution: Operations run one at a time, in order
- Rollback on failure: If operation N fails, operations N-1, N-2, …, 0 are rolled back in reverse order
- Failed operations don’t rollback: Only successfully executed operations are rolled back
- Rollback errors captured: If rollback fails, error includes which rollbacks succeeded/failed
Examples
Section titled “Examples”Atomic parent + child creation:
async function createAccountWithProfile( accountId: string, profileId: string): Promise<void> { const accountDO = env.ACCOUNT_DO.get(env.ACCOUNT_DO.idFromName(accountId)); const profileDO = env.PROFILE_DO.get(env.PROFILE_DO.idFromName(profileId));
const operations: CascadeOperation[] = [ { execute: async () => { await accountDO.createAccount({ id: accountId, email: 'user@example.com' }); return 'account_created'; }, rollback: async () => { await accountDO.deleteAccount(); }, }, { execute: async () => { await profileDO.createProfile({ id: profileId, accountId }); return 'profile_created'; }, rollback: async () => { await profileDO.deleteProfile(); }, }, ];
try { const result = await cascade(operations); console.log('Success:', result.results); // ['account_created', 'profile_created'] } catch (error) { if (error instanceof CascadeError) { // Rollback was triggered - both account and profile were cleaned up console.error('Failed to create account:', error.message);
if (error.partialRollback) { // Some rollbacks failed - manual intervention may be needed console.error('Partial rollback:', error.partialRollback); } } }}Multi-step workflow with state changes:
async function processOrder(orderId: string): Promise<void> { const orderDO = env.ORDER_DO.get(env.ORDER_DO.idFromName(orderId)); const inventoryDO = env.INVENTORY_DO.get(env.INVENTORY_DO.idFromName('global')); const paymentDO = env.PAYMENT_DO.get(env.PAYMENT_DO.idFromName(orderId));
const operations: CascadeOperation[] = [ { execute: async () => { const reserved = await inventoryDO.reserveItems(orderId, ['item_1', 'item_2']); return reserved; }, rollback: async () => { await inventoryDO.releaseReservation(orderId); }, }, { execute: async () => { const charge = await paymentDO.chargeCard(orderId, 5000); return charge; }, rollback: async () => { await paymentDO.refund(orderId); }, }, { execute: async () => { await orderDO.markFulfilled(orderId); return 'fulfilled'; }, rollback: async () => { await orderDO.markCancelled(orderId); }, }, ];
const result = await cascade(operations); // If any step fails, all previous steps are automatically rolled back: // - Payment refunded // - Inventory released // - Order cancelled
console.log('Order processed:', result.results);}Handling partial rollback failures:
async function complexOperation(): Promise<void> { const operations: CascadeOperation[] = [ { execute: async () => createResource1(), rollback: async () => deleteResource1(), // This might fail }, { execute: async () => createResource2(), rollback: async () => deleteResource2(), }, ];
try { await cascade(operations); } catch (error) { if (error instanceof CascadeError) { console.error('Operation failed:', error.originalError);
if (error.partialRollback) { // Some rollbacks succeeded, some failed console.log('Successfully rolled back:', error.partialRollback.succeeded); console.log('Failed to roll back:', error.partialRollback.failed);
// Manual cleanup may be needed for failed rollbacks for (const index of error.partialRollback.failed) { await manualCleanup(index); } } } }}Integration with hierarchy patterns:
async function createEntityHierarchy( parentId: string, childIds: string[]): Promise<void> { const parentDO = env.ENTITY_DO.get(env.ENTITY_DO.idFromName(parentId));
const operations: CascadeOperation[] = [ { execute: async () => { await parentDO.createEntity({ id: parentId, type: 'parent' }); return parentId; }, rollback: async () => { await parentDO.deleteEntity(); }, }, // Add child operations dynamically ...childIds.map((childId): CascadeOperation => { const childDO = env.ENTITY_DO.get(env.ENTITY_DO.idFromName(childId)); return { execute: async () => { await childDO.createEntity({ id: childId, parentId, type: 'child' }); return childId; }, rollback: async () => { await childDO.deleteEntity(); }, }; }), ];
const result = await cascade(operations); console.log('Created hierarchy:', result.results);}Summary
Section titled “Summary”| Utility | Purpose | Key Feature |
|---|---|---|
calculateTieredCharge() | Graduated tier pricing | Multi-tier quantity ranges with per-tier breakdown |
ThresholdMonitor | Threshold crossing detection | Debounce prevents alert spam |
cascade() | Multi-DO coordination | Automatic rollback in reverse order on failure |
These utilities provide building blocks for common patterns in distributed, event-sourced systems without coupling to specific z0 primitives.