@digilogiclabs/platform-core
v1.1.0
Published
Vendor-agnostic infrastructure abstraction layer for DLL Platform
Maintainers
Readme
@digilogiclabs/platform-core
Vendor-agnostic infrastructure abstraction layer for building portable, enterprise-grade applications.
Overview
Platform Core provides a unified API for common infrastructure services, allowing you to swap providers without changing application code. Build once, deploy anywhere.
Features
Core Infrastructure
- Database - Query builder abstraction (PostgreSQL, Supabase, Memory)
- Cache - Key-value caching (Redis, Upstash, Memory)
- Storage - File storage (S3/MinIO/R2, Supabase Storage, Memory)
- Email - Transactional email (SMTP, Resend, Console, Memory)
- Queue - Background jobs (BullMQ, Memory)
- Tracing - Distributed tracing (OpenTelemetry, Memory, Noop)
- Health Checks - Built-in health monitoring for all services
Enterprise Patterns
- Middleware - Composable before/after hooks for all operations
- Hooks - Lifecycle events for database, cache, email, queue operations
- Resilience - Retry, circuit breaker, timeout, bulkhead, fallback patterns
- Metrics - Counters, gauges, histograms, timings with tag support
Installation
npm install @digilogiclabs/platform-core
# or
pnpm add @digilogiclabs/platform-coreOptional Peer Dependencies
Install only the providers you need:
# For Supabase database
pnpm add @supabase/supabase-js
# For Upstash cache
pnpm add @upstash/redis
# For S3 storage
pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
# For Resend email
pnpm add resendQuick Start
Development (Memory Adapters)
import { createPlatform } from '@digilogiclabs/platform-core';
// Uses memory adapters by default - no external services needed
const platform = createPlatform();
// Use the platform
const users = await platform.db.from('users').where('active', '=', true).execute();
await platform.cache.set('user:123', users.data[0], 3600);
await platform.email.send({
to: '[email protected]',
subject: 'Welcome!',
text: 'Hello from Platform Core',
});Production (Real Adapters)
import { createPlatformAsync } from '@digilogiclabs/platform-core';
// Use environment variables to configure providers
const platform = await createPlatformAsync();
// Or configure explicitly
const platform = await createPlatformAsync({
database: { provider: 'supabase' },
cache: { provider: 'upstash' },
storage: { provider: 's3' },
email: { provider: 'resend' },
});Environment Variables
# Provider selection
PLATFORM_DB_PROVIDER=memory|supabase
PLATFORM_CACHE_PROVIDER=memory|upstash
PLATFORM_STORAGE_PROVIDER=memory|s3|minio|r2
PLATFORM_EMAIL_PROVIDER=memory|console|resend
PLATFORM_QUEUE_PROVIDER=memory
# Supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# Upstash
UPSTASH_REDIS_REST_URL=https://your-redis.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token
# S3 / MinIO / R2
S3_ENDPOINT=https://s3.amazonaws.com
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=your-key
S3_SECRET_ACCESS_KEY=your-secret
S3_BUCKET=your-bucket
S3_FORCE_PATH_STYLE=false
# Resend
RESEND_API_KEY=re_your_api_key
[email protected]API Reference
Platform
interface IPlatform {
db: IDatabase;
cache: ICache;
storage: IStorage;
email: IEmail;
queue: IQueue;
healthCheck(): Promise<PlatformHealthStatus>;
close(): Promise<void>;
}Database
// Query builder
const users = await platform.db
.from<User>('users')
.where('active', '=', true)
.orderBy('created_at', 'desc')
.limit(10)
.execute();
// Insert
await platform.db
.from<User>('users')
.insert({ name: 'John', email: '[email protected]' })
.execute();
// Update
await platform.db
.from<User>('users')
.where('id', '=', '123')
.update({ name: 'Jane' })
.execute();
// Delete
await platform.db
.from<User>('users')
.where('id', '=', '123')
.delete()
.execute();Cache
// Basic operations
await platform.cache.set('key', value, 3600); // TTL in seconds
const value = await platform.cache.get<User>('key');
await platform.cache.delete('key');
// Batch operations
const values = await platform.cache.mget(['key1', 'key2', 'key3']);
await platform.cache.mset([
{ key: 'a', value: 1 },
{ key: 'b', value: 2, ttl: 60 },
]);
// Increment/Decrement
const count = await platform.cache.incr('counter');
// Pattern delete
await platform.cache.deletePattern('user:*');Storage
// Upload
const file = await platform.storage.upload('path/to/file.txt', buffer, {
contentType: 'text/plain',
});
// Download
const data = await platform.storage.download('path/to/file.txt');
// Get signed URL
const url = await platform.storage.getSignedUrl('path/to/file.txt', 3600);
// Delete
await platform.storage.delete('path/to/file.txt');
// Check existence
const exists = await platform.storage.exists('path/to/file.txt');// Send email
const result = await platform.email.send({
to: '[email protected]',
from: '[email protected]',
subject: 'Hello',
text: 'Plain text body',
html: '<h1>HTML body</h1>',
});
// Batch send
const results = await platform.email.sendBatch([
{ to: '[email protected]', subject: 'A', text: 'A' },
{ to: '[email protected]', subject: 'B', text: 'B' },
]);Queue
// Add job
const job = await platform.queue.add('sendEmail', {
userId: '123',
template: 'welcome',
});
// Process jobs
platform.queue.process(async (job) => {
console.log('Processing:', job.name, job.data);
return { success: true };
});
// Get job status
const job = await platform.queue.getJob('job_id');Health Checks
const health = await platform.healthCheck();
console.log(health);
// {
// healthy: true,
// services: {
// database: true,
// cache: true,
// storage: true,
// email: true,
// queue: true,
// },
// timestamp: 1701234567890,
// }Testing
Use memory adapters for fast, isolated tests:
import { createPlatform, MemoryDatabase, MemoryCache } from '@digilogiclabs/platform-core';
describe('MyService', () => {
const platform = createPlatform(); // Uses memory adapters
beforeEach(async () => {
// Reset state between tests
await platform.close();
});
it('should create user', async () => {
const result = await platform.db
.from('users')
.insert({ name: 'Test' })
.execute();
expect(result.data).toHaveLength(1);
});
});Direct Adapter Usage
You can also use adapters directly:
import {
MemoryDatabase,
MemoryCache,
SupabaseDatabase,
UpstashCache,
S3Storage,
ResendEmail,
ConsoleEmail,
} from '@digilogiclabs/platform-core';
// Create adapters directly
const db = new MemoryDatabase();
const cache = new MemoryCache();
const email = new ConsoleEmail(); // Logs to consoleMigration from Direct Provider Usage
Before (Direct Supabase)
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(url, key);
const { data } = await supabase.from('users').select('*').eq('active', true);After (Platform Core)
import { createPlatformAsync } from '@digilogiclabs/platform-core';
const platform = await createPlatformAsync();
const result = await platform.db.from('users').where('active', '=', true).execute();Enterprise Patterns
Middleware
Compose middleware for cross-cutting concerns:
import { createMiddlewareChain, createLoggingMiddleware, createMetricsMiddleware } from '@digilogiclabs/platform-core';
const chain = createMiddlewareChain()
.use(createLoggingMiddleware(logger))
.use(createMetricsMiddleware(metrics))
.use({
name: 'custom',
before: (ctx) => console.log('Before:', ctx.operation),
after: (ctx) => console.log('After:', ctx.operation, ctx.duration + 'ms'),
onError: (ctx, error) => console.error('Error:', error),
});
// Execute with middleware
const result = await chain.execute(
{ service: 'database', operation: 'query', args: { table: 'users' }, logger, startTime: Date.now(), correlationId: 'abc' },
async () => db.from('users').execute()
);Hooks
Register lifecycle hooks for platform events:
import { createHookRegistry } from '@digilogiclabs/platform-core';
const hooks = createHookRegistry(logger);
hooks.register({
onReady: () => console.log('Platform ready'),
onShutdown: () => console.log('Shutting down'),
beforeQuery: ({ table, operation }) => console.log(`Query: ${operation} on ${table}`),
afterQuery: ({ table, duration }) => console.log(`Query completed in ${duration}ms`),
onCacheHit: (key, value) => console.log(`Cache hit: ${key}`),
onCacheMiss: (key) => console.log(`Cache miss: ${key}`),
onError: ({ service, operation, error }) => console.error(`Error in ${service}.${operation}:`, error),
});
// Execute hooks
await hooks.execute('onReady');
await hooks.execute('beforeQuery', { table: 'users', operation: 'select' });Resilience Patterns
Retry with Exponential Backoff
import { withRetry, RetryConfigs, RetryPredicates } from '@digilogiclabs/platform-core';
const result = await withRetry(
() => fetchData(),
{
maxAttempts: 3,
baseDelay: 100,
maxDelay: 5000,
backoffMultiplier: 2,
shouldRetry: RetryPredicates.networkErrors,
}
);
// Or use presets
const result = await withRetry(() => fetchData(), RetryConfigs.standard);Circuit Breaker
import { CircuitBreaker, CircuitBreakerRegistry } from '@digilogiclabs/platform-core';
const breaker = new CircuitBreaker({
failureThreshold: 5,
resetTimeout: 30000,
halfOpenRequests: 3,
});
const result = await breaker.execute(() => externalApiCall());
// Or use a registry for named breakers
const registry = new CircuitBreakerRegistry();
const apiBreaker = registry.get('external-api');Timeout
import { withTimeout, TimeoutError } from '@digilogiclabs/platform-core';
try {
const result = await withTimeout(() => slowOperation(), 5000);
} catch (error) {
if (error instanceof TimeoutError) {
console.log('Operation timed out');
}
}Bulkhead (Concurrency Isolation)
import { Bulkhead } from '@digilogiclabs/platform-core';
const bulkhead = new Bulkhead({
maxConcurrent: 10,
maxQueued: 100,
timeout: 30000,
});
const result = await bulkhead.execute(() => cpuIntensiveTask());Fallback
import { withFallback, FallbackStrategies } from '@digilogiclabs/platform-core';
// Static fallback
const result = await withFallback(
() => fetchFromApi(),
FallbackStrategies.value({ cached: true, data: [] })
);
// Dynamic fallback
const result = await withFallback(
() => fetchFromPrimary(),
FallbackStrategies.execute(() => fetchFromSecondary())
);Metrics
Track application metrics with a provider-agnostic interface:
import { MemoryMetrics, createScopedMetrics } from '@digilogiclabs/platform-core';
const metrics = new MemoryMetrics();
// Counter
metrics.increment('api.requests', 1, { method: 'GET', path: '/users' });
metrics.decrement('active.connections');
// Gauge
metrics.gauge('queue.size', 42, { queue: 'emails' });
// Histogram
metrics.histogram('response.size', 1024, { endpoint: '/api/users' });
// Timer
const stopTimer = metrics.startTimer('api.request.duration', { method: 'POST' });
// ... do work ...
stopTimer(); // Records duration
// Timing
metrics.timing('db.query.duration', 42, { table: 'users' });
// Scoped metrics with prefix
const dbMetrics = createScopedMetrics(metrics, 'database', { service: 'users' });
dbMetrics.timing('query', 50); // Records as 'database.query' with service=users tag
// Get summary (for testing)
const summary = metrics.getSummary();
console.log(summary.counters, summary.timings);Local Development
Start the development infrastructure:
# Start core services (PostgreSQL, Redis, MinIO, MailHog)
./infrastructure/scripts/dev.sh start
# Start with observability (adds Prometheus, Grafana)
./infrastructure/scripts/dev.sh start:obs
# Start with dev tools (adds PgAdmin, RedisInsight)
./infrastructure/scripts/dev.sh start:tools
# Check service health
./infrastructure/scripts/dev.sh health
# Connect to databases
./infrastructure/scripts/dev.sh psql
./infrastructure/scripts/dev.sh redis-cliService URLs:
- PostgreSQL:
localhost:5432(user: dll, pass: development) - Redis:
localhost:6379(pass: development) - MinIO:
localhost:9000(console: 9001) - MailHog:
localhost:8025(SMTP: 1025) - Prometheus:
localhost:9090(with observability profile) - Grafana:
localhost:3001(with observability profile)
License
MIT
