@fjell/registry
Version:
Dependency injection and service location system for the Fjell ecosystem
265 lines (222 loc) • 11.5 kB
text/typescript
import { createRegistry, Instance } from '../src';
import { ServiceClient } from '../src/RegistryStats';
export async function runRegistryStatisticsExample() {
// Example demonstrating Registry statistics tracking with scopes
console.log('=== Registry Statistics Tracking with Scopes Example ===\n');
// Create a registry
const registry = createRegistry('services');
// Create some mock instances for different environments
const authProdService: Instance<'auth'> = {
coordinate: { kta: ['auth'], scopes: ['production'] } as any,
registry: registry as any,
} as any;
const authDevService: Instance<'auth'> = {
coordinate: { kta: ['auth'], scopes: ['development'] } as any,
registry: registry as any,
} as any;
const userProdService: Instance<'user'> = {
coordinate: { kta: ['user'], scopes: ['production', 'sql'] } as any,
registry: registry as any,
} as any;
const userDevService: Instance<'user'> = {
coordinate: { kta: ['user'], scopes: ['development', 'nosql'] } as any,
registry: registry as any,
} as any;
const cacheService: Instance<'cache', 'redis'> = {
coordinate: { kta: ['cache', 'redis'], scopes: [] } as any,
registry: registry as any,
} as any;
// Register the services with different scopes
registry.register(['auth'], authProdService, { scopes: ['production'] });
registry.register(['auth'], authDevService, { scopes: ['development'] });
registry.register(['user'], userProdService, { scopes: ['production', 'sql'] });
registry.register(['user'], userDevService, { scopes: ['development', 'nosql'] });
registry.register(['cache', 'redis'], cacheService); // No scopes
// Check initial statistics
let stats = registry.getStatistics();
console.log('Initial statistics:');
console.log(`Total get calls: ${stats.totalGetCalls}`);
console.log(`Coordinate call records: ${stats.coordinateCallRecords.length} entries`);
console.log('');
// Use the services with different scope combinations and clients
console.log('Making service calls with different scopes and clients...');
// Application calls (direct calls from the application)
registry.get(['auth'], { scopes: ['production'], client: 'MyWebApp' });
registry.get(['auth'], { scopes: ['development'], client: 'MyWebApp' });
registry.get(['auth'], { scopes: ['production'], client: 'MyWebApp' }); // Same app, second call
// Service-to-service calls (simulated by specifying service clients)
const orderServiceClient: ServiceClient = {
registryType: 'services',
coordinate: { kta: ['order'], scopes: ['business'] }
};
const paymentServiceClient: ServiceClient = {
registryType: 'services',
coordinate: { kta: ['payment'], scopes: ['stripe'] }
};
registry.get(['user'], { scopes: ['production', 'sql'], client: orderServiceClient });
registry.get(['user'], { scopes: ['sql', 'production'], client: orderServiceClient }); // Same service, scope order normalized
registry.get(['user'], { scopes: ['development', 'nosql'], client: paymentServiceClient });
// Mixed calls - some with no client specified
registry.get(['cache', 'redis']); // No client specified
registry.get(['cache', 'redis'], { client: 'CacheUtility' }); // Application client
// More service-to-service calls
registry.get(['auth'], { scopes: ['production'], client: orderServiceClient });
registry.get(['cache', 'redis'], { client: paymentServiceClient });
// Check updated statistics
stats = registry.getStatistics();
console.log('\nStatistics after service calls:');
console.log(`Total get calls: ${stats.totalGetCalls}`);
console.log('\nClient Summary:');
console.log(` Service-to-service calls: ${stats.clientSummary.serviceCalls}`);
console.log(` Application calls: ${stats.clientSummary.applicationCalls}`);
console.log(` Unidentified calls: ${stats.clientSummary.unidentifiedCalls}`);
console.log('\nDetailed coordinate call records:');
// Display statistics for each coordinate/scope combination with client breakdown
stats.coordinateCallRecords.forEach((record, index) => {
const ktaDisplay = record.kta.join(' → ');
const scopesDisplay = record.scopes.length > 0 ? record.scopes.join(', ') : 'no scopes';
console.log(` ${index + 1}. ${ktaDisplay} [${scopesDisplay}]: ${record.count} calls`);
// Show client breakdown
record.clientCalls.forEach((clientCall, clientIndex) => {
if (typeof clientCall.client === 'string') {
console.log(` ${clientIndex + 1}a. App "${clientCall.client}": ${clientCall.count} calls`);
} else if (clientCall.client) {
const serviceDisplay = clientCall.client.coordinate.kta.join('.');
const serviceScopeDisplay = clientCall.client.coordinate.scopes.join(', ');
console.log(` ${clientIndex + 1}b. Service ${clientCall.client.registryType}/${serviceDisplay}[${serviceScopeDisplay}]: ${clientCall.count} calls`);
} else {
console.log(` ${clientIndex + 1}c. Unidentified client: ${clientCall.count} calls`);
}
});
});
// Analysis by coordinate (aggregating across scopes)
console.log('\nAggregated analysis by coordinate:');
const coordinateMap = new Map<string, number>();
stats.coordinateCallRecords.forEach(record => {
const ktaKey = record.kta.join('.');
coordinateMap.set(ktaKey, (coordinateMap.get(ktaKey) || 0) + record.count);
});
for (const [coordinate, totalCalls] of coordinateMap) {
console.log(` ${coordinate}: ${totalCalls} total calls`);
}
// Find the most accessed coordinate/scope combination
let mostAccessedRecord = stats.coordinateCallRecords[0];
for (const record of stats.coordinateCallRecords) {
if (record.count > mostAccessedRecord.count) {
mostAccessedRecord = record;
}
}
if (mostAccessedRecord) {
const ktaDisplay = mostAccessedRecord.kta.join(' → ');
const scopesDisplay = mostAccessedRecord.scopes.length > 0
? mostAccessedRecord.scopes.join(', ')
: 'no scopes';
console.log(`\nMost accessed: ${ktaDisplay} [${scopesDisplay}] (${mostAccessedRecord.count} calls)`);
}
// Environment-based analysis with client types
console.log('\nEnvironment-based analysis:');
let prodCalls = 0;
let devCalls = 0;
let noScopeCalls = 0;
let prodAppCalls = 0;
let prodServiceCalls = 0;
let devAppCalls = 0;
let devServiceCalls = 0;
stats.coordinateCallRecords.forEach(record => {
if (record.scopes.includes('production')) {
prodCalls += record.count;
record.clientCalls.forEach(clientCall => {
if (typeof clientCall.client === 'string') {
prodAppCalls += clientCall.count;
} else if (clientCall.client) {
prodServiceCalls += clientCall.count;
}
});
} else if (record.scopes.includes('development')) {
devCalls += record.count;
record.clientCalls.forEach(clientCall => {
if (typeof clientCall.client === 'string') {
devAppCalls += clientCall.count;
} else if (clientCall.client) {
devServiceCalls += clientCall.count;
}
});
} else if (record.scopes.length === 0) {
noScopeCalls += record.count;
}
});
console.log(` Production services: ${prodCalls} calls (${prodAppCalls} from apps, ${prodServiceCalls} from services)`);
console.log(` Development services: ${devCalls} calls (${devAppCalls} from apps, ${devServiceCalls} from services)`);
console.log(` Unscoped services: ${noScopeCalls} calls`);
// Demonstrate immutability
console.log('\nTesting immutability of returned statistics...');
const stats1 = registry.getStatistics();
const stats2 = registry.getStatistics();
console.log(`Are record arrays the same object? ${stats1.coordinateCallRecords === stats2.coordinateCallRecords}`);
console.log('(Should be false - each call returns a new array to prevent external mutation)');
// Try to modify the returned records (this won't affect the internal tracking)
if (stats1.coordinateCallRecords.length > 0) {
const originalCount = stats1.coordinateCallRecords[0].count;
stats1.coordinateCallRecords[0].count = 999;
const stats3 = registry.getStatistics();
console.log(`After trying to modify returned data, original count preserved? ${stats3.coordinateCallRecords[0].count === originalCount}`);
console.log('(Should be true - internal tracking is protected from external mutation)');
}
console.log('\n=== Advanced Statistics Example Complete ===');
// Demonstration of automatic service-to-service tracking via factory Registry
console.log('\n=== Automatic Service-to-Service Tracking Demo ===');
// Create a service that uses other services through the Registry passed to its factory
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const orderProcessorService = registry.createInstance(['order', 'processor'], ['business'], (coordinate, context) => {
// The registry passed here is proxied to automatically track this service as the client
// When this service calls get() on the registry, it will automatically be tracked
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const authService = context.registry.get(['auth'], { scopes: ['production'] });
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const userService = context.registry.get(['user'], { scopes: ['production', 'sql'] });
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const cacheService = context.registry.get(['cache', 'redis']);
console.log('Order processor created and retrieved dependencies automatically tracked!');
return {
coordinate,
registry: context.registry,
processOrder: (orderId: string) => {
console.log(`Processing order ${orderId} with dependencies`);
return { success: true, orderId };
}
};
});
// Get final statistics to show the automatic tracking
const finalStats = registry.getStatistics();
console.log(`\nAfter service creation with automatic tracking:`);
console.log(`Total get calls: ${finalStats.totalGetCalls}`);
console.log(`Service-to-service calls: ${finalStats.clientSummary.serviceCalls}`);
// Show the new automatically tracked calls
console.log('\nNew automatically tracked service calls:');
finalStats.coordinateCallRecords.forEach(record => {
record.clientCalls.forEach(clientCall => {
if (clientCall.client && typeof clientCall.client !== 'string' &&
clientCall.client.coordinate.kta.includes('order') &&
clientCall.client.coordinate.kta.includes('processor')) {
const ktaDisplay = record.kta.join(' → ');
const scopesDisplay = record.scopes.length > 0 ? record.scopes.join(', ') : 'no scopes';
console.log(` ${ktaDisplay} [${scopesDisplay}] called by order.processor service: ${clientCall.count} calls`);
}
});
});
console.log('\n=== Automatic Tracking Demo Complete ===');
// Return key statistics for testing
return {
totalGetCalls: finalStats.totalGetCalls,
coordinateCallRecords: finalStats.coordinateCallRecords.length,
mostAccessedCount: mostAccessedRecord ? mostAccessedRecord.count : 0,
prodCalls,
devCalls,
noScopeCalls,
clientSummary: finalStats.clientSummary
};
}
// Run the example if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
runRegistryStatisticsExample().catch(console.error);
}