Skip to content

Commit f26a67f

Browse files
feat: Add static typing for eventbus (#586)
1 parent a7918ac commit f26a67f

50 files changed

Lines changed: 569 additions & 167 deletions

Some content is hidden

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

.changeset/eager-numbers-dress.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@squide/firefly-module-federation": major
3+
"@squide/firefly": major
4+
"@squide/core": major
5+
---
6+
7+
Added typed EventBus through module augmentation of EventMap.

agent-docs/design/cross-module-communication.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,25 @@ coordination happens through the FireflyRuntime API.
77

88
## Event Bus
99

10-
Pub/sub messaging for decoupled communication between modules:
10+
Pub/sub messaging for decoupled communication between modules. Events are type-safe via module augmentation of the `EventMap` interface (same pattern as `EnvironmentVariables` and `FeatureFlags`). All Squide native events are pre-augmented; consumer apps add their own events:
11+
12+
```ts
13+
// types/event-map.d.ts — consumer augmentation
14+
declare module "@squide/firefly" {
15+
interface EventMap {
16+
"tenant-changed": { tenantId: string };
17+
}
18+
}
19+
```
1120

1221
```tsx
13-
// Dispatch an event
22+
// Dispatch — payload type inferred from EventMap
1423
const dispatch = useEventBusDispatcher();
15-
dispatch("event-name", payload);
24+
dispatch("tenant-changed", { tenantId: "abc" });
1625

17-
// Listen for an event
18-
const handler = useCallback((data, context) => { /* ... */ }, []);
19-
useEventBusListener("event-name", handler, { once: true });
26+
// Listen — handler payload type inferred from EventMap
27+
const handler = useCallback(data => { /* ... */ }, []);
28+
useEventBusListener("tenant-changed", handler, { once: true });
2029
```
2130

2231
## Plugins

agent-skills/workleap-squide/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ description: |
1111
(7) Squide hooks for event bus, environment variables, feature flags, logging, or bootstrapping state
1212
(8) Error boundaries or modular architecture in Squide applications
1313
metadata:
14-
version: 1.9
14+
version: 1.10
1515
---
1616

1717
# Squide Framework

agent-skills/workleap-squide/references/hooks-api.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -222,34 +222,51 @@ useDeferredRegistrations(undefined, {
222222

223223
## Messaging Hooks
224224

225+
The event bus is type-safe via module augmentation of the `EventMap` interface (same pattern as `EnvironmentVariables` and `FeatureFlags`). Events must be declared in `EventMap` before they can be dispatched or listened to.
226+
227+
### TypeScript Augmentation (EventMap Interface)
228+
229+
```ts
230+
// types/event-map.d.ts
231+
import "@squide/firefly";
232+
233+
declare module "@squide/firefly" {
234+
interface EventMap {
235+
"tenant-changed": { tenantId: string };
236+
"theme-updated": { mode: "light" | "dark" };
237+
}
238+
}
239+
```
240+
241+
All Squide native events (bootstrapping lifecycle, data fetching, AppRouter state) are pre-augmented and already type-safe.
242+
225243
### useEventBusListener(eventName, handler, options?)
226-
Listen to events from the event bus.
244+
Listen to events from the event bus. The handler payload type is inferred from `EventMap`.
227245

228246
```ts
229247
import { useEventBusListener } from "@squide/firefly";
230248
import { useCallback } from "react";
231249

232-
const handleEvent = useCallback((data, context) => {
233-
console.log("Event received:", data);
250+
// Payload inferred as { tenantId: string }
251+
const handleTenantChanged = useCallback(data => {
252+
console.log("Tenant:", data?.tenantId);
234253
}, []);
235-
236-
// Listen to all events
237-
useEventBusListener("user-updated", handleEvent);
254+
useEventBusListener("tenant-changed", handleTenantChanged);
238255

239256
// Listen once
240-
useEventBusListener("init-complete", handleEvent, { once: true });
257+
useEventBusListener("tenant-changed", handleTenantChanged, { once: true });
241258
```
242259

243260
### useEventBusDispatcher()
244-
Get a function to dispatch events.
261+
Get a function to dispatch events. The payload type is enforced by `EventMap`.
245262

246263
```ts
247264
import { useEventBusDispatcher } from "@squide/firefly";
248265

249266
const dispatch = useEventBusDispatcher();
250267

251-
// Dispatch event with payload
252-
dispatch("user-updated", { id: 123, name: "John" });
268+
// Payload must match EventMap["tenant-changed"]
269+
dispatch("tenant-changed", { tenantId: "abc" });
253270
```
254271

255272
## Environment & Configuration Hooks

docs/essentials/use-event-bus.md

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ Register a function that will be invoked each time the specified event is dispat
1717
import { useCallback } from "react";
1818
import { useEventBusListener } from "@squide/firefly";
1919

20-
const handleFoo = useCallback((data, context) => {
20+
const handleShowToast = useCallback(data => {
2121
// Do something...
2222
}, []);
2323

24-
// Listen to every "foo" events.
25-
useEventBusListener("foo", handleFoo);
24+
// Listen to every "show-toast" events.
25+
useEventBusListener("show-toast", handleShowToast);
2626
```
2727

2828
## Add an event listener that will be invoked once
@@ -33,12 +33,12 @@ Register a function that will be invoked once, and then automatically unregister
3333
import { useCallback } from "react";
3434
import { useEventBusListener } from "@squide/firefly";
3535

36-
const handleFoo = useCallback((data, context) => {
36+
const handleShowToast = useCallback(data => {
3737
// Do something...
3838
}, []);
3939

40-
// Listen to the first "foo" event.
41-
useEventBusListener("foo", handleFoo, { once: true };
40+
// Listen to the first "show-toast" event.
41+
useEventBusListener("show-toast", handleShowToast, { once: true });
4242
```
4343

4444
## Dispatch an event
@@ -48,8 +48,72 @@ import { useEventBusDispatcher } from "@squide/firefly";
4848

4949
const dispatch = useEventBusDispatcher();
5050

51-
// Dispatch a "foo" event with a "bar" payload.
52-
dispatch("foo", "bar");
51+
// Dispatch a "show-toast" event with a string payload.
52+
dispatch("show-toast", "Hello!");
5353
```
5454

55+
## Setup the typings
5556

57+
Before dispatching or listening to events, modules should [augment](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) the [EventMap](../reference/messaging/EventMap.md) interface with the events they intend to use to ensure type safety and autocompletion.
58+
59+
First, create a types folder in the project:
60+
61+
``` !#7-8
62+
project
63+
├── src
64+
├────── register.tsx
65+
├────── Page.tsx
66+
├────── index.tsx
67+
├────── App.tsx
68+
├── types
69+
├────── event-map.d.ts
70+
```
71+
72+
Then create an `event-map.d.ts` file:
73+
74+
```ts !#6 project/types/event-map.d.ts
75+
import "@squide/firefly";
76+
77+
declare module "@squide/firefly" {
78+
interface EventMap {
79+
// Each entry maps an event name to its payload type.
80+
"show-toast": string;
81+
}
82+
}
83+
```
84+
85+
Finally, update the project `tsconfig.json` to include the `types` folder:
86+
87+
```json !#5-7 project/tsconfig.json
88+
{
89+
"compilerOptions": {
90+
"incremental": true,
91+
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
92+
"types": [
93+
"./types/event-map.d.ts"
94+
]
95+
},
96+
"exclude": ["dist", "node_modules"]
97+
}
98+
```
99+
100+
If any other project using those events must also reference the project's `event-map.d.ts` file:
101+
102+
```json !#5-7 project/tsconfig.json
103+
{
104+
"compilerOptions": {
105+
"incremental": true,
106+
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
107+
"types": [
108+
"../another-project/types/event-map.d.ts"
109+
]
110+
},
111+
"exclude": ["dist", "node_modules"]
112+
}
113+
```
114+
115+
Once configured, the event bus hooks are fully typed:
116+
117+
:::align-image-left
118+
![Auto-completion example](../static/event-bus-typings.png){width=735}
119+
:::
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
order: 90
3+
toc:
4+
depth: 2-3
5+
---
6+
7+
# EventMap
8+
9+
The `EventMap` interface defines the shape and types of the event names and payload values that modules dispatch and listen to through the [FireflyRuntime](../runtime/FireflyRuntime.md) instance event bus.
10+
11+
Consumer applications are expected to [augment](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) this interface to declare the events they intend to use, providing a fully type-safe experience when working with the event bus.
12+
13+
All Squide native events (bootstrapping lifecycle, data fetching, AppRouter state transitions) are pre-augmented and already type-safe. You only need to augment `EventMap` for your own application events.
14+
15+
## Augment the interface
16+
17+
First, create a types folder in the project:
18+
19+
``` !#7-8
20+
project
21+
├── src
22+
├────── register.tsx
23+
├────── Page.tsx
24+
├────── index.tsx
25+
├────── App.tsx
26+
├── types
27+
├────── event-map.d.ts
28+
```
29+
30+
Then create an `event-map.d.ts` file:
31+
32+
```ts !#6 project/types/event-map.d.ts
33+
import "@squide/firefly";
34+
35+
declare module "@squide/firefly" {
36+
interface EventMap {
37+
// Each entry maps an event name to its payload type.
38+
"show-toast": string;
39+
}
40+
}
41+
```
42+
43+
Finally, update the project `tsconfig.json` to include the `types` folder:
44+
45+
```json !#5-7 project/tsconfig.json
46+
{
47+
"compilerOptions": {
48+
"incremental": true,
49+
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
50+
"types": [
51+
"./types/event-map.d.ts"
52+
]
53+
},
54+
"exclude": ["dist", "node_modules"]
55+
}
56+
```
57+
58+
Any project that uses these events must also reference the project's `event-map.d.ts` file:
59+
60+
```json !#5-7 project/tsconfig.json
61+
{
62+
"compilerOptions": {
63+
"incremental": true,
64+
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
65+
"types": [
66+
"../another-project/types/event-map.d.ts"
67+
]
68+
},
69+
"exclude": ["dist", "node_modules"]
70+
}
71+
```

docs/reference/messaging/useEventBusDispatcher.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ None
1919

2020
### Returns
2121

22-
A dispatch function: `(eventName: string, payload?: {}) => void`.
22+
A dispatch function. The event name must be a key augmented in [EventMap](./EventMap.md). The payload type is inferred from the event name.
2323

2424
## Usage
2525

@@ -28,5 +28,5 @@ import { useEventBusDispatcher } from "@squide/firefly";
2828

2929
const dispatch = useEventBusDispatcher();
3030

31-
dispatch("foo", "bar");
31+
dispatch("show-toast", "Hello!");
3232
```

docs/reference/messaging/useEventBusListener.md

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,37 @@ useEventBusListener(eventName, callback: () => {}, options?: { once? })
1515

1616
### Parameters
1717

18-
- `eventName`: The name of the event to listen for.
19-
- `callback`: A function to be executed when a event matching the provided name is dispatched.
18+
- `eventName`: The name of the event to listen for. Must be a key augmented in [EventMap](./EventMap.md).
19+
- `callback`: A function to be executed when an event matching the provided name is dispatched.
2020
- `options`: An optional object literal of options:
21-
- `once`: Whether or not the event listener should be automatically removed once an event as been handled.
21+
- `once`: Whether or not the event listener should be automatically removed once an event has been handled.
2222

2323
### Returns
2424

2525
Nothing
2626

2727
## Usage
2828

29-
```ts !#9,12
29+
```ts !#8
3030
import { useCallback } from "react";
3131
import { useEventBusListener } from "@squide/firefly";
3232

33-
const handleFoo = useCallback((data, context) => {
34-
// Do something...
33+
const handleToast = useCallback(data => {
34+
console.log("Toast:", data);
3535
}, []);
3636

37-
// Listen to every "foo" events.
38-
useEventBusListener("foo", handleFoo);
37+
useEventBusListener("show-toast", handleToast);
38+
```
39+
40+
### Listen once
41+
42+
```ts !#8
43+
import { useCallback } from "react";
44+
import { useEventBusListener } from "@squide/firefly";
45+
46+
const handleToast = useCallback(data => {
47+
console.log("Toast:", data);
48+
}, []);
3949

40-
// Listen to the first "foo" event, then automatically remove the listener.
41-
useEventBusListener("foo", handleFoo, { once: true };
50+
useEventBusListener("show-toast", handleToast, { once: true });
4251
```

docs/static/event-bus-typings.png

45.1 KB
Loading

docs/updating/default.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ order: 50
1515
- [Migrate from v8 to v15.0](./migrate-from-v8-to-v15.0.md)
1616
- [Migrate to firefly v16.0](./migrate-to-firefly-v16.0.md)
1717
- [Migrate to firefly v16.2](./migrate-to-firefly-v16.2.md)
18+
- [Migrate to firefly v17.0](./migrate-to-firefly-v17.0.md)
1819
- [Migrate to Rsbuild](./migrate-to-rsbuild.md) [!badge variant="danger" size="xs" text="experimental"]
1920
- [Migrate to Just-In-Time (JIT) packages](./migrate-to-jit-packages.md)

0 commit comments

Comments
 (0)