@fjell/registry 4.4.5 → 4.4.6
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 +546 -0
- package/dist/Coordinate.cjs +8 -5
- package/dist/Coordinate.d.ts +1 -1
- package/dist/Coordinate.js +8 -5
- package/dist/Instance.cjs +1 -1
- package/dist/Instance.d.ts +1 -1
- package/dist/Instance.js +1 -1
- package/dist/Registry.cjs +99 -90
- package/dist/Registry.d.ts +3 -42
- package/dist/Registry.js +99 -90
- package/dist/RegistryHub.cjs +78 -0
- package/dist/RegistryHub.d.ts +3 -0
- package/dist/RegistryHub.js +74 -0
- package/dist/errors/CoordinateError.cjs +70 -0
- package/dist/errors/CoordinateError.d.ts +28 -0
- package/dist/errors/CoordinateError.js +63 -0
- package/dist/errors/InstanceError.cjs +101 -0
- package/dist/errors/InstanceError.d.ts +42 -0
- package/dist/errors/InstanceError.js +92 -0
- package/dist/errors/RegistryError.cjs +82 -0
- package/dist/errors/RegistryError.d.ts +31 -0
- package/dist/errors/RegistryError.js +75 -0
- package/dist/errors/RegistryHubError.cjs +92 -0
- package/dist/errors/RegistryHubError.d.ts +39 -0
- package/dist/errors/RegistryHubError.js +84 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/index.cjs +501 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -1
- package/dist/types.d.ts +90 -0
- package/docs/TIMING_NODE_OPTIMIZATION.md +207 -0
- package/docs/TIMING_README.md +170 -0
- package/docs/memory-data/scaling-10-instances.json +526 -0
- package/docs/memory-data/scaling-100-instances.json +526 -0
- package/docs/memory-data/scaling-1000-instances.json +276 -0
- package/docs/memory-data/scaling-10000-instances.json +126 -0
- package/docs/memory-data/scaling-20-instances.json +526 -0
- package/docs/memory-data/scaling-200-instances.json +526 -0
- package/docs/memory-data/scaling-2000-instances.json +276 -0
- package/docs/memory-data/scaling-50-instances.json +526 -0
- package/docs/memory-data/scaling-500-instances.json +276 -0
- package/docs/memory-data/scaling-5000-instances.json +126 -0
- package/docs/memory-overhead.svg +120 -0
- package/docs/memory.md +430 -0
- package/docs/timing-range.svg +174 -0
- package/docs/timing.md +483 -0
- package/examples/README.md +187 -0
- package/examples/multi-level-keys.ts +374 -0
- package/examples/registry-hub-types.ts +437 -0
- package/examples/simple-example.ts +250 -0
- package/package.json +5 -3
package/README.md
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
# Fjell Registry
|
|
2
|
+
|
|
3
|
+
A comprehensive dependency injection and service location system for the Fjell ecosystem. The Registry provides a centralized way to register, scope, and retrieve service instances based on type hierarchies and contextual scopes.
|
|
4
|
+
|
|
5
|
+
## Core Concepts
|
|
6
|
+
|
|
7
|
+
### Instance
|
|
8
|
+
An `Instance` represents any registered service or component in the system. It consists of:
|
|
9
|
+
- **Coordinate**: Defines the service's identity (key types + scopes)
|
|
10
|
+
- **Registry**: Reference to the registry managing this instance
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
interface Instance<S extends string, L1 extends string = never, ...> {
|
|
14
|
+
coordinate: Coordinate<S, L1, L2, L3, L4, L5>;
|
|
15
|
+
registry: Registry;
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Coordinate
|
|
20
|
+
A `Coordinate` uniquely identifies an instance using:
|
|
21
|
+
- **Key Type Array (KTA)**: Hierarchical type identifiers (e.g., `['User', 'Profile']`)
|
|
22
|
+
- **Scopes**: Context qualifiers (e.g., `['firestore']`, `['postgresql']`)
|
|
23
|
+
|
|
24
|
+
This allows multiple implementations of the same service:
|
|
25
|
+
```typescript
|
|
26
|
+
// Same User service, different storage backends
|
|
27
|
+
const firestoreUser = createCoordinate(['User'], ['firestore']);
|
|
28
|
+
const postgresUser = createCoordinate(['User'], ['postgresql']);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Registry
|
|
32
|
+
The central service locator that:
|
|
33
|
+
- **Has a mandatory type identifier** (e.g., 'services', 'data', 'cache')
|
|
34
|
+
- Creates and registers instances atomically (no circular dependencies)
|
|
35
|
+
- Retrieves instances by type and scope
|
|
36
|
+
- Maintains a hierarchical tree of services
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
interface Registry {
|
|
40
|
+
readonly type: string; // Mandatory type identifier
|
|
41
|
+
createInstance: <...>(...) => Instance<...>;
|
|
42
|
+
register: (...) => void; // Deprecated
|
|
43
|
+
get: (...) => Instance | null;
|
|
44
|
+
instanceTree: InstanceTree;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Create registries with their type
|
|
48
|
+
const serviceRegistry = createRegistry('services');
|
|
49
|
+
const dataRegistry = createRegistry('data');
|
|
50
|
+
const cacheRegistry = createRegistry('cache');
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### RegistryHub
|
|
54
|
+
A higher-level registry that manages multiple Registry instances. The RegistryHub serves as a central hub where different registries can be organized automatically using their type property.
|
|
55
|
+
|
|
56
|
+
**Key Features:**
|
|
57
|
+
- **Automatic Registry Organization**: Registers registries using their built-in type property
|
|
58
|
+
- **Unified Access**: Single point of access to instances across all registries
|
|
59
|
+
- **Type Safety**: Maintains type safety while providing cross-registry access
|
|
60
|
+
- **Lifecycle Management**: Register, unregister, and manage multiple registries
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
interface RegistryHub {
|
|
64
|
+
registerRegistry: (registry: Registry) => void; // Uses registry.type automatically
|
|
65
|
+
get: (type: string, kta: string[], options?: { scopes?: string[] }) => Instance | null;
|
|
66
|
+
getRegistry: (type: string) => Registry | null;
|
|
67
|
+
getRegisteredTypes: () => string[];
|
|
68
|
+
unregisterRegistry: (type: string) => boolean;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Architecture:**
|
|
73
|
+
```
|
|
74
|
+
RegistryHub
|
|
75
|
+
├── Registry (type: 'services') → Auth, User, Payment services
|
|
76
|
+
├── Registry (type: 'data') → Repositories, Data layers
|
|
77
|
+
├── Registry (type: 'cache') → Cache implementations
|
|
78
|
+
└── Registry (type: 'integrations') → External APIs, webhooks
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### InstanceFactory
|
|
82
|
+
A factory function that creates instances:
|
|
83
|
+
```typescript
|
|
84
|
+
type InstanceFactory<S extends string, L1, L2, L3, L4, L5> = (
|
|
85
|
+
registry: Registry,
|
|
86
|
+
coordinate: Coordinate<S, L1, L2, L3, L4, L5>
|
|
87
|
+
) => Instance<S, L1, L2, L3, L4, L5>;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Benefits:**
|
|
91
|
+
- ✅ No circular dependencies (factory receives populated registry + coordinate)
|
|
92
|
+
- ✅ Dependency injection friendly (factory can access other services)
|
|
93
|
+
- ✅ Atomic creation and registration
|
|
94
|
+
- ✅ Type-safe instance creation
|
|
95
|
+
|
|
96
|
+
## Architecture
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
createRegistry('services').createInstance(['User'], ['firestore'], factory)
|
|
100
|
+
↓
|
|
101
|
+
1. Creates Coordinate { kta: ['User'], scopes: ['firestore'] }
|
|
102
|
+
2. Calls factory(registry, coordinate)
|
|
103
|
+
3. Validates returned instance
|
|
104
|
+
4. Registers instance automatically
|
|
105
|
+
5. Returns ready-to-use instance
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Registry Structure:**
|
|
109
|
+
```
|
|
110
|
+
Registry (type: 'services')
|
|
111
|
+
├── Instance Tree (by Key Types)
|
|
112
|
+
│ ├── User
|
|
113
|
+
│ │ ├── [ScopedInstance] scope: ['firestore'] ← Created via factory
|
|
114
|
+
│ │ └── [ScopedInstance] scope: ['postgresql'] ← Created via factory
|
|
115
|
+
│ └── User.Profile
|
|
116
|
+
│ ├── [ScopedInstance] scope: ['firestore']
|
|
117
|
+
│ └── [ScopedInstance] scope: ['postgresql']
|
|
118
|
+
└── Atomic Create+Register Logic
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**RegistryHub Structure:**
|
|
122
|
+
```
|
|
123
|
+
RegistryHub
|
|
124
|
+
├── Registry (type: 'services')
|
|
125
|
+
│ ├── User (multiple scopes)
|
|
126
|
+
│ ├── Auth (multiple scopes)
|
|
127
|
+
│ └── Payment (multiple scopes)
|
|
128
|
+
├── Registry (type: 'data')
|
|
129
|
+
│ ├── UserRepository (multiple scopes)
|
|
130
|
+
│ └── OrderRepository (multiple scopes)
|
|
131
|
+
└── Registry (type: 'cache')
|
|
132
|
+
├── UserCache (multiple scopes)
|
|
133
|
+
└── SessionCache (multiple scopes)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Usage Patterns
|
|
137
|
+
|
|
138
|
+
### 1. **Recommended: Registry-Managed Creation** ✅
|
|
139
|
+
```typescript
|
|
140
|
+
const registry = createRegistry('services');
|
|
141
|
+
|
|
142
|
+
// Create and register instances atomically - no circular dependency!
|
|
143
|
+
const userService = registry.createInstance(['User'], ['firestore'], (registry, coordinate) => {
|
|
144
|
+
// Your instance implementation here
|
|
145
|
+
return {
|
|
146
|
+
coordinate,
|
|
147
|
+
registry,
|
|
148
|
+
// ... your service implementation
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Instance is automatically registered and ready to use
|
|
153
|
+
const retrievedService = registry.get(['User'], { scopes: ['firestore'] });
|
|
154
|
+
console.log(userService === retrievedService); // true
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 2. **RegistryHub: Managing Multiple Registries** ✅
|
|
158
|
+
```typescript
|
|
159
|
+
const hub = createRegistryHub();
|
|
160
|
+
|
|
161
|
+
// Create domain-specific registries with their types
|
|
162
|
+
const serviceRegistry = createRegistry('services');
|
|
163
|
+
const dataRegistry = createRegistry('data');
|
|
164
|
+
const cacheRegistry = createRegistry('cache');
|
|
165
|
+
|
|
166
|
+
// Register registries in the hub - no type parameter needed!
|
|
167
|
+
hub.registerRegistry(serviceRegistry); // Uses 'services' from registry.type
|
|
168
|
+
hub.registerRegistry(dataRegistry); // Uses 'data' from registry.type
|
|
169
|
+
hub.registerRegistry(cacheRegistry); // Uses 'cache' from registry.type
|
|
170
|
+
|
|
171
|
+
// Create instances in specific registries
|
|
172
|
+
const authService = serviceRegistry.createInstance(['Auth'], ['jwt'], authFactory);
|
|
173
|
+
const userRepo = dataRegistry.createInstance(['User'], ['firestore'], repoFactory);
|
|
174
|
+
const userCache = cacheRegistry.createInstance(['User'], ['redis'], cacheFactory);
|
|
175
|
+
|
|
176
|
+
// Access instances through the hub - unified interface
|
|
177
|
+
const auth = hub.get('services', ['Auth'], { scopes: ['jwt'] });
|
|
178
|
+
const user = hub.get('data', ['User'], { scopes: ['firestore'] });
|
|
179
|
+
const cache = hub.get('cache', ['User'], { scopes: ['redis'] });
|
|
180
|
+
|
|
181
|
+
// Hub management
|
|
182
|
+
console.log(hub.getRegisteredTypes()); // ['services', 'data', 'cache']
|
|
183
|
+
const specificRegistry = hub.getRegistry('services');
|
|
184
|
+
hub.unregisterRegistry('cache'); // Remove if needed
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 3. Multiple Implementations with Scopes
|
|
188
|
+
```typescript
|
|
189
|
+
const registry = createRegistry('data');
|
|
190
|
+
|
|
191
|
+
// Firestore implementation
|
|
192
|
+
const firestoreUser = registry.createInstance(['User'], ['firestore'], (registry, coordinate) => ({
|
|
193
|
+
coordinate,
|
|
194
|
+
registry,
|
|
195
|
+
save: async (user) => { /* firestore logic */ },
|
|
196
|
+
find: async (query) => { /* firestore logic */ }
|
|
197
|
+
}));
|
|
198
|
+
|
|
199
|
+
// PostgreSQL implementation
|
|
200
|
+
const postgresUser = registry.createInstance(['User'], ['postgresql'], (registry, coordinate) => ({
|
|
201
|
+
coordinate,
|
|
202
|
+
registry,
|
|
203
|
+
save: async (user) => { /* postgresql logic */ },
|
|
204
|
+
find: async (query) => { /* postgresql logic */ }
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
// Context-aware retrieval
|
|
208
|
+
const prodService = registry.get(['User'], { scopes: ['firestore'] });
|
|
209
|
+
const devService = registry.get(['User'], { scopes: ['postgresql'] });
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 4. Hierarchical Services
|
|
213
|
+
```typescript
|
|
214
|
+
const registry = createRegistry('services');
|
|
215
|
+
|
|
216
|
+
// Register nested services
|
|
217
|
+
const profileService = registry.createInstance(['User', 'Profile'], ['firestore'], factoryFunction);
|
|
218
|
+
const settingsService = registry.createInstance(['User', 'Settings'], ['postgresql'], factoryFunction);
|
|
219
|
+
|
|
220
|
+
// Retrieve nested services
|
|
221
|
+
const userProfile = registry.get(['User', 'Profile'], { scopes: ['firestore'] });
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### 5. **Cross-Registry Dependencies with RegistryHub**
|
|
225
|
+
```typescript
|
|
226
|
+
const hub = createRegistryHub();
|
|
227
|
+
const serviceRegistry = createRegistry('services');
|
|
228
|
+
const dataRegistry = createRegistry('data');
|
|
229
|
+
|
|
230
|
+
// Registries automatically use their type property
|
|
231
|
+
hub.registerRegistry(serviceRegistry); // → 'services'
|
|
232
|
+
hub.registerRegistry(dataRegistry); // → 'data'
|
|
233
|
+
|
|
234
|
+
// Service that depends on data layer
|
|
235
|
+
const orderService = serviceRegistry.createInstance(['Order'], ['business'], (registry, coordinate) => {
|
|
236
|
+
// Access data layer through hub
|
|
237
|
+
const userRepo = hub.get('data', ['User'], { scopes: ['firestore'] });
|
|
238
|
+
const orderRepo = hub.get('data', ['Order'], { scopes: ['firestore'] });
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
coordinate,
|
|
242
|
+
registry,
|
|
243
|
+
createOrder: async (orderData) => {
|
|
244
|
+
const user = await userRepo.operations.findOne(orderData.userId);
|
|
245
|
+
return orderRepo.operations.create({ ...orderData, user });
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 6. ~~Legacy Registration~~ (Deprecated)
|
|
252
|
+
```typescript
|
|
253
|
+
// OLD WAY - has circular dependency issue
|
|
254
|
+
const registry = createRegistry('legacy');
|
|
255
|
+
const userService = createInstance(registry, createCoordinate(['User'], ['firestore'])); // ❌ Circular!
|
|
256
|
+
registry.register(['User'], userService, { scopes: ['firestore'] });
|
|
257
|
+
|
|
258
|
+
// Use registry.createInstance() instead! ✅
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 7. Cascade Pattern
|
|
262
|
+
The Registry enables automatic service discovery:
|
|
263
|
+
```typescript
|
|
264
|
+
// System receives an item and automatically finds the right service
|
|
265
|
+
function saveItem(item: Item) {
|
|
266
|
+
const registry = createRegistry('services');
|
|
267
|
+
const service = registry.get(item.getKeyTypes(), {
|
|
268
|
+
scopes: ['cache', 'fast']
|
|
269
|
+
}) || registry.get(item.getKeyTypes(), {
|
|
270
|
+
scopes: ['database']
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
return service.operations.save(item);
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## RegistryHub Patterns
|
|
278
|
+
|
|
279
|
+
### 1. **Domain-Driven Registry Organization**
|
|
280
|
+
```typescript
|
|
281
|
+
const hub = createRegistryHub();
|
|
282
|
+
|
|
283
|
+
// Domain registries with explicit types
|
|
284
|
+
const userDomainRegistry = createRegistry('user-domain');
|
|
285
|
+
const orderDomainRegistry = createRegistry('order-domain');
|
|
286
|
+
const paymentDomainRegistry = createRegistry('payment-domain');
|
|
287
|
+
const notificationDomainRegistry = createRegistry('notification-domain');
|
|
288
|
+
|
|
289
|
+
// Register using their built-in types
|
|
290
|
+
hub.registerRegistry(userDomainRegistry); // → 'user-domain'
|
|
291
|
+
hub.registerRegistry(orderDomainRegistry); // → 'order-domain'
|
|
292
|
+
hub.registerRegistry(paymentDomainRegistry); // → 'payment-domain'
|
|
293
|
+
hub.registerRegistry(notificationDomainRegistry); // → 'notification-domain'
|
|
294
|
+
|
|
295
|
+
// Each domain manages its own services
|
|
296
|
+
const userService = hub.get('user-domain', ['User'], { scopes: ['api'] });
|
|
297
|
+
const orderService = hub.get('order-domain', ['Order'], { scopes: ['business'] });
|
|
298
|
+
const paymentGateway = hub.get('payment-domain', ['Gateway'], { scopes: ['stripe'] });
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### 2. **Environment-Based Registry Management**
|
|
302
|
+
```typescript
|
|
303
|
+
const hub = createRegistryHub();
|
|
304
|
+
|
|
305
|
+
if (process.env.NODE_ENV === 'production') {
|
|
306
|
+
const prodDataRegistry = createRegistry('data');
|
|
307
|
+
const redisRegistry = createRegistry('cache');
|
|
308
|
+
hub.registerRegistry(prodDataRegistry);
|
|
309
|
+
hub.registerRegistry(redisRegistry);
|
|
310
|
+
} else {
|
|
311
|
+
const devDataRegistry = createRegistry('data');
|
|
312
|
+
const memoryRegistry = createRegistry('cache');
|
|
313
|
+
hub.registerRegistry(devDataRegistry);
|
|
314
|
+
hub.registerRegistry(memoryRegistry);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Same code works across environments
|
|
318
|
+
const userRepo = hub.get('data', ['User']);
|
|
319
|
+
const userCache = hub.get('cache', ['User']);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### 3. **Module-Based Registry Organization**
|
|
323
|
+
```typescript
|
|
324
|
+
// Each module/library provides its own typed registry
|
|
325
|
+
import { createUserModuleRegistry } from '@myapp/user-module';
|
|
326
|
+
import { createOrderModuleRegistry } from '@myapp/order-module';
|
|
327
|
+
import { createPaymentModuleRegistry } from '@myapp/payment-module';
|
|
328
|
+
|
|
329
|
+
const hub = createRegistryHub();
|
|
330
|
+
|
|
331
|
+
// These functions return registries with proper types
|
|
332
|
+
const userRegistry = createUserModuleRegistry(); // Registry with type 'users'
|
|
333
|
+
const orderRegistry = createOrderModuleRegistry(); // Registry with type 'orders'
|
|
334
|
+
const paymentRegistry = createPaymentModuleRegistry(); // Registry with type 'payments'
|
|
335
|
+
|
|
336
|
+
// Auto-register using their types
|
|
337
|
+
hub.registerRegistry(userRegistry); // → 'users'
|
|
338
|
+
hub.registerRegistry(orderRegistry); // → 'orders'
|
|
339
|
+
hub.registerRegistry(paymentRegistry); // → 'payments'
|
|
340
|
+
|
|
341
|
+
// Cross-module integration
|
|
342
|
+
const orderService = hub.get('orders', ['OrderService']);
|
|
343
|
+
const paymentProcessor = hub.get('payments', ['Processor'], { scopes: ['stripe'] });
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Library Integration Patterns
|
|
347
|
+
|
|
348
|
+
### fjell-lib Integration
|
|
349
|
+
```typescript
|
|
350
|
+
// In fjell-lib
|
|
351
|
+
export const createServiceRegistry = () => createRegistry('fjell-lib');
|
|
352
|
+
|
|
353
|
+
// In applications
|
|
354
|
+
import { createServiceRegistry } from '@fjell/lib';
|
|
355
|
+
const hub = createRegistryHub();
|
|
356
|
+
hub.registerRegistry(createServiceRegistry()); // → 'fjell-lib'
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### fjell-cache Integration
|
|
360
|
+
```typescript
|
|
361
|
+
// In fjell-cache
|
|
362
|
+
export const createCacheRegistry = () => createRegistry('fjell-cache');
|
|
363
|
+
|
|
364
|
+
// In applications
|
|
365
|
+
import { createCacheRegistry } from '@fjell/cache';
|
|
366
|
+
const hub = createRegistryHub();
|
|
367
|
+
hub.registerRegistry(createCacheRegistry()); // → 'fjell-cache'
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### fjell-client-api Integration
|
|
371
|
+
```typescript
|
|
372
|
+
// In fjell-client-api
|
|
373
|
+
export const createClientApiRegistry = () => createRegistry('fjell-client-api');
|
|
374
|
+
|
|
375
|
+
// In applications
|
|
376
|
+
import { createClientApiRegistry } from '@fjell/client-api';
|
|
377
|
+
const hub = createRegistryHub();
|
|
378
|
+
hub.registerRegistry(createClientApiRegistry()); // → 'fjell-client-api'
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Design Benefits
|
|
382
|
+
|
|
383
|
+
1. **Unified Service Location**: Single pattern across all Fjell libraries
|
|
384
|
+
2. **Multiple Implementations**: Support Firestore, PostgreSQL, etc. for same types
|
|
385
|
+
3. **Contextual Scoping**: Environment-aware service selection
|
|
386
|
+
4. **Hierarchical Organization**: Natural type hierarchy support
|
|
387
|
+
5. **Dependency Injection**: Clean separation of configuration and usage
|
|
388
|
+
6. **Self-Documenting Registries**: Registry type is built-in, no external tracking needed
|
|
389
|
+
7. **Cross-Registry Access**: Unified interface for accessing instances across multiple registries
|
|
390
|
+
8. **Error Prevention**: Impossible to register a registry under the wrong type
|
|
391
|
+
|
|
392
|
+
## Migration Strategy
|
|
393
|
+
|
|
394
|
+
### Phase 1: Core Libraries
|
|
395
|
+
- ✅ fjell-registry (base implementation + RegistryHub with typed registries)
|
|
396
|
+
- 🔄 fjell-lib (create registry with type 'fjell-lib')
|
|
397
|
+
- 🔄 fjell-lib-sequelize (create registry with type 'fjell-lib-sequelize')
|
|
398
|
+
- 🔄 fjell-lib-firestore (create registry with type 'fjell-lib-firestore')
|
|
399
|
+
|
|
400
|
+
### Phase 2: Service Libraries
|
|
401
|
+
- 🔄 fjell-cache (create registry with type 'fjell-cache')
|
|
402
|
+
- 🔄 fjell-client-api (create registry with type 'fjell-client-api')
|
|
403
|
+
|
|
404
|
+
### Phase 3: UI Libraries (Future)
|
|
405
|
+
- fjell-express-router (create registry with type 'fjell-express-router')
|
|
406
|
+
- fjell-providers (create registry with type 'fjell-providers')
|
|
407
|
+
|
|
408
|
+
### Phase 4: RegistryHub Integration
|
|
409
|
+
- Applications use RegistryHub to organize all typed registries
|
|
410
|
+
- Libraries export factory functions that create properly typed registries
|
|
411
|
+
- Cross-module dependency management through hub
|
|
412
|
+
|
|
413
|
+
## Configuration Examples
|
|
414
|
+
|
|
415
|
+
### Multi-Database Setup with RegistryHub
|
|
416
|
+
```typescript
|
|
417
|
+
const hub = createRegistryHub();
|
|
418
|
+
|
|
419
|
+
// Create typed registries
|
|
420
|
+
const servicesRegistry = createRegistry('services');
|
|
421
|
+
const firestoreDataRegistry = createRegistry('firestore-data');
|
|
422
|
+
const postgresDataRegistry = createRegistry('postgres-data');
|
|
423
|
+
|
|
424
|
+
// Register Firestore services
|
|
425
|
+
firestoreDataRegistry.createInstance(['User'], ['production'], firestoreUserFactory);
|
|
426
|
+
firestoreDataRegistry.createInstance(['Order'], ['production'], firestoreOrderFactory);
|
|
427
|
+
|
|
428
|
+
// Register PostgreSQL services
|
|
429
|
+
postgresDataRegistry.createInstance(['User'], ['development'], postgresUserFactory);
|
|
430
|
+
postgresDataRegistry.createInstance(['Analytics'], [], postgresAnalyticsFactory);
|
|
431
|
+
|
|
432
|
+
// Register business services
|
|
433
|
+
servicesRegistry.createInstance(['UserService'], ['business'], userServiceFactory);
|
|
434
|
+
servicesRegistry.createInstance(['OrderService'], ['business'], orderServiceFactory);
|
|
435
|
+
|
|
436
|
+
// Register all typed registries
|
|
437
|
+
hub.registerRegistry(servicesRegistry); // → 'services'
|
|
438
|
+
hub.registerRegistry(firestoreDataRegistry); // → 'firestore-data'
|
|
439
|
+
hub.registerRegistry(postgresDataRegistry); // → 'postgres-data'
|
|
440
|
+
|
|
441
|
+
// Context-aware retrieval through hub
|
|
442
|
+
const userService = hub.get('services', ['UserService']);
|
|
443
|
+
const userRepo = hub.get('firestore-data', ['User'], { scopes: ['production'] });
|
|
444
|
+
const analyticsRepo = hub.get('postgres-data', ['Analytics']);
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Service Composition with RegistryHub
|
|
448
|
+
```typescript
|
|
449
|
+
const hub = createRegistryHub();
|
|
450
|
+
|
|
451
|
+
// Create and register typed registries
|
|
452
|
+
const dataRegistry = createRegistry('data');
|
|
453
|
+
const servicesRegistry = createRegistry('services');
|
|
454
|
+
const integrationsRegistry = createRegistry('integrations');
|
|
455
|
+
|
|
456
|
+
hub.registerRegistry(dataRegistry); // → 'data'
|
|
457
|
+
hub.registerRegistry(servicesRegistry); // → 'services'
|
|
458
|
+
hub.registerRegistry(integrationsRegistry); // → 'integrations'
|
|
459
|
+
|
|
460
|
+
// Complex service with cross-registry dependencies
|
|
461
|
+
const orderService = servicesRegistry.createInstance(['Order'], ['business'], (registry, coordinate) => {
|
|
462
|
+
return {
|
|
463
|
+
coordinate,
|
|
464
|
+
registry,
|
|
465
|
+
createOrder: async (orderData) => {
|
|
466
|
+
const userRepo = hub.get('data', ['User']);
|
|
467
|
+
const paymentService = hub.get('integrations', ['Payment']);
|
|
468
|
+
const inventoryService = hub.get('services', ['Inventory']);
|
|
469
|
+
|
|
470
|
+
// Business logic using services from different typed registries
|
|
471
|
+
const user = await userRepo.operations.findOne(orderData.userId);
|
|
472
|
+
const payment = await paymentService.processPayment(orderData.payment);
|
|
473
|
+
const inventory = await inventoryService.reserveItems(orderData.items);
|
|
474
|
+
|
|
475
|
+
return { user, payment, inventory };
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
This architecture provides the foundation for a scalable, maintainable service ecosystem where components can be developed independently but work together seamlessly. The RegistryHub with typed registries extends this capability by enabling better organization and automatic management of multiple registries across different domains or modules.
|
|
482
|
+
|
|
483
|
+
## Memory Profiling and Performance
|
|
484
|
+
|
|
485
|
+
### Memory Overhead Testing
|
|
486
|
+
|
|
487
|
+
The Fjell Registry includes comprehensive memory profiling to ensure optimal performance in production environments. The memory tests measure the actual memory footprint of core infrastructure components and provide automated documentation.
|
|
488
|
+
|
|
489
|
+
#### Running Memory Tests
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
# Run memory tests and generate documentation
|
|
493
|
+
pnpm run test:memory
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### Memory Test Coverage
|
|
497
|
+
|
|
498
|
+
The memory tests analyze:
|
|
499
|
+
|
|
500
|
+
- **Registry Infrastructure**: Memory overhead of creating Registry and RegistryHub instances
|
|
501
|
+
- **Instance Creation**: Memory cost per instance including coordinates and storage
|
|
502
|
+
- **Tree Structure**: Memory efficiency of the instance tree data structures
|
|
503
|
+
- **Scoped Instances**: Additional memory overhead for scoped instance management
|
|
504
|
+
- **Scaling Behavior**: Memory usage from 10 to 100,000 instances with detailed scaling analysis
|
|
505
|
+
|
|
506
|
+
#### Comprehensive Scaling Tests
|
|
507
|
+
|
|
508
|
+
Memory tests include scaling analysis across multiple instance counts:
|
|
509
|
+
|
|
510
|
+
- **Small Scale**: 10, 20, 50, 100, 200 instances
|
|
511
|
+
- **Medium Scale**: 500, 1,000, 2,000, 5,000 instances
|
|
512
|
+
- **Large Scale**: 10,000, 20,000, 50,000, 100,000 instances
|
|
513
|
+
|
|
514
|
+
#### Memory Constraints
|
|
515
|
+
|
|
516
|
+
Current memory constraints ensure optimal performance:
|
|
517
|
+
|
|
518
|
+
- **Registry Creation**: ≤ 83 kB per registry
|
|
519
|
+
- **Instance Creation**: ≤ 2.4 kB per instance
|
|
520
|
+
- **Coordinate Objects**: ≤ 1.5 kB per coordinate
|
|
521
|
+
- **Tree Nodes**: ≤ 3.9 kB per tree node
|
|
522
|
+
- **Complex Instances**: ≤ 4.9 kB for multi-level instances
|
|
523
|
+
|
|
524
|
+
#### Generated Documentation
|
|
525
|
+
|
|
526
|
+
Memory tests automatically generate `./docs/memory.md` with:
|
|
527
|
+
|
|
528
|
+
- Detailed memory usage analysis with human-readable units (kB, MB)
|
|
529
|
+
- Comprehensive scaling analysis from 10 to 100,000 instances
|
|
530
|
+
- Performance characteristics and scaling efficiency metrics
|
|
531
|
+
- Memory growth patterns and per-instance consistency analysis
|
|
532
|
+
- Efficiency metrics and optimization recommendations
|
|
533
|
+
- Troubleshooting guide for memory issues
|
|
534
|
+
- Constraint definitions and thresholds
|
|
535
|
+
|
|
536
|
+
#### Key Performance Characteristics
|
|
537
|
+
|
|
538
|
+
- **Linear Scaling**: Memory usage scales predictably from 10 to 100,000 instances
|
|
539
|
+
- **Consistent Per-Instance Cost**: ~1.9-2.4 kB per instance across all scales
|
|
540
|
+
- **Efficient Storage**: Optimized tree structures minimize overhead
|
|
541
|
+
- **High-Volume Performance**: 100,000 instances use only ~189 MB (excellent memory efficiency)
|
|
542
|
+
- **Scope Efficiency**: Minimal additional memory for scoped instances
|
|
543
|
+
- **Fast Creation**: Instance creation remains fast even at large scales
|
|
544
|
+
- **Human-Readable Reporting**: All memory measurements displayed in kB/MB format
|
|
545
|
+
|
|
546
|
+
Use these metrics to monitor memory usage in production and ensure the registry infrastructure remains performant as your application scales from prototype to enterprise levels.
|
package/dist/Coordinate.cjs
CHANGED
|
@@ -5,25 +5,28 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
5
5
|
const logger$1 = require('./logger.cjs');
|
|
6
6
|
|
|
7
7
|
const logger = logger$1.default.get("Coordinate");
|
|
8
|
-
const createCoordinate = (kta, scopes)=>{
|
|
8
|
+
const createCoordinate = (kta, scopes = [])=>{
|
|
9
|
+
const ktArray = Array.isArray(kta) ? kta : [
|
|
10
|
+
kta
|
|
11
|
+
];
|
|
9
12
|
const toString = ()=>{
|
|
10
13
|
logger.debug("toString", {
|
|
11
14
|
kta,
|
|
12
15
|
scopes
|
|
13
16
|
});
|
|
14
|
-
return `${
|
|
17
|
+
return `${ktArray.join(', ')} - ${scopes.join(', ')}`;
|
|
15
18
|
};
|
|
16
19
|
logger.debug("createCoordinate", {
|
|
17
|
-
kta,
|
|
20
|
+
kta: ktArray,
|
|
18
21
|
scopes,
|
|
19
22
|
toString
|
|
20
23
|
});
|
|
21
24
|
return {
|
|
22
|
-
kta,
|
|
25
|
+
kta: ktArray,
|
|
23
26
|
scopes,
|
|
24
27
|
toString
|
|
25
28
|
};
|
|
26
29
|
};
|
|
27
30
|
|
|
28
31
|
exports.createCoordinate = createCoordinate;
|
|
29
|
-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,
|
|
32
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29vcmRpbmF0ZS5janMiLCJzb3VyY2VzIjpbXSwic291cmNlc0NvbnRlbnQiOltdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OzsifQ==
|
package/dist/Coordinate.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export interface Coordinate<S extends string, L1 extends string = never, L2 exte
|
|
|
4
4
|
scopes: string[];
|
|
5
5
|
toString: () => string;
|
|
6
6
|
}
|
|
7
|
-
export declare const createCoordinate: <S extends string, L1 extends string = never, L2 extends string = never, L3 extends string = never, L4 extends string = never, L5 extends string = never>(kta: ItemTypeArray<S, L1, L2, L3, L4, L5
|
|
7
|
+
export declare const createCoordinate: <S extends string, L1 extends string = never, L2 extends string = never, L3 extends string = never, L4 extends string = never, L5 extends string = never>(kta: ItemTypeArray<S, L1, L2, L3, L4, L5> | S, scopes?: string[]) => Coordinate<S, L1, L2, L3, L4, L5>;
|
package/dist/Coordinate.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import LibLogger from './logger.js';
|
|
2
2
|
|
|
3
3
|
const logger = LibLogger.get("Coordinate");
|
|
4
|
-
const createCoordinate = (kta, scopes)=>{
|
|
4
|
+
const createCoordinate = (kta, scopes = [])=>{
|
|
5
|
+
const ktArray = Array.isArray(kta) ? kta : [
|
|
6
|
+
kta
|
|
7
|
+
];
|
|
5
8
|
const toString = ()=>{
|
|
6
9
|
logger.debug("toString", {
|
|
7
10
|
kta,
|
|
8
11
|
scopes
|
|
9
12
|
});
|
|
10
|
-
return `${
|
|
13
|
+
return `${ktArray.join(', ')} - ${scopes.join(', ')}`;
|
|
11
14
|
};
|
|
12
15
|
logger.debug("createCoordinate", {
|
|
13
|
-
kta,
|
|
16
|
+
kta: ktArray,
|
|
14
17
|
scopes,
|
|
15
18
|
toString
|
|
16
19
|
});
|
|
17
20
|
return {
|
|
18
|
-
kta,
|
|
21
|
+
kta: ktArray,
|
|
19
22
|
scopes,
|
|
20
23
|
toString
|
|
21
24
|
};
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
export { createCoordinate };
|
|
25
|
-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,
|
|
28
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29vcmRpbmF0ZS5qcyIsInNvdXJjZXMiOltdLCJzb3VyY2VzQ29udGVudCI6W10sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7In0=
|
package/dist/Instance.cjs
CHANGED
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
5
5
|
const logger$1 = require('./logger.cjs');
|
|
6
6
|
|
|
7
7
|
const logger = logger$1.default.get("Instance");
|
|
8
|
-
const createInstance = (
|
|
8
|
+
const createInstance = (registry, coordinate)=>{
|
|
9
9
|
logger.debug("createInstance", {
|
|
10
10
|
coordinate,
|
|
11
11
|
registry
|
package/dist/Instance.d.ts
CHANGED
|
@@ -21,5 +21,5 @@ export interface Instance<S extends string, L1 extends string = never, L2 extend
|
|
|
21
21
|
/** The registry object that manages the registration and lookup of model instances */
|
|
22
22
|
registry: Registry;
|
|
23
23
|
}
|
|
24
|
-
export declare const createInstance: <S extends string, L1 extends string = never, L2 extends string = never, L3 extends string = never, L4 extends string = never, L5 extends string = never>(coordinate: Coordinate<S, L1, L2, L3, L4, L5
|
|
24
|
+
export declare const createInstance: <S extends string, L1 extends string = never, L2 extends string = never, L3 extends string = never, L4 extends string = never, L5 extends string = never>(registry: Registry, coordinate: Coordinate<S, L1, L2, L3, L4, L5>) => Instance<S, L1, L2, L3, L4, L5>;
|
|
25
25
|
export declare const isInstance: (instance: any) => instance is Instance<any, any, any, any, any, any>;
|
package/dist/Instance.js
CHANGED