Skip to content

Commit 4abcc74

Browse files
committed
Rename Router → EventBus (v9 breaking change)
The name `Router` is overloaded with URL-routing terminology in the web ecosystem. This renames the central dispatch class to `EventBus` — a name that accurately reflects its role as an event-driven state bus. Breaking changes - `Router` → `EventBus` - `router` variable convention → `bus` - `RouterContext` → `EventBusContext` - `EspRouterContextProvider` → `EspEventBusContextProvider` - `RouterProvider` → `EventBusProvider` - `useRouter()` → `useEventBus()` - `ConnectableComponentChildProps.router` → `.bus` - `src/router/` → `src/eventBus/` - `tests/router/` → `tests/eventBus/` - `espRouterContextProvider.tsx` → `espEventBusContextProvider.tsx` - `routerSpy.ts` → `eventBusSpy.ts` - Error messages, DevTools labels, and Logger names updated throughout
1 parent 613013a commit 4abcc74

65 files changed

Lines changed: 683 additions & 693 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
## Project Overview
44

5-
ESP is a TypeScript/JavaScript framework for managing model state changes in a deterministic, event-driven manner. A central `Router` sits between event publishers and models: publishers call `router.publishEvent(modelId, eventType, event)`, the router queues and dispatches events through ordered observation stages, and a frozen immutable snapshot of the mutated model is then pushed to model observers. The framework uses an immer-based functional modeling pattern (`ModelBuilder`) and is designed for complex composite single-page applications.
5+
ESP is a TypeScript/JavaScript framework for managing model state changes in a deterministic, event-driven manner. A central `EventBus` sits between event publishers and models: publishers call `bus.publishEvent(modelId, eventType, event)`, the bus queues and dispatches events through ordered observation stages, and a frozen immutable snapshot of the mutated model is then pushed to model observers. The framework uses an immer-based functional modeling pattern (`ModelBuilder`) and is designed for complex composite single-page applications.
66

7-
The monorepo contains the core router, dependency injection container, and React integration.
7+
The monorepo contains the core event bus, dependency injection container, and React integration.
88

99
## Monorepo Structure
1010

1111
```
1212
packages/
13-
esp-js # Core event router — foundational, no dependencies on other esp-* packages
13+
esp-js # Core event bus — foundational, no dependencies on other esp-* packages
1414
esp-js-di # Standalone IoC container (JavaScript, not TypeScript)
1515
esp-js-react # React bindings (ConnectableComponent, hooks); depends on esp-js
1616
```
@@ -105,27 +105,27 @@ npm run test-ci # vitest run (no watch, CI mode)
105105

106106
## Key Architectural Patterns
107107

108-
### The Router event dispatch cycle
108+
### The EventBus event dispatch cycle
109109

110-
1. `router.publishEvent(modelId, eventType, event)` — enqueues event
111-
2. Router drains the queue, dispatching each event through four sequential observation stages:
110+
1. `bus.publishEvent(modelId, eventType, event)` — enqueues event
111+
2. EventBus drains the queue, dispatching each event through four sequential observation stages:
112112
- `preview` — observe before mutation; can cancel the event
113113
- `normal` — primary mutation stage
114114
- `committed` — only fires if `eventContext.commit()` was called during `normal`
115115
- `final` — fires regardless of commit; observe after all mutation
116-
3. After all events for a model are processed, a frozen immutable snapshot of the model is pushed to model observers (`router.getModelObservable(modelId)`)
116+
3. After all events for a model are processed, a frozen immutable snapshot of the model is pushed to model observers (`bus.getModelObservable(modelId)`)
117117

118118
### Functional model pattern (esp-js core)
119119

120120
- Create a plain object or class instance as the initial state
121-
- Register with `router.modelBuilder(modelId, initialState).withEventHandler(...).build()`
122-
- Event handlers receive an immer `draft` — mutate it directly; the router produces a new frozen snapshot after all handlers run
121+
- Register with `bus.modelBuilder(modelId, initialState).withEventHandler(...).build()`
122+
- Event handlers receive an immer `draft` — mutate it directly; the bus produces a new frozen snapshot after all handlers run
123123
- Preview handlers and effect handlers receive `Readonly<TModel>` (no draft)
124124
- Class instances used as model state must include `[immerable] = true` (from `immer`) for immer to handle them
125125

