mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 17:53:39 +00:00
Rework derived state (#7290)
* Remove derived state from state classes * Create provider for derived state Derived state is automatically stored to memory storage, but can be derived from any observable. * Fixup state provider method definitions * Test `DefaultDerivedState` * remove implementation notes * Write docs for derived state * fixup derived state provider types * Implement buffered delayUntil operator * Move state types to a common module * Move mock ports to centra location * Alias DerivedStateDependency type * Add dependencies to browser * Prefer internal rxjs operators for ref counting * WIP * Ensure complete on subjects * Foreground/background messaging for browser Defers work for browser to the background * Test foreground port behaviors * Inject foreground and background derived state services * remove unnecessary class field * Adhere to required options * Add dderived state to CLI * Prefer type definition in type parameters to options * Prefer instance method * Implements factory methods for common uses * Remove nothing test * Remove share subject reference Share manages connector subjects internally and will reuse them until refcount is 0 and the cleanup time has passed. Saving our own reference just risks memory leaks without real testability benefits. * Fix interaction state
This commit is contained in:
131
libs/common/src/platform/state/derive-definition.ts
Normal file
131
libs/common/src/platform/state/derive-definition.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { DerivedStateDependencies, ShapeToInstances, StorageKey } from "../../types/state";
|
||||
|
||||
import { KeyDefinition } from "./key-definition";
|
||||
import { StateDefinition } from "./state-definition";
|
||||
|
||||
declare const depShapeMarker: unique symbol;
|
||||
/**
|
||||
* A set of options for customizing the behavior of a {@link DeriveDefinition}
|
||||
*/
|
||||
type DeriveDefinitionOptions<TFrom, TTo, TDeps extends DerivedStateDependencies = never> = {
|
||||
/**
|
||||
* A function to use to convert values from TFrom to TTo. This is called on each emit of the parent state observable
|
||||
* and the resulting value will be emitted from the derived state observable.
|
||||
*
|
||||
* @param from Populated with the latest emission from the parent state observable.
|
||||
* @param deps Populated with the dependencies passed into the constructor of the derived state.
|
||||
* These are constant for the lifetime of the derived state.
|
||||
* @returns The derived state value or a Promise that resolves to the derived state value.
|
||||
*/
|
||||
derive: (from: TFrom, deps: ShapeToInstances<TDeps>) => TTo | Promise<TTo>;
|
||||
/**
|
||||
* A function to use to safely convert your type from json to your expected type.
|
||||
*
|
||||
* **Important:** Your data may be serialized/deserialized at any time and this
|
||||
* callback needs to be able to faithfully re-initialize from the JSON object representation of your type.
|
||||
*
|
||||
* @param jsonValue The JSON object representation of your state.
|
||||
* @returns The fully typed version of your state.
|
||||
*/
|
||||
deserializer: (serialized: Jsonify<TTo>) => TTo;
|
||||
/**
|
||||
* An object defining the dependencies of the derive function. The keys of the object are the names of the dependencies
|
||||
* and the values are the types of the dependencies.
|
||||
*
|
||||
* for example:
|
||||
* ```
|
||||
* {
|
||||
* myService: MyService,
|
||||
* myOtherService: MyOtherService,
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
[depShapeMarker]?: TDeps;
|
||||
/**
|
||||
* The number of milliseconds to wait before cleaning up the state after the last subscriber has unsubscribed.
|
||||
* Defaults to 1000ms.
|
||||
*/
|
||||
cleanupDelayMs?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* DeriveDefinitions describe state derived from another observable, the value type of which is given by `TFrom`.
|
||||
*
|
||||
* The StateDefinition is used to describe the domain of the state, and the DeriveDefinition
|
||||
* sub-divides that domain into specific keys. These keys are used to cache data in memory and enables derived state to
|
||||
* be calculated once regardless of multiple execution contexts.
|
||||
*/
|
||||
|
||||
export class DeriveDefinition<TFrom, TTo, TDeps extends DerivedStateDependencies> {
|
||||
/**
|
||||
* Creates a new instance of a DeriveDefinition. Derived state is always stored in memory, so the storage location
|
||||
* defined in @link{StateDefinition} is ignored.
|
||||
*
|
||||
* @param stateDefinition The state definition for which this key belongs to.
|
||||
* @param uniqueDerivationName The name of the key, this should be unique per domain.
|
||||
* @param options A set of options to customize the behavior of {@link DeriveDefinition}.
|
||||
* @param options.derive A function to use to convert values from TFrom to TTo. This is called on each emit of the parent state observable
|
||||
* and the resulting value will be emitted from the derived state observable.
|
||||
* @param options.cleanupDelayMs The number of milliseconds to wait before cleaning up the state after the last subscriber has unsubscribed.
|
||||
* Defaults to 1000ms.
|
||||
* @param options.dependencyShape An object defining the dependencies of the derive function. The keys of the object are the names of the dependencies
|
||||
* and the values are the types of the dependencies.
|
||||
* for example:
|
||||
* ```
|
||||
* {
|
||||
* myService: MyService,
|
||||
* myOtherService: MyOtherService,
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param options.deserializer A function to use to safely convert your type from json to your expected type.
|
||||
* Your data may be serialized/deserialized at any time and this needs callback needs to be able to faithfully re-initialize
|
||||
* from the JSON object representation of your type.
|
||||
*/
|
||||
constructor(
|
||||
readonly stateDefinition: StateDefinition,
|
||||
readonly uniqueDerivationName: string,
|
||||
readonly options: DeriveDefinitionOptions<TFrom, TTo, TDeps>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Factory that produces a {@link DeriveDefinition} from a {@link KeyDefinition} and a set of options. The returned
|
||||
* definition will have the same key as the given key definition, but will not collide with it in storage, even if
|
||||
* they both reside in memory.
|
||||
* @param keyDefinition
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
static from<TFrom, TTo, TDeps extends DerivedStateDependencies = never>(
|
||||
keyDefinition: KeyDefinition<TFrom>,
|
||||
options: DeriveDefinitionOptions<TFrom, TTo, TDeps>,
|
||||
) {
|
||||
return new DeriveDefinition(keyDefinition.stateDefinition, keyDefinition.key, options);
|
||||
}
|
||||
|
||||
get derive() {
|
||||
return this.options.derive;
|
||||
}
|
||||
|
||||
deserialize(serialized: Jsonify<TTo>): TTo {
|
||||
return this.options.deserializer(serialized);
|
||||
}
|
||||
|
||||
get cleanupDelayMs() {
|
||||
return this.options.cleanupDelayMs < 0 ? 0 : this.options.cleanupDelayMs ?? 1000;
|
||||
}
|
||||
|
||||
buildCacheKey(): string {
|
||||
return `derived_${this.stateDefinition.name}_${this.uniqueDerivationName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link StorageKey} that points to the data for the given derived definition.
|
||||
* @returns A key that is ready to be used in a storage service to get data.
|
||||
*/
|
||||
get storageKey(): StorageKey {
|
||||
return `derived_${this.stateDefinition.name}_${this.uniqueDerivationName}` as StorageKey;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user