From c6f7e0230166e4dddb5153497ed2114aa2b29509 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Wed, 21 Aug 2024 08:39:30 -0400 Subject: [PATCH] Move composable-controller utility types to base-controller (#4581) ## Explanation This commit moves `BaseController`-related types and functions in `@metamask/composable-controller` to `@metamask/base-controller`. Because applying these changes requires a concurrent major update of `@metamask/base-controller`, this commit will be excluded from `@metamask/composable-controller@8.0.0` (https://github.com/MetaMask/core/pull/4467), so that complications can be avoided while applying `8.0.0` to mobile. ## References - Blocked by https://github.com/MetaMask/core/pull/4467 - Blocked by https://github.com/MetaMask/metamask-mobile/pull/10441 ## Changelog ### `@metamask/base-controller` (minor) ### Added - Migrate from `@metamask/composable-controller@8.0.0` into `@metamask/base-controller`: types `LegacyControllerStateConstraint`, `RestrictedControllerMessengerConstraint` and type guard functions `isBaseController`, `isBaseControllerV1` ([#4581](https://github.com/MetaMask/core/pull/4581)) - Add and export types `ControllerInstance`, `BaseControllerInstance`, `StateDeriverConstraint`, `StateMetadataConstraint`, `StatePropertyMetadataConstraint`, `BaseControllerV1Instance`, `ConfigConstraintV1`, `StateConstraintV1` ([#4581](https://github.com/MetaMask/core/pull/4581)) ### `@metamask/composable-controller` (major) ### Removed - **BREAKING:** Remove exports for types `LegacyControllerStateConstraint`, `RestrictedControllerMessengerConstraint`, and type guard functions `isBaseController`, `isBaseControllerV1` ([#4467](https://github.com/MetaMask/core/pull/4467)) - These have been migrated to `@metamask/base-controller@6.2.0`. ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've highlighted breaking changes using the "BREAKING" category above as appropriate --- packages/base-controller/jest.config.js | 2 +- packages/base-controller/package.json | 1 + .../src/BaseControllerV1.test.ts | 47 +++++- .../base-controller/src/BaseControllerV1.ts | 51 ++++++ .../src/BaseControllerV2.test.ts | 43 ++++- .../base-controller/src/BaseControllerV2.ts | 87 +++++++++- .../src/RestrictedControllerMessenger.ts | 17 ++ packages/base-controller/src/index.ts | 37 ++++- packages/base-controller/tsconfig.json | 3 + packages/composable-controller/package.json | 1 - .../src/ComposableController.ts | 153 ++---------------- packages/composable-controller/src/index.ts | 8 +- yarn.lock | 2 +- 13 files changed, 284 insertions(+), 168 deletions(-) diff --git a/packages/base-controller/jest.config.js b/packages/base-controller/jest.config.js index 9392b3b234..ca08413339 100644 --- a/packages/base-controller/jest.config.js +++ b/packages/base-controller/jest.config.js @@ -17,7 +17,7 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 96.29, + branches: 100, functions: 100, lines: 100, statements: 100, diff --git a/packages/base-controller/package.json b/packages/base-controller/package.json index 16208fd329..1a5bc1691a 100644 --- a/packages/base-controller/package.json +++ b/packages/base-controller/package.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@metamask/auto-changelog": "^3.4.4", + "@metamask/json-rpc-engine": "^9.0.2", "@types/jest": "^27.4.1", "@types/sinon": "^9.0.10", "deepmerge": "^4.2.2", diff --git a/packages/base-controller/src/BaseControllerV1.test.ts b/packages/base-controller/src/BaseControllerV1.test.ts index eb3d64c846..f268f0f4a3 100644 --- a/packages/base-controller/src/BaseControllerV1.test.ts +++ b/packages/base-controller/src/BaseControllerV1.test.ts @@ -1,18 +1,61 @@ +import { JsonRpcEngine } from '@metamask/json-rpc-engine'; import * as sinon from 'sinon'; import type { BaseConfig, BaseState } from './BaseControllerV1'; -import { BaseControllerV1 as BaseController } from './BaseControllerV1'; +import { + BaseControllerV1 as BaseController, + isBaseControllerV1, +} from './BaseControllerV1'; +import type { + CountControllerAction, + CountControllerEvent, +} from './BaseControllerV2.test'; +import { + CountController, + countControllerName, + countControllerStateMetadata, + getCountMessenger, +} from './BaseControllerV2.test'; +import { ControllerMessenger } from './ControllerMessenger'; const STATE = { name: 'foo' }; const CONFIG = { disabled: true }; -class TestController extends BaseController { +// eslint-disable-next-line jest/no-export +export class TestController extends BaseController { constructor(config?: BaseConfig, state?: BaseState) { super(config, state); this.initialize(); } } +describe('isBaseControllerV1', () => { + it('should return false if passed a V1 controller', () => { + const controller = new TestController(); + expect(isBaseControllerV1(controller)).toBe(true); + }); + + it('should return false if passed a V2 controller', () => { + const controllerMessenger = new ControllerMessenger< + CountControllerAction, + CountControllerEvent + >(); + const controller = new CountController({ + messenger: getCountMessenger(controllerMessenger), + name: countControllerName, + state: { count: 0 }, + metadata: countControllerStateMetadata, + }); + expect(isBaseControllerV1(controller)).toBe(false); + }); + + it('should return false if passed a non-controller', () => { + const notController = new JsonRpcEngine(); + // @ts-expect-error Intentionally passing invalid input to test runtime behavior + expect(isBaseControllerV1(notController)).toBe(false); + }); +}); + describe('BaseController', () => { afterEach(() => { sinon.restore(); diff --git a/packages/base-controller/src/BaseControllerV1.ts b/packages/base-controller/src/BaseControllerV1.ts index 23e4596275..97843f642e 100644 --- a/packages/base-controller/src/BaseControllerV1.ts +++ b/packages/base-controller/src/BaseControllerV1.ts @@ -1,3 +1,34 @@ +import type { PublicInterface } from '@metamask/utils'; + +import type { ControllerInstance } from './BaseControllerV2'; + +/** + * Determines if the given controller is an instance of `BaseControllerV1` + * + * @param controller - Controller instance to check + * @returns True if the controller is an instance of `BaseControllerV1` + */ +export function isBaseControllerV1( + controller: ControllerInstance, +): controller is BaseControllerV1Instance { + return ( + 'name' in controller && + typeof controller.name === 'string' && + 'config' in controller && + typeof controller.config === 'object' && + 'defaultConfig' in controller && + typeof controller.defaultConfig === 'object' && + 'state' in controller && + typeof controller.state === 'object' && + 'defaultState' in controller && + typeof controller.defaultState === 'object' && + 'disabled' in controller && + typeof controller.disabled === 'boolean' && + 'subscribe' in controller && + typeof controller.subscribe === 'function' + ); +} + /** * State change callbacks */ @@ -31,6 +62,26 @@ export interface BaseState { name?: string; } +/** + * The narrowest supertype for `BaseControllerV1` config objects. + * This type can be assigned to any `BaseControllerV1` config object. + */ +export type ConfigConstraint = BaseConfig & object; + +/** + * The narrowest supertype for `BaseControllerV1` state objects. + * This type can be assigned to any `BaseControllerV1` state object. + */ +export type StateConstraint = BaseState & object; + +/** + * The widest subtype of all controller instances that extend from `BaseControllerV1`. + * Any `BaseControllerV1` instance can be assigned to this type. + */ +export type BaseControllerV1Instance = PublicInterface< + BaseControllerV1 +>; + /** * @deprecated This class has been renamed to BaseControllerV1 and is no longer recommended for use for controllers. Please use BaseController (formerly BaseControllerV2) instead. * diff --git a/packages/base-controller/src/BaseControllerV2.test.ts b/packages/base-controller/src/BaseControllerV2.test.ts index a3d878f857..9227505090 100644 --- a/packages/base-controller/src/BaseControllerV2.test.ts +++ b/packages/base-controller/src/BaseControllerV2.test.ts @@ -1,6 +1,9 @@ +/* eslint-disable jest/no-export */ import type { Draft, Patch } from 'immer'; import * as sinon from 'sinon'; +import { JsonRpcEngine } from '../../json-rpc-engine/src'; +import { TestController } from './BaseControllerV1.test'; import type { ControllerGetStateAction, ControllerStateChangeEvent, @@ -9,27 +12,28 @@ import { BaseController, getAnonymizedState, getPersistentState, + isBaseController, } from './BaseControllerV2'; import { ControllerMessenger } from './ControllerMessenger'; import type { RestrictedControllerMessenger } from './RestrictedControllerMessenger'; -const countControllerName = 'CountController'; +export const countControllerName = 'CountController'; type CountControllerState = { count: number; }; -type CountControllerAction = ControllerGetStateAction< +export type CountControllerAction = ControllerGetStateAction< typeof countControllerName, CountControllerState >; -type CountControllerEvent = ControllerStateChangeEvent< +export type CountControllerEvent = ControllerStateChangeEvent< typeof countControllerName, CountControllerState >; -const countControllerStateMetadata = { +export const countControllerStateMetadata = { count: { persist: true, anonymous: true, @@ -50,7 +54,7 @@ type CountMessenger = RestrictedControllerMessenger< * @param controllerMessenger - The controller messenger. * @returns A restricted controller messenger for the Count controller. */ -function getCountMessenger( +export function getCountMessenger( controllerMessenger?: ControllerMessenger< CountControllerAction, CountControllerEvent @@ -69,7 +73,7 @@ function getCountMessenger( }); } -class CountController extends BaseController< +export class CountController extends BaseController< typeof countControllerName, CountControllerState, CountMessenger @@ -177,6 +181,33 @@ class MessagesController extends BaseController< } } +describe('isBaseController', () => { + it('should return true if passed a V2 controller', () => { + const controllerMessenger = new ControllerMessenger< + CountControllerAction, + CountControllerEvent + >(); + const controller = new CountController({ + messenger: getCountMessenger(controllerMessenger), + name: countControllerName, + state: { count: 0 }, + metadata: countControllerStateMetadata, + }); + expect(isBaseController(controller)).toBe(true); + }); + + it('should return false if passed a V1 controller', () => { + const controller = new TestController(); + expect(isBaseController(controller)).toBe(false); + }); + + it('should return false if passed a non-controller', () => { + const notController = new JsonRpcEngine(); + // @ts-expect-error Intentionally passing invalid input to test runtime behavior + expect(isBaseController(notController)).toBe(false); + }); +}); + describe('BaseController', () => { afterEach(() => { sinon.restore(); diff --git a/packages/base-controller/src/BaseControllerV2.ts b/packages/base-controller/src/BaseControllerV2.ts index 00ea682e5f..355d156689 100644 --- a/packages/base-controller/src/BaseControllerV2.ts +++ b/packages/base-controller/src/BaseControllerV2.ts @@ -1,12 +1,38 @@ -import type { Json } from '@metamask/utils'; +import type { Json, PublicInterface } from '@metamask/utils'; import { enablePatches, produceWithPatches, applyPatches, freeze } from 'immer'; import type { Draft, Patch } from 'immer'; +import type { + BaseControllerV1Instance, + StateConstraint as StateConstraintV1, +} from './BaseControllerV1'; import type { ActionConstraint, EventConstraint } from './ControllerMessenger'; -import type { RestrictedControllerMessenger } from './RestrictedControllerMessenger'; +import type { + RestrictedControllerMessenger, + RestrictedControllerMessengerConstraint, +} from './RestrictedControllerMessenger'; enablePatches(); +/** + * Determines if the given controller is an instance of `BaseController` + * + * @param controller - Controller instance to check + * @returns True if the controller is an instance of `BaseController` + */ +export function isBaseController( + controller: ControllerInstance, +): controller is BaseControllerInstance { + return ( + 'name' in controller && + typeof controller.name === 'string' && + 'state' in controller && + typeof controller.state === 'object' && + 'metadata' in controller && + typeof controller.metadata === 'object' + ); +} + /** * A type that constrains the state of all controllers. * @@ -14,6 +40,14 @@ enablePatches(); */ export type StateConstraint = Record; +/** + * A universal supertype for the controller state object, encompassing both `BaseControllerV1` and `BaseControllerV2` state. + */ +// TODO: Remove once BaseControllerV2 migrations are completed for all controllers. +export type LegacyControllerStateConstraint = + | StateConstraintV1 + | StateConstraint; + /** * A state change listener. * @@ -72,6 +106,55 @@ export type StatePropertyMetadata = { anonymous: boolean | StateDeriver; }; +/** + * A universal supertype of `StateDeriver` types. + * This type can be assigned to any `StateDeriver` type. + */ +export type StateDeriverConstraint = (value: never) => Json; + +/** + * A universal supertype of `StatePropertyMetadata` types. + * This type can be assigned to any `StatePropertyMetadata` type. + */ +export type StatePropertyMetadataConstraint = { + [P in 'anonymous' | 'persist']: boolean | StateDeriverConstraint; +}; + +/** + * A universal supertype of `StateMetadata` types. + * This type can be assigned to any `StateMetadata` type. + */ +export type StateMetadataConstraint = Record< + string, + StatePropertyMetadataConstraint +>; + +/** + * The widest subtype of all controller instances that inherit from `BaseController` (formerly `BaseControllerV2`). + * Any `BaseController` subclass instance can be assigned to this type. + */ +export type BaseControllerInstance = Omit< + PublicInterface< + BaseController< + string, + StateConstraint, + RestrictedControllerMessengerConstraint + > + >, + 'metadata' +> & { + metadata: StateMetadataConstraint; +}; + +/** + * A widest subtype of all controller instances that inherit from `BaseController` (formerly `BaseControllerV2`) or `BaseControllerV1`. + * Any `BaseController` or `BaseControllerV1` subclass instance can be assigned to this type. + */ +// TODO: Remove once BaseControllerV2 migrations are completed for all controllers. +export type ControllerInstance = + | BaseControllerV1Instance + | BaseControllerInstance; + export type ControllerGetStateAction< ControllerName extends string, ControllerState extends StateConstraint, diff --git a/packages/base-controller/src/RestrictedControllerMessenger.ts b/packages/base-controller/src/RestrictedControllerMessenger.ts index e3a077cff7..62c8ae0f8e 100644 --- a/packages/base-controller/src/RestrictedControllerMessenger.ts +++ b/packages/base-controller/src/RestrictedControllerMessenger.ts @@ -13,6 +13,23 @@ import type { SelectorFunction, } from './ControllerMessenger'; +/** + * A universal supertype of all `RestrictedControllerMessenger` instances. + * This type can be assigned to any `RestrictedControllerMessenger` type. + * + * @template ControllerName - Name of the controller. Optionally can be used to + * narrow this type to a constraint for the messenger of a specific controller. + */ +export type RestrictedControllerMessengerConstraint< + ControllerName extends string = string, +> = RestrictedControllerMessenger< + ControllerName, + ActionConstraint, + EventConstraint, + string, + string +>; + /** * A restricted controller messenger. * diff --git a/packages/base-controller/src/index.ts b/packages/base-controller/src/index.ts index 3eeb5ef47b..a4eab5b572 100644 --- a/packages/base-controller/src/index.ts +++ b/packages/base-controller/src/index.ts @@ -1,11 +1,24 @@ -export type { BaseConfig, BaseState, Listener } from './BaseControllerV1'; -export { BaseControllerV1 } from './BaseControllerV1'; export type { + BaseConfig, + BaseControllerV1Instance, + BaseState, + ConfigConstraint as ConfigConstraintV1, + Listener, + StateConstraint as StateConstraintV1, +} from './BaseControllerV1'; +export { BaseControllerV1, isBaseControllerV1 } from './BaseControllerV1'; +export type { + BaseControllerInstance, + ControllerInstance, Listener as ListenerV2, StateConstraint, + LegacyControllerStateConstraint, StateDeriver, + StateDeriverConstraint, StateMetadata, + StateMetadataConstraint, StatePropertyMetadata, + StatePropertyMetadataConstraint, ControllerGetStateAction, ControllerStateChangeEvent, } from './BaseControllerV2'; @@ -13,6 +26,22 @@ export { BaseController, getAnonymizedState, getPersistentState, + isBaseController, } from './BaseControllerV2'; -export * from './ControllerMessenger'; -export * from './RestrictedControllerMessenger'; +export type { + ActionHandler, + ExtractActionParameters, + ExtractActionResponse, + ExtractEventHandler, + ExtractEventPayload, + GenericEventHandler, + SelectorFunction, + ActionConstraint, + EventConstraint, + NamespacedBy, + NotNamespacedBy, + NamespacedName, +} from './ControllerMessenger'; +export { ControllerMessenger } from './ControllerMessenger'; +export type { RestrictedControllerMessengerConstraint } from './RestrictedControllerMessenger'; +export { RestrictedControllerMessenger } from './RestrictedControllerMessenger'; diff --git a/packages/base-controller/tsconfig.json b/packages/base-controller/tsconfig.json index 580d6934db..93d58af655 100644 --- a/packages/base-controller/tsconfig.json +++ b/packages/base-controller/tsconfig.json @@ -6,6 +6,9 @@ "references": [ { "path": "../controller-utils" + }, + { + "path": "../json-rpc-engine" } ], "include": ["../../types", "./src", "./tests"] diff --git a/packages/composable-controller/package.json b/packages/composable-controller/package.json index 4d84d2c9f6..a3300c2113 100644 --- a/packages/composable-controller/package.json +++ b/packages/composable-controller/package.json @@ -46,7 +46,6 @@ "devDependencies": { "@metamask/auto-changelog": "^3.4.4", "@metamask/json-rpc-engine": "^9.0.2", - "@metamask/utils": "^9.1.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "immer": "^9.0.6", diff --git a/packages/composable-controller/src/ComposableController.ts b/packages/composable-controller/src/ComposableController.ts index 684dffdda9..e93cbde984 100644 --- a/packages/composable-controller/src/ComposableController.ts +++ b/packages/composable-controller/src/ComposableController.ts @@ -1,16 +1,17 @@ import type { - ActionConstraint, - BaseConfig, - BaseControllerV1, - BaseState, - EventConstraint, RestrictedControllerMessenger, StateConstraint, + StateConstraintV1, StateMetadata, ControllerStateChangeEvent, + LegacyControllerStateConstraint, + ControllerInstance, +} from '@metamask/base-controller'; +import { + BaseController, + isBaseController, + isBaseControllerV1, } from '@metamask/base-controller'; -import { BaseController } from '@metamask/base-controller'; -import type { Json, PublicInterface } from '@metamask/utils'; import type { Patch } from 'immer'; export const controllerName = 'ComposableController'; @@ -18,142 +19,6 @@ export const controllerName = 'ComposableController'; export const INVALID_CONTROLLER_ERROR = 'Invalid controller: controller must have a `messagingSystem` or be a class inheriting from `BaseControllerV1`.'; -/** - * A universal supertype for the `BaseControllerV1` state object. - */ -type ConfigConstraintV1 = BaseConfig & object; - -/** - * A universal supertype for the `BaseControllerV1` state object. - */ -type StateConstraintV1 = BaseState & object; - -/** - * A universal subtype of all controller instances that extend from `BaseControllerV1`. - * Any `BaseControllerV1` instance can be assigned to this type. - * - * Note that this type is not the widest subtype or narrowest supertype of all `BaseControllerV1` instances. - * This type is therefore unsuitable for general use as a type constraint, and is only intended for use within the ComposableController. - */ -type BaseControllerV1Instance = PublicInterface< - BaseControllerV1 ->; - -/** - * A universal supertype of functions that accept a piece of controller state and return some derivation of that state. - */ -type StateDeriverConstraint = (value: never) => Json; - -/** - * A universal supertype of metadata objects for individual state properties. - */ -type StatePropertyMetadataConstraint = { - [P in 'anonymous' | 'persist']: boolean | StateDeriverConstraint; -}; - -/** - * A universal supertype of state metadata objects. - */ -type StateMetadataConstraint = Record; - -/** - * A universal subtype of all controller instances that extend from `BaseController` (formerly `BaseControllerV2`). - * Any `BaseController` instance can be assigned to this type. - * - * Note that this type is not the widest subtype or narrowest supertype of all `BaseController` instances. - * This type is therefore unsuitable for general use as a type constraint, and is only intended for use within the ComposableController. - * - * For this reason, we only look for `BaseController` properties that we use in the ComposableController (name and state). - */ -type BaseControllerInstance = Omit< - PublicInterface< - BaseController< - string, - StateConstraint, - RestrictedControllerMessengerConstraint - > - >, - 'metadata' -> & { - metadata: StateMetadataConstraint; -}; - -/** - * A universal subtype of all controller instances that extend from `BaseController` (formerly `BaseControllerV2`) or `BaseControllerV1`. - * Any `BaseController` or `BaseControllerV1` instance can be assigned to this type. - * - * Note that this type is not the widest subtype or narrowest supertype of all `BaseController` and `BaseControllerV1` instances. - * This type is therefore unsuitable for general use as a type constraint, and is only intended for use within the ComposableController. - */ -type ControllerInstance = BaseControllerV1Instance | BaseControllerInstance; - -/** - * The narrowest supertype of all `RestrictedControllerMessenger` instances. - * - * @template ControllerName - Name of the controller. - * Optionally can be used to narrow the type to a specific controller. - */ -export type RestrictedControllerMessengerConstraint< - ControllerName extends string = string, -> = RestrictedControllerMessenger< - ControllerName, - ActionConstraint, - EventConstraint, - string, - string ->; - -/** - * Determines if the given controller is an instance of `BaseControllerV1` - * @param controller - Controller instance to check - * @returns True if the controller is an instance of `BaseControllerV1` - */ -export function isBaseControllerV1( - controller: ControllerInstance, -): controller is BaseControllerV1Instance { - return ( - 'name' in controller && - typeof controller.name === 'string' && - 'config' in controller && - typeof controller.config === 'object' && - 'defaultConfig' in controller && - typeof controller.defaultConfig === 'object' && - 'state' in controller && - typeof controller.state === 'object' && - 'defaultState' in controller && - typeof controller.defaultState === 'object' && - 'disabled' in controller && - typeof controller.disabled === 'boolean' && - 'subscribe' in controller && - typeof controller.subscribe === 'function' - ); -} - -/** - * Determines if the given controller is an instance of `BaseController` - * @param controller - Controller instance to check - * @returns True if the controller is an instance of `BaseController` - */ -export function isBaseController( - controller: ControllerInstance, -): controller is BaseControllerInstance { - return ( - 'name' in controller && - typeof controller.name === 'string' && - 'state' in controller && - typeof controller.state === 'object' && - 'metadata' in controller && - typeof controller.metadata === 'object' - ); -} - -/** - * A universal supertype for the controller state object, encompassing both `BaseControllerV1` and `BaseControllerV2` state. - */ -export type LegacyControllerStateConstraint = - | StateConstraintV1 - | StateConstraint; - /** * A universal supertype for the composable controller state object. * @@ -237,7 +102,7 @@ export type ChildControllerStateChangeEvents< * * @template ComposableControllerState - A type object that maps controller names to their state types. */ -type AllowedEvents< +export type AllowedEvents< ComposableControllerState extends ComposableControllerStateConstraint, > = ChildControllerStateChangeEvents; diff --git a/packages/composable-controller/src/index.ts b/packages/composable-controller/src/index.ts index fc00056334..8a0b5f5da2 100644 --- a/packages/composable-controller/src/index.ts +++ b/packages/composable-controller/src/index.ts @@ -3,11 +3,5 @@ export type { ComposableControllerStateChangeEvent, ComposableControllerEvents, ComposableControllerMessenger, - LegacyControllerStateConstraint, - RestrictedControllerMessengerConstraint, -} from './ComposableController'; -export { - ComposableController, - isBaseController, - isBaseControllerV1, } from './ComposableController'; +export { ComposableController } from './ComposableController'; diff --git a/yarn.lock b/yarn.lock index a86da0f5ce..fb7ac3eaae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2405,6 +2405,7 @@ __metadata: resolution: "@metamask/base-controller@workspace:packages/base-controller" dependencies: "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/json-rpc-engine": "npm:^9.0.2" "@metamask/utils": "npm:^9.1.0" "@types/jest": "npm:^27.4.1" "@types/sinon": "npm:^9.0.10" @@ -2487,7 +2488,6 @@ __metadata: "@metamask/auto-changelog": "npm:^3.4.4" "@metamask/base-controller": "npm:^6.0.3" "@metamask/json-rpc-engine": "npm:^9.0.2" - "@metamask/utils": "npm:^9.1.0" "@types/jest": "npm:^27.4.1" deepmerge: "npm:^4.2.2" immer: "npm:^9.0.6"