mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 05:13:29 +00:00
Require lifetime specification of user-scoped data (#8669)
* Require lifetime specification of user-scoped data * Decouple tests for different classes This coupling assumed constant interfaces with classes that isn't a guarantee and requires significant acrobatics to make types work, now that key definitions are not a consistent base. * Fix types
This commit is contained in:
@@ -15,8 +15,6 @@ import {
|
|||||||
DerivedStateProvider,
|
DerivedStateProvider,
|
||||||
UserKeyDefinition,
|
UserKeyDefinition,
|
||||||
} from "../src/platform/state";
|
} from "../src/platform/state";
|
||||||
// eslint-disable-next-line import/no-restricted-paths -- Needed to type check similarly to the real state providers
|
|
||||||
import { isUserKeyDefinition } from "../src/platform/state/user-key-definition";
|
|
||||||
import { UserId } from "../src/types/guid";
|
import { UserId } from "../src/types/guid";
|
||||||
import { DerivedStateDependencies } from "../src/types/state";
|
import { DerivedStateDependencies } from "../src/types/state";
|
||||||
|
|
||||||
@@ -71,37 +69,28 @@ export class FakeSingleUserStateProvider implements SingleUserStateProvider {
|
|||||||
mock = mock<SingleUserStateProvider>();
|
mock = mock<SingleUserStateProvider>();
|
||||||
establishedMocks: Map<string, FakeSingleUserState<unknown>> = new Map();
|
establishedMocks: Map<string, FakeSingleUserState<unknown>> = new Map();
|
||||||
states: Map<string, SingleUserState<unknown>> = new Map();
|
states: Map<string, SingleUserState<unknown>> = new Map();
|
||||||
get<T>(
|
get<T>(userId: UserId, userKeyDefinition: UserKeyDefinition<T>): SingleUserState<T> {
|
||||||
userId: UserId,
|
this.mock.get(userId, userKeyDefinition);
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
const cacheKey = `${userKeyDefinition.fullName}_${userKeyDefinition.stateDefinition.defaultStorageLocation}_${userId}`;
|
||||||
): SingleUserState<T> {
|
|
||||||
this.mock.get(userId, keyDefinition);
|
|
||||||
if (keyDefinition instanceof KeyDefinition) {
|
|
||||||
keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition);
|
|
||||||
}
|
|
||||||
const cacheKey = `${keyDefinition.fullName}_${keyDefinition.stateDefinition.defaultStorageLocation}_${userId}`;
|
|
||||||
let result = this.states.get(cacheKey);
|
let result = this.states.get(cacheKey);
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
let fake: FakeSingleUserState<T>;
|
let fake: FakeSingleUserState<T>;
|
||||||
// Look for established mock
|
// Look for established mock
|
||||||
if (this.establishedMocks.has(keyDefinition.key)) {
|
if (this.establishedMocks.has(userKeyDefinition.key)) {
|
||||||
fake = this.establishedMocks.get(keyDefinition.key) as FakeSingleUserState<T>;
|
fake = this.establishedMocks.get(userKeyDefinition.key) as FakeSingleUserState<T>;
|
||||||
} else {
|
} else {
|
||||||
fake = new FakeSingleUserState<T>(userId);
|
fake = new FakeSingleUserState<T>(userId);
|
||||||
}
|
}
|
||||||
fake.keyDefinition = keyDefinition;
|
fake.keyDefinition = userKeyDefinition;
|
||||||
result = fake;
|
result = fake;
|
||||||
this.states.set(cacheKey, result);
|
this.states.set(cacheKey, result);
|
||||||
}
|
}
|
||||||
return result as SingleUserState<T>;
|
return result as SingleUserState<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFake<T>(
|
getFake<T>(userId: UserId, userKeyDefinition: UserKeyDefinition<T>): FakeSingleUserState<T> {
|
||||||
userId: UserId,
|
return this.get(userId, userKeyDefinition) as FakeSingleUserState<T>;
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
|
||||||
): FakeSingleUserState<T> {
|
|
||||||
return this.get(userId, keyDefinition) as FakeSingleUserState<T>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mockFor<T>(userId: UserId, keyDefinitionKey: string, initialValue?: T): FakeSingleUserState<T> {
|
mockFor<T>(userId: UserId, keyDefinitionKey: string, initialValue?: T): FakeSingleUserState<T> {
|
||||||
@@ -122,28 +111,25 @@ export class FakeActiveUserStateProvider implements ActiveUserStateProvider {
|
|||||||
this.activeUserId$ = accountService.activeAccountSubject.asObservable().pipe(map((a) => a?.id));
|
this.activeUserId$ = accountService.activeAccountSubject.asObservable().pipe(map((a) => a?.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
get<T>(keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>): ActiveUserState<T> {
|
get<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T> {
|
||||||
if (keyDefinition instanceof KeyDefinition) {
|
const cacheKey = `${userKeyDefinition.fullName}_${userKeyDefinition.stateDefinition.defaultStorageLocation}`;
|
||||||
keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition);
|
|
||||||
}
|
|
||||||
const cacheKey = `${keyDefinition.fullName}_${keyDefinition.stateDefinition.defaultStorageLocation}`;
|
|
||||||
let result = this.states.get(cacheKey);
|
let result = this.states.get(cacheKey);
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
// Look for established mock
|
// Look for established mock
|
||||||
if (this.establishedMocks.has(keyDefinition.key)) {
|
if (this.establishedMocks.has(userKeyDefinition.key)) {
|
||||||
result = this.establishedMocks.get(keyDefinition.key);
|
result = this.establishedMocks.get(userKeyDefinition.key);
|
||||||
} else {
|
} else {
|
||||||
result = new FakeActiveUserState<T>(this.accountService);
|
result = new FakeActiveUserState<T>(this.accountService);
|
||||||
}
|
}
|
||||||
result.keyDefinition = keyDefinition;
|
result.keyDefinition = userKeyDefinition;
|
||||||
this.states.set(cacheKey, result);
|
this.states.set(cacheKey, result);
|
||||||
}
|
}
|
||||||
return result as ActiveUserState<T>;
|
return result as ActiveUserState<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFake<T>(keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>): FakeActiveUserState<T> {
|
getFake<T>(userKeyDefinition: UserKeyDefinition<T>): FakeActiveUserState<T> {
|
||||||
return this.get(keyDefinition) as FakeActiveUserState<T>;
|
return this.get(userKeyDefinition) as FakeActiveUserState<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
mockFor<T>(keyDefinitionKey: string, initialValue?: T): FakeActiveUserState<T> {
|
mockFor<T>(keyDefinitionKey: string, initialValue?: T): FakeActiveUserState<T> {
|
||||||
@@ -159,70 +145,56 @@ export class FakeActiveUserStateProvider implements ActiveUserStateProvider {
|
|||||||
|
|
||||||
export class FakeStateProvider implements StateProvider {
|
export class FakeStateProvider implements StateProvider {
|
||||||
mock = mock<StateProvider>();
|
mock = mock<StateProvider>();
|
||||||
getUserState$<T>(
|
getUserState$<T>(userKeyDefinition: UserKeyDefinition<T>, userId?: UserId): Observable<T> {
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
this.mock.getUserState$(userKeyDefinition, userId);
|
||||||
userId?: UserId,
|
|
||||||
): Observable<T> {
|
|
||||||
if (isUserKeyDefinition(keyDefinition)) {
|
|
||||||
this.mock.getUserState$(keyDefinition, userId);
|
|
||||||
} else {
|
|
||||||
this.mock.getUserState$(keyDefinition, userId);
|
|
||||||
}
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
return this.getUser<T>(userId, keyDefinition).state$;
|
return this.getUser<T>(userId, userKeyDefinition).state$;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getActive(keyDefinition).state$;
|
return this.getActive(userKeyDefinition).state$;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserStateOrDefault$<T>(
|
getUserStateOrDefault$<T>(
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
userKeyDefinition: UserKeyDefinition<T>,
|
||||||
config: { userId: UserId | undefined; defaultValue?: T },
|
config: { userId: UserId | undefined; defaultValue?: T },
|
||||||
): Observable<T> {
|
): Observable<T> {
|
||||||
const { userId, defaultValue = null } = config;
|
const { userId, defaultValue = null } = config;
|
||||||
if (isUserKeyDefinition(keyDefinition)) {
|
this.mock.getUserStateOrDefault$(userKeyDefinition, config);
|
||||||
this.mock.getUserStateOrDefault$(keyDefinition, config);
|
|
||||||
} else {
|
|
||||||
this.mock.getUserStateOrDefault$(keyDefinition, config);
|
|
||||||
}
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
return this.getUser<T>(userId, keyDefinition).state$;
|
return this.getUser<T>(userId, userKeyDefinition).state$;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.activeUserId$.pipe(
|
return this.activeUserId$.pipe(
|
||||||
take(1),
|
take(1),
|
||||||
switchMap((userId) =>
|
switchMap((userId) =>
|
||||||
userId != null ? this.getUser(userId, keyDefinition).state$ : of(defaultValue),
|
userId != null ? this.getUser(userId, userKeyDefinition).state$ : of(defaultValue),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserState<T>(
|
async setUserState<T>(
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
userKeyDefinition: UserKeyDefinition<T>,
|
||||||
value: T,
|
value: T,
|
||||||
userId?: UserId,
|
userId?: UserId,
|
||||||
): Promise<[UserId, T]> {
|
): Promise<[UserId, T]> {
|
||||||
await this.mock.setUserState(keyDefinition, value, userId);
|
await this.mock.setUserState(userKeyDefinition, value, userId);
|
||||||
if (userId) {
|
if (userId) {
|
||||||
return [userId, await this.getUser(userId, keyDefinition).update(() => value)];
|
return [userId, await this.getUser(userId, userKeyDefinition).update(() => value)];
|
||||||
} else {
|
} else {
|
||||||
return await this.getActive(keyDefinition).update(() => value);
|
return await this.getActive(userKeyDefinition).update(() => value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getActive<T>(keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>): ActiveUserState<T> {
|
getActive<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T> {
|
||||||
return this.activeUser.get(keyDefinition);
|
return this.activeUser.get(userKeyDefinition);
|
||||||
}
|
}
|
||||||
|
|
||||||
getGlobal<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> {
|
getGlobal<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> {
|
||||||
return this.global.get(keyDefinition);
|
return this.global.get(keyDefinition);
|
||||||
}
|
}
|
||||||
|
|
||||||
getUser<T>(
|
getUser<T>(userId: UserId, userKeyDefinition: UserKeyDefinition<T>): SingleUserState<T> {
|
||||||
userId: UserId,
|
return this.singleUser.get(userId, userKeyDefinition);
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
|
||||||
): SingleUserState<T> {
|
|
||||||
return this.singleUser.get(userId, keyDefinition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDerived<TFrom, TTo, TDeps extends DerivedStateDependencies>(
|
getDerived<TFrom, TTo, TDeps extends DerivedStateDependencies>(
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { Observable, distinctUntilChanged, map } from "rxjs";
|
|||||||
|
|
||||||
import { AccountService } from "../../../auth/abstractions/account.service";
|
import { AccountService } from "../../../auth/abstractions/account.service";
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import { KeyDefinition } from "../key-definition";
|
import { UserKeyDefinition } from "../user-key-definition";
|
||||||
import { UserKeyDefinition, isUserKeyDefinition } from "../user-key-definition";
|
|
||||||
import { ActiveUserState } from "../user-state";
|
import { ActiveUserState } from "../user-state";
|
||||||
import { ActiveUserStateProvider, SingleUserStateProvider } from "../user-state.provider";
|
import { ActiveUserStateProvider, SingleUserStateProvider } from "../user-state.provider";
|
||||||
|
|
||||||
@@ -23,11 +22,7 @@ export class DefaultActiveUserStateProvider implements ActiveUserStateProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get<T>(keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>): ActiveUserState<T> {
|
get<T>(keyDefinition: UserKeyDefinition<T>): ActiveUserState<T> {
|
||||||
if (!isUserKeyDefinition(keyDefinition)) {
|
|
||||||
keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other providers cache the creation of their corresponding `State` objects, this instance
|
// All other providers cache the creation of their corresponding `State` objects, this instance
|
||||||
// doesn't need to do that since it calls `SingleUserStateProvider` it will go through their caching
|
// doesn't need to do that since it calls `SingleUserStateProvider` it will go through their caching
|
||||||
// layer, because of that, the creation of this instance is quite simple and not worth caching.
|
// layer, because of that, the creation of this instance is quite simple and not worth caching.
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
import { KeyDefinition } from "../key-definition";
|
|
||||||
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
import { UserKeyDefinition, isUserKeyDefinition } from "../user-key-definition";
|
import { UserKeyDefinition } from "../user-key-definition";
|
||||||
import { SingleUserState } from "../user-state";
|
import { SingleUserState } from "../user-state";
|
||||||
import { SingleUserStateProvider } from "../user-state.provider";
|
import { SingleUserStateProvider } from "../user-state.provider";
|
||||||
|
|
||||||
@@ -16,13 +15,7 @@ export class DefaultSingleUserStateProvider implements SingleUserStateProvider {
|
|||||||
private readonly stateEventRegistrarService: StateEventRegistrarService,
|
private readonly stateEventRegistrarService: StateEventRegistrarService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get<T>(
|
get<T>(userId: UserId, keyDefinition: UserKeyDefinition<T>): SingleUserState<T> {
|
||||||
userId: UserId,
|
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
|
||||||
): SingleUserState<T> {
|
|
||||||
if (!isUserKeyDefinition(keyDefinition)) {
|
|
||||||
keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition);
|
|
||||||
}
|
|
||||||
const [location, storageService] = this.storageServiceProvider.get(
|
const [location, storageService] = this.storageServiceProvider.get(
|
||||||
keyDefinition.stateDefinition.defaultStorageLocation,
|
keyDefinition.stateDefinition.defaultStorageLocation,
|
||||||
keyDefinition.stateDefinition.storageLocationOverrides,
|
keyDefinition.stateDefinition.storageLocationOverrides,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { UserId } from "../../../types/guid";
|
|||||||
import { DeriveDefinition } from "../derive-definition";
|
import { DeriveDefinition } from "../derive-definition";
|
||||||
import { KeyDefinition } from "../key-definition";
|
import { KeyDefinition } from "../key-definition";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateDefinition } from "../state-definition";
|
||||||
|
import { UserKeyDefinition } from "../user-key-definition";
|
||||||
|
|
||||||
import { DefaultStateProvider } from "./default-state.provider";
|
import { DefaultStateProvider } from "./default-state.provider";
|
||||||
|
|
||||||
@@ -52,12 +53,12 @@ describe("DefaultStateProvider", () => {
|
|||||||
describe.each([
|
describe.each([
|
||||||
[
|
[
|
||||||
"getUserState$",
|
"getUserState$",
|
||||||
(keyDefinition: KeyDefinition<string>, userId?: UserId) =>
|
(keyDefinition: UserKeyDefinition<string>, userId?: UserId) =>
|
||||||
sut.getUserState$(keyDefinition, userId),
|
sut.getUserState$(keyDefinition, userId),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"getUserStateOrDefault$",
|
"getUserStateOrDefault$",
|
||||||
(keyDefinition: KeyDefinition<string>, userId?: UserId) =>
|
(keyDefinition: UserKeyDefinition<string>, userId?: UserId) =>
|
||||||
sut.getUserStateOrDefault$(keyDefinition, { userId: userId }),
|
sut.getUserStateOrDefault$(keyDefinition, { userId: userId }),
|
||||||
],
|
],
|
||||||
])(
|
])(
|
||||||
@@ -65,7 +66,7 @@ describe("DefaultStateProvider", () => {
|
|||||||
(
|
(
|
||||||
_testName: string,
|
_testName: string,
|
||||||
methodUnderTest: (
|
methodUnderTest: (
|
||||||
keyDefinition: KeyDefinition<string>,
|
keyDefinition: UserKeyDefinition<string>,
|
||||||
userId?: UserId,
|
userId?: UserId,
|
||||||
) => Observable<string>,
|
) => Observable<string>,
|
||||||
) => {
|
) => {
|
||||||
@@ -75,9 +76,14 @@ describe("DefaultStateProvider", () => {
|
|||||||
name: "name",
|
name: "name",
|
||||||
status: AuthenticationStatus.LoggedOut,
|
status: AuthenticationStatus.LoggedOut,
|
||||||
};
|
};
|
||||||
const keyDefinition = new KeyDefinition<string>(new StateDefinition("test", "disk"), "test", {
|
const keyDefinition = new UserKeyDefinition<string>(
|
||||||
deserializer: (s) => s,
|
new StateDefinition("test", "disk"),
|
||||||
});
|
"test",
|
||||||
|
{
|
||||||
|
deserializer: (s) => s,
|
||||||
|
clearOn: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it("should follow the specified user if userId is provided", async () => {
|
it("should follow the specified user if userId is provided", async () => {
|
||||||
const state = singleUserStateProvider.getFake(userId, keyDefinition);
|
const state = singleUserStateProvider.getFake(userId, keyDefinition);
|
||||||
@@ -125,9 +131,14 @@ describe("DefaultStateProvider", () => {
|
|||||||
name: "name",
|
name: "name",
|
||||||
status: AuthenticationStatus.LoggedOut,
|
status: AuthenticationStatus.LoggedOut,
|
||||||
};
|
};
|
||||||
const keyDefinition = new KeyDefinition<string>(new StateDefinition("test", "disk"), "test", {
|
const keyDefinition = new UserKeyDefinition<string>(
|
||||||
deserializer: (s) => s,
|
new StateDefinition("test", "disk"),
|
||||||
});
|
"test",
|
||||||
|
{
|
||||||
|
deserializer: (s) => s,
|
||||||
|
clearOn: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it("should not emit any values until a truthy user id is supplied", async () => {
|
it("should not emit any values until a truthy user id is supplied", async () => {
|
||||||
accountService.activeAccountSubject.next(null);
|
accountService.activeAccountSubject.next(null);
|
||||||
@@ -149,9 +160,14 @@ describe("DefaultStateProvider", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("getUserStateOrDefault$", () => {
|
describe("getUserStateOrDefault$", () => {
|
||||||
const keyDefinition = new KeyDefinition<string>(new StateDefinition("test", "disk"), "test", {
|
const keyDefinition = new UserKeyDefinition<string>(
|
||||||
deserializer: (s) => s,
|
new StateDefinition("test", "disk"),
|
||||||
});
|
"test",
|
||||||
|
{
|
||||||
|
deserializer: (s) => s,
|
||||||
|
clearOn: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it("should emit default value if no userId supplied and first active user id emission in falsy", async () => {
|
it("should emit default value if no userId supplied and first active user id emission in falsy", async () => {
|
||||||
accountService.activeAccountSubject.next(null);
|
accountService.activeAccountSubject.next(null);
|
||||||
@@ -168,9 +184,14 @@ describe("DefaultStateProvider", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("setUserState", () => {
|
describe("setUserState", () => {
|
||||||
const keyDefinition = new KeyDefinition<string>(new StateDefinition("test", "disk"), "test", {
|
const keyDefinition = new UserKeyDefinition<string>(
|
||||||
deserializer: (s) => s,
|
new StateDefinition("test", "disk"),
|
||||||
});
|
"test",
|
||||||
|
{
|
||||||
|
deserializer: (s) => s,
|
||||||
|
clearOn: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it("should set the state for the active user if no userId is provided", async () => {
|
it("should set the state for the active user if no userId is provided", async () => {
|
||||||
const value = "value";
|
const value = "value";
|
||||||
@@ -202,8 +223,9 @@ describe("DefaultStateProvider", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should bind the activeUserStateProvider", () => {
|
it("should bind the activeUserStateProvider", () => {
|
||||||
const keyDefinition = new KeyDefinition(new StateDefinition("test", "disk"), "test", {
|
const keyDefinition = new UserKeyDefinition(new StateDefinition("test", "disk"), "test", {
|
||||||
deserializer: () => null,
|
deserializer: () => null,
|
||||||
|
clearOn: [],
|
||||||
});
|
});
|
||||||
const existing = activeUserStateProvider.get(keyDefinition);
|
const existing = activeUserStateProvider.get(keyDefinition);
|
||||||
const actual = sut.getActive(keyDefinition);
|
const actual = sut.getActive(keyDefinition);
|
||||||
@@ -212,8 +234,9 @@ describe("DefaultStateProvider", () => {
|
|||||||
|
|
||||||
it("should bind the singleUserStateProvider", () => {
|
it("should bind the singleUserStateProvider", () => {
|
||||||
const userId = "user" as UserId;
|
const userId = "user" as UserId;
|
||||||
const keyDefinition = new KeyDefinition(new StateDefinition("test", "disk"), "test", {
|
const keyDefinition = new UserKeyDefinition(new StateDefinition("test", "disk"), "test", {
|
||||||
deserializer: () => null,
|
deserializer: () => null,
|
||||||
|
clearOn: [],
|
||||||
});
|
});
|
||||||
const existing = singleUserStateProvider.get(userId, keyDefinition);
|
const existing = singleUserStateProvider.get(userId, keyDefinition);
|
||||||
const actual = sut.getUser(userId, keyDefinition);
|
const actual = sut.getUser(userId, keyDefinition);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { DeriveDefinition } from "../derive-definition";
|
|||||||
import { DerivedState } from "../derived-state";
|
import { DerivedState } from "../derived-state";
|
||||||
import { DerivedStateProvider } from "../derived-state.provider";
|
import { DerivedStateProvider } from "../derived-state.provider";
|
||||||
import { GlobalStateProvider } from "../global-state.provider";
|
import { GlobalStateProvider } from "../global-state.provider";
|
||||||
import { KeyDefinition } from "../key-definition";
|
|
||||||
import { StateProvider } from "../state.provider";
|
import { StateProvider } from "../state.provider";
|
||||||
import { UserKeyDefinition } from "../user-key-definition";
|
import { UserKeyDefinition } from "../user-key-definition";
|
||||||
import { ActiveUserStateProvider, SingleUserStateProvider } from "../user-state.provider";
|
import { ActiveUserStateProvider, SingleUserStateProvider } from "../user-state.provider";
|
||||||
@@ -22,47 +21,44 @@ export class DefaultStateProvider implements StateProvider {
|
|||||||
this.activeUserId$ = this.activeUserStateProvider.activeUserId$;
|
this.activeUserId$ = this.activeUserStateProvider.activeUserId$;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserState$<T>(
|
getUserState$<T>(userKeyDefinition: UserKeyDefinition<T>, userId?: UserId): Observable<T> {
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
|
||||||
userId?: UserId,
|
|
||||||
): Observable<T> {
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
return this.getUser<T>(userId, keyDefinition).state$;
|
return this.getUser<T>(userId, userKeyDefinition).state$;
|
||||||
} else {
|
} else {
|
||||||
return this.activeUserId$.pipe(
|
return this.activeUserId$.pipe(
|
||||||
filter((userId) => userId != null), // Filter out null-ish user ids since we can't get state for a null user id
|
filter((userId) => userId != null), // Filter out null-ish user ids since we can't get state for a null user id
|
||||||
take(1),
|
take(1),
|
||||||
switchMap((userId) => this.getUser<T>(userId, keyDefinition).state$),
|
switchMap((userId) => this.getUser<T>(userId, userKeyDefinition).state$),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserStateOrDefault$<T>(
|
getUserStateOrDefault$<T>(
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
userKeyDefinition: UserKeyDefinition<T>,
|
||||||
config: { userId: UserId | undefined; defaultValue?: T },
|
config: { userId: UserId | undefined; defaultValue?: T },
|
||||||
): Observable<T> {
|
): Observable<T> {
|
||||||
const { userId, defaultValue = null } = config;
|
const { userId, defaultValue = null } = config;
|
||||||
if (userId) {
|
if (userId) {
|
||||||
return this.getUser<T>(userId, keyDefinition).state$;
|
return this.getUser<T>(userId, userKeyDefinition).state$;
|
||||||
} else {
|
} else {
|
||||||
return this.activeUserId$.pipe(
|
return this.activeUserId$.pipe(
|
||||||
take(1),
|
take(1),
|
||||||
switchMap((userId) =>
|
switchMap((userId) =>
|
||||||
userId != null ? this.getUser<T>(userId, keyDefinition).state$ : of(defaultValue),
|
userId != null ? this.getUser<T>(userId, userKeyDefinition).state$ : of(defaultValue),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserState<T>(
|
async setUserState<T>(
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
userKeyDefinition: UserKeyDefinition<T>,
|
||||||
value: T,
|
value: T,
|
||||||
userId?: UserId,
|
userId?: UserId,
|
||||||
): Promise<[UserId, T]> {
|
): Promise<[UserId, T]> {
|
||||||
if (userId) {
|
if (userId) {
|
||||||
return [userId, await this.getUser<T>(userId, keyDefinition).update(() => value)];
|
return [userId, await this.getUser<T>(userId, userKeyDefinition).update(() => value)];
|
||||||
} else {
|
} else {
|
||||||
return await this.getActive<T>(keyDefinition).update(() => value);
|
return await this.getActive<T>(userKeyDefinition).update(() => value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { StorageServiceProvider } from "../../services/storage-service.provider"
|
|||||||
import { KeyDefinition } from "../key-definition";
|
import { KeyDefinition } from "../key-definition";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateDefinition } from "../state-definition";
|
||||||
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
|
import { UserKeyDefinition } from "../user-key-definition";
|
||||||
|
|
||||||
import { DefaultActiveUserState } from "./default-active-user-state";
|
import { DefaultActiveUserState } from "./default-active-user-state";
|
||||||
import { DefaultActiveUserStateProvider } from "./default-active-user-state.provider";
|
import { DefaultActiveUserStateProvider } from "./default-active-user-state.provider";
|
||||||
@@ -41,94 +42,132 @@ describe("Specific State Providers", () => {
|
|||||||
const fakeDiskStateDefinition = new StateDefinition("fake", "disk");
|
const fakeDiskStateDefinition = new StateDefinition("fake", "disk");
|
||||||
const fakeAlternateDiskStateDefinition = new StateDefinition("fakeAlternate", "disk");
|
const fakeAlternateDiskStateDefinition = new StateDefinition("fakeAlternate", "disk");
|
||||||
const fakeMemoryStateDefinition = new StateDefinition("fake", "memory");
|
const fakeMemoryStateDefinition = new StateDefinition("fake", "memory");
|
||||||
|
const makeKeyDefinition = (stateDefinition: StateDefinition, key: string) =>
|
||||||
const fakeDiskKeyDefinition = new KeyDefinition<boolean>(fakeDiskStateDefinition, "fake", {
|
new KeyDefinition<boolean>(stateDefinition, key, {
|
||||||
deserializer: (b) => b,
|
|
||||||
});
|
|
||||||
const fakeAlternateKeyDefinition = new KeyDefinition<boolean>(
|
|
||||||
fakeAlternateDiskStateDefinition,
|
|
||||||
"fake",
|
|
||||||
{
|
|
||||||
deserializer: (b) => b,
|
deserializer: (b) => b,
|
||||||
},
|
});
|
||||||
);
|
const makeUserKeyDefinition = (stateDefinition: StateDefinition, key: string) =>
|
||||||
const fakeMemoryKeyDefinition = new KeyDefinition<boolean>(fakeMemoryStateDefinition, "fake", {
|
new UserKeyDefinition<boolean>(stateDefinition, key, {
|
||||||
deserializer: (b) => b,
|
|
||||||
});
|
|
||||||
const fakeDiskKeyDefinitionAlternate = new KeyDefinition<boolean>(
|
|
||||||
fakeDiskStateDefinition,
|
|
||||||
"fakeAlternate",
|
|
||||||
{
|
|
||||||
deserializer: (b) => b,
|
deserializer: (b) => b,
|
||||||
|
clearOn: [],
|
||||||
|
});
|
||||||
|
const keyDefinitions = {
|
||||||
|
disk: {
|
||||||
|
keyDefinition: makeKeyDefinition(fakeDiskStateDefinition, "fake"),
|
||||||
|
userKeyDefinition: makeUserKeyDefinition(fakeDiskStateDefinition, "fake"),
|
||||||
|
altKeyDefinition: makeKeyDefinition(fakeDiskStateDefinition, "fakeAlternate"),
|
||||||
|
altUserKeyDefinition: makeUserKeyDefinition(fakeDiskStateDefinition, "fakeAlternate"),
|
||||||
},
|
},
|
||||||
);
|
memory: {
|
||||||
|
keyDefinition: makeKeyDefinition(fakeMemoryStateDefinition, "fake"),
|
||||||
|
userKeyDefinition: makeUserKeyDefinition(fakeMemoryStateDefinition, "fake"),
|
||||||
|
},
|
||||||
|
alternateDisk: {
|
||||||
|
keyDefinition: makeKeyDefinition(fakeAlternateDiskStateDefinition, "fake"),
|
||||||
|
userKeyDefinition: makeUserKeyDefinition(fakeAlternateDiskStateDefinition, "fake"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const globalAndSingle = [
|
describe("active provider", () => {
|
||||||
{
|
it("returns a DefaultActiveUserState", () => {
|
||||||
getMethod: (keyDefinition: KeyDefinition<boolean>) => globalSut.get(keyDefinition),
|
const state = activeSut.get(keyDefinitions.disk.userKeyDefinition);
|
||||||
expectedInstance: DefaultGlobalState,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Use a static user id so that it has the same signature as the rest and then write special tests
|
|
||||||
// handling differing user id
|
|
||||||
getMethod: (keyDefinition: KeyDefinition<boolean>) => singleSut.get(fakeUser1, keyDefinition),
|
|
||||||
expectedInstance: DefaultSingleUserState,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
describe.each([
|
expect(state).toBeInstanceOf(DefaultActiveUserState);
|
||||||
{
|
|
||||||
getMethod: (keyDefinition: KeyDefinition<boolean>) => activeSut.get(keyDefinition),
|
|
||||||
expectedInstance: DefaultActiveUserState,
|
|
||||||
},
|
|
||||||
...globalAndSingle,
|
|
||||||
])("common behavior %s", ({ getMethod, expectedInstance }) => {
|
|
||||||
it("returns expected instance", () => {
|
|
||||||
const state = getMethod(fakeDiskKeyDefinition);
|
|
||||||
|
|
||||||
expect(state).toBeTruthy();
|
|
||||||
expect(state).toBeInstanceOf(expectedInstance);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns different instances when the storage location differs", () => {
|
it("returns different instances when the storage location differs", () => {
|
||||||
const stateDisk = getMethod(fakeDiskKeyDefinition);
|
const stateDisk = activeSut.get(keyDefinitions.disk.userKeyDefinition);
|
||||||
const stateMemory = getMethod(fakeMemoryKeyDefinition);
|
const stateMemory = activeSut.get(keyDefinitions.memory.userKeyDefinition);
|
||||||
expect(stateDisk).not.toStrictEqual(stateMemory);
|
expect(stateDisk).not.toStrictEqual(stateMemory);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns different instances when the state name differs", () => {
|
it("returns different instances when the state name differs", () => {
|
||||||
const state = getMethod(fakeDiskKeyDefinition);
|
const state = activeSut.get(keyDefinitions.disk.userKeyDefinition);
|
||||||
const stateAlt = getMethod(fakeAlternateKeyDefinition);
|
const stateAlt = activeSut.get(keyDefinitions.alternateDisk.userKeyDefinition);
|
||||||
expect(state).not.toStrictEqual(stateAlt);
|
expect(state).not.toStrictEqual(stateAlt);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns different instances when the key differs", () => {
|
it("returns different instances when the key differs", () => {
|
||||||
const state = getMethod(fakeDiskKeyDefinition);
|
const state = activeSut.get(keyDefinitions.disk.userKeyDefinition);
|
||||||
const stateAlt = getMethod(fakeDiskKeyDefinitionAlternate);
|
const stateAlt = activeSut.get(keyDefinitions.disk.altUserKeyDefinition);
|
||||||
expect(state).not.toStrictEqual(stateAlt);
|
expect(state).not.toStrictEqual(stateAlt);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.each(globalAndSingle)("Global And Single Behavior", ({ getMethod }) => {
|
describe("single provider", () => {
|
||||||
it("returns cached instance on repeated request", () => {
|
it("returns a DefaultSingleUserState", () => {
|
||||||
const stateFirst = getMethod(fakeDiskKeyDefinition);
|
const state = singleSut.get(fakeUser1, keyDefinitions.disk.userKeyDefinition);
|
||||||
const stateCached = getMethod(fakeDiskKeyDefinition);
|
|
||||||
expect(stateFirst).toStrictEqual(stateCached);
|
expect(state).toBeInstanceOf(DefaultSingleUserState);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
it("returns different instances when the storage location differs", () => {
|
||||||
|
const stateDisk = singleSut.get(fakeUser1, keyDefinitions.disk.userKeyDefinition);
|
||||||
|
const stateMemory = singleSut.get(fakeUser1, keyDefinitions.memory.userKeyDefinition);
|
||||||
|
expect(stateDisk).not.toStrictEqual(stateMemory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns different instances when the state name differs", () => {
|
||||||
|
const state = singleSut.get(fakeUser1, keyDefinitions.disk.userKeyDefinition);
|
||||||
|
const stateAlt = singleSut.get(fakeUser1, keyDefinitions.alternateDisk.userKeyDefinition);
|
||||||
|
expect(state).not.toStrictEqual(stateAlt);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns different instances when the key differs", () => {
|
||||||
|
const state = singleSut.get(fakeUser1, keyDefinitions.disk.userKeyDefinition);
|
||||||
|
const stateAlt = singleSut.get(fakeUser1, keyDefinitions.disk.altUserKeyDefinition);
|
||||||
|
expect(state).not.toStrictEqual(stateAlt);
|
||||||
|
});
|
||||||
|
|
||||||
describe("DefaultSingleUserStateProvider only behavior", () => {
|
|
||||||
const fakeUser2 = "00000000-0000-1000-a000-000000000002" as UserId;
|
const fakeUser2 = "00000000-0000-1000-a000-000000000002" as UserId;
|
||||||
|
|
||||||
it("returns different instances when the user id differs", () => {
|
it("returns different instances when the user id differs", () => {
|
||||||
const user1State = singleSut.get(fakeUser1, fakeDiskKeyDefinition);
|
const user1State = singleSut.get(fakeUser1, keyDefinitions.disk.userKeyDefinition);
|
||||||
const user2State = singleSut.get(fakeUser2, fakeDiskKeyDefinition);
|
const user2State = singleSut.get(fakeUser2, keyDefinitions.disk.userKeyDefinition);
|
||||||
expect(user1State).not.toStrictEqual(user2State);
|
expect(user1State).not.toStrictEqual(user2State);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns an instance with the userId property corresponding to the user id passed in", () => {
|
it("returns an instance with the userId property corresponding to the user id passed in", () => {
|
||||||
const userState = singleSut.get(fakeUser1, fakeDiskKeyDefinition);
|
const userState = singleSut.get(fakeUser1, keyDefinitions.disk.userKeyDefinition);
|
||||||
expect(userState.userId).toBe(fakeUser1);
|
expect(userState.userId).toBe(fakeUser1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns cached instance on repeated request", () => {
|
||||||
|
const stateFirst = singleSut.get(fakeUser1, keyDefinitions.disk.userKeyDefinition);
|
||||||
|
const stateCached = singleSut.get(fakeUser1, keyDefinitions.disk.userKeyDefinition);
|
||||||
|
expect(stateFirst).toStrictEqual(stateCached);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("global provider", () => {
|
||||||
|
it("returns a DefaultGlobalState", () => {
|
||||||
|
const state = globalSut.get(keyDefinitions.disk.keyDefinition);
|
||||||
|
|
||||||
|
expect(state).toBeInstanceOf(DefaultGlobalState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns different instances when the storage location differs", () => {
|
||||||
|
const stateDisk = globalSut.get(keyDefinitions.disk.keyDefinition);
|
||||||
|
const stateMemory = globalSut.get(keyDefinitions.memory.keyDefinition);
|
||||||
|
expect(stateDisk).not.toStrictEqual(stateMemory);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns different instances when the state name differs", () => {
|
||||||
|
const state = globalSut.get(keyDefinitions.disk.keyDefinition);
|
||||||
|
const stateAlt = globalSut.get(keyDefinitions.alternateDisk.keyDefinition);
|
||||||
|
expect(state).not.toStrictEqual(stateAlt);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns different instances when the key differs", () => {
|
||||||
|
const state = globalSut.get(keyDefinitions.disk.keyDefinition);
|
||||||
|
const stateAlt = globalSut.get(keyDefinitions.disk.altKeyDefinition);
|
||||||
|
expect(state).not.toStrictEqual(stateAlt);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns cached instance on repeated request", () => {
|
||||||
|
const stateFirst = globalSut.get(keyDefinitions.disk.keyDefinition);
|
||||||
|
const stateCached = globalSut.get(keyDefinitions.disk.keyDefinition);
|
||||||
|
expect(stateFirst).toStrictEqual(stateCached);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,22 +21,6 @@ export abstract class StateProvider {
|
|||||||
/** @see{@link ActiveUserStateProvider.activeUserId$} */
|
/** @see{@link ActiveUserStateProvider.activeUserId$} */
|
||||||
abstract activeUserId$: Observable<UserId | undefined>;
|
abstract activeUserId$: Observable<UserId | undefined>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a state observable for a given key and userId.
|
|
||||||
*
|
|
||||||
* @remarks If userId is falsy the observable returned will attempt to point to the currently active user _and not update if the active user changes_.
|
|
||||||
* This is different to how `getActive` works and more similar to `getUser` for whatever user happens to be active at the time of the call.
|
|
||||||
* If no user happens to be active at the time this method is called with a falsy userId then this observable will not emit a value until
|
|
||||||
* a user becomes active. If you are not confident a user is active at the time this method is called, you may want to pipe a call to `timeout`
|
|
||||||
* or instead call {@link getUserStateOrDefault$} and supply a value you would rather have given in the case of no passed in userId and no active user.
|
|
||||||
*
|
|
||||||
* @note consider converting your {@link KeyDefinition} to a {@link UserKeyDefinition} for additional features.
|
|
||||||
*
|
|
||||||
* @param keyDefinition - The key definition for the state you want to get.
|
|
||||||
* @param userId - The userId for which you want the state for. If not provided, the state for the currently active user will be returned.
|
|
||||||
*/
|
|
||||||
abstract getUserState$<T>(keyDefinition: KeyDefinition<T>, userId?: UserId): Observable<T>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a state observable for a given key and userId.
|
* Gets a state observable for a given key and userId.
|
||||||
*
|
*
|
||||||
@@ -51,23 +35,6 @@ export abstract class StateProvider {
|
|||||||
*/
|
*/
|
||||||
abstract getUserState$<T>(keyDefinition: UserKeyDefinition<T>, userId?: UserId): Observable<T>;
|
abstract getUserState$<T>(keyDefinition: UserKeyDefinition<T>, userId?: UserId): Observable<T>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a state observable for a given key and userId
|
|
||||||
*
|
|
||||||
* @remarks If userId is falsy the observable return will first attempt to point to the currently active user but will not follow subsequent active user changes,
|
|
||||||
* if there is no immediately available active user, then it will fallback to returning a default value in an observable that immediately completes.
|
|
||||||
*
|
|
||||||
* @note consider converting your {@link KeyDefinition} to a {@link UserKeyDefinition} for additional features.
|
|
||||||
*
|
|
||||||
* @param keyDefinition - The key definition for the state you want to get.
|
|
||||||
* @param config.userId - The userId for which you want the state for. If not provided, the state for the currently active user will be returned.
|
|
||||||
* @param config.defaultValue - The default value that should be wrapped in an observable if no active user is immediately available and no truthy userId is passed in.
|
|
||||||
*/
|
|
||||||
abstract getUserStateOrDefault$<T>(
|
|
||||||
keyDefinition: KeyDefinition<T>,
|
|
||||||
config: { userId: UserId | undefined; defaultValue?: T },
|
|
||||||
): Observable<T>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a state observable for a given key and userId
|
* Gets a state observable for a given key and userId
|
||||||
*
|
*
|
||||||
@@ -97,56 +64,11 @@ export abstract class StateProvider {
|
|||||||
userId?: UserId,
|
userId?: UserId,
|
||||||
): Promise<[UserId, T]>;
|
): Promise<[UserId, T]>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the state for a given key and userId.
|
|
||||||
*
|
|
||||||
* **NOTE** Consider converting your {@link KeyDefinition} to a {@link UserKeyDefinition} for additional features.
|
|
||||||
*
|
|
||||||
* @overload
|
|
||||||
* @param keyDefinition - The key definition for the state you want to set.
|
|
||||||
* @param value - The value to set the state to.
|
|
||||||
* @param userId - The userId for which you want to set the state for. If not provided, the state for the currently active user will be set.
|
|
||||||
*/
|
|
||||||
abstract setUserState<T>(
|
|
||||||
keyDefinition: KeyDefinition<T>,
|
|
||||||
value: T,
|
|
||||||
userId?: UserId,
|
|
||||||
): Promise<[UserId, T]>;
|
|
||||||
|
|
||||||
abstract setUserState<T>(
|
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
|
||||||
value: T,
|
|
||||||
userId?: UserId,
|
|
||||||
): Promise<[UserId, T]>;
|
|
||||||
|
|
||||||
/** @see{@link ActiveUserStateProvider.get} */
|
/** @see{@link ActiveUserStateProvider.get} */
|
||||||
abstract getActive<T>(keyDefinition: UserKeyDefinition<T>): ActiveUserState<T>;
|
abstract getActive<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T>;
|
||||||
|
|
||||||
/**
|
|
||||||
* @see{@link ActiveUserStateProvider.get}
|
|
||||||
*
|
|
||||||
* **NOTE** Consider converting your {@link KeyDefinition} to a {@link UserKeyDefinition} for additional features.
|
|
||||||
*/
|
|
||||||
abstract getActive<T>(keyDefinition: KeyDefinition<T>): ActiveUserState<T>;
|
|
||||||
|
|
||||||
/** @see{@link ActiveUserStateProvider.get} */
|
|
||||||
abstract getActive<T>(keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>): ActiveUserState<T>;
|
|
||||||
|
|
||||||
/** @see{@link SingleUserStateProvider.get} */
|
/** @see{@link SingleUserStateProvider.get} */
|
||||||
abstract getUser<T>(userId: UserId, keyDefinition: UserKeyDefinition<T>): SingleUserState<T>;
|
abstract getUser<T>(userId: UserId, userKeyDefinition: UserKeyDefinition<T>): SingleUserState<T>;
|
||||||
|
|
||||||
/**
|
|
||||||
* @see{@link SingleUserStateProvider.get}
|
|
||||||
*
|
|
||||||
* **NOTE** Consider converting your {@link KeyDefinition} to a {@link UserKeyDefinition} for additional features.
|
|
||||||
*/
|
|
||||||
abstract getUser<T>(userId: UserId, keyDefinition: KeyDefinition<T>): SingleUserState<T>;
|
|
||||||
|
|
||||||
/** @see{@link SingleUserStateProvider.get} */
|
|
||||||
abstract getUser<T>(
|
|
||||||
userId: UserId,
|
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
|
||||||
): SingleUserState<T>;
|
|
||||||
|
|
||||||
/** @see{@link GlobalStateProvider.get} */
|
/** @see{@link GlobalStateProvider.get} */
|
||||||
abstract getGlobal<T>(keyDefinition: KeyDefinition<T>): GlobalState<T>;
|
abstract getGlobal<T>(keyDefinition: KeyDefinition<T>): GlobalState<T>;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { StorageKey } from "../../types/state";
|
|||||||
import { Utils } from "../misc/utils";
|
import { Utils } from "../misc/utils";
|
||||||
|
|
||||||
import { array, record } from "./deserialization-helpers";
|
import { array, record } from "./deserialization-helpers";
|
||||||
import { KeyDefinition, KeyDefinitionOptions } from "./key-definition";
|
import { KeyDefinitionOptions } from "./key-definition";
|
||||||
import { StateDefinition } from "./state-definition";
|
import { StateDefinition } from "./state-definition";
|
||||||
|
|
||||||
export type ClearEvent = "lock" | "logout";
|
export type ClearEvent = "lock" | "logout";
|
||||||
@@ -14,15 +14,6 @@ export type UserKeyDefinitionOptions<T> = KeyDefinitionOptions<T> & {
|
|||||||
|
|
||||||
const USER_KEY_DEFINITION_MARKER: unique symbol = Symbol("UserKeyDefinition");
|
const USER_KEY_DEFINITION_MARKER: unique symbol = Symbol("UserKeyDefinition");
|
||||||
|
|
||||||
export function isUserKeyDefinition<T>(
|
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
|
||||||
): keyDefinition is UserKeyDefinition<T> {
|
|
||||||
return (
|
|
||||||
USER_KEY_DEFINITION_MARKER in keyDefinition &&
|
|
||||||
keyDefinition[USER_KEY_DEFINITION_MARKER] === true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UserKeyDefinition<T> {
|
export class UserKeyDefinition<T> {
|
||||||
readonly [USER_KEY_DEFINITION_MARKER] = true;
|
readonly [USER_KEY_DEFINITION_MARKER] = true;
|
||||||
/**
|
/**
|
||||||
@@ -63,20 +54,6 @@ export class UserKeyDefinition<T> {
|
|||||||
return this.options.cleanupDelayMs < 0 ? 0 : this.options.cleanupDelayMs ?? 1000;
|
return this.options.cleanupDelayMs < 0 ? 0 : this.options.cleanupDelayMs ?? 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param keyDefinition
|
|
||||||
* @returns
|
|
||||||
*
|
|
||||||
* @deprecated You should not use this to convert, just create a {@link UserKeyDefinition}
|
|
||||||
*/
|
|
||||||
static fromBaseKeyDefinition<T>(keyDefinition: KeyDefinition<T>) {
|
|
||||||
return new UserKeyDefinition<T>(keyDefinition.stateDefinition, keyDefinition.key, {
|
|
||||||
...keyDefinition["options"],
|
|
||||||
clearOn: [], // Default to not clearing
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link UserKeyDefinition} for state that is an array.
|
* Creates a {@link UserKeyDefinition} for state that is an array.
|
||||||
* @param stateDefinition The state definition to be added to the UserKeyDefinition
|
* @param stateDefinition The state definition to be added to the UserKeyDefinition
|
||||||
|
|||||||
@@ -2,22 +2,11 @@ import { Observable } from "rxjs";
|
|||||||
|
|
||||||
import { UserId } from "../../types/guid";
|
import { UserId } from "../../types/guid";
|
||||||
|
|
||||||
import { KeyDefinition } from "./key-definition";
|
|
||||||
import { UserKeyDefinition } from "./user-key-definition";
|
import { UserKeyDefinition } from "./user-key-definition";
|
||||||
import { ActiveUserState, SingleUserState } from "./user-state";
|
import { ActiveUserState, SingleUserState } from "./user-state";
|
||||||
|
|
||||||
/** A provider for getting an implementation of state scoped to a given key and userId */
|
/** A provider for getting an implementation of state scoped to a given key and userId */
|
||||||
export abstract class SingleUserStateProvider {
|
export abstract class SingleUserStateProvider {
|
||||||
/**
|
|
||||||
* Gets a {@link SingleUserState} scoped to the given {@link KeyDefinition} and {@link UserId}
|
|
||||||
*
|
|
||||||
* **NOTE** Consider converting your {@link KeyDefinition} to a {@link UserKeyDefinition} for additional features.
|
|
||||||
*
|
|
||||||
* @param userId - The {@link UserId} for which you want the user state for.
|
|
||||||
* @param keyDefinition - The {@link KeyDefinition} for which you want the user state for.
|
|
||||||
*/
|
|
||||||
abstract get<T>(userId: UserId, keyDefinition: KeyDefinition<T>): SingleUserState<T>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a {@link SingleUserState} scoped to the given {@link UserKeyDefinition} and {@link UserId}
|
* Gets a {@link SingleUserState} scoped to the given {@link UserKeyDefinition} and {@link UserId}
|
||||||
*
|
*
|
||||||
@@ -25,11 +14,6 @@ export abstract class SingleUserStateProvider {
|
|||||||
* @param userKeyDefinition - The {@link UserKeyDefinition} for which you want the user state for.
|
* @param userKeyDefinition - The {@link UserKeyDefinition} for which you want the user state for.
|
||||||
*/
|
*/
|
||||||
abstract get<T>(userId: UserId, userKeyDefinition: UserKeyDefinition<T>): SingleUserState<T>;
|
abstract get<T>(userId: UserId, userKeyDefinition: UserKeyDefinition<T>): SingleUserState<T>;
|
||||||
|
|
||||||
abstract get<T>(
|
|
||||||
userId: UserId,
|
|
||||||
keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>,
|
|
||||||
): SingleUserState<T>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A provider for getting an implementation of state scoped to a given key, but always pointing
|
/** A provider for getting an implementation of state scoped to a given key, but always pointing
|
||||||
@@ -48,16 +32,4 @@ export abstract class ActiveUserStateProvider {
|
|||||||
* @param keyDefinition - The {@link UserKeyDefinition} for which you want the user state for.
|
* @param keyDefinition - The {@link UserKeyDefinition} for which you want the user state for.
|
||||||
*/
|
*/
|
||||||
abstract get<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T>;
|
abstract get<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a {@link ActiveUserState} scoped to the given {@link KeyDefinition}, but updates when active user changes such
|
|
||||||
* that the emitted values always represents the state for the currently active user.
|
|
||||||
*
|
|
||||||
* **NOTE** Consider converting your {@link KeyDefinition} to a {@link UserKeyDefinition} for additional features.
|
|
||||||
*
|
|
||||||
* @param keyDefinition - The {@link KeyDefinition} for which you want the user state for.
|
|
||||||
*/
|
|
||||||
abstract get<T>(keyDefinition: KeyDefinition<T>): ActiveUserState<T>;
|
|
||||||
|
|
||||||
abstract get<T>(keyDefinition: KeyDefinition<T> | UserKeyDefinition<T>): ActiveUserState<T>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user