@digilogiclabs/platform-core
v1.4.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
- Security - HTML escaping, URL detection, content sanitization for emails
- API Utilities - Structured errors, error classification, pagination helpers
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);Security Utilities
Helpers for safe rendering of user content in emails and HTML output:
import {
escapeHtml,
containsUrls,
containsHtml,
stripHtml,
defangUrl,
sanitizeForEmail,
} from "@digilogiclabs/platform-core";
// Escape HTML special characters
escapeHtml('<script>alert("xss")</script>');
// → '<script>alert("xss")</script>'
// Detect URLs or HTML in user input
containsUrls("visit https://evil.com"); // true
containsUrls("visit evil.com"); // true (bare domain detection)
containsHtml("<b>bold</b>"); // true
// Strip HTML tags
stripHtml("<p>Hello <b>world</b></p>"); // "Hello world"
// Defang URLs to prevent auto-linking in email clients
defangUrl("https://evil.com/path"); // "hxxps://evil[com]/path"
// Sanitize user content for HTML email templates
sanitizeForEmail(userInput); // Escapes all HTML entitiesAPI Utilities
Framework-agnostic error handling and response helpers:
import {
ApiError,
ApiErrorCode,
CommonApiErrors,
classifyError,
buildPagination,
isApiError,
} from "@digilogiclabs/platform-core";
import type {
ApiSuccessResponse,
ApiPaginatedResponse,
} from "@digilogiclabs/platform-core";
// Pre-built error factories
throw CommonApiErrors.notFound("User"); // 404
throw CommonApiErrors.unauthorized(); // 401
throw CommonApiErrors.forbidden(); // 403
throw CommonApiErrors.validationError(details); // 400
throw CommonApiErrors.rateLimitExceeded(); // 429
// Custom API errors
throw new ApiError(422, "Invalid input", ApiErrorCode.VALIDATION_ERROR, {
field: "email",
message: "Invalid format",
});
// Classify any error into { status, body }
// Handles: ApiError, Zod validation errors, PostgreSQL errors, generic errors
const { status, body } = classifyError(error, isDev);
// → { status: 409, body: { error: "Resource already exists", code: "CONFLICT" } }
// Pagination helper
const pagination = buildPagination(1, 20, 100);
// → { page: 1, limit: 20, total: 100, totalPages: 5, hasMore: true }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
