For production retry, backoff, circuit-breaker, and monitoring guidance, see
docs/RATE_LIMITING.md.
Type-safe client library for Dorisio payment infrastructure. Send tips, verify wallets, and manage creator payouts on Stellar.
✨ Production-Ready
- Type-safe with full TypeScript support
- Domain-specific error handling (PaymentError, WalletVerificationError, AuthError)
- Idempotency support prevents double-charging on retries
- Webhook signature verification for async events
- Comprehensive examples (vanilla JS + React)
🚀 Developer Experience
- Exported Zod schemas for consumer validation
- Full JSDoc documentation with examples
- OpenAPI 3.0 specification and interactive documentation viewer
- Backend route generation CLI (
npx dorisio generate-backend) - TypeDoc API reference auto-generated
- Architecture Decision Records (ADRs) documenting core design choices
- React hooks for seamless integration
- Comprehensive unit tests (100% passing)
💰 Payment Features
- Create tips with optional messages
- Build and submit Stellar transactions
- Check transaction confirmation status
- Transaction history with pagination
- Creator earnings tracking
🔐 Security
- Challenge-response wallet verification
- Wallet linking with Stellar integration
- Safe retry logic with exponential backoff
- Constant-time signature verification
npm install dorisio-sdkimport { DorisioClient, PaymentError, Schemas } from 'dorisio-sdk';
import { v4 as uuidv4 } from 'uuid';
const client = new DorisioClient({
baseURL: 'https://api.dorisio.com',
timeout: 30000,
});
// Validate input with exported schemas
const tipInput = Schemas.Payment.createTip.parse({
amount: 50,
currency: 'USD',
creatorId: '550e8400-e29b-41d4-a716-446655440000',
message: 'Great content!',
idempotencyKey: uuidv4(), // Safe to retry with same key
});
try {
const tip = await client.payments.createTip(tipInput);
console.log('Tip created:', tip.id);
} catch (error) {
if (error instanceof PaymentError) {
console.error('Payment failed:', error.message);
}
}import { DorisioProvider, useCreateTip, useWallet } from 'dorisio-sdk/react';
import { Schemas } from 'dorisio-sdk';
function TipButton() {
const { createTip, loading, error } = useCreateTip();
const handleTip = async () => {
const input = Schemas.Payment.createTip.parse({
amount: 50,
currency: 'USD',
creatorId: 'xxx',
idempotencyKey: uuidv4(),
});
await createTip(input);
};
return (
<button onClick={handleTip} disabled={loading}>
{loading ? 'Processing...' : 'Send Tip'}
</button>
);
}
export function App() {
return (
<DorisioProvider config={{ baseURL: 'https://api.dorisio.com' }}>
<TipButton />
</DorisioProvider>
);
}Test the SDK offline without hitting testnet or the Dorisio API. When mode: "sandbox" is set, HttpClient bypasses fetch and returns deterministic mock responses for every client method.
import { DorisioClient } from 'dorisio-sdk';
const client = new DorisioClient({
baseUrl: 'https://api.dorisio.com',
token: 'test-token',
mode: 'sandbox', // no network calls
sandboxSeed: 42, // optional — same seed => same mocks
});
const tip = await client.createTip({
creatorId: 'mock-creator-123',
amount: 50,
message: 'Test tip',
});
// Inspect what the sandbox handled
console.log(client.getSandboxHistory());
// Toggle to live without recreating the client
client.setMode('live');Or use the dedicated helper:
import { createSandboxClient } from 'dorisio-sdk';
const client = createSandboxClient({ seed: 42, latency: 0 });
await client.getCurrentUser();DorisioClient({ mode: 'sandbox' })works with zero network calls- Client methods return deterministic mocks (seeded)
client.getSandboxHistory()/clearSandboxHistory()for test assertionsclient.setMode('sandbox' | 'live')toggles without recreating the client
- Published API Docs - Auto-generated from JSDoc and deployed to GitHub Pages on every push to
main - Local TypeDoc build - Regenerate with
npm run docs - Examples - Runnable code samples
-
Vanilla JS - Auth, wallet, payments
-
React Components - CreateTip, WalletStatus
-
Interceptors - Logging, metrics, auth refresh, retry policy
-
Hook into the request/response lifecycle for cross-cutting concerns (logging, metrics, retry policy, header injection). See INTERCEPTORS.md for the full guide.
import { DorisioClient } from 'dorisio-sdk';
const client = new DorisioClient({ baseUrl: 'https://api.dorisio.com', token: 'token' });
const interceptors = client.getHttpClient().getInterceptors();
interceptors.addRequestInterceptor((options) => {
options.headers = { ...options.headers, 'X-Request-Id': crypto.randomUUID() };
return options;
});InterceptorManager, RequestInterceptor, ResponseInterceptor, ErrorInterceptor, and
RequestOptions are exported from the package root. Interceptors run sequentially in registration
order and may be async; error interceptors observe failures but do not swallow them.
Catch specific errors and handle them appropriately:
import { PaymentError, WalletVerificationError, AuthError } from 'dorisio-sdk';
try {
await client.payments.createTip({/* ... */});
} catch (error) {
if (error instanceof PaymentError) {
console.error('Payment failed:', error.message);
console.error('Transaction:', error.transactionHash);
} else if (error instanceof WalletVerificationError) {
console.error('Wallet error:', error.message);
} else if (error instanceof AuthError) {
console.error('Auth failed:', error.message);
}
}Dorisio SDK exports both Zod schemas for runtime validation and their corresponding inferred TypeScript types for compile-time safety:
- When to use TypeScript types: Use inferred types (
CreateTipInput,LoginInput,WalletInfo, etc.) across your application code, component props, and API boundaries for compile-time type checking without runtime overhead. - When to use Zod schemas / Normalizers: Use schemas (
PaymentSchemas,AuthSchemas,CreatorSchemas,WalletSchemas) or SDK normalizers (normalizeCreateTip,normalizeCreatorProfile) at I/O boundaries—such as processing user forms, untrusted API responses, webhooks, or query parameters—to validate data shapes and prevent invalid requests.
import {
PaymentSchemas,
type CreateTipInput,
normalizeCreateTip,
} from 'dorisio-sdk';
import { v4 as uuidv4 } from 'uuid';
// 1. Static typing with inferred types
const tipData: CreateTipInput = {
amount: 50,
currency: 'USD',
creatorId: '550e8400-e29b-41d4-a716-446655440000',
message: 'Great work!',
idempotencyKey: uuidv4(),
};
// 2. Runtime validation via Zod schemas
const validatedTip = PaymentSchemas.createTip.parse(tipData);
// Or safe parsing with error handling
const result = PaymentSchemas.createTip.safeParse(untrustedInput);
if (!result.success) {
console.error('Validation errors:', result.error.flatten());
}
// 3. Normalizer helper for runtime validation & transformation
const normalized = normalizeCreateTip(untrustedInput);Safe retries with unique keys:
const idempotencyKey = uuidv4(); // Generate once
try {
const tip = await client.payments.createTip({
creatorId: 'xxx',
amount: 50,
idempotencyKey, // Prevents double-charging on retry
});
} catch (error) {
// Safe to retry with same key - returns same tip
const tip = await client.payments.createTip({
creatorId: 'xxx',
amount: 50,
idempotencyKey, // Same key = same result
});
}Verify incoming webhooks are authentic:
import { verifyWebhookSignature, parseWebhookPayload } from 'dorisio-sdk';
// In your webhook handler
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
req.headers['x-dorisio-signature'],
process.env.DORISIO_WEBHOOK_SECRET!
);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = parseWebhookPayload(req.body);
console.log(`Event: ${event.event}`, event.data);You can pass custom headers per request across all client methods to pass tracing headers, tenant identifiers, or custom auth metadata. Custom headers merge with and can override default client headers:
// Custom headers on API calls
const tip = await client.createTip(
{
creatorId: 'creator-123',
amount: 25,
message: 'Awesome work!',
},
{
headers: {
'X-Trace-Id': 'trace-abc-123',
'X-Tenant-ID': 'tenant-99',
},
}
);
// Custom headers on creator fetching
const creator = await client.getCreator('creator-123', {
headers: { 'X-Custom-Header': 'custom-value' },
});Perform bulk operations with concurrency limits, exponential backoff retries, and detailed breakdown of successful and failed items:
// Batch fetch creators with retry capability
const result = await client.getCreatorsBatch(['creator-1', 'creator-2', 'creator-3'], {
concurrency: 5,
maxRetries: 3,
retryDelayMs: 500,
});
console.log(`Processed ${result.successful.length} of ${result.total} creators.`);
if (result.hasFailures) {
console.warn(`Failed items: ${result.failed.length}`);
// Retry only the failed items
const retryResult = await client.retryBatch(result.failed, async (creatorId) => {
return client.getCreator(creatorId);
});
}
// Batch tip creations with partial failure resilience
const tipBatch = await client.createTipsBatch([
{ creatorId: 'c1', amount: 10 },
{ creatorId: 'c2', amount: 20 },
]);Every request is assigned a unique X-Request-ID for end-to-end tracing across your frontend and backend infrastructure:
// Custom request ID generator or structured logger
const client = new DorisioClient({
baseUrl: 'https://api.dorisio.com',
token: 'my-token',
requestIdGenerator: () => `req-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
logger: {
level: 'debug',
handler: (entry) => {
console.log(`[${entry.timestamp}] [${entry.level.toUpperCase()}] [req:${entry.requestId}] ${entry.message}`);
},
},
});
// Access request ID on errors for debugging and support tickets
try {
await client.getCreator('non-existent');
} catch (error) {
if (error instanceof DorisioError) {
console.error(`Request failed with ID ${error.requestId}: ${error.message}`);
}
}const { createTip, loading, error, data } = useCreateTip();
const tip = await createTip(
{
amount: 50,
currency: 'USD',
creatorId: 'xxx',
idempotencyKey: uuidv4(),
},
{
headers: { 'X-Source': 'creator-modal' },
}
);const { wallet, loading, error, refetch } = useWallet({
headers: { 'X-Custom-Header': 'wallet-view' },
});
console.log(wallet?.balance, wallet?.network);
refetch(); // Manual refreshconst { creator, loading, error, refetch } = useCreator('creator-123', {
headers: { 'X-App-Client': 'web-dashboard' },
});
console.log('Creator name:', creator?.name);const { user, loading, error, refetch } = useUser({
headers: { 'X-Context': 'user-profile' },
});
console.log('Logged in user:', user?.email);const { transactions, total, page, goToPage, loading } = useTransactionHistory({
limit: 20,
});
transactions.forEach((tx) => {
console.log(`$${tx.amount} to ${tx.creatorId}`);
});const client = new DorisioClient({
baseURL: 'https://api.dorisio.com',
timeout: 30000,
retryAttempts: 3,
retryDelay: 1000,
});DORISIO_API_URL=https://api.dorisio.com
DORISIO_WEBHOOK_SECRET=your-secret-key# Install
npm install
# Run tests (86 tests, 100% passing)
npm run test
# Type check
npm run type-check
# Lint
npm run lint
# Generate docs (output: docs/, deployed to GitHub Pages by .github/workflows/docs.yml)
npm run docs
# Build
npm run build
# Watch mode
npm run devSee examples/ for complete working examples:
- Auth Flow - Register, login, session validation
- Wallet Linking - Challenge-response verification
- Payments - Tips with idempotency
- React Components - CreateTip form, WalletStatus display
- Interceptors - Logging, metrics, auth refresh, retry policy wrappers
// Create tip (with idempotency)
await client.payments.createTip({
creatorId: string;
amount: number;
message?: string;
idempotencyKey?: string;
});
// Get transaction
await client.payments.getTransaction(transactionId);
// Get history
await client.payments.getTransactionHistory({ limit: 20, offset: 0 });// Register
await client.auth.register({
email: string;
password: string;
name: string;
});
// Login
await client.auth.login({
email: string;
password: string;
});
// Validate session
await client.auth.validateSession();
// Logout
await client.auth.logout();// Request challenge
const challenge = await client.auth.requestWalletChallenge();
// Verify and link
await client.auth.verifyAndLinkWallet({
challenge: string;
signature: string;
publicKey: string;
});
// Get balance
await client.wallet.getBalance();The SDK provides domain-specific error classes with typed fields that let you write recovery logic without string-parsing error messages.
See docs/ERROR_HANDLING.md for the full guide, including retry patterns, circuit breaker, error logging setup, React hook usage, and sandbox testing for error paths.
| Class | statusCode | code | Key fields |
|---|---|---|---|
DorisioError |
varies | varies | base class — statusCode, code |
AuthError |
varies | varies | generic auth failure |
AuthenticationError |
401 | UNAUTHORIZED |
— |
AuthorizationError |
403 | FORBIDDEN |
— |
ValidationError |
400 | VALIDATION_ERROR |
details (field-level errors) |
PaymentError |
varies | varies | transactionHash |
WalletVerificationError |
varies | varies | challenge |
RateLimitError |
429 | RATE_LIMITED |
retryAfter (seconds) |
TimeoutError |
408 | TIMEOUT |
— |
NetworkError |
0 | NETWORK_ERROR |
— |
NotFoundError |
404 | NOT_FOUND |
— |
import {
AuthenticationError,
ValidationError,
RateLimitError,
TimeoutError,
NetworkError,
PaymentError,
DorisioError,
} from 'dorisio-sdk';
import { v4 as uuidv4 } from 'uuid';
const idempotencyKey = uuidv4(); // generate once, reuse on retry
try {
const tip = await client.payments.createTip({
creatorId: 'xxx',
amount: 50,
idempotencyKey,
});
} catch (error) {
if (error instanceof ValidationError) {
// Input rejected — show field errors, don't retry
showFormErrors(error.details);
} else if (error instanceof AuthenticationError) {
// Session expired — redirect to login
client.clearToken();
redirectToLogin();
} else if (error instanceof RateLimitError) {
// Server-requested back-off — wait, then retry with same idempotencyKey
await sleep((error.retryAfter ?? 60) * 1000);
await client.payments.createTip({ creatorId: 'xxx', amount: 50, idempotencyKey });
} else if (error instanceof PaymentError && error.transactionHash) {
// Transaction may have reached Stellar — verify before retrying
const status = await client.payments.checkTransactionConfirmation(error.transactionHash);
if (status !== 'confirmed') {
// Safe to retry with same idempotencyKey
await client.payments.createTip({ creatorId: 'xxx', amount: 50, idempotencyKey });
}
} else if (error instanceof TimeoutError || error instanceof NetworkError) {
// Transient failure — same idempotencyKey prevents double-charging
await retryWithBackoff(() =>
client.payments.createTip({ creatorId: 'xxx', amount: 50, idempotencyKey })
);
} else if (!(error instanceof DorisioError)) {
// Unexpected non-SDK error — propagate
throw error;
}
}async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxAttempts = 3,
initialDelayMs = 500
): Promise<T> {
let delay = initialDelayMs;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) throw error;
await new Promise((resolve) => setTimeout(resolve, delay));
delay *= 2; // exponential backoff
}
}
throw new Error('Unreachable');
}Full guide: docs/ERROR_HANDLING.md
- Modern browsers with ES2020+ support
- Node.js >=20.0.0
- React >=18.0.0 (optional, for React hooks)
- Automatic retry with exponential backoff
- Request timeout: 30s (configurable)
- Concurrent request limit: 10
- Response compression enabled
- HTTPS only in production
- Bearer token authentication
- HMAC-SHA256 webhook verification
- Constant-time signature comparison
- No secrets in logs
- Input validation with Zod
The Dorisio CLI streamlines project scaffolding and backend route integration:
# Scaffold Dorisio into a frontend project
npx dorisio init
# Generate backend route handlers with Zod validation & error handling
npx dorisio generate-backend --framework express --output ./src/routes
npx dorisio generate-backend --framework nextjs --output ./app/api/dorisio
npx dorisio generate-backend --framework fastify --output ./src/routes --javascript
# Generate OpenAPI 3.0 specification from SDK type definitions
npm run generate:openapi- 📄 OpenAPI 3.0 Specification (Interactive API Viewer)
- 🔄 Migration Guide (v0.0.x to v0.1.x)
- 🏛️ Architecture Decision Records (ADRs)
- 📖 TypeDoc API Documentation
See CHANGELOG.md for version history and release notes.
See CONTRIBUTING.md for development guidelines.
MIT - See LICENSE for details