Shared utilities and types for dotdo postgres packages
npm install @dotdo/postgres-shared> Shared utilities and types for postgres.do packages. Zero external dependencies.


This package provides foundational utilities used across all @dotdo/* packages, including error handling, logging, caching, circuit breakers, and security primitives. Designed for Cloudflare Workers with zero external dependencies.
- Installation
- Features
- Quick Start
- API Reference
- Error Handling
- Logging
- Circuit Breaker
- Cache
- Validation
- Security
- Sync Primitives
- Related Packages
``bash`
npm install @dotdo/postgres-shared
| Feature | Description |
|---------|-------------|
| Standardized Errors | Type-safe error hierarchy with codes, retryable flags, and recovery patterns |
| Unified Logger | Multi-level logging with structured metadata and child loggers |
| Circuit Breaker | Resilience pattern for cross-DO communication with multi-instance tracking |
| Bounded Cache | LRU cache with TTL support and automatic cleanup |
| Validation | Input validation utilities with sanitization |
| Security | CSP middleware, secret sanitization, and crypto utilities |
| Sync Primitives | Mutex, semaphore, and read-write locks for concurrency control |
`typescript
import {
PostgresError,
createLogger,
LogLevel,
CircuitBreaker,
BoundedCache,
validateInput,
} from '@dotdo/postgres-shared'
// Create a logger
const logger = createLogger({
level: LogLevel.INFO,
prefix: '[MyService]',
})
logger.info('Service started', { version: '1.0.0' })
// Use the cache
const cache = new BoundedCache
maxSize: 1000,
defaultTTL: 60000,
})
cache.set('user:123', 'cached-data')
const data = cache.get('user:123')
// Use circuit breaker for resilient DO calls
const cb = new CircuitBreaker({ failureThreshold: 5 })
const result = await cb.execute('tenant-123', async () => {
return await doStub.fetch(request)
})
`
Import from @dotdo/postgres-shared or @dotdo/postgres-shared/errors.
#### PostgresError
Base error class with standardized fields for programmatic handling.
`typescript
import {
PostgresError,
ConnectionError,
QueryError,
ValidationError,
AuthError,
wrapError,
} from '@dotdo/postgres-shared'
// Throw typed errors
throw new QueryError('Query failed', {
code: 'QUERY_TIMEOUT',
context: { sql: 'SELECT * FROM users', timeout: 30000 },
retryable: true,
})
// Wrap unknown errors
try {
await riskyOperation()
} catch (e) {
throw wrapError(e, { context: { operation: 'riskyOperation' } })
}
// Check error properties
if (error instanceof PostgresError) {
console.log(error.code) // 'QUERY_TIMEOUT'
console.log(error.retryable) // true
console.log(error.statusCode) // 504
console.log(error.timestamp) // Date
console.log(error.context) // { sql: '...', timeout: 30000 }
}
`
#### Error Categories
| Error Class | Use Case |
|-------------|----------|
| QueryError | SQL execution failures |ConnectionError
| | Network/connection issues |ValidationError
| | Invalid input data |AuthError
| | Authentication/authorization failures |NotFoundError
| | Resource not found |ConflictError
| | Concurrent modification conflicts |RateLimitError
| | Rate limiting exceeded |
Import from @dotdo/postgres-shared or @dotdo/postgres-shared/logger.
#### Unified Logger
Consistent logging across all packages with structured metadata support.
`typescript
import { createLogger, LogLevel, type ILogger } from '@dotdo/postgres-shared'
// Basic usage
const logger = createLogger()
logger.info('Hello world')
logger.error('Something failed', { error: err.message })
// With configuration
const logger = createLogger({
level: LogLevel.DEBUG, // Log level threshold
prefix: '[MyService]', // Prefix for all messages
timestamps: true, // Include ISO timestamps
})
// Log with structured context
logger.info('User logged in', {
userId: '123',
ip: '192.168.1.1',
userAgent: 'Mozilla/5.0...',
})
// Child loggers with scoped context
const requestLogger = logger.child({ requestId: 'abc-123' })
requestLogger.info('Processing') // Includes requestId automatically
// Set level at runtime
logger.setLevel(LogLevel.ERROR) // Only errors from now on
`
#### Log Levels
| Level | Value | Use For |
|-------|-------|---------|
| DEBUG | 0 | Detailed debugging info |INFO
| | 1 | Normal operation info |WARN
| | 2 | Recoverable issues |ERROR
| | 3 | Errors needing attention |SILENT
| | 4 | Disable all logging |
Import from @dotdo/postgres-shared or @dotdo/postgres-shared/circuit-breaker.
#### CircuitBreaker
Prevents cascading failures in distributed systems.
`typescript
import {
CircuitBreaker,
CircuitOpenError,
DOCircuitBreakerWrapper,
} from '@dotdo/postgres-shared'
// Basic usage
const cb = new CircuitBreaker({
failureThreshold: 5, // Open after 5 failures
resetTimeoutMs: 30000, // Try again after 30s
halfOpenMaxAttempts: 3, // Allow 3 probes in half-open
})
try {
const result = await cb.execute('tenant-123', async () => {
return await doStub.fetch(request)
})
} catch (error) {
if (error instanceof CircuitOpenError) {
return new Response('Service temporarily unavailable', {
status: 503,
headers: { 'Retry-After': '30' },
})
}
throw error
}
// Check circuit state
const state = cb.getState('tenant-123') // 'CLOSED' | 'OPEN' | 'HALF_OPEN'
// Wrap DO stubs for automatic protection
const wrapper = new DOCircuitBreakerWrapper({
failureThreshold: 3,
fallbackResponse: () => new Response('Unavailable', { status: 503 }),
})
const protectedStub = wrapper.wrap(doStub, 'tenant-123')
const response = await protectedStub.fetch(request)
`
#### UnifiedCircuitBreaker
Flexible API supporting both single and multi-instance modes.
`typescript
import { UnifiedCircuitBreaker } from '@dotdo/postgres-shared'
// Single-instance mode (simpler API)
const cb = new UnifiedCircuitBreaker({
mode: 'single',
failureThreshold: 5,
resetTimeoutMs: 30000,
})
await cb.execute(async () => {
return await fetchData()
})
// Multi-instance mode (track multiple services)
const cb = new UnifiedCircuitBreaker({
mode: 'multi',
failureThreshold: 5,
})
await cb.execute('service-a', async () => await serviceA.call())
await cb.execute('service-b', async () => await serviceB.call())
`
Import from @dotdo/postgres-shared or @dotdo/postgres-shared/cache.
#### BoundedCache
LRU cache with TTL support for memory-safe caching.
`typescript
import { BoundedCache } from '@dotdo/postgres-shared'
const cache = new BoundedCache
maxSize: 1000, // Max entries (LRU eviction)
defaultTTL: 60000, // 1 minute TTL
cleanupInterval: 30000, // Cleanup every 30s
})
// Basic operations
cache.set('session:123', session)
cache.set('session:456', session, 120000) // Custom TTL
const session = cache.get('session:123') // null if expired
const exists = cache.has('session:123')
cache.delete('session:123')
cache.clear()
// Statistics
console.log(cache.size) // Current entry count
`
#### InvalidTokenCache
Specialized cache for tracking invalid authentication tokens.
`typescript
import { InvalidTokenCache } from '@dotdo/postgres-shared'
const invalidTokens = new InvalidTokenCache({
maxSize: 10000,
ttl: 3600000, // 1 hour
})
// Mark a token as invalid
invalidTokens.markInvalid('token-abc')
// Check if invalid
if (invalidTokens.isInvalid('token-abc')) {
return new Response('Unauthorized', { status: 401 })
}
`
Import from @dotdo/postgres-shared or @dotdo/postgres-shared/validation.
#### Input Validation
`typescript
import {
validateInput,
validateEmail,
validateUUID,
validateUrl,
sanitizeInput,
} from '@dotdo/postgres-shared/validation'
// Validate with schema
const result = validateInput(data, {
email: { type: 'email', required: true },
age: { type: 'number', min: 0, max: 150 },
name: { type: 'string', maxLength: 100 },
})
if (!result.valid) {
console.log(result.errors) // ['email: invalid format', ...]
}
// Individual validators
validateEmail('user@example.com') // true
validateUUID('123e4567-e89b-12d3-a456-426614174000') // true
validateUrl('https://example.com') // true
// Sanitize input
const clean = sanitizeInput(userInput) // HTML-escaped
`
#### CSP Middleware
Content Security Policy middleware for Hono.
`typescript
import { createCSPMiddleware } from '@dotdo/postgres-shared/csp'
import { Hono } from 'hono'
const app = new Hono()
app.use('*', createCSPMiddleware({
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'", 'wss:'],
}))
`
#### Sanitization
Prevent secret leakage in logs and error messages.
`typescript
import {
sanitizeConnectionString,
sanitizeErrorMessage,
sanitizeHeaders,
} from '@dotdo/postgres-shared/sanitize'
// Mask credentials in connection strings
sanitizeConnectionString('postgres://user:pass@host/db')
// => 'postgres://user:*@host/db'
// Remove sensitive data from error messages
sanitizeErrorMessage('Failed with password=secret123')
// => 'Failed with password=*'
// Clean request headers
sanitizeHeaders(request.headers)
// => Removes Authorization, Cookie, etc.
`
#### Crypto Utilities
`typescript
import {
generateSecureToken,
hashString,
constantTimeCompare,
} from '@dotdo/postgres-shared/crypto'
// Generate secure random token
const token = await generateSecureToken(32)
// Hash a string (SHA-256)
const hash = await hashString('password')
// Timing-safe comparison (prevents timing attacks)
const isValid = constantTimeCompare(provided, expected)
`
Import from @dotdo/postgres-shared/sync-primitives.
Concurrency control primitives for async operations.
`typescript
import {
Mutex,
Semaphore,
ReadWriteLock,
} from '@dotdo/postgres-shared/sync-primitives'
// Mutex for exclusive access
const mutex = new Mutex()
await mutex.runExclusive(async () => {
// Only one execution at a time
await criticalSection()
})
// Semaphore for limited concurrency
const semaphore = new Semaphore(5) // Max 5 concurrent
await semaphore.runExclusive(async () => {
await limitedResource()
})
// Read-write lock
const rwLock = new ReadWriteLock()
await rwLock.runWithReadLock(async () => {
// Multiple readers allowed
return await readData()
})
await rwLock.runWithWriteLock(async () => {
// Exclusive writer access
await writeData()
})
`
Serverless-safe event emitter that avoids mutable module state.
`typescript
import { EventEmitterSafe } from '@dotdo/postgres-shared/event-emitter-safe'
const emitter = new EventEmitterSafe<{
'user:created': { id: string; email: string }
'user:deleted': { id: string }
}>()
// Subscribe to events
const unsubscribe = emitter.on('user:created', (data) => {
console.log('User created:', data.email)
})
// Emit events
emitter.emit('user:created', { id: '123', email: 'user@example.com' })
// Unsubscribe
unsubscribe()
`
Tiered storage policy configuration for data movement between HOT/WARM/COLD tiers.
`typescript
import {
createTierPolicy,
StorageTier,
} from '@dotdo/postgres-shared/storage-policy'
const policy = createTierPolicy({
ttl: {
hotTTLMs: 60000, // 1 minute in HOT
warmTTLMs: 300000, // 5 minutes in WARM
coldTTLMs: 86400000, // 1 day before archive
},
promotion: {
coldToWarmAccessCount: 3,
warmToHotAccessCount: 10,
},
})
// Determine recommended tier
const tier = policy.recommendTier({
accessCount: 15,
lastAccessMs: Date.now() - 30000,
}) // StorageTier.HOT
`
Track data access patterns for tiered storage decisions.
`typescript
import { AccessPatternTracker } from '@dotdo/postgres-shared/access-pattern-tracker'
const tracker = new AccessPatternTracker({
windowMs: 60000, // 1 minute window
maxEntries: 10000,
})
// Record access
tracker.recordAccess('page:123')
tracker.recordAccess('page:123')
tracker.recordAccess('page:123')
// Get access stats
const stats = tracker.getStats('page:123')
console.log(stats.accessCount) // 3
console.log(stats.accessRate) // accesses per minute
console.log(stats.isHot) // true if frequently accessed
`
- @dotdo/postgres - PostgreSQL Durable Object server
- postgres.do - SQL client and CLI
- @dotdo/pglite` - PGLite fork for Cloudflare Workers
MIT