npm package: @pawel-li/signal-store-logger
Description: Dev-only, zero-config, diff-aware state logger for **NgRx Signal Stores**, plain **Angular services**, and **components** that use Angular signals.
Latest version: 0.1.0

# @pawel-li/signal-store-logger

Dev-only, zero-config, diff-aware state logger for **NgRx Signal Stores**, plain **Angular services**, and **components** that use Angular signals.

- Timestamped, color-coded console output
- Tracks _what_ changed, _why_ (action + UI interaction), and _when_ (wall-clock + delta)
- Source-map–aware stack traces
- Pattern warnings (oscillation, stuck loaders, rapid fire, redundant dispatches)
- Tree-shaken to nothing in production

---

## Quick start

```bash
npm install @pawel-li/signal-store-logger
```

```ts
// app.config.ts
import { provideSignalStoreLogger, withStore } from '@pawel-li/signal-store-logger';

export const appConfig: ApplicationConfig = {
  providers: [
    provideSignalStoreLogger({
      stores: [withStore(CartStore)],
      captureInitial: true,
      trace: true,
    }),
  ],
};
```

Console output:

```
── CartStore ────────────────────────────────────
what   items    [] → [3 items]
       total    0 → 42.99
why    (click) [Add to Cart] → CartStore.addItem(id: 7)
when   14:23:01.442  (+0.8s)
```

---

## Track plain services

Use `withSignals()` to observe any `@Injectable()` that exposes Angular signals:

```ts
provideSignalStoreLogger({
  stores: [withStore(CartStore)],
  targets: [
    withSignals(PulseService, {
      pick: s => ({ count: s.count, isLive: s.isLive }),
    }),
  ],
});
```

Console badge differentiates services from stores:

```
── [svc] PulseService ──────────────────────────
what   count    0 → 1
why    (click) [Start] → PulseService.start()
when   14:23:02.110  (+0.7s)
```

---

## Track components

Call `injectSignalLogger()` inside any component (or directive) constructor:

```ts
import { injectSignalLogger } from '@pawel-li/signal-store-logger';

@Component({ ... })
export class CounterComponent {
  readonly count = signal(0);

  constructor() {
    injectSignalLogger('CounterComponent', () => ({
      count: this.count,
    }));
  }
}
```

```
── [cmp] CounterComponent ──────────────────────
what   count    4 → 5
when   14:23:03.002  (+0.4s)
```

---

## Documentation

| Topic | File |
|---|---|
| Installation & first log | [docs/getting-started.md](docs/getting-started.md) |
| Tracking services | [docs/tracking-services.md](docs/tracking-services.md) |
| Tracking components | [docs/tracking-components.md](docs/tracking-components.md) |
| `LoggerConfig` full reference | [docs/configuration.md](docs/configuration.md) |
| Console output & `OutputConfig` | [docs/output-config.md](docs/output-config.md) |
| `StateChangeEntry` type reference | [docs/state-change-entry.md](docs/state-change-entry.md) |
| Pattern warnings | [docs/pattern-warnings.md](docs/pattern-warnings.md) |
| Advanced (sanitize, filter, SSR, custom logger) | [docs/advanced.md](docs/advanced.md) |
| Source tracing & source maps | [docs/source-tracing.md](docs/source-tracing.md) |

---

## Running tests

```bash
nx test signal-store-logger
```

---

## v0.1.0 Architecture

The internal architecture was redesigned in v0.1.0:

- **Orchestrator pipeline**: `LoggerOrchestratorService` drives the detect → enrich → resolve → render pipeline
- **Decoupled renderer**: `ConsoleRendererService` extends abstract `LoggerRenderer` — display-only, no business logic
- **Pattern detection for all targets**: `PatternDetectorService` now runs in the pipeline, applying to stores, services, and components
- **`EnrichedStateChangeEntry`**: State change entries carry pre-computed `warnings: string[]`
- **Zero production overhead**: `provideSignalStoreLogger()` returns `makeEnvironmentProviders([])` in prod — no DI tokens, no services, complete tree-shaking
- **Simplified `injectSignalLogger`**: Delegates to the orchestrator instead of duplicating tracking logic