@asaidimu/utils-artifacts 8.0.0 → 8.1.0

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.
Files changed (4) hide show
  1. package/README.md +190 -166
  2. package/index.d.mts +10 -10
  3. package/index.d.ts +10 -10
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -14,23 +14,23 @@ The `@asaidimu/utils-artifacts` library empowers developers to build highly orga
14
14
 
15
15
  ## 🚀 Quick Links
16
16
 
17
- - [Overview & Features](#-overview--features)
18
- - [Why Use This Library? (Power & Use Cases)](#-why-use-this-library-power--use-cases)
19
- - [Installation & Setup](#-installation--setup)
20
- - [Usage Documentation](#-usage-documentation)
21
- - [Basic Usage](#basic-usage)
22
- - [Registering Artifacts](#registering-artifacts)
23
- - [Resolving & Requiring Artifacts](#resolving--requiring-artifacts)
24
- - [Watching Artifact Changes](#watching-artifact-changes)
25
- - [Reactive Dependencies (State Selection)](#reactive-dependencies-state-selection)
26
- - [Artifact Lifecycle (Cleanup & Dispose)](#artifact-lifecycle-cleanup--dispose)
27
- - [Streaming Artifacts](#streaming-artifacts)
28
- - [Invalidating Artifacts](#invalidating-artifacts)
29
- - [Debugging Artifacts](#debugging-artifacts)
30
- - [Project Architecture](#-project-architecture)
31
- - [Considerations & Potential Pitfalls](#-considerations--potential-pitfalls)
32
- - [Development & Contributing](#-development--contributing)
33
- - [Additional Information](#-additional-information)
17
+ - [Overview & Features](#-overview--features)
18
+ - [Why Use This Library? (Power & Use Cases)](#-why-use-this-library-power--use-cases)
19
+ - [Installation & Setup](#-installation--setup)
20
+ - [Usage Documentation](#-usage-documentation)
21
+ - [Basic Usage](#basic-usage)
22
+ - [Registering Artifacts](#registering-artifacts)
23
+ - [Resolving & Requiring Artifacts](#resolving--requiring-artifacts)
24
+ - [Watching Artifact Changes](#watching-artifact-changes)
25
+ - [Reactive Dependencies (State Selection)](#reactive-dependencies-state-selection)
26
+ - [Artifact Lifecycle (Cleanup & Dispose)](#artifact-lifecycle-cleanup--dispose)
27
+ - [Streaming Artifacts](#streaming-artifacts)
28
+ - [Invalidating Artifacts](#invalidating-artifacts)
29
+ - [Debugging Artifacts](#debugging-artifacts)
30
+ - [Project Architecture](#-project-architecture)
31
+ - [Considerations & Potential Pitfalls](#-considerations--potential-pitfalls)
32
+ - [Development & Contributing](#-development--contributing)
33
+ - [Additional Information](#-additional-information)
34
34
 
35
35
  ---
36
36
 
@@ -40,16 +40,16 @@ The `@asaidimu/utils-artifacts` library empowers developers to build highly orga
40
40
 
41
41
  ### Key Features
42
42
 
43
- * **Declarative Dependency Injection (DI)**: Define artifact dependencies implicitly through factory context methods (`ctx.use`, `ctx.resolve`, `ctx.require`).
44
- * **Reactive State Management**: Automatically re-evaluate artifacts when slices of a global `DataStore` change via `ctx.select`, enabling highly reactive applications.
45
- * **Flexible Scoping**: Supports `Singleton` (single, cached instance) and `Transient` (new instance per resolution) artifact scopes.
46
- * **Advanced Lifecycle Management**: `onCleanup` for instance-specific resource release and `onDispose` for permanent artifact teardown.
47
- * **Streaming Artifacts**: Use `ctx.stream` to build long-lived artifacts that continuously emit new values, ideal for real-time data, web sockets, or periodic tasks.
48
- * **Robust Error Handling & Retries**: Integrated retry logic for resilient operations and a comprehensive `ArtifactError` hierarchy for clear diagnostics.
49
- * **Concurrency Primitives**: Internal utilities for managing race conditions and sequential execution of async operations.
50
- * **Debuggability**: `container.debugInfo()` provides a snapshot of the artifact graph, statuses, and dependencies for easy troubleshooting.
51
- * **Watcher API**: `container.watch()` allows external consumers to subscribe to artifact changes without directly resolving them.
52
- * **Circular Dependency Detection**: Prevents infinite loops during resolution by detecting and reporting cycles in the dependency graph.
43
+ - **Declarative Dependency Injection (DI)**: Define artifact dependencies implicitly through factory context methods (`ctx.use`, `ctx.resolve`, `ctx.require`).
44
+ - **Reactive State Management**: Automatically re-evaluate artifacts when slices of a global `DataStore` change via `ctx.select`, enabling highly reactive applications.
45
+ - **Flexible Scoping**: Supports `Singleton` (single, cached instance) and `Transient` (new instance per resolution) artifact scopes.
46
+ - **Advanced Lifecycle Management**: `onCleanup` for instance-specific resource release and `onDispose` for permanent artifact teardown.
47
+ - **Streaming Artifacts**: Use `ctx.stream` to build long-lived artifacts that continuously emit new values, ideal for real-time data, web sockets, or periodic tasks.
48
+ - **Robust Error Handling & Retries**: Integrated retry logic for resilient operations and a comprehensive `ArtifactError` hierarchy for clear diagnostics.
49
+ - **Concurrency Primitives**: Internal utilities for managing race conditions and sequential execution of async operations.
50
+ - **Debuggability**: `container.debugInfo()` provides a snapshot of the artifact graph, statuses, and dependencies for easy troubleshooting.
51
+ - **Watcher API**: `container.watch()` allows external consumers to subscribe to artifact changes without directly resolving them.
52
+ - **Circular Dependency Detection**: Prevents infinite loops during resolution by detecting and reporting cycles in the dependency graph.
53
53
 
54
54
  ---
55
55
 
@@ -59,18 +59,18 @@ The `@asaidimu/utils-artifacts` library is designed to tackle the complexities o
59
59
 
60
60
  ### Core Power
61
61
 
62
- * **Automatic Reactivity**: Effortlessly build applications where components automatically update when underlying data or services change. This dramatically simplifies state synchronization and UI updates.
63
- * **Sophisticated Dependency Management**: Declaring dependencies is natural and integrated. The library handles complex resolution graphs, including cycles, and ensures dependencies are met before an artifact is built.
64
- * **Lifecycle Control**: Fine-grained control over artifact lifecycles (`Singleton`, `Transient`) with explicit hooks for cleanup and disposal ensures proper resource management, preventing leaks.
65
- * **Decoupled Architecture**: Promotes a clean separation of concerns, making code more modular, testable, and easier to maintain.
62
+ - **Automatic Reactivity**: Effortlessly build applications where components automatically update when underlying data or services change. This dramatically simplifies state synchronization and UI updates.
63
+ - **Sophisticated Dependency Management**: Declaring dependencies is natural and integrated. The library handles complex resolution graphs, including cycles, and ensures dependencies are met before an artifact is built.
64
+ - **Lifecycle Control**: Fine-grained control over artifact lifecycles (`Singleton`, `Transient`) with explicit hooks for cleanup and disposal ensures proper resource management, preventing leaks.
65
+ - **Decoupled Architecture**: Promotes a clean separation of concerns, making code more modular, testable, and easier to maintain.
66
66
 
67
67
  ### Key Use Cases
68
68
 
69
- * **Complex Front-End Applications**: Ideal for managing intricate application state, services, and UI components in large-scale SPAs where reactivity and component lifecycles are paramount.
70
- * **Microservice Orchestration**: Coordinating services that depend on each other, managing their initialization, and handling their inter-service communication dependencies.
71
- * **Real-time Data Pipelines & Dashboards**: The streaming API (`ctx.stream`) is perfect for artifacts that continuously produce data, such as WebSocket clients, data feeders, or background processing agents.
72
- * **Shared Resource Management**: Managing singleton instances of expensive resources like API clients, database connections, or global caches with predictable lifecycles.
73
- * **Feature Toggling & Configuration**: Dynamically loading or updating configurations and feature flags that affect multiple parts of the application.
69
+ - **Complex Front-End Applications**: Ideal for managing intricate application state, services, and UI components in large-scale SPAs where reactivity and component lifecycles are paramount.
70
+ - **Microservice Orchestration**: Coordinating services that depend on each other, managing their initialization, and handling their inter-service communication dependencies.
71
+ - **Real-time Data Pipelines & Dashboards**: The streaming API (`ctx.stream`) is perfect for artifacts that continuously produce data, such as WebSocket clients, data feeders, or background processing agents.
72
+ - **Shared Resource Management**: Managing singleton instances of expensive resources like API clients, database connections, or global caches with predictable lifecycles.
73
+ - **Feature Toggling & Configuration**: Dynamically loading or updating configurations and feature flags that affect multiple parts of the application.
74
74
 
75
75
  ---
76
76
 
@@ -78,9 +78,9 @@ The `@asaidimu/utils-artifacts` library is designed to tackle the complexities o
78
78
 
79
79
  ### Prerequisites
80
80
 
81
- * Node.js (LTS recommended)
82
- * npm or Yarn (package manager)
83
- * A compatible reactive data store library (e.g., `@asaidimu/utils-store`) that adheres to the `DataStore` interface (providing `watch`, `get`, `set` methods).
81
+ - Node.js (LTS recommended)
82
+ - npm or Yarn (package manager)
83
+ - A compatible reactive data store library (e.g., `@asaidimu/utils-store`) that adheres to the `DataStore` interface (providing `watch`, `get`, `set` methods).
84
84
 
85
85
  ### Installation Steps
86
86
 
@@ -101,26 +101,26 @@ yarn add @asaidimu/utils-artifacts
101
101
  You can verify the installation by attempting to import and register a basic artifact:
102
102
 
103
103
  ```typescript
104
- import { ArtifactContainer } from '@asaidimu/utils-artifacts';
105
- import { ReactiveDataStore } from '@asaidimu/utils-store'; // Assuming you have this store
104
+ import { ArtifactContainer } from "@asaidimu/utils-artifacts";
105
+ import { ReactiveDataStore } from "@asaidimu/utils-store"; // Assuming you have this store
106
106
 
107
107
  // Define your application's global state and artifact registry types
108
- type AppState = { count: number; };
109
- type AppRegistry = { myService: string; };
108
+ type AppState = { count: number };
109
+ type AppRegistry = { myService: string };
110
110
 
111
111
  const store = new ReactiveDataStore<AppState>({ count: 0 });
112
112
  const container = new ArtifactContainer<AppRegistry, AppState>(store);
113
113
 
114
114
  container.register({
115
- key: 'myService',
115
+ key: "myService",
116
116
  factory: async ({ use }) => {
117
- const currentCount = await use(({ select }) => select(s => s.count));
117
+ const currentCount = await use(({ select }) => select((s) => s.count));
118
118
  return `Service is running with count: ${currentCount}`;
119
119
  },
120
120
  });
121
121
 
122
122
  async function runExample() {
123
- const service = await container.resolve('myService');
123
+ const service = await container.resolve("myService");
124
124
  console.log(service.instance); // Expected: Service is running with count: 0
125
125
  }
126
126
 
@@ -259,32 +259,38 @@ main();
259
259
  Use `container.register()` to define an artifact. Each artifact requires a unique `key` and a `factory` function.
260
260
 
261
261
  ```typescript
262
- import { ArtifactScopes, ArtifactTemplate } from '@asaidimu/utils-artifacts';
262
+ import { ArtifactScopes, ArtifactTemplate } from "@asaidimu/utils-artifacts";
263
263
 
264
264
  // Define your app's state and registry types for better type safety
265
- interface MyAppState { /* ... */ }
266
- interface MyAppRegistry { myService: string; }
265
+ interface MyAppState {
266
+ /* ... */
267
+ }
268
+ interface MyAppRegistry {
269
+ myService: string;
270
+ }
267
271
 
268
272
  // Type assertion for the container
269
273
  const container = new ArtifactContainer<MyAppRegistry, MyAppState>(myStore);
270
274
 
271
275
  // Example of registering an artifact
272
- container.register<MyAppRegistry, MyAppState, 'myService'>({
273
- key: 'myService',
276
+ container.register<MyAppRegistry, MyAppState, "myService">({
277
+ key: "myService",
274
278
  factory: async (ctx) => {
275
279
  // Access dependencies using ctx.use()
276
- const logger = await ctx.use(({ require }) => require('logger'));
277
- const apiUrl = await ctx.use(({ select }) => select(state => state.config.apiUrl));
280
+ const logger = await ctx.use(({ require }) => require("logger"));
281
+ const apiUrl = await ctx.use(({ select }) =>
282
+ select((state) => state.config.apiUrl),
283
+ );
278
284
 
279
285
  logger.log(`Initializing myService with API URL: ${apiUrl}`);
280
286
  return `Initialized: ${apiUrl}`;
281
287
  },
282
288
  // Optional parameters:
283
289
  scope: ArtifactScopes.Singleton, // 'singleton' (default) or 'transient'
284
- lazy: true, // For singletons: true (default) to build on first resolve, false to build on registration.
285
- timeout: 5000, // Max time in ms for the factory to complete.
286
- retries: 3, // Number of retries on factory failure (external errors only).
287
- debounce: 100, // Delay in ms before rebuilding after dependency changes.
290
+ lazy: true, // For singletons: true (default) to build on first resolve, false to build on registration.
291
+ timeout: 5000, // Max time in ms for the factory to complete.
292
+ retries: 3, // Number of retries on factory failure (external errors only).
293
+ debounce: 100, // Delay in ms before rebuilding after dependency changes.
288
294
  });
289
295
  ```
290
296
 
@@ -329,26 +335,26 @@ interface UseDependencyContext<TRegistry, TState> {
329
335
 
330
336
  ### Resolving & Requiring Artifacts
331
337
 
332
- * `container.resolve(key)`: Returns a `Promise<ResolvedArtifact<T>>`. This union type represents the artifact's state: `ReadyArtifact`, `ErrorArtifact`, or `PendingArtifact`. Check `.ready` and `.error` properties for robust handling.
333
- * `container.require(key)`: Returns a `Promise<T>` directly. It throws an error if resolution fails. Use this when you expect success and prefer exception-based error handling.
338
+ - `container.resolve(key)`: Returns a `Promise<ResolvedArtifact<T>>`. This union type represents the artifact's state: `ReadyArtifact`, `ErrorArtifact`, or `PendingArtifact`. Check `.ready` and `.error` properties for robust handling.
339
+ - `container.require(key)`: Returns a `Promise<T>` directly. It throws an error if resolution fails. Use this when you expect success and prefer exception-based error handling.
334
340
 
335
341
  ```typescript
336
342
  // Using resolve for defensive programming
337
- const myServiceResult = await container.resolve('myService');
343
+ const myServiceResult = await container.resolve("myService");
338
344
  if (myServiceResult.ready) {
339
- console.log('Service instance:', myServiceResult.instance);
345
+ console.log("Service instance:", myServiceResult.instance);
340
346
  } else if (myServiceResult.error) {
341
- console.error('Service failed:', myServiceResult.error);
347
+ console.error("Service failed:", myServiceResult.error);
342
348
  } else {
343
- console.log('Service is pending or idle.');
349
+ console.log("Service is pending or idle.");
344
350
  }
345
351
 
346
352
  // Using require for direct access when errors are handled upstream
347
353
  try {
348
- const myServiceInstance = await container.require('myService');
349
- console.log('Service instance:', myServiceInstance);
354
+ const myServiceInstance = await container.require("myService");
355
+ console.log("Service instance:", myServiceInstance);
350
356
  } catch (error) {
351
- console.error('Failed to get service:', error);
357
+ console.error("Failed to get service:", error);
352
358
  }
353
359
  ```
354
360
 
@@ -357,17 +363,17 @@ try {
357
363
  The `container.watch(key)` method returns an `ArtifactObserver`. This allows you to subscribe to artifact state changes without directly resolving them, ideal for UI updates.
358
364
 
359
365
  ```typescript
360
- const loggerWatcher = container.watch('logger');
366
+ const loggerWatcher = container.watch("logger");
361
367
 
362
368
  const unsubscribe = loggerWatcher.subscribe((resolvedArtifact) => {
363
369
  if (resolvedArtifact.ready) {
364
- console.log('Logger updated:', resolvedArtifact.instance);
370
+ console.log("Logger updated:", resolvedArtifact.instance);
365
371
  } else if (resolvedArtifact.error) {
366
- console.error('Logger error:', resolvedArtifact.error);
372
+ console.error("Logger error:", resolvedArtifact.error);
367
373
  }
368
374
  // `get()` retrieves the current resolved artifact state without subscribing.
369
375
  const currentLogger = loggerWatcher.get();
370
- console.log('Current logger:', currentLogger.instance);
376
+ console.log("Current logger:", currentLogger.instance);
371
377
  });
372
378
 
373
379
  // To stop listening for updates:
@@ -380,42 +386,54 @@ Artifacts can automatically react to changes in your application's global state
380
386
 
381
387
  ```typescript
382
388
  interface AppState {
383
- user: { name: string; theme: 'light' | 'dark'; };
389
+ user: { name: string; theme: "light" | "dark" };
390
+ }
391
+ interface AppRegistry {
392
+ userGreeting: string;
384
393
  }
385
- interface AppRegistry { userGreeting: string; }
386
394
 
387
395
  // Assume appStore is an instance of DataStore<AppState>
388
396
  const container = new ArtifactContainer<AppRegistry, AppState>(appStore);
389
397
 
390
398
  container.register({
391
- key: 'userGreeting',
399
+ key: "userGreeting",
392
400
  factory: async ({ use }) => {
393
401
  // This artifact will rebuild if appStore.user.theme changes.
394
- const theme = await use(({ select }) => select(state => state.user.theme));
395
- const userName = await use(({ select }) => select(state => state.user.name));
402
+ const theme = await use(({ select }) =>
403
+ select((state) => state.user.theme),
404
+ );
405
+ const userName = await use(({ select }) =>
406
+ select((state) => state.user.name),
407
+ );
396
408
  return `Hello, ${userName}! Your theme is ${theme}.`;
397
409
  },
398
410
  });
399
411
 
400
412
  // Example of state change triggering rebuild
401
- await appStore.set(state => ({ ...state, user: { ...state.user, theme: 'dark' } }));
402
- const greeting = await container.resolve('userGreeting');
413
+ await appStore.set((state) => ({
414
+ ...state,
415
+ user: { ...state.user, theme: "dark" },
416
+ }));
417
+ const greeting = await container.resolve("userGreeting");
403
418
  console.log(greeting.instance); // Output will reflect the new theme.
404
419
  ```
405
420
 
406
421
  ### Artifact Lifecycle (Cleanup & Dispose)
407
422
 
408
- * `ctx.onCleanup(fn)`: Registers a function to be executed *before* a singleton artifact's instance is replaced or before a transient artifact instance is discarded. Useful for releasing resources tied to the *current instance* (e.g., clearing timers, unsubscribing local event listeners).
409
- * `ctx.onDispose(fn)`: Registers a function to be executed *only when the artifact is permanently unregistered* from the container or when the container itself is disposed. Use this for final resource cleanup (e.g., closing database connections).
423
+ - `ctx.onCleanup(fn)`: Registers a function to be executed _before_ a singleton artifact's instance is replaced or before a transient artifact instance is discarded. Useful for releasing resources tied to the _current instance_ (e.g., clearing timers, unsubscribing local event listeners).
424
+ - `ctx.onDispose(fn)`: Registers a function to be executed _only when the artifact is permanently unregistered_ from the container or when the container itself is disposed. Use this for final resource cleanup (e.g., closing database connections).
410
425
 
411
426
  ```typescript
412
427
  container.register({
413
- key: 'resourceArtifact',
428
+ key: "resourceArtifact",
414
429
  scope: ArtifactScopes.Singleton,
415
430
  factory: ({ onCleanup, onDispose, use }) => {
416
431
  const resourceId = Math.random();
417
432
  console.log(`Resource ${resourceId} created.`);
418
- const timerId = setInterval(() => console.log(`Resource ${resourceId} heartbeat...`), 1000);
433
+ const timerId = setInterval(
434
+ () => console.log(`Resource ${resourceId} heartbeat...`),
435
+ 1000,
436
+ );
419
437
 
420
438
  // Cleanup for the current instance (e.g., on rebuild or if transient)
421
439
  onCleanup(() => {
@@ -425,7 +443,9 @@ container.register({
425
443
 
426
444
  // Dispose for permanent removal from container
427
445
  onDispose(() => {
428
- console.log(`Disposing artifact 'resourceArtifact' (resource ${resourceId}).`);
446
+ console.log(
447
+ `Disposing artifact 'resourceArtifact' (resource ${resourceId}).`,
448
+ );
429
449
  // Final resource release
430
450
  });
431
451
 
@@ -434,16 +454,16 @@ container.register({
434
454
  });
435
455
 
436
456
  async function lifecycleExample() {
437
- await container.resolve('resourceArtifact'); // Instance created and logs "heartbeat..."
438
- console.log('Artifact resolved.');
457
+ await container.resolve("resourceArtifact"); // Instance created and logs "heartbeat..."
458
+ console.log("Artifact resolved.");
439
459
 
440
460
  // Simulate invalidation: will trigger onCleanup for the current instance, then rebuild.
441
- await container.invalidate('resourceArtifact');
442
- console.log('Artifact invalidated and rebuilt.');
461
+ await container.invalidate("resourceArtifact");
462
+ console.log("Artifact invalidated and rebuilt.");
443
463
 
444
464
  // Manually dispose the artifact and container
445
- await container.unregister('resourceArtifact'); // Triggers onDispose
446
- console.log('Artifact unregistered.');
465
+ await container.unregister("resourceArtifact"); // Triggers onDispose
466
+ console.log("Artifact unregistered.");
447
467
  }
448
468
  lifecycleExample();
449
469
  ```
@@ -454,20 +474,20 @@ For artifacts that continuously emit values (e.g., real-time data, streams), use
454
474
 
455
475
  ```typescript
456
476
  container.register({
457
- key: 'dataStream',
477
+ key: "dataStream",
458
478
  scope: ArtifactScopes.Singleton,
459
479
  factory: ({ stream, onCleanup, use }) => {
460
480
  let counter = 0;
461
481
  let intervalId: ReturnType<typeof setInterval> | undefined;
462
482
 
463
483
  stream(async ({ emit, signal, value }) => {
464
- console.log('Data stream started...');
465
- const logger = await use(({ require }) => require('logger')); // Example dependency
466
- logger.log('Stream active. Emitting values.');
484
+ console.log("Data stream started...");
485
+ const logger = await use(({ require }) => require("logger")); // Example dependency
486
+ logger.log("Stream active. Emitting values.");
467
487
 
468
488
  intervalId = setInterval(() => {
469
489
  if (signal.aborted) {
470
- console.log('Stream signal aborted. Clearing interval.');
490
+ console.log("Stream signal aborted. Clearing interval.");
471
491
  clearInterval(intervalId);
472
492
  return;
473
493
  }
@@ -476,7 +496,7 @@ container.register({
476
496
  console.log(`Emitting: ${newValue}`);
477
497
  emit(newValue); // Emit the new value to the container
478
498
  if (counter >= 5) {
479
- console.log('Stream reached limit, stopping.');
499
+ console.log("Stream reached limit, stopping.");
480
500
  clearInterval(intervalId);
481
501
  // The factory function can return to stop the stream producer.
482
502
  }
@@ -484,25 +504,25 @@ container.register({
484
504
  });
485
505
 
486
506
  onCleanup(() => {
487
- console.log('Cleaning up data stream instance...');
507
+ console.log("Cleaning up data stream instance...");
488
508
  // Ensure interval is cleared if stream is aborted or rebuilt.
489
509
  if (intervalId) clearInterval(intervalId);
490
510
  });
491
511
 
492
- return 'Initial stream value'; // Initial value before first emission
512
+ return "Initial stream value"; // Initial value before first emission
493
513
  },
494
514
  });
495
515
 
496
516
  async function streamExample() {
497
- const watcher = container.watch('dataStream');
517
+ const watcher = container.watch("dataStream");
498
518
  const unsubscribe = watcher.subscribe((art) => {
499
- if (art.ready) console.log('Stream received:', art.instance);
519
+ if (art.ready) console.log("Stream received:", art.instance);
500
520
  });
501
521
 
502
522
  // Keep alive for a few seconds to observe stream emissions
503
- await new Promise(res => setTimeout(res, 3000));
523
+ await new Promise((res) => setTimeout(res, 3000));
504
524
  unsubscribe();
505
- console.log('Stream watcher unsubscribed.');
525
+ console.log("Stream watcher unsubscribed.");
506
526
  }
507
527
  streamExample();
508
528
  ```
@@ -513,10 +533,10 @@ Manually trigger an artifact to rebuild and cascade invalidations to its depende
513
533
 
514
534
  ```typescript
515
535
  // Invalidate an artifact; it will rebuild if it has dependents, is lazy and watched, or eagerly.
516
- await container.invalidate('myArtifact');
536
+ await container.invalidate("myArtifact");
517
537
 
518
538
  // Force immediate rebuild, bypassing any debounce delay.
519
- await container.invalidate('myArtifact', true);
539
+ await container.invalidate("myArtifact", true);
520
540
  ```
521
541
 
522
542
  ### Debugging Artifacts
@@ -525,14 +545,18 @@ The `container.debugInfo()` method provides a snapshot of the container's intern
525
545
 
526
546
  ```typescript
527
547
  const debugNodes = container.debugInfo();
528
- debugNodes.forEach(node => {
548
+ debugNodes.forEach((node) => {
529
549
  console.log(`
530
550
  Artifact ID: ${node.id}`);
531
551
  console.log(` Scope: ${node.scope}`);
532
552
  console.log(` Status: ${node.status}`); // e.g., 'active', 'error', 'idle', 'building', 'pending', 'debouncing'
533
- console.log(` Dependencies (Artifacts): ${node.dependencies.join(', ') || 'None'}`);
534
- console.log(` Dependencies (State Paths): ${node.stateDependencies.join(', ') || 'None'}`);
535
- console.log(` Dependents: ${node.dependents.join(', ') || 'None'}`);
553
+ console.log(
554
+ ` Dependencies (Artifacts): ${node.dependencies.join(", ") || "None"}`,
555
+ );
556
+ console.log(
557
+ ` Dependencies (State Paths): ${node.stateDependencies.join(", ") || "None"}`,
558
+ );
559
+ console.log(` Dependents: ${node.dependents.join(", ") || "None"}`);
536
560
  console.log(` Build Count: ${node.buildCount}`);
537
561
  });
538
562
  ```
@@ -543,41 +567,41 @@ Artifact ID: ${node.id}`);
543
567
 
544
568
  The `@asaidimu/utils-artifacts` library is architected around a central `ArtifactContainer` that orchestrates several specialized internal components:
545
569
 
546
- * **`ArtifactContainer`**: The main public API. It aggregates and coordinates the other components, providing methods for registering, resolving, watching, invalidating, and disposing artifacts.
547
- * **`ArtifactRegistry`**: Stores the definitions (`ArtifactTemplate`s) for all artifacts. It maps artifact keys to their factories and configuration options.
548
- * **`ArtifactCache`**: Manages the lifecycle and instances of resolved artifacts. It stores singleton instances, tracks artifact versions, and caches results.
549
- * **`ArtifactDependencyGraph`**: Maintains the relationships between artifacts and their dependencies on global state paths. This graph is critical for detecting circular dependencies and for efficiently cascading invalidations.
550
- * **`ArtifactManager`**: The core engine responsible for artifact lifecycle management. It handles artifact building (executing factories), managing retries, timeouts, stream propagation, and the reactive invalidation process. It utilizes the other components for dependency resolution and state observation.
551
- * **`ArtifactObserverManager`**: Implements the `container.watch()` API. It manages subscriptions from external consumers, notifies them of artifact state changes, and tracks active watchers to manage lazy loading and resource cleanup.
570
+ - **`ArtifactContainer`**: The main public API. It aggregates and coordinates the other components, providing methods for registering, resolving, watching, invalidating, and disposing artifacts.
571
+ - **`ArtifactRegistry`**: Stores the definitions (`ArtifactTemplate`s) for all artifacts. It maps artifact keys to their factories and configuration options.
572
+ - **`ArtifactCache`**: Manages the lifecycle and instances of resolved artifacts. It stores singleton instances, tracks artifact versions, and caches results.
573
+ - **`ArtifactDependencyGraph`**: Maintains the relationships between artifacts and their dependencies on global state paths. This graph is critical for detecting circular dependencies and for efficiently cascading invalidations.
574
+ - **`ArtifactManager`**: The core engine responsible for artifact lifecycle management. It handles artifact building (executing factories), managing retries, timeouts, stream propagation, and the reactive invalidation process. It utilizes the other components for dependency resolution and state observation.
575
+ - **`ArtifactObserverManager`**: Implements the `container.watch()` API. It manages subscriptions from external consumers, notifies them of artifact state changes, and tracks active watchers to manage lazy loading and resource cleanup.
552
576
 
553
577
  ### Data Flow Example (Resolution & Reactivity)
554
578
 
555
579
  1. **Registration**: An artifact (`key`, `factory`, `scope`, etc.) is registered with the `ArtifactRegistry`. The `ArtifactDependencyGraph` registers a node for this artifact.
556
580
  2. **Resolution (`container.resolve`)**:
557
- * The `ArtifactContainer` delegates to `ArtifactManager`.
558
- * The `Manager` checks the `ArtifactCache`. If a `Singleton` is already built and valid, its instance is returned.
559
- * If not cached or if an invalidation occurred, the `Manager` retrieves the artifact's `ArtifactTemplate` from the `Registry`.
560
- * The `Manager` prepares the `ArtifactFactoryContext` and executes the artifact's `factory` function.
561
- * **Dependency Declaration**:
562
- * Inside the factory, `ctx.use(({ resolve/require }) => ...)` calls recursively trigger artifact resolution, updating the `ArtifactDependencyGraph`.
563
- * `ctx.use(({ select }) => ...)` registers state path dependencies. The `Manager` sets up listeners on the `DataStore` for these paths.
564
- * **Build Execution**: The `Manager` handles potential retries, timeouts, and cleanup logic.
565
- * **Cache Update**: Upon successful build, the instance is stored in the `ArtifactCache`. The `ArtifactDependencyGraph` is updated with the artifact's declared dependencies.
581
+ - The `ArtifactContainer` delegates to `ArtifactManager`.
582
+ - The `Manager` checks the `ArtifactCache`. If a `Singleton` is already built and valid, its instance is returned.
583
+ - If not cached or if an invalidation occurred, the `Manager` retrieves the artifact's `ArtifactTemplate` from the `Registry`.
584
+ - The `Manager` prepares the `ArtifactFactoryContext` and executes the artifact's `factory` function.
585
+ - **Dependency Declaration**:
586
+ - Inside the factory, `ctx.use(({ resolve/require }) => ...)` calls recursively trigger artifact resolution, updating the `ArtifactDependencyGraph`.
587
+ - `ctx.use(({ select }) => ...)` registers state path dependencies. The `Manager` sets up listeners on the `DataStore` for these paths.
588
+ - **Build Execution**: The `Manager` handles potential retries, timeouts, and cleanup logic.
589
+ - **Cache Update**: Upon successful build, the instance is stored in the `ArtifactCache`. The `ArtifactDependencyGraph` is updated with the artifact's declared dependencies.
566
590
  3. **Reactivity & Invalidation**:
567
- * If a `DataStore` path watched by an artifact changes, the `Manager` is notified.
568
- * The `Manager` identifies artifacts dependent on that path using the `ArtifactDependencyGraph`.
569
- * These dependent artifacts are marked as invalid. If they are `Singleton`s, their `onCleanup` hooks are called, and they are scheduled for rebuild (potentially with debouncing).
570
- * If a dependency artifact is rebuilt or invalidated, its dependents are similarly cascaded.
571
- * When an artifact rebuilds, its `factory` is executed again, re-evaluating its dependencies and state selections.
572
- * `ArtifactObserverManager` is notified of changes to update any watching consumers.
591
+ - If a `DataStore` path watched by an artifact changes, the `Manager` is notified.
592
+ - The `Manager` identifies artifacts dependent on that path using the `ArtifactDependencyGraph`.
593
+ - These dependent artifacts are marked as invalid. If they are `Singleton`s, their `onCleanup` hooks are called, and they are scheduled for rebuild (potentially with debouncing).
594
+ - If a dependency artifact is rebuilt or invalidated, its dependents are similarly cascaded.
595
+ - When an artifact rebuilds, its `factory` is executed again, re-evaluating its dependencies and state selections.
596
+ - `ArtifactObserverManager` is notified of changes to update any watching consumers.
573
597
 
574
598
  ### Core Concepts
575
599
 
576
- * **Artifact**: A unit of work or data within the application, defined by a `key` and a `factory` function.
577
- * **Scope**: Determines the lifecycle: `Singleton` (one instance) or `Transient` (new instance per resolution).
578
- * **Factory**: A function that creates an artifact's instance, declaring its dependencies and side effects.
579
- * **Dependencies**: Artifacts can depend on other artifacts (`resolve`/`require`) or state slices (`select`).
580
- * **Reactivity**: Artifacts automatically update when their declared dependencies change.
600
+ - **Artifact**: A unit of work or data within the application, defined by a `key` and a `factory` function.
601
+ - **Scope**: Determines the lifecycle: `Singleton` (one instance) or `Transient` (new instance per resolution).
602
+ - **Factory**: A function that creates an artifact's instance, declaring its dependencies and side effects.
603
+ - **Dependencies**: Artifacts can depend on other artifacts (`resolve`/`require`) or state slices (`select`).
604
+ - **Reactivity**: Artifacts automatically update when their declared dependencies change.
581
605
 
582
606
  ---
583
607
 
@@ -587,36 +611,36 @@ While `@asaidimu/utils-artifacts` offers powerful features, understanding its op
587
611
 
588
612
  ### Debouncing Latency
589
613
 
590
- * **Issue**: The `debounce` option allows delaying artifact rebuilds after dependency changes to aggregate rapid updates. However, overly large debounce values or frequent invalidation cascades can introduce noticeable latency in reflecting updates, especially in UI-critical paths.
591
- * **Recommendation**: Carefully tune `debounce` values. Consider the trade-off between reducing update churn and responsiveness. For high-frequency updates, explore streaming or alternative reactivity patterns if latency becomes an issue.
614
+ - **Issue**: The `debounce` option allows delaying artifact rebuilds after dependency changes to aggregate rapid updates. However, overly large debounce values or frequent invalidation cascades can introduce noticeable latency in reflecting updates, especially in UI-critical paths.
615
+ - **Recommendation**: Carefully tune `debounce` values. Consider the trade-off between reducing update churn and responsiveness. For high-frequency updates, explore streaming or alternative reactivity patterns if latency becomes an issue.
592
616
 
593
617
  ### Wasted Build Effort from Late Staleness Detection
594
618
 
595
- * **Issue**: The library detects if dependencies have changed *after* an artifact's factory has already executed. This means significant computation might be discarded if a dependency was updated mid-build, leading to wasted effort.
596
- * **Recommendation**: Optimize artifact factories to be as performant as possible. For critical, long-running builds, consider architecting them to check for dependency validity more proactively if feasible, or accept this as a trade-off for a simpler dependency tracking mechanism.
619
+ - **Issue**: The library detects if dependencies have changed _after_ an artifact's factory has already executed. This means significant computation might be discarded if a dependency was updated mid-build, leading to wasted effort.
620
+ - **Recommendation**: Optimize artifact factories to be as performant as possible. For critical, long-running builds, consider architecting them to check for dependency validity more proactively if feasible, or accept this as a trade-off for a simpler dependency tracking mechanism.
597
621
 
598
622
  ### Efficiency of Global State Watching
599
623
 
600
- * **Observation**: The library's reactivity relies heavily on the underlying `DataStore`'s `watch` mechanism. Inefficient state selectors or overly broad subscriptions can lead to excessive artifact rebuilds and impact application performance.
601
- * **Recommendation**:
602
- * Write granular and performant state selectors using `ctx.select()`.
603
- * Minimize the number of state paths an artifact subscribes to.
604
- * Ensure the `DataStore` implementation itself is optimized for change detection and notification.
624
+ - **Observation**: The library's reactivity relies heavily on the underlying `DataStore`'s `watch` mechanism. Inefficient state selectors or overly broad subscriptions can lead to excessive artifact rebuilds and impact application performance.
625
+ - **Recommendation**:
626
+ - Write granular and performant state selectors using `ctx.select()`.
627
+ - Minimize the number of state paths an artifact subscribes to.
628
+ - Ensure the `DataStore` implementation itself is optimized for change detection and notification.
605
629
 
606
630
  ### Startup Cost of Eager Loading
607
631
 
608
- * **Issue**: Singletons registered with `lazy: false` are eagerly instantiated upon container setup. If many such artifacts are registered, this can lead to a substantial upfront cost during application startup, impacting initial load times.
609
- * **Recommendation**: Favor `lazy: true` (the default for singletons) whenever possible. Only use eager loading for artifacts that are truly required immediately at startup or where their initialization cost is negligible.
632
+ - **Issue**: Singletons registered with `lazy: false` are eagerly instantiated upon container setup. If many such artifacts are registered, this can lead to a substantial upfront cost during application startup, impacting initial load times.
633
+ - **Recommendation**: Favor `lazy: true` (the default for singletons) whenever possible. Only use eager loading for artifacts that are truly required immediately at startup or where their initialization cost is negligible.
610
634
 
611
635
  ### Sequential Cleanup/Dispose Execution
612
636
 
613
- * **Observation**: Cleanup and dispose functions are executed sequentially. If an artifact has many dependencies with complex or time-consuming teardown routines, this sequential execution can slow down the overall teardown process for an artifact or the entire container.
614
- * **Recommendation**: For artifacts with numerous or computationally intensive cleanup tasks, evaluate if these tasks can be safely executed concurrently (e.g., using `Promise.all` within the cleanup/dispose logic) to improve teardown performance.
637
+ - **Observation**: Cleanup and dispose functions are executed sequentially. If an artifact has many dependencies with complex or time-consuming teardown routines, this sequential execution can slow down the overall teardown process for an artifact or the entire container.
638
+ - **Recommendation**: For artifacts with numerous or computationally intensive cleanup tasks, evaluate if these tasks can be safely executed concurrently (e.g., using `Promise.all` within the cleanup/dispose logic) to improve teardown performance.
615
639
 
616
640
  ### Dependency Graph Complexity
617
641
 
618
- * **Observation**: While the dependency graph implementation is optimized, operations on extremely large graphs (hundreds or thousands of artifacts with complex interdependencies) can incur significant computational costs during resolution, invalidation, or cycle detection.
619
- * **Recommendation**: For applications with exceptionally large artifact graphs, consider strategies for modularizing the container or pruning less critical dependencies if performance becomes a bottleneck.
642
+ - **Observation**: While the dependency graph implementation is optimized, operations on extremely large graphs (hundreds or thousands of artifacts with complex interdependencies) can incur significant computational costs during resolution, invalidation, or cycle detection.
643
+ - **Recommendation**: For applications with exceptionally large artifact graphs, consider strategies for modularizing the container or pruning less critical dependencies if performance becomes a bottleneck.
620
644
 
621
645
  ---
622
646
 
@@ -638,8 +662,8 @@ While `@asaidimu/utils-artifacts` offers powerful features, understanding its op
638
662
 
639
663
  ### Scripts
640
664
 
641
- * `npm test`: Runs all tests once.
642
- * `npm test:watch`: Runs tests in watch mode, rerunning on file changes.
665
+ - `npm test`: Runs all tests once.
666
+ - `npm test:watch`: Runs tests in watch mode, rerunning on file changes.
643
667
 
644
668
  ### Testing
645
669
 
@@ -659,26 +683,26 @@ The project uses `vitest` for testing. All new code should be accompanied by tes
659
683
 
660
684
  ### Troubleshooting
661
685
 
662
- * **`ArtifactNotFoundError`**: You are trying to `resolve` or `watch` an artifact that hasn't been registered. Verify artifact keys and ensure registration order.
663
- * **`CircularDependencyError`**: The dependency graph contains a loop. Check the error message's path and redesign dependencies to break the cycle. `container.debugInfo()` is helpful here.
664
- * **`TimeoutError`**: An artifact factory took too long to execute. Increase the `timeout` option or optimize the factory function and its dependencies.
665
- * **Unexpected Rebuilds/No Rebuilds**:
666
- * Inspect artifact status and dependencies using `container.debugInfo()`.
667
- * Ensure `ctx.select()` selectors correctly capture reactive state slices.
668
- * Review `debounce` settings if rebuilds are too frequent or delayed.
669
- * Verify `onCleanup`/`onDispose` hooks are correctly implemented.
686
+ - **`ArtifactNotFoundError`**: You are trying to `resolve` or `watch` an artifact that hasn't been registered. Verify artifact keys and ensure registration order.
687
+ - **`CircularDependencyError`**: The dependency graph contains a loop. Check the error message's path and redesign dependencies to break the cycle. `container.debugInfo()` is helpful here.
688
+ - **`TimeoutError`**: An artifact factory took too long to execute. Increase the `timeout` option or optimize the factory function and its dependencies.
689
+ - **Unexpected Rebuilds/No Rebuilds**:
690
+ - Inspect artifact status and dependencies using `container.debugInfo()`.
691
+ - Ensure `ctx.select()` selectors correctly capture reactive state slices.
692
+ - Review `debounce` settings if rebuilds are too frequent or delayed.
693
+ - Verify `onCleanup`/`onDispose` hooks are correctly implemented.
670
694
 
671
695
  ### FAQ
672
696
 
673
- * **`Singleton` vs. `Transient` Scope**:
674
- * **`Singleton`**: Only one instance is ever created and shared across all resolutions. Ideal for services, configurations, or shared resources. Supports lifecycle hooks (`onCleanup`, `onDispose`) and streaming.
675
- * **`Transient`**: A new instance is created every time the artifact is resolved. Useful for ephemeral objects. Does not support streaming or persistent `onDispose`.
676
- * **`resolve` vs. `require`**:
677
- * Use `resolve` for robust handling of artifact states (ready, error, pending) via the `ResolvedArtifact` object.
678
- * Use `require` when you expect immediate success and want errors to propagate as exceptions.
679
- * **`onCleanup` vs. `onDispose`**:
680
- * `onCleanup`: Runs before an instance is replaced (Singleton rebuild) or discarded (Transient). Use for instance-specific resource cleanup.
681
- * `onDispose`: Runs only when the artifact is permanently removed from the container. Use for global resource cleanup.
697
+ - **`Singleton` vs. `Transient` Scope**:
698
+ - **`Singleton`**: Only one instance is ever created and shared across all resolutions. Ideal for services, configurations, or shared resources. Supports lifecycle hooks (`onCleanup`, `onDispose`) and streaming.
699
+ - **`Transient`**: A new instance is created every time the artifact is resolved. Useful for ephemeral objects. Does not support streaming or persistent `onDispose`.
700
+ - **`resolve` vs. `require`**:
701
+ - Use `resolve` for robust handling of artifact states (ready, error, pending) via the `ResolvedArtifact` object.
702
+ - Use `require` when you expect immediate success and want errors to propagate as exceptions.
703
+ - **`onCleanup` vs. `onDispose`**:
704
+ - `onCleanup`: Runs before an instance is replaced (Singleton rebuild) or discarded (Transient). Use for instance-specific resource cleanup.
705
+ - `onDispose`: Runs only when the artifact is permanently removed from the container. Use for global resource cleanup.
682
706
 
683
707
  ---
684
708
 
package/index.d.mts CHANGED
@@ -364,17 +364,17 @@ interface ArtifactFactoryContext<TRegistry extends Record<string, any>, TState e
364
364
  */
365
365
  onDispose(callback: ArtifactCleanup): void;
366
366
  /**
367
- * Starts a streaming process for a Singleton artifact.
368
- * The callback receives an `ArtifactStreamContext` to emit values and manage the
369
- * stream's lifecycle. It can return a cleanup function (or a Promise resolving
370
- * to one) to handle resource disposal.
371
- * * @param callback - The streaming logic implementation. Can be synchronous or
372
- * asynchronous, optionally returning a cleanup function.
373
- * @throws {SystemError} If called on a Transient artifact, as they do not
374
- * support persistent streaming states.
375
- */
367
+ * Starts a streaming process for a Singleton artifact.
368
+ * The callback receives an `ArtifactStreamContext` to emit values and manage the
369
+ * stream's lifecycle. It can return a cleanup function (or a Promise resolving
370
+ * to one) to handle resource disposal.
371
+ * * @param callback - The streaming logic implementation. Can be synchronous or
372
+ * asynchronous, optionally returning a cleanup function.
373
+ * @throws {SystemError} If called on a Transient artifact, as they do not
374
+ * support persistent streaming states.
375
+ */
376
+ stream(callback: (ctx: ArtifactStreamContext<TState, TArtifact>) => (void | (() => void | Promise<void>)) | Promise<void | (() => void | Promise<void>)>): void;
376
377
  stream(callback: (ctx: ArtifactStreamContext<TState, TArtifact>) => (void | (() => void | Promise<void>)) | Promise<void | (() => void | Promise<void>)>): void;
377
- stream(callback: (ctx: ArtifactStreamContext<TState, TArtifact>) => (void | (() => (void | Promise<void>))) | Promise<(void | (() => (void | Promise<void>)))>): void;
378
378
  /**
379
379
  * An AbortSignal that indicates if the artifacts has been unregistered
380
380
  */
package/index.d.ts CHANGED
@@ -364,17 +364,17 @@ interface ArtifactFactoryContext<TRegistry extends Record<string, any>, TState e
364
364
  */
365
365
  onDispose(callback: ArtifactCleanup): void;
366
366
  /**
367
- * Starts a streaming process for a Singleton artifact.
368
- * The callback receives an `ArtifactStreamContext` to emit values and manage the
369
- * stream's lifecycle. It can return a cleanup function (or a Promise resolving
370
- * to one) to handle resource disposal.
371
- * * @param callback - The streaming logic implementation. Can be synchronous or
372
- * asynchronous, optionally returning a cleanup function.
373
- * @throws {SystemError} If called on a Transient artifact, as they do not
374
- * support persistent streaming states.
375
- */
367
+ * Starts a streaming process for a Singleton artifact.
368
+ * The callback receives an `ArtifactStreamContext` to emit values and manage the
369
+ * stream's lifecycle. It can return a cleanup function (or a Promise resolving
370
+ * to one) to handle resource disposal.
371
+ * * @param callback - The streaming logic implementation. Can be synchronous or
372
+ * asynchronous, optionally returning a cleanup function.
373
+ * @throws {SystemError} If called on a Transient artifact, as they do not
374
+ * support persistent streaming states.
375
+ */
376
+ stream(callback: (ctx: ArtifactStreamContext<TState, TArtifact>) => (void | (() => void | Promise<void>)) | Promise<void | (() => void | Promise<void>)>): void;
376
377
  stream(callback: (ctx: ArtifactStreamContext<TState, TArtifact>) => (void | (() => void | Promise<void>)) | Promise<void | (() => void | Promise<void>)>): void;
377
- stream(callback: (ctx: ArtifactStreamContext<TState, TArtifact>) => (void | (() => (void | Promise<void>))) | Promise<(void | (() => (void | Promise<void>)))>): void;
378
378
  /**
379
379
  * An AbortSignal that indicates if the artifacts has been unregistered
380
380
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asaidimu/utils-artifacts",
3
- "version": "8.0.0",
3
+ "version": "8.1.0",
4
4
  "description": "Reactive artifact container.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",