@asaidimu/utils-artifacts 1.0.0 → 2.0.1
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 +103 -139
- package/index.d.mts +118 -274
- package/index.d.ts +118 -274
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@asaidimu/utils-artifacts`
|
|
2
2
|
|
|
3
3
|
A powerful, reactive dependency injection container for managing application artifacts and their lifecycles. It provides a robust framework for building modular, maintainable, and highly responsive applications by decoupling components and managing their state-driven dependencies.
|
|
4
4
|
|
|
@@ -16,8 +16,7 @@ A powerful, reactive dependency injection container for managing application art
|
|
|
16
16
|
* [Artifact-to-Artifact Dependencies](#artifact-to-artifact-dependencies)
|
|
17
17
|
* [Lifecycle Hooks: `onCleanup` & `onDispose`](#lifecycle-hooks-oncleanup--ondispose)
|
|
18
18
|
* [Watching Artifacts for Changes](#watching-artifacts-for-changes)
|
|
19
|
-
* [
|
|
20
|
-
* [Hierarchical Containers](#hierarchical-containers)
|
|
19
|
+
* [Updating Intermediate Values with `ctx.update`](#updating-intermediate-values-with-ctxupdate)
|
|
21
20
|
* [Debugging Information](#debugging-information)
|
|
22
21
|
* [Project Architecture](#-project-architecture)
|
|
23
22
|
* [Development & Contributing](#-development--contributing)
|
|
@@ -25,7 +24,7 @@ A powerful, reactive dependency injection container for managing application art
|
|
|
25
24
|
|
|
26
25
|
---
|
|
27
26
|
|
|
28
|
-
⚠️ **Beta Warning**
|
|
27
|
+
⚠️ **Beta Warning**
|
|
29
28
|
This package is currently in **beta**. The API is subject to rapid changes and should not be considered stable. Breaking changes may occur frequently without notice as we iterate and improve. We’ll update this warning once the package reaches a stable release. Use at your own risk and share feedback or report issues to help us improve!
|
|
30
29
|
|
|
31
30
|
## 📚 Overview & Features
|
|
@@ -38,15 +37,14 @@ This package is ideal for building complex, event-driven systems, front-end appl
|
|
|
38
37
|
|
|
39
38
|
* **Reactive Dependency Injection**: Automatically invalidates and rebuilds artifacts when their declared dependencies (other artifacts or global state slices) change.
|
|
40
39
|
* **Singleton & Transient Scopes**: Control whether an artifact is instantiated once and reused (`Singleton`) or created fresh on every request (`Transient`).
|
|
41
|
-
* **Hierarchical Containers**: Create child containers that can inherit and resolve artifacts from their parents, enabling scoped dependency trees.
|
|
42
40
|
* **State-Driven Invalidation with Debouncing**: Integrates with a `DataStore` to track state dependencies, triggering artifact rebuilds only when relevant state paths change, with configurable debouncing.
|
|
43
41
|
* **Fine-grained Dependency Tracking**: Explicitly declare dependencies on other artifacts using `ctx.resolve()` and on state slices using `ctx.select()`.
|
|
44
42
|
* **Robust Error Handling**: Built-in detection and specific error types for circular dependencies, missing artifacts, and illegal operations, distinguishing between `system` (structural) and `external` (runtime) errors.
|
|
45
43
|
* **Lifecycle Hooks**: Register `onCleanup` callbacks for instance-specific resource release and `onDispose` for final resource teardown.
|
|
46
44
|
* **Asynchronous Artifact Resolution**: Supports `async`/`await` in artifact factories, with configurable retries for external failures and timeouts.
|
|
47
|
-
* **`
|
|
48
|
-
* **Live Updates via `
|
|
49
|
-
* **Comprehensive Debugging Info**: `
|
|
45
|
+
* **`ArtifactObserver` for Reactive Observation**: Observe changes to an artifact's resolved value over time, subscribing to updates without repeatedly resolving.
|
|
46
|
+
* **Live Updates via `ctx.update()`**: Singleton artifacts can proactively update their value from within their factory, immediately notifying dependents and watchers.
|
|
47
|
+
* **Comprehensive Debugging Info**: `debugInfo()` provides a snapshot of the container's state, including artifact statuses, dependencies, and dependents.
|
|
50
48
|
|
|
51
49
|
---
|
|
52
50
|
|
|
@@ -80,7 +78,7 @@ To use the `ArtifactContainer`, you need to initialize it with an object that pr
|
|
|
80
78
|
If you're using `@asaidimu/utils-store` as your `DataStore`:
|
|
81
79
|
|
|
82
80
|
```typescript
|
|
83
|
-
import { ArtifactContainer,
|
|
81
|
+
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
84
82
|
import { ReactiveDataStore } from '@asaidimu/utils-store'; // Assuming this provides DataStore interface
|
|
85
83
|
|
|
86
84
|
interface AppState {
|
|
@@ -111,8 +109,8 @@ You can verify the installation by trying to import and instantiate the `Artifac
|
|
|
111
109
|
import { ArtifactContainer } from '@asaidimu/utils-artifacts';
|
|
112
110
|
// Assuming a mock or real DataStore is available
|
|
113
111
|
const mockStore = {
|
|
114
|
-
get: () => ({}),
|
|
115
|
-
watch: () => () => {},
|
|
112
|
+
get: () => ({}), // Returns an empty object for state
|
|
113
|
+
watch: () => () => {}, // Returns a no-op unsubscribe function
|
|
116
114
|
};
|
|
117
115
|
const container = new ArtifactContainer(mockStore);
|
|
118
116
|
console.log('ArtifactContainer initialized successfully!');
|
|
@@ -132,7 +130,7 @@ Artifacts can be registered with different scopes, controlling their lifecycle.
|
|
|
132
130
|
A singleton artifact is created only once. Subsequent `resolve` calls return the same instance.
|
|
133
131
|
|
|
134
132
|
```typescript
|
|
135
|
-
import { ArtifactContainer,
|
|
133
|
+
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
136
134
|
import { ReactiveDataStore } from '@asaidimu/utils-store';
|
|
137
135
|
|
|
138
136
|
const store = new ReactiveDataStore({});
|
|
@@ -150,7 +148,7 @@ container.register({
|
|
|
150
148
|
version: '1.0.0'
|
|
151
149
|
};
|
|
152
150
|
},
|
|
153
|
-
scope:
|
|
151
|
+
scope: ArtifactScopes.Singleton,
|
|
154
152
|
lazy: false, // Build immediately on registration
|
|
155
153
|
});
|
|
156
154
|
|
|
@@ -179,7 +177,7 @@ runSingletonExample();
|
|
|
179
177
|
A transient artifact creates a new instance every time it is resolved.
|
|
180
178
|
|
|
181
179
|
```typescript
|
|
182
|
-
import { ArtifactContainer,
|
|
180
|
+
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
183
181
|
import { ReactiveDataStore } from '@asaidimu/utils-store';
|
|
184
182
|
|
|
185
183
|
const store = new ReactiveDataStore({});
|
|
@@ -194,7 +192,7 @@ container.register({
|
|
|
194
192
|
timestamp: Date.now()
|
|
195
193
|
};
|
|
196
194
|
},
|
|
197
|
-
scope:
|
|
195
|
+
scope: ArtifactScopes.Transient,
|
|
198
196
|
});
|
|
199
197
|
|
|
200
198
|
async function runTransientExample() {
|
|
@@ -222,7 +220,7 @@ runTransientExample();
|
|
|
222
220
|
Artifacts can react to changes in your global `DataStore` state by using `ctx.select()` within their factory's `use` block.
|
|
223
221
|
|
|
224
222
|
```typescript
|
|
225
|
-
import { ArtifactContainer,
|
|
223
|
+
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
226
224
|
import { ReactiveDataStore } from '@asaidimu/utils-store';
|
|
227
225
|
|
|
228
226
|
interface AppState {
|
|
@@ -247,7 +245,7 @@ container.register({
|
|
|
247
245
|
console.log(`UI Config Build ${uiComponentBuilds}: Theme=${theme}, Notifications=${notifications}`);
|
|
248
246
|
return { theme, notifications };
|
|
249
247
|
},
|
|
250
|
-
scope:
|
|
248
|
+
scope: ArtifactScopes.Singleton,
|
|
251
249
|
});
|
|
252
250
|
|
|
253
251
|
async function runReactiveStateExample() {
|
|
@@ -271,7 +269,7 @@ async function runReactiveStateExample() {
|
|
|
271
269
|
|
|
272
270
|
// Resolve again
|
|
273
271
|
uiConfig = await container.resolve('uiConfiguration');
|
|
274
|
-
console.assert(uiConfig.instance?.
|
|
272
|
+
console.assert(uiConfig.instance?.notifications === false, 'Notifications should be false');
|
|
275
273
|
console.assert(uiComponentBuilds === 3, 'Build count should increment after another state change');
|
|
276
274
|
|
|
277
275
|
// Output:
|
|
@@ -292,7 +290,7 @@ runReactiveStateExample();
|
|
|
292
290
|
Artifacts can depend on other artifacts using `ctx.resolve()` within their factory's `use` block. Changes to a dependency will invalidate the dependent artifact.
|
|
293
291
|
|
|
294
292
|
```typescript
|
|
295
|
-
import { ArtifactContainer,
|
|
293
|
+
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
296
294
|
import { ReactiveDataStore } from '@asaidimu/utils-store';
|
|
297
295
|
|
|
298
296
|
const store = new ReactiveDataStore({});
|
|
@@ -307,7 +305,7 @@ container.register({
|
|
|
307
305
|
await new Promise(res => setTimeout(res, 50)); // Simulate async work
|
|
308
306
|
return { client: 'PostgreSQL', status: 'connected' };
|
|
309
307
|
},
|
|
310
|
-
scope:
|
|
308
|
+
scope: ArtifactScopes.Singleton,
|
|
311
309
|
});
|
|
312
310
|
|
|
313
311
|
let userRepositoryBuilds = 0;
|
|
@@ -322,7 +320,7 @@ container.register({
|
|
|
322
320
|
findUser: (id: string) => `User ${id} from ${db.instance?.client}`
|
|
323
321
|
};
|
|
324
322
|
},
|
|
325
|
-
scope:
|
|
323
|
+
scope: ArtifactScopes.Singleton,
|
|
326
324
|
});
|
|
327
325
|
|
|
328
326
|
async function runArtifactDependencyExample() {
|
|
@@ -365,7 +363,7 @@ Artifacts can register cleanup functions to manage resources.
|
|
|
365
363
|
* `onDispose`: Executed *only* when the artifact is unregistered or the container itself is disposed. Ideal for final, permanent resource release.
|
|
366
364
|
|
|
367
365
|
```typescript
|
|
368
|
-
import { ArtifactContainer,
|
|
366
|
+
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
369
367
|
import { ReactiveDataStore } from '@asaidimu/utils-store';
|
|
370
368
|
|
|
371
369
|
const store = new ReactiveDataStore({});
|
|
@@ -380,7 +378,7 @@ container.register({
|
|
|
380
378
|
let count = 0;
|
|
381
379
|
currentInterval = setInterval(() => {
|
|
382
380
|
count++;
|
|
383
|
-
console.log(`Tick: ${count}`);
|
|
381
|
+
// console.log(`Tick: ${count}`); // Uncomment to see live ticks
|
|
384
382
|
}, 1000);
|
|
385
383
|
|
|
386
384
|
onCleanup(() => {
|
|
@@ -395,14 +393,14 @@ container.register({
|
|
|
395
393
|
|
|
396
394
|
return { start: Date.now() };
|
|
397
395
|
},
|
|
398
|
-
scope:
|
|
396
|
+
scope: ArtifactScopes.Singleton,
|
|
399
397
|
lazy: false, // Eagerly build
|
|
400
398
|
});
|
|
401
399
|
|
|
402
400
|
async function runLifecycleHooksExample() {
|
|
403
401
|
await container.resolve('ticker');
|
|
404
|
-
console.log('Ticker running for
|
|
405
|
-
await new Promise(res => setTimeout(res,
|
|
402
|
+
console.log('Ticker running for 2 seconds...');
|
|
403
|
+
await new Promise(res => setTimeout(res, 2000));
|
|
406
404
|
|
|
407
405
|
console.log('\n--- Invalidating Ticker (will trigger onCleanup and rebuild) ---');
|
|
408
406
|
const ticker = await container.resolve('ticker'); // Get current to invalidate
|
|
@@ -426,7 +424,7 @@ runLifecycleHooksExample();
|
|
|
426
424
|
The `watch()` method provides a reactive way to observe an artifact's value without repeatedly calling `resolve()`. This is particularly useful for UI frameworks or reactive data flows.
|
|
427
425
|
|
|
428
426
|
```typescript
|
|
429
|
-
import { ArtifactContainer,
|
|
427
|
+
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
430
428
|
import { ReactiveDataStore } from '@asaidimu/utils-store';
|
|
431
429
|
|
|
432
430
|
interface AppState {
|
|
@@ -442,7 +440,7 @@ container.register({
|
|
|
442
440
|
const count = await use((ctx) => ctx.select((state) => state.counter));
|
|
443
441
|
return `Current count: ${count}`;
|
|
444
442
|
},
|
|
445
|
-
scope:
|
|
443
|
+
scope: ArtifactScopes.Singleton,
|
|
446
444
|
});
|
|
447
445
|
|
|
448
446
|
async function runWatcherExample() {
|
|
@@ -450,11 +448,10 @@ async function runWatcherExample() {
|
|
|
450
448
|
const watcher = container.watch('reactiveCounter');
|
|
451
449
|
|
|
452
450
|
let unsubscribe = watcher.subscribe(() => {
|
|
453
|
-
const resolved = watcher.get();
|
|
454
451
|
if (resolved.ready) {
|
|
455
452
|
console.log('Watcher received update:', resolved.instance);
|
|
456
453
|
} else if (resolved.error) {
|
|
457
|
-
console.error('Watcher received error:', resolved.error);
|
|
454
|
+
console.error('Watcher received error:', resolved.error.message);
|
|
458
455
|
} else {
|
|
459
456
|
console.log('Watcher received pending update (artifact not ready yet).');
|
|
460
457
|
}
|
|
@@ -475,135 +472,97 @@ async function runWatcherExample() {
|
|
|
475
472
|
unsubscribe(); // Unsubscribe first
|
|
476
473
|
await watcher.dispose(); // Then dispose the watcher itself
|
|
477
474
|
|
|
475
|
+
// After dispose, attempts to get will yield WatcherDisposedError
|
|
476
|
+
console.log('Watcher after dispose:', watcher.get().error.message);
|
|
477
|
+
|
|
478
478
|
// Output:
|
|
479
479
|
// Setting up watcher...
|
|
480
480
|
// Watcher received pending update (artifact not ready yet).
|
|
481
481
|
// Watcher received update: Current count: 0
|
|
482
482
|
//
|
|
483
483
|
// --- Incrementing counter ---
|
|
484
|
+
// Watcher received pending update (artifact not ready yet).
|
|
484
485
|
// Watcher received update: Current count: 1
|
|
485
486
|
//
|
|
486
487
|
// --- Incrementing counter again ---
|
|
488
|
+
// Watcher received pending update (artifact not ready yet).
|
|
487
489
|
// Watcher received update: Current count: 2
|
|
488
490
|
//
|
|
489
491
|
// --- Disposing watcher ---
|
|
492
|
+
// Watcher after dispose: [ArtifactContainer] Artifact with key:reactiveCounter has already been disposed
|
|
490
493
|
}
|
|
491
494
|
|
|
492
495
|
runWatcherExample();
|
|
493
496
|
```
|
|
494
497
|
|
|
495
|
-
###
|
|
498
|
+
### Updating Intermediate Values with `ctx.update`
|
|
496
499
|
|
|
497
|
-
For long-running or multi-stage artifact factories, `ctx.
|
|
500
|
+
For long-running or multi-stage artifact factories, `ctx.update()` allows a Singleton artifact to publish intermediate values. This updates the artifact's current instance and notifies dependents/watchers without fully completing the factory execution.
|
|
498
501
|
|
|
499
502
|
```typescript
|
|
500
|
-
import { ArtifactContainer,
|
|
501
|
-
import { ReactiveDataStore } from
|
|
503
|
+
import { ArtifactContainer, ArtifactScopes } from "@asaidimu/utils-artifacts";
|
|
504
|
+
import { ReactiveDataStore } from "@asaidimu/utils-store";
|
|
502
505
|
|
|
503
506
|
const store = new ReactiveDataStore({});
|
|
504
507
|
const container = new ArtifactContainer(store);
|
|
505
508
|
|
|
506
509
|
container.register({
|
|
507
|
-
key:
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
510
|
+
key: "longProcessArtifact",
|
|
511
|
+
factory: async ({ update: publish }) => { // `update` is renamed to `publish` for clarity in this example
|
|
512
|
+
async function process() {
|
|
513
|
+
await new Promise((res) => setTimeout(res, 100));
|
|
514
|
+
console.log("Starting long process...");
|
|
511
515
|
|
|
512
|
-
|
|
513
|
-
|
|
516
|
+
await publish("Step 1: Initializing...");
|
|
517
|
+
await new Promise((res) => setTimeout(res, 100));
|
|
514
518
|
|
|
515
|
-
|
|
516
|
-
|
|
519
|
+
await publish("Step 2: Loading data...");
|
|
520
|
+
await new Promise((res) => setTimeout(res, 100));
|
|
517
521
|
|
|
518
|
-
|
|
519
|
-
|
|
522
|
+
await publish("Step 3: Processing data...");
|
|
523
|
+
await new Promise((res) => setTimeout(res, 100));
|
|
520
524
|
|
|
521
|
-
|
|
522
|
-
|
|
525
|
+
console.log("Long process complete.");
|
|
526
|
+
}
|
|
523
527
|
|
|
524
|
-
|
|
525
|
-
|
|
528
|
+
queueMicrotask(process); // Start the process in the background,
|
|
529
|
+
return "Factory Starting";
|
|
526
530
|
},
|
|
527
|
-
scope:
|
|
531
|
+
scope: ArtifactScopes.Singleton,
|
|
528
532
|
});
|
|
529
533
|
|
|
530
|
-
async function
|
|
531
|
-
const watcher = container.watch(
|
|
534
|
+
async function runUpdateExample() {
|
|
535
|
+
const watcher = container.watch("longProcessArtifact");
|
|
536
|
+
|
|
532
537
|
const unsubscribe = watcher.subscribe(() => {
|
|
533
538
|
const resolved = watcher.get();
|
|
534
539
|
if (resolved.instance) {
|
|
535
|
-
console.log(
|
|
540
|
+
console.log("Watcher observed update:", resolved.instance);
|
|
536
541
|
}
|
|
537
542
|
});
|
|
538
543
|
|
|
539
|
-
|
|
540
|
-
|
|
544
|
+
// Resolve the artifact to trigger its factory and the background process
|
|
545
|
+
await container.resolve("longProcessArtifact");
|
|
546
|
+
|
|
547
|
+
// Give some time for the process to run and yield updates
|
|
548
|
+
await new Promise((res) => setTimeout(res, 600));
|
|
541
549
|
|
|
542
550
|
unsubscribe();
|
|
543
551
|
await watcher.dispose();
|
|
544
552
|
|
|
545
|
-
// Output will show intermediate values from `
|
|
553
|
+
// Output will show intermediate values from `ctx.update` as they occur,
|
|
546
554
|
// then the final result once the factory completes.
|
|
547
555
|
}
|
|
548
556
|
|
|
549
|
-
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
### Hierarchical Containers
|
|
553
|
-
|
|
554
|
-
Create child containers to manage dependencies in a nested, scoped manner. Child containers can resolve artifacts registered in their parent.
|
|
555
|
-
|
|
556
|
-
```typescript
|
|
557
|
-
import { ArtifactContainer, ArtifactScope } from '@asaidimu/utils-artifacts';
|
|
558
|
-
import { ReactiveDataStore } from '@asaidimu/utils-store';
|
|
559
|
-
|
|
560
|
-
const store = new ReactiveDataStore({});
|
|
561
|
-
const parentContainer = new ArtifactContainer(store);
|
|
562
|
-
|
|
563
|
-
parentContainer.register({
|
|
564
|
-
key: 'globalConfig',
|
|
565
|
-
factory: () => ({ logLevel: 'info', serverUrl: 'api.example.com' }),
|
|
566
|
-
scope: ArtifactScope.Singleton,
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
const childContainer = parentContainer.createChild();
|
|
570
|
-
|
|
571
|
-
childContainer.register({
|
|
572
|
-
key: 'featureService',
|
|
573
|
-
factory: async ({ use }) => {
|
|
574
|
-
const config = await use((ctx) => ctx.resolve('globalConfig')); // Resolves from parent
|
|
575
|
-
return {
|
|
576
|
-
name: 'FeatureService',
|
|
577
|
-
apiUrl: `${config.instance?.serverUrl}/feature`
|
|
578
|
-
};
|
|
579
|
-
},
|
|
580
|
-
scope: ArtifactScope.Singleton,
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
async function runHierarchicalExample() {
|
|
584
|
-
const globalConfig = await parentContainer.resolve('globalConfig');
|
|
585
|
-
const featureService = await childContainer.resolve('featureService');
|
|
586
|
-
|
|
587
|
-
console.log('Global Config from parent:', globalConfig.instance);
|
|
588
|
-
console.log('Feature Service from child:', featureService.instance);
|
|
589
|
-
|
|
590
|
-
console.assert(globalConfig.instance?.logLevel === 'info', 'Parent artifact resolved');
|
|
591
|
-
console.assert(featureService.instance?.apiUrl === 'api.example.com/feature', 'Child resolved parent artifact');
|
|
592
|
-
|
|
593
|
-
// Output:
|
|
594
|
-
// Global Config from parent: { logLevel: 'info', serverUrl: 'api.example.com' }
|
|
595
|
-
// Feature Service from child: { name: 'FeatureService', apiUrl: 'api.example.com/feature' }
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
runHierarchicalExample();
|
|
557
|
+
runUpdateExample();
|
|
599
558
|
```
|
|
600
559
|
|
|
601
560
|
### Debugging Information
|
|
602
561
|
|
|
603
|
-
Use `
|
|
562
|
+
Use `debugInfo()` to inspect the current state of artifacts within a container, including their status, dependencies, and dependents.
|
|
604
563
|
|
|
605
564
|
```typescript
|
|
606
|
-
import { ArtifactContainer,
|
|
565
|
+
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
607
566
|
import { ReactiveDataStore } from '@asaidimu/utils-store';
|
|
608
567
|
|
|
609
568
|
const store = new ReactiveDataStore({});
|
|
@@ -612,7 +571,7 @@ const container = new ArtifactContainer(store);
|
|
|
612
571
|
container.register({
|
|
613
572
|
key: 'serviceA',
|
|
614
573
|
factory: () => 'Service A Instance',
|
|
615
|
-
scope:
|
|
574
|
+
scope: ArtifactScopes.Singleton,
|
|
616
575
|
});
|
|
617
576
|
|
|
618
577
|
container.register({
|
|
@@ -621,13 +580,13 @@ container.register({
|
|
|
621
580
|
await use((ctx) => ctx.resolve('serviceA'));
|
|
622
581
|
return 'Service B Instance';
|
|
623
582
|
},
|
|
624
|
-
scope:
|
|
583
|
+
scope: ArtifactScopes.Singleton,
|
|
625
584
|
});
|
|
626
585
|
|
|
627
586
|
async function runDebugInfoExample() {
|
|
628
587
|
await container.resolve('serviceB'); // Resolve to build dependencies
|
|
629
588
|
|
|
630
|
-
const debugInfo = container.
|
|
589
|
+
const debugInfo = container.debugInfo();
|
|
631
590
|
console.log('--- Artifact Debug Information ---');
|
|
632
591
|
debugInfo.forEach(node => {
|
|
633
592
|
console.log(`ID: ${node.id}`);
|
|
@@ -659,42 +618,45 @@ The `@asaidimu/utils-artifacts` package is built around the central `ArtifactCon
|
|
|
659
618
|
|
|
660
619
|
### Core Components
|
|
661
620
|
|
|
662
|
-
* **`ArtifactContainer`**: The
|
|
663
|
-
* **`
|
|
664
|
-
* **`
|
|
665
|
-
* **`
|
|
666
|
-
* **`
|
|
621
|
+
* **`ArtifactContainer`**: The public-facing entry point of the system. It orchestrates the registration, resolution, lifecycle management (Singleton/Transient), and reactive updates of all artifacts. It provides methods like `register`, `resolve`, `watch`, `invalidate`, and `debugInfo`.
|
|
622
|
+
* **`ArtifactRegistry`**: Manages the definitions of all registered artifacts, storing their factory functions and configuration options (scope, lazy loading, retries, debounce, timeout).
|
|
623
|
+
* **`ArtifactCache`**: Stores the runtime state of resolved singleton artifacts, including their instances, errors, cleanup functions, state dependencies, and internal synchronization primitives (`Once`, `Serializer`). It's responsible for packaging cached data into the public `ResolvedArtifact` format.
|
|
624
|
+
* **`ArtifactDependencyGraph`**: A specialized wrapper around a generic `DependencyGraph` that tracks artifact-to-artifact dependencies and state path dependencies. It's crucial for detecting circular dependencies and determining the cascade order during invalidation.
|
|
625
|
+
* **`ArtifactManager`**: The core engine responsible for the actual building, invalidation, and disposal of artifact instances. It interacts heavily with the `Registry`, `Cache`, `DependencyGraph`, and `DataStore`. It implements retry logic, timeouts, and manages lifecycle hooks.
|
|
626
|
+
* **`ArtifactObserverManager`**: Handles the creation and management of `ArtifactObserver` instances, allowing external code to subscribe to artifact changes. It also manages reference counting for watchers and notifies them of updates.
|
|
627
|
+
* **`DataStore` (External Dependency)**: The container relies on an external data store (e.g., `@asaidimu/utils-store`) to manage global application state. The container interacts with it via `watch` (for reactive subscriptions to state changes) and `get` (for reading the current state).
|
|
628
|
+
* **Synchronization Primitives (`Mutex`, `Once`, `Serializer`)**: These internal utilities provide robust mechanisms for managing concurrency, ensuring single execution of tasks, and processing operations sequentially. They are fundamental building blocks for the `ArtifactManager` and `ArtifactCache`.
|
|
667
629
|
|
|
668
630
|
### Data Flow
|
|
669
631
|
|
|
670
|
-
1. **Registration (`container.register`)**: An `
|
|
632
|
+
1. **Registration (`container.register`)**: An `ArtifactTemplate` (containing an `ArtifactFactory` and its `options`) is stored in the `ArtifactRegistry`. A corresponding node is added to the `ArtifactDependencyGraph`.
|
|
671
633
|
2. **Resolution (`container.resolve`)**:
|
|
672
|
-
* If the artifact is a `Singleton` and already built,
|
|
673
|
-
* If not built, the `
|
|
674
|
-
* **Dependency Declaration (`ctx.use`)**: Inside the factory, `ctx.use` provides:
|
|
675
|
-
* `ctx.resolve(key)`:
|
|
676
|
-
* `ctx.select(selector)`:
|
|
677
|
-
3. **Dependency Graph Construction**: As dependencies are declared, the `
|
|
634
|
+
* If the artifact is a `Singleton` and already built, its cached instance from `ArtifactCache` is returned.
|
|
635
|
+
* If not built (or `Transient`), the `ArtifactManager` invokes the `ArtifactFactory` within a specialized `ArtifactFactoryContext`.
|
|
636
|
+
* **Dependency Declaration (`ctx.use`)**: Inside the factory, `ctx.use` provides methods to declare dependencies:
|
|
637
|
+
* `ctx.resolve(key)`: Recursively triggers resolution of another artifact. The relationship is added to the `ArtifactDependencyGraph`.
|
|
638
|
+
* `ctx.select(selector)`: Extracts specific state paths from the global `DataStore`. These paths are registered as state dependencies for the artifact.
|
|
639
|
+
3. **Dependency Graph Construction**: As dependencies are declared (via `ctx.resolve` or `ctx.select`), the `ArtifactDependencyGraph` constructs a precise, bidirectional graph, linking artifacts and their state dependencies.
|
|
678
640
|
4. **Reactive Invalidation**:
|
|
679
|
-
* **State Changes**: When a `DataStore` value changes,
|
|
680
|
-
* **Artifact Changes**: When an artifact is rebuilt or `
|
|
641
|
+
* **State Changes**: When a `DataStore` value changes, `ArtifactManager` identifies affected artifacts (those subscribed to the changed state paths) and calls `container.invalidate()` for them.
|
|
642
|
+
* **Artifact Changes**: When an artifact is rebuilt, invalidated, or proactively `update()`s its value, its direct and transitive `dependents` are identified by the `ArtifactDependencyGraph`, and `container.invalidate()` is called for each.
|
|
681
643
|
5. **Rebuild Process**: `container.invalidate()` triggers:
|
|
682
|
-
* **Debouncing**: If configured, rebuilds are debounced to consolidate multiple rapid invalidations
|
|
683
|
-
* **Cleanup**: `onCleanup` hooks for the old instance are
|
|
684
|
-
* **New Instance**: The `ArtifactFactory` is re-executed
|
|
685
|
-
* **Graph Update**: The dependency graph is updated based on
|
|
686
|
-
* **Notification**: `
|
|
687
|
-
6. **Disposal (`container.dispose` / `container.unregister`)**: All resources (`onDispose` hooks, state subscriptions,
|
|
644
|
+
* **Debouncing**: If configured, rebuilds are debounced (`activeDebounceMs`) to consolidate multiple rapid invalidations.
|
|
645
|
+
* **Cleanup**: `onCleanup` hooks for the *old* instance are executed.
|
|
646
|
+
* **New Instance**: The `ArtifactFactory` is re-executed by the `ArtifactManager`, creating a new instance.
|
|
647
|
+
* **Graph Update**: The dependency graph is updated based on any *new* dependencies declared by the re-executed factory.
|
|
648
|
+
* **Notification**: `ArtifactObserver` subscribers are notified of the new instance via `ArtifactObserverManager`.
|
|
649
|
+
6. **Disposal (`container.dispose` / `container.unregister`)**: All resources associated with an artifact (`onDispose` hooks, state subscriptions, cache entry, graph links) are released. Full container disposal clears everything.
|
|
688
650
|
|
|
689
651
|
### Extension Points
|
|
690
652
|
|
|
691
653
|
The primary extension point is the **`ArtifactFactory` function**. By implementing custom factories, you can:
|
|
692
|
-
* Integrate with any third-party library or framework.
|
|
693
|
-
* Define complex business logic.
|
|
694
|
-
* Manage external resources like database connections, API clients, or message queues
|
|
654
|
+
* Integrate with any third-party library or framework (e.g., database clients, API wrappers, authentication services).
|
|
655
|
+
* Define complex business logic that reacts to state or other services.
|
|
656
|
+
* Manage external resources like database connections, API clients, or message queues, ensuring they are properly initialized and cleaned up using `onCleanup` and `onDispose`.
|
|
695
657
|
* Provide custom logging, monitoring, or telemetry by injecting those services as dependencies.
|
|
696
658
|
|
|
697
|
-
The `ArtifactContainer` itself is designed to be highly configurable via `
|
|
659
|
+
The `ArtifactContainer` itself is designed to be highly configurable via `ArtifactTemplate` options, allowing fine-tuning of scope, lazy loading, error handling, and reactive behavior.
|
|
698
660
|
|
|
699
661
|
---
|
|
700
662
|
|
|
@@ -724,6 +686,8 @@ We welcome contributions! Please read the guidelines below.
|
|
|
724
686
|
|
|
725
687
|
* `bun install`: Installs project dependencies.
|
|
726
688
|
* `bun run test`: Runs the test suite using Vitest.
|
|
689
|
+
* `bun run test:watch`: Runs tests in watch mode for continuous feedback.
|
|
690
|
+
* `bun run test:browser`: Runs tests in a browser environment.
|
|
727
691
|
* `bun run build`: Compiles the TypeScript source code into JavaScript.
|
|
728
692
|
|
|
729
693
|
### Testing
|
|
@@ -761,14 +725,15 @@ Provide as much detail as possible, including steps to reproduce, expected behav
|
|
|
761
725
|
|
|
762
726
|
### Troubleshooting
|
|
763
727
|
|
|
764
|
-
* **`ArtifactNotFoundError`**: This typically means you're trying to `resolve` an artifact that hasn't been `register`ed in the current container
|
|
728
|
+
* **`ArtifactNotFoundError`**: This typically means you're trying to `resolve` or `watch` an artifact that hasn't been `register`ed in the current container. Double-check your `key`s and registration calls.
|
|
765
729
|
* **`CircularDependencyError`**: Occurs when artifact A depends on B, and B depends back on A (directly or indirectly). This indicates a structural problem in your dependency graph. Redesign your artifacts to break the cycle. The error message will show the path of the cycle.
|
|
766
730
|
* **Artifact Not Updating Reactively**:
|
|
767
731
|
* Ensure your artifact is a `Singleton` (Transient artifacts don't cache or react to invalidations).
|
|
768
732
|
* Verify you are using `ctx.use()` and `ctx.select()` (for state) or `ctx.resolve()` (for other artifacts) inside your factory to declare dependencies.
|
|
769
733
|
* Check that your `DataStore` implementation is correctly triggering its `watch` callbacks.
|
|
770
|
-
* **`IllegalScopeError`**: You might be trying to `
|
|
771
|
-
*
|
|
734
|
+
* **`IllegalScopeError`**: You might be trying to `update` a value from a `Transient` artifact. `update` is only meaningful for `Singleton` artifacts, which maintain a persistent instance.
|
|
735
|
+
* **`TimeoutError`**: Your artifact factory took longer than its configured `timeout` to complete, or a `Mutex` lock operation exceeded its timeout. Consider increasing the `timeout` or optimizing your factory's logic.
|
|
736
|
+
* **`WatcherDisposedError`**: You are attempting to access a watcher's state (`watcher.get()`) after `watcher.dispose()` has been called. Ensure you unsubscribe and dispose of watchers when no longer needed, and avoid using them afterward.
|
|
772
737
|
|
|
773
738
|
### FAQ
|
|
774
739
|
|
|
@@ -780,7 +745,7 @@ Provide as much detail as possible, including steps to reproduce, expected behav
|
|
|
780
745
|
* **Singleton**: For services, database connections, configuration objects, or any resource that should be shared and consistently available throughout your application. They are efficient as they are built once and reused.
|
|
781
746
|
* **Transient**: For objects that need a fresh instance every time they are requested, such as temporary calculators, request-scoped contexts, or factory functions that produce unique outputs.
|
|
782
747
|
* **How does `debounce` work?**
|
|
783
|
-
`debounce` prevents an artifact from rebuilding too frequently. If multiple invalidations occur within the specified `debounce` time, they are consolidated, and the artifact's factory is only run once after the activity ceases. This is crucial for performance with rapid state changes.
|
|
748
|
+
`debounce` prevents an artifact from rebuilding too frequently. If multiple invalidations occur within the specified `debounce` time, they are consolidated, and the artifact's factory is only run once after the activity ceases. This is crucial for performance with rapid state changes, especially when many state changes might trigger the same artifact rebuild.
|
|
784
749
|
|
|
785
750
|
### Changelog / Roadmap
|
|
786
751
|
|
|
@@ -796,4 +761,3 @@ This project is licensed under the MIT License. See the [LICENSE](https://github
|
|
|
796
761
|
* Developed as part of the `@asaidimu` utilities collection.
|
|
797
762
|
* Uses [Semantic Release](https://semantic-release.gitbook.io/semantic-release/) for automated versioning and publishing.
|
|
798
763
|
* Leverages [Vitest](https://vitest.dev/) for fast and reliable testing.
|
|
799
|
-
|