platform-core
The foundation library that every other Archipel package depends on. Provides object mapping, template engines, event pub/sub, and shared utilities.
Note: Configuration has moved to
platform-configuration, caching toplatform-caching, cryptography/OTP toplatform-cryptography, mapping toplatform-mapping, resource management toplatform-resources, and health checks toplatform-health. Re-exports are still available fromplatform-corefor backward compatibility.
Package: @breadstone/archipel-platform-core
npm install @breadstone/archipel-platform-coreInstallation
import {
ConfigModule,
ConfigRegistry,
MappingModule,
MappingService,
BcryptService,
CryptoService,
OtpService,
MemoryLayeredCache,
RedisLayeredCache,
ContentTemplateEngine,
EventHub,
} from '@breadstone/archipel-platform-core';Configuration System
Extracted: Configuration has been extracted into
platform-configuration. The imports below still work via re-exports for backward compatibility. New code should import from@breadstone/archipel-platform-configurationdirectly.See the Configuration Management guide for full documentation.
Defining Config Keys
import { createConfigKey, type IConfigRegistryEntry } from '@breadstone/archipel-platform-core';
// Type-safe config keys with phantom types
const SMTP_HOST = createConfigKey<string>('SMTP_HOST');
const SMTP_PORT = createConfigKey<number>('SMTP_PORT');
const SMTP_SECURE = createConfigKey<boolean>('SMTP_SECURE');
export const MY_MODULE_CONFIG_ENTRIES: ReadonlyArray<IConfigRegistryEntry> = [
{ key: SMTP_HOST, module: 'my-module', required: true, description: 'SMTP server hostname' },
{ key: SMTP_PORT, module: 'my-module', required: false, defaultValue: 587, description: 'SMTP port' },
{ key: SMTP_SECURE, module: 'my-module', required: false, defaultValue: true },
];Registering in a Module
import { Module } from '@nestjs/common';
import { ConfigModule } from '@breadstone/archipel-platform-core';
import { MY_MODULE_CONFIG_ENTRIES } from './env';
@Module({
imports: [ConfigModule.register('my-module', MY_MODULE_CONFIG_ENTRIES)],
})
export class MyModule {}Using the ConfigService
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@breadstone/archipel-platform-core';
@Injectable()
export class SmtpClient {
private readonly _host: string;
private readonly _port: number;
constructor(configService: ConfigService) {
this._host = configService.get<string>('SMTP_HOST');
this._port = configService.tryGet<number>('SMTP_PORT', 587);
}
}ConfigRegistry (Static Access)
import { ConfigRegistry } from '@breadstone/archipel-platform-core';
// List all registered modules
const modules = ConfigRegistry.getModules(); // ['my-module', 'platform-auth', ...]
// Get all required entries
const required = ConfigRegistry.getRequired();
// Get entries grouped by module
const grouped = ConfigRegistry.getByModule();Caching
Layered caching with pluggable backends, metrics, and stale-while-revalidate support.
ILayeredCache Interface
interface ILayeredCache<TKey, TValue> {
get(key: TKey): TValue | undefined;
getAsync(key: TKey): Promise<TValue | undefined>;
set(key: TKey, value: TValue): void;
setAsync(key: TKey, value: TValue): Promise<void>;
invalidate(key: TKey): void;
invalidateAsync(key: TKey): Promise<void>;
warm?(key: TKey): Promise<TValue | undefined>;
stats?(): ICacheStats;
}MemoryLayeredCache
In-memory LRU cache with TTL and stale-while-revalidate.
import { MemoryLayeredCache } from '@breadstone/archipel-platform-core';
const productCache = new MemoryLayeredCache<string, IProduct>(
async (key) => {
// Load function — called on cache miss
return await this._productRepository.findById(key);
},
{
ttlMs: 60_000, // 60 seconds TTL
staleWhileRevalidate: true, // Serve stale data while refreshing
maxEntries: 1000, // LRU eviction after 1000 entries
cacheName: 'products', // For metrics labeling
metricsRecorder: telemetryRecorder,
},
);
// Usage
const product = await productCache.getAsync('prod-123');
// Invalidate after mutation
productCache.invalidate('prod-123');
// Check stats
const stats = productCache.stats();
// → { hits: 142, misses: 23, loads: 23, loadErrors: 0, evictions: 0 }RedisLayeredCache
Redis-backed distributed cache for multi-instance deployments.
import { RedisLayeredCache } from '@breadstone/archipel-platform-core';
const sessionCache = new RedisLayeredCache<string, ISessionData>(
async (key) => await this._sessionStore.findByToken(key),
{
url: 'redis://localhost:6379',
keyPrefix: 'session:',
ttlSeconds: 3600,
serialize: (value) => JSON.stringify(value),
deserialize: (raw) => JSON.parse(raw),
cacheName: 'sessions',
},
);ICacheMetricsRecorder
Integrate cache metrics with your observability stack.
import type { ICacheMetricsRecorder } from '@breadstone/archipel-platform-core';
class MyMetricsRecorder implements ICacheMetricsRecorder {
public recordHit(cache: string): void {
/* prometheus counter */
}
public recordMiss(cache: string): void {
/* prometheus counter */
}
public recordLoadSuccess(cache: string): void {
/* ... */
}
public recordLoadError(cache: string): void {
/* ... */
}
public recordEviction(cache: string): void {
/* ... */
}
}Mapping Service
Extracted: Mapping functionality now lives in
platform-mapping. The imports below still work via backward-compatible re-exports fromplatform-core, but new code should import from@breadstone/archipel-platform-mappingdirectly.
Centralized object mapping with type-safe keys and profiles.
Defining Mapping Keys
import { createMappingKey } from '@breadstone/archipel-platform-core';
import type { IUserEntity } from './entities';
import type { UserResponse } from './responses';
export const USER_TO_RESPONSE = createMappingKey<IUserEntity, UserResponse>('UserEntity→UserResponse');
export const USER_TO_SUMMARY = createMappingKey<IUserEntity, UserSummary>('UserEntity→UserSummary');Creating a Mapping Profile
import { Injectable } from '@nestjs/common';
import { MappingProfileBase, type IMappingBuilder } from '@breadstone/archipel-platform-core';
import { USER_TO_RESPONSE, USER_TO_SUMMARY } from './mapping-keys';
@Injectable()
export class UserMappingProfile extends MappingProfileBase {
public override configure(builder: IMappingBuilder): void {
builder.createKeyedMap(USER_TO_RESPONSE, (source) => ({
id: source.id,
name: source.userName,
email: source.email,
verified: source.isVerified,
roles: [...source.roles],
}));
builder.createKeyedMap(USER_TO_SUMMARY, (source) => ({
id: source.id,
displayName: source.userName ?? source.email ?? 'Anonymous',
}));
}
}Registering Profiles in a Module
import { Module } from '@nestjs/common';
import { MappingModule } from '@breadstone/archipel-platform-core';
import { UserMappingProfile } from './mappers/UserMappingProfile';
import { OrderMappingProfile } from './mappers/OrderMappingProfile';
@Module({
imports: [MappingModule.withProfiles([UserMappingProfile, OrderMappingProfile])],
})
export class AppModule {}Using in a Controller
import { Controller, Get, Param } from '@nestjs/common';
import { MappingService } from '@breadstone/archipel-platform-core';
import { USER_TO_RESPONSE } from './mapping-keys';
@Controller('users')
export class UserController {
constructor(
private readonly _userService: UserService,
private readonly _mappingService: MappingService,
) {}
@Get(':id')
public async getUser(@Param('id') id: string): Promise<UserResponse> {
const entity = await this._userService.findById(id);
return this._mappingService.map(USER_TO_RESPONSE, entity);
}
}Cryptography & OTP
BcryptService
Password hashing with bcrypt.
import { BcryptService } from '@breadstone/archipel-platform-core';
@Injectable()
export class AccountService {
constructor(private readonly _bcrypt: BcryptService) {}
public async register(password: string): Promise<void> {
const hash = await this._bcrypt.hash(password);
// Store hash in database
}
public async verifyPassword(input: string, storedHash: string): Promise<boolean> {
return this._bcrypt.compare(input, storedHash);
}
}CryptoService
UUID-based identifier generation.
import { CryptoService } from '@breadstone/archipel-platform-core';
const crypto = new CryptoService();
const token = crypto.getRandomGuid('verify');
// → "verify-a1b2c3d4-e5f6-7890-abcd-ef1234567890"OtpService
TOTP one-time password generation and verification (otplib).
import { OtpService } from '@breadstone/archipel-platform-core';
import type { IOtpService } from '@breadstone/archipel-platform-core';
@Injectable()
export class MfaSetupService {
constructor(@Inject(OTP_SERVICE_TOKEN) private readonly _otp: IOtpService) {}
public setup(userEmail: string): { secret: string; uri: string } {
const secret = this._otp.generateSecret();
const uri = this._otp.generateUri({
issuer: 'MyApp',
label: userEmail,
secret,
});
return { secret, uri };
}
public verify(token: string, secret: string): boolean {
return this._otp.verify(token, secret);
// Window = 1 → accepts current + previous 30s interval
}
}Event System
Type-safe publish/subscribe with RxJS.
Defining Events
import { createEventKey, type IEventMap } from '@breadstone/archipel-platform-core';
interface IOrderEvents extends IEventMap {
'order.created': { orderId: string; userId: string };
'order.shipped': { orderId: string; trackingNumber: string };
'order.cancelled': { orderId: string; reason: string };
}
export const ORDER_CREATED = createEventKey<IOrderEvents, 'order.created'>('order.created');
export const ORDER_SHIPPED = createEventKey<IOrderEvents, 'order.shipped'>('order.shipped');Publishing & Subscribing
import { EventHub } from '@breadstone/archipel-platform-core';
import { ORDER_CREATED, ORDER_SHIPPED } from './events';
@Injectable()
export class OrderService {
constructor(private readonly _events: EventHub) {}
public async createOrder(userId: string): Promise<void> {
const orderId = '...';
// ... create order logic
this._events.publish(ORDER_CREATED, { orderId, userId });
}
}
@Injectable()
export class NotificationService {
constructor(events: EventHub) {
events.subscribe(ORDER_CREATED, (payload) => {
// payload is typed as { orderId: string; userId: string }
this.sendOrderConfirmation(payload.userId, payload.orderId);
});
}
}Content Template Engine
Handlebars-style template compilation with block helpers.
import { ContentTemplateEngine } from '@breadstone/archipel-platform-core';
const engine = new ContentTemplateEngine();
// Simple variable substitution
const result = engine.compileTemplate('Hello {{name}}, welcome to {{app}}!', { name: 'Alice', app: 'MyApp' });
// → "Hello Alice, welcome to MyApp!"
// Block helpers
const list = engine.compileTemplate('{{#each items}}- {{this.name}}: {{this.price}}€\n{{/each}}', {
items: [
{ name: 'Widget', price: 9.99 },
{ name: 'Gadget', price: 19.99 },
],
});
// Conditionals
const conditional = engine.compileTemplate('{{#if isPremium}}Premium Member{{/if}}{{#if !isPremium}}Free Tier{{/if}}', {
isPremium: true,
});Identifier Generation
Pluggable ID generation via IdentifierModule.
import { IdentifierModule, ID_GENERATOR_TOKEN, type IIdGenerator } from '@breadstone/archipel-platform-core';
// Module setup — choose strategy
@Module({
imports: [IdentifierModule.register('CUID')],
})
export class AppModule {}
// Usage in a service
@Injectable()
export class OrderService {
constructor(@Inject(ID_GENERATOR_TOKEN) private readonly _idGen: IIdGenerator) {}
public createOrder(): string {
return this._idGen.generate(); // CUID, UUID, or GUID depending on registration
}
}Utility Services
| Service | Purpose |
|---|---|
DeviceParserService | Parse User-Agent headers into IDeviceInfo (browser, OS, device type) |
HostService | Host configuration (title, logo, features, health endpoints) |
UserAvatarGeneratorService | Generate deterministic avatars from user identifiers |
UsernameGeneratorService | Generate random usernames from word lists |
SseHub | Server-Sent Events hub — bounded to 10,000 event subjects |
maskSensitiveFields() | Recursively masks sensitive keys (password, token, apikey, etc.) in objects for safe logging |
maskSensitive() | Serializes a value to JSON with sensitive keys masked |
HTTP Logger Middleware
The HttpLoggerMiddleware logs incoming requests with automatic masking of sensitive data in query parameters, route params, and request bodies. Sensitive fields (passwords, tokens, API keys, emails, etc.) are replaced with '***' before logging.
Module Summary
| Export | Type | Description |
|---|---|---|
ConfigModule | NestJS Module | Configuration registration and validation (re-export from platform-configuration) |
MappingModule | NestJS Module | Mapping profile registration (re-export from platform-mapping) |
EventModule | NestJS Module | Event pub/sub system |
IdentifierModule | NestJS Module | ID generation strategy |
HostModule | NestJS Module | Host services and templates |
HealthModule | NestJS Module | Health check orchestration (re-export from platform-health) |
ResourceModule | NestJS Module | Resource loading strategies (re-export from platform-resources) |
MappingService | Service | Central object mapping (re-export from platform-mapping) |
ConfigService | Service | Type-safe config access (re-export from platform-configuration) |
BcryptService | Service | Password hashing |
CryptoService | Service | UUID generation |
OtpService | Service | TOTP generation/verification |
EventHub | Service | Event publish/subscribe |
ContentTemplateEngine | Service | Template compilation |
MemoryLayeredCache | Class | In-memory LRU cache |
RedisLayeredCache | Class | Redis-backed cache |
maskSensitiveFields() | Utility | Recursively masks sensitive keys in objects for safe logging |
maskSensitive() | Utility | JSON-serializes values with sensitive keys masked |
HttpLoggerMiddleware | Middleware | Request logging with automatic sensitive data masking |