126126
### React integration (esp-js-react)
127127

128-
- `<EspRouterContextProvider router={router}>` — provides router via context
128+
- `<EspEventBusContextProvider bus={bus}>` — provides bus via context
129129
- `ConnectableComponent` / `connect()` — subscribes to a model by `modelId`, re-renders on model updates; requires explicit `view` prop
130130
- `useSyncModelWithSelector` — hook-based subscription with selector and equality function
131131

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ ESP gives you the ability to manage changes to a model in a deterministic event
88
It does this by adding specific processing workflow around changes to a model's state.
99
It was born out of the need to manage complex UI and/or server state.
1010

11-
At its core is a `Router` which sits between event publishers and the model.
12-
Those wanting to change the model publish events to the `Router`.
11+
At its core is a `EventBus` which sits between event publishers and the model.
12+
Those wanting to change the model publish events to the `EventBus`.
1313
The model observes the events and applies the changes.
1414
The model is then dispatched to model observers so new state can be applied.
1515
It's lightweight, easy to apply and puts the model at the forefront of your design.
@@ -18,11 +18,9 @@ ESP 2.0 adds a host of other additional libraries to help you build composite si
1818
It allows you to use either OO, and/or immutable pattens (Redux like) for modeling independent and decoupled screens within your composite application.
1919
Features include:
2020

