Skip to content

Commit df1cdc8

Browse files
committed
stash
1 parent b46845f commit df1cdc8

File tree

3 files changed

+124
-26
lines changed

3 files changed

+124
-26
lines changed

packages/composable-controller/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
- **BREAKING:** Passing a non-controller into `controllers` constructor option now throws an error ([#3904](https://github.com/MetaMask/core/pull/3904))
1717
- **BREAKING:** The `AllowedActions` parameter of the `ComposableControllerMessenger` type is narrowed from `string` to `never`, as `ComposableController` does not use any external controller actions. ([#3904](https://github.com/MetaMask/core/pull/3904))
18-
- Subscribe to the `stateChange` events of `BaseControllerV1` controllers that have a `messagingSystem` class field with an assigned instance of the `RestrictedControllerMessenger` class. ([#3964](https://github.com/MetaMask/core/pull/3964))
18+
- **BREAKING:** The `ComposableController` class is now a generic class that expects one generic argument `ChildControllers` in the form of a union of the controllers passed into the `controllers` array constructor option ([#3941](https://github.com/MetaMask/core/pull/3941))
19+
- Child controllers that extend `BaseControllerV1` must have an overridden `name` property that is defined using the `as const` assertion for the `ComposableController` class to be typed correctly.
20+
- The controller state is now constrained by a `ComposedControllerState` generic argument, which is automaticallly derived from the `ChildControllers` argument without needing to be passed in by the user.
21+
- The `messenger` constructor option is constrained by a `ComposedControllerMessenger` generic argument which is also automatically derived, and is required to contain the `stateChange` events for all child controllers in its `Events` and `AllowedEvents` parameters, as well as the composable controller instance in its `Events` parameter.
1922

2023
### Removed
2124

packages/composable-controller/src/ComposableController.test.ts

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,50 @@ class FooController extends BaseController<
6060
}
6161
}
6262

63+
type QuzControllerState = {
64+
quz: string;
65+
};
66+
type QuzControllerEvent = {
67+
type: `QuzController:stateChange`;
68+
payload: [QuzControllerState, Patch[]];
69+
};
70+
71+
type QuzMessenger = RestrictedControllerMessenger<
72+
'QuzController',
73+
never,
74+
QuzControllerEvent,
75+
never,
76+
never
77+
>;
78+
79+
const quzControllerStateMetadata = {
80+
quz: {
81+
persist: true,
82+
anonymous: true,
83+
},
84+
};
85+
86+
class QuzController extends BaseController<
87+
'QuzController',
88+
QuzControllerState,
89+
QuzMessenger
90+
> {
91+
constructor(messagingSystem: QuzMessenger) {
92+
super({
93+
messenger: messagingSystem,
94+
metadata: quzControllerStateMetadata,
95+
name: 'QuzController',
96+
state: { quz: 'quz' },
97+
});
98+
}
99+
100+
updateQuz(quz: string) {
101+
super.update((state) => {
102+
state.quz = quz;
103+
});
104+
}
105+
}
106+
63107
// Mock BaseControllerV1 classes
64108

65109
type BarControllerState = BaseState & {
@@ -71,7 +115,7 @@ class BarController extends BaseControllerV1<never, BarControllerState> {
71115
bar: 'bar',
72116
};
73117

74-
override name = 'BarController';
118+
override name = 'BarController' as const;
75119

76120
constructor() {
77121
super();
@@ -92,7 +136,7 @@ class BazController extends BaseControllerV1<never, BazControllerState> {
92136
baz: 'baz',
93137
};
94138

95-
override name = 'BazController';
139+
override name = 'BazController' as const;
96140

97141
constructor() {
98142
super();
@@ -157,23 +201,39 @@ describe('ComposableController', () => {
157201
it('should compose controller state', () => {
158202
const controllerMessenger = new ControllerMessenger<
159203
never,
160-
FooControllerEvent
204+
FooControllerEvent | QuzControllerEvent
161205
>();
162-
const fooControllerMessenger = controllerMessenger.getRestricted({
206+
const fooMessenger = controllerMessenger.getRestricted<
207+
'FooController',
208+
never,
209+
never
210+
>({
163211
name: 'FooController',
164212
});
165-
const fooController = new FooController(fooControllerMessenger);
213+
const quzMessenger = controllerMessenger.getRestricted<
214+
'QuzController',
215+
never,
216+
never
217+
>({
218+
name: 'QuzController',
219+
});
220+
const fooController = new FooController(fooMessenger);
221+
const quzController = new QuzController(quzMessenger);
166222

167223
const composableControllerMessenger = controllerMessenger.getRestricted({
168224
name: 'ComposableController',
169-
allowedEvents: ['FooController:stateChange'],
225+
allowedEvents: [
226+
'FooController:stateChange',
227+
'QuzController:stateChange',
228+
],
170229
});
171230
const composableController = new ComposableController({
172-
controllers: [fooController],
231+
controllers: [fooController, quzController],
173232
messenger: composableControllerMessenger,
174233
});
175234
expect(composableController.state).toStrictEqual({
176235
FooController: { foo: 'foo' },
236+
QuzController: { quz: 'quz' },
177237
});
178238
});
179239

packages/composable-controller/src/ComposableController.ts

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import type {
33
BaseState,
44
BaseConfig,
55
RestrictedControllerMessenger,
6+
EventConstraint,
67
StateMetadata,
78
StateConstraint,
9+
ControllerStateChangeEvent,
810
} from '@metamask/base-controller';
911
import type { Patch } from 'immer';
1012

@@ -113,13 +115,44 @@ export type ComposableControllerMessenger = RestrictedControllerMessenger<
113115
AllowedEvents['type']
114116
>;
115117

118+
type GetStateChangeEvents<Controller extends ControllerInstance> =
119+
Controller extends ControllerInstance
120+
? Controller extends BaseControllerV1Instance
121+
? {
122+
type: `${Controller['name']}:stateChange`;
123+
payload: [Controller['state'], Patch[]];
124+
}
125+
: ControllerStateChangeEvent<Controller['name'], Controller['state']>
126+
: never;
127+
116128
/**
117129
* Controller that can be used to compose multiple controllers together.
118130
*/
119-
export class ComposableController extends BaseController<
131+
export class ComposableController<
132+
ChildControllers extends ControllerInstance,
133+
ComposedControllerState extends ComposableControllerState = {
134+
[P in ChildControllers as P['name']]: P['state'];
135+
},
136+
ComposedControllerStateChangeEvent extends EventConstraint & {
137+
type: `${typeof controllerName}:stateChange`;
138+
} = ControllerStateChangeEvent<
139+
typeof controllerName,
140+
ComposedControllerState
141+
>,
142+
ChildControllersStateChangeEvents extends EventConstraint & {
143+
type: `${string}:stateChange`;
144+
} = GetStateChangeEvents<ChildControllers>,
145+
ComposedControllerMessenger extends ComposableControllerMessenger = RestrictedControllerMessenger<
146+
typeof controllerName,
147+
never,
148+
ComposedControllerStateChangeEvent | ChildControllersStateChangeEvents,
149+
never,
150+
ChildControllersStateChangeEvents['type']
151+
>,
152+
> extends BaseController<
120153
typeof controllerName,
121-
ComposableControllerState,
122-
ComposableControllerMessenger
154+
ComposedControllerState,
155+
ComposedControllerMessenger
123156
> {
124157
/**
125158
* Creates a ComposableController instance.
@@ -133,29 +166,29 @@ export class ComposableController extends BaseController<
133166
controllers,
134167
messenger,
135168
}: {
136-
controllers: ControllerInstance[];
137-
messenger: ComposableControllerMessenger;
169+
controllers: ChildControllers[];
170+
messenger: ComposedControllerMessenger;
138171
}) {
139172
if (messenger === undefined) {
140173
throw new Error(`Messaging system is required`);
141174
}
142175

143176
super({
144177
name: controllerName,
145-
metadata: controllers.reduce<StateMetadata<ComposableControllerState>>(
178+
metadata: controllers.reduce<StateMetadata<ComposedControllerState>>(
146179
(metadata, controller) => ({
147180
...metadata,
148181
[controller.name]: isBaseController(controller)
149182
? controller.metadata
150183
: { persist: true, anonymous: true },
151184
}),
152-
{},
185+
{} as never,
153186
),
154-
state: controllers.reduce<ComposableControllerState>(
187+
state: controllers.reduce<ComposedControllerState>(
155188
(state, controller) => {
156189
return { ...state, [controller.name]: controller.state };
157190
},
158-
{},
191+
{} as never,
159192
),
160193
messenger,
161194
});
@@ -179,10 +212,11 @@ export class ComposableController extends BaseController<
179212
const { name } = controller;
180213
if (isBaseControllerV1(controller)) {
181214
controller.subscribe((childState) => {
182-
this.update((state) => ({
183-
...state,
184-
[name]: childState,
185-
}));
215+
this.update((state) => {
216+
Object.assign(state, {
217+
[name]: childState,
218+
});
219+
});
186220
});
187221
}
188222
if (
@@ -191,11 +225,12 @@ export class ComposableController extends BaseController<
191225
) {
192226
this.messagingSystem.subscribe(
193227
`${name}:stateChange`,
194-
(childState: StateConstraint) => {
195-
this.update((state) => ({
196-
...state,
197-
[name]: childState,
198-
}));
228+
(childState: Record<string, unknown>) => {
229+
this.update((state) => {
230+
Object.assign(state, {
231+
[name]: childState,
232+
});
233+
});
199234
},
200235
);
201236
}

0 commit comments

Comments
 (0)