@fjell/registry 4.4.9 → 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.
- package/dist/Registry.cjs +36 -3
- package/dist/Registry.js +36 -3
- package/dist/RegistryStats.cjs +200 -0
- package/dist/RegistryStats.d.ts +103 -0
- package/dist/RegistryStats.js +196 -0
- package/dist/index.cjs +229 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/types.d.ts +7 -0
- package/docs/README.md +5 -5
- package/docs/index.html +1 -1
- package/docs/memory-data/scaling-10-instances.json +206 -206
- package/docs/memory-data/scaling-100-instances.json +206 -206
- package/docs/memory-data/scaling-1000-instances.json +108 -108
- package/docs/memory-data/scaling-10000-instances.json +49 -49
- package/docs/memory-data/scaling-20-instances.json +208 -208
- package/docs/memory-data/scaling-200-instances.json +206 -206
- package/docs/memory-data/scaling-2000-instances.json +109 -109
- package/docs/memory-data/scaling-50-instances.json +206 -206
- package/docs/memory-data/scaling-500-instances.json +108 -108
- package/docs/memory-data/scaling-5000-instances.json +49 -49
- package/docs/memory-overhead.svg +16 -16
- package/docs/memory.md +122 -122
- package/docs/public/memory.md +111 -111
- package/docs/public/package.json +65 -0
- package/docs/src/App.css +84 -22
- package/docs/src/App.tsx +48 -25
- package/docs/src/index.css +1 -7
- package/docs/timing-range.svg +38 -40
- package/docs/timing.md +122 -122
- package/examples/README.md +19 -0
- package/examples/registry-statistics-example.ts +264 -0
- package/package.json +1 -1
|
@@ -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
|
+
}
|