@asaidimu/utils-artifacts 8.2.3 → 8.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,715 +1,347 @@
1
1
  # @asaidimu/utils-artifacts
2
2
 
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
-
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
-
9
- [![npm version](https://img.shields.io/npm/v/@asaidimu/utils-artifacts.svg?style=flat-square)](https://www.npmjs.com/package/@asaidimu/utils-artifacts)
10
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
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)
3
+ Reactive dependency injection and lifecycle management for TypeScript applications.
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Overview](#overview)
10
+ 2. [Core Concepts](#core-concepts)
11
+ 3. [Quick Start](#quick-start)
12
+ 4. [Usage](#usage)
13
+ - [Registering Artifacts](#registering-artifacts)
14
+ - [Resolving Dependencies](# resolves-dependencies)
15
+ - [Streaming Artifacts](#streaming-artifacts)
16
+ - [Parameterized Artifacts](#parameterized-artifacts)
17
+ - [Lifecycle Management](#lifecycle-management)
18
+ - [Observing Artifacts](#observing-artifacts)
19
+ 5. [Architecture](#architecture)
20
+ 6. [Development](#development)
21
+ 7. [Testing](#testing)
22
+ 8. [API Reference](#api-reference)
12
23
 
13
24
  ---
14
25
 
15
- ## 🚀 Quick Links
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)
34
-
35
- ---
36
-
37
- ## ✨ Overview & Features
38
-
39
- `@asaidimu/utils-artifacts` is a reactive dependency injection container designed to bring order and efficiency to complex application architectures. It allows you to define application components (called "artifacts") as factories that declare their dependencies on other artifacts and global application state. The container automatically manages the lifecycle, instantiation, and invalidation of these artifacts, ensuring that components are always up-to-date with their dependencies.
40
-
41
- ### Key Features
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.
53
-
54
- ---
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.
26
+ ## 1. Overview
74
27
 
75
- ---
28
+ `@asaidimu/utils-artifacts` is a high-performance TypeScript library for managing the lifecycle, dependencies, and reactivity of application components, referred to as **artifacts**.
76
29
 
77
- ## đŸ“Ļ Installation & Setup
30
+ Unlike traditional Dependency Injection (DI) containers, this system implements a **Lazy-Resolution, Lazy-Invalidation** model. This ensures that components are created only when requested and are rebuilt only when their specific dependencies change, minimizing unnecessary re-computations and avoiding the manual orchestration of event listeners in the UI layer.
78
31
 
79
- ### Prerequisites
32
+ ## 2. Core Concepts
80
33
 
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).
34
+ To use the library effectively, it is essential to understand the mental model of an artifact's lifecycle:
84
35
 
85
- ### Installation Steps
36
+ ### The Artifact Lifecycle
86
37
 
87
- To install the library, use your preferred package manager:
38
+ An artifact transitions through the following states:
88
39
 
89
- ```bash
90
- npm install @asaidimu/utils-artifacts
91
- # or
92
- yarn add @asaidimu/utils-artifacts
93
- ```
40
+ - **Idle**: The artifact is registered but has not yet been resolved. No resources are consumed.
41
+ - **Pending**: The factory function is currently executing (asynchronous resolution).
42
+ - **Ready**: The artifact instance is available and cached.
43
+ - **Invalidated**: A dependency change has occurred. The cached instance is marked as stale, but the artifact is not rebuilt immediately. It returns to the **Idle** state for the next `resolve()` call.
94
44
 
95
- ### Configuration
45
+ ### Singleton vs. Transient
96
46
 
97
- `@asaidimu/utils-artifacts` is a library and does not require global configuration files. Its primary setup involves initializing the `ArtifactContainer` with an instance of a `DataStore`.
47
+ - **Singleton**: A single instance is shared across the entire container. Ideal for services, API clients, and shared state managers.
48
+ - **Transient**: A fresh instance is created on every `resolve()` call. Ideal for lightweight utility functions or state transformations.
98
49
 
99
- ### Verification
100
-
101
- You can verify the installation by attempting to import and register a basic artifact:
50
+ ## 3. Quick Start
102
51
 
103
52
  ```typescript
53
+ import { ReactiveDataStore } from "@asaidimu/utils-store";
104
54
  import { ArtifactContainer } from "@asaidimu/utils-artifacts";
105
- import { ReactiveDataStore } from "@asaidimu/utils-store"; // Assuming you have this store
106
-
107
- // Define your application's global state and artifact registry types
108
- type AppState = { count: number };
109
- type AppRegistry = { myService: string };
110
55
 
111
- const store = new ReactiveDataStore<AppState>({ count: 0 });
112
- const container = new ArtifactContainer<AppRegistry, AppState>(store);
113
-
114
- container.register({
115
- key: "myService",
116
- factory: async ({ use }) => {
117
- const currentCount = await use(({ select }) => select((s) => s.count));
118
- return `Service is running with count: ${currentCount}`;
119
- },
120
- });
121
-
122
- async function runExample() {
123
- const service = await container.resolve("myService");
124
- console.log(service.instance); // Expected: Service is running with count: 0
56
+ interface State {
57
+ count: number;
125
58
  }
126
59
 
127
- runExample();
128
- ```
129
-
130
- ---
131
-
132
- ## 📖 Usage Documentation
133
-
134
- ### Basic Usage
135
-
136
- The core of the library is the `ArtifactContainer`. You initialize it with a `DataStore` that manages your application's global state.
137
-
138
- ```typescript
139
- import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
140
- import { ReactiveDataStore } from '@asaidimu/utils-store'; // Assuming this is your state store
141
-
142
- // 1. Define your application's global state and artifact registry types
143
- interface AppState {
144
- appName: string;
145
- version: string;
146
- config: {
147
- theme: 'light' | 'dark';
148
- apiUrl: string;
149
- };
60
+ interface Registry {
61
+ counterLabel: string;
150
62
  }
151
63
 
152
- interface AppRegistry {
153
- logger: LoggerService;
154
- apiClient: ApiClient;
155
- themeService: 'light' | 'dark';
156
- appInfo: string;
157
- }
158
-
159
- // Example DataStore (from @asaidimu/utils-store or similar)
160
- const appStore = new ReactiveDataStore<AppState>({
161
- appName: 'My App',
162
- version: '1.0.0',
163
- config: {
164
- theme: 'light',
165
- apiUrl: 'https://api.example.com',
166
- },
167
- });
168
-
169
- // 2. Instantiate the ArtifactContainer
170
- const container = new ArtifactContainer<AppRegistry, AppState>(appStore);
171
-
172
- // Mock services for demonstration
173
- class LoggerService {
174
- constructor(private prefix: string) {}
175
- log(message: string) {
176
- console.log(`[${this.prefix}] ${message}`);
177
- }
178
- }
179
-
180
- interface ApiClient {
181
- fetchData(path: string): Promise<{ message: string }>;
182
- }
183
-
184
- // 3. Define and register your artifact factories
185
- container.register({
186
- key: 'logger',
187
- scope: ArtifactScopes.Singleton, // Ensure only one instance of the logger
188
- factory: () => new LoggerService('APP'),
189
- });
64
+ const store = new ReactiveDataStore<State>({ count: 0 });
65
+ const container = new ArtifactContainer<Registry, State>(store);
190
66
 
67
+ // Register a reactive artifact
191
68
  container.register({
192
- key: 'apiClient',
193
- scope: ArtifactScopes.Singleton,
69
+ key: "counterLabel",
194
70
  factory: async ({ use }) => {
195
- // ApiClient depends on the logger and state.config.apiUrl
196
- const logger = await use(({ require }) => require('logger'));
197
- const apiUrl = await use(({ select }) => select(state => state.config.apiUrl));
198
-
199
- logger.log(`Initializing API client for ${apiUrl}`);
200
- return {
201
- fetchData: async (path: string) => {
202
- logger.log(`Fetching from ${apiUrl}${path}`);
203
- // Simulate API call
204
- await new Promise(res => setTimeout(res, 100));
205
- return { message: `Data from ${apiUrl}${path}` };
206
- }
207
- };
71
+ const count = await use((ctx) => ctx.select((s) => s.count));
72
+ return `The current count is ${count}`;
208
73
  },
209
74
  });
210
75
 
211
- container.register({
212
- key: 'themeService',
213
- scope: ArtifactScopes.Singleton,
214
- factory: async ({ use }) => {
215
- // Theme service depends directly on state
216
- return await use(({ select }) => select(state => state.config.theme));
217
- },
218
- });
76
+ // Resolve the artifact
77
+ const result = await container.resolve("counterLabel");
78
+ console.log(result.instance); // "The current count is 0"
219
79
 
220
- container.register({
221
- key: 'appInfo',
222
- scope: ArtifactScopes.Singleton,
223
- factory: async ({ use }) => {
224
- // App info depends on other artifacts and state
225
- const logger = await use(({ require }) => require('logger'));
226
- const apiClient = await use(({ require }) => require('apiClient'));
227
- const appName = await use(({ select }) => select(s => s.appName));
228
-
229
- const data = await apiClient.fetchData('/info');
230
- logger.log(`App Info: ${appName}, Data: ${data.message}`);
231
- return `App: ${appName}, API Data: ${data.message}`;
232
- },
80
+ // watch the artifact
81
+ const observer = container.watch("counterLabel");
82
+ observer.subscribe((result) => {
83
+ console.log(`Observed: ${result.instance}`); // "The current count is 0"
233
84
  });
234
85
 
235
- // 4. Resolve artifacts to use them
236
- async function main() {
237
- const logger = await container.resolve('logger');
238
- logger.instance?.log('Application started.');
239
-
240
- const appInfo = await container.resolve('appInfo');
241
- console.log(appInfo.instance); // Logs the app information after API call
242
-
243
- // Example of reacting to state changes
244
- console.log('
245
- --- Changing theme ---');
246
- await appStore.set(s => ({ ...s, config: { ...s.config, theme: 'dark' } }));
86
+ // simulate state changes
87
+ while (true) {
88
+ const { count } = store.get();
89
+ if (count === 10) {
90
+ break;
91
+ }
247
92
 
248
- // Because themeService depends on state.config.theme, it will be rebuilt.
249
- // Any artifact that depends on themeService would also be rebuilt.
250
- const newTheme = await container.resolve('themeService');
251
- console.log('New Theme:', newTheme.instance); // Expected: dark
93
+ await new Promise((r) => setTimeout(r, 300));
94
+ store.set(({ count }) => ({ count: count + 1 }));
252
95
  }
253
96
 
254
- main();
97
+ // TODO
98
+ // add example operations for invalidate and calling cleanup
99
+ // add more examples for complex artifact interactions
255
100
  ```
256
101
 
102
+ ## 4. Usage
103
+
257
104
  ### Registering Artifacts
258
105
 
259
- Use `container.register()` to define an artifact. Each artifact requires a unique `key` and a `factory` function.
106
+ Artifacts are registered via an `ArtifactTemplate`. By default, artifacts are singletons and lazily instantiated.
260
107
 
261
108
  ```typescript
262
- import { ArtifactScopes, ArtifactTemplate } from "@asaidimu/utils-artifacts";
263
-
264
- // Define your app's state and registry types for better type safety
265
- interface MyAppState {
266
- /* ... */
267
- }
268
- interface MyAppRegistry {
269
- myService: string;
270
- }
271
-
272
- // Type assertion for the container
273
- const container = new ArtifactContainer<MyAppRegistry, MyAppState>(myStore);
274
-
275
- // Example of registering an artifact
276
- container.register<MyAppRegistry, MyAppState, "myService">({
277
- key: "myService",
278
- factory: async (ctx) => {
279
- // Access dependencies using ctx.use()
280
- const logger = await ctx.use(({ require }) => require("logger"));
281
- const apiUrl = await ctx.use(({ select }) =>
282
- select((state) => state.config.apiUrl),
283
- );
284
-
285
- logger.log(`Initializing myService with API URL: ${apiUrl}`);
286
- return `Initialized: ${apiUrl}`;
287
- },
288
- // Optional parameters:
289
- scope: ArtifactScopes.Singleton, // 'singleton' (default) or 'transient'
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.
109
+ container.register({
110
+ key: "apiClient",
111
+ factory: () => new ApiClient(),
112
+ scope: "singleton",
113
+ lazy: true,
294
114
  });
295
115
  ```
296
116
 
297
- The `factory` function receives an `ArtifactFactoryContext` object, providing access to dependencies, state, and lifecycle management:
298
-
299
- ```typescript
300
- /**
301
- * Context provided to an artifact's factory function.
302
- * @template TRegistry The full artifact registry type.
303
- * @template TState The global state type.
304
- * @template TArtifact The type of the artifact being created.
305
- */
306
- interface ArtifactFactoryContext<TRegistry, TState, TArtifact> {
307
- /** Returns the current global state object. Non-reactive. */
308
- state(): TState;
309
- /** The previous instance of a singleton artifact (if available). */
310
- previous?: TArtifact;
311
- /** Executes a callback within a dependency tracking context. */
312
- use<R>(callback: (ctx: UseDependencyContext<TRegistry, TState>) => R | Promise<R>): Promise<R>;
313
- /** Registers a cleanup function for the current artifact instance. */
314
- onCleanup(cleanup: ArtifactCleanup): void;
315
- /** Registers a cleanup function for when the artifact is permanently disposed. */
316
- onDispose(callback: ArtifactCleanup): void;
317
- /** Starts a streaming process for a Singleton artifact. */
318
- stream(callback: (ctx: ArtifactStreamContext<TState, TArtifact>) => ...): void;
319
- }
320
-
321
- /**
322
- * Context for resolving dependencies within `use()` callback.
323
- * @template TRegistry The full artifact registry type.
324
- * @template TState The global state type.
325
- */
326
- interface UseDependencyContext<TRegistry, TState> {
327
- /** Resolves another artifact (returns `ResolvedArtifact`). */
328
- resolve<K extends keyof TRegistry>(key: K): Promise<ResolvedArtifact<TRegistry[K]>>;
329
- /** Resolves an artifact and throws on error (returns instance directly). */
330
- require<K extends keyof TRegistry>(key: K): Promise<TRegistry[K]>;
331
- /** Selects a slice of global state reactively. */
332
- select<S>(selector: (state: TState) => S): S;
333
- }
334
- ```
335
-
336
- ### Resolving & Requiring Artifacts
337
-
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.
340
-
341
- ```typescript
342
- // Using resolve for defensive programming
343
- const myServiceResult = await container.resolve("myService");
344
- if (myServiceResult.ready) {
345
- console.log("Service instance:", myServiceResult.instance);
346
- } else if (myServiceResult.error) {
347
- console.error("Service failed:", myServiceResult.error);
348
- } else {
349
- console.log("Service is pending or idle.");
350
- }
351
-
352
- // Using require for direct access when errors are handled upstream
353
- try {
354
- const myServiceInstance = await container.require("myService");
355
- console.log("Service instance:", myServiceInstance);
356
- } catch (error) {
357
- console.error("Failed to get service:", error);
358
- }
359
- ```
360
-
361
- ### Watching Artifact Changes
117
+ ### Resolving Dependencies
362
118
 
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.
119
+ Use the `ctx.use` callback to declare dependencies on other artifacts or state slices. This is the mandatory method for the container to track the dependency graph and trigger invalidations.
364
120
 
365
121
  ```typescript
366
- const loggerWatcher = container.watch("logger");
122
+ container.register({
123
+ key: "userService",
124
+ factory: async ({ use }) => {
125
+ // Dependency on another artifact
126
+ const client = await use((ctx) => ctx.require("apiClient"));
127
+ // Dependency on a slice of the global state
128
+ const config = await use((ctx) => ctx.select((s) => s.authConfig));
367
129
 
368
- const unsubscribe = loggerWatcher.subscribe((resolvedArtifact) => {
369
- if (resolvedArtifact.ready) {
370
- console.log("Logger updated:", resolvedArtifact.instance);
371
- } else if (resolvedArtifact.error) {
372
- console.error("Logger error:", resolvedArtifact.error);
373
- }
374
- // `get()` retrieves the current resolved artifact state without subscribing.
375
- const currentLogger = loggerWatcher.get();
376
- console.log("Current logger:", currentLogger.instance);
130
+ return new UserService(client, config);
131
+ },
377
132
  });
378
-
379
- // To stop listening for updates:
380
- // unsubscribe();
381
133
  ```
382
134
 
383
- ### Reactive Dependencies (State Selection)
135
+ ### Streaming Artifacts
384
136
 
385
- Artifacts can automatically react to changes in your application's global state by using `ctx.select()` within the `use` callback.
137
+ Singletons can emit multiple values over time. The container automatically notifies all dependents and observers upon every emission, triggering a cascading invalidation.
386
138
 
387
139
  ```typescript
388
- interface AppState {
389
- user: { name: string; theme: "light" | "dark" };
390
- }
391
- interface AppRegistry {
392
- userGreeting: string;
393
- }
394
-
395
- // Assume appStore is an instance of DataStore<AppState>
396
- const container = new ArtifactContainer<AppRegistry, AppState>(appStore);
397
-
398
140
  container.register({
399
- key: "userGreeting",
400
- factory: async ({ use }) => {
401
- // This artifact will rebuild if appStore.user.theme changes.
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
- );
408
- return `Hello, ${userName}! Your theme is ${theme}.`;
141
+ key: "socketData",
142
+ factory: ({ stream }) => {
143
+ stream(({ emit, signal }) => {
144
+ const socket = new WebSocket("...");
145
+ socket.onmessage = (e) => emit(e.data);
146
+
147
+ // IMPORTANT: Always use the signal to stop the stream
148
+ // when the artifact is invalidated or unregistered.
149
+ signal.addEventListener("abort", () => {
150
+ socket.close();
151
+ });
152
+
153
+ // If the stream is a loop, check signal.aborted in every iteration
154
+ // while (!signal.aborted) {
155
+ // await someAsyncWork();
156
+ // emit(result);
157
+ // }
158
+ });
159
+ return null; // Initial value
409
160
  },
410
161
  });
411
-
412
- // Example of state change triggering rebuild
413
- await appStore.set((state) => ({
414
- ...state,
415
- user: { ...state.user, theme: "dark" },
416
- }));
417
- const greeting = await container.resolve("userGreeting");
418
- console.log(greeting.instance); // Output will reflect the new theme.
419
162
  ```
420
163
 
421
- ### Artifact Lifecycle (Cleanup & Dispose)
164
+ ### Parameterized Artifacts
422
165
 
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).
166
+ Define a `paramKey` to create distinct instances based on resolution parameters.
425
167
 
426
- ```typescript
427
- container.register({
428
- key: "resourceArtifact",
429
- scope: ArtifactScopes.Singleton,
430
- factory: ({ onCleanup, onDispose, use }) => {
431
- const resourceId = Math.random();
432
- console.log(`Resource ${resourceId} created.`);
433
- const timerId = setInterval(
434
- () => console.log(`Resource ${resourceId} heartbeat...`),
435
- 1000,
436
- );
437
-
438
- // Cleanup for the current instance (e.g., on rebuild or if transient)
439
- onCleanup(() => {
440
- console.log(`Cleaning up instance for resource ${resourceId}...`);
441
- clearInterval(timerId);
442
- });
168
+ > **Critical Resource Management**
169
+ > Parameterized singletons are cached indefinitely by default to ensure reactive consistency. In high-cardinality scenarios (e.g., resolving thousands of unique user IDs), this can lead to memory exhaustion over long application runtimes.
170
+ >
171
+ > **Best Practices for Resource Management**:
172
+ >
173
+ > 1. **Prefer `transient` scope**: If the artifact does not need to be shared across multiple consumers, use `scope: 'transient'` to avoid cache accumulation.
174
+ > 2. **Manual Cleanup**: For `singleton` parameterized artifacts, invoke `container.unregister(key, params)` when the artifact is no longer needed (e.g., when a user logs out or a view is destroyed).
175
+ > 3. **Monitor Cache**: If resolving dynamic, high-volume data, track the number of active parameter keys to ensure the container remains within memory constraints.
443
176
 
444
- // Dispose for permanent removal from container
445
- onDispose(() => {
446
- console.log(
447
- `Disposing artifact 'resourceArtifact' (resource ${resourceId}).`,
448
- );
449
- // Final resource release
450
- });
177
+ **Critical Note**: The `paramKey` function must be **deterministic**. The string returned by this function is the actual primary key used for caching and invalidation. If the function is non-deterministic or produces collisions, you will either lose caching benefits or accidentally invalidate unrelated instances of the same parameterized artifact.
451
178
 
452
- return { id: resourceId };
179
+ ```typescript
180
+ container.register({
181
+ key: "userProfile",
182
+ paramKey: (params) => `user:${params.id}`,
183
+ factory: async (ctx) => {
184
+ const { id } = ctx.params;
185
+ return fetchUser(id);
453
186
  },
454
187
  });
455
188
 
456
- async function lifecycleExample() {
457
- await container.resolve("resourceArtifact"); // Instance created and logs "heartbeat..."
458
- console.log("Artifact resolved.");
459
-
460
- // Simulate invalidation: will trigger onCleanup for the current instance, then rebuild.
461
- await container.invalidate("resourceArtifact");
462
- console.log("Artifact invalidated and rebuilt.");
463
-
464
- // Manually dispose the artifact and container
465
- await container.unregister("resourceArtifact"); // Triggers onDispose
466
- console.log("Artifact unregistered.");
467
- }
468
- lifecycleExample();
189
+ // Resolves the cached singleton for user:123
190
+ const user = await container.resolve("userProfile", { id: "123" });
469
191
  ```
470
192
 
471
- ### Streaming Artifacts
193
+ ### Lifecycle Management
472
194
 
473
- For artifacts that continuously emit values (e.g., real-time data, streams), use `ctx.stream()`. This is only supported for `Singleton` artifacts.
195
+ Explicitly manage the lifecycle of an artifact by using `onCleanup` and `onDispose` within the factory.
474
196
 
475
197
  ```typescript
476
198
  container.register({
477
- key: "dataStream",
478
- scope: ArtifactScopes.Singleton,
479
- factory: ({ stream, onCleanup, use }) => {
480
- let counter = 0;
481
- let intervalId: ReturnType<typeof setInterval> | undefined;
482
-
483
- stream(async ({ emit, signal, value }) => {
484
- console.log("Data stream started...");
485
- const logger = await use(({ require }) => require("logger")); // Example dependency
486
- logger.log("Stream active. Emitting values.");
487
-
488
- intervalId = setInterval(() => {
489
- if (signal.aborted) {
490
- console.log("Stream signal aborted. Clearing interval.");
491
- clearInterval(intervalId);
492
- return;
493
- }
494
- counter++;
495
- const newValue = `Data update #${counter}`;
496
- console.log(`Emitting: ${newValue}`);
497
- emit(newValue); // Emit the new value to the container
498
- if (counter >= 5) {
499
- console.log("Stream reached limit, stopping.");
500
- clearInterval(intervalId);
501
- // The factory function can return to stop the stream producer.
502
- }
503
- }, 500);
504
- });
199
+ key: "connection",
200
+ factory: async ({ onCleanup, onDispose }) => {
201
+ const conn = new Connection();
202
+ await conn.connect();
505
203
 
506
- onCleanup(() => {
507
- console.log("Cleaning up data stream instance...");
508
- // Ensure interval is cleared if stream is aborted or rebuilt.
509
- if (intervalId) clearInterval(intervalId);
510
- });
204
+ onCleanup(() => conn.disconnect());
205
+ onDispose(() => conn.destroy());
511
206
 
512
- return "Initial stream value"; // Initial value before first emission
207
+ return conn;
513
208
  },
514
209
  });
515
-
516
- async function streamExample() {
517
- const watcher = container.watch("dataStream");
518
- const unsubscribe = watcher.subscribe((art) => {
519
- if (art.ready) console.log("Stream received:", art.instance);
520
- });
521
-
522
- // Keep alive for a few seconds to observe stream emissions
523
- await new Promise((res) => setTimeout(res, 3000));
524
- unsubscribe();
525
- console.log("Stream watcher unsubscribed.");
526
- }
527
- streamExample();
528
210
  ```
529
211
 
530
- ### Invalidating Artifacts
212
+ ### Observing Artifacts
531
213
 
532
- Manually trigger an artifact to rebuild and cascade invalidations to its dependents.
214
+ Use `ArtifactObserver` (via `container.watch`) to bind the artifact's state to UI components or external monitors.
533
215
 
534
216
  ```typescript
535
- // Invalidate an artifact; it will rebuild if it has dependents, is lazy and watched, or eagerly.
536
- await container.invalidate("myArtifact");
537
-
538
- // Force immediate rebuild, bypassing any debounce delay.
539
- await container.invalidate("myArtifact", true);
540
- ```
541
-
542
- ### Debugging Artifacts
543
-
544
- The `container.debugInfo()` method provides a snapshot of the container's internal state, which is invaluable for understanding dependencies, status, and build counts.
217
+ const observer = container.watch("userProfile", { id: "123" });
545
218
 
546
- ```typescript
547
- const debugNodes = container.debugInfo();
548
- debugNodes.forEach((node) => {
549
- console.log(`
550
- Artifact ID: ${node.id}`);
551
- console.log(` Scope: ${node.scope}`);
552
- console.log(` Status: ${node.status}`); // e.g., 'active', 'error', 'idle', 'building', 'pending', 'debouncing'
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"}`);
560
- console.log(` Build Count: ${node.buildCount}`);
219
+ observer.subscribe((resolved) => {
220
+ console.log("Artifact state changed:", resolved.ready);
221
+ if (resolved.ready) {
222
+ console.log("Instance available:", resolved.instance);
223
+ }
561
224
  });
562
225
  ```
563
226
 
564
- ---
565
-
566
- ## đŸ—ī¸ Project Architecture
567
-
568
- The `@asaidimu/utils-artifacts` library is architected around a central `ArtifactContainer` that orchestrates several specialized internal components:
569
-
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.
576
-
577
- ### Data Flow Example (Resolution & Reactivity)
578
-
579
- 1. **Registration**: An artifact (`key`, `factory`, `scope`, etc.) is registered with the `ArtifactRegistry`. The `ArtifactDependencyGraph` registers a node for this artifact.
580
- 2. **Resolution (`container.resolve`)**:
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.
590
- 3. **Reactivity & Invalidation**:
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.
597
-
598
- ### Core Concepts
599
-
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.
227
+ ## 5. Architecture
605
228
 
606
- ---
607
-
608
- ## 📜 Considerations & Potential Pitfalls
229
+ `@asaidimu/utils-artifacts` implements a **Directed Acyclic Graph (DAG)** for dependency management using a **Pull-based Invalidation** model.
609
230
 
610
- 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.
231
+ ### Troubleshooting Circular Dependencies
611
232
 
612
- ### Debouncing Latency
233
+ Circular dependencies occur when artifact A depends on B, and B depends on A, creating a cycle in the DAG. The container will detect this at runtime and throw a `SystemError`.
613
234
 
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.
235
+ **Common Remediation Strategies**:
616
236
 
617
- ### Wasted Build Effort from Late Staleness Detection
237
+ 1. **Extract Shared Logic**: Identify the common dependency shared by both artifacts and move it into a third, independent base artifact.
238
+ 2. **Use Provider Pattern**: Refactor the shared behavior into a 'provider' artifact that both A and B can depend on without forming a loop.
239
+ 3. **Decompose Artifacts**: Break the circular logic into smaller, discrete units where one unit coordinates the interaction between the others.
618
240
 
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.
241
+ ### Push vs. Pull Reactivity
621
242
 
622
- ### Efficiency of Global State Watching
243
+ In traditional reactive systems (Push model), a change in a source value immediately triggers a chain reaction of re-computations across all dependents. This often leads to "update storms" where a single state change causes dozens of immediate, synchronous re-renders or calculations, regardless of whether the result is actually needed at that moment.
623
244
 
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.
245
+ In contrast, this library uses a **Pull model**. When a state slice or source artifact emits a change, the container simply marks all downstream dependent artifacts as **Invalidated** (setting a stale flag). It does not execute any factories. No work is performed until a consumer actually calls `resolve()` or an observer is notified.
629
246
 
630
- ### Startup Cost of Eager Loading
247
+ This approach ensures that if a state value changes ten times in a single event loop, the dependent artifacts are only rebuilt **once**—the next time they are requested. This collapses multiple invalidations into a single, deferred rebuild, maximizing computational efficiency and reducing main-thread blocking.
631
248
 
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.
249
+ ## 6. Development
634
250
 
635
- ### Sequential Cleanup/Dispose Execution
636
-
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.
251
+ ```bash
252
+ # Install dependencies
253
+ bun install
639
254
 
640
- ### Dependency Graph Complexity
255
+ # Run unit tests
256
+ bun test
641
257
 
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.
258
+ # Run tests in watch mode
259
+ bun run test:watch
260
+ ```
644
261
 
645
- ---
262
+ ## 7. Testing
646
263
 
647
- ## đŸ› ī¸ Development & Contributing
264
+ The library is verified against a comprehensive suite including:
648
265
 
649
- ### Development Setup
266
+ - Resolution race condition prevention.
267
+ - Circular dependency detection.
268
+ - Circular dependency detection.
269
+ - Stream lifecycle and AbortSignal compliance.
270
+ - Parameterized invalidation cascades.
650
271
 
651
- 1. **Clone the repository:**
652
- ```bash
653
- git clone https://github.com/asaidimu/erp-utils.git
654
- cd erp-utils/src/artifacts
655
- ```
656
- 2. **Install dependencies:**
657
- ```bash
658
- npm install
659
- # or
660
- yarn install
661
- ```
272
+ ## 8. API Reference
662
273
 
663
- ### Scripts
274
+ ### ArtifactContainer
664
275
 
665
- - `npm test`: Runs all tests once.
666
- - `npm test:watch`: Runs tests in watch mode, rerunning on file changes.
276
+ | Method | Description |
277
+ | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
278
+ | `register(template)` | Registers a new artifact template. |
279
+ | `resolve(key, params?)` | Resolves the artifact. Returns a `ResolvedArtifact` object containing the `instance` and `ready` status. |
280
+ | `require(key, params?)` | Same as `resolve`, but returns the instance directly or throws if resolution if resolution fails. |
281
+ | `invalidate(key, params?)` | Manually trigger a rebuild of the artifact and its dependents. |
282
+ | `unregister(key, params?)` | Removes the artifact from the registry. If `params` is omitted, the entire template is removed; if provided, only the specific virtual artifact instance is removed. |
667
283
 
668
- ### Testing
284
+ ### ArtifactObserver
669
285
 
670
- The project uses `vitest` for testing. All new code should be accompanied by tests.
286
+ | Method | Description |
287
+ | :---------------------------- | :---------------------------------------------------------------------------------------------------------- |
288
+ | `get()` | Returns the current cached snapshot of the artifact. |
289
+ | `subscribe(callback, eager?)` | Subscribes to changes. The callback is invoked whenever the artifact state changes (Ready, Error, Pending). |
290
+ | `resolve()` | Manually triggers the resolution of the artifact if it is currently idle. |
671
291
 
672
- ### Contributing Guidelines
292
+ ### ArtifactFactoryContext
673
293
 
674
- 1. **Fork and Branch**: Fork the repository and create a new branch from `main`.
675
- 2. **Code Quality**: Write clean, readable TypeScript code adhering to project conventions.
676
- 3. **Tests**: Add unit/integration tests for all new features and bug fixes.
677
- 4. **Commit Messages**: Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) (e.g., `feat: add streaming support`, `fix: correct debounce logic`).
678
- 5. **Pull Requests**: Submit PRs with clear descriptions and link to relevant issues.
294
+ | Method | Description |
295
+ | :-------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
296
+ | `use(callback)` | The primary method for declaring dependencies. Any `resolve` or `select` calls inside the callback are registered as dependencies. |
297
+ | `onCleanup(cleanup)` | Registers a function to be executed before the artifact is rebuilt or disposed. |
298
+ | `onDispose(callback)` | Registers a function to be executed be executed before the artifact is permanently removed. |
299
+ | `stream(callback)` | Initializes a streaming process for a Singleton artifact. |
300
+ | `state()` | Returns a non-reactive read of the current global state. |
679
301
 
680
- ---
302
+ ### ArtifactTemplate
681
303
 
682
- ## â„šī¸ Additional Information
304
+ | Method | Description |
305
+ | :--------- | :---------------------------------------------------------------------- |
306
+ | `key` | Unique identifier for the artifact. |
307
+ | `factory` | The factory function that creates the artifact instance. |
308
+ | `scope` | `singleton` (default) or `transient`. |
309
+ | `lazy` | If `true` (default), the artifact is built only when requested. |
310
+ | `timeout` | Maximum time allowed for the factory to complete. |
311
+ | `retries` | Number of times to retry the factory on failure. |
312
+ | `debounce` | Base debounce time in milliseconds for invalidation events to collapse. |
313
+ | `paramKey` | Function to generate a unique key for parameterized artifacts. |
683
314
 
684
- ### Troubleshooting
315
+ ### ResolvedArtifact
685
316
 
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.
317
+ | Property | Description |
318
+ | :--------- | :----------------------------------------------------------------------------------- |
319
+ | `instance` | The resolved artifact instance. Available only when `ready` is true. |
320
+ | `ready` | Boolean indicating if the artifact is resolved and available for use. |
321
+ | `error` | The error caught during the factory execution. Available only when `ready` is false. |
694
322
 
695
- ### FAQ
323
+ #### Error Handling & Recovery
696
324
 
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.
325
+ Since `ResolvedArtifact` is a discriminated union, you can safely narrow the type of the instance or the error by checking the `ready` flag:
706
326
 
707
- ---
327
+ ```typescript
328
+ const artifact = await container.resolve("userProfile");
708
329
 
709
- ### License
330
+ if (artifact.ready) {
331
+ // Type is narrowed to ReadyArtifact: instance is available
332
+ console.log(artifact.instance);
333
+ } else {
334
+ // Type is narrowed to ErrorArtifact: error is available
335
+ console.error("Failed to resolve artifact:", artifact.error);
710
336
 
711
- This project is licensed under the [MIT License](https://github.com/asaidimu/erp-utils/blob/main/LICENSE).
337
+ // Optionally trigger a manual rebuild to recover from transient failures
338
+ // await container.invalidate("userProfile");
339
+ }
340
+ ```
712
341
 
713
- ### Acknowledgments
342
+ ### ArtifactStreamContext
714
343
 
715
- This library is part of the `@asaidimu/erp-utils` monorepository and relies on a compatible `DataStore` implementation for reactive state management.
344
+ | Method | Description |
345
+ | { "type": "text", "text": "| `emit(value)` | Emits a new value, triggering invalidation of dependents. |
346
+ | `set(update)` | Dispatches a state update to the global store. |
347
+ | `signal` | AbortSignal to stop the stream. |