Resilience
- @ductape/client
- React
- Vue 3
Build fault-tolerant applications with Ductape's resilience features including quotas for load distribution, fallbacks for automatic failover, and health monitoring.
When using a publishable key (frontend), include session (token from your backend) in every request.
Quotas
Quotas distribute load across multiple providers based on weighted distribution, with health-aware provider selection.
Running Quota Operations
import { Ductape } from '@ductape/client';
const ductape = new Ductape({
accessKey: 'your-access-key',
product: 'your-product',
env: 'prd'
});
// Execute operation with weighted provider distribution
const sessionToken = getSessionFromYourBackend();
const result = await ductape.resilience.quotas.run({
tag: 'payment-quota',
input: {
amount: 1000,
currency: 'usd',
customer: 'cus_123'
},
session: sessionToken,
});
console.log(`Processed by ${result.provider}`);
console.log(`Latency: ${result.latency}ms`);
console.log(`Healthy provider: ${result.wasHealthy}`);
console.log(`Retries used: ${result.retriesUsed}`);
Checking Quota Status
// Check and consume quota
const quotaResult = await ductape.resilience.quotas.check({
tag: 'api-calls',
identifier: 'user-123',
amount: 1
});
if (!quotaResult.allowed) {
console.log('Rate limited!');
console.log(`Remaining: ${quotaResult.remaining}`);
console.log(`Resets at: ${new Date(quotaResult.resetsAt)}`);
}
Getting Quota Status
// Get quota status without consuming
const status = await ductape.resilience.quotas.status({
tag: 'api-calls',
identifier: 'user-123'
});
console.log(`Used: ${status.used} / ${status.limit}`);
console.log(`Remaining: ${status.remaining}`);
Subscribing to Quota Changes
// Connect first for real-time features
await ductape.connect();
// Subscribe to quota changes
const subscription = ductape.resilience.quotas.subscribe(
{
tag: 'api-calls',
identifier: 'user-123'
},
(events) => {
events.forEach(event => {
console.log(`Quota changed: ${event.remaining} remaining`);
});
}
);
// Unsubscribe when done
subscription.unsubscribe();
Fallbacks
Fallbacks provide automatic failover across multiple providers, trying them sequentially until one succeeds.
Running Fallback Operations
// Execute with automatic failover
const result = await ductape.resilience.fallbacks.run({
tag: 'email-fallback',
input: {
to: 'user@example.com',
subject: 'Welcome!',
body: 'Thank you for signing up'
}
});
console.log(`Email sent via ${result.provider}`);
console.log(`Latency: ${result.latency}ms`);
console.log(`Providers tried: ${result.providersTried}`);
console.log(`Used healthy provider: ${result.wasHealthy}`);
Fallback with Complex Input
// Send payment with fallback providers
const paymentResult = await ductape.resilience.fallbacks.run({
tag: 'payment-fallback',
input: {
amount: 5000,
currency: 'usd',
customer: {
id: 'cus_123',
email: 'customer@example.com'
},
metadata: {
order_id: 'ord_456',
source: 'web'
}
}
});
if (paymentResult.data.success) {
console.log('Payment processed successfully');
console.log(`Transaction ID: ${paymentResult.data.transactionId}`);
}
Health Monitoring
Monitor the health status of your services and providers.
Checking Health Status
// Get health status
const health = await ductape.resilience.health.status({
tag: 'payment-service'
});
console.log(`Overall status: ${health.status}`); // 'healthy' | 'degraded' | 'unhealthy'
console.log(`Last check: ${health.lastCheck}`);
// Check individual probes
health.probes.forEach(probe => {
console.log(`${probe.name}: ${probe.status}`);
if (probe.latency) {
console.log(` Latency: ${probe.latency}ms`);
}
if (probe.error) {
console.log(` Error: ${probe.error}`);
}
});
Subscribing to Health Changes
// Connect first for real-time features
await ductape.connect();
// Subscribe to health status changes
const healthSubscription = ductape.resilience.health.subscribe(
{
tag: 'payment-service'
},
(events) => {
events.forEach(event => {
console.log(`Health changed to ${event.status}`);
if (event.status === 'unhealthy') {
// Alert your team or trigger automated recovery
console.warn('Service is unhealthy!');
}
});
}
);
// Unsubscribe when done
healthSubscription.unsubscribe();
Complete Example: Payment Processing
import { Ductape } from '@ductape/client';
const ductape = new Ductape({
accessKey: 'your-access-key',
product: 'payment-app',
env: 'prd'
});
async function processPayment(userId: string, amount: number) {
try {
// First, check if user has quota available
const quota = await ductape.resilience.quotas.check({
tag: 'payment-quota',
identifier: userId,
amount: 1
});
if (!quota.allowed) {
throw new Error(`Rate limit exceeded. Resets at ${new Date(quota.resetsAt)}`);
}
// Check health of payment service before proceeding
const health = await ductape.resilience.health.status({
tag: 'payment-providers'
});
if (health.status === 'unhealthy') {
throw new Error('Payment service is currently unavailable');
}
// Process payment with automatic failover
const result = await ductape.resilience.fallbacks.run({
tag: 'payment-fallback',
input: {
userId,
amount,
currency: 'usd'
}
});
console.log(`Payment processed by ${result.provider} in ${result.latency}ms`);
return result.data;
} catch (error) {
console.error('Payment failed:', error);
throw error;
}
}
// Process a payment
processPayment('user-123', 1000);
Type Safety
All resilience methods are fully typed:
interface PaymentResult {
transactionId: string;
status: 'success' | 'failed';
processorFee: number;
}
// Type-safe quota run
const result = await ductape.resilience.quotas.run<PaymentResult>({
tag: 'payment-quota',
input: { amount: 1000 }
});
// TypeScript knows result.data is PaymentResult
console.log(result.data.transactionId);
// Type-safe fallback run
const fallbackResult = await ductape.resilience.fallbacks.run<PaymentResult>({
tag: 'payment-fallback',
input: { amount: 1000 }
});
console.log(fallbackResult.data.status); // TypeScript knows this is 'success' | 'failed'
Error Handling
try {
const result = await ductape.resilience.quotas.run({
tag: 'email-quota',
input: { to: 'user@example.com', subject: 'Hello' }
});
console.log('Email sent successfully');
} catch (error) {
if (error.message.includes('No providers available')) {
console.error('All email providers are down');
} else if (error.message.includes('All retries failed')) {
console.error('Maximum retries exceeded');
} else {
console.error('Unexpected error:', error);
}
}
Best Practices
- Check Quotas First: Always check quota availability before expensive operations
- Monitor Health: Subscribe to health changes to proactively handle degraded services
- Use Fallbacks for Critical Operations: Implement fallbacks for operations that must succeed
- Handle Errors Gracefully: Always handle quota exceeded and service unavailable scenarios
- Log Provider Information: Track which providers are being used for debugging and optimization
- Set Appropriate Timeouts: Configure reasonable timeouts for your use case
- Test Failover Scenarios: Regularly test that your fallback chains work as expected
React hooks for building fault-tolerant applications with quotas, fallbacks, and health monitoring.
useQuotaRun
Execute operations with weighted load distribution across multiple providers.
Basic Usage
import { useQuotaRun } from '@ductape/react';
function PaymentProcessor() {
const { mutate: processPayment, isLoading, data, error } = useQuotaRun({
onSuccess: (result) => {
console.log(`Payment processed by ${result.provider}`);
console.log(`Latency: ${result.latency}ms`);
alert('Payment successful!');
},
onError: (error) => {
alert(`Payment failed: ${error.message}`);
}
});
const handlePayment = (amount: number, currency: string) => {
processPayment({
tag: 'payment-quota',
input: {
amount,
currency,
customer: 'cus_123'
}
});
};
return (
<div>
<button
onClick={() => handlePayment(1000, 'usd')}
disabled={isLoading}
>
{isLoading ? 'Processing...' : 'Pay $10.00'}
</button>
{data && (
<div>
<p>Provider: {data.provider}</p>
<p>Latency: {data.latency}ms</p>
<p>Retries: {data.retriesUsed}</p>
</div>
)}
{error && <p style={{ color: 'red' }}>{error.message}</p>}
</div>
);
}
With Type Safety
interface PaymentResult {
transactionId: string;
status: 'success' | 'failed';
processorFee: number;
}
function TypeSafePayment() {
const { mutate, data } = useQuotaRun<PaymentResult>({
onSuccess: (result) => {
// TypeScript knows result.data is PaymentResult
console.log(`Transaction ID: ${result.data.transactionId}`);
console.log(`Fee: $${result.data.processorFee}`);
}
});
const handlePayment = () => {
mutate({
tag: 'payment-quota',
input: { amount: 5000, currency: 'usd' }
});
};
return (
<div>
<button onClick={handlePayment}>Process Payment</button>
{data && (
<div>
<p>Transaction: {data.data.transactionId}</p>
<p>Status: {data.data.status}</p>
</div>
)}
</div>
);
}
Email Service with Load Distribution
function EmailSender() {
const { mutate: sendEmail, isLoading } = useQuotaRun({
onSuccess: (result) => {
console.log(`Email sent via ${result.provider}`);
}
});
const handleSend = (to: string, subject: string, body: string) => {
sendEmail({
tag: 'email-quota',
input: { to, subject, body }
});
};
return (
<button
onClick={() => handleSend('user@example.com', 'Hello', 'Welcome!')}
disabled={isLoading}
>
Send Email
</button>
);
}
useFallbackRun
Execute operations with automatic failover across providers.
Basic Usage
import { useFallbackRun } from '@ductape/react';
function EmailWithFallback() {
const { mutate: sendEmail, isLoading, data, error } = useFallbackRun({
onSuccess: (result) => {
console.log(`Email sent via ${result.provider}`);
console.log(`Tried ${result.providersTried} providers`);
alert('Email sent successfully!');
},
onError: (error) => {
alert('All email providers failed');
}
});
const handleSend = () => {
sendEmail({
tag: 'email-fallback',
input: {
to: 'user@example.com',
subject: 'Important Message',
body: 'This is a critical email'
}
});
};
return (
<div>
<button onClick={handleSend} disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send Email'}
</button>
{data && (
<div>
<p>✓ Sent via {data.provider}</p>
<p>Providers tried: {data.providersTried}</p>
<p>Latency: {data.latency}ms</p>
</div>
)}
{error && <p style={{ color: 'red' }}>❌ {error.message}</p>}
</div>
);
}
Payment Processing with Failover
interface PaymentResponse {
success: boolean;
transactionId: string;
gateway: string;
}
function PaymentWithFallback() {
const { mutate: processPayment, isLoading, data } = useFallbackRun<PaymentResponse>({
onSuccess: (result) => {
if (result.data.success) {
console.log(`Paid via ${result.data.gateway}`);
}
}
});
const handlePayment = (amount: number) => {
processPayment({
tag: 'payment-fallback',
input: {
amount,
currency: 'usd',
customer: 'cus_123'
}
});
};
return (
<div>
<button onClick={() => handlePayment(2500)} disabled={isLoading}>
{isLoading ? 'Processing...' : 'Pay $25.00'}
</button>
{data?.data && (
<div>
<p>Gateway: {data.data.gateway}</p>
<p>Transaction: {data.data.transactionId}</p>
<p>Providers tried: {data.providersTried}</p>
</div>
)}
</div>
);
}
useQuotaCheck
Check and consume quota with rate limiting.
Basic Quota Check
import { useQuotaCheck } from '@ductape/react';
function ApiRateLimiter() {
const { mutate: checkQuota, data, isLoading } = useQuotaCheck({
onSuccess: (result) => {
if (!result.allowed) {
alert(`Rate limited! Resets at ${new Date(result.resetsAt)}`);
}
}
});
const handleApiCall = (userId: string) => {
checkQuota({
tag: 'api-calls',
identifier: userId,
amount: 1
});
};
return (
<div>
<button
onClick={() => handleApiCall('user-123')}
disabled={isLoading}
>
Make API Call
</button>
{data && (
<div>
<p>Allowed: {data.allowed ? '✓' : '❌'}</p>
<p>Used: {data.used} / {data.limit}</p>
<p>Remaining: {data.remaining}</p>
<p>Resets: {new Date(data.resetsAt).toLocaleString()}</p>
</div>
)}
</div>
);
}
Pre-flight Quota Check
function ExpensiveOperation() {
const { mutate: checkQuota, data } = useQuotaCheck();
const { mutate: processPayment, isLoading } = useQuotaRun();
const handleSubmit = async (userId: string, amount: number) => {
// Check quota first
checkQuota(
{
tag: 'payment-quota',
identifier: userId,
amount: 1
},
{
onSuccess: (quota) => {
if (quota.allowed) {
// Proceed with payment
processPayment({
tag: 'payment-quota',
input: { amount, userId }
});
} else {
alert(`Rate limit exceeded. Try again at ${new Date(quota.resetsAt)}`);
}
}
}
);
};
return (
<button onClick={() => handleSubmit('user-123', 1000)} disabled={isLoading}>
Process Payment
</button>
);
}
useQuotaStatus
Query quota status without consuming.
Display Quota Usage
import { useQuotaStatus } from '@ductape/react';
function QuotaDisplay({ userId }: { userId: string }) {
const { mutate: getStatus, data, isLoading } = useQuotaStatus({
onSuccess: (status) => {
console.log(`User has ${status.remaining} API calls remaining`);
}
});
useEffect(() => {
getStatus({
tag: 'api-calls',
identifier: userId
});
}, [userId]);
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h3>API Quota Status</h3>
{data && (
<>
<div>Used: {data.used} / {data.limit}</div>
<div>Remaining: {data.remaining}</div>
<div>Resets: {new Date(data.resetsAt).toLocaleString()}</div>
<div style={{
width: '100%',
height: '20px',
backgroundColor: '#eee'
}}>
<div style={{
width: `${(data.used / data.limit) * 100}%`,
height: '100%',
backgroundColor: data.remaining > 10 ? 'green' : 'red'
}} />
</div>
</>
)}
</div>
);
}
useQuotaSubscription
Subscribe to real-time quota changes.
Real-time Quota Monitor
import { useQuotaSubscription } from '@ductape/react';
function QuotaMonitor({ userId }: { userId: string }) {
const { data: quotaChanges, isSubscribed } = useQuotaSubscription(
{
tag: 'api-calls',
identifier: userId
},
{
onData: (events) => {
events.forEach(event => {
if (event.remaining < 10) {
alert('Warning: Low quota remaining!');
}
});
}
}
);
return (
<div>
<h3>Live Quota Monitor</h3>
<p>Status: {isSubscribed ? '🟢 Connected' : '🔴 Disconnected'}</p>
{quotaChanges && quotaChanges.length > 0 && (
<div>
<p>Last Update:</p>
<p>Remaining: {quotaChanges[0].remaining}</p>
<p>Limit: {quotaChanges[0].limit}</p>
</div>
)}
</div>
);
}
useHealthStatus
Query health status of services.
Service Health Dashboard
import { useHealthStatus } from '@ductape/react';
function HealthDashboard() {
const { mutate: checkHealth, data, isLoading } = useHealthStatus({
onSuccess: (health) => {
if (health.status === 'unhealthy') {
console.warn('Service is unhealthy!');
}
}
});
useEffect(() => {
// Check health on mount
checkHealth({ tag: 'payment-service' });
// Refresh every 30 seconds
const interval = setInterval(() => {
checkHealth({ tag: 'payment-service' });
}, 30000);
return () => clearInterval(interval);
}, []);
if (isLoading) return <div>Checking health...</div>;
return (
<div>
<h2>Service Health</h2>
{data && (
<>
<div>
Status: {' '}
<span style={{
color: data.status === 'healthy' ? 'green' :
data.status === 'degraded' ? 'orange' : 'red'
}}>
{data.status.toUpperCase()}
</span>
</div>
<div>Last Check: {data.lastCheck}</div>
<h3>Probes</h3>
<ul>
{data.probes.map(probe => (
<li key={probe.name}>
<strong>{probe.name}</strong>: {probe.status}
{probe.latency && ` (${probe.latency}ms)`}
{probe.error && ` - ${probe.error}`}
</li>
))}
</ul>
</>
)}
</div>
);
}
useHealthSubscription
Subscribe to real-time health changes.
Real-time Health Alerts
import { useHealthSubscription } from '@ductape/react';
import { useState } from 'react';
function HealthAlerts() {
const [alerts, setAlerts] = useState<string[]>([]);
const { data, isSubscribed } = useHealthSubscription(
{ tag: 'payment-service' },
{
onData: (events) => {
events.forEach(event => {
if (event.status === 'unhealthy') {
setAlerts(prev => [
...prev,
`⚠️ Service unhealthy at ${event.lastCheck}`
]);
} else if (event.status === 'healthy') {
setAlerts(prev => [
...prev,
`✓ Service recovered at ${event.lastCheck}`
]);
}
});
}
}
);
return (
<div>
<h3>Health Alerts</h3>
<p>Monitoring: {isSubscribed ? '🟢 Active' : '🔴 Inactive'}</p>
{data && data.length > 0 && (
<div>
Current Status: {data[0].status}
</div>
)}
<div>
<h4>Alert History</h4>
<ul>
{alerts.map((alert, i) => (
<li key={i}>{alert}</li>
))}
</ul>
</div>
</div>
);
}
Complete Example: Resilient Payment Flow
import { useQuotaCheck, useFallbackRun, useHealthStatus } from '@ductape/react';
import { useState } from 'react';
interface PaymentData {
amount: number;
currency: string;
userId: string;
}
function ResilientPaymentFlow() {
const [paymentData, setPaymentData] = useState<PaymentData>({
amount: 1000,
currency: 'usd',
userId: 'user-123'
});
const { mutate: checkQuota, data: quotaData } = useQuotaCheck();
const { mutate: checkHealth, data: healthData } = useHealthStatus();
const {
mutate: processPayment,
isLoading: isProcessing,
data: paymentResult
} = useFallbackRun();
const handlePayment = async () => {
// Step 1: Check quota
checkQuota(
{
tag: 'payment-quota',
identifier: paymentData.userId,
amount: 1
},
{
onSuccess: (quota) => {
if (!quota.allowed) {
alert(`Rate limit exceeded. Resets at ${new Date(quota.resetsAt)}`);
return;
}
// Step 2: Check health
checkHealth(
{ tag: 'payment-service' },
{
onSuccess: (health) => {
if (health.status === 'unhealthy') {
alert('Payment service is currently unavailable');
return;
}
// Step 3: Process payment with fallback
processPayment({
tag: 'payment-fallback',
input: paymentData
});
}
}
);
}
}
);
};
return (
<div>
<h2>Payment Processor</h2>
<div>
<label>
Amount: $
<input
type="number"
value={paymentData.amount / 100}
onChange={(e) => setPaymentData({
...paymentData,
amount: parseFloat(e.target.value) * 100
})}
/>
</label>
</div>
<button
onClick={handlePayment}
disabled={isProcessing}
>
{isProcessing ? 'Processing...' : 'Pay Now'}
</button>
{quotaData && (
<div>
<p>Quota: {quotaData.remaining} / {quotaData.limit}</p>
</div>
)}
{healthData && (
<div>
<p>Service Health: {healthData.status}</p>
</div>
)}
{paymentResult && (
<div style={{ color: 'green' }}>
✓ Payment processed via {paymentResult.provider}
<br />
Latency: {paymentResult.latency}ms
<br />
Providers tried: {paymentResult.providersTried}
</div>
)}
</div>
);
}
Best Practices
- Check Quotas First: Always check quota before expensive operations
- Monitor Health: Use health checks before critical operations
- Use Fallbacks for Critical Paths: Implement fallbacks for must-succeed operations
- Handle Loading States: Show appropriate UI during operations
- Display Error Messages: Provide clear feedback when operations fail
- Show Quota Status: Display remaining quota to users proactively
- Subscribe to Changes: Use subscriptions for real-time monitoring
- Type Your Responses: Use TypeScript generics for type-safe results
Vue 3 composables for building fault-tolerant applications with quotas, fallbacks, and health monitoring.
useQuotaRun
Execute operations with weighted load distribution across multiple providers.
Basic Usage
<script setup>
import { useQuotaRun } from '@ductape/vue';
const { mutate: processPayment, isLoading, data, error } = useQuotaRun({
onSuccess: (result) => {
console.log(`Payment processed by ${result.provider}`);
console.log(`Latency: ${result.latency}ms`);
alert('Payment successful!');
},
onError: (error) => {
alert(`Payment failed: ${error.message}`);
}
});
const handlePayment = (amount, currency) => {
processPayment({
tag: 'payment-quota',
input: {
amount,
currency,
customer: 'cus_123'
}
});
};
</script>
<template>
<div>
<button
@click="handlePayment(1000, 'usd')"
:disabled="isLoading"
>
{{ isLoading ? 'Processing...' : 'Pay $10.00' }}
</button>
<div v-if="data">
<p>Provider: {{ data.provider }}</p>
<p>Latency: {{ data.latency }}ms</p>
<p>Retries: {{ data.retriesUsed }}</p>
</div>
<p v-if="error" style="color: red">{{ error.message }}</p>
</div>
</template>
With Type Safety
<script setup lang="ts">
import { useQuotaRun } from '@ductape/vue';
interface PaymentResult {
transactionId: string;
status: 'success' | 'failed';
processorFee: number;
}
const { mutate, data } = useQuotaRun<PaymentResult>({
onSuccess: (result) => {
// TypeScript knows result.data is PaymentResult
console.log(`Transaction ID: ${result.data.transactionId}`);
console.log(`Fee: $${result.data.processorFee}`);
}
});
const handlePayment = () => {
mutate({
tag: 'payment-quota',
input: { amount: 5000, currency: 'usd' }
});
};
</script>
<template>
<div>
<button @click="handlePayment">Process Payment</button>
<div v-if="data">
<p>Transaction: {{ data.data.transactionId }}</p>
<p>Status: {{ data.data.status }}</p>
</div>
</div>
</template>
Email Service with Load Distribution
<script setup>
import { useQuotaRun } from '@ductape/vue';
const { mutate: sendEmail, isLoading } = useQuotaRun({
onSuccess: (result) => {
console.log(`Email sent via ${result.provider}`);
}
});
const handleSend = (to, subject, body) => {
sendEmail({
tag: 'email-quota',
input: { to, subject, body }
});
};
</script>
<template>
<button
@click="handleSend('user@example.com', 'Hello', 'Welcome!')"
:disabled="isLoading"
>
Send Email
</button>
</template>
useFallbackRun
Execute operations with automatic failover across providers.
Basic Usage
<script setup>
import { useFallbackRun } from '@ductape/vue';
const { mutate: sendEmail, isLoading, data, error } = useFallbackRun({
onSuccess: (result) => {
console.log(`Email sent via ${result.provider}`);
console.log(`Tried ${result.providersTried} providers`);
alert('Email sent successfully!');
},
onError: (error) => {
alert('All email providers failed');
}
});
const handleSend = () => {
sendEmail({
tag: 'email-fallback',
input: {
to: 'user@example.com',
subject: 'Important Message',
body: 'This is a critical email'
}
});
};
</script>
<template>
<div>
<button @click="handleSend" :disabled="isLoading">
{{ isLoading ? 'Sending...' : 'Send Email' }}
</button>
<div v-if="data">
<p>✓ Sent via {{ data.provider }}</p>
<p>Providers tried: {{ data.providersTried }}</p>
<p>Latency: {{ data.latency }}ms</p>
</div>
<p v-if="error" style="color: red">❌ {{ error.message }}</p>
</div>
</template>
Payment Processing with Failover
<script setup lang="ts">
import { useFallbackRun } from '@ductape/vue';
interface PaymentResponse {
success: boolean;
transactionId: string;
gateway: string;
}
const { mutate: processPayment, isLoading, data } = useFallbackRun<PaymentResponse>({
onSuccess: (result) => {
if (result.data.success) {
console.log(`Paid via ${result.data.gateway}`);
}
}
});
const handlePayment = (amount) => {
processPayment({
tag: 'payment-fallback',
input: {
amount,
currency: 'usd',
customer: 'cus_123'
}
});
};
</script>
<template>
<div>
<button @click="handlePayment(2500)" :disabled="isLoading">
{{ isLoading ? 'Processing...' : 'Pay $25.00' }}
</button>
<div v-if="data?.data">
<p>Gateway: {{ data.data.gateway }}</p>
<p>Transaction: {{ data.data.transactionId }}</p>
<p>Providers tried: {{ data.providersTried }}</p>
</div>
</div>
</template>
useQuotaCheck
Check and consume quota with rate limiting.
Basic Quota Check
<script setup>
import { useQuotaCheck } from '@ductape/vue';
const { mutate: checkQuota, data, isLoading } = useQuotaCheck({
onSuccess: (result) => {
if (!result.allowed) {
alert(`Rate limited! Resets at ${new Date(result.resetsAt)}`);
}
}
});
const handleApiCall = (userId) => {
checkQuota({
tag: 'api-calls',
identifier: userId,
amount: 1
});
};
</script>
<template>
<div>
<button
@click="handleApiCall('user-123')"
:disabled="isLoading"
>
Make API Call
</button>
<div v-if="data">
<p>Allowed: {{ data.allowed ? '✓' : '❌' }}</p>
<p>Used: {{ data.used }} / {{ data.limit }}</p>
<p>Remaining: {{ data.remaining }}</p>
<p>Resets: {{ new Date(data.resetsAt).toLocaleString() }}</p>
</div>
</div>
</template>
Pre-flight Quota Check
<script setup>
import { useQuotaCheck, useQuotaRun } from '@ductape/vue';
const { mutate: checkQuota } = useQuotaCheck();
const { mutate: processPayment, isLoading } = useQuotaRun();
const handleSubmit = (userId, amount) => {
// Check quota first
checkQuota(
{
tag: 'payment-quota',
identifier: userId,
amount: 1
},
{
onSuccess: (quota) => {
if (quota.allowed) {
// Proceed with payment
processPayment({
tag: 'payment-quota',
input: { amount, userId }
});
} else {
alert(`Rate limit exceeded. Try again at ${new Date(quota.resetsAt)}`);
}
}
}
);
};
</script>
<template>
<button @click="handleSubmit('user-123', 1000)" :disabled="isLoading">
Process Payment
</button>
</template>
useQuotaStatus
Query quota status without consuming.
Display Quota Usage
<script setup>
import { useQuotaStatus } from '@ductape/vue';
import { onMounted } from 'vue';
const props = defineProps<{ userId: string }>();
const { data, isLoading, refetch } = useQuotaStatus(
{
tag: 'api-calls',
identifier: props.userId
},
{
refetchInterval: 30000 // Refresh every 30 seconds
}
);
onMounted(() => {
if (data.value) {
console.log(`User has ${data.value.remaining} API calls remaining`);
}
});
</script>
<template>
<div>
<h3>API Quota Status</h3>
<div v-if="isLoading">Loading...</div>
<div v-else-if="data">
<div>Used: {{ data.used }} / {{ data.limit }}</div>
<div>Remaining: {{ data.remaining }}</div>
<div>Resets: {{ new Date(data.resetsAt).toLocaleString() }}</div>
<div style="width: 100%; height: 20px; background-color: #eee">
<div
:style="{
width: `${(data.used / data.limit) * 100}%`,
height: '100%',
backgroundColor: data.remaining > 10 ? 'green' : 'red'
}"
/>
</div>
<button @click="refetch">Refresh</button>
</div>
</div>
</template>
useQuotaSubscription
Subscribe to real-time quota changes.
Real-time Quota Monitor
<script setup>
import { useQuotaSubscription } from '@ductape/vue';
const props = defineProps<{ userId: string }>();
const { data: quotaChanges, isSubscribed } = useQuotaSubscription(
{
tag: 'api-calls',
identifier: props.userId
},
{
onData: (events) => {
events.forEach(event => {
if (event.remaining < 10) {
alert('Warning: Low quota remaining!');
}
});
}
}
);
</script>
<template>
<div>
<h3>Live Quota Monitor</h3>
<p>Status: {{ isSubscribed ? '🟢 Connected' : '🔴 Disconnected' }}</p>
<div v-if="quotaChanges && quotaChanges.length > 0">
<p>Last Update:</p>
<p>Remaining: {{ quotaChanges[0].remaining }}</p>
<p>Limit: {{ quotaChanges[0].limit }}</p>
</div>
</div>
</template>
useHealthStatus
Query health status of services.
Service Health Dashboard
<script setup>
import { useHealthStatus } from '@ductape/vue';
import { onMounted } from 'vue';
const { data, isLoading, refetch } = useHealthStatus(
{ tag: 'payment-service' },
{ refetchInterval: 30000 } // Check every 30 seconds
);
onMounted(() => {
if (data.value?.status === 'unhealthy') {
console.warn('Service is unhealthy!');
}
});
</script>
<template>
<div>
<h2>Service Health</h2>
<div v-if="isLoading">Checking health...</div>
<div v-else-if="data">
<div>
Status:
<span
:style="{
color: data.status === 'healthy' ? 'green' :
data.status === 'degraded' ? 'orange' : 'red'
}"
>
{{ data.status.toUpperCase() }}
</span>
</div>
<div>Last Check: {{ data.lastCheck }}</div>
<h3>Probes</h3>
<ul>
<li v-for="probe in data.probes" :key="probe.name">
<strong>{{ probe.name }}</strong>: {{ probe.status }}
<span v-if="probe.latency"> ({{ probe.latency }}ms)</span>
<span v-if="probe.error"> - {{ probe.error }}</span>
</li>
</ul>
<button @click="refetch">Refresh</button>
</div>
</div>
</template>
useHealthSubscription
Subscribe to real-time health changes.
Real-time Health Alerts
<script setup>
import { useHealthSubscription } from '@ductape/vue';
import { ref } from 'vue';
const alerts = ref<string[]>([]);
const { data, isSubscribed } = useHealthSubscription(
{ tag: 'payment-service' },
{
onData: (events) => {
events.forEach(event => {
if (event.status === 'unhealthy') {
alerts.value.push(
`⚠️ Service unhealthy at ${event.lastCheck}`
);
} else if (event.status === 'healthy') {
alerts.value.push(
`✓ Service recovered at ${event.lastCheck}`
);
}
});
}
}
);
</script>
<template>
<div>
<h3>Health Alerts</h3>
<p>Monitoring: {{ isSubscribed ? '🟢 Active' : '🔴 Inactive' }}</p>
<div v-if="data && data.length > 0">
Current Status: {{ data[0].status }}
</div>
<div>
<h4>Alert History</h4>
<ul>
<li v-for="(alert, i) in alerts" :key="i">{{ alert }}</li>
</ul>
</div>
</div>
</template>
Complete Example: Resilient Payment Flow
<script setup>
import { useQuotaCheck, useFallbackRun, useHealthStatus } from '@ductape/vue';
import { ref, reactive } from 'vue';
const paymentData = reactive({
amount: 1000,
currency: 'usd',
userId: 'user-123'
});
const { mutate: checkQuota, data: quotaData } = useQuotaCheck();
const { mutate: checkHealth, data: healthData } = useHealthStatus();
const {
mutate: processPayment,
isLoading: isProcessing,
data: paymentResult
} = useFallbackRun();
const handlePayment = () => {
// Step 1: Check quota
checkQuota(
{
tag: 'payment-quota',
identifier: paymentData.userId,
amount: 1
},
{
onSuccess: (quota) => {
if (!quota.allowed) {
alert(`Rate limit exceeded. Resets at ${new Date(quota.resetsAt)}`);
return;
}
// Step 2: Check health
checkHealth(
{ tag: 'payment-service' },
{
onSuccess: (health) => {
if (health.status === 'unhealthy') {
alert('Payment service is currently unavailable');
return;
}
// Step 3: Process payment with fallback
processPayment({
tag: 'payment-fallback',
input: paymentData
});
}
}
);
}
}
);
};
</script>
<template>
<div>
<h2>Payment Processor</h2>
<div>
<label>
Amount: $
<input
type="number"
:value="paymentData.amount / 100"
@input="paymentData.amount = parseFloat($event.target.value) * 100"
/>
</label>
</div>
<button @click="handlePayment" :disabled="isProcessing">
{{ isProcessing ? 'Processing...' : 'Pay Now' }}
</button>
<div v-if="quotaData">
<p>Quota: {{ quotaData.remaining }} / {{ quotaData.limit }}</p>
</div>
<div v-if="healthData">
<p>Service Health: {{ healthData.status }}</p>
</div>
<div v-if="paymentResult" style="color: green">
✓ Payment processed via {{ paymentResult.provider }}
<br />
Latency: {{ paymentResult.latency }}ms
<br />
Providers tried: {{ paymentResult.providersTried }}
</div>
</div>
</template>
Composition with Other Composables
<script setup>
import { useQuotaRun } from '@ductape/vue';
import { useDatabaseQuery } from '@ductape/vue';
import { computed } from 'vue';
// Fetch user data
const { data: user } = useDatabaseQuery({
table: 'users',
where: { id: 'user-123' }
});
// Process payment with quota
const { mutate: pay, isLoading, data: payment } = useQuotaRun();
const userQuotaRemaining = computed(() => {
return user.value?.quotaRemaining ?? 0;
});
const handlePayment = () => {
if (userQuotaRemaining.value < 1) {
alert('Insufficient quota');
return;
}
pay({
tag: 'payment-quota',
input: {
userId: user.value.id,
amount: 1000
}
});
};
</script>
<template>
<div>
<p>Quota Remaining: {{ userQuotaRemaining }}</p>
<button @click="handlePayment" :disabled="isLoading">
Pay $10.00
</button>
<div v-if="payment">
Payment processed via {{ payment.provider }}
</div>
</div>
</template>
Best Practices
- Check Quotas First: Always check quota before expensive operations
- Monitor Health: Use health checks before critical operations
- Use Fallbacks for Critical Paths: Implement fallbacks for must-succeed operations
- Leverage Reactivity: Use Vue's reactive system with composables
- Handle Loading States: Show appropriate UI during operations
- Display Error Messages: Provide clear feedback when operations fail
- Show Quota Status: Display remaining quota to users proactively
- Subscribe to Changes: Use subscriptions for real-time monitoring
- Type Your Responses: Use TypeScript generics for type-safe results
- Compose Composables: Combine resilience composables with other Ductape composables