Wirestate
Wirestate is a foundation for DI-backed TypeScript application architecture.
It gives you the pieces a UI framework layer needs but should not invent every time:
- Scoped containers for ownership boundaries.
- Injectable services for workflows and state owners.
- Lifecycle hooks for setup, cleanup, provider attach, and provider detach.
- Container-local events, commands, and queries for service communication.
- Seeds for hydration, tests, and per-subtree startup data.
React and Lit packages adapt that core to component trees. Reactivity stays outside the core: use MobX, Preact Signals, Lit Signals, or plain TypeScript.
Packages
| NPM | Package | Description |
|---|---|---|
@wirestate/core | DI container, services, events, commands, queries, seeds | |
@wirestate/react | React integration (hooks, providers) | |
@wirestate/react-mobx | React MobX re-exports and decorator wrappers | |
@wirestate/react-signals | React Signals (Preact) re-exports | |
@wirestate/lit | Lit integration (hooks, controllers, providers) | |
@wirestate/lit-signals | Lit signals re-exports |
Install
Import reflect-metadata once at the application entry point before decorated services are loaded.
For React
bash
# Core + React integration
npm install @wirestate/core @wirestate/react reflect-metadata
npm install react react-dom
# With MobX reactivity
npm install @wirestate/react-mobx mobx mobx-react-lite
# With Preact Signals reactivity
npm install @wirestate/react-signals @preact/signals-reactFor Lit
bash
# Core + Lit integration
npm install @wirestate/core @wirestate/lit reflect-metadata
npm install lit @lit/context @lit/reactive-element
# With Signals reactivity
npm install @wirestate/lit-signals @lit-labs/signals signal-polyfillExamples
React + Signals
tsx
import { Injectable } from "@wirestate/core";
import { ContainerProvider, useInjection } from "@wirestate/react";
import { signal, Signal } from "@wirestate/react-signals";
@Injectable()
class CounterService {
public readonly count: Signal<number> = signal(0);
public increment(): void {
this.count.value += 1;
}
}
export function Application() {
const config = useMemo(() => ({ entries: [CounterService] }), []);
return (
<ContainerProvider config={config}>
<Counter />
</ContainerProvider>
);
}
function Counter() {
const counterService = useInjection(CounterService);
return (
<button onClick={() => counterService.increment()}>
Count: {counterService.count.value}
</button>
);
}React + MobX
tsx
import { Injectable } from "@wirestate/core";
import { ContainerProvider, useInjection } from "@wirestate/react";
import {
Action,
Observable,
makeObservable,
observer,
} from "@wirestate/react-mobx";
@Injectable()
class CounterService {
@Observable()
public count: number = 0;
public constructor() {
makeObservable(this);
}
@Action()
public increment(): void {
this.count += 1;
}
}
export function Application() {
const config = useMemo(() => ({ entries: [CounterService] }), []);
return (
<ContainerProvider config={config}>
<Counter />
</ContainerProvider>
);
}
const Counter = observer(function Counter() {
const counterService = useInjection(CounterService);
return (
<button onClick={() => counterService.increment()}>
Count: {counterService.count}
</button>
);
});Lit + Signals
ts
import { Injectable } from "@wirestate/core";
import { ContainerProvider, containerProvide, injection } from "@wirestate/lit";
import { signal, State, watch } from "@wirestate/lit-signals";
import { html, LitElement } from "lit";
import { customElement } from "lit/decorators.js";
@Injectable()
class CounterService {
public readonly count: State<number> = signal(0);
public increment(): void {
this.count.set(this.count.get() + 1);
}
}
@customElement("counter-application")
class CounterApplication extends LitElement {
@containerProvide({ config: { entries: [CounterService] } })
private containerProvider!: ContainerProvider;
protected render() {
return html`<counter-button></counter-button>`;
}
}
@customElement("counter-button")
class CounterButton extends LitElement {
@injection(CounterService)
private counterService!: CounterService;
protected render() {
return html`
<button @click=${() => this.counterService.increment()}>
Count: ${watch(this.counterService.count)}
</button>
`;
}
}Docs
License
MIT