21-
* The core event router - esp-js [![npm](https://img.shields.io/npm/v/esp-js.svg)](https://www.npmjs.com/package/esp-js)
21+
* The core EventBus - esp-js [![npm](https://img.shields.io/npm/v/esp-js.svg)](https://www.npmjs.com/package/esp-js)
2222
* Dependency injection container - esp-js-di [![npm](https://img.shields.io/npm/v/esp-js-di.svg)](https://www.npmjs.com/package/esp-js-di)
23-
* Module loading system and composite application toolbox - esp-js-ui [![npm](https://img.shields.io/npm/v/esp-js-ui.svg)](https://www.npmjs.com/package/esp-js-ui)
2423
* React support - esp-js-react [![npm](https://img.shields.io/npm/v/esp-js-react.svg)](https://www.npmjs.com/package/esp-js-react)
25-
* Immutable state models - esp-js-polimer [![npm](https://img.shields.io/npm/v/esp-js-polimer.svg)](https://www.npmjs.com/package/esp-js-polimer)
2624

2725
It's built on typescript and type definitions are included in the npm packages.
2826

packages/esp-js-di/CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,10 @@ Primary surface: `Container` class. `RegistrationModifier` is returned by `regis
117117

118118
## Common Tasks
119119

120-
**Register the router and services:**
120+
**Register the eventbus and services:**
121121
```javascript
122-
container.register('router', Router).singleton();
123-
container.register('myService', MyService).inject('router').singleton();
122+
container.register('bus', EventBus).singleton();
123+
container.register('myService', MyService).inject('bus').singleton();
124124
```
125125

126126
**Create and dispose a module's child container:**

packages/esp-js-di/README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ ESP gives you the ability to manage changes to a model in a deterministic event
88
It does this by adding specific processing workflow around changes to a model's state.
99
It was born out of the need to manage complex UI and/or server state.
1010

11-
At its core is a `Router` which sits between event publishers and the model.
12-
Those wanting to change the model publish events to the `Router`.
11+
At its core is a `EventBus` which sits between event publishers and the model.
12+
Those wanting to change the model publish events to the `EventBus`.
1313
The model observes the events and applies the changes.
1414
The model is then dispatched to model observers so new state can be applied.
1515
It's lightweight, easy to apply and puts the model at the forefront of your design.
@@ -18,11 +18,9 @@ ESP 2.0 adds a host of other additional libraries to help you build composite si
1818
It allows you to use either OO, and/or immutable pattens (Redux like) for modeling independent and decoupled screens within your composite application.
1919
Features include:
2020

21-
* The core event router - esp-js [![npm](https://img.shields.io/npm/v/esp-js.svg)](https://www.npmjs.com/package/esp-js)
21+
* The core EventBus - esp-js [![npm](https://img.shields.io/npm/v/esp-js.svg)](https://www.npmjs.com/package/esp-js)
2222
* Dependency injection container - esp-js-di [![npm](https://img.shields.io/npm/v/esp-js-di.svg)](https://www.npmjs.com/package/esp-js-di)
23-
* Module loading system and composite application toolbox - esp-js-ui [![npm](https://img.shields.io/npm/v/esp-js-ui.svg)](https://www.npmjs.com/package/esp-js-ui)
2423
* React support - esp-js-react [![npm](https://img.shields.io/npm/v/esp-js-react.svg)](https://www.npmjs.com/package/esp-js-react)
25-
* Immutable state models - esp-js-polimer [![npm](https://img.shields.io/npm/v/esp-js-polimer.svg)](https://www.npmjs.com/package/esp-js-polimer)
2624

2725
It's built on typescript and type definitions are included in the npm packages.
2826

packages/esp-js-react/CLAUDE.md

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Package Purpose
44

5-
React bindings for ESP. Provides two complementary integration approaches: the `ConnectableComponent`/`connect()` HOC pattern and the `useSyncModelWithSelector` hook. Also provides React context providers for the `Router` and model.
5+
React bindings for ESP. Provides two complementary integration approaches: the `ConnectableComponent`/`connect()` HOC pattern and the `useSyncModelWithSelector` hook. Also provides React context providers for the `EventBus` and model.
66

77
## Role in Monorepo
88

@@ -27,29 +27,29 @@ Note: the Vite entry and output filename is `esp-react` (see `vite.config.ts`),
2727

2828
```
2929
src/
30-
index.ts # Public exports
31-
connectableComponent.tsx # ConnectableComponent — HOC that subscribes to router model
32-
connect.tsx # connect() factory — creates typed ConnectableComponentFactory
33-
espRouterContextProvider.tsx # RouterProvider, EspRouterContextProvider, useRouter, usePublishEvent
34-
espModelContextProvider.tsx # EspModelContextProvider, useGetModel, useGetModelId, usePublishModelEvent
35-
useSyncModelWithSelector.ts # useSyncModelWithSelector hook + SyncModelWithSelectorOptions
30+
index.ts # Public exports
31+
connectableComponent.tsx # ConnectableComponent — HOC that subscribes to bus model
32+
connect.tsx # connect() factory — creates typed ConnectableComponentFactory
33+
espEventBusContextProvider.tsx # EventBusProvider, EspEventBusContextProvider, useEventBus, usePublishEvent
34+
espModelContextProvider.tsx # EspModelContextProvider, useGetModel, useGetModelId, usePublishModelEvent
35+
useSyncModelWithSelector.ts # useSyncModelWithSelector hook + SyncModelWithSelectorOptions
3636
```
3737

3838
## Key Concepts and Patterns
3939

40-
### Router Context
40+
### EventBus Context
4141

42-
Wrap the application (or subtree) with `EspRouterContextProvider` to make the router available via context:
42+
Wrap the application (or subtree) with `EspEventBusContextProvider` to make the bus available via context:
4343

4444
```tsx
45-
<EspRouterContextProvider router={router}>
45+
<EspEventBusContextProvider bus={bus}>
4646
<App />
47-
</EspRouterContextProvider>
47+
</EspEventBusContextProvider>
4848
```
4949

50-
Access the router in any descendant:
50+
Access the bus in any descendant:
5151
```tsx
52-
const router = useRouter();
52+
const bus = useEventBus();
5353
const publishEvent = usePublishEvent(); // (modelId, eventType, event) => void
5454
```
5555

@@ -67,11 +67,11 @@ const MyConnectedView = connect<MyModel, MyPublishProps>(
6767
<MyConnectedView modelId="my-model" />
6868
```
6969

70-
`ConnectableComponent` subscribes to `router.getModelObservable(modelId)` and re-renders when the model updates.
70+
`ConnectableComponent` subscribes to `bus.getModelObservable(modelId)` and re-renders when the model updates.
7171

7272
### useSyncModelWithSelector
7373

74-
Hook-based approach using React 18's `useSyncExternalStore`. The model emitted by the router is always a frozen immutable snapshot (produced by immer via `ModelBuilder`) — no unwrapping required.
74+
Hook-based approach using React 18's `useSyncExternalStore`. The model emitted by the bus is always a frozen immutable snapshot (produced by immer via `ModelBuilder`) — no unwrapping required.
7575

7676
```tsx
7777
const orders = useSyncModelWithSelector<OrdersState>(
@@ -109,8 +109,8 @@ export { ConnectableComponent, ConnectableComponentProps, MapModelToProps, Creat
109109
// Hook approach
110110
export { useSyncModelWithSelector, SyncModelWithSelectorOptionsBuilder, syncModelWithSelectorOptions, SyncModelWithSelectorOptions, SyncModelWithSelectorEqualityFn }
111111

112-
// Router context
113-
export { RouterProvider, EspRouterContextProvider, RouterContext, useRouter, PublishEventDelegate, PublishEventContext, usePublishEvent }
112+
// EventBus context
113+
export { EventBusProvider, EspEventBusContextProvider, EventBusContext, useEventBus, PublishEventDelegate, PublishEventContext, usePublishEvent }
114114

115115
// Model context
116116
export { EspModelContextProvider, useGetModelId, useGetModel, usePublishModelEvent, usePublishModelEventWithEntityKey, GetModelIdContext, GetModelContext, PublishModelEventDelegate, PublishModelEventContext, PublishModelEventWithEntityKeyDelegate, PublishModelEventWithEntityKeyContext }
@@ -120,26 +120,26 @@ export { EspModelContextProvider, useGetModelId, useGetModel, usePublishModelEve
120120

121121
- Test files: `tests/**/*Tests.tsx`
122122
- Uses `@testing-library/react` for rendering and interaction
123-
- `tests/testApi/` — shared test fixtures including mock router and model helpers
124-
- `testModel.ts``createTestModel(router, modelId)` registers a model via `ModelBuilder`
123+
- `tests/testApi/` — shared test fixtures including mock bus and model helpers
124+
- `testModel.ts``createTestModel(bus, modelId)` registers a model via `ModelBuilder`
125125
- `testApi.tsx``setupTestModel(modelId)` and `setupModel(modelId, model)` helpers
126-
- `routerSpy.ts``RouterSpy extends Router`, wraps `getModelObservable()` to count subscriptions (does **not** use `Observable.create` — reactive module is internal)
126+
- `eventBusSpy.ts``EventBusSpy extends EventBus`, wraps `getModelObservable()` to count subscriptions (does **not** use `Observable.create` — reactive module is internal)
127127
- Notable test files:
128128
- `connectableComponentTests.tsx` — HOC subscription and re-render behaviour; models use `[immerable] = true`
129129
- `useSyncModelWithSelectorTests.tsx` — hook selector and equality function behaviour
130-
- `espModelContextProviderTests.tsx`, `espRouterContextProviderTests.tsx` — context hook tests; use `router.getModel()` to read current snapshot after events
130+
- `espModelContextProviderTests.tsx`, `espEventBusContextProviderTests.tsx` — context hook tests; use `bus.getModel()` to read current snapshot after events
131131

132132
## Common Tasks
133133

134134
**Minimal setup with hooks:**
135135
```tsx
136136
function App() {
137137
return (
138-
<EspRouterContextProvider router={router}>
138+
<EspEventBusContextProvider bus={bus}>
139139
<EspModelContextProvider modelId="counter">
140140
<CounterView />
141141
</EspModelContextProvider>
142-
</EspRouterContextProvider>
142+
</EspEventBusContextProvider>
143143
);
144144
}
145145

packages/esp-js-react/README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ ESP gives you the ability to manage changes to a model in a deterministic event
88
It does this by adding specific processing workflow around changes to a model's state.
99
It was born out of the need to manage complex UI and/or server state.
1010

11-
At its core is a `Router` which sits between event publishers and the model.
12-
Those wanting to change the model publish events to the `Router`.
11+
At its core is a `EventBus` which sits between event publishers and the model.
12+
Those wanting to change the model publish events to the `EventBus`.
1313
The model observes the events and applies the changes.
1414
The model is then dispatched to model observers so new state can be applied.
1515
It's lightweight, easy to apply and puts the model at the forefront of your design.
@@ -18,11 +18,9 @@ ESP 2.0 adds a host of other additional libraries to help you build composite si
1818
It allows you to use either OO, and/or immutable pattens (Redux like) for modeling independent and decoupled screens within your composite application.
1919
Features include:
2020

21-
* The core event router - esp-js [![npm](https://img.shields.io/npm/v/esp-js.svg)](https://www.npmjs.com/package/esp-js)
21+
* The core EventBus- esp-js [![npm](https://img.shields.io/npm/v/esp-js.svg)](https://www.npmjs.com/package/esp-js)
2222
* Dependency injection container - esp-js-di [![npm](https://img.shields.io/npm/v/esp-js-di.svg)](https://www.npmjs.com/package/esp-js-di)
23-
* Module loading system and composite application toolbox - esp-js-ui [![npm](https://img.shields.io/npm/v/esp-js-ui.svg)](https://www.npmjs.com/package/esp-js-ui)
2423
* React support - esp-js-react [![npm](https://img.shields.io/npm/v/esp-js-react.svg)](https://www.npmjs.com/package/esp-js-react)
25-
* Immutable state models - esp-js-polimer [![npm](https://img.shields.io/npm/v/esp-js-polimer.svg)](https://www.npmjs.com/package/esp-js-polimer)
2624

2725
It's built on typescript and type definitions are included in the npm packages.
2826

packages/esp-js-react/src/connectableComponent.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
2-
import {useRouter} from './espRouterContextProvider';
2+
import {useEventBus} from './espEventBusContextProvider';
33
import {useMemo} from 'react';
4-
import {Router} from 'esp-js';
4+
import {EventBus} from 'esp-js';
55
import {EspModelContextProvider, useGetModelId} from './espModelContextProvider';
66
import {syncModelWithSelectorOptions, useSyncModelWithSelector} from './useSyncModelWithSelector';
77

@@ -20,12 +20,12 @@ export interface ConnectableComponentProps<TModel = {}, TPublishEventProps = {},
2020
export interface ConnectableComponentChildProps<TModel = object> {
2121
modelId: string;
2222
model: TModel;
23-
router: Router;
23+
bus: EventBus;
2424
[key: string]: any; // ...rest props
2525
}
2626

2727
const getChildProps = <TModel, TModelMappedToProps, TPublishEventProps>(
28-
router: Router,
28+
bus: EventBus,
2929
modelId: string,
3030
restProps: object,
3131
model: TModel,
@@ -34,7 +34,7 @@ const getChildProps = <TModel, TModelMappedToProps, TPublishEventProps>(
3434
): ConnectableComponentChildProps<TModel> => {
3535
let childProps = {
3636
modelId,
37-
router: router,
37+
bus: bus,
3838
...restProps,
3939
...publishEventProps,
4040
model
@@ -57,21 +57,21 @@ export const ConnectableComponent = <TModel = {}, TPublishEventProps = {}, TMode
5757
...rest
5858
}: ConnectableComponentProps<TModel, TPublishEventProps, TModelMappedToProps>
5959
) => {
60-
const router = useRouter();
60+
const bus = useEventBus();
6161
// Note: we always need to render the hooks else react will complain about a different count.
6262
let modelIdFromContext = useGetModelId();
6363
modelId = modelId || modelIdFromContext;
6464
const publishEventProps: TPublishEventProps = useMemo(
6565
() => {
6666
if (createPublishEventProps) {
6767
const publishModelEvent = (eventType: string, event: any) => {
68-
router.publishEvent(modelId, eventType, event);
68+
bus.publishEvent(modelId, eventType, event);
6969
};
7070
return createPublishEventProps(publishModelEvent);
7171
}
7272
return {} as TPublishEventProps;
7373
},
74-
[router, modelId]
74+
[bus, modelId]
7575
);
7676
const model = useSyncModelWithSelector<TModel, TModel>(
7777
m => m,
@@ -81,10 +81,10 @@ export const ConnectableComponent = <TModel = {}, TPublishEventProps = {}, TMode
8181
if (model == null) {
8282
return null;
8383
}
84-
let childProps = getChildProps(router, modelId, rest, model, mapModelToProps, publishEventProps);
84+
let childProps = getChildProps(bus, modelId, rest, model, mapModelToProps, publishEventProps);
8585
let viewElement = view ? React.createElement(view as React.ComponentType<any>, childProps) : null;
8686
return (
87-
<EspModelContextProvider modelId={modelId} model={model} router={router} {...childProps}>
87+
<EspModelContextProvider modelId={modelId} model={model} bus={bus} {...childProps}>
8888
{viewElement}
8989
</EspModelContextProvider>
9090
);

0 commit comments

Comments
 (0)