Skip to content

Utilities

Helper functions for common patterns in event-sourced systems.

Prerequisites: core-concepts.md


The z0 SDK includes utility functions for common operations in distributed, event-sourced systems:

  • calculateTieredCharge() - Graduated tier pricing math for usage-based billing
  • ThresholdMonitor - Threshold crossing detection with debounce for alerts
  • cascade() - 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.


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)
import { calculateTieredCharge } from '@z0-app/sdk';
import type { Tier, TieredChargeResult } from '@z0-app/sdk';
function calculateTieredCharge(
quantity: number,
tiers: Tier[]
): TieredChargeResult
interface 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;
}

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 tier

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.00

Flat 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;
}
}

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.

import { ThresholdMonitor, THRESHOLD_MONITOR_FACT_TYPES } from '@z0-app/sdk';
// Basic monitor (no fact emission)
const monitor = new ThresholdMonitor();
// With fact emission
const monitor = new ThresholdMonitor({
factManager,
monitorId: 'balance_monitor',
configVersion: 1,
tenantId: 'tnt_acme',
});
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)
  1. Crossing detection: oldValue >= threshold && newValue < threshold'crossed'
  2. Recovery detection: oldValue < threshold && newValue >= threshold'recovered'
  3. Exact threshold: Values exactly at threshold are treated as “at or above”
  4. Debounce: Once triggered, subsequent crossings return 'none' until recovery
  5. Null threshold: Returns 'none' (no monitoring without threshold)

The monitor prevents alert spam by tracking triggered state:

const monitor = new ThresholdMonitor();
// First drop below threshold
monitor.check(1000, 500, 750); // 'crossed' - alert fired
// Still below threshold - debounced (no repeated alerts)
monitor.check(500, 400, 750); // 'none' - debounced
monitor.check(400, 300, 750); // 'none' - debounced
// Recover back above threshold
monitor.check(300, 800, 750); // 'recovered' - reset state
// Can alert again after recovery
monitor.check(800, 600, 750); // 'crossed' - alert fired again

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 triggered
if (monitor.isTriggered(75)) {
console.log('Alert is active');
}

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
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;
}
  1. Sequential execution: Operations run one at a time, in order
  2. Rollback on failure: If operation N fails, operations N-1, N-2, …, 0 are rolled back in reverse order
  3. Failed operations don’t rollback: Only successfully executed operations are rolled back
  4. Rollback errors captured: If rollback fails, error includes which rollbacks succeeded/failed

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);
}

UtilityPurposeKey Feature
calculateTieredCharge()Graduated tier pricingMulti-tier quantity ranges with per-tier breakdown
ThresholdMonitorThreshold crossing detectionDebounce prevents alert spam
cascade()Multi-DO coordinationAutomatic 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.