@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 CHANGED
@@ -1,4 +1,4 @@
1
- # @asaidimu/utils-artifacts
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
- * [Yielding Intermediate Values](#yielding-intermediate-values)
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
- * **`ArtifactWatcher` for Reactive Observation**: Observe changes to an artifact's resolved value over time, subscribing to updates without repeatedly resolving.
48
- * **Live Updates via `yield()`**: Singleton artifacts can proactively update their value from within their factory, immediately notifying dependents and watchers.
49
- * **Comprehensive Debugging Info**: `getDebugInfo()` provides a snapshot of the container's state, including artifact statuses, dependencies, and dependents.
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, ArtifactScope } from '@asaidimu/utils-artifacts';
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, ArtifactScope } from '@asaidimu/utils-artifacts';
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: ArtifactScope.Singleton,
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, ArtifactScope } from '@asaidimu/utils-artifacts';
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: ArtifactScope.Transient,
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, ArtifactScope } from '@asaidimu/utils-artifacts';
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: ArtifactScope.Singleton,
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?.notificationsEnabled === false, 'Notifications should be false');
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, ArtifactScope } from '@asaidimu/utils-artifacts';
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: ArtifactScope.Singleton,
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: ArtifactScope.Singleton,
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, ArtifactScope } from '@asaidimu/utils-artifacts';
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: ArtifactScope.Singleton,
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 3 seconds...');
405
- await new Promise(res => setTimeout(res, 3000));
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, ArtifactScope } from '@asaidimu/utils-artifacts';
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: ArtifactScope.Singleton,
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
- ### Yielding Intermediate Values
498
+ ### Updating Intermediate Values with `ctx.update`
496
499
 
497
- For long-running or multi-stage artifact factories, `ctx.yield()` 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.
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, ArtifactScope } from '@asaidimu/utils-artifacts';
501
- import { ReactiveDataStore } from '@asaidimu/utils-store';
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: 'longProcessArtifact',
508
- factory: async ({ yield: publish }) => {
509
- async function process() {
510
- console.log('Starting long process...');
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
- publish('Step 1: Initializing...');
513
- await new Promise(res => setTimeout(res, 100));
516
+ await publish("Step 1: Initializing...");
517
+ await new Promise((res) => setTimeout(res, 100));
514
518
 
515
- publish('Step 2: Loading data...');
516
- await new Promise(res => setTimeout(res, 100));
519
+ await publish("Step 2: Loading data...");
520
+ await new Promise((res) => setTimeout(res, 100));
517
521
 
518
- publish('Step 3: Processing data...');
519
- await new Promise(res => setTimeout(res, 100));
522
+ await publish("Step 3: Processing data...");
523
+ await new Promise((res) => setTimeout(res, 100));
520
524
 
521
- console.log('Long process complete.');
522
- }
525
+ console.log("Long process complete.");
526
+ }
523
527
 
524
- process()
525
- return 'Created factory...';
528
+ queueMicrotask(process); // Start the process in the background,
529
+ return "Factory Starting";
526
530
  },
527
- scope: ArtifactScope.Singleton,
531
+ scope: ArtifactScopes.Singleton,
528
532
  });
529
533
 
530
- async function runYieldExample() {
531
- const watcher = container.watch('longProcessArtifact');
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('Watcher observed yield:', resolved.instance);
540
+ console.log("Watcher observed update:", resolved.instance);
536
541
  }
537
542
  });
538
543
 
539
- await container.resolve('longProcessArtifact'); // Trigger the factory
540
- console.log('Factory finished.');
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 `yield` as they occur,
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
- runYieldExample();
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 `getDebugInfo()` to inspect the current state of artifacts within a container, including their status, dependencies, and dependents.
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, ArtifactScope } from '@asaidimu/utils-artifacts';
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: ArtifactScope.Singleton,
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: ArtifactScope.Singleton,
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.getDebugInfo();
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 heart of the system. It manages the registration, resolution, lifecycle (Singleton/Transient), and reactive updates of all artifacts. It can also create child containers to form a hierarchy.
663
- * **`ArtifactDefinition`**: An internal representation of a registered artifact, holding its configuration (scope, lazy loading, retries, etc.) and runtime state (current instance, error, cleanup functions, dependency graph links).
664
- * **`ArtifactFactory`**: A function that defines how an artifact instance is created. It receives an `ArtifactFactoryContext` which is crucial for declaring dependencies and managing its lifecycle.
665
- * **`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) and `get` (for reading current state). The `buildPaths` function is used internally to derive specific state paths for granular dependency tracking.
666
- * **`ArtifactWatcher`**: An observable interface for tracking the resolved state of an artifact over time, providing `get` for current value and `subscribe` for updates.
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 `ArtifactFactory` is associated with a `key` and `ArtifactOptions` (scope, lazy, retries, etc.).
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, the cached instance is returned.
673
- * If not built, the `ArtifactFactory` is invoked within a specialized context.
674
- * **Dependency Declaration (`ctx.use`)**: Inside the factory, `ctx.use` provides:
675
- * `ctx.resolve(key)`: Declares a dependency on another artifact. This call recursively triggers resolution of the dependency.
676
- * `ctx.select(selector)`: Declares a dependency on a slice of the global `DataStore` state. Internally, `buildPaths` extracts specific state paths from the selector.
677
- 3. **Dependency Graph Construction**: As dependencies are declared, the `ArtifactContainer` builds a precise, bidirectional dependency graph, linking artifacts to their dependents and subscribed state paths.
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, its `watch` callback is invoked. If the changed path matches an `ArtifactDefinition`'s `stateDependencies`, `container.invalidate()` is called for that artifact.
680
- * **Artifact Changes**: When an artifact is rebuilt or `yield()`s a new value, its `dependents` are identified and `container.invalidate()` is called for each.
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 into a single rebuild.
683
- * **Cleanup**: `onCleanup` hooks for the old instance are run.
684
- * **New Instance**: The `ArtifactFactory` is re-executed, creating a new instance.
685
- * **Graph Update**: The dependency graph is updated based on the new factory execution.
686
- * **Notification**: `ArtifactWatcher` subscribers are notified of the new instance.
687
- 6. **Disposal (`container.dispose` / `container.unregister`)**: All resources (`onDispose` hooks, state subscriptions, instance cache) are released.
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 `ArtifactOptions`, allowing fine-tuning of scope, lazy loading, error handling, and reactive behavior.
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 or any of its parent containers. Double-check your `key`s and registration calls.
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 `yield` a value from a `Transient` artifact. `yield` is only meaningful for `Singleton` artifacts, which maintain a persistent instance.
771
- * **Timeout Errors**: Your artifact factory took longer than `timeoutMs` to complete. Consider increasing the `timeoutMs` or optimizing your factory's logic.
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
-