@asaidimu/utils-artifacts 7.3.0 → 8.0.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.
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  A powerful TypeScript library for managing application components (artifacts) with dependency injection, reactive state updates, and robust lifecycle management. This library provides a flexible container to define, resolve, and orchestrate various parts of your application, each reacting dynamically to state changes and dependencies.
4
4
 
5
+ ## ✨ Unlocking Application Power with Reactive Artifacts
6
+
7
+ The `@asaidimu/utils-artifacts` library empowers developers to build highly organized, maintainable, and performant applications by providing a sophisticated reactive dependency injection container. It excels at managing complex application states and component lifecycles, ensuring that your application's pieces stay synchronized and up-to-date with minimal manual effort. Its design focuses on **declarative dependency management** and **automatic reactivity**, allowing developers to concentrate on business logic rather than intricate synchronization and lifecycle plumbing.
8
+
5
9
  [![npm version](https://img.shields.io/npm/v/@asaidimu/utils-artifacts.svg?style=flat-square)](https://www.npmjs.com/package/@asaidimu/utils-artifacts)
6
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
7
11
  [![Build Status](https://img.shields.io/github/actions/workflow/status/asaidimu/erp-utils/ci.yml?branch=main&style=flat-square)](https://github.com/asaidimu/erp-utils/actions/workflows/ci.yml)
@@ -11,6 +15,7 @@ A powerful TypeScript library for managing application components (artifacts) wi
11
15
  ## 🚀 Quick Links
12
16
 
13
17
  - [Overview & Features](#-overview--features)
18
+ - [Why Use This Library? (Power & Use Cases)](#-why-use-this-library-power--use-cases)
14
19
  - [Installation & Setup](#-installation--setup)
15
20
  - [Usage Documentation](#-usage-documentation)
16
21
  - [Basic Usage](#basic-usage)
@@ -22,9 +27,8 @@ A powerful TypeScript library for managing application components (artifacts) wi
22
27
  - [Streaming Artifacts](#streaming-artifacts)
23
28
  - [Invalidating Artifacts](#invalidating-artifacts)
24
29
  - [Debugging Artifacts](#debugging-artifacts)
25
- - [Retry Utility](#retry-utility)
26
- - [Concurrency Utilities (Once & Serializer)](#concurrency-utilities-once--serializer)
27
30
  - [Project Architecture](#-project-architecture)
31
+ - [Considerations & Potential Pitfalls](#-considerations--potential-pitfalls)
28
32
  - [Development & Contributing](#-development--contributing)
29
33
  - [Additional Information](#-additional-information)
30
34
 
@@ -36,26 +40,47 @@ A powerful TypeScript library for managing application components (artifacts) wi
36
40
 
37
41
  ### Key Features
38
42
 
39
- * **Dependency Injection (DI)**: Declare dependencies within artifact factories using `ctx.use`, `ctx.resolve`, and `ctx.require`.
40
- * **Reactive State Management**: Automatically re-evaluate artifacts when slices of a global `DataStore` (or any compatible state management system) change via `ctx.select`.
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.
41
45
  * **Flexible Scoping**: Supports `Singleton` (single, cached instance) and `Transient` (new instance per resolution) artifact scopes.
42
- * **Advanced Lifecycle Management**: `onCleanup` for instance-specific cleanup and `onDispose` for permanent resource release.
46
+ * **Advanced Lifecycle Management**: `onCleanup` for instance-specific resource release and `onDispose` for permanent artifact teardown.
43
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.
44
- * **Robust Error Handling & Retries**: Integrated retry logic (`Retry` utility) for resilient operations and a comprehensive `ArtifactError` hierarchy for clear diagnostics.
45
- * **Concurrency Primitives**: Internal `Once` and `Serializer` utilities for managing race conditions and sequential execution of async operations.
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.
46
50
  * **Debuggability**: `container.debugInfo()` provides a snapshot of the artifact graph, statuses, and dependencies for easy troubleshooting.
47
- * **Watcher API**: `container.watch()` allows external consumers to subscribe to artifact value changes without directly resolving them.
51
+ * **Watcher API**: `container.watch()` allows external consumers to subscribe to artifact changes without directly resolving them.
48
52
  * **Circular Dependency Detection**: Prevents infinite loops during resolution by detecting and reporting cycles in the dependency graph.
49
53
 
50
54
  ---
51
55
 
56
+ ## 🚀 Why Use This Library? (Power & Use Cases)
57
+
58
+ The `@asaidimu/utils-artifacts` library is designed to tackle the complexities of modern application development by providing a robust, reactive, and well-structured approach to managing application components and their interdependencies.
59
+
60
+ ### Core Power
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.
66
+
67
+ ### Key Use Cases
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.
74
+
75
+ ---
76
+
52
77
  ## 📦 Installation & Setup
53
78
 
54
79
  ### Prerequisites
55
80
 
56
81
  * Node.js (LTS recommended)
57
82
  * npm or Yarn (package manager)
58
- * A reactive data store library that adheres to the `DataStore` interface (e.g., `@asaidimu/utils-store`).
83
+ * A compatible reactive data store library (e.g., `@asaidimu/utils-store`) that adheres to the `DataStore` interface (providing `watch`, `get`, `set` methods).
59
84
 
60
85
  ### Installation Steps
61
86
 
@@ -112,7 +137,7 @@ The core of the library is the `ArtifactContainer`. You initialize it with a `Da
112
137
 
113
138
  ```typescript
114
139
  import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
115
- import { ReactiveDataStore } from '@asaidimu/utils-store';
140
+ import { ReactiveDataStore } from '@asaidimu/utils-store'; // Assuming this is your state store
116
141
 
117
142
  // 1. Define your application's global state and artifact registry types
118
143
  interface AppState {
@@ -131,7 +156,7 @@ interface AppRegistry {
131
156
  appInfo: string;
132
157
  }
133
158
 
134
- // Example DataStore (from @asaidimu/utils-store)
159
+ // Example DataStore (from @asaidimu/utils-store or similar)
135
160
  const appStore = new ReactiveDataStore<AppState>({
136
161
  appName: 'My App',
137
162
  version: '1.0.0',
@@ -144,7 +169,7 @@ const appStore = new ReactiveDataStore<AppState>({
144
169
  // 2. Instantiate the ArtifactContainer
145
170
  const container = new ArtifactContainer<AppRegistry, AppState>(appStore);
146
171
 
147
- // 3. Define and register your artifact factories
172
+ // Mock services for demonstration
148
173
  class LoggerService {
149
174
  constructor(private prefix: string) {}
150
175
  log(message: string) {
@@ -152,6 +177,11 @@ class LoggerService {
152
177
  }
153
178
  }
154
179
 
180
+ interface ApiClient {
181
+ fetchData(path: string): Promise<{ message: string }>;
182
+ }
183
+
184
+ // 3. Define and register your artifact factories
155
185
  container.register({
156
186
  key: 'logger',
157
187
  scope: ArtifactScopes.Singleton, // Ensure only one instance of the logger
@@ -211,10 +241,11 @@ async function main() {
211
241
  console.log(appInfo.instance); // Logs the app information after API call
212
242
 
213
243
  // Example of reacting to state changes
214
- console.log('\n--- Changing theme ---');
244
+ console.log('
245
+ --- Changing theme ---');
215
246
  await appStore.set(s => ({ ...s, config: { ...s.config, theme: 'dark' } }));
216
247
 
217
- // Because themeService depends on state.config.theme, it will be rebuilt
248
+ // Because themeService depends on state.config.theme, it will be rebuilt.
218
249
  // Any artifact that depends on themeService would also be rebuilt.
219
250
  const newTheme = await container.resolve('themeService');
220
251
  console.log('New Theme:', newTheme.instance); // Expected: dark
@@ -228,214 +259,247 @@ main();
228
259
  Use `container.register()` to define an artifact. Each artifact requires a unique `key` and a `factory` function.
229
260
 
230
261
  ```typescript
231
- import { ArtifactScopes } from '@asaidimu/utils-artifacts';
262
+ import { ArtifactScopes, ArtifactTemplate } from '@asaidimu/utils-artifacts';
232
263
 
233
- container.register({
234
- key: 'myArtifact',
235
- factory: () => 'Hello, Artifact!',
264
+ // Define your app's state and registry types for better type safety
265
+ interface MyAppState { /* ... */ }
266
+ interface MyAppRegistry { myService: string; }
267
+
268
+ // Type assertion for the container
269
+ const container = new ArtifactContainer<MyAppRegistry, MyAppState>(myStore);
270
+
271
+ // Example of registering an artifact
272
+ container.register<MyAppRegistry, MyAppState, 'myService'>({
273
+ key: 'myService',
274
+ factory: async (ctx) => {
275
+ // 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));
278
+
279
+ logger.log(`Initializing myService with API URL: ${apiUrl}`);
280
+ return `Initialized: ${apiUrl}`;
281
+ },
236
282
  // Optional parameters:
237
283
  scope: ArtifactScopes.Singleton, // 'singleton' (default) or 'transient'
238
- lazy: true, // true (default) for singletons, false to build on registration
239
- timeout: 5000, // Max time in ms for factory to complete
240
- retries: 3, // Number of retries on factory failure
241
- debounce: 100, // Delay in ms for rebuilding on dependency changes
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.
242
288
  });
243
289
  ```
244
290
 
245
- The `factory` function receives an `ArtifactFactoryContext` object:
291
+ The `factory` function receives an `ArtifactFactoryContext` object, providing access to dependencies, state, and lifecycle management:
246
292
 
247
293
  ```typescript
294
+ /**
295
+ * Context provided to an artifact's factory function.
296
+ * @template TRegistry The full artifact registry type.
297
+ * @template TState The global state type.
298
+ * @template TArtifact The type of the artifact being created.
299
+ */
248
300
  interface ArtifactFactoryContext<TRegistry, TState, TArtifact> {
249
- state(): TState; // Get current global state (non-reactive)
250
- previous?: TArtifact; // Previous instance (for singletons on rebuild)
301
+ /** Returns the current global state object. Non-reactive. */
302
+ state(): TState;
303
+ /** The previous instance of a singleton artifact (if available). */
304
+ previous?: TArtifact;
305
+ /** Executes a callback within a dependency tracking context. */
251
306
  use<R>(callback: (ctx: UseDependencyContext<TRegistry, TState>) => R | Promise<R>): Promise<R>;
252
- onCleanup(cleanup: ArtifactCleanup): void; // Register cleanup for current instance
253
- onDispose(callback: ArtifactCleanup): void; // Register cleanup for artifact (permanent)
254
- stream(callback: (ctx: ArtifactStreamContext<TState, TArtifact>) => ...): void; // Start streaming values (singletons only)
307
+ /** Registers a cleanup function for the current artifact instance. */
308
+ onCleanup(cleanup: ArtifactCleanup): void;
309
+ /** Registers a cleanup function for when the artifact is permanently disposed. */
310
+ onDispose(callback: ArtifactCleanup): void;
311
+ /** Starts a streaming process for a Singleton artifact. */
312
+ stream(callback: (ctx: ArtifactStreamContext<TState, TArtifact>) => ...): void;
255
313
  }
256
314
 
315
+ /**
316
+ * Context for resolving dependencies within `use()` callback.
317
+ * @template TRegistry The full artifact registry type.
318
+ * @template TState The global state type.
319
+ */
257
320
  interface UseDependencyContext<TRegistry, TState> {
258
- resolve<K extends keyof TRegistry>(key: K): Promise<ResolvedArtifact<TRegistry[K]>>; // Resolve an artifact (returns ResolvedArtifact)
259
- require<K extends keyof TRegistry>(key: K): Promise<TRegistry[K]>; // Resolve an artifact (throws on error, returns instance directly)
260
- select<S>(selector: (state: TState) => S): S; // Select state slice (reactive)
321
+ /** Resolves another artifact (returns `ResolvedArtifact`). */
322
+ resolve<K extends keyof TRegistry>(key: K): Promise<ResolvedArtifact<TRegistry[K]>>;
323
+ /** Resolves an artifact and throws on error (returns instance directly). */
324
+ require<K extends keyof TRegistry>(key: K): Promise<TRegistry[K]>;
325
+ /** Selects a slice of global state reactively. */
326
+ select<S>(selector: (state: TState) => S): S;
261
327
  }
262
328
  ```
263
329
 
264
330
  ### Resolving & Requiring Artifacts
265
331
 
266
- * `container.resolve(key)`: Returns a `Promise<ResolvedArtifact<T>>`. `ResolvedArtifact` is a union type that can be `ReadyArtifact`, `ErrorArtifact`, or `PendingArtifact`. You should check the `ready` and `error` properties.
267
- * `container.require(key)`: Returns a `Promise<T>` directly. If resolution fails or the artifact has an error, it will throw the error. Use this when you are certain the artifact will resolve successfully.
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.
268
334
 
269
335
  ```typescript
270
- import { ArtifactError } from '@asaidimu/utils-artifacts';
271
-
272
- // Using resolve (recommended for robust error handling)
273
- const myArtifactResult = await container.resolve('myArtifact');
274
- if (myArtifactResult.ready) {
275
- console.log('Artifact instance:', myArtifactResult.instance);
276
- } else if (myArtifactResult.error) {
277
- console.error('Artifact failed to resolve:', myArtifactResult.error);
336
+ // Using resolve for defensive programming
337
+ const myServiceResult = await container.resolve('myService');
338
+ if (myServiceResult.ready) {
339
+ console.log('Service instance:', myServiceResult.instance);
340
+ } else if (myServiceResult.error) {
341
+ console.error('Service failed:', myServiceResult.error);
278
342
  } else {
279
- console.log('Artifact is pending/idle.');
343
+ console.log('Service is pending or idle.');
280
344
  }
281
345
 
282
- // Using require (for simpler usage when errors are handled upstream or unexpected)
346
+ // Using require for direct access when errors are handled upstream
283
347
  try {
284
- const myArtifactInstance = await container.require('myArtifact');
285
- console.log('Artifact instance:', myArtifactInstance);
348
+ const myServiceInstance = await container.require('myService');
349
+ console.log('Service instance:', myServiceInstance);
286
350
  } catch (error) {
287
- if (error instanceof ArtifactError) {
288
- console.error('Artifact system error:', error.message);
289
- } else {
290
- console.error('Artifact runtime error:', error);
291
- }
351
+ console.error('Failed to get service:', error);
292
352
  }
293
353
  ```
294
354
 
295
355
  ### Watching Artifact Changes
296
356
 
297
- The `watch()` method provides an observer pattern to react to artifact changes without needing to repeatedly call `resolve()`. It's particularly useful for UI frameworks.
357
+ 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.
298
358
 
299
359
  ```typescript
300
- const myServiceWatcher = container.watch('myService');
360
+ const loggerWatcher = container.watch('logger');
301
361
 
302
- const unsubscribe = myServiceWatcher.subscribe((resolvedArtifact) => {
362
+ const unsubscribe = loggerWatcher.subscribe((resolvedArtifact) => {
303
363
  if (resolvedArtifact.ready) {
304
- console.log('myService updated:', resolvedArtifact.instance);
364
+ console.log('Logger updated:', resolvedArtifact.instance);
305
365
  } else if (resolvedArtifact.error) {
306
- console.error('myService error:', resolvedArtifact.error);
366
+ console.error('Logger error:', resolvedArtifact.error);
307
367
  }
308
- // The `get()` method can also be used inside the callback or outside
309
- // to get the current state of the artifact.
310
- const current = myServiceWatcher.get();
311
- console.log('Current state from get():', current.instance);
368
+ // `get()` retrieves the current resolved artifact state without subscribing.
369
+ const currentLogger = loggerWatcher.get();
370
+ console.log('Current logger:', currentLogger.instance);
312
371
  });
313
372
 
314
- // To stop receiving updates:
315
- unsubscribe();
373
+ // To stop listening for updates:
374
+ // unsubscribe();
316
375
  ```
317
376
 
318
377
  ### Reactive Dependencies (State Selection)
319
378
 
320
- Artifacts can react to changes in the global `DataStore` by using `ctx.select()`.
379
+ Artifacts can automatically react to changes in your application's global state by using `ctx.select()` within the `use` callback.
321
380
 
322
381
  ```typescript
323
- interface UserSettings { userId: string; theme: string; };
324
- type AppRegistry = { userPreference: string };
382
+ interface AppState {
383
+ user: { name: string; theme: 'light' | 'dark'; };
384
+ }
385
+ interface AppRegistry { userGreeting: string; }
325
386
 
326
- const userStore = new ReactiveDataStore<UserSettings>({ userId: 'guest', theme: 'light' });
327
- const userContainer = new ArtifactContainer<AppRegistry, UserSettings>(userStore);
387
+ // Assume appStore is an instance of DataStore<AppState>
388
+ const container = new ArtifactContainer<AppRegistry, AppState>(appStore);
328
389
 
329
- userContainer.register({
330
- key: 'userPreference',
390
+ container.register({
391
+ key: 'userGreeting',
331
392
  factory: async ({ use }) => {
332
- // This artifact will be rebuilt if state.theme changes
333
- const theme = await use(({ select }) => select(state => state.theme));
334
- return `Current theme is: ${theme}`;
393
+ // 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));
396
+ return `Hello, ${userName}! Your theme is ${theme}.`;
335
397
  },
336
398
  });
337
399
 
338
- async function runReactiveExample() {
339
- let preference = await userContainer.resolve('userPreference');
340
- console.log(preference.instance); // Output: Current theme is: light
341
-
342
- // Update the store, which will trigger 'userPreference' to rebuild
343
- await userStore.set({ theme: 'dark' });
344
-
345
- // Resolve again to get the new instance
346
- preference = await userContainer.resolve('userPreference');
347
- console.log(preference.instance); // Output: Current theme is: dark
348
- }
349
-
350
- runReactiveExample();
400
+ // Example of state change triggering rebuild
401
+ await appStore.set(state => ({ ...state, user: { ...state.user, theme: 'dark' } }));
402
+ const greeting = await container.resolve('userGreeting');
403
+ console.log(greeting.instance); // Output will reflect the new theme.
351
404
  ```
352
405
 
353
406
  ### Artifact Lifecycle (Cleanup & Dispose)
354
407
 
355
- * `ctx.onCleanup(fn)`: Registers a function to run *before* a singleton artifact is rebuilt (due to invalidation) or before a transient artifact instance is discarded. Use this for instance-specific resource release (e.g., clearing timers, event listeners).
356
- * `ctx.onDispose(fn)`: Registers a function to run *only when the artifact is permanently unregistered* from the container or the container itself is disposed. Use this for permanent resource release (e.g., closing database connections, unsubscribing from global events).
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).
357
410
 
358
411
  ```typescript
359
412
  container.register({
360
- key: 'myResource',
413
+ key: 'resourceArtifact',
361
414
  scope: ArtifactScopes.Singleton,
362
- factory: ({ onCleanup, onDispose }) => {
363
- const resource = { id: Math.random(), intervalId: setInterval(() => {}, 1000) };
364
- console.log(`Resource ${resource.id} created.`);
415
+ factory: ({ onCleanup, onDispose, use }) => {
416
+ const resourceId = Math.random();
417
+ console.log(`Resource ${resourceId} created.`);
418
+ const timerId = setInterval(() => console.log(`Resource ${resourceId} heartbeat...`), 1000);
365
419
 
420
+ // Cleanup for the current instance (e.g., on rebuild or if transient)
366
421
  onCleanup(() => {
367
- console.log(`Cleaning up instance ${resource.id}...`);
368
- clearInterval(resource.intervalId);
422
+ console.log(`Cleaning up instance for resource ${resourceId}...`);
423
+ clearInterval(timerId);
369
424
  });
370
425
 
426
+ // Dispose for permanent removal from container
371
427
  onDispose(() => {
372
- console.log(`Disposing artifact 'myResource'.`);
373
- // Additional permanent resource release here
428
+ console.log(`Disposing artifact 'resourceArtifact' (resource ${resourceId}).`);
429
+ // Final resource release
374
430
  });
375
431
 
376
- return resource;
432
+ return { id: resourceId };
377
433
  },
378
434
  });
379
435
 
380
436
  async function lifecycleExample() {
381
- await container.resolve('myResource');
382
- // Simulate an invalidation (e.g., a dependency changed)
383
- await container.invalidate('myResource'); // Triggers cleanup, then rebuilds
384
- await container.resolve('myResource'); // A new instance is now resolved.
437
+ await container.resolve('resourceArtifact'); // Instance created and logs "heartbeat..."
438
+ console.log('Artifact resolved.');
385
439
 
386
- // When unregistering, onDispose is called
387
- await container.unregister('myResource'); // Triggers onCleanup (if active), then onDispose
440
+ // Simulate invalidation: will trigger onCleanup for the current instance, then rebuild.
441
+ await container.invalidate('resourceArtifact');
442
+ console.log('Artifact invalidated and rebuilt.');
443
+
444
+ // Manually dispose the artifact and container
445
+ await container.unregister('resourceArtifact'); // Triggers onDispose
446
+ console.log('Artifact unregistered.');
388
447
  }
389
448
  lifecycleExample();
390
449
  ```
391
450
 
392
451
  ### Streaming Artifacts
393
452
 
394
- Singletons can continuously emit new values using `ctx.stream()`. This is powerful for reactive data sources.
453
+ For artifacts that continuously emit values (e.g., real-time data, streams), use `ctx.stream()`. This is only supported for `Singleton` artifacts.
395
454
 
396
455
  ```typescript
397
456
  container.register({
398
- key: 'counterStream',
457
+ key: 'dataStream',
399
458
  scope: ArtifactScopes.Singleton,
400
- factory: ({ stream, onCleanup }) => {
401
- let count = 0;
402
- let interval: ReturnType<typeof setInterval>;
459
+ factory: ({ stream, onCleanup, use }) => {
460
+ let counter = 0;
461
+ let intervalId: ReturnType<typeof setInterval> | undefined;
462
+
463
+ 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.');
403
467
 
404
- stream(async ({ emit, signal }) => {
405
- console.log('Counter stream started...');
406
- interval = setInterval(() => {
468
+ intervalId = setInterval(() => {
407
469
  if (signal.aborted) {
408
- console.log('Stream aborted, stopping interval.');
409
- clearInterval(interval);
470
+ console.log('Stream signal aborted. Clearing interval.');
471
+ clearInterval(intervalId);
410
472
  return;
411
473
  }
412
- count++;
413
- emit(count); // Emit the new value
414
- if (count >= 5) {
415
- console.log('Count limit reached, stopping stream.');
416
- clearInterval(interval);
417
- return; // Stream producer can return to end the stream
474
+ counter++;
475
+ const newValue = `Data update #${counter}`;
476
+ console.log(`Emitting: ${newValue}`);
477
+ emit(newValue); // Emit the new value to the container
478
+ if (counter >= 5) {
479
+ console.log('Stream reached limit, stopping.');
480
+ clearInterval(intervalId);
481
+ // The factory function can return to stop the stream producer.
418
482
  }
419
483
  }, 500);
420
484
  });
421
485
 
422
486
  onCleanup(() => {
423
- console.log('Cleaning up counter stream instance...');
424
- // Ensure interval is cleared if stream is aborted/rebuilt
425
- clearInterval(interval);
487
+ console.log('Cleaning up data stream instance...');
488
+ // Ensure interval is cleared if stream is aborted or rebuilt.
489
+ if (intervalId) clearInterval(intervalId);
426
490
  });
427
491
 
428
- return 0; // Initial value before stream starts emitting
492
+ return 'Initial stream value'; // Initial value before first emission
429
493
  },
430
494
  });
431
495
 
432
496
  async function streamExample() {
433
- const watcher = container.watch('counterStream');
497
+ const watcher = container.watch('dataStream');
434
498
  const unsubscribe = watcher.subscribe((art) => {
435
- if (art.ready) console.log('Counter value:', art.instance);
499
+ if (art.ready) console.log('Stream received:', art.instance);
436
500
  });
437
501
 
438
- // Keep alive for a few seconds to see stream emissions
502
+ // Keep alive for a few seconds to observe stream emissions
439
503
  await new Promise(res => setTimeout(res, 3000));
440
504
  unsubscribe();
441
505
  console.log('Stream watcher unsubscribed.');
@@ -445,26 +509,27 @@ streamExample();
445
509
 
446
510
  ### Invalidating Artifacts
447
511
 
448
- You can manually trigger an artifact to rebuild, which will also cascade invalidations to its dependents.
512
+ Manually trigger an artifact to rebuild and cascade invalidations to its dependents.
449
513
 
450
514
  ```typescript
451
- // Invalidate a specific artifact
515
+ // Invalidate an artifact; it will rebuild if it has dependents, is lazy and watched, or eagerly.
452
516
  await container.invalidate('myArtifact');
453
517
 
454
- // Force immediate rebuild, bypassing any debounce delay
518
+ // Force immediate rebuild, bypassing any debounce delay.
455
519
  await container.invalidate('myArtifact', true);
456
520
  ```
457
521
 
458
522
  ### Debugging Artifacts
459
523
 
460
- The `debugInfo()` method provides a snapshot of the container's internal state, useful for understanding dependencies, status, and build counts.
524
+ The `container.debugInfo()` method provides a snapshot of the container's internal state, which is invaluable for understanding dependencies, status, and build counts.
461
525
 
462
526
  ```typescript
463
527
  const debugNodes = container.debugInfo();
464
528
  debugNodes.forEach(node => {
465
- console.log(`\nID: ${node.id}`);
529
+ console.log(`
530
+ Artifact ID: ${node.id}`);
466
531
  console.log(` Scope: ${node.scope}`);
467
- console.log(` Status: ${node.status}`); // active, error, idle, building, pending, debouncing
532
+ console.log(` Status: ${node.status}`); // e.g., 'active', 'error', 'idle', 'building', 'pending', 'debouncing'
468
533
  console.log(` Dependencies (Artifacts): ${node.dependencies.join(', ') || 'None'}`);
469
534
  console.log(` Dependencies (State Paths): ${node.stateDependencies.join(', ') || 'None'}`);
470
535
  console.log(` Dependents: ${node.dependents.join(', ') || 'None'}`);
@@ -472,177 +537,86 @@ debugNodes.forEach(node => {
472
537
  });
473
538
  ```
474
539
 
475
- ### Retry Utility
540
+ ---
476
541
 
477
- The `Retry` class provides flexible retry logic for any async operation, supporting various strategies.
542
+ ## 🏗️ Project Architecture
478
543
 
479
- ```typescript
480
- import { Retry, RetryExhaustedError, RetryPredicates } from '@asaidimu/utils-artifacts/retry';
544
+ The `@asaidimu/utils-artifacts` library is architected around a central `ArtifactContainer` that orchestrates several specialized internal components:
481
545
 
482
- const unreliableOperation = async (attempt: number) => {
483
- if (attempt < 3) {
484
- console.log(`Unreliable operation failing on attempt ${attempt}`);
485
- throw new Error('Transient network error');
486
- }
487
- console.log(`Unreliable operation succeeding on attempt ${attempt}`);
488
- return 'Success!';
489
- };
490
-
491
- async function runRetryExample() {
492
- try {
493
- const result = await Retry.execute(
494
- () => unreliableOperation(retryAttempt), // `retryAttempt` is for demonstration, actual attempt is internal.
495
- {
496
- retries: 4, // Total attempts: 1 (initial) + 4 (retries) = 5
497
- strategy: 'exponential',
498
- delay: 100, // Base delay 100ms
499
- factor: 2, // Multiplier 2x (100, 200, 400, 800)
500
- maxDelay: 1000,
501
- onRetry: (err, attempt, nextDelay) => {
502
- console.warn(`Attempt ${attempt} failed: ${err}. Retrying in ${nextDelay}ms.`);
503
- retryAttempt = attempt; // For demonstration only
504
- },
505
- }
506
- );
507
- console.log('Retry successful:', result);
508
- } catch (e) {
509
- if (e instanceof RetryExhaustedError) {
510
- console.error(`Retry exhausted after ${e.attempts} attempts. Last error:`, e.lastError);
511
- } else {
512
- console.error('Unexpected error:', e);
513
- }
514
- }
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.
515
552
 
516
- // Example with conditional retry based on error type
517
- const fetchWithRetry = async (url: string) => {
518
- return Retry.execute(
519
- async () => {
520
- const response = await fetch(url);
521
- if (response.status >= 500) {
522
- throw { status: response.status, message: 'Server error' }; // Simulate HTTP 5xx
523
- }
524
- return response.json();
525
- },
526
- {
527
- retries: 5,
528
- strategy: 'conditional',
529
- shouldRetry: RetryPredicates.any(
530
- RetryPredicates.networkErrors,
531
- RetryPredicates.serverErrors,
532
- RetryPredicates.httpStatus(429) // Also retry on Too Many Requests
533
- ),
534
- delay: (attempt) => Math.min(100 * Math.pow(2, attempt), 2000), // Custom delay function
535
- }
536
- );
537
- };
553
+ ### Data Flow Example (Resolution & Reactivity)
538
554
 
539
- // const data = await fetchWithRetry('https://api.example.com/data');
540
- }
541
- let retryAttempt = 0; // Used for demonstration purposes only
542
- runRetryExample();
543
- ```
555
+ 1. **Registration**: An artifact (`key`, `factory`, `scope`, etc.) is registered with the `ArtifactRegistry`. The `ArtifactDependencyGraph` registers a node for this artifact.
556
+ 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.
566
+ 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.
573
+
574
+ ### Core Concepts
575
+
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.
544
581
 
545
- ### Concurrency Utilities (Once & Serializer)
582
+ ---
546
583
 
547
- `Once` ensures a function runs exactly one time, caching its result. `Serializer` ensures functions run sequentially. These are primarily used internally but are exposed for advanced use cases.
584
+ ## 📜 Considerations & Potential Pitfalls
548
585
 
549
- ```typescript
550
- import { Once, Serializer } from '@asaidimu/utils-artifacts/sync';
551
-
552
- async function runOnceExample() {
553
- const initialization = new Once<string>();
554
- const expensiveInit = async () => {
555
- console.log('Performing expensive initialization...');
556
- await new Promise(res => setTimeout(res, 200));
557
- return 'Initialized Resource';
558
- };
586
+ While `@asaidimu/utils-artifacts` offers powerful features, understanding its operational nuances and potential trade-offs is key to leveraging it effectively and avoiding common issues.
559
587
 
560
- const [res1, res2, res3] = await Promise.all([
561
- initialization.do(expensiveInit),
562
- initialization.do(expensiveInit),
563
- initialization.do(expensiveInit),
564
- ]);
588
+ ### Debouncing Latency
565
589
 
566
- console.log(res1.value, res2.value, res3.value); // All will be 'Initialized Resource'
567
- // expensiveInit will be called only once.
568
- }
569
- runOnceExample();
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.
570
592
 
571
- async function runSerializerExample() {
572
- const queue = new Serializer<string>();
573
- const order: string[] = [];
593
+ ### Wasted Build Effort from Late Staleness Detection
574
594
 
575
- const task1 = async () => {
576
- await new Promise(res => setTimeout(res, 100));
577
- order.push('Task 1');
578
- return 'Result 1';
579
- };
580
- const task2 = async () => {
581
- order.push('Task 2');
582
- return 'Result 2';
583
- };
584
- const task3 = async () => {
585
- await new Promise(res => setTimeout(res, 50));
586
- order.push('Task 3');
587
- return 'Result 3';
588
- };
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.
589
597
 
590
- await Promise.all([
591
- queue.do(task1),
592
- queue.do(task2),
593
- queue.do(task3),
594
- ]);
598
+ ### Efficiency of Global State Watching
595
599
 
596
- console.log(order); // Expected: ['Task 1', 'Task 2', 'Task 3']
597
- }
598
- runSerializerExample();
599
- ```
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.
600
605
 
601
- ---
606
+ ### Startup Cost of Eager Loading
602
607
 
603
- ## 🏗️ Project Architecture
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.
604
610
 
605
- The `ArtifactContainer` is designed with a modular architecture, delegating responsibilities to specialized internal components:
611
+ ### Sequential Cleanup/Dispose Execution
606
612
 
607
- * **`ArtifactContainer`**: The public API entry point. It orchestrates interactions between the other internal components, providing a unified interface for artifact management.
608
- * **`ArtifactRegistry`**: Stores the definitions (`ArtifactTemplate`s) of all registered artifacts, mapping unique keys to their factory functions and configuration options.
609
- * **`ArtifactCache`**: Manages the storage and retrieval of resolved artifact instances, particularly for `Singleton` scoped artifacts. It handles caching, versioning, and state associated with active instances.
610
- * **`ArtifactDependencyGraph`**: A bidirectional graph (`DependencyGraph`) that maps artifact-to-artifact dependencies and tracks which artifacts depend on state paths. This is crucial for circular dependency detection and efficient invalidation cascades.
611
- * **`ArtifactManager`**: The core lifecycle manager. It handles the intricate process of building artifacts (executing factories), managing retries and timeouts, orchestrating `onCleanup`/`onDispose`, propagating stream emissions, and managing the reactive invalidation process based on artifact and state dependencies.
612
- * **`ArtifactObserverManager`**: Manages the `container.watch()` API, maintaining subscriptions from external consumers and notifying them of artifact state changes. It handles reference counting and lazy initialization of watched artifacts.
613
- * **`Retry`**: A standalone utility providing flexible retry mechanisms (fixed, exponential, jittered, conditional) for any asynchronous operation.
614
- * **`Once` & `Serializer`**: Low-level concurrency primitives used internally (and exposed) to ensure that asynchronous operations (like artifact builds or stream emissions) execute safely and predictably, avoiding race conditions.
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.
615
615
 
616
- ### Data Flow
616
+ ### Dependency Graph Complexity
617
617
 
618
- 1. **Registration**: An `ArtifactTemplate` is registered with the `ArtifactRegistry`. A corresponding node is added to the `ArtifactDependencyGraph`.
619
- 2. **Resolution (`container.resolve`)**:
620
- * The `ArtifactContainer` forwards the request to the `ArtifactManager`.
621
- * The `Manager` consults the `ArtifactCache`. If a `Singleton` is already built and fresh, it's returned immediately.
622
- * Otherwise, the `Manager` retrieves the `ArtifactTemplate` from the `Registry`.
623
- * A factory is executed with an `ArtifactFactoryContext`.
624
- * Inside the factory:
625
- * `ctx.use(({ resolve }) => ...)`: Triggers recursive resolution of dependent artifacts. The `Manager` performs cycle detection via the `DependencyGraph` and updates artifact dependencies.
626
- * `ctx.use(({ select }) => ...)`: Registers state path dependencies with the `Manager`, which then subscribes to these paths via the `DataStore`.
627
- * `ctx.stream(...)`: For singletons, registers a stream producer that can `emit` new values.
628
- * `ctx.onCleanup`/`onDispose`: Registers lifecycle hooks.
629
- * The `Manager` handles retries for factory execution and commits the resulting instance (or error) to the `ArtifactCache`.
630
- * The `ArtifactCache` packages the result into a `ResolvedArtifact` for the consumer.
631
- 3. **Invalidation**:
632
- * **State Change**: A change in the `DataStore` (observed by `ArtifactManager` via `store.watch()`) triggers invalidation of dependent artifacts.
633
- * **Artifact Change**: A dependency being rebuilt or a stream emitting a new value, or manual `container.invalidate()` triggers invalidation.
634
- * The `ArtifactManager` runs the `onCleanup` hooks for the old instance, removes it from the `ArtifactCache`, and uses the `DependencyGraph` to identify and cascade invalidations to all affected dependents.
635
- * If configured (e.g., not lazy, has watchers), the `Manager` triggers a rebuild for the artifact.
636
- * Finally, `ArtifactObserverManager` notifies all active watchers.
637
-
638
- ### Extension Points
639
-
640
- The primary extension point is the `ArtifactFactory` function itself, which receives the `ArtifactFactoryContext`. This context allows artifacts to:
641
-
642
- * Declare and react to external dependencies (other artifacts, global state).
643
- * Manage their internal lifecycle (cleanup, disposal).
644
- * Create streaming data sources.
645
- * Integrate with the underlying `DataStore` for dispatching actions.
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.
646
620
 
647
621
  ---
648
622
 
@@ -650,8 +624,6 @@ The primary extension point is the `ArtifactFactory` function itself, which rece
650
624
 
651
625
  ### Development Setup
652
626
 
653
- To set up the project for local development:
654
-
655
627
  1. **Clone the repository:**
656
628
  ```bash
657
629
  git clone https://github.com/asaidimu/erp-utils.git
@@ -663,44 +635,23 @@ To set up the project for local development:
663
635
  # or
664
636
  yarn install
665
637
  ```
666
- 3. **Build the project (if applicable, though typically handled by IDE/watch mode):**
667
- ```bash
668
- npm run build # Or `tsc` if not defined in package.json scripts
669
- ```
670
638
 
671
639
  ### Scripts
672
640
 
673
- The `package.json` defines the following scripts:
674
-
675
641
  * `npm test`: Runs all tests once.
676
642
  * `npm test:watch`: Runs tests in watch mode, rerunning on file changes.
677
- * `npm test:browser`: Runs tests in a browser environment (if configured), typically once.
678
643
 
679
644
  ### Testing
680
645
 
681
- The project uses `vitest` for testing.
682
-
683
- * To run all tests: `npm test`
684
- * To run tests continuously during development: `npm test:watch`
685
-
686
- Tests utilize `fake-indexeddb` as seen in `vitest.setup.ts`, ensuring a consistent environment.
646
+ The project uses `vitest` for testing. All new code should be accompanied by tests.
687
647
 
688
648
  ### Contributing Guidelines
689
649
 
690
- We welcome contributions! Please follow these guidelines:
691
-
692
- 1. **Fork the repository** and create your branch from `main`.
693
- 2. **Ensure code quality**: Write clean, readable TypeScript code. Adhere to existing coding style (ESLint and Prettier are typically configured in parent project).
694
- 3. **Tests**: All new features and bug fixes should be accompanied by appropriate unit or integration tests. Ensure existing tests pass.
695
- 4. **Commit Messages**: Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for clear and consistent commit history. (e.g., `feat: add new artifact scope`, `fix: resolve circular dependency issue`).
696
- 5. **Pull Requests**:
697
- * Open a detailed Pull Request describing the changes, new features, or bug fixes.
698
- * Reference any related issues.
699
- * Ensure your branch is up-to-date with `main`.
700
-
701
- ### Issue Reporting
702
-
703
- If you find a bug or have a feature request, please open an issue on the [GitHub Issues page](https://github.com/asaidimu/erp-utils/issues). Provide as much detail as possible, including steps to reproduce bugs and clear descriptions for feature requests.
650
+ 1. **Fork and Branch**: Fork the repository and create a new branch from `main`.
651
+ 2. **Code Quality**: Write clean, readable TypeScript code adhering to project conventions.
652
+ 3. **Tests**: Add unit/integration tests for all new features and bug fixes.
653
+ 4. **Commit Messages**: Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) (e.g., `feat: add streaming support`, `fix: correct debounce logic`).
654
+ 5. **Pull Requests**: Submit PRs with clear descriptions and link to relevant issues.
704
655
 
705
656
  ---
706
657
 
@@ -708,33 +659,28 @@ If you find a bug or have a feature request, please open an issue on the [GitHub
708
659
 
709
660
  ### Troubleshooting
710
661
 
711
- * **`ArtifactNotFoundError`**: This means you're trying to `resolve` or `watch` an artifact that hasn't been `register`ed. Double-check your artifact keys and ensure registration happens before resolution.
712
- * **`CircularDependencyError`**: This occurs when your artifact graph forms a loop (e.g., A depends on B, B depends on A). The `debugInfo()` output and the error message's path can help you identify the cycle. Redesign your dependencies to break the cycle.
713
- * **`TimeoutError`**: Your artifact factory took longer than the specified `timeout` during registration. This can indicate long-running sync operations, slow async dependencies, or an infinite loop. Increase the timeout or optimize your factory.
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.
714
665
  * **Unexpected Rebuilds/No Rebuilds**:
715
- * Use `container.debugInfo()` to inspect artifact statuses and `stateDependencies`/`dependencies`.
716
- * Ensure your `ctx.select()` selectors correctly identify the state slices you intend to react to.
717
- * Check `debounce` settings if an artifact seems to rebuild too frequently or too slowly.
718
- * Verify `onCleanup` and `onDispose` hooks are being called as expected to rule out resource leaks.
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.
719
670
 
720
671
  ### FAQ
721
672
 
722
- * **What's the difference between `Singleton` and `Transient` scopes?**
723
- * `Singleton`: Only one instance of the artifact is ever created. Subsequent `resolve` calls return the same instance. This is suitable for services, configurations, or shared resources. They support state dependencies, `onCleanup`/`onDispose`, and `stream`.
724
- * `Transient`: A new instance is created every time the artifact is resolved. Useful for ephemeral objects that should not be shared or cached. They do not support `stream` or persistent `onCleanup`/`onDispose` (though a single `cleanup` can be returned by `resolve`).
725
- * **When should I use `resolve` versus `require`?**
726
- * Use `resolve` when you need to defensively handle potential errors or pending states of an artifact. It returns a `ResolvedArtifact` object that allows explicit checks (`.ready`, `.error`).
727
- * Use `require` when you are confident the artifact will resolve successfully and prefer to get the instance directly, allowing errors to propagate as exceptions. This simplifies code where error handling is done at a higher level.
728
- * **How does `debounce` work for invalidation?**
729
- `debounce` adds a delay (in milliseconds) before an artifact rebuilds after its dependencies change. If multiple dependency changes occur within this debounce period, the rebuild is aggregated into a single event, preventing excessive rapid rebuilds. `container.invalidate(key, true)` can bypass this debounce.
730
- * **What is the purpose of `onCleanup` vs. `onDispose`?**
731
- * `onCleanup`: Tied to the *current instance's lifecycle*. It runs when a `Singleton` artifact's instance is replaced (e.g., due to an invalidation and rebuild), or when a `Transient` instance is returned and subsequently discarded. Use for resources specific to that particular instance.
732
- * `onDispose`: Tied to the *artifact's registration lifecycle*. It runs only when the artifact is permanently `unregister`ed from the container or when the container itself is `dispose`d. Use for resources that should persist across instance rebuilds but be released when the artifact itself is no longer managed.
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.
733
682
 
734
- ### Changelog/Roadmap
735
-
736
- For detailed version history, please refer to the [CHANGELOG.md](https://github.com/asaidimu/erp-utils/blob/main/CHANGELOG.md) in the main repository.
737
- Future plans and roadmap items are tracked via GitHub issues and milestones.
683
+ ---
738
684
 
739
685
  ### License
740
686
 
@@ -742,4 +688,4 @@ This project is licensed under the [MIT License](https://github.com/asaidimu/erp
742
688
 
743
689
  ### Acknowledgments
744
690
 
745
- This library is part of the `@asaidimu/erp-utils` monorepository and integrates closely with `@core/store/types` (specifically, the `DataStore` interface) for reactive state management.
691
+ This library is part of the `@asaidimu/erp-utils` monorepository and relies on a compatible `DataStore` implementation for reactive state management.