Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | 36x 36x 36x 51x 51x 26x 2x 3x | import type { Logger } from '@metamask/logger';
import type { Json } from '@metamask/utils';
import type { ControllerStorage } from './storage/controller-storage.ts';
/**
* Base type for controller methods.
* Controllers expose their public API through a methods object.
*/
export type ControllerMethods = Record<string, (...args: never[]) => unknown>;
/**
* Configuration passed to all controllers during initialization.
*/
export type ControllerConfig = {
logger: Logger;
};
/**
* Abstract base class for controllers.
*
* Provides state management via ControllerStorage with:
* - Synchronous state access via `this.state`
* - Async state updates via `this.update()`
* - Automatic persistence handled by storage layer
*
* Subclasses must:
* - Call `harden(this)` at the end of their constructor
*
* @template ControllerName - Literal string type for the controller name
* @template State - The state object shape (must be JSON-serializable)
* @template Methods - The public method interface
*
* @example
* ```typescript
* class MyController extends Controller<'MyController', MyState, MyMethods> {
* private constructor(storage: ControllerStorage<MyState>, logger: Logger) {
* super('MyController', storage, logger);
* harden(this);
* }
*
* static create(config: ControllerConfig, deps: MyDeps): MyMethods {
* const controller = new MyController(deps.storage, config.logger);
* return controller.makeFacet();
* }
*
* makeFacet(): MyMethods {
* return makeDefaultExo('MyController', { ... });
* }
* }
* ```
*/
export abstract class Controller<
ControllerName extends string,
State extends Record<string, Json>,
Methods extends ControllerMethods,
> {
readonly #name: ControllerName;
readonly #storage: ControllerStorage<State>;
readonly #logger: Logger;
/**
* Protected constructor - subclasses must call this via super().
*
* @param name - Controller name for debugging/logging.
* @param storage - ControllerStorage instance for state management.
* @param logger - Logger instance.
*/
protected constructor(
name: ControllerName,
storage: ControllerStorage<State>,
logger: Logger,
) {
this.#name = name;
this.#storage = storage;
this.#logger = logger;
// Note: Subclass must call harden(this) after its own initialization
}
/**
* Controller name for debugging/logging.
*
* @returns The controller name.
*/
protected get name(): ControllerName {
return this.#name;
}
/**
* Current state (readonly).
* Provides synchronous access to in-memory state.
*
* @returns The current readonly state.
*/
protected get state(): Readonly<State> {
return this.#storage.state;
}
/**
* Logger instance for this controller.
*
* @returns The logger instance.
*/
protected get logger(): Logger {
return this.#logger;
}
/**
* Update state using an immer producer function.
* State is updated synchronously in memory.
* Persistence is handled automatically by the storage layer (debounced).
*
* @param producer - Function that mutates a draft of the state.
*/
protected update(producer: (draft: State) => void): void {
this.#storage.update(producer);
}
/**
* Clear storage and reset to default state.
*/
clearState(): void {
this.#storage.clear();
}
/**
* Returns the hardened exo with public methods.
* Subclasses implement this to define their public interface.
*
* @returns A hardened exo object with the controller's public methods.
*/
abstract makeFacet(): Methods;
}
harden(Controller);
|