@fjell/registry 4.4.10 → 4.4.11

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.
@@ -0,0 +1,264 @@
1
+ import { createRegistry, Instance } from '../src';
2
+ import { ServiceClient } from '../src/RegistryStats';
3
+
4
+ export async function runRegistryStatisticsExample() {
5
+ // Example demonstrating Registry statistics tracking with scopes
6
+ console.log('=== Registry Statistics Tracking with Scopes Example ===\n');
7
+
8
+ // Create a registry
9
+ const registry = createRegistry('services');
10
+
11
+ // Create some mock instances for different environments
12
+ const authProdService: Instance<'auth'> = {
13
+ coordinate: { kta: ['auth'], scopes: ['production'] } as any,
14
+ registry: registry as any,
15
+ } as any;
16
+
17
+ const authDevService: Instance<'auth'> = {
18
+ coordinate: { kta: ['auth'], scopes: ['development'] } as any,
19
+ registry: registry as any,
20
+ } as any;
21
+
22
+ const userProdService: Instance<'user'> = {
23
+ coordinate: { kta: ['user'], scopes: ['production', 'sql'] } as any,
24
+ registry: registry as any,
25
+ } as any;
26
+
27
+ const userDevService: Instance<'user'> = {
28
+ coordinate: { kta: ['user'], scopes: ['development', 'nosql'] } as any,
29
+ registry: registry as any,
30
+ } as any;
31
+
32
+ const cacheService: Instance<'cache', 'redis'> = {
33
+ coordinate: { kta: ['cache', 'redis'], scopes: [] } as any,
34
+ registry: registry as any,
35
+ } as any;
36
+
37
+ // Register the services with different scopes
38
+ registry.register(['auth'], authProdService, { scopes: ['production'] });
39
+ registry.register(['auth'], authDevService, { scopes: ['development'] });
40
+ registry.register(['user'], userProdService, { scopes: ['production', 'sql'] });
41
+ registry.register(['user'], userDevService, { scopes: ['development', 'nosql'] });
42
+ registry.register(['cache', 'redis'], cacheService); // No scopes
43
+
44
+ // Check initial statistics
45
+ let stats = registry.getStatistics();
46
+ console.log('Initial statistics:');
47
+ console.log(`Total get calls: ${stats.totalGetCalls}`);
48
+ console.log(`Coordinate call records: ${stats.coordinateCallRecords.length} entries`);
49
+ console.log('');
50
+
51
+ // Use the services with different scope combinations and clients
52
+ console.log('Making service calls with different scopes and clients...');
53
+
54
+ // Application calls (direct calls from the application)
55
+ registry.get(['auth'], { scopes: ['production'], client: 'MyWebApp' });
56
+ registry.get(['auth'], { scopes: ['development'], client: 'MyWebApp' });
57
+ registry.get(['auth'], { scopes: ['production'], client: 'MyWebApp' }); // Same app, second call
58
+
59
+ // Service-to-service calls (simulated by specifying service clients)
60
+ const orderServiceClient: ServiceClient = {
61
+ registryType: 'services',
62
+ coordinate: { kta: ['order'], scopes: ['business'] }
63
+ };
64
+
65
+ const paymentServiceClient: ServiceClient = {
66
+ registryType: 'services',
67
+ coordinate: { kta: ['payment'], scopes: ['stripe'] }
68
+ };
69
+
70
+ registry.get(['user'], { scopes: ['production', 'sql'], client: orderServiceClient });
71
+ registry.get(['user'], { scopes: ['sql', 'production'], client: orderServiceClient }); // Same service, scope order normalized
72
+ registry.get(['user'], { scopes: ['development', 'nosql'], client: paymentServiceClient });
73
+
74
+ // Mixed calls - some with no client specified
75
+ registry.get(['cache', 'redis']); // No client specified
76
+ registry.get(['cache', 'redis'], { client: 'CacheUtility' }); // Application client
77
+
78
+ // More service-to-service calls
79
+ registry.get(['auth'], { scopes: ['production'], client: orderServiceClient });
80
+ registry.get(['cache', 'redis'], { client: paymentServiceClient });
81
+
82
+ // Check updated statistics
83
+ stats = registry.getStatistics();
84
+ console.log('\nStatistics after service calls:');
85
+ console.log(`Total get calls: ${stats.totalGetCalls}`);
86
+ console.log('\nClient Summary:');
87
+ console.log(` Service-to-service calls: ${stats.clientSummary.serviceCalls}`);
88
+ console.log(` Application calls: ${stats.clientSummary.applicationCalls}`);
89
+ console.log(` Unidentified calls: ${stats.clientSummary.unidentifiedCalls}`);
90
+ console.log('\nDetailed coordinate call records:');
91
+
92
+ // Display statistics for each coordinate/scope combination with client breakdown
93
+ stats.coordinateCallRecords.forEach((record, index) => {
94
+ const ktaDisplay = record.kta.join(' → ');
95
+ const scopesDisplay = record.scopes.length > 0 ? record.scopes.join(', ') : 'no scopes';
96
+ console.log(` ${index + 1}. ${ktaDisplay} [${scopesDisplay}]: ${record.count} calls`);
97
+
98
+ // Show client breakdown
99
+ record.clientCalls.forEach((clientCall, clientIndex) => {
100
+ if (typeof clientCall.client === 'string') {
101
+ console.log(` ${clientIndex + 1}a. App "${clientCall.client}": ${clientCall.count} calls`);
102
+ } else if (clientCall.client) {
103
+ const serviceDisplay = clientCall.client.coordinate.kta.join('.');
104
+ const serviceScopeDisplay = clientCall.client.coordinate.scopes.join(', ');
105
+ console.log(` ${clientIndex + 1}b. Service ${clientCall.client.registryType}/${serviceDisplay}[${serviceScopeDisplay}]: ${clientCall.count} calls`);
106
+ } else {
107
+ console.log(` ${clientIndex + 1}c. Unidentified client: ${clientCall.count} calls`);
108
+ }
109
+ });
110
+ });
111
+
112
+ // Analysis by coordinate (aggregating across scopes)
113
+ console.log('\nAggregated analysis by coordinate:');
114
+ const coordinateMap = new Map<string, number>();
115
+
116
+ stats.coordinateCallRecords.forEach(record => {
117
+ const ktaKey = record.kta.join('.');
118
+ coordinateMap.set(ktaKey, (coordinateMap.get(ktaKey) || 0) + record.count);
119
+ });
120
+
121
+ for (const [coordinate, totalCalls] of coordinateMap) {
122
+ console.log(` ${coordinate}: ${totalCalls} total calls`);
123
+ }
124
+
125
+ // Find the most accessed coordinate/scope combination
126
+ let mostAccessedRecord = stats.coordinateCallRecords[0];
127
+ for (const record of stats.coordinateCallRecords) {
128
+ if (record.count > mostAccessedRecord.count) {
129
+ mostAccessedRecord = record;
130
+ }
131
+ }
132
+
133
+ if (mostAccessedRecord) {
134
+ const ktaDisplay = mostAccessedRecord.kta.join(' → ');
135
+ const scopesDisplay = mostAccessedRecord.scopes.length > 0
136
+ ? mostAccessedRecord.scopes.join(', ')
137
+ : 'no scopes';
138
+ console.log(`\nMost accessed: ${ktaDisplay} [${scopesDisplay}] (${mostAccessedRecord.count} calls)`);
139
+ }
140
+
141
+ // Environment-based analysis with client types
142
+ console.log('\nEnvironment-based analysis:');
143
+ let prodCalls = 0;
144
+ let devCalls = 0;
145
+ let noScopeCalls = 0;
146
+ let prodAppCalls = 0;
147
+ let prodServiceCalls = 0;
148
+ let devAppCalls = 0;
149
+ let devServiceCalls = 0;
150
+
151
+ stats.coordinateCallRecords.forEach(record => {
152
+ if (record.scopes.includes('production')) {
153
+ prodCalls += record.count;
154
+ record.clientCalls.forEach(clientCall => {
155
+ if (typeof clientCall.client === 'string') {
156
+ prodAppCalls += clientCall.count;
157
+ } else if (clientCall.client) {
158
+ prodServiceCalls += clientCall.count;
159
+ }
160
+ });
161
+ } else if (record.scopes.includes('development')) {
162
+ devCalls += record.count;
163
+ record.clientCalls.forEach(clientCall => {
164
+ if (typeof clientCall.client === 'string') {
165
+ devAppCalls += clientCall.count;
166
+ } else if (clientCall.client) {
167
+ devServiceCalls += clientCall.count;
168
+ }
169
+ });
170
+ } else if (record.scopes.length === 0) {
171
+ noScopeCalls += record.count;
172
+ }
173
+ });
174
+
175
+ console.log(` Production services: ${prodCalls} calls (${prodAppCalls} from apps, ${prodServiceCalls} from services)`);
176
+ console.log(` Development services: ${devCalls} calls (${devAppCalls} from apps, ${devServiceCalls} from services)`);
177
+ console.log(` Unscoped services: ${noScopeCalls} calls`);
178
+
179
+ // Demonstrate immutability
180
+ console.log('\nTesting immutability of returned statistics...');
181
+ const stats1 = registry.getStatistics();
182
+ const stats2 = registry.getStatistics();
183
+
184
+ console.log(`Are record arrays the same object? ${stats1.coordinateCallRecords === stats2.coordinateCallRecords}`);
185
+ console.log('(Should be false - each call returns a new array to prevent external mutation)');
186
+
187
+ // Try to modify the returned records (this won't affect the internal tracking)
188
+ if (stats1.coordinateCallRecords.length > 0) {
189
+ const originalCount = stats1.coordinateCallRecords[0].count;
190
+ stats1.coordinateCallRecords[0].count = 999;
191
+
192
+ const stats3 = registry.getStatistics();
193
+ console.log(`After trying to modify returned data, original count preserved? ${stats3.coordinateCallRecords[0].count === originalCount}`);
194
+ console.log('(Should be true - internal tracking is protected from external mutation)');
195
+ }
196
+
197
+ console.log('\n=== Advanced Statistics Example Complete ===');
198
+
199
+ // Demonstration of automatic service-to-service tracking via factory Registry
200
+ console.log('\n=== Automatic Service-to-Service Tracking Demo ===');
201
+
202
+ // Create a service that uses other services through the Registry passed to its factory
203
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
204
+ const orderProcessorService = registry.createInstance(['order', 'processor'], ['business'], (coordinate, context) => {
205
+ // The registry passed here is proxied to automatically track this service as the client
206
+
207
+ // When this service calls get() on the registry, it will automatically be tracked
208
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
209
+ const authService = context.registry.get(['auth'], { scopes: ['production'] });
210
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
211
+ const userService = context.registry.get(['user'], { scopes: ['production', 'sql'] });
212
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
213
+ const cacheService = context.registry.get(['cache', 'redis']);
214
+
215
+ console.log('Order processor created and retrieved dependencies automatically tracked!');
216
+
217
+ return {
218
+ coordinate,
219
+ registry: context.registry,
220
+ processOrder: (orderId: string) => {
221
+ console.log(`Processing order ${orderId} with dependencies`);
222
+ return { success: true, orderId };
223
+ }
224
+ };
225
+ });
226
+
227
+ // Get final statistics to show the automatic tracking
228
+ const finalStats = registry.getStatistics();
229
+ console.log(`\nAfter service creation with automatic tracking:`);
230
+ console.log(`Total get calls: ${finalStats.totalGetCalls}`);
231
+ console.log(`Service-to-service calls: ${finalStats.clientSummary.serviceCalls}`);
232
+
233
+ // Show the new automatically tracked calls
234
+ console.log('\nNew automatically tracked service calls:');
235
+ finalStats.coordinateCallRecords.forEach(record => {
236
+ record.clientCalls.forEach(clientCall => {
237
+ if (clientCall.client && typeof clientCall.client !== 'string' &&
238
+ clientCall.client.coordinate.kta.includes('order') &&
239
+ clientCall.client.coordinate.kta.includes('processor')) {
240
+ const ktaDisplay = record.kta.join(' → ');
241
+ const scopesDisplay = record.scopes.length > 0 ? record.scopes.join(', ') : 'no scopes';
242
+ console.log(` ${ktaDisplay} [${scopesDisplay}] called by order.processor service: ${clientCall.count} calls`);
243
+ }
244
+ });
245
+ });
246
+
247
+ console.log('\n=== Automatic Tracking Demo Complete ===');
248
+
249
+ // Return key statistics for testing
250
+ return {
251
+ totalGetCalls: finalStats.totalGetCalls,
252
+ coordinateCallRecords: finalStats.coordinateCallRecords.length,
253
+ mostAccessedCount: mostAccessedRecord ? mostAccessedRecord.count : 0,
254
+ prodCalls,
255
+ devCalls,
256
+ noScopeCalls,
257
+ clientSummary: finalStats.clientSummary
258
+ };
259
+ }
260
+
261
+ // Run the example if this file is executed directly
262
+ if (import.meta.url === `file://${process.argv[1]}`) {
263
+ runRegistryStatisticsExample().catch(console.error);
264
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjell/registry",
3
- "version": "4.4.10",
3
+ "version": "4.4.11",
4
4
  "keywords": [
5
5
  "registry",
6
6
  "fjell"