@asaidimu/utils-artifacts 3.1.8 → 4.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 +199 -140
- package/index.d.mts +26 -4
- package/index.d.ts +26 -4
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
A powerful, reactive dependency injection container for managing application artifacts and their lifecycles. It provides a robust framework for building modular, maintainable, and highly responsive applications by decoupling components and managing their state-driven dependencies.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@asaidimu/utils-artifacts)
|
|
6
|
-
[](https://
|
|
7
|
-
[](https://github.com/asaidimu/utils/actions?query=workflow%3ACI)
|
|
6
|
+
[](https://github.com/asaidimu/erp-utils/blob/main/LICENSE)
|
|
7
|
+
[](https://github.com/asaidimu/erp-utils/actions?query=workflow%3ACI)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
### 🚀 Quick Links
|
|
10
12
|
|
|
11
13
|
* [Overview & Features](#-overview--features)
|
|
12
14
|
* [Installation & Setup](#-installation--setup)
|
|
@@ -16,7 +18,7 @@ A powerful, reactive dependency injection container for managing application art
|
|
|
16
18
|
* [Artifact-to-Artifact Dependencies](#artifact-to-artifact-dependencies)
|
|
17
19
|
* [Lifecycle Hooks: `onCleanup` & `onDispose`](#lifecycle-hooks-oncleanup--ondispose)
|
|
18
20
|
* [Watching Artifacts for Changes](#watching-artifacts-for-changes)
|
|
19
|
-
* [
|
|
21
|
+
* [Streaming Artifacts with `ctx.stream()`](#streaming-artifacts-with-ctxstream)
|
|
20
22
|
* [Debugging Information](#debugging-information)
|
|
21
23
|
* [Project Architecture](#-project-architecture)
|
|
22
24
|
* [Development & Contributing](#-development--contributing)
|
|
@@ -35,16 +37,16 @@ This package is ideal for building complex, event-driven systems, front-end appl
|
|
|
35
37
|
|
|
36
38
|
### ✨ Key Features
|
|
37
39
|
|
|
38
|
-
* **Reactive Dependency Injection**: Automatically invalidates and rebuilds artifacts when their declared dependencies (other artifacts or global state slices) change.
|
|
40
|
+
* **Reactive Dependency Injection**: Automatically invalidates and rebuilds `Singleton` artifacts when their declared dependencies (other artifacts or global state slices) change, ensuring reactive updates throughout your application.
|
|
39
41
|
* **Singleton & Transient Scopes**: Control whether an artifact is instantiated once and reused (`Singleton`) or created fresh on every request (`Transient`).
|
|
40
|
-
* **State-Driven Invalidation with Debouncing**: Integrates with a `DataStore` to track state dependencies, triggering artifact rebuilds only when relevant state paths change, with configurable debouncing.
|
|
41
|
-
* **Fine-grained Dependency Tracking**: Explicitly declare dependencies on other artifacts using `ctx.resolve()` and on state slices using `ctx.select()`.
|
|
42
|
-
* **Robust Error Handling**: Built-in detection and specific error types for circular dependencies, missing artifacts,
|
|
43
|
-
* **Lifecycle Hooks**: Register `onCleanup` callbacks for instance-specific resource release and `onDispose` for final resource teardown.
|
|
44
|
-
* **Asynchronous Artifact Resolution**: Supports `async`/`await` in artifact factories, with configurable retries for external failures and
|
|
45
|
-
* **`ArtifactObserver` for Reactive Observation**:
|
|
46
|
-
* **Live
|
|
47
|
-
* **Comprehensive Debugging Info**: `debugInfo()` provides a snapshot of the container's state, including artifact statuses, dependencies, and dependents.
|
|
42
|
+
* **State-Driven Invalidation with Debouncing**: Integrates with a `DataStore` (e.g., `@asaidimu/utils-store`) to track state dependencies, triggering artifact rebuilds only when relevant state paths change, with configurable debouncing to prevent excessive rebuilds.
|
|
43
|
+
* **Fine-grained Dependency Tracking**: Explicitly declare dependencies on other artifacts using `ctx.use(({ resolve }) => resolve('someKey'))` and on state slices using `ctx.use(({ select }) => select(state => state.somePath))`.
|
|
44
|
+
* **Robust Error Handling**: Built-in detection and specific error types for circular dependencies (`CircularDependencyError`), missing artifacts (`ArtifactNotFoundError`), illegal operations (`IllegalScopeError`), and timeouts (`TimeoutError`), distinguishing between `system` (structural) and `external` (runtime) errors.
|
|
45
|
+
* **Lifecycle Hooks**: Register `onCleanup` callbacks for instance-specific resource release (executed before a `Singleton` instance is rebuilt) and `onDispose` for final resource teardown (executed when the artifact is unregistered).
|
|
46
|
+
* **Asynchronous Artifact Resolution**: Supports `async`/`await` in artifact factories, with configurable `retries` for external failures and `timeout` settings for long-running operations.
|
|
47
|
+
* **`ArtifactObserver` for Reactive Observation**: Use `container.watch(key)` to get an observer that allows subscribing to an artifact's resolved value over time, providing reactive updates to UI components or other systems.
|
|
48
|
+
* **Live Streaming with `ctx.stream()`**: `Singleton` artifacts can emit multiple values over their lifetime from within their factory using `ctx.stream()`, immediately notifying dependents and watchers of new data. This is ideal for continuous data sources like WebSockets or background processes.
|
|
49
|
+
* **Comprehensive Debugging Info**: `container.debugInfo()` provides a runtime snapshot of the container's state, including artifact statuses, dependencies (artifact-to-artifact and state-to-artifact), and dependents.
|
|
48
50
|
|
|
49
51
|
---
|
|
50
52
|
|
|
@@ -52,9 +54,9 @@ This package is ideal for building complex, event-driven systems, front-end appl
|
|
|
52
54
|
|
|
53
55
|
### Prerequisites
|
|
54
56
|
|
|
55
|
-
* Node.js (v18 or higher recommended)
|
|
56
|
-
*
|
|
57
|
-
* An implementation of
|
|
57
|
+
* **Node.js**: (v18 or higher recommended)
|
|
58
|
+
* **Package Manager**: `Bun`, `npm`, or `Yarn`.
|
|
59
|
+
* **DataStore Implementation**: An external implementation of a `DataStore` interface with `get` and `watch` methods. We highly recommend using [`@asaidimu/utils-store`](https://www.npmjs.com/package/@asaidimu/utils-store) as it is designed for seamless integration.
|
|
58
60
|
|
|
59
61
|
### Installation Steps
|
|
60
62
|
|
|
@@ -73,13 +75,13 @@ yarn add @asaidimu/utils-artifacts
|
|
|
73
75
|
|
|
74
76
|
### Configuration
|
|
75
77
|
|
|
76
|
-
To use the `ArtifactContainer`, you need to initialize it with an object that provides `
|
|
78
|
+
To use the `ArtifactContainer`, you need to initialize it with an object that provides `watch` (for reactive subscriptions to state changes), `get` (for reading the current state), and `set` (for allowing artifacts to update state, e.g., via `ctx.stream` or `ctx.set`).
|
|
77
79
|
|
|
78
80
|
If you're using `@asaidimu/utils-store` as your `DataStore`:
|
|
79
81
|
|
|
80
82
|
```typescript
|
|
81
|
-
import { ArtifactContainer
|
|
82
|
-
import { ReactiveDataStore } from '@asaidimu/utils-store'; //
|
|
83
|
+
import { ArtifactContainer } from '@asaidimu/utils-artifacts';
|
|
84
|
+
import { ReactiveDataStore } from '@asaidimu/utils-store'; // Recommended DataStore
|
|
83
85
|
|
|
84
86
|
interface AppState {
|
|
85
87
|
config: { theme: string; debugMode: boolean };
|
|
@@ -96,6 +98,7 @@ const initialAppState: AppState = {
|
|
|
96
98
|
const store = new ReactiveDataStore<AppState>(initialAppState);
|
|
97
99
|
|
|
98
100
|
// Initialize the ArtifactContainer
|
|
101
|
+
// The generic arguments <TRegistry, TState> are inferred or can be explicitly provided.
|
|
99
102
|
const container = new ArtifactContainer<Record<string, any>, AppState>(store);
|
|
100
103
|
|
|
101
104
|
// Now you can start registering artifacts
|
|
@@ -111,6 +114,7 @@ import { ArtifactContainer } from '@asaidimu/utils-artifacts';
|
|
|
111
114
|
const mockStore = {
|
|
112
115
|
get: () => ({}), // Returns an empty object for state
|
|
113
116
|
watch: () => () => {}, // Returns a no-op unsubscribe function
|
|
117
|
+
set: async () => ({}), // Returns a no-op set function
|
|
114
118
|
};
|
|
115
119
|
const container = new ArtifactContainer(mockStore);
|
|
116
120
|
console.log('ArtifactContainer initialized successfully!');
|
|
@@ -123,11 +127,11 @@ console.log('ArtifactContainer initialized successfully!');
|
|
|
123
127
|
|
|
124
128
|
### Basic Artifacts (Singleton & Transient)
|
|
125
129
|
|
|
126
|
-
Artifacts can be registered with different scopes, controlling their lifecycle.
|
|
130
|
+
Artifacts can be registered with different scopes, controlling their lifecycle and instantiation behavior.
|
|
127
131
|
|
|
128
132
|
#### Singleton Scope
|
|
129
133
|
|
|
130
|
-
A
|
|
134
|
+
A `Singleton` artifact is created only once. Subsequent `resolve` calls or reactive updates will return or use the same instance, unless its dependencies change, which triggers a rebuild. This is the default scope.
|
|
131
135
|
|
|
132
136
|
```typescript
|
|
133
137
|
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
@@ -149,7 +153,7 @@ container.register({
|
|
|
149
153
|
};
|
|
150
154
|
},
|
|
151
155
|
scope: ArtifactScopes.Singleton,
|
|
152
|
-
lazy: false, // Build immediately on registration
|
|
156
|
+
lazy: false, // Build immediately on registration if it's a Singleton
|
|
153
157
|
});
|
|
154
158
|
|
|
155
159
|
async function runSingletonExample() {
|
|
@@ -174,7 +178,7 @@ runSingletonExample();
|
|
|
174
178
|
|
|
175
179
|
#### Transient Scope
|
|
176
180
|
|
|
177
|
-
A
|
|
181
|
+
A `Transient` artifact creates a new instance every time it is resolved. These artifacts do not participate in caching, reactive invalidation, or lifecycle hooks like `onCleanup` and `onDispose`.
|
|
178
182
|
|
|
179
183
|
```typescript
|
|
180
184
|
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
@@ -217,7 +221,7 @@ runTransientExample();
|
|
|
217
221
|
|
|
218
222
|
### Reactive State Dependencies
|
|
219
223
|
|
|
220
|
-
Artifacts can react to changes in your global `DataStore` state by using `ctx.select
|
|
224
|
+
Artifacts can react to changes in your global `DataStore` state by using `ctx.use(({ select }) => ...)` within their factory. When the selected state slice changes, the `Singleton` artifact is automatically invalidated and rebuilt.
|
|
221
225
|
|
|
222
226
|
```typescript
|
|
223
227
|
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
@@ -287,7 +291,7 @@ runReactiveStateExample();
|
|
|
287
291
|
|
|
288
292
|
### Artifact-to-Artifact Dependencies
|
|
289
293
|
|
|
290
|
-
Artifacts can depend on other artifacts using `ctx.resolve()` within their factory
|
|
294
|
+
Artifacts can depend on other artifacts using `ctx.use(({ resolve }) => resolve('otherArtifactKey'))` or `ctx.use(({ require }) => require('otherArtifactKey'))` within their factory. Changes (invalidations) to a dependency will automatically invalidate the dependent artifact.
|
|
291
295
|
|
|
292
296
|
```typescript
|
|
293
297
|
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
@@ -314,10 +318,10 @@ container.register({
|
|
|
314
318
|
factory: async ({ use }) => {
|
|
315
319
|
userRepositoryBuilds++;
|
|
316
320
|
console.log(`Building UserRepository (${userRepositoryBuilds})...`);
|
|
317
|
-
const db = await use((ctx) => ctx.
|
|
321
|
+
const db = await use((ctx) => ctx.require('dbConnection')); // Using 'require' for direct instance
|
|
318
322
|
return {
|
|
319
|
-
dbClient: db.
|
|
320
|
-
findUser: (id: string) => `User ${id} from ${db.
|
|
323
|
+
dbClient: db.client,
|
|
324
|
+
findUser: (id: string) => `User ${id} from ${db.client}`
|
|
321
325
|
};
|
|
322
326
|
},
|
|
323
327
|
scope: ArtifactScopes.Singleton,
|
|
@@ -357,10 +361,10 @@ runArtifactDependencyExample();
|
|
|
357
361
|
|
|
358
362
|
### Lifecycle Hooks: `onCleanup` & `onDispose`
|
|
359
363
|
|
|
360
|
-
Artifacts can register cleanup functions to manage resources.
|
|
364
|
+
Artifacts can register cleanup functions to manage resources, ensuring proper release when instances are no longer needed.
|
|
361
365
|
|
|
362
|
-
* `onCleanup`:
|
|
363
|
-
* `onDispose`:
|
|
366
|
+
* `onCleanup(callback)`: Registered callbacks are executed *before* a `Singleton` artifact's instance is replaced (due to invalidation/rebuild). Ideal for releasing resources tied to that specific *instance* (e.g., stopping timers, closing old connections).
|
|
367
|
+
* `onDispose(callback)`: Registered callbacks are executed *only* when the artifact is explicitly `unregister()`ed from the container or the container itself is `dispose()`d. Ideal for final, permanent resource release associated with the *artifact definition itself*.
|
|
364
368
|
|
|
365
369
|
```typescript
|
|
366
370
|
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
@@ -388,7 +392,7 @@ container.register({
|
|
|
388
392
|
|
|
389
393
|
onDispose(() => {
|
|
390
394
|
console.log('Disposing Ticker artifact (final cleanup)...');
|
|
391
|
-
// Additional final cleanup could go here
|
|
395
|
+
// Additional final cleanup could go here, e.g., closing file handles
|
|
392
396
|
});
|
|
393
397
|
|
|
394
398
|
return { start: Date.now() };
|
|
@@ -421,10 +425,10 @@ runLifecycleHooksExample();
|
|
|
421
425
|
|
|
422
426
|
### Watching Artifacts for Changes
|
|
423
427
|
|
|
424
|
-
The `watch()` method provides a reactive way to observe an artifact's value without repeatedly calling `resolve()`. This is particularly useful for UI frameworks or reactive data flows.
|
|
428
|
+
The `container.watch(key)` method provides a reactive way to observe an artifact's value without repeatedly calling `resolve()`. This is particularly useful for UI frameworks or reactive data flows where you want to be notified of changes as they occur.
|
|
425
429
|
|
|
426
430
|
```typescript
|
|
427
|
-
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
431
|
+
import { ArtifactContainer, ArtifactScopes, WatcherDisposedError } from '@asaidimu/utils-artifacts';
|
|
428
432
|
import { ReactiveDataStore } from '@asaidimu/utils-store';
|
|
429
433
|
|
|
430
434
|
interface AppState {
|
|
@@ -434,11 +438,13 @@ interface AppState {
|
|
|
434
438
|
const store = new ReactiveDataStore<AppState>({ counter: 0 });
|
|
435
439
|
const container = new ArtifactContainer<any, AppState>(store);
|
|
436
440
|
|
|
441
|
+
let reactiveCounterBuilds = 0;
|
|
437
442
|
container.register({
|
|
438
443
|
key: 'reactiveCounter',
|
|
439
444
|
factory: async ({ use }) => {
|
|
445
|
+
reactiveCounterBuilds++;
|
|
440
446
|
const count = await use((ctx) => ctx.select((state) => state.counter));
|
|
441
|
-
return `Current count: ${count}`;
|
|
447
|
+
return `Current count: ${count} (Build: ${reactiveCounterBuilds})`;
|
|
442
448
|
},
|
|
443
449
|
scope: ArtifactScopes.Singleton,
|
|
444
450
|
});
|
|
@@ -447,18 +453,18 @@ async function runWatcherExample() {
|
|
|
447
453
|
console.log('Setting up watcher...');
|
|
448
454
|
const watcher = container.watch('reactiveCounter');
|
|
449
455
|
|
|
450
|
-
let unsubscribe = watcher.subscribe(() => {
|
|
456
|
+
let unsubscribe = watcher.subscribe((resolved) => {
|
|
451
457
|
if (resolved.ready) {
|
|
452
458
|
console.log('Watcher received update:', resolved.instance);
|
|
453
459
|
} else if (resolved.error) {
|
|
454
460
|
console.error('Watcher received error:', resolved.error.message);
|
|
455
461
|
} else {
|
|
456
|
-
console.log('Watcher received pending update (artifact not ready
|
|
462
|
+
console.log('Watcher received pending update (artifact not yet ready).');
|
|
457
463
|
}
|
|
458
464
|
});
|
|
459
465
|
|
|
460
|
-
//
|
|
461
|
-
await new Promise(res => setTimeout(res, 10));
|
|
466
|
+
// Give watcher time to resolve the artifact initially
|
|
467
|
+
await new Promise(res => setTimeout(res, 10));
|
|
462
468
|
|
|
463
469
|
console.log('\n--- Incrementing counter ---');
|
|
464
470
|
await store.set({ counter: 1 });
|
|
@@ -468,141 +474,182 @@ async function runWatcherExample() {
|
|
|
468
474
|
await store.set({ counter: 2 });
|
|
469
475
|
await new Promise(res => setTimeout(res, 10));
|
|
470
476
|
|
|
471
|
-
console.log('\n--- Disposing watcher ---');
|
|
472
|
-
unsubscribe(); // Unsubscribe
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
+
console.log('\n--- Unsubscribing & Disposing watcher ---');
|
|
478
|
+
unsubscribe(); // Unsubscribe from updates
|
|
479
|
+
|
|
480
|
+
// After all subscribers are gone, the underlying artifact instance for watching transients is cleaned up.
|
|
481
|
+
// The watcher itself can then be disposed, making it unusable.
|
|
482
|
+
await new Promise(res => setTimeout(res, 10)); // Give cleanup a moment
|
|
483
|
+
// For singletons, watcher.dispose() is effectively a no-op as the underlying artifact remains
|
|
484
|
+
// For transients, it would clean up the internal singleton clone.
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
watcher.get(); // Attempt to access after dispose
|
|
488
|
+
} catch (e) {
|
|
489
|
+
if (e instanceof WatcherDisposedError) {
|
|
490
|
+
console.error('Caught expected error after watcher dispose:', e.message);
|
|
491
|
+
} else {
|
|
492
|
+
throw e;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
477
495
|
|
|
478
496
|
// Output:
|
|
479
497
|
// Setting up watcher...
|
|
480
|
-
// Watcher received pending update (artifact not ready
|
|
481
|
-
// Watcher received update: Current count: 0
|
|
498
|
+
// Watcher received pending update (artifact not yet ready).
|
|
499
|
+
// Watcher received update: Current count: 0 (Build: 1)
|
|
482
500
|
//
|
|
483
501
|
// --- Incrementing counter ---
|
|
484
|
-
// Watcher received
|
|
485
|
-
// Watcher received update: Current count: 1
|
|
502
|
+
// Watcher received update: Current count: 1 (Build: 2)
|
|
486
503
|
//
|
|
487
504
|
// --- Incrementing counter again ---
|
|
488
|
-
// Watcher received
|
|
489
|
-
// Watcher received update: Current count: 2
|
|
505
|
+
// Watcher received update: Current count: 2 (Build: 3)
|
|
490
506
|
//
|
|
491
|
-
// --- Disposing watcher ---
|
|
492
|
-
//
|
|
507
|
+
// --- Unsubscribing & Disposing watcher ---
|
|
508
|
+
// Caught expected error after watcher dispose: [ArtifactContainer] Artifact with key:reactiveCounter has already been disposed
|
|
493
509
|
}
|
|
494
510
|
|
|
495
511
|
runWatcherExample();
|
|
496
512
|
```
|
|
497
513
|
|
|
498
|
-
###
|
|
514
|
+
### Streaming Artifacts with `ctx.stream()`
|
|
499
515
|
|
|
500
|
-
|
|
516
|
+
`Singleton` artifacts can leverage `ctx.stream(callback)` to become continuous sources of data. The `callback` receives an `ArtifactStreamContext` which allows emitting new values over time via `ctx.emit(value)`. Each emission updates the artifact's instance, notifying all dependents and watchers immediately. This is ideal for scenarios like WebSocket clients, background workers, or long-running computations that produce intermediate results.
|
|
501
517
|
|
|
502
518
|
```typescript
|
|
503
519
|
import { ArtifactContainer, ArtifactScopes } from "@asaidimu/utils-artifacts";
|
|
504
520
|
import { ReactiveDataStore } from "@asaidimu/utils-store";
|
|
505
521
|
|
|
506
522
|
const store = new ReactiveDataStore({});
|
|
507
|
-
const container = new ArtifactContainer(store);
|
|
523
|
+
const container = new ArtifactContainer<{ producer: number; consumer: string }, {}>(store);
|
|
508
524
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
factory: async ({ update: publish }) => { // `update` is renamed to `publish` for clarity in this example
|
|
512
|
-
async function process() {
|
|
513
|
-
await new Promise((res) => setTimeout(res, 100));
|
|
514
|
-
console.log("Starting long process...");
|
|
515
|
-
|
|
516
|
-
await publish("Step 1: Initializing...");
|
|
517
|
-
await new Promise((res) => setTimeout(res, 100));
|
|
518
|
-
|
|
519
|
-
await publish("Step 2: Loading data...");
|
|
520
|
-
await new Promise((res) => setTimeout(res, 100));
|
|
525
|
+
const PRODUCER_TIMEOUT = 50; // ms
|
|
526
|
+
const PRODUCER_LIMIT = 3;
|
|
521
527
|
|
|
522
|
-
|
|
523
|
-
|
|
528
|
+
container.register({
|
|
529
|
+
key: "producer",
|
|
530
|
+
factory: ({ stream }) => {
|
|
531
|
+
let value = 0;
|
|
532
|
+
|
|
533
|
+
// The stream callback is executed in the background
|
|
534
|
+
stream(async ({ emit, signal }) => {
|
|
535
|
+
console.log("Producer: Stream started.");
|
|
536
|
+
while (!signal.aborted) {
|
|
537
|
+
await new Promise((res) => setTimeout(res, PRODUCER_TIMEOUT)); // Simulate async work
|
|
538
|
+
value++;
|
|
539
|
+
console.log(`Producer: Emitting value: ${value}`);
|
|
540
|
+
await emit(value); // Emit new value, notifying dependents and watchers
|
|
541
|
+
if (value >= PRODUCER_LIMIT) {
|
|
542
|
+
console.log("Producer: Reached limit, stopping stream.");
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
});
|
|
524
547
|
|
|
525
|
-
|
|
526
|
-
|
|
548
|
+
return value; // Initial value returned by the factory
|
|
549
|
+
},
|
|
550
|
+
scope: ArtifactScopes.Singleton,
|
|
551
|
+
});
|
|
527
552
|
|
|
528
|
-
|
|
529
|
-
|
|
553
|
+
container.register({
|
|
554
|
+
key: "consumer",
|
|
555
|
+
factory: async ({ use }) => {
|
|
556
|
+
// This artifact depends on the 'producer'
|
|
557
|
+
const producerValue = await use(({ require }) => require("producer"));
|
|
558
|
+
return `Consumed: ${producerValue}`;
|
|
530
559
|
},
|
|
531
560
|
scope: ArtifactScopes.Singleton,
|
|
532
561
|
});
|
|
533
562
|
|
|
534
|
-
async function
|
|
535
|
-
const watcher = container.watch("
|
|
563
|
+
async function runStreamExample() {
|
|
564
|
+
const watcher = container.watch("consumer");
|
|
565
|
+
const receivedValues: string[] = [];
|
|
536
566
|
|
|
537
|
-
const unsubscribe = watcher.subscribe(() => {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
console.log(
|
|
567
|
+
const unsubscribe = watcher.subscribe((resolved) => {
|
|
568
|
+
if (resolved.instance && !receivedValues.includes(resolved.instance)) {
|
|
569
|
+
receivedValues.push(resolved.instance);
|
|
570
|
+
console.log(`Watcher: Received "${resolved.instance}"`);
|
|
541
571
|
}
|
|
542
572
|
});
|
|
543
573
|
|
|
544
|
-
//
|
|
545
|
-
await container.resolve("
|
|
574
|
+
// Initial resolve to kick-start the producer factory and stream
|
|
575
|
+
await container.resolve("consumer");
|
|
546
576
|
|
|
547
|
-
// Give
|
|
548
|
-
|
|
577
|
+
// Give enough time for the stream to run and emit all values
|
|
578
|
+
// (PRODUCER_LIMIT * PRODUCER_TIMEOUT) + some buffer for processing
|
|
579
|
+
await new Promise((res) => setTimeout(res, (PRODUCER_LIMIT * PRODUCER_TIMEOUT) + 100));
|
|
549
580
|
|
|
550
|
-
|
|
551
|
-
|
|
581
|
+
console.log('\n--- Stream Complete ---');
|
|
582
|
+
console.log('All received values:', receivedValues);
|
|
583
|
+
// Expected: ["Consumed: 0", "Consumed: 1", "Consumed: 2", "Consumed: 3"]
|
|
552
584
|
|
|
553
|
-
//
|
|
554
|
-
// then the final result once the factory completes.
|
|
585
|
+
unsubscribe(); // Clean up the watcher
|
|
555
586
|
}
|
|
556
587
|
|
|
557
|
-
|
|
588
|
+
runStreamExample();
|
|
558
589
|
```
|
|
559
590
|
|
|
560
591
|
### Debugging Information
|
|
561
592
|
|
|
562
|
-
Use `debugInfo()` to inspect the current state of artifacts within a container, including their status, dependencies, and dependents.
|
|
593
|
+
Use `container.debugInfo()` to inspect the current state of all registered artifacts within a container, including their status, direct dependencies (artifact-to-artifact and state), and dependents. This is an invaluable tool for understanding your application's dependency graph at runtime.
|
|
563
594
|
|
|
564
595
|
```typescript
|
|
565
596
|
import { ArtifactContainer, ArtifactScopes } from '@asaidimu/utils-artifacts';
|
|
566
597
|
import { ReactiveDataStore } from '@asaidimu/utils-store';
|
|
567
598
|
|
|
568
|
-
|
|
569
|
-
|
|
599
|
+
interface AppState {
|
|
600
|
+
configVersion: number;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const store = new ReactiveDataStore<AppState>({ configVersion: 1 });
|
|
604
|
+
const container = new ArtifactContainer<any, AppState>(store);
|
|
570
605
|
|
|
571
606
|
container.register({
|
|
572
|
-
key: '
|
|
573
|
-
factory: () =>
|
|
607
|
+
key: 'configService',
|
|
608
|
+
factory: async ({ use }) => {
|
|
609
|
+
const version = await use((ctx) => ctx.select((state) => state.configVersion));
|
|
610
|
+
return `Config v${version}`;
|
|
611
|
+
},
|
|
574
612
|
scope: ArtifactScopes.Singleton,
|
|
575
613
|
});
|
|
576
614
|
|
|
577
615
|
container.register({
|
|
578
|
-
key: '
|
|
616
|
+
key: 'dataProcessor',
|
|
579
617
|
factory: async ({ use }) => {
|
|
580
|
-
await use((ctx) => ctx.resolve('
|
|
581
|
-
return
|
|
618
|
+
const config = await use((ctx) => ctx.resolve('configService'));
|
|
619
|
+
return `Processing with ${config.instance}`;
|
|
582
620
|
},
|
|
583
621
|
scope: ArtifactScopes.Singleton,
|
|
584
622
|
});
|
|
585
623
|
|
|
624
|
+
container.register({
|
|
625
|
+
key: 'ephemeralTool',
|
|
626
|
+
factory: () => 'A transient tool',
|
|
627
|
+
scope: ArtifactScopes.Transient,
|
|
628
|
+
});
|
|
629
|
+
|
|
586
630
|
async function runDebugInfoExample() {
|
|
587
|
-
await container.resolve('
|
|
631
|
+
await container.resolve('dataProcessor'); // Resolve to build dependencies
|
|
632
|
+
await container.resolve('ephemeralTool'); // Resolve a transient (doesn't cache, but will show up if just resolved)
|
|
588
633
|
|
|
589
634
|
const debugInfo = container.debugInfo();
|
|
590
635
|
console.log('--- Artifact Debug Information ---');
|
|
591
636
|
debugInfo.forEach(node => {
|
|
592
|
-
console.log(
|
|
637
|
+
console.log(`\nID: ${node.id}`);
|
|
593
638
|
console.log(` Scope: ${node.scope}`);
|
|
594
639
|
console.log(` Status: ${node.status}`);
|
|
595
|
-
console.log(` Dependencies: ${node.dependencies.join(', ') || 'None'}`);
|
|
640
|
+
console.log(` Dependencies (Artifacts): ${node.dependencies.join(', ') || 'None'}`);
|
|
641
|
+
console.log(` Dependencies (State Paths): ${node.stateDependencies.join(', ') || 'None'}`);
|
|
596
642
|
console.log(` Dependents: ${node.dependents.join(', ') || 'None'}`);
|
|
597
643
|
console.log(` Render Count: ${node.renderCount}`);
|
|
598
644
|
console.log('---');
|
|
599
645
|
});
|
|
600
646
|
|
|
601
|
-
// Example output for '
|
|
602
|
-
// ID:
|
|
647
|
+
// Example output for 'dataProcessor':
|
|
648
|
+
// ID: dataProcessor
|
|
603
649
|
// Scope: singleton
|
|
604
650
|
// Status: active
|
|
605
|
-
// Dependencies:
|
|
651
|
+
// Dependencies (Artifacts): configService
|
|
652
|
+
// Dependencies (State Paths): None
|
|
606
653
|
// Dependents: None
|
|
607
654
|
// Render Count: 1
|
|
608
655
|
}
|
|
@@ -614,39 +661,44 @@ runDebugInfoExample();
|
|
|
614
661
|
|
|
615
662
|
## 🏛️ Project Architecture
|
|
616
663
|
|
|
617
|
-
The `@asaidimu/utils-artifacts` package is built around the central `ArtifactContainer` class, which orchestrates the lifecycle and dependencies of various "artifacts" (any component or value you register).
|
|
664
|
+
The `@asaidimu/utils-artifacts` package is built around the central `ArtifactContainer` class, which orchestrates the lifecycle and dependencies of various "artifacts" (any component or value you register). It is designed to be modular and extensible, promoting maintainability and testability.
|
|
618
665
|
|
|
619
666
|
### Core Components
|
|
620
667
|
|
|
621
|
-
* **`ArtifactContainer`**: The public-facing entry point of the system. It orchestrates the registration, resolution, lifecycle management (Singleton
|
|
622
|
-
* **`ArtifactRegistry`**: Manages the definitions of all registered artifacts, storing their factory functions and configuration options (scope, lazy loading, retries, debounce, timeout).
|
|
623
|
-
* **`ArtifactCache`**: Stores the runtime state of resolved
|
|
624
|
-
* **`ArtifactDependencyGraph`**: A specialized wrapper around a generic `DependencyGraph` that tracks artifact-to-artifact dependencies and state path dependencies. It's crucial for detecting circular dependencies and determining the cascade order during invalidation.
|
|
625
|
-
* **`ArtifactManager`**: The core engine responsible for the actual building, invalidation, and disposal of artifact instances. It interacts heavily with the `Registry`, `Cache`, `DependencyGraph`, and `DataStore`. It implements retry logic, timeouts, and manages lifecycle hooks.
|
|
626
|
-
* **`ArtifactObserverManager`**: Handles the creation and management of `ArtifactObserver` instances, allowing external code to subscribe to artifact changes. It also manages reference counting for watchers and notifies them of updates.
|
|
627
|
-
* **`DataStore` (External Dependency)**: The container relies on an external data store (e.g., `@asaidimu/utils-store`) to manage global application state. The container interacts with it via `watch` (for reactive subscriptions to state changes)
|
|
628
|
-
* **Synchronization Primitives (`Mutex`, `Once`, `Serializer`)**: These internal utilities provide robust mechanisms for managing concurrency, ensuring single execution of tasks, and processing operations sequentially. They are fundamental building blocks for the `ArtifactManager` and `ArtifactCache`.
|
|
668
|
+
* **`ArtifactContainer`**: The public-facing entry point of the system. It orchestrates the registration, resolution, lifecycle management (`Singleton`/`Transient`), and reactive updates of all artifacts. It provides methods like `register`, `resolve`, `watch`, `invalidate`, and `debugInfo`.
|
|
669
|
+
* **`ArtifactRegistry`**: Manages the definitions of all registered artifacts, storing their factory functions and configuration options (scope, lazy loading, retries, debounce, timeout). It ensures unique keys and provides basic CRUD for artifact templates.
|
|
670
|
+
* **`ArtifactCache`**: Stores the runtime state of resolved `Singleton` artifacts, including their instances, errors, cleanup functions, state dependencies, and internal synchronization primitives (`Once`, `Serializer`). It's responsible for packaging cached data into the public `ResolvedArtifact` format, maintaining reference stability.
|
|
671
|
+
* **`ArtifactDependencyGraph`**: A specialized wrapper around a generic `DependencyGraph` that tracks artifact-to-artifact dependencies and state path dependencies. It's crucial for detecting circular dependencies, managing the dependency graph, and determining the cascade order during invalidation.
|
|
672
|
+
* **`ArtifactManager`**: The core engine responsible for the actual building, invalidation, and disposal of artifact instances. It interacts heavily with the `Registry`, `Cache`, `DependencyGraph`, and `DataStore`. It implements retry logic, timeouts, and manages lifecycle hooks (`onCleanup`, `onDispose`) and `ctx.stream` functionality.
|
|
673
|
+
* **`ArtifactObserverManager`**: Handles the creation and management of `ArtifactObserver` instances, allowing external code to subscribe to artifact changes. It also manages reference counting for watchers and notifies them of updates, including special handling for `Transient` artifacts to ensure they are watched via a temporary `Singleton` clone.
|
|
674
|
+
* **`DataStore` (External Dependency)**: The container relies on an external data store (e.g., `@asaidimu/utils-store`) to manage global application state. The container interacts with it via `watch` (for reactive subscriptions to state changes), `get` (for reading the current state), and `set` (allowing artifacts to dispatch state updates).
|
|
675
|
+
* **Synchronization Primitives (`Mutex`, `Once`, `Serializer`)**: These internal utilities (`src/artifacts/sync.ts`) provide robust mechanisms for managing concurrency, ensuring single execution of tasks, and processing operations sequentially. They are fundamental building blocks for the `ArtifactManager` and `ArtifactCache`.
|
|
676
|
+
* **Error Handling (`ArtifactError` hierarchy)**: A dedicated set of custom error classes (`src/artifacts/errors.ts`) provides clear, categorized error messages for system-level issues (e.g., circular dependencies, missing artifacts, illegal operations), simplifying debugging and error handling.
|
|
629
677
|
|
|
630
678
|
### Data Flow
|
|
631
679
|
|
|
632
680
|
1. **Registration (`container.register`)**: An `ArtifactTemplate` (containing an `ArtifactFactory` and its `options`) is stored in the `ArtifactRegistry`. A corresponding node is added to the `ArtifactDependencyGraph`.
|
|
633
|
-
2. **Resolution (`container.resolve`)**:
|
|
681
|
+
2. **Resolution (`container.resolve` or `container.require`)**:
|
|
634
682
|
* If the artifact is a `Singleton` and already built, its cached instance from `ArtifactCache` is returned.
|
|
635
683
|
* If not built (or `Transient`), the `ArtifactManager` invokes the `ArtifactFactory` within a specialized `ArtifactFactoryContext`.
|
|
636
684
|
* **Dependency Declaration (`ctx.use`)**: Inside the factory, `ctx.use` provides methods to declare dependencies:
|
|
637
|
-
* `ctx.resolve(key)`: Recursively triggers resolution of another artifact. The relationship is added to the `ArtifactDependencyGraph`.
|
|
685
|
+
* `ctx.resolve(key)`/`ctx.require(key)`: Recursively triggers resolution of another artifact. The relationship is added to the `ArtifactDependencyGraph`.
|
|
638
686
|
* `ctx.select(selector)`: Extracts specific state paths from the global `DataStore`. These paths are registered as state dependencies for the artifact.
|
|
639
|
-
3. **Dependency Graph Construction**: As dependencies are declared (via `ctx.resolve` or `ctx.select`), the `ArtifactDependencyGraph` constructs a precise, bidirectional graph, linking artifacts and their state dependencies.
|
|
687
|
+
3. **Dependency Graph Construction**: As dependencies are declared (via `ctx.resolve`/`ctx.require` or `ctx.select`), the `ArtifactDependencyGraph` constructs a precise, bidirectional graph, linking artifacts and their state dependencies.
|
|
640
688
|
4. **Reactive Invalidation**:
|
|
641
|
-
* **State Changes**: When a `DataStore` value changes, `ArtifactManager` identifies affected artifacts (those subscribed to the changed state paths) and calls `container.invalidate()` for them.
|
|
642
|
-
* **Artifact Changes**: When
|
|
689
|
+
* **State Changes**: When a `DataStore` value changes, `ArtifactManager` identifies affected `Singleton` artifacts (those subscribed to the changed state paths) and calls `container.invalidate()` for them.
|
|
690
|
+
* **Artifact Changes**: When a `Singleton` artifact is rebuilt, explicitly `invalidate()`d, or proactively `stream()`s a new value, its direct and transitive `dependents` are identified by the `ArtifactDependencyGraph`, and `container.invalidate()` is called for each, cascading updates.
|
|
643
691
|
5. **Rebuild Process**: `container.invalidate()` triggers:
|
|
644
|
-
* **Debouncing**: If configured, rebuilds are debounced (`activeDebounceMs`) to consolidate multiple rapid invalidations.
|
|
692
|
+
* **Debouncing**: If configured (`debounce` option), rebuilds are debounced (`activeDebounceMs`) to consolidate multiple rapid invalidations, preventing excessive factory executions.
|
|
645
693
|
* **Cleanup**: `onCleanup` hooks for the *old* instance are executed.
|
|
646
694
|
* **New Instance**: The `ArtifactFactory` is re-executed by the `ArtifactManager`, creating a new instance.
|
|
647
695
|
* **Graph Update**: The dependency graph is updated based on any *new* dependencies declared by the re-executed factory.
|
|
648
696
|
* **Notification**: `ArtifactObserver` subscribers are notified of the new instance via `ArtifactObserverManager`.
|
|
649
|
-
6. **
|
|
697
|
+
6. **Streaming Updates (`ctx.stream`)**: If a `Singleton` artifact uses `ctx.stream`, subsequent `ctx.emit(value)` calls from within its factory:
|
|
698
|
+
* Update the artifact's cached instance in `ArtifactCache`.
|
|
699
|
+
* Increment the artifact's internal version.
|
|
700
|
+
* Trigger `invalidate()` on all its dependents and `notifyObservers()`, effectively propagating the streamed value reactively.
|
|
701
|
+
7. **Disposal (`container.dispose` / `container.unregister`)**: All resources associated with an artifact (`onDispose` hooks, state subscriptions, cache entry, graph links) are released. Full container disposal clears everything.
|
|
650
702
|
|
|
651
703
|
### Extension Points
|
|
652
704
|
|
|
@@ -656,7 +708,7 @@ The primary extension point is the **`ArtifactFactory` function**. By implementi
|
|
|
656
708
|
* Manage external resources like database connections, API clients, or message queues, ensuring they are properly initialized and cleaned up using `onCleanup` and `onDispose`.
|
|
657
709
|
* Provide custom logging, monitoring, or telemetry by injecting those services as dependencies.
|
|
658
710
|
|
|
659
|
-
The `ArtifactContainer` itself is designed to be highly configurable via `ArtifactTemplate` options, allowing fine-tuning of scope, lazy loading, error handling, and reactive behavior.
|
|
711
|
+
The `ArtifactContainer` itself is designed to be highly configurable via `ArtifactTemplate` options, allowing fine-tuning of scope, lazy loading, error handling, and reactive behavior.
|
|
660
712
|
|
|
661
713
|
---
|
|
662
714
|
|
|
@@ -668,8 +720,8 @@ We welcome contributions! Please read the guidelines below.
|
|
|
668
720
|
|
|
669
721
|
1. **Clone the repository**:
|
|
670
722
|
```bash
|
|
671
|
-
git clone https://github.com/asaidimu/utils.git
|
|
672
|
-
cd utils/src/artifacts
|
|
723
|
+
git clone https://github.com/asaidimu/erp-utils.git
|
|
724
|
+
cd erp-utils/src/artifacts # Navigate to this package's directory
|
|
673
725
|
```
|
|
674
726
|
2. **Install dependencies**:
|
|
675
727
|
```bash
|
|
@@ -684,15 +736,17 @@ We welcome contributions! Please read the guidelines below.
|
|
|
684
736
|
|
|
685
737
|
### Scripts
|
|
686
738
|
|
|
739
|
+
The `package.json` for this package includes the following scripts:
|
|
740
|
+
|
|
687
741
|
* `bun install`: Installs project dependencies.
|
|
688
742
|
* `bun run test`: Runs the test suite using Vitest.
|
|
689
743
|
* `bun run test:watch`: Runs tests in watch mode for continuous feedback.
|
|
690
|
-
* `bun run test:browser`: Runs tests in a browser environment.
|
|
744
|
+
* `bun run test:browser`: Runs tests in a browser environment using Playwright.
|
|
691
745
|
* `bun run build`: Compiles the TypeScript source code into JavaScript.
|
|
692
746
|
|
|
693
747
|
### Testing
|
|
694
748
|
|
|
695
|
-
This project uses [Vitest](https://vitest.dev/) for testing.
|
|
749
|
+
This project uses [Vitest](https://vitest.dev/) for testing, with support for browser-based tests via Playwright.
|
|
696
750
|
|
|
697
751
|
* To run all tests:
|
|
698
752
|
```bash
|
|
@@ -702,21 +756,25 @@ This project uses [Vitest](https://vitest.dev/) for testing.
|
|
|
702
756
|
```bash
|
|
703
757
|
bun run test --watch
|
|
704
758
|
```
|
|
759
|
+
* To run tests in a browser environment:
|
|
760
|
+
```bash
|
|
761
|
+
bun run test:browser
|
|
762
|
+
```
|
|
705
763
|
* Ensure all new features or bug fixes are accompanied by appropriate unit and/or integration tests.
|
|
706
764
|
|
|
707
765
|
### Contributing Guidelines
|
|
708
766
|
|
|
709
767
|
We appreciate your interest in contributing to `@asaidimu/utils-artifacts`!
|
|
710
|
-
Please refer to the main repository's [CONTRIBUTING.md](https://github.com/asaidimu/utils/blob/main/CONTRIBUTING.md) for detailed guidelines on:
|
|
768
|
+
Please refer to the main repository's [CONTRIBUTING.md](https://github.com/asaidimu/erp-utils/blob/main/CONTRIBUTING.md) for detailed guidelines on:
|
|
711
769
|
|
|
712
770
|
* Reporting issues
|
|
713
771
|
* Submitting pull requests
|
|
714
772
|
* Coding standards
|
|
715
|
-
* Commit message conventions (we follow Conventional Commits)
|
|
773
|
+
* Commit message conventions (we follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/))
|
|
716
774
|
|
|
717
775
|
### Issue Reporting
|
|
718
776
|
|
|
719
|
-
Found a bug or have a feature request? Please open an issue on our [GitHub Issues page](https://github.com/asaidimu/utils/issues).
|
|
777
|
+
Found a bug or have a feature request? Please open an issue on our [GitHub Issues page](https://github.com/asaidimu/erp-utils/issues).
|
|
720
778
|
Provide as much detail as possible, including steps to reproduce, expected behavior, and your environment.
|
|
721
779
|
|
|
722
780
|
---
|
|
@@ -728,12 +786,12 @@ Provide as much detail as possible, including steps to reproduce, expected behav
|
|
|
728
786
|
* **`ArtifactNotFoundError`**: This typically means you're trying to `resolve` or `watch` an artifact that hasn't been `register`ed in the current container. Double-check your `key`s and registration calls.
|
|
729
787
|
* **`CircularDependencyError`**: Occurs when artifact A depends on B, and B depends back on A (directly or indirectly). This indicates a structural problem in your dependency graph. Redesign your artifacts to break the cycle. The error message will show the path of the cycle.
|
|
730
788
|
* **Artifact Not Updating Reactively**:
|
|
731
|
-
* Ensure your artifact is a `Singleton` (Transient artifacts
|
|
732
|
-
* Verify you are using `ctx.use()` and `ctx.select()` (for state) or `ctx.resolve()` (for
|
|
733
|
-
* Check that your `DataStore` implementation is correctly triggering its `watch` callbacks.
|
|
734
|
-
* **`IllegalScopeError`**: You might be trying to `
|
|
735
|
-
* **`TimeoutError`**: Your artifact factory took longer than its configured `timeout` to complete, or
|
|
736
|
-
* **`WatcherDisposedError`**: You are attempting to access a watcher's state (`watcher.get()`) after `watcher.dispose()` has been called. Ensure you unsubscribe and dispose of watchers when no longer needed, and avoid using them afterward.
|
|
789
|
+
* Ensure your artifact is a `Singleton` (Transient artifacts do not cache, react to invalidations, or support streaming).
|
|
790
|
+
* Verify you are using `ctx.use()` and `ctx.select()` (for state dependencies) or `ctx.use(({ resolve }) => resolve('otherKey'))` (for artifact dependencies) inside your factory to properly declare dependencies.
|
|
791
|
+
* Check that your `DataStore` implementation is correctly triggering its `watch` callbacks when state changes.
|
|
792
|
+
* **`IllegalScopeError`**: You might be trying to `stream` a value from a `Transient` artifact. The `stream` method is only meaningful for `Singleton` artifacts, which maintain a persistent, updateable instance.
|
|
793
|
+
* **`TimeoutError`**: Your artifact factory took longer than its configured `timeout` to complete, or an internal `Mutex` lock operation exceeded its timeout. Consider increasing the `timeout` in the artifact's `ArtifactTemplate` or optimizing your factory's logic.
|
|
794
|
+
* **`WatcherDisposedError`**: You are attempting to access a watcher's state (`watcher.get()`) after `watcher.dispose()` has been called. Ensure you unsubscribe from all callbacks (`unsubscribe()`) and dispose of watchers when no longer needed (`watcher.dispose()`), and avoid using them afterward.
|
|
737
795
|
|
|
738
796
|
### FAQ
|
|
739
797
|
|
|
@@ -742,22 +800,23 @@ Provide as much detail as possible, including steps to reproduce, expected behav
|
|
|
742
800
|
* **Why use this over a simpler DI library?**
|
|
743
801
|
`@asaidimu/utils-artifacts` goes beyond basic DI by adding a powerful reactive layer. It automatically handles the propagation of changes from global state or other artifacts, making it easier to build self-updating components and reactive systems without manual subscription management.
|
|
744
802
|
* **When should I use `Singleton` vs. `Transient`?**
|
|
745
|
-
* **Singleton**: For services, database connections, configuration objects, or any resource that should be shared and consistently available throughout your application. They are efficient as they are built once and reused.
|
|
746
|
-
* **Transient**: For objects that need a fresh instance every time they are requested, such as temporary calculators, request-scoped contexts, or factory functions that produce unique outputs.
|
|
803
|
+
* **Singleton**: For services, database connections, configuration objects, or any resource that should be shared and consistently available throughout your application. They are efficient as they are built once and reused, and they react to dependency changes.
|
|
804
|
+
* **Transient**: For objects that need a fresh instance every time they are requested, such as temporary calculators, request-scoped contexts, or factory functions that produce unique, short-lived outputs. They do not react to dependencies or retain state.
|
|
747
805
|
* **How does `debounce` work?**
|
|
748
|
-
`debounce` prevents
|
|
806
|
+
The `debounce` option (part of `ArtifactTemplate`) prevents a `Singleton` artifact from rebuilding too frequently. If multiple invalidations occur within the specified `debounce` time, they are consolidated, and the artifact's factory is only run once after the activity ceases (or after the last invalidation within the debounce window). This is crucial for performance with rapid state changes, especially when many state changes might trigger the same artifact rebuild.
|
|
749
807
|
|
|
750
808
|
### Changelog / Roadmap
|
|
751
809
|
|
|
752
|
-
* **Changelog**: For a detailed list of changes, please refer to the [CHANGELOG.md](https://github.com/asaidimu/utils/blob/main/CHANGELOG.md) file in the main repository.
|
|
753
|
-
* **Roadmap**: Future plans and upcoming features are typically outlined in the main repository's [issue tracker](https://github.com/asaidimu/utils/issues) or project boards.
|
|
810
|
+
* **Changelog**: For a detailed list of changes, please refer to the [CHANGELOG.md](https://github.com/asaidimu/erp-utils/blob/main/CHANGELOG.md) file in the main repository.
|
|
811
|
+
* **Roadmap**: Future plans and upcoming features for `@asaidimu/utils-artifacts` are typically outlined in the main repository's [issue tracker](https://github.com/asaidimu/erp-utils/issues) or project boards.
|
|
754
812
|
|
|
755
813
|
### License
|
|
756
814
|
|
|
757
|
-
This project is licensed under the MIT License
|
|
815
|
+
This project is licensed under the [MIT License](https://github.com/asaidimu/erp-utils/blob/main/LICENSE).
|
|
758
816
|
|
|
759
817
|
### Acknowledgments
|
|
760
818
|
|
|
761
819
|
* Developed as part of the `@asaidimu` utilities collection.
|
|
820
|
+
* Leverages foundational packages like [`@asaidimu/utils-store`](https://www.npmjs.com/package/@asaidimu/utils-store) and `@asaidimu/events` within the `erp-utils` monorepo.
|
|
762
821
|
* Uses [Semantic Release](https://semantic-release.gitbook.io/semantic-release/) for automated versioning and publishing.
|
|
763
|
-
*
|
|
822
|
+
* Utilizes [Vitest](https://vitest.dev/) for fast and reliable testing.
|
package/index.d.mts
CHANGED
|
@@ -244,6 +244,16 @@ interface UseDependencyContext<TRegistry extends Record<string, any>, TState ext
|
|
|
244
244
|
* @throws {SystemError} if the key is missing or if resolving creates a circular dependency.
|
|
245
245
|
*/
|
|
246
246
|
resolve<K extends keyof TRegistry>(key: K): Promise<ResolvedArtifact<TRegistry[K]>>;
|
|
247
|
+
/**
|
|
248
|
+
* Resolves another artifact from the container and registers a dependency on it.
|
|
249
|
+
* If the dependency changes, the current artifact will be invalidated and rebuilt.
|
|
250
|
+
* @template K The key of the artifact to resolve.
|
|
251
|
+
* @param key The key of the artifact to resolve.
|
|
252
|
+
* @returns A Promise that resolves to a the resolved instance, unlike
|
|
253
|
+
* resolve, this will throw an error if resolution fails.
|
|
254
|
+
* @throws {SystemError} if any error occurs during resolution.
|
|
255
|
+
*/
|
|
256
|
+
require<K extends keyof TRegistry>(key: K): Promise<TRegistry[K]>;
|
|
247
257
|
/**
|
|
248
258
|
* Selects a slice of the global state and registers a dependency on it.
|
|
249
259
|
* If the selected state slice changes (based on a deep comparison), the current
|
|
@@ -460,11 +470,13 @@ interface ArtifactObserver<TArtifact> {
|
|
|
460
470
|
*/
|
|
461
471
|
subscribe(callback: (artifact: ResolvedArtifact<TArtifact>) => void): () => void;
|
|
462
472
|
/**
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
473
|
+
* Resolves another artifact from the container and registers a dependency on it.
|
|
474
|
+
* If the dependency changes, the current artifact will be invalidated and rebuilt.
|
|
475
|
+
* @returns A Promise that resolves to a `ResolvedArtifact` containing the instance
|
|
476
|
+
* or an external error.
|
|
477
|
+
* @throws {SystemError} if resolution fails.
|
|
466
478
|
*/
|
|
467
|
-
|
|
479
|
+
resolve(): Promise<ResolvedArtifact<TArtifact>>;
|
|
468
480
|
}
|
|
469
481
|
/**
|
|
470
482
|
* Configuration options for defining an artifact. These options control
|
|
@@ -621,6 +633,16 @@ declare class ArtifactContainer<TRegistry extends Record<string, any> = Record<s
|
|
|
621
633
|
* @throws {ArtifactError} if a cycle is detected or other system errors occur
|
|
622
634
|
*/
|
|
623
635
|
resolve<K extends keyof TRegistry>(key: K): Promise<ResolvedArtifact<TRegistry[K]>>;
|
|
636
|
+
/**
|
|
637
|
+
* Resolves an artifact by its key, returning its instance directly.
|
|
638
|
+
* This is a wrapper around resolve to prevent defensive programming
|
|
639
|
+
*
|
|
640
|
+
* @param key The unique identifier for the artifact
|
|
641
|
+
* @returns A Promise that resolves to a ResolvedArtifact
|
|
642
|
+
* @throws {ArtifactNotFoundError} if the artifact is not found
|
|
643
|
+
* @throws {ArtifactError} if a cycle is detected or other system errors occur
|
|
644
|
+
*/
|
|
645
|
+
require<K extends keyof TRegistry>(key: K): Promise<TRegistry[K]>;
|
|
624
646
|
/**
|
|
625
647
|
* Returns an ArtifactWatcher for a given artifact key.
|
|
626
648
|
* The watcher allows subscribing to changes in the artifact's resolved value.
|
package/index.d.ts
CHANGED
|
@@ -244,6 +244,16 @@ interface UseDependencyContext<TRegistry extends Record<string, any>, TState ext
|
|
|
244
244
|
* @throws {SystemError} if the key is missing or if resolving creates a circular dependency.
|
|
245
245
|
*/
|
|
246
246
|
resolve<K extends keyof TRegistry>(key: K): Promise<ResolvedArtifact<TRegistry[K]>>;
|
|
247
|
+
/**
|
|
248
|
+
* Resolves another artifact from the container and registers a dependency on it.
|
|
249
|
+
* If the dependency changes, the current artifact will be invalidated and rebuilt.
|
|
250
|
+
* @template K The key of the artifact to resolve.
|
|
251
|
+
* @param key The key of the artifact to resolve.
|
|
252
|
+
* @returns A Promise that resolves to a the resolved instance, unlike
|
|
253
|
+
* resolve, this will throw an error if resolution fails.
|
|
254
|
+
* @throws {SystemError} if any error occurs during resolution.
|
|
255
|
+
*/
|
|
256
|
+
require<K extends keyof TRegistry>(key: K): Promise<TRegistry[K]>;
|
|
247
257
|
/**
|
|
248
258
|
* Selects a slice of the global state and registers a dependency on it.
|
|
249
259
|
* If the selected state slice changes (based on a deep comparison), the current
|
|
@@ -460,11 +470,13 @@ interface ArtifactObserver<TArtifact> {
|
|
|
460
470
|
*/
|
|
461
471
|
subscribe(callback: (artifact: ResolvedArtifact<TArtifact>) => void): () => void;
|
|
462
472
|
/**
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
473
|
+
* Resolves another artifact from the container and registers a dependency on it.
|
|
474
|
+
* If the dependency changes, the current artifact will be invalidated and rebuilt.
|
|
475
|
+
* @returns A Promise that resolves to a `ResolvedArtifact` containing the instance
|
|
476
|
+
* or an external error.
|
|
477
|
+
* @throws {SystemError} if resolution fails.
|
|
466
478
|
*/
|
|
467
|
-
|
|
479
|
+
resolve(): Promise<ResolvedArtifact<TArtifact>>;
|
|
468
480
|
}
|
|
469
481
|
/**
|
|
470
482
|
* Configuration options for defining an artifact. These options control
|
|
@@ -621,6 +633,16 @@ declare class ArtifactContainer<TRegistry extends Record<string, any> = Record<s
|
|
|
621
633
|
* @throws {ArtifactError} if a cycle is detected or other system errors occur
|
|
622
634
|
*/
|
|
623
635
|
resolve<K extends keyof TRegistry>(key: K): Promise<ResolvedArtifact<TRegistry[K]>>;
|
|
636
|
+
/**
|
|
637
|
+
* Resolves an artifact by its key, returning its instance directly.
|
|
638
|
+
* This is a wrapper around resolve to prevent defensive programming
|
|
639
|
+
*
|
|
640
|
+
* @param key The unique identifier for the artifact
|
|
641
|
+
* @returns A Promise that resolves to a ResolvedArtifact
|
|
642
|
+
* @throws {ArtifactNotFoundError} if the artifact is not found
|
|
643
|
+
* @throws {ArtifactError} if a cycle is detected or other system errors occur
|
|
644
|
+
*/
|
|
645
|
+
require<K extends keyof TRegistry>(key: K): Promise<TRegistry[K]>;
|
|
624
646
|
/**
|
|
625
647
|
* Returns an ArtifactWatcher for a given artifact key.
|
|
626
648
|
* The watcher allows subscribing to changes in the artifact's resolved value.
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,t,r=Object.create,s=Object.defineProperty,n=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,c=(e={"src/store/node_modules/@asaidimu/events/index.js"(e,t){var r,s=Object.defineProperty,n=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,o={};((e,t)=>{for(var r in t)s(e,r,{get:t[r],enumerable:!0})})(o,{createEventBus:()=>c}),t.exports=(r=o,((e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))a.call(e,c)||c===r||s(e,c,{get:()=>t[c],enumerable:!(o=n(t,c))||o.enumerable});return e})(s({},"__esModule",{value:!0}),r));var c=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error("EventBus Error:",e),crossTab:!1,channelName:"event-bus-channel"})=>{const t=new Map;let r=[],s=0,n=0;const i=new Map,a=new Map;let o=null;e.crossTab&&"undefined"!=typeof BroadcastChannel?o=new BroadcastChannel(e.channelName):e.crossTab&&console.warn("BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.");const c=(e,t)=>{s++,n+=t,i.set(e,(i.get(e)||0)+1)},h=()=>{const t=r;r=[],t.forEach((({name:t,payload:r})=>{const s=performance.now();try{(a.get(t)||[]).forEach((e=>e(r)))}catch(s){e.errorHandler({...s,eventName:t,payload:r})}c(t,performance.now()-s)}))},d=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(h,e.batchDelay)}})(),l=e=>{const r=t.get(e);r?a.set(e,Array.from(r)):a.delete(e)};return o&&(o.onmessage=e=>{const{name:t,payload:r}=e.data;(a.get(t)||[]).forEach((e=>e(r)))}),{subscribe:(e,r)=>{t.has(e)||t.set(e,new Set);const s=t.get(e);return s.add(r),l(e),()=>{s.delete(r),0===s.size?(t.delete(e),a.delete(e)):l(e)}},emit:({name:t,payload:s})=>{if(e.async)return r.push({name:t,payload:s}),r.length>=e.batchSize?h():d(),void(o&&o.postMessage({name:t,payload:s}));const n=performance.now();try{(a.get(t)||[]).forEach((e=>e(s))),o&&o.postMessage({name:t,payload:s})}catch(r){e.errorHandler({...r,eventName:t,payload:s})}c(t,performance.now()-n)},getMetrics:()=>({totalEvents:s,activeSubscriptions:Array.from(t.values()).reduce(((e,t)=>e+t.size),0),eventCounts:i,averageEmitDuration:s>0?n/s:0}),clear:()=>{t.clear(),a.clear(),r=[],s=0,n=0,i.clear(),o&&(o.close(),o=null)}}}}},function(){return t||(0,e[i(e)[0]])((t={exports:{}}).exports,t),t.exports}),h=(e=>(e.Singleton="singleton",e.Transient="transient",e))(h||{}),d=class e extends Error{category;constructor(t,r,s){super(t,{cause:s}),this.name="ArtifactError",this.category=r,Object.setPrototypeOf(this,e.prototype)}},l=class extends d{constructor(e){super(`[ArtifactContainer] Artifact "${e}" not found.`,"system")}},u=class extends d{constructor(e){super(`[ArtifactContainer] Artifact with key:${e} has already been disposed`,"system")}},p=class extends d{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`,"system")}},g=class extends d{constructor(e){super(`[ArtifactContainer] An artifact with key:${e} already exists!`,"system")}},f=class extends d{constructor(){super("[Serializer] The serializer has been marked as done!","system")}},y=class{artifacts=new Map;register({key:e,factory:t,lazy:r,...s}){if(this.artifacts.has(e))throw new g(String(e));const{scope:n,...i}=s,a={key:e,factory:t,scope:s.scope??"singleton",lazy:void 0===r||r,...i};return this.artifacts.set(e,a),()=>this.unregister(e)}get(e){if(!this.has(e))throw new l(String(e));return this.artifacts.get(e)}has(e){return this.artifacts.has(e)}async unregister(e){this.artifacts.has(e)&&this.artifacts.delete(e)}size(){return this.artifacts.size}keys(){return Array.from(this.artifacts.keys())}clear(){this.artifacts.clear()}};((e,t,c)=>{c=null!=e?r(a(e)):{},((e,t,r,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))o.call(e,c)||c===r||s(e,c,{get:()=>t[c],enumerable:!(a=n(t,c))||a.enumerable})})(e&&e.__esModule?c:s(c,"default",{value:e,enumerable:!0}),e)})(c());var m=class{_locked=!1;_capacity;waiters=[];constructor(e){this._capacity=e?.capacity??1/0}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let t;const r=new Promise((e=>t=e));if(this.waiters.push(t),null!=e)try{await Promise.race([r,new Promise(((r,s)=>setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),s(new p("Mutex lock timed out"))}),e)))])}catch(e){throw e}else await r}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}},w=class{constructor(e=!1,t=!1){this.retry=e,this.throws=t}mutex=new m;promise=null;_value=null;_error;_done=!1;async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{const t=await e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.retry&&!this._done&&(this.promise=null)}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}running(){return null!==this.promise&&!this._done}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){this._done=!1,this.promise=null,this._value=null,this._error=void 0}resolved(){return this.promise}done(){return this._done}_awaitWithTimeout(e,t,r="Operation timed out"){return null==t?e:Promise.race([e,new Promise(((e,s)=>setTimeout((()=>s(new p(r))),t)))])}},v=class{mutex;_done=!1;_lastValue=null;_lastError=void 0;constructor(e){this.mutex=new m({capacity:e?.capacity??1e3})}async do(e,t){if(this._done)return{value:null,error:new f};try{await this.mutex.lock(t)}catch(e){return{value:null,error:e}}let r,s=null;try{if(this._done)throw new f;s=await e(),this._lastValue=s,this._lastError=void 0}catch(e){r=e,this._lastError=e}finally{this.mutex.unlock()}return{value:s,error:r}}peek(){return{value:this._lastValue,error:this._lastError}}close(){this._done=!0}pending(){return this.mutex.pending()}running(){return this.mutex.locked()}},b=class{constructor(e,t,r,s,n){this.registry=e,this.cache=t,this.graph=r,this.store=s,this.observer=n}async build(e,t){const r=this.registry.get(e);if(!r)throw new d(`Template not found for artifact "${String(e)}"`,"system");const s=t?new Set(t):new Set;if(s.has(e))throw new d(`Cycle detected: Artifact "${String(e)}" depends on itself via path: ${Array.from(s).join(" -> ")}`,"system");s.add(e);let n=this.cache.get(e);n||(n=this.createCachedArtifact(r),this.cache.set(e,n));const i=await("transient"===r.scope?this.executeBuild(r,n,s):n.buildOnce.do((()=>this.executeBuild(r,n,s))));if("transient"===r.scope)return i;const a=this.cache.package(e,((t,r)=>this.invalidate(e,t,r)));return n.stream&&n.streamOnce.do(n.stream),a}async executeBuild(e,t,r){const s=e.key;t.buildCount++;const n=[],i=[];let a=!0;t.activeDebounceMs=e.debounce??0,n.push((()=>{a=!1}));const o=new Set,c=new Set,h=new Map,l={state:()=>this.store.get(!0),previous:t.instance,onCleanup:e=>n.push(e),onDispose:e=>i.push(e),use:async e=>e({resolve:async e=>{if(e===s)throw new d(`Artifact "${String(s)}" depends on itself.`,"system");const t=this.graph.wouldCreateCycle(s,e,r);if(t)throw new d(`Adding dependency "${String(e)}" to "${String(s)}" would create a cycle: ${t.join(" -> ")}`,"system");c.add(e);const n=await this.build(e,r),i=this.cache.get(e);return i&&h.set(e,i.version),n},select:e=>(function(e,t="."){const r=new Set,s=(e=[])=>new Proxy({},{get:(n,i)=>{if("symbol"==typeof i)return;const a=[...e,i],o=a.join(t);return r.add(o),s(a)}});try{e(s())}catch(e){throw new Error(`Selector failed during path analysis. This usually means the selector is too complex. Selectors must be simple property accessors only. Error: ${e instanceof Error?e.message:String(e)}`)}const n=Array.from(r);return n.filter((e=>!n.some((r=>r!==e&&r.startsWith(e+t)))))}(e).forEach((e=>o.add(e))),e(this.store.get(!0)))}),stream:async r=>{if("transient"===e.scope)throw new d(`[ArtifactManager] Illegal stream on transient artifact "${String(s)}"`,"system");const n=async(e,r=void 0)=>{await t.streamSerializer.do((async()=>{void 0!==t.stream&&(t.instance=e,t.error=r,t.version++,this.cache.invalidatePackage(s),await this.processStream(s))}))},i={value:()=>t.instance,signal:t.streamController.signal,set:(...e)=>this.store.set(...e),emit:e=>n(e)};t.stream=async()=>{try{await r(i)}catch(e){await n(void 0,e),this.invalidate(s,!1,!0)}}}};let u,p,g=0;const f=(e.retries??0)+1;for(;g<f;)try{const t=e.factory(l);if(e.timeout){const r=new Promise(((t,r)=>setTimeout((()=>r(new Error(`Timeout: ${e.timeout}ms`))),e.timeout)));u=await Promise.race([t,r])}else u=await t;break}catch(e){if(e instanceof d)throw e;g++,g>=f&&(p=e)}if(a){for(const[e,t]of h){const r=this.cache.get(e);r&&r.version!==t&&(p=new d(`Build stale on arrival: Dependency "${String(e)}" changed during build.`,"system"))}if("singleton"===e.scope&&this.updateDependencyGraph(s,c,o),"singleton"===e.scope&&(t.cleanupFunctions=n,t.disposeFunctions=i),p?t.error=p:t.instance=u,t.version++,this.cache.invalidatePackage(s),"transient"===e.scope)return{instance:u,cleanup:this.createCompositeCleanup(n),error:p,ready:!0,invalidate:async()=>console.warn(`[ArtifactManager] Cannot invalidate transient "${String(s)}"`)}}else{const e=this.createCompositeCleanup(n);e&&await e()}}async invalidate(e,t=!1,r=!1){const s=this.cache.get(e);if(s)return s.debounceTimer&&(clearTimeout(s.debounceTimer),s.debounceTimer=void 0),!t&&s.activeDebounceMs>0?new Promise((n=>{s.debounceTimer=setTimeout((()=>{s.debounceTimer=void 0,this.executeInvalidation(e,t,r).then(n).catch(n)}),s.activeDebounceMs)})):this.executeInvalidation(e,t,r)}async executeInvalidation(e,t,r=!1){const s=this.cache.get(e);s&&(await s.invalidationOnce.do((async()=>{s.version++,await this.cache.invalidateInstance(e);const n=this.graph.getDependents(e);await Promise.all(n.map((e=>this.invalidate(e).catch((t=>{console.error(`[ArtifactManager] Cascade failed for "${String(e)}":`,t)})))));const i=this.registry.get(e);!i||!t&&i.lazy&&!this.hasWatchers(e)||r||await this.build(e),this.observer.notify(e)})),s.invalidationOnce.reset())}async dispose(e){const t=this.cache.get(e);t&&(t.streamController.abort(),t.buildOnce.running()&&await t.buildOnce.resolved(),t.invalidationOnce.running()&&await t.invalidationOnce.resolved(),t.streamOnce.running()&&await t.streamOnce.resolved(),await this.cache.invalidateInstance(e),this.graph.removeNode(e),this.cache.delete(e))}async processStream(e){try{const t=this.graph.getDependents(e);await Promise.all(t.map((e=>this.invalidate(e).catch((t=>{console.error(`[ArtifactManager] Failed to invalidate dependent "${String(e)}":`,t)}))))),this.observer.notify(e)}catch(t){console.error(`[ArtifactManager] Stream propagation error "${e}":`,t)}}updateDependencyGraph(e,t,r){const s=this.cache.get(e);s&&(this.graph.hasNode(e)||this.graph.registerNode(e),this.graph.setDependencies(e,t),s.stateUnsubscribe&&s.stateUnsubscribe(),s.stateDependencies=r,r.size>0&&(s.stateUnsubscribe=this.store.watch(Array.from(r),(async()=>{await this.invalidate(e)}))))}createCachedArtifact(e){return{instance:void 0,version:0,streamController:new AbortController,error:void 0,cleanupFunctions:[],disposeFunctions:[],buildCount:0,stateDependencies:new Set,buildOnce:new w(!0,!0),streamOnce:new w(!0,!0),streamSerializer:new v,activeDebounceMs:e.debounce??0,invalidationOnce:new w(!0,!1)}}createCompositeCleanup(e){if(e.length)return async()=>{for(let t=e.length-1;t>=0;t--)try{await e[t]()}catch(e){console.error("[ArtifactManager] Cleanup error:",e)}}}hasWatchers(e){return this.observer.hasWatchers(e)??!1}},C=class{dependencies=new Map;dependents=new Map;metadata=new Map;registerNode(e,t){this.dependencies.has(e)||this.dependencies.set(e,new Set),this.dependents.has(e)||this.dependents.set(e,new Set),void 0!==t&&this.metadata.set(e,t)}removeNode(e){if(!this.hasNode(e))return;const t=this.dependencies.get(e);if(t)for(const r of t)this.dependents.get(r)?.delete(e);const r=this.dependents.get(e);if(r)for(const t of r)this.dependencies.get(t)?.delete(e);this.dependencies.delete(e),this.dependents.delete(e),this.metadata.delete(e)}hasNode(e){return this.dependencies.has(e)}addDependency(e,t){this.registerNode(e),this.registerNode(t),this.dependencies.get(e).add(t),this.dependents.get(t).add(e)}removeDependency(e,t){this.dependencies.get(e)?.delete(t),this.dependents.get(t)?.delete(e)}getDependencies(e){const t=this.dependencies.get(e);return t?new Set(t):new Set}getDependents(e){const t=this.dependents.get(e);return t?new Set(t):new Set}getMetadata(e){return this.metadata.get(e)}setMetadata(e,t){this.registerNode(e),this.metadata.set(e,t)}setDependencies(e,t){this.registerNode(e);const r=this.dependencies.get(e),s=new Set(t);for(const t of r)s.has(t)||this.removeDependency(e,t);for(const t of s)r.has(t)||this.addDependency(e,t)}wouldCreateCycle(e,t){if(e===t)return[e,t];const r=[t],s=new Set([t]),n=new Map;for(;r.length>0;){const i=r.shift();if(i===e){const r=[];let s=e;for(;void 0!==s&&(r.push(s),s!==t);)s=n.get(s);return r.reverse(),[e,...r]}const a=this.dependencies.get(i);if(a)for(const e of a)s.has(e)||(s.add(e),n.set(e,i),r.push(e))}return null}topologicalSort(){const e=new Map,t=Array.from(this.dependencies.keys());for(const r of t)e.set(r,this.dependencies.get(r)?.size??0);const r=[];for(const[t,s]of e)0===s&&r.push(t);const s=[];for(;r.length>0;){const t=r.shift();s.push(t);const n=this.dependents.get(t);if(n)for(const t of n){const s=(e.get(t)||0)-1;e.set(t,s),0===s&&r.push(t)}}if(s.length!==t.length)throw new Error("Cycle detected in graph; topological sort impossible.");return s}getTransitiveDependencies(e,t=!1){return this.bfs(e,"dependencies",t)}getTransitiveDependents(e,t=!1){return this.bfs(e,"dependents",t)}bfs(e,t,r){const s=new Set,n=new Set,i=[e];n.add(e),r&&s.add(e);const a="dependencies"===t?this.dependencies:this.dependents;for(;i.length>0;){const e=i.shift(),t=a.get(e);if(t)for(const e of t)n.has(e)||(n.add(e),s.add(e),i.push(e))}return s}getAllNodes(){return Array.from(this.dependencies.keys())}size(){return this.dependencies.size}clear(){this.dependencies.clear(),this.dependents.clear(),this.metadata.clear()}toDebugString(){return Array.from(this.dependencies.entries()).map((([e,t])=>{const r=Array.from(t).join(", ");return`${String(e)} → [${r||"∅"}]`})).join("\n")}},A=class{graph;constructor(){this.graph=new C}registerNode(e){this.graph.registerNode(e)}removeNode(e){this.graph.removeNode(e)}addDependency(e,t){this.graph.addDependency(e,t)}removeDependency(e,t){this.graph.removeDependency(e,t)}getDependents(e){const t=this.graph.getDependents(e);return Array.from(t)}getDependencies(e){const t=this.graph.getDependencies(e);return Array.from(t)}getTransitiveDependents(e){const t=this.graph.getTransitiveDependents(e,!1);return new Set(Array.from(t))}setDependencies(e,t){const r=Array.from(t).map((e=>e));this.graph.setDependencies(e,r)}wouldCreateCycle(e,t,r){if(r?.has(t)){const e=Array.from(r),s=e.indexOf(t),n=e.slice(s);return n.push(t),n}const s=this.graph.wouldCreateCycle(e,t);return s||null}hasNode(e){return this.graph.hasNode(e)}getAllNodes(){return this.graph.getAllNodes()}clear(){this.graph.clear()}size(){return this.graph.size()}toDebugString(){return this.graph.toDebugString()}},_=class{cache=new Map;emptyInstance={instance:void 0,error:void 0,ready:!1,cleanup:void 0,invalidate:async(e,t)=>{}};get(e){return this.cache.get(e)}set(e,t){this.cache.set(e,t)}delete(e){this.cache.delete(e)}has(e){return this.cache.has(e)}clear(){this.cache.clear()}size(){return this.cache.size}keys(){return Array.from(this.cache.keys())}package(e,t){const r=this.get(e);if(!r)return this.emptyInstance;if(r.packagedArtifact)return this.updatePackagedArtifact(r,r.packagedArtifact,t),r.packagedArtifact;const s={instance:r.instance,error:r.error,ready:r.buildOnce.done()&&!r.buildOnce.running(),cleanup:this.createCompositeCleanup(r.cleanupFunctions),invalidate:t};return r.packagedArtifact=s,s}async invalidatePackage(e){const t=this.get(e);t&&(t.packagedArtifact=void 0)}async invalidateInstance(e){const t=this.get(e);if(t){t.stateUnsubscribe&&(t.stateUnsubscribe(),t.stateUnsubscribe=void 0),t.debounceTimer&&(clearTimeout(t.debounceTimer),t.debounceTimer=void 0),t.streamController.abort(),await t.streamOnce.resolved(),t.streamOnce.reset(),t.streamSerializer.close();for(let e=t.cleanupFunctions.length-1;e>=0;e--)try{await t.cleanupFunctions[e]()}catch(e){console.error("[ArtifactManager] Cleanup error:",e)}for(let e=t.disposeFunctions.length-1;e>=0;e--)try{await t.disposeFunctions[e]()}catch(e){console.error("[ArtifactManager] Dispose error:",e)}t.instance=void 0,t.error=void 0,t.cleanupFunctions=[],t.disposeFunctions=[],t.buildOnce.reset(),t.stream=void 0,t.streamSerializer=new v,t.streamController=new AbortController}}updatePackagedArtifact(e,t,r){t.instance=e.instance,t.error=e.error,t.ready=e.buildOnce.done()&&!e.buildOnce.running(),t.cleanup=this.createCompositeCleanup(e.cleanupFunctions),t.invalidate=r}createCompositeCleanup(e){if(e.length)return async()=>{for(let t=e.length-1;t>=0;t--)try{await e[t]()}catch(e){console.error("[ArtifactCache] Cleanup error:",e)}}}},k=class{constructor(e,t,r){this.registry=e,this.cache=t,this.container=r}listeners=new Map;watcherCache=new Map;watch(e){const t=String(e),r=this.registry.get(e);if(!r)throw new Error(`Artifact "${t}" not registered`);let s=t;if("transient"===r.scope&&(s=`${t}__watched`,this.registry.has(s)||this.registry.register({key:s,factory:r.factory,scope:"singleton",lazy:r.lazy,timeout:r.timeout,retries:r.retries,debounce:r.debounce})),this.watcherCache.has(s)){const e=this.watcherCache.get(s);return e.count++,e}let n=!1;const i={id:t,count:1,get:()=>{if(!this.cache.get(s))return this.cache.package(s,(e=>this.container.invalidate(s,e)));const e=this.cache.package(s,(e=>this.container.invalidate(s,e)));return n?{...e,ready:!1,instance:void 0,error:new u(t),cleanup:()=>{},invalidate:()=>Promise.resolve()}:e},subscribe:e=>{this.listeners.has(s)||this.listeners.set(s,new Set);const t=()=>e(i.get());return this.listeners.get(s).add(t),this.container.resolve(s).catch((e=>{console.error(`[ArtifactObserver] Background resolution failed for "${s}":`,e)})).then(t),()=>this.listeners.get(s)?.delete(t)},dispose:async()=>{i.count--,i.count<=0&&(this.watcherCache.delete(s),"transient"===r.scope&&(await this.registry.unregister(s),this.cache.delete(s)),n=!0,this.notify(s),this.listeners.delete(s))}};return this.watcherCache.set(s,i),i}notify(e){const t=this.listeners.get(e);t&&t.forEach((t=>{try{t()}catch(t){console.error(`[ArtifactObserver] Listener error for "${e}":`,t)}}))}hasWatchers(e){return this.watcherCache.has(e)||this.watcherCache.has(`${e}__watched`)}getWatcherCount(e){const t=this.watcherCache.get(e)||this.watcherCache.get(`${e}__watched`);return t?.count??0}clear(){this.watcherCache.clear(),this.listeners.clear()}};exports.ArtifactContainer=class{registry;cache;graph;manager;observer;store;constructor(e){this.store={watch:(...t)=>e.watch(...t),get:()=>e.get(!0),set:(...t)=>e.set(...t)},this.registry=new y,this.cache=new _,this.graph=new A,this.observer=new k(this.registry,this.cache,this),this.manager=new b(this.registry,this.cache,this.graph,this.store,this.observer)}debugInfo(){const e=[];return this.registry.keys().forEach((t=>{const r=this.registry.get(t),s=this.cache.get(t);if(!r)return;const n=s?s.buildOnce.running()?"pending":s.error?"error":void 0!==s.instance?"active":"idle":"idle";e.push({id:t,scope:r.scope??"singleton",status:n,dependencies:this.graph.getDependencies(t).map((e=>String(e))),dependents:this.graph.getDependents(t).map((e=>String(e))),stateDependencies:s?Array.from(s.stateDependencies):[],renderCount:s?.buildCount??0})})),e}register(e){const{key:t}=e,r=t;this.registry.has(t)&&(console.warn(`[ArtifactContainer] Overwriting "${r}".`),this.manager.dispose(t).catch((e=>{console.error(`[ArtifactContainer] Failed to dispose existing artifact "${r}":`,e)}))),this.registry.register(e),this.graph.registerNode(t);const s=e.scope??"singleton";return(e.lazy??!0)||"singleton"!==s||this.resolve(t).catch((e=>{console.error(`[ArtifactContainer] Eager load failed for "${r}":`,e)})),()=>this.unregister(t)}async unregister(e){await this.manager.dispose(e),await this.registry.unregister(e)}async resolve(e){if(!this.registry.has(e))throw new l(e);return this.manager.build(e)}watch(e){if(!this.registry.has(e))throw new l(e);return this.observer.watch(e)}peek(e){const t=this.cache.get(e);if(void 0!==t?.instance)return t?.instance}async invalidate(e,t=!1){return this.manager.invalidate(e,t)}notifyObservers(e){this.observer.notify(e)}hasWatchers(e){return this.observer.hasWatchers(e)}dispose(){this.registry.keys().forEach((e=>{this.manager.dispose(e).catch((t=>{console.error(`[ArtifactContainer] Failed to dispose artifact "${String(e)}":`,t)}))})),this.registry.clear(),this.cache.clear(),this.graph.clear(),this.observer.clear()}},exports.ArtifactScopes=h;
|
|
1
|
+
"use strict";var e,t,r=Object.create,s=Object.defineProperty,n=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,c=(e={"node_modules/@asaidimu/events/index.js"(e,t){var r,s=Object.defineProperty,n=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,o={};((e,t)=>{for(var r in t)s(e,r,{get:t[r],enumerable:!0})})(o,{createEventBus:()=>c}),t.exports=(r=o,((e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))a.call(e,c)||c===r||s(e,c,{get:()=>t[c],enumerable:!(o=n(t,c))||o.enumerable});return e})(s({},"__esModule",{value:!0}),r));var c=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error("EventBus Error:",e),crossTab:!1,channelName:"event-bus-channel"})=>{const t=new Map;let r=[],s=0,n=0;const i=new Map,a=new Map;let o=null;e.crossTab&&"undefined"!=typeof BroadcastChannel?o=new BroadcastChannel(e.channelName):e.crossTab&&console.warn("BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.");const c=(e,t)=>{s++,n+=t,i.set(e,(i.get(e)||0)+1)},h=()=>{const t=r;r=[],t.forEach((({name:t,payload:r})=>{const s=performance.now();try{(a.get(t)||[]).forEach((e=>e(r)))}catch(s){e.errorHandler({...s,eventName:t,payload:r})}c(t,performance.now()-s)}))},d=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(h,e.batchDelay)}})(),l=e=>{const r=t.get(e);r?a.set(e,Array.from(r)):a.delete(e)};return o&&(o.onmessage=e=>{const{name:t,payload:r}=e.data;(a.get(t)||[]).forEach((e=>e(r)))}),{subscribe:(e,r)=>{t.has(e)||t.set(e,new Set);const s=t.get(e);return s.add(r),l(e),()=>{s.delete(r),0===s.size?(t.delete(e),a.delete(e)):l(e)}},emit:({name:t,payload:s})=>{if(e.async)return r.push({name:t,payload:s}),r.length>=e.batchSize?h():d(),void(o&&o.postMessage({name:t,payload:s}));const n=performance.now();try{(a.get(t)||[]).forEach((e=>e(s))),o&&o.postMessage({name:t,payload:s})}catch(r){e.errorHandler({...r,eventName:t,payload:s})}c(t,performance.now()-n)},getMetrics:()=>({totalEvents:s,activeSubscriptions:Array.from(t.values()).reduce(((e,t)=>e+t.size),0),eventCounts:i,averageEmitDuration:s>0?n/s:0}),clear:()=>{t.clear(),a.clear(),r=[],s=0,n=0,i.clear(),o&&(o.close(),o=null)}}}}},function(){return t||(0,e[i(e)[0]])((t={exports:{}}).exports,t),t.exports}),h=(e=>(e.Singleton="singleton",e.Transient="transient",e))(h||{}),d=class e extends Error{category;constructor(t,r,s){super(t,{cause:s}),this.name="ArtifactError",this.category=r,Object.setPrototypeOf(this,e.prototype)}},l=class extends d{constructor(e){super(`[ArtifactContainer] Artifact "${e}" not found.`,"system")}},u=class extends d{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`,"system")}},p=class extends d{constructor(e){super(`[ArtifactContainer] An artifact with key:${e} already exists!`,"system")}},g=class extends d{constructor(){super("[Serializer] The serializer has been marked as done!","system")}},f=class{artifacts=new Map;register({key:e,factory:t,lazy:r,...s}){if(this.artifacts.has(e))throw new p(String(e));const{scope:n,...i}=s,a={key:e,factory:t,scope:s.scope??"singleton",lazy:void 0===r||r,...i};return this.artifacts.set(e,a),()=>this.unregister(e)}get(e){if(!this.has(e))throw new l(String(e));return this.artifacts.get(e)}has(e){return this.artifacts.has(e)}async unregister(e){this.artifacts.has(e)&&this.artifacts.delete(e)}size(){return this.artifacts.size}keys(){return Array.from(this.artifacts.keys())}clear(){this.artifacts.clear()}};((e,t,c)=>{c=null!=e?r(a(e)):{},((e,t,r,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))o.call(e,c)||c===r||s(e,c,{get:()=>t[c],enumerable:!(a=n(t,c))||a.enumerable})})(e&&e.__esModule?c:s(c,"default",{value:e,enumerable:!0}),e)})(c());var y=class{_locked=!1;_capacity;waiters=[];constructor(e){this._capacity=e?.capacity??1/0}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let t;const r=new Promise((e=>t=e));if(this.waiters.push(t),null!=e)try{await Promise.race([r,new Promise(((r,s)=>setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),s(new u("Mutex lock timed out"))}),e)))])}catch(e){throw e}else await r}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}},m=class{constructor(e=!1,t=!1){this.retry=e,this.throws=t}mutex=new y;promise=null;_value=null;_error;_done=!1;async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{const t=await e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.retry&&!this._done&&(this.promise=null)}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}running(){return null!==this.promise&&!this._done}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){this._done=!1,this.promise=null,this._value=null,this._error=void 0}resolved(){return this.promise}done(){return this._done}_awaitWithTimeout(e,t,r="Operation timed out"){return null==t?e:Promise.race([e,new Promise(((e,s)=>setTimeout((()=>s(new u(r))),t)))])}},w=class{mutex;_done=!1;_lastValue=null;_lastError=void 0;constructor(e){this.mutex=new y({capacity:e?.capacity??1e3})}async do(e,t){if(this._done)return{value:null,error:new g};try{await this.mutex.lock(t)}catch(e){return{value:null,error:e}}let r,s=null;try{if(this._done)throw new g;s=await e(),this._lastValue=s,this._lastError=void 0}catch(e){r=e,this._lastError=e}finally{this.mutex.unlock()}return{value:s,error:r}}peek(){return{value:this._lastValue,error:this._lastError}}close(){this._done=!0}pending(){return this.mutex.pending()}running(){return this.mutex.locked()}},v=class{constructor(e,t,r,s,n){this.registry=e,this.cache=t,this.graph=r,this.store=s,this.observer=n}async build(e,t){const r=this.registry.get(e);if(!r)throw new d(`Template not found for artifact "${String(e)}"`,"system");const s=t?new Set(t):new Set;if(s.has(e))throw new d(`Cycle detected: Artifact "${String(e)}" depends on itself via path: ${Array.from(s).join(" -> ")}`,"system");s.add(e);let n=this.cache.get(e);n||(n=this.createCachedArtifact(r),this.cache.set(e,n));const i=await("transient"===r.scope?this.executeBuild(r,n,s):n.buildOnce.do((()=>this.executeBuild(r,n,s))));if("transient"===r.scope)return i;const a=this.cache.package(e,((t,r)=>this.invalidate(e,t,r)));return n.stream&&n.streamOnce.do(n.stream),a}async executeBuild(e,t,r){const s=e.key;t.buildCount++;const n=[],i=[];let a=!0;t.activeDebounceMs=e.debounce??0,n.push((()=>{a=!1}));const o=new Set,c=new Set,h=new Map,l=this.graph,u=this.cache,p=this.build.bind(this);async function g(e){if(e===s)throw new d(`Artifact "${String(s)}" depends on itself.`,"system");const t=l.wouldCreateCycle(s,e,r);if(t)throw new d(`Adding dependency "${String(e)}" to "${String(s)}" would create a cycle: ${t.join(" -> ")}`,"system");c.add(e);const n=await p(e,r),i=u.get(e);return i&&h.set(e,i.version),n}const f={state:()=>this.store.get(!0),previous:t.instance,onCleanup:e=>n.push(e),onDispose:e=>i.push(e),use:async e=>e({resolve:g,require:async e=>{const t=await g(e);if(t.error)throw t.error;return t.instance},select:e=>(function(e,t="."){const r=new Set,s=(e=[])=>new Proxy({},{get:(n,i)=>{if("symbol"==typeof i)return;const a=[...e,i],o=a.join(t);return r.add(o),s(a)}});try{e(s())}catch(e){throw new Error(`Selector failed during path analysis. This usually means the selector is too complex. Selectors must be simple property accessors only. Error: ${e instanceof Error?e.message:String(e)}`)}const n=Array.from(r);return n.filter((e=>!n.some((r=>r!==e&&r.startsWith(e+t)))))}(e).forEach((e=>o.add(e))),e(this.store.get(!0)))}),stream:async r=>{if("transient"===e.scope)throw new d(`[ArtifactManager] Illegal stream on transient artifact "${String(s)}"`,"system");const n=async(e,r=void 0)=>{await t.streamSerializer.do((async()=>{void 0!==t.stream&&(t.instance=e,t.error=r,t.version++,this.cache.invalidatePackage(s),await this.processStream(s))}))},i={value:()=>t.instance,signal:t.streamController.signal,set:(...e)=>this.store.set(...e),emit:e=>n(e)};t.stream=async()=>{try{await r(i)}catch(e){await n(void 0,e),this.invalidate(s,!1,!0)}}}};let y,m,w=0;const v=(e.retries??0)+1;for(;w<v;)try{const t=e.factory(f);if(e.timeout){const r=new Promise(((t,r)=>setTimeout((()=>r(new Error(`Timeout: ${e.timeout}ms`))),e.timeout)));y=await Promise.race([t,r])}else y=await t;break}catch(e){if(e instanceof d)throw e;w++,w>=v&&(m=e)}if(a){for(const[e,t]of h){const r=this.cache.get(e);r&&r.version!==t&&(m=new d(`Build stale on arrival: Dependency "${String(e)}" changed during build.`,"system"))}if("singleton"===e.scope&&this.updateDependencyGraph(s,c,o),"singleton"===e.scope&&(t.cleanupFunctions=n,t.disposeFunctions=i),m?t.error=m:t.instance=y,t.version++,this.cache.invalidatePackage(s),"transient"===e.scope)return{instance:y,cleanup:this.createCompositeCleanup(n),error:m,ready:!0,invalidate:async()=>console.warn(`[ArtifactManager] Cannot invalidate transient "${String(s)}"`)}}else{const e=this.createCompositeCleanup(n);e&&await e()}}async invalidate(e,t=!1,r=!1){const s=this.cache.get(e);if(s)return s.debounceTimer&&(clearTimeout(s.debounceTimer),s.debounceTimer=void 0),!t&&s.activeDebounceMs>0?new Promise((n=>{s.debounceTimer=setTimeout((()=>{s.debounceTimer=void 0,this.executeInvalidation(e,t,r).then(n).catch(n)}),s.activeDebounceMs)})):this.executeInvalidation(e,t,r)}async executeInvalidation(e,t,r=!1){const s=this.cache.get(e);s&&(await s.invalidationOnce.do((async()=>{s.version++,await this.cache.invalidateInstance(e);const n=this.graph.getDependents(e);await Promise.all(n.map((e=>this.invalidate(e).catch((t=>{console.error(`[ArtifactManager] Cascade failed for "${String(e)}":`,t)})))));const i=this.registry.get(e);!i||!t&&i.lazy&&!this.hasWatchers(e)||r||await this.build(e),this.observer.notify(e)})),s.invalidationOnce.reset())}async dispose(e){const t=this.cache.get(e);t&&(t.streamController.abort(),t.buildOnce.running()&&await t.buildOnce.resolved(),t.invalidationOnce.running()&&await t.invalidationOnce.resolved(),t.streamOnce.running()&&await t.streamOnce.resolved(),await this.cache.invalidateInstance(e),this.graph.removeNode(e),this.cache.delete(e))}async processStream(e){try{const t=this.graph.getDependents(e);await Promise.all(t.map((e=>this.invalidate(e).catch((t=>{console.error(`[ArtifactManager] Failed to invalidate dependent "${String(e)}":`,t)}))))),this.observer.notify(e)}catch(t){console.error(`[ArtifactManager] Stream propagation error "${e}":`,t)}}updateDependencyGraph(e,t,r){const s=this.cache.get(e);s&&(this.graph.hasNode(e)||this.graph.registerNode(e),this.graph.setDependencies(e,t),s.stateUnsubscribe&&s.stateUnsubscribe(),s.stateDependencies=r,r.size>0&&(s.stateUnsubscribe=this.store.watch(Array.from(r),(async()=>{await this.invalidate(e)}))))}createCachedArtifact(e){return{instance:void 0,version:0,streamController:new AbortController,error:void 0,cleanupFunctions:[],disposeFunctions:[],buildCount:0,stateDependencies:new Set,buildOnce:new m(!0,!0),streamOnce:new m(!0,!0),streamSerializer:new w,activeDebounceMs:e.debounce??0,invalidationOnce:new m(!0,!1)}}createCompositeCleanup(e){if(e.length)return async()=>{for(let t=e.length-1;t>=0;t--)try{await e[t]()}catch(e){console.error("[ArtifactManager] Cleanup error:",e)}}}hasWatchers(e){return this.observer.hasWatchers(e)??!1}},b=class{dependencies=new Map;dependents=new Map;metadata=new Map;registerNode(e,t){this.dependencies.has(e)||this.dependencies.set(e,new Set),this.dependents.has(e)||this.dependents.set(e,new Set),void 0!==t&&this.metadata.set(e,t)}removeNode(e){if(!this.hasNode(e))return;const t=this.dependencies.get(e);if(t)for(const r of t)this.dependents.get(r)?.delete(e);const r=this.dependents.get(e);if(r)for(const t of r)this.dependencies.get(t)?.delete(e);this.dependencies.delete(e),this.dependents.delete(e),this.metadata.delete(e)}hasNode(e){return this.dependencies.has(e)}addDependency(e,t){this.registerNode(e),this.registerNode(t),this.dependencies.get(e).add(t),this.dependents.get(t).add(e)}removeDependency(e,t){this.dependencies.get(e)?.delete(t),this.dependents.get(t)?.delete(e)}getDependencies(e){const t=this.dependencies.get(e);return t?new Set(t):new Set}getDependents(e){const t=this.dependents.get(e);return t?new Set(t):new Set}getMetadata(e){return this.metadata.get(e)}setMetadata(e,t){this.registerNode(e),this.metadata.set(e,t)}setDependencies(e,t){this.registerNode(e);const r=this.dependencies.get(e),s=new Set(t);for(const t of r)s.has(t)||this.removeDependency(e,t);for(const t of s)r.has(t)||this.addDependency(e,t)}wouldCreateCycle(e,t){if(e===t)return[e,t];const r=[t],s=new Set([t]),n=new Map;for(;r.length>0;){const i=r.shift();if(i===e){const r=[];let s=e;for(;void 0!==s&&(r.push(s),s!==t);)s=n.get(s);return r.reverse(),[e,...r]}const a=this.dependencies.get(i);if(a)for(const e of a)s.has(e)||(s.add(e),n.set(e,i),r.push(e))}return null}topologicalSort(){const e=new Map,t=Array.from(this.dependencies.keys());for(const r of t)e.set(r,this.dependencies.get(r)?.size??0);const r=[];for(const[t,s]of e)0===s&&r.push(t);const s=[];for(;r.length>0;){const t=r.shift();s.push(t);const n=this.dependents.get(t);if(n)for(const t of n){const s=(e.get(t)||0)-1;e.set(t,s),0===s&&r.push(t)}}if(s.length!==t.length)throw new Error("Cycle detected in graph; topological sort impossible.");return s}getTransitiveDependencies(e,t=!1){return this.bfs(e,"dependencies",t)}getTransitiveDependents(e,t=!1){return this.bfs(e,"dependents",t)}bfs(e,t,r){const s=new Set,n=new Set,i=[e];n.add(e),r&&s.add(e);const a="dependencies"===t?this.dependencies:this.dependents;for(;i.length>0;){const e=i.shift(),t=a.get(e);if(t)for(const e of t)n.has(e)||(n.add(e),s.add(e),i.push(e))}return s}getAllNodes(){return Array.from(this.dependencies.keys())}size(){return this.dependencies.size}clear(){this.dependencies.clear(),this.dependents.clear(),this.metadata.clear()}toDebugString(){return Array.from(this.dependencies.entries()).map((([e,t])=>{const r=Array.from(t).join(", ");return`${String(e)} → [${r||"∅"}]`})).join("\n")}},C=class{graph;constructor(){this.graph=new b}registerNode(e){this.graph.registerNode(e)}removeNode(e){this.graph.removeNode(e)}addDependency(e,t){this.graph.addDependency(e,t)}removeDependency(e,t){this.graph.removeDependency(e,t)}getDependents(e){const t=this.graph.getDependents(e);return Array.from(t)}getDependencies(e){const t=this.graph.getDependencies(e);return Array.from(t)}getTransitiveDependents(e){const t=this.graph.getTransitiveDependents(e,!1);return new Set(Array.from(t))}setDependencies(e,t){const r=Array.from(t).map((e=>e));this.graph.setDependencies(e,r)}wouldCreateCycle(e,t,r){if(r?.has(t)){const e=Array.from(r),s=e.indexOf(t),n=e.slice(s);return n.push(t),n}const s=this.graph.wouldCreateCycle(e,t);return s||null}hasNode(e){return this.graph.hasNode(e)}getAllNodes(){return this.graph.getAllNodes()}clear(){this.graph.clear()}size(){return this.graph.size()}toDebugString(){return this.graph.toDebugString()}},A=class{cache=new Map;emptyInstance={instance:void 0,error:void 0,ready:!1,cleanup:void 0,invalidate:async(e,t)=>{}};get(e){return this.cache.get(e)}set(e,t){this.cache.set(e,t)}delete(e){this.cache.delete(e)}has(e){return this.cache.has(e)}clear(){this.cache.clear()}size(){return this.cache.size}keys(){return Array.from(this.cache.keys())}package(e,t){const r=this.get(e);if(!r)return this.emptyInstance;if(r.packagedArtifact)return this.updatePackagedArtifact(r,r.packagedArtifact,t),r.packagedArtifact;const s={instance:r.instance,error:r.error,ready:r.buildOnce.done()&&!r.buildOnce.running(),cleanup:this.createCompositeCleanup(r.cleanupFunctions),invalidate:t};return r.packagedArtifact=s,s}async invalidatePackage(e){const t=this.get(e);t&&(t.packagedArtifact=void 0)}async invalidateInstance(e){const t=this.get(e);if(t){t.stateUnsubscribe&&(t.stateUnsubscribe(),t.stateUnsubscribe=void 0),t.debounceTimer&&(clearTimeout(t.debounceTimer),t.debounceTimer=void 0),t.streamController.abort(),await t.streamOnce.resolved(),t.streamOnce.reset(),t.streamSerializer.close();for(let e=t.cleanupFunctions.length-1;e>=0;e--)try{await t.cleanupFunctions[e]()}catch(e){console.error("[ArtifactManager] Cleanup error:",e)}for(let e=t.disposeFunctions.length-1;e>=0;e--)try{await t.disposeFunctions[e]()}catch(e){console.error("[ArtifactManager] Dispose error:",e)}t.instance=void 0,t.error=void 0,t.cleanupFunctions=[],t.disposeFunctions=[],t.buildOnce.reset(),t.stream=void 0,t.streamSerializer=new w,t.streamController=new AbortController}}updatePackagedArtifact(e,t,r){t.instance=e.instance,t.error=e.error,t.ready=e.buildOnce.done()&&!e.buildOnce.running(),t.cleanup=this.createCompositeCleanup(e.cleanupFunctions),t.invalidate=r}createCompositeCleanup(e){if(e.length)return async()=>{for(let t=e.length-1;t>=0;t--)try{await e[t]()}catch(e){console.error("[ArtifactCache] Cleanup error:",e)}}}},_=class{constructor(e,t,r){this.registry=e,this.cache=t,this.container=r}listeners=new Map;watcherCache=new Map;watch(e){const t=String(e),r=this.registry.get(e);if(!r)throw new Error(`Artifact "${t}" not registered`);const s="transient"===r.scope?`${t}__watched`:t;if(this.watcherCache.has(s))return this.watcherCache.get(s).watcher;const n={count:0,init:new m(!1,!0),watcher:null},i=()=>n.init.do((async()=>{"transient"!==r.scope||this.registry.has(s)||this.registry.register({key:s,factory:r.factory,scope:"singleton",lazy:r.lazy,timeout:r.timeout,retries:r.retries,debounce:r.debounce})}));return n.watcher={id:t,get count(){return n.count},get:()=>{this.cache.get(s);return this.cache.package(s,(e=>this.container.invalidate(s,e)))},resolve:async()=>(await i(),this.container.resolve(s)),subscribe:e=>{0===n.count&&i(),n.count++,this.listeners.has(s)||this.listeners.set(s,new Set);const t=()=>e(n.watcher.get());return this.listeners.get(s).add(t),this.container.resolve(s).catch((e=>{console.error(`[ArtifactObserver] Background resolution failed for "${s}":`,e)})).then(t),()=>{this.listeners.get(s)?.delete(t),n.count--,0===n.count&&(this.listeners.delete(s),"transient"===r.scope&&(this.registry.unregister(s).catch((e=>{console.error(`[ArtifactObserver] Cleanup failed for "${s}":`,e)})),this.cache.delete(s)),n.init.reset())}}},this.watcherCache.set(s,n),n.watcher}notify(e){const t=this.listeners.get(e);t&&t.forEach((t=>{try{t()}catch(t){console.error(`[ArtifactObserver] Listener error for "${e}":`,t)}}))}hasWatchers(e){return this.watcherCache.has(e)||this.watcherCache.has(`${e}__watched`)}getWatcherCount(e){const t=this.watcherCache.get(e)||this.watcherCache.get(`${e}__watched`);return t?.count??0}clear(){this.watcherCache.clear(),this.listeners.clear()}};exports.ArtifactContainer=class{registry;cache;graph;manager;observer;store;constructor(e){this.store={watch:(...t)=>e.watch(...t),get:()=>e.get(!0),set:(...t)=>e.set(...t)},this.registry=new f,this.cache=new A,this.graph=new C,this.observer=new _(this.registry,this.cache,this),this.manager=new v(this.registry,this.cache,this.graph,this.store,this.observer)}debugInfo(){const e=[];return this.registry.keys().forEach((t=>{const r=this.registry.get(t),s=this.cache.get(t);if(!r)return;const n=s?s.buildOnce.running()?"pending":s.error?"error":void 0!==s.instance?"active":"idle":"idle";e.push({id:t,scope:r.scope??"singleton",status:n,dependencies:this.graph.getDependencies(t).map((e=>String(e))),dependents:this.graph.getDependents(t).map((e=>String(e))),stateDependencies:s?Array.from(s.stateDependencies):[],renderCount:s?.buildCount??0})})),e}register(e){const{key:t}=e,r=t;this.registry.has(t)&&(console.warn(`[ArtifactContainer] Overwriting "${r}".`),this.manager.dispose(t).catch((e=>{console.error(`[ArtifactContainer] Failed to dispose existing artifact "${r}":`,e)}))),this.registry.register(e),this.graph.registerNode(t);const s=e.scope??"singleton";return(e.lazy??!0)||"singleton"!==s||this.resolve(t).catch((e=>{console.error(`[ArtifactContainer] Eager load failed for "${r}":`,e)})),()=>this.unregister(t)}async unregister(e){await this.manager.dispose(e),await this.registry.unregister(e)}async resolve(e){if(!this.registry.has(e))throw new l(e);return this.manager.build(e)}async require(e){const t=await this.resolve(e);if(t.error)throw t.error;return t.instance}watch(e){if(!this.registry.has(e))throw new l(e);return this.observer.watch(e)}peek(e){const t=this.cache.get(e);if(void 0!==t?.instance)return t?.instance}async invalidate(e,t=!1){return this.manager.invalidate(e,t)}notifyObservers(e){this.observer.notify(e)}hasWatchers(e){return this.observer.hasWatchers(e)}dispose(){this.registry.keys().forEach((e=>{this.manager.dispose(e).catch((t=>{console.error(`[ArtifactContainer] Failed to dispose artifact "${String(e)}":`,t)}))})),this.registry.clear(),this.cache.clear(),this.graph.clear(),this.observer.clear()}},exports.ArtifactScopes=h;
|
package/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e,t,r=Object.create,s=Object.defineProperty,n=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,c=(e={"src/store/node_modules/@asaidimu/events/index.js"(e,t){var r,s=Object.defineProperty,n=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,o={};((e,t)=>{for(var r in t)s(e,r,{get:t[r],enumerable:!0})})(o,{createEventBus:()=>c}),t.exports=(r=o,((e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))a.call(e,c)||c===r||s(e,c,{get:()=>t[c],enumerable:!(o=n(t,c))||o.enumerable});return e})(s({},"__esModule",{value:!0}),r));var c=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error("EventBus Error:",e),crossTab:!1,channelName:"event-bus-channel"})=>{const t=new Map;let r=[],s=0,n=0;const i=new Map,a=new Map;let o=null;e.crossTab&&"undefined"!=typeof BroadcastChannel?o=new BroadcastChannel(e.channelName):e.crossTab&&console.warn("BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.");const c=(e,t)=>{s++,n+=t,i.set(e,(i.get(e)||0)+1)},h=()=>{const t=r;r=[],t.forEach((({name:t,payload:r})=>{const s=performance.now();try{(a.get(t)||[]).forEach((e=>e(r)))}catch(s){e.errorHandler({...s,eventName:t,payload:r})}c(t,performance.now()-s)}))},d=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(h,e.batchDelay)}})(),l=e=>{const r=t.get(e);r?a.set(e,Array.from(r)):a.delete(e)};return o&&(o.onmessage=e=>{const{name:t,payload:r}=e.data;(a.get(t)||[]).forEach((e=>e(r)))}),{subscribe:(e,r)=>{t.has(e)||t.set(e,new Set);const s=t.get(e);return s.add(r),l(e),()=>{s.delete(r),0===s.size?(t.delete(e),a.delete(e)):l(e)}},emit:({name:t,payload:s})=>{if(e.async)return r.push({name:t,payload:s}),r.length>=e.batchSize?h():d(),void(o&&o.postMessage({name:t,payload:s}));const n=performance.now();try{(a.get(t)||[]).forEach((e=>e(s))),o&&o.postMessage({name:t,payload:s})}catch(r){e.errorHandler({...r,eventName:t,payload:s})}c(t,performance.now()-n)},getMetrics:()=>({totalEvents:s,activeSubscriptions:Array.from(t.values()).reduce(((e,t)=>e+t.size),0),eventCounts:i,averageEmitDuration:s>0?n/s:0}),clear:()=>{t.clear(),a.clear(),r=[],s=0,n=0,i.clear(),o&&(o.close(),o=null)}}}}},function(){return t||(0,e[i(e)[0]])((t={exports:{}}).exports,t),t.exports}),h=(e=>(e.Singleton="singleton",e.Transient="transient",e))(h||{}),d=class e extends Error{category;constructor(t,r,s){super(t,{cause:s}),this.name="ArtifactError",this.category=r,Object.setPrototypeOf(this,e.prototype)}},l=class extends d{constructor(e){super(`[ArtifactContainer] Artifact "${e}" not found.`,"system")}},u=class extends d{constructor(e){super(`[ArtifactContainer] Artifact with key:${e} has already been disposed`,"system")}},p=class extends d{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`,"system")}},g=class extends d{constructor(e){super(`[ArtifactContainer] An artifact with key:${e} already exists!`,"system")}},f=class extends d{constructor(){super("[Serializer] The serializer has been marked as done!","system")}},y=class{artifacts=new Map;register({key:e,factory:t,lazy:r,...s}){if(this.artifacts.has(e))throw new g(String(e));const{scope:n,...i}=s,a={key:e,factory:t,scope:s.scope??"singleton",lazy:void 0===r||r,...i};return this.artifacts.set(e,a),()=>this.unregister(e)}get(e){if(!this.has(e))throw new l(String(e));return this.artifacts.get(e)}has(e){return this.artifacts.has(e)}async unregister(e){this.artifacts.has(e)&&this.artifacts.delete(e)}size(){return this.artifacts.size}keys(){return Array.from(this.artifacts.keys())}clear(){this.artifacts.clear()}};((e,t,c)=>{c=null!=e?r(a(e)):{},((e,t,r,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))o.call(e,c)||c===r||s(e,c,{get:()=>t[c],enumerable:!(a=n(t,c))||a.enumerable})})(e&&e.__esModule?c:s(c,"default",{value:e,enumerable:!0}),e)})(c());var m=class{_locked=!1;_capacity;waiters=[];constructor(e){this._capacity=e?.capacity??1/0}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let t;const r=new Promise((e=>t=e));if(this.waiters.push(t),null!=e)try{await Promise.race([r,new Promise(((r,s)=>setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),s(new p("Mutex lock timed out"))}),e)))])}catch(e){throw e}else await r}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}},w=class{constructor(e=!1,t=!1){this.retry=e,this.throws=t}mutex=new m;promise=null;_value=null;_error;_done=!1;async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{const t=await e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.retry&&!this._done&&(this.promise=null)}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}running(){return null!==this.promise&&!this._done}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){this._done=!1,this.promise=null,this._value=null,this._error=void 0}resolved(){return this.promise}done(){return this._done}_awaitWithTimeout(e,t,r="Operation timed out"){return null==t?e:Promise.race([e,new Promise(((e,s)=>setTimeout((()=>s(new p(r))),t)))])}},v=class{mutex;_done=!1;_lastValue=null;_lastError=void 0;constructor(e){this.mutex=new m({capacity:e?.capacity??1e3})}async do(e,t){if(this._done)return{value:null,error:new f};try{await this.mutex.lock(t)}catch(e){return{value:null,error:e}}let r,s=null;try{if(this._done)throw new f;s=await e(),this._lastValue=s,this._lastError=void 0}catch(e){r=e,this._lastError=e}finally{this.mutex.unlock()}return{value:s,error:r}}peek(){return{value:this._lastValue,error:this._lastError}}close(){this._done=!0}pending(){return this.mutex.pending()}running(){return this.mutex.locked()}},b=class{constructor(e,t,r,s,n){this.registry=e,this.cache=t,this.graph=r,this.store=s,this.observer=n}async build(e,t){const r=this.registry.get(e);if(!r)throw new d(`Template not found for artifact "${String(e)}"`,"system");const s=t?new Set(t):new Set;if(s.has(e))throw new d(`Cycle detected: Artifact "${String(e)}" depends on itself via path: ${Array.from(s).join(" -> ")}`,"system");s.add(e);let n=this.cache.get(e);n||(n=this.createCachedArtifact(r),this.cache.set(e,n));const i=await("transient"===r.scope?this.executeBuild(r,n,s):n.buildOnce.do((()=>this.executeBuild(r,n,s))));if("transient"===r.scope)return i;const a=this.cache.package(e,((t,r)=>this.invalidate(e,t,r)));return n.stream&&n.streamOnce.do(n.stream),a}async executeBuild(e,t,r){const s=e.key;t.buildCount++;const n=[],i=[];let a=!0;t.activeDebounceMs=e.debounce??0,n.push((()=>{a=!1}));const o=new Set,c=new Set,h=new Map,l={state:()=>this.store.get(!0),previous:t.instance,onCleanup:e=>n.push(e),onDispose:e=>i.push(e),use:async e=>e({resolve:async e=>{if(e===s)throw new d(`Artifact "${String(s)}" depends on itself.`,"system");const t=this.graph.wouldCreateCycle(s,e,r);if(t)throw new d(`Adding dependency "${String(e)}" to "${String(s)}" would create a cycle: ${t.join(" -> ")}`,"system");c.add(e);const n=await this.build(e,r),i=this.cache.get(e);return i&&h.set(e,i.version),n},select:e=>(function(e,t="."){const r=new Set,s=(e=[])=>new Proxy({},{get:(n,i)=>{if("symbol"==typeof i)return;const a=[...e,i],o=a.join(t);return r.add(o),s(a)}});try{e(s())}catch(e){throw new Error(`Selector failed during path analysis. This usually means the selector is too complex. Selectors must be simple property accessors only. Error: ${e instanceof Error?e.message:String(e)}`)}const n=Array.from(r);return n.filter((e=>!n.some((r=>r!==e&&r.startsWith(e+t)))))}(e).forEach((e=>o.add(e))),e(this.store.get(!0)))}),stream:async r=>{if("transient"===e.scope)throw new d(`[ArtifactManager] Illegal stream on transient artifact "${String(s)}"`,"system");const n=async(e,r=void 0)=>{await t.streamSerializer.do((async()=>{void 0!==t.stream&&(t.instance=e,t.error=r,t.version++,this.cache.invalidatePackage(s),await this.processStream(s))}))},i={value:()=>t.instance,signal:t.streamController.signal,set:(...e)=>this.store.set(...e),emit:e=>n(e)};t.stream=async()=>{try{await r(i)}catch(e){await n(void 0,e),this.invalidate(s,!1,!0)}}}};let u,p,g=0;const f=(e.retries??0)+1;for(;g<f;)try{const t=e.factory(l);if(e.timeout){const r=new Promise(((t,r)=>setTimeout((()=>r(new Error(`Timeout: ${e.timeout}ms`))),e.timeout)));u=await Promise.race([t,r])}else u=await t;break}catch(e){if(e instanceof d)throw e;g++,g>=f&&(p=e)}if(a){for(const[e,t]of h){const r=this.cache.get(e);r&&r.version!==t&&(p=new d(`Build stale on arrival: Dependency "${String(e)}" changed during build.`,"system"))}if("singleton"===e.scope&&this.updateDependencyGraph(s,c,o),"singleton"===e.scope&&(t.cleanupFunctions=n,t.disposeFunctions=i),p?t.error=p:t.instance=u,t.version++,this.cache.invalidatePackage(s),"transient"===e.scope)return{instance:u,cleanup:this.createCompositeCleanup(n),error:p,ready:!0,invalidate:async()=>console.warn(`[ArtifactManager] Cannot invalidate transient "${String(s)}"`)}}else{const e=this.createCompositeCleanup(n);e&&await e()}}async invalidate(e,t=!1,r=!1){const s=this.cache.get(e);if(s)return s.debounceTimer&&(clearTimeout(s.debounceTimer),s.debounceTimer=void 0),!t&&s.activeDebounceMs>0?new Promise((n=>{s.debounceTimer=setTimeout((()=>{s.debounceTimer=void 0,this.executeInvalidation(e,t,r).then(n).catch(n)}),s.activeDebounceMs)})):this.executeInvalidation(e,t,r)}async executeInvalidation(e,t,r=!1){const s=this.cache.get(e);s&&(await s.invalidationOnce.do((async()=>{s.version++,await this.cache.invalidateInstance(e);const n=this.graph.getDependents(e);await Promise.all(n.map((e=>this.invalidate(e).catch((t=>{console.error(`[ArtifactManager] Cascade failed for "${String(e)}":`,t)})))));const i=this.registry.get(e);!i||!t&&i.lazy&&!this.hasWatchers(e)||r||await this.build(e),this.observer.notify(e)})),s.invalidationOnce.reset())}async dispose(e){const t=this.cache.get(e);t&&(t.streamController.abort(),t.buildOnce.running()&&await t.buildOnce.resolved(),t.invalidationOnce.running()&&await t.invalidationOnce.resolved(),t.streamOnce.running()&&await t.streamOnce.resolved(),await this.cache.invalidateInstance(e),this.graph.removeNode(e),this.cache.delete(e))}async processStream(e){try{const t=this.graph.getDependents(e);await Promise.all(t.map((e=>this.invalidate(e).catch((t=>{console.error(`[ArtifactManager] Failed to invalidate dependent "${String(e)}":`,t)}))))),this.observer.notify(e)}catch(t){console.error(`[ArtifactManager] Stream propagation error "${e}":`,t)}}updateDependencyGraph(e,t,r){const s=this.cache.get(e);s&&(this.graph.hasNode(e)||this.graph.registerNode(e),this.graph.setDependencies(e,t),s.stateUnsubscribe&&s.stateUnsubscribe(),s.stateDependencies=r,r.size>0&&(s.stateUnsubscribe=this.store.watch(Array.from(r),(async()=>{await this.invalidate(e)}))))}createCachedArtifact(e){return{instance:void 0,version:0,streamController:new AbortController,error:void 0,cleanupFunctions:[],disposeFunctions:[],buildCount:0,stateDependencies:new Set,buildOnce:new w(!0,!0),streamOnce:new w(!0,!0),streamSerializer:new v,activeDebounceMs:e.debounce??0,invalidationOnce:new w(!0,!1)}}createCompositeCleanup(e){if(e.length)return async()=>{for(let t=e.length-1;t>=0;t--)try{await e[t]()}catch(e){console.error("[ArtifactManager] Cleanup error:",e)}}}hasWatchers(e){return this.observer.hasWatchers(e)??!1}},C=class{dependencies=new Map;dependents=new Map;metadata=new Map;registerNode(e,t){this.dependencies.has(e)||this.dependencies.set(e,new Set),this.dependents.has(e)||this.dependents.set(e,new Set),void 0!==t&&this.metadata.set(e,t)}removeNode(e){if(!this.hasNode(e))return;const t=this.dependencies.get(e);if(t)for(const r of t)this.dependents.get(r)?.delete(e);const r=this.dependents.get(e);if(r)for(const t of r)this.dependencies.get(t)?.delete(e);this.dependencies.delete(e),this.dependents.delete(e),this.metadata.delete(e)}hasNode(e){return this.dependencies.has(e)}addDependency(e,t){this.registerNode(e),this.registerNode(t),this.dependencies.get(e).add(t),this.dependents.get(t).add(e)}removeDependency(e,t){this.dependencies.get(e)?.delete(t),this.dependents.get(t)?.delete(e)}getDependencies(e){const t=this.dependencies.get(e);return t?new Set(t):new Set}getDependents(e){const t=this.dependents.get(e);return t?new Set(t):new Set}getMetadata(e){return this.metadata.get(e)}setMetadata(e,t){this.registerNode(e),this.metadata.set(e,t)}setDependencies(e,t){this.registerNode(e);const r=this.dependencies.get(e),s=new Set(t);for(const t of r)s.has(t)||this.removeDependency(e,t);for(const t of s)r.has(t)||this.addDependency(e,t)}wouldCreateCycle(e,t){if(e===t)return[e,t];const r=[t],s=new Set([t]),n=new Map;for(;r.length>0;){const i=r.shift();if(i===e){const r=[];let s=e;for(;void 0!==s&&(r.push(s),s!==t);)s=n.get(s);return r.reverse(),[e,...r]}const a=this.dependencies.get(i);if(a)for(const e of a)s.has(e)||(s.add(e),n.set(e,i),r.push(e))}return null}topologicalSort(){const e=new Map,t=Array.from(this.dependencies.keys());for(const r of t)e.set(r,this.dependencies.get(r)?.size??0);const r=[];for(const[t,s]of e)0===s&&r.push(t);const s=[];for(;r.length>0;){const t=r.shift();s.push(t);const n=this.dependents.get(t);if(n)for(const t of n){const s=(e.get(t)||0)-1;e.set(t,s),0===s&&r.push(t)}}if(s.length!==t.length)throw new Error("Cycle detected in graph; topological sort impossible.");return s}getTransitiveDependencies(e,t=!1){return this.bfs(e,"dependencies",t)}getTransitiveDependents(e,t=!1){return this.bfs(e,"dependents",t)}bfs(e,t,r){const s=new Set,n=new Set,i=[e];n.add(e),r&&s.add(e);const a="dependencies"===t?this.dependencies:this.dependents;for(;i.length>0;){const e=i.shift(),t=a.get(e);if(t)for(const e of t)n.has(e)||(n.add(e),s.add(e),i.push(e))}return s}getAllNodes(){return Array.from(this.dependencies.keys())}size(){return this.dependencies.size}clear(){this.dependencies.clear(),this.dependents.clear(),this.metadata.clear()}toDebugString(){return Array.from(this.dependencies.entries()).map((([e,t])=>{const r=Array.from(t).join(", ");return`${String(e)} → [${r||"∅"}]`})).join("\n")}},A=class{graph;constructor(){this.graph=new C}registerNode(e){this.graph.registerNode(e)}removeNode(e){this.graph.removeNode(e)}addDependency(e,t){this.graph.addDependency(e,t)}removeDependency(e,t){this.graph.removeDependency(e,t)}getDependents(e){const t=this.graph.getDependents(e);return Array.from(t)}getDependencies(e){const t=this.graph.getDependencies(e);return Array.from(t)}getTransitiveDependents(e){const t=this.graph.getTransitiveDependents(e,!1);return new Set(Array.from(t))}setDependencies(e,t){const r=Array.from(t).map((e=>e));this.graph.setDependencies(e,r)}wouldCreateCycle(e,t,r){if(r?.has(t)){const e=Array.from(r),s=e.indexOf(t),n=e.slice(s);return n.push(t),n}const s=this.graph.wouldCreateCycle(e,t);return s||null}hasNode(e){return this.graph.hasNode(e)}getAllNodes(){return this.graph.getAllNodes()}clear(){this.graph.clear()}size(){return this.graph.size()}toDebugString(){return this.graph.toDebugString()}},_=class{cache=new Map;emptyInstance={instance:void 0,error:void 0,ready:!1,cleanup:void 0,invalidate:async(e,t)=>{}};get(e){return this.cache.get(e)}set(e,t){this.cache.set(e,t)}delete(e){this.cache.delete(e)}has(e){return this.cache.has(e)}clear(){this.cache.clear()}size(){return this.cache.size}keys(){return Array.from(this.cache.keys())}package(e,t){const r=this.get(e);if(!r)return this.emptyInstance;if(r.packagedArtifact)return this.updatePackagedArtifact(r,r.packagedArtifact,t),r.packagedArtifact;const s={instance:r.instance,error:r.error,ready:r.buildOnce.done()&&!r.buildOnce.running(),cleanup:this.createCompositeCleanup(r.cleanupFunctions),invalidate:t};return r.packagedArtifact=s,s}async invalidatePackage(e){const t=this.get(e);t&&(t.packagedArtifact=void 0)}async invalidateInstance(e){const t=this.get(e);if(t){t.stateUnsubscribe&&(t.stateUnsubscribe(),t.stateUnsubscribe=void 0),t.debounceTimer&&(clearTimeout(t.debounceTimer),t.debounceTimer=void 0),t.streamController.abort(),await t.streamOnce.resolved(),t.streamOnce.reset(),t.streamSerializer.close();for(let e=t.cleanupFunctions.length-1;e>=0;e--)try{await t.cleanupFunctions[e]()}catch(e){console.error("[ArtifactManager] Cleanup error:",e)}for(let e=t.disposeFunctions.length-1;e>=0;e--)try{await t.disposeFunctions[e]()}catch(e){console.error("[ArtifactManager] Dispose error:",e)}t.instance=void 0,t.error=void 0,t.cleanupFunctions=[],t.disposeFunctions=[],t.buildOnce.reset(),t.stream=void 0,t.streamSerializer=new v,t.streamController=new AbortController}}updatePackagedArtifact(e,t,r){t.instance=e.instance,t.error=e.error,t.ready=e.buildOnce.done()&&!e.buildOnce.running(),t.cleanup=this.createCompositeCleanup(e.cleanupFunctions),t.invalidate=r}createCompositeCleanup(e){if(e.length)return async()=>{for(let t=e.length-1;t>=0;t--)try{await e[t]()}catch(e){console.error("[ArtifactCache] Cleanup error:",e)}}}},k=class{constructor(e,t,r){this.registry=e,this.cache=t,this.container=r}listeners=new Map;watcherCache=new Map;watch(e){const t=String(e),r=this.registry.get(e);if(!r)throw new Error(`Artifact "${t}" not registered`);let s=t;if("transient"===r.scope&&(s=`${t}__watched`,this.registry.has(s)||this.registry.register({key:s,factory:r.factory,scope:"singleton",lazy:r.lazy,timeout:r.timeout,retries:r.retries,debounce:r.debounce})),this.watcherCache.has(s)){const e=this.watcherCache.get(s);return e.count++,e}let n=!1;const i={id:t,count:1,get:()=>{if(!this.cache.get(s))return this.cache.package(s,(e=>this.container.invalidate(s,e)));const e=this.cache.package(s,(e=>this.container.invalidate(s,e)));return n?{...e,ready:!1,instance:void 0,error:new u(t),cleanup:()=>{},invalidate:()=>Promise.resolve()}:e},subscribe:e=>{this.listeners.has(s)||this.listeners.set(s,new Set);const t=()=>e(i.get());return this.listeners.get(s).add(t),this.container.resolve(s).catch((e=>{console.error(`[ArtifactObserver] Background resolution failed for "${s}":`,e)})).then(t),()=>this.listeners.get(s)?.delete(t)},dispose:async()=>{i.count--,i.count<=0&&(this.watcherCache.delete(s),"transient"===r.scope&&(await this.registry.unregister(s),this.cache.delete(s)),n=!0,this.notify(s),this.listeners.delete(s))}};return this.watcherCache.set(s,i),i}notify(e){const t=this.listeners.get(e);t&&t.forEach((t=>{try{t()}catch(t){console.error(`[ArtifactObserver] Listener error for "${e}":`,t)}}))}hasWatchers(e){return this.watcherCache.has(e)||this.watcherCache.has(`${e}__watched`)}getWatcherCount(e){const t=this.watcherCache.get(e)||this.watcherCache.get(`${e}__watched`);return t?.count??0}clear(){this.watcherCache.clear(),this.listeners.clear()}},S=class{registry;cache;graph;manager;observer;store;constructor(e){this.store={watch:(...t)=>e.watch(...t),get:()=>e.get(!0),set:(...t)=>e.set(...t)},this.registry=new y,this.cache=new _,this.graph=new A,this.observer=new k(this.registry,this.cache,this),this.manager=new b(this.registry,this.cache,this.graph,this.store,this.observer)}debugInfo(){const e=[];return this.registry.keys().forEach((t=>{const r=this.registry.get(t),s=this.cache.get(t);if(!r)return;const n=s?s.buildOnce.running()?"pending":s.error?"error":void 0!==s.instance?"active":"idle":"idle";e.push({id:t,scope:r.scope??"singleton",status:n,dependencies:this.graph.getDependencies(t).map((e=>String(e))),dependents:this.graph.getDependents(t).map((e=>String(e))),stateDependencies:s?Array.from(s.stateDependencies):[],renderCount:s?.buildCount??0})})),e}register(e){const{key:t}=e,r=t;this.registry.has(t)&&(console.warn(`[ArtifactContainer] Overwriting "${r}".`),this.manager.dispose(t).catch((e=>{console.error(`[ArtifactContainer] Failed to dispose existing artifact "${r}":`,e)}))),this.registry.register(e),this.graph.registerNode(t);const s=e.scope??"singleton";return(e.lazy??!0)||"singleton"!==s||this.resolve(t).catch((e=>{console.error(`[ArtifactContainer] Eager load failed for "${r}":`,e)})),()=>this.unregister(t)}async unregister(e){await this.manager.dispose(e),await this.registry.unregister(e)}async resolve(e){if(!this.registry.has(e))throw new l(e);return this.manager.build(e)}watch(e){if(!this.registry.has(e))throw new l(e);return this.observer.watch(e)}peek(e){const t=this.cache.get(e);if(void 0!==t?.instance)return t?.instance}async invalidate(e,t=!1){return this.manager.invalidate(e,t)}notifyObservers(e){this.observer.notify(e)}hasWatchers(e){return this.observer.hasWatchers(e)}dispose(){this.registry.keys().forEach((e=>{this.manager.dispose(e).catch((t=>{console.error(`[ArtifactContainer] Failed to dispose artifact "${String(e)}":`,t)}))})),this.registry.clear(),this.cache.clear(),this.graph.clear(),this.observer.clear()}};export{S as ArtifactContainer,h as ArtifactScopes};
|
|
1
|
+
var e,t,r=Object.create,s=Object.defineProperty,n=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,c=(e={"node_modules/@asaidimu/events/index.js"(e,t){var r,s=Object.defineProperty,n=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,o={};((e,t)=>{for(var r in t)s(e,r,{get:t[r],enumerable:!0})})(o,{createEventBus:()=>c}),t.exports=(r=o,((e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))a.call(e,c)||c===r||s(e,c,{get:()=>t[c],enumerable:!(o=n(t,c))||o.enumerable});return e})(s({},"__esModule",{value:!0}),r));var c=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error("EventBus Error:",e),crossTab:!1,channelName:"event-bus-channel"})=>{const t=new Map;let r=[],s=0,n=0;const i=new Map,a=new Map;let o=null;e.crossTab&&"undefined"!=typeof BroadcastChannel?o=new BroadcastChannel(e.channelName):e.crossTab&&console.warn("BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.");const c=(e,t)=>{s++,n+=t,i.set(e,(i.get(e)||0)+1)},h=()=>{const t=r;r=[],t.forEach((({name:t,payload:r})=>{const s=performance.now();try{(a.get(t)||[]).forEach((e=>e(r)))}catch(s){e.errorHandler({...s,eventName:t,payload:r})}c(t,performance.now()-s)}))},d=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(h,e.batchDelay)}})(),l=e=>{const r=t.get(e);r?a.set(e,Array.from(r)):a.delete(e)};return o&&(o.onmessage=e=>{const{name:t,payload:r}=e.data;(a.get(t)||[]).forEach((e=>e(r)))}),{subscribe:(e,r)=>{t.has(e)||t.set(e,new Set);const s=t.get(e);return s.add(r),l(e),()=>{s.delete(r),0===s.size?(t.delete(e),a.delete(e)):l(e)}},emit:({name:t,payload:s})=>{if(e.async)return r.push({name:t,payload:s}),r.length>=e.batchSize?h():d(),void(o&&o.postMessage({name:t,payload:s}));const n=performance.now();try{(a.get(t)||[]).forEach((e=>e(s))),o&&o.postMessage({name:t,payload:s})}catch(r){e.errorHandler({...r,eventName:t,payload:s})}c(t,performance.now()-n)},getMetrics:()=>({totalEvents:s,activeSubscriptions:Array.from(t.values()).reduce(((e,t)=>e+t.size),0),eventCounts:i,averageEmitDuration:s>0?n/s:0}),clear:()=>{t.clear(),a.clear(),r=[],s=0,n=0,i.clear(),o&&(o.close(),o=null)}}}}},function(){return t||(0,e[i(e)[0]])((t={exports:{}}).exports,t),t.exports}),h=(e=>(e.Singleton="singleton",e.Transient="transient",e))(h||{}),d=class e extends Error{category;constructor(t,r,s){super(t,{cause:s}),this.name="ArtifactError",this.category=r,Object.setPrototypeOf(this,e.prototype)}},l=class extends d{constructor(e){super(`[ArtifactContainer] Artifact "${e}" not found.`,"system")}},u=class extends d{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`,"system")}},p=class extends d{constructor(e){super(`[ArtifactContainer] An artifact with key:${e} already exists!`,"system")}},g=class extends d{constructor(){super("[Serializer] The serializer has been marked as done!","system")}},f=class{artifacts=new Map;register({key:e,factory:t,lazy:r,...s}){if(this.artifacts.has(e))throw new p(String(e));const{scope:n,...i}=s,a={key:e,factory:t,scope:s.scope??"singleton",lazy:void 0===r||r,...i};return this.artifacts.set(e,a),()=>this.unregister(e)}get(e){if(!this.has(e))throw new l(String(e));return this.artifacts.get(e)}has(e){return this.artifacts.has(e)}async unregister(e){this.artifacts.has(e)&&this.artifacts.delete(e)}size(){return this.artifacts.size}keys(){return Array.from(this.artifacts.keys())}clear(){this.artifacts.clear()}};((e,t,c)=>{c=null!=e?r(a(e)):{},((e,t,r,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))o.call(e,c)||c===r||s(e,c,{get:()=>t[c],enumerable:!(a=n(t,c))||a.enumerable})})(e&&e.__esModule?c:s(c,"default",{value:e,enumerable:!0}),e)})(c());var y=class{_locked=!1;_capacity;waiters=[];constructor(e){this._capacity=e?.capacity??1/0}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let t;const r=new Promise((e=>t=e));if(this.waiters.push(t),null!=e)try{await Promise.race([r,new Promise(((r,s)=>setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),s(new u("Mutex lock timed out"))}),e)))])}catch(e){throw e}else await r}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}},m=class{constructor(e=!1,t=!1){this.retry=e,this.throws=t}mutex=new y;promise=null;_value=null;_error;_done=!1;async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{const t=await e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.retry&&!this._done&&(this.promise=null)}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}running(){return null!==this.promise&&!this._done}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){this._done=!1,this.promise=null,this._value=null,this._error=void 0}resolved(){return this.promise}done(){return this._done}_awaitWithTimeout(e,t,r="Operation timed out"){return null==t?e:Promise.race([e,new Promise(((e,s)=>setTimeout((()=>s(new u(r))),t)))])}},w=class{mutex;_done=!1;_lastValue=null;_lastError=void 0;constructor(e){this.mutex=new y({capacity:e?.capacity??1e3})}async do(e,t){if(this._done)return{value:null,error:new g};try{await this.mutex.lock(t)}catch(e){return{value:null,error:e}}let r,s=null;try{if(this._done)throw new g;s=await e(),this._lastValue=s,this._lastError=void 0}catch(e){r=e,this._lastError=e}finally{this.mutex.unlock()}return{value:s,error:r}}peek(){return{value:this._lastValue,error:this._lastError}}close(){this._done=!0}pending(){return this.mutex.pending()}running(){return this.mutex.locked()}},v=class{constructor(e,t,r,s,n){this.registry=e,this.cache=t,this.graph=r,this.store=s,this.observer=n}async build(e,t){const r=this.registry.get(e);if(!r)throw new d(`Template not found for artifact "${String(e)}"`,"system");const s=t?new Set(t):new Set;if(s.has(e))throw new d(`Cycle detected: Artifact "${String(e)}" depends on itself via path: ${Array.from(s).join(" -> ")}`,"system");s.add(e);let n=this.cache.get(e);n||(n=this.createCachedArtifact(r),this.cache.set(e,n));const i=await("transient"===r.scope?this.executeBuild(r,n,s):n.buildOnce.do((()=>this.executeBuild(r,n,s))));if("transient"===r.scope)return i;const a=this.cache.package(e,((t,r)=>this.invalidate(e,t,r)));return n.stream&&n.streamOnce.do(n.stream),a}async executeBuild(e,t,r){const s=e.key;t.buildCount++;const n=[],i=[];let a=!0;t.activeDebounceMs=e.debounce??0,n.push((()=>{a=!1}));const o=new Set,c=new Set,h=new Map,l=this.graph,u=this.cache,p=this.build.bind(this);async function g(e){if(e===s)throw new d(`Artifact "${String(s)}" depends on itself.`,"system");const t=l.wouldCreateCycle(s,e,r);if(t)throw new d(`Adding dependency "${String(e)}" to "${String(s)}" would create a cycle: ${t.join(" -> ")}`,"system");c.add(e);const n=await p(e,r),i=u.get(e);return i&&h.set(e,i.version),n}const f={state:()=>this.store.get(!0),previous:t.instance,onCleanup:e=>n.push(e),onDispose:e=>i.push(e),use:async e=>e({resolve:g,require:async e=>{const t=await g(e);if(t.error)throw t.error;return t.instance},select:e=>(function(e,t="."){const r=new Set,s=(e=[])=>new Proxy({},{get:(n,i)=>{if("symbol"==typeof i)return;const a=[...e,i],o=a.join(t);return r.add(o),s(a)}});try{e(s())}catch(e){throw new Error(`Selector failed during path analysis. This usually means the selector is too complex. Selectors must be simple property accessors only. Error: ${e instanceof Error?e.message:String(e)}`)}const n=Array.from(r);return n.filter((e=>!n.some((r=>r!==e&&r.startsWith(e+t)))))}(e).forEach((e=>o.add(e))),e(this.store.get(!0)))}),stream:async r=>{if("transient"===e.scope)throw new d(`[ArtifactManager] Illegal stream on transient artifact "${String(s)}"`,"system");const n=async(e,r=void 0)=>{await t.streamSerializer.do((async()=>{void 0!==t.stream&&(t.instance=e,t.error=r,t.version++,this.cache.invalidatePackage(s),await this.processStream(s))}))},i={value:()=>t.instance,signal:t.streamController.signal,set:(...e)=>this.store.set(...e),emit:e=>n(e)};t.stream=async()=>{try{await r(i)}catch(e){await n(void 0,e),this.invalidate(s,!1,!0)}}}};let y,m,w=0;const v=(e.retries??0)+1;for(;w<v;)try{const t=e.factory(f);if(e.timeout){const r=new Promise(((t,r)=>setTimeout((()=>r(new Error(`Timeout: ${e.timeout}ms`))),e.timeout)));y=await Promise.race([t,r])}else y=await t;break}catch(e){if(e instanceof d)throw e;w++,w>=v&&(m=e)}if(a){for(const[e,t]of h){const r=this.cache.get(e);r&&r.version!==t&&(m=new d(`Build stale on arrival: Dependency "${String(e)}" changed during build.`,"system"))}if("singleton"===e.scope&&this.updateDependencyGraph(s,c,o),"singleton"===e.scope&&(t.cleanupFunctions=n,t.disposeFunctions=i),m?t.error=m:t.instance=y,t.version++,this.cache.invalidatePackage(s),"transient"===e.scope)return{instance:y,cleanup:this.createCompositeCleanup(n),error:m,ready:!0,invalidate:async()=>console.warn(`[ArtifactManager] Cannot invalidate transient "${String(s)}"`)}}else{const e=this.createCompositeCleanup(n);e&&await e()}}async invalidate(e,t=!1,r=!1){const s=this.cache.get(e);if(s)return s.debounceTimer&&(clearTimeout(s.debounceTimer),s.debounceTimer=void 0),!t&&s.activeDebounceMs>0?new Promise((n=>{s.debounceTimer=setTimeout((()=>{s.debounceTimer=void 0,this.executeInvalidation(e,t,r).then(n).catch(n)}),s.activeDebounceMs)})):this.executeInvalidation(e,t,r)}async executeInvalidation(e,t,r=!1){const s=this.cache.get(e);s&&(await s.invalidationOnce.do((async()=>{s.version++,await this.cache.invalidateInstance(e);const n=this.graph.getDependents(e);await Promise.all(n.map((e=>this.invalidate(e).catch((t=>{console.error(`[ArtifactManager] Cascade failed for "${String(e)}":`,t)})))));const i=this.registry.get(e);!i||!t&&i.lazy&&!this.hasWatchers(e)||r||await this.build(e),this.observer.notify(e)})),s.invalidationOnce.reset())}async dispose(e){const t=this.cache.get(e);t&&(t.streamController.abort(),t.buildOnce.running()&&await t.buildOnce.resolved(),t.invalidationOnce.running()&&await t.invalidationOnce.resolved(),t.streamOnce.running()&&await t.streamOnce.resolved(),await this.cache.invalidateInstance(e),this.graph.removeNode(e),this.cache.delete(e))}async processStream(e){try{const t=this.graph.getDependents(e);await Promise.all(t.map((e=>this.invalidate(e).catch((t=>{console.error(`[ArtifactManager] Failed to invalidate dependent "${String(e)}":`,t)}))))),this.observer.notify(e)}catch(t){console.error(`[ArtifactManager] Stream propagation error "${e}":`,t)}}updateDependencyGraph(e,t,r){const s=this.cache.get(e);s&&(this.graph.hasNode(e)||this.graph.registerNode(e),this.graph.setDependencies(e,t),s.stateUnsubscribe&&s.stateUnsubscribe(),s.stateDependencies=r,r.size>0&&(s.stateUnsubscribe=this.store.watch(Array.from(r),(async()=>{await this.invalidate(e)}))))}createCachedArtifact(e){return{instance:void 0,version:0,streamController:new AbortController,error:void 0,cleanupFunctions:[],disposeFunctions:[],buildCount:0,stateDependencies:new Set,buildOnce:new m(!0,!0),streamOnce:new m(!0,!0),streamSerializer:new w,activeDebounceMs:e.debounce??0,invalidationOnce:new m(!0,!1)}}createCompositeCleanup(e){if(e.length)return async()=>{for(let t=e.length-1;t>=0;t--)try{await e[t]()}catch(e){console.error("[ArtifactManager] Cleanup error:",e)}}}hasWatchers(e){return this.observer.hasWatchers(e)??!1}},b=class{dependencies=new Map;dependents=new Map;metadata=new Map;registerNode(e,t){this.dependencies.has(e)||this.dependencies.set(e,new Set),this.dependents.has(e)||this.dependents.set(e,new Set),void 0!==t&&this.metadata.set(e,t)}removeNode(e){if(!this.hasNode(e))return;const t=this.dependencies.get(e);if(t)for(const r of t)this.dependents.get(r)?.delete(e);const r=this.dependents.get(e);if(r)for(const t of r)this.dependencies.get(t)?.delete(e);this.dependencies.delete(e),this.dependents.delete(e),this.metadata.delete(e)}hasNode(e){return this.dependencies.has(e)}addDependency(e,t){this.registerNode(e),this.registerNode(t),this.dependencies.get(e).add(t),this.dependents.get(t).add(e)}removeDependency(e,t){this.dependencies.get(e)?.delete(t),this.dependents.get(t)?.delete(e)}getDependencies(e){const t=this.dependencies.get(e);return t?new Set(t):new Set}getDependents(e){const t=this.dependents.get(e);return t?new Set(t):new Set}getMetadata(e){return this.metadata.get(e)}setMetadata(e,t){this.registerNode(e),this.metadata.set(e,t)}setDependencies(e,t){this.registerNode(e);const r=this.dependencies.get(e),s=new Set(t);for(const t of r)s.has(t)||this.removeDependency(e,t);for(const t of s)r.has(t)||this.addDependency(e,t)}wouldCreateCycle(e,t){if(e===t)return[e,t];const r=[t],s=new Set([t]),n=new Map;for(;r.length>0;){const i=r.shift();if(i===e){const r=[];let s=e;for(;void 0!==s&&(r.push(s),s!==t);)s=n.get(s);return r.reverse(),[e,...r]}const a=this.dependencies.get(i);if(a)for(const e of a)s.has(e)||(s.add(e),n.set(e,i),r.push(e))}return null}topologicalSort(){const e=new Map,t=Array.from(this.dependencies.keys());for(const r of t)e.set(r,this.dependencies.get(r)?.size??0);const r=[];for(const[t,s]of e)0===s&&r.push(t);const s=[];for(;r.length>0;){const t=r.shift();s.push(t);const n=this.dependents.get(t);if(n)for(const t of n){const s=(e.get(t)||0)-1;e.set(t,s),0===s&&r.push(t)}}if(s.length!==t.length)throw new Error("Cycle detected in graph; topological sort impossible.");return s}getTransitiveDependencies(e,t=!1){return this.bfs(e,"dependencies",t)}getTransitiveDependents(e,t=!1){return this.bfs(e,"dependents",t)}bfs(e,t,r){const s=new Set,n=new Set,i=[e];n.add(e),r&&s.add(e);const a="dependencies"===t?this.dependencies:this.dependents;for(;i.length>0;){const e=i.shift(),t=a.get(e);if(t)for(const e of t)n.has(e)||(n.add(e),s.add(e),i.push(e))}return s}getAllNodes(){return Array.from(this.dependencies.keys())}size(){return this.dependencies.size}clear(){this.dependencies.clear(),this.dependents.clear(),this.metadata.clear()}toDebugString(){return Array.from(this.dependencies.entries()).map((([e,t])=>{const r=Array.from(t).join(", ");return`${String(e)} → [${r||"∅"}]`})).join("\n")}},C=class{graph;constructor(){this.graph=new b}registerNode(e){this.graph.registerNode(e)}removeNode(e){this.graph.removeNode(e)}addDependency(e,t){this.graph.addDependency(e,t)}removeDependency(e,t){this.graph.removeDependency(e,t)}getDependents(e){const t=this.graph.getDependents(e);return Array.from(t)}getDependencies(e){const t=this.graph.getDependencies(e);return Array.from(t)}getTransitiveDependents(e){const t=this.graph.getTransitiveDependents(e,!1);return new Set(Array.from(t))}setDependencies(e,t){const r=Array.from(t).map((e=>e));this.graph.setDependencies(e,r)}wouldCreateCycle(e,t,r){if(r?.has(t)){const e=Array.from(r),s=e.indexOf(t),n=e.slice(s);return n.push(t),n}const s=this.graph.wouldCreateCycle(e,t);return s||null}hasNode(e){return this.graph.hasNode(e)}getAllNodes(){return this.graph.getAllNodes()}clear(){this.graph.clear()}size(){return this.graph.size()}toDebugString(){return this.graph.toDebugString()}},_=class{cache=new Map;emptyInstance={instance:void 0,error:void 0,ready:!1,cleanup:void 0,invalidate:async(e,t)=>{}};get(e){return this.cache.get(e)}set(e,t){this.cache.set(e,t)}delete(e){this.cache.delete(e)}has(e){return this.cache.has(e)}clear(){this.cache.clear()}size(){return this.cache.size}keys(){return Array.from(this.cache.keys())}package(e,t){const r=this.get(e);if(!r)return this.emptyInstance;if(r.packagedArtifact)return this.updatePackagedArtifact(r,r.packagedArtifact,t),r.packagedArtifact;const s={instance:r.instance,error:r.error,ready:r.buildOnce.done()&&!r.buildOnce.running(),cleanup:this.createCompositeCleanup(r.cleanupFunctions),invalidate:t};return r.packagedArtifact=s,s}async invalidatePackage(e){const t=this.get(e);t&&(t.packagedArtifact=void 0)}async invalidateInstance(e){const t=this.get(e);if(t){t.stateUnsubscribe&&(t.stateUnsubscribe(),t.stateUnsubscribe=void 0),t.debounceTimer&&(clearTimeout(t.debounceTimer),t.debounceTimer=void 0),t.streamController.abort(),await t.streamOnce.resolved(),t.streamOnce.reset(),t.streamSerializer.close();for(let e=t.cleanupFunctions.length-1;e>=0;e--)try{await t.cleanupFunctions[e]()}catch(e){console.error("[ArtifactManager] Cleanup error:",e)}for(let e=t.disposeFunctions.length-1;e>=0;e--)try{await t.disposeFunctions[e]()}catch(e){console.error("[ArtifactManager] Dispose error:",e)}t.instance=void 0,t.error=void 0,t.cleanupFunctions=[],t.disposeFunctions=[],t.buildOnce.reset(),t.stream=void 0,t.streamSerializer=new w,t.streamController=new AbortController}}updatePackagedArtifact(e,t,r){t.instance=e.instance,t.error=e.error,t.ready=e.buildOnce.done()&&!e.buildOnce.running(),t.cleanup=this.createCompositeCleanup(e.cleanupFunctions),t.invalidate=r}createCompositeCleanup(e){if(e.length)return async()=>{for(let t=e.length-1;t>=0;t--)try{await e[t]()}catch(e){console.error("[ArtifactCache] Cleanup error:",e)}}}},A=class{constructor(e,t,r){this.registry=e,this.cache=t,this.container=r}listeners=new Map;watcherCache=new Map;watch(e){const t=String(e),r=this.registry.get(e);if(!r)throw new Error(`Artifact "${t}" not registered`);const s="transient"===r.scope?`${t}__watched`:t;if(this.watcherCache.has(s))return this.watcherCache.get(s).watcher;const n={count:0,init:new m(!1,!0),watcher:null},i=()=>n.init.do((async()=>{"transient"!==r.scope||this.registry.has(s)||this.registry.register({key:s,factory:r.factory,scope:"singleton",lazy:r.lazy,timeout:r.timeout,retries:r.retries,debounce:r.debounce})}));return n.watcher={id:t,get count(){return n.count},get:()=>{this.cache.get(s);return this.cache.package(s,(e=>this.container.invalidate(s,e)))},resolve:async()=>(await i(),this.container.resolve(s)),subscribe:e=>{0===n.count&&i(),n.count++,this.listeners.has(s)||this.listeners.set(s,new Set);const t=()=>e(n.watcher.get());return this.listeners.get(s).add(t),this.container.resolve(s).catch((e=>{console.error(`[ArtifactObserver] Background resolution failed for "${s}":`,e)})).then(t),()=>{this.listeners.get(s)?.delete(t),n.count--,0===n.count&&(this.listeners.delete(s),"transient"===r.scope&&(this.registry.unregister(s).catch((e=>{console.error(`[ArtifactObserver] Cleanup failed for "${s}":`,e)})),this.cache.delete(s)),n.init.reset())}}},this.watcherCache.set(s,n),n.watcher}notify(e){const t=this.listeners.get(e);t&&t.forEach((t=>{try{t()}catch(t){console.error(`[ArtifactObserver] Listener error for "${e}":`,t)}}))}hasWatchers(e){return this.watcherCache.has(e)||this.watcherCache.has(`${e}__watched`)}getWatcherCount(e){const t=this.watcherCache.get(e)||this.watcherCache.get(`${e}__watched`);return t?.count??0}clear(){this.watcherCache.clear(),this.listeners.clear()}},k=class{registry;cache;graph;manager;observer;store;constructor(e){this.store={watch:(...t)=>e.watch(...t),get:()=>e.get(!0),set:(...t)=>e.set(...t)},this.registry=new f,this.cache=new _,this.graph=new C,this.observer=new A(this.registry,this.cache,this),this.manager=new v(this.registry,this.cache,this.graph,this.store,this.observer)}debugInfo(){const e=[];return this.registry.keys().forEach((t=>{const r=this.registry.get(t),s=this.cache.get(t);if(!r)return;const n=s?s.buildOnce.running()?"pending":s.error?"error":void 0!==s.instance?"active":"idle":"idle";e.push({id:t,scope:r.scope??"singleton",status:n,dependencies:this.graph.getDependencies(t).map((e=>String(e))),dependents:this.graph.getDependents(t).map((e=>String(e))),stateDependencies:s?Array.from(s.stateDependencies):[],renderCount:s?.buildCount??0})})),e}register(e){const{key:t}=e,r=t;this.registry.has(t)&&(console.warn(`[ArtifactContainer] Overwriting "${r}".`),this.manager.dispose(t).catch((e=>{console.error(`[ArtifactContainer] Failed to dispose existing artifact "${r}":`,e)}))),this.registry.register(e),this.graph.registerNode(t);const s=e.scope??"singleton";return(e.lazy??!0)||"singleton"!==s||this.resolve(t).catch((e=>{console.error(`[ArtifactContainer] Eager load failed for "${r}":`,e)})),()=>this.unregister(t)}async unregister(e){await this.manager.dispose(e),await this.registry.unregister(e)}async resolve(e){if(!this.registry.has(e))throw new l(e);return this.manager.build(e)}async require(e){const t=await this.resolve(e);if(t.error)throw t.error;return t.instance}watch(e){if(!this.registry.has(e))throw new l(e);return this.observer.watch(e)}peek(e){const t=this.cache.get(e);if(void 0!==t?.instance)return t?.instance}async invalidate(e,t=!1){return this.manager.invalidate(e,t)}notifyObservers(e){this.observer.notify(e)}hasWatchers(e){return this.observer.hasWatchers(e)}dispose(){this.registry.keys().forEach((e=>{this.manager.dispose(e).catch((t=>{console.error(`[ArtifactContainer] Failed to dispose artifact "${String(e)}":`,t)}))})),this.registry.clear(),this.cache.clear(),this.graph.clear(),this.observer.clear()}};export{k as ArtifactContainer,h as ArtifactScopes};
|