@digilogiclabs/platform-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,545 @@
1
+ # @digilogiclabs/platform-core
2
+
3
+ > Vendor-agnostic infrastructure abstraction layer for building portable, enterprise-grade applications.
4
+
5
+ [![Tests](https://img.shields.io/badge/tests-343%20passing-brightgreen)](./tests)
6
+ [![Adapters](https://img.shields.io/badge/adapters-14-blue)](./src/adapters)
7
+ [![Interfaces](https://img.shields.io/badge/interfaces-12-blue)](./src/interfaces)
8
+
9
+ ## Overview
10
+
11
+ Platform Core provides a unified API for common infrastructure services, allowing you to swap providers without changing application code. Build once, deploy anywhere.
12
+
13
+ ## Features
14
+
15
+ ### Core Infrastructure
16
+ - **Database** - Query builder abstraction (PostgreSQL, Supabase, Memory)
17
+ - **Cache** - Key-value caching (Redis, Upstash, Memory)
18
+ - **Storage** - File storage (S3/MinIO/R2, Supabase Storage, Memory)
19
+ - **Email** - Transactional email (SMTP, Resend, Console, Memory)
20
+ - **Queue** - Background jobs (BullMQ, Memory)
21
+ - **Tracing** - Distributed tracing (OpenTelemetry, Memory, Noop)
22
+ - **Health Checks** - Built-in health monitoring for all services
23
+
24
+ ### Enterprise Patterns
25
+ - **Middleware** - Composable before/after hooks for all operations
26
+ - **Hooks** - Lifecycle events for database, cache, email, queue operations
27
+ - **Resilience** - Retry, circuit breaker, timeout, bulkhead, fallback patterns
28
+ - **Metrics** - Counters, gauges, histograms, timings with tag support
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install @digilogiclabs/platform-core
34
+ # or
35
+ pnpm add @digilogiclabs/platform-core
36
+ ```
37
+
38
+ ### Optional Peer Dependencies
39
+
40
+ Install only the providers you need:
41
+
42
+ ```bash
43
+ # For Supabase database
44
+ pnpm add @supabase/supabase-js
45
+
46
+ # For Upstash cache
47
+ pnpm add @upstash/redis
48
+
49
+ # For S3 storage
50
+ pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
51
+
52
+ # For Resend email
53
+ pnpm add resend
54
+ ```
55
+
56
+ ## Quick Start
57
+
58
+ ### Development (Memory Adapters)
59
+
60
+ ```typescript
61
+ import { createPlatform } from '@digilogiclabs/platform-core';
62
+
63
+ // Uses memory adapters by default - no external services needed
64
+ const platform = createPlatform();
65
+
66
+ // Use the platform
67
+ const users = await platform.db.from('users').where('active', '=', true).execute();
68
+ await platform.cache.set('user:123', users.data[0], 3600);
69
+ await platform.email.send({
70
+ to: 'user@example.com',
71
+ subject: 'Welcome!',
72
+ text: 'Hello from Platform Core',
73
+ });
74
+ ```
75
+
76
+ ### Production (Real Adapters)
77
+
78
+ ```typescript
79
+ import { createPlatformAsync } from '@digilogiclabs/platform-core';
80
+
81
+ // Use environment variables to configure providers
82
+ const platform = await createPlatformAsync();
83
+
84
+ // Or configure explicitly
85
+ const platform = await createPlatformAsync({
86
+ database: { provider: 'supabase' },
87
+ cache: { provider: 'upstash' },
88
+ storage: { provider: 's3' },
89
+ email: { provider: 'resend' },
90
+ });
91
+ ```
92
+
93
+ ## Environment Variables
94
+
95
+ ```bash
96
+ # Provider selection
97
+ PLATFORM_DB_PROVIDER=memory|supabase
98
+ PLATFORM_CACHE_PROVIDER=memory|upstash
99
+ PLATFORM_STORAGE_PROVIDER=memory|s3|minio|r2
100
+ PLATFORM_EMAIL_PROVIDER=memory|console|resend
101
+ PLATFORM_QUEUE_PROVIDER=memory
102
+
103
+ # Supabase
104
+ SUPABASE_URL=https://your-project.supabase.co
105
+ SUPABASE_ANON_KEY=your-anon-key
106
+ SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
107
+
108
+ # Upstash
109
+ UPSTASH_REDIS_REST_URL=https://your-redis.upstash.io
110
+ UPSTASH_REDIS_REST_TOKEN=your-token
111
+
112
+ # S3 / MinIO / R2
113
+ S3_ENDPOINT=https://s3.amazonaws.com
114
+ S3_REGION=us-east-1
115
+ S3_ACCESS_KEY_ID=your-key
116
+ S3_SECRET_ACCESS_KEY=your-secret
117
+ S3_BUCKET=your-bucket
118
+ S3_FORCE_PATH_STYLE=false
119
+
120
+ # Resend
121
+ RESEND_API_KEY=re_your_api_key
122
+ EMAIL_FROM=noreply@yourdomain.com
123
+ ```
124
+
125
+ ## API Reference
126
+
127
+ ### Platform
128
+
129
+ ```typescript
130
+ interface IPlatform {
131
+ db: IDatabase;
132
+ cache: ICache;
133
+ storage: IStorage;
134
+ email: IEmail;
135
+ queue: IQueue;
136
+
137
+ healthCheck(): Promise<PlatformHealthStatus>;
138
+ close(): Promise<void>;
139
+ }
140
+ ```
141
+
142
+ ### Database
143
+
144
+ ```typescript
145
+ // Query builder
146
+ const users = await platform.db
147
+ .from<User>('users')
148
+ .where('active', '=', true)
149
+ .orderBy('created_at', 'desc')
150
+ .limit(10)
151
+ .execute();
152
+
153
+ // Insert
154
+ await platform.db
155
+ .from<User>('users')
156
+ .insert({ name: 'John', email: 'john@example.com' })
157
+ .execute();
158
+
159
+ // Update
160
+ await platform.db
161
+ .from<User>('users')
162
+ .where('id', '=', '123')
163
+ .update({ name: 'Jane' })
164
+ .execute();
165
+
166
+ // Delete
167
+ await platform.db
168
+ .from<User>('users')
169
+ .where('id', '=', '123')
170
+ .delete()
171
+ .execute();
172
+ ```
173
+
174
+ ### Cache
175
+
176
+ ```typescript
177
+ // Basic operations
178
+ await platform.cache.set('key', value, 3600); // TTL in seconds
179
+ const value = await platform.cache.get<User>('key');
180
+ await platform.cache.delete('key');
181
+
182
+ // Batch operations
183
+ const values = await platform.cache.mget(['key1', 'key2', 'key3']);
184
+ await platform.cache.mset([
185
+ { key: 'a', value: 1 },
186
+ { key: 'b', value: 2, ttl: 60 },
187
+ ]);
188
+
189
+ // Increment/Decrement
190
+ const count = await platform.cache.incr('counter');
191
+
192
+ // Pattern delete
193
+ await platform.cache.deletePattern('user:*');
194
+ ```
195
+
196
+ ### Storage
197
+
198
+ ```typescript
199
+ // Upload
200
+ const file = await platform.storage.upload('path/to/file.txt', buffer, {
201
+ contentType: 'text/plain',
202
+ });
203
+
204
+ // Download
205
+ const data = await platform.storage.download('path/to/file.txt');
206
+
207
+ // Get signed URL
208
+ const url = await platform.storage.getSignedUrl('path/to/file.txt', 3600);
209
+
210
+ // Delete
211
+ await platform.storage.delete('path/to/file.txt');
212
+
213
+ // Check existence
214
+ const exists = await platform.storage.exists('path/to/file.txt');
215
+ ```
216
+
217
+ ### Email
218
+
219
+ ```typescript
220
+ // Send email
221
+ const result = await platform.email.send({
222
+ to: 'user@example.com',
223
+ from: 'noreply@yourdomain.com',
224
+ subject: 'Hello',
225
+ text: 'Plain text body',
226
+ html: '<h1>HTML body</h1>',
227
+ });
228
+
229
+ // Batch send
230
+ const results = await platform.email.sendBatch([
231
+ { to: 'a@example.com', subject: 'A', text: 'A' },
232
+ { to: 'b@example.com', subject: 'B', text: 'B' },
233
+ ]);
234
+ ```
235
+
236
+ ### Queue
237
+
238
+ ```typescript
239
+ // Add job
240
+ const job = await platform.queue.add('sendEmail', {
241
+ userId: '123',
242
+ template: 'welcome',
243
+ });
244
+
245
+ // Process jobs
246
+ platform.queue.process(async (job) => {
247
+ console.log('Processing:', job.name, job.data);
248
+ return { success: true };
249
+ });
250
+
251
+ // Get job status
252
+ const job = await platform.queue.getJob('job_id');
253
+ ```
254
+
255
+ ### Health Checks
256
+
257
+ ```typescript
258
+ const health = await platform.healthCheck();
259
+
260
+ console.log(health);
261
+ // {
262
+ // healthy: true,
263
+ // services: {
264
+ // database: true,
265
+ // cache: true,
266
+ // storage: true,
267
+ // email: true,
268
+ // queue: true,
269
+ // },
270
+ // timestamp: 1701234567890,
271
+ // }
272
+ ```
273
+
274
+ ## Testing
275
+
276
+ Use memory adapters for fast, isolated tests:
277
+
278
+ ```typescript
279
+ import { createPlatform, MemoryDatabase, MemoryCache } from '@digilogiclabs/platform-core';
280
+
281
+ describe('MyService', () => {
282
+ const platform = createPlatform(); // Uses memory adapters
283
+
284
+ beforeEach(async () => {
285
+ // Reset state between tests
286
+ await platform.close();
287
+ });
288
+
289
+ it('should create user', async () => {
290
+ const result = await platform.db
291
+ .from('users')
292
+ .insert({ name: 'Test' })
293
+ .execute();
294
+
295
+ expect(result.data).toHaveLength(1);
296
+ });
297
+ });
298
+ ```
299
+
300
+ ## Direct Adapter Usage
301
+
302
+ You can also use adapters directly:
303
+
304
+ ```typescript
305
+ import {
306
+ MemoryDatabase,
307
+ MemoryCache,
308
+ SupabaseDatabase,
309
+ UpstashCache,
310
+ S3Storage,
311
+ ResendEmail,
312
+ ConsoleEmail,
313
+ } from '@digilogiclabs/platform-core';
314
+
315
+ // Create adapters directly
316
+ const db = new MemoryDatabase();
317
+ const cache = new MemoryCache();
318
+ const email = new ConsoleEmail(); // Logs to console
319
+ ```
320
+
321
+ ## Migration from Direct Provider Usage
322
+
323
+ ### Before (Direct Supabase)
324
+
325
+ ```typescript
326
+ import { createClient } from '@supabase/supabase-js';
327
+
328
+ const supabase = createClient(url, key);
329
+ const { data } = await supabase.from('users').select('*').eq('active', true);
330
+ ```
331
+
332
+ ### After (Platform Core)
333
+
334
+ ```typescript
335
+ import { createPlatformAsync } from '@digilogiclabs/platform-core';
336
+
337
+ const platform = await createPlatformAsync();
338
+ const result = await platform.db.from('users').where('active', '=', true).execute();
339
+ ```
340
+
341
+ ## Enterprise Patterns
342
+
343
+ ### Middleware
344
+
345
+ Compose middleware for cross-cutting concerns:
346
+
347
+ ```typescript
348
+ import { createMiddlewareChain, createLoggingMiddleware, createMetricsMiddleware } from '@digilogiclabs/platform-core';
349
+
350
+ const chain = createMiddlewareChain()
351
+ .use(createLoggingMiddleware(logger))
352
+ .use(createMetricsMiddleware(metrics))
353
+ .use({
354
+ name: 'custom',
355
+ before: (ctx) => console.log('Before:', ctx.operation),
356
+ after: (ctx) => console.log('After:', ctx.operation, ctx.duration + 'ms'),
357
+ onError: (ctx, error) => console.error('Error:', error),
358
+ });
359
+
360
+ // Execute with middleware
361
+ const result = await chain.execute(
362
+ { service: 'database', operation: 'query', args: { table: 'users' }, logger, startTime: Date.now(), correlationId: 'abc' },
363
+ async () => db.from('users').execute()
364
+ );
365
+ ```
366
+
367
+ ### Hooks
368
+
369
+ Register lifecycle hooks for platform events:
370
+
371
+ ```typescript
372
+ import { createHookRegistry } from '@digilogiclabs/platform-core';
373
+
374
+ const hooks = createHookRegistry(logger);
375
+
376
+ hooks.register({
377
+ onReady: () => console.log('Platform ready'),
378
+ onShutdown: () => console.log('Shutting down'),
379
+ beforeQuery: ({ table, operation }) => console.log(`Query: ${operation} on ${table}`),
380
+ afterQuery: ({ table, duration }) => console.log(`Query completed in ${duration}ms`),
381
+ onCacheHit: (key, value) => console.log(`Cache hit: ${key}`),
382
+ onCacheMiss: (key) => console.log(`Cache miss: ${key}`),
383
+ onError: ({ service, operation, error }) => console.error(`Error in ${service}.${operation}:`, error),
384
+ });
385
+
386
+ // Execute hooks
387
+ await hooks.execute('onReady');
388
+ await hooks.execute('beforeQuery', { table: 'users', operation: 'select' });
389
+ ```
390
+
391
+ ### Resilience Patterns
392
+
393
+ #### Retry with Exponential Backoff
394
+
395
+ ```typescript
396
+ import { withRetry, RetryConfigs, RetryPredicates } from '@digilogiclabs/platform-core';
397
+
398
+ const result = await withRetry(
399
+ () => fetchData(),
400
+ {
401
+ maxAttempts: 3,
402
+ baseDelay: 100,
403
+ maxDelay: 5000,
404
+ backoffMultiplier: 2,
405
+ shouldRetry: RetryPredicates.networkErrors,
406
+ }
407
+ );
408
+
409
+ // Or use presets
410
+ const result = await withRetry(() => fetchData(), RetryConfigs.standard);
411
+ ```
412
+
413
+ #### Circuit Breaker
414
+
415
+ ```typescript
416
+ import { CircuitBreaker, CircuitBreakerRegistry } from '@digilogiclabs/platform-core';
417
+
418
+ const breaker = new CircuitBreaker({
419
+ failureThreshold: 5,
420
+ resetTimeout: 30000,
421
+ halfOpenRequests: 3,
422
+ });
423
+
424
+ const result = await breaker.execute(() => externalApiCall());
425
+
426
+ // Or use a registry for named breakers
427
+ const registry = new CircuitBreakerRegistry();
428
+ const apiBreaker = registry.get('external-api');
429
+ ```
430
+
431
+ #### Timeout
432
+
433
+ ```typescript
434
+ import { withTimeout, TimeoutError } from '@digilogiclabs/platform-core';
435
+
436
+ try {
437
+ const result = await withTimeout(() => slowOperation(), 5000);
438
+ } catch (error) {
439
+ if (error instanceof TimeoutError) {
440
+ console.log('Operation timed out');
441
+ }
442
+ }
443
+ ```
444
+
445
+ #### Bulkhead (Concurrency Isolation)
446
+
447
+ ```typescript
448
+ import { Bulkhead } from '@digilogiclabs/platform-core';
449
+
450
+ const bulkhead = new Bulkhead({
451
+ maxConcurrent: 10,
452
+ maxQueued: 100,
453
+ timeout: 30000,
454
+ });
455
+
456
+ const result = await bulkhead.execute(() => cpuIntensiveTask());
457
+ ```
458
+
459
+ #### Fallback
460
+
461
+ ```typescript
462
+ import { withFallback, FallbackStrategies } from '@digilogiclabs/platform-core';
463
+
464
+ // Static fallback
465
+ const result = await withFallback(
466
+ () => fetchFromApi(),
467
+ FallbackStrategies.value({ cached: true, data: [] })
468
+ );
469
+
470
+ // Dynamic fallback
471
+ const result = await withFallback(
472
+ () => fetchFromPrimary(),
473
+ FallbackStrategies.execute(() => fetchFromSecondary())
474
+ );
475
+ ```
476
+
477
+ ### Metrics
478
+
479
+ Track application metrics with a provider-agnostic interface:
480
+
481
+ ```typescript
482
+ import { MemoryMetrics, createScopedMetrics } from '@digilogiclabs/platform-core';
483
+
484
+ const metrics = new MemoryMetrics();
485
+
486
+ // Counter
487
+ metrics.increment('api.requests', 1, { method: 'GET', path: '/users' });
488
+ metrics.decrement('active.connections');
489
+
490
+ // Gauge
491
+ metrics.gauge('queue.size', 42, { queue: 'emails' });
492
+
493
+ // Histogram
494
+ metrics.histogram('response.size', 1024, { endpoint: '/api/users' });
495
+
496
+ // Timer
497
+ const stopTimer = metrics.startTimer('api.request.duration', { method: 'POST' });
498
+ // ... do work ...
499
+ stopTimer(); // Records duration
500
+
501
+ // Timing
502
+ metrics.timing('db.query.duration', 42, { table: 'users' });
503
+
504
+ // Scoped metrics with prefix
505
+ const dbMetrics = createScopedMetrics(metrics, 'database', { service: 'users' });
506
+ dbMetrics.timing('query', 50); // Records as 'database.query' with service=users tag
507
+
508
+ // Get summary (for testing)
509
+ const summary = metrics.getSummary();
510
+ console.log(summary.counters, summary.timings);
511
+ ```
512
+
513
+ ## Local Development
514
+
515
+ Start the development infrastructure:
516
+
517
+ ```bash
518
+ # Start core services (PostgreSQL, Redis, MinIO, MailHog)
519
+ ./infrastructure/scripts/dev.sh start
520
+
521
+ # Start with observability (adds Prometheus, Grafana)
522
+ ./infrastructure/scripts/dev.sh start:obs
523
+
524
+ # Start with dev tools (adds PgAdmin, RedisInsight)
525
+ ./infrastructure/scripts/dev.sh start:tools
526
+
527
+ # Check service health
528
+ ./infrastructure/scripts/dev.sh health
529
+
530
+ # Connect to databases
531
+ ./infrastructure/scripts/dev.sh psql
532
+ ./infrastructure/scripts/dev.sh redis-cli
533
+ ```
534
+
535
+ Service URLs:
536
+ - PostgreSQL: `localhost:5432` (user: dll, pass: development)
537
+ - Redis: `localhost:6379` (pass: development)
538
+ - MinIO: `localhost:9000` (console: 9001)
539
+ - MailHog: `localhost:8025` (SMTP: 1025)
540
+ - Prometheus: `localhost:9090` (with observability profile)
541
+ - Grafana: `localhost:3001` (with observability profile)
542
+
543
+ ## License
544
+
545
+ MIT