Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions libs/state/spec/rx-state.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ describe('RxStateService', () => {
});

describe('get', () => {
it('should return readonly state', () => {
service.set({ bol: false });
// @ts-expect-error Cannot assign to 'bol' because it is a read-only property.
service.get().bol = true;
// service.get() returns a reference to the state object so it is mutable.
expect(service.get().bol).toBe(true);
});

it('should return undefined as initial value', () => {
const state = setupState({ initialState: undefined });
const val = state.get();
Expand All @@ -155,6 +163,16 @@ describe('RxStateService', () => {
});

describe('select', () => {
it('should have readonly state projection', () => {
const state = setupState({ initialState: initialPrimitiveState });
state.select(['num', 'bol'], (x) => {
// @ts-expect-error Cannot assign to 'num' because it is a read-only property.
x.num = 1;
return x;
});
expect(state.get().num).toBe(initialPrimitiveState.num);
});

it('should return undefined as initial value', () => {
testScheduler.run(({ expectObservable }) => {
const state = setupState({ initialState: undefined });
Expand Down Expand Up @@ -273,6 +291,16 @@ describe('RxStateService', () => {
});

describe('set', () => {
it('should have readonly state projection', () => {
service.set({ bol: false });
service.set((s) => {
// @ts-expect-error Cannot assign to 'bol' because it is a read-only property.
s.bol = true;
return { bol: false };
});
expect(service.get().bol).toBe(false);
});

describe('with state partial', () => {
it('should add new slices', () => {
const state = setupState({});
Expand Down Expand Up @@ -338,6 +366,16 @@ describe('RxStateService', () => {
});

describe('connect', () => {
it('should have readonly state projection', () => {
service.set({ bol: false });
service.connect(of({ bol: true }), (s) => {
// @ts-expect-error Cannot assign to 'bol' because it is a read-only property.
s.bol = true;
return { bol: false };
});
expect(service.get().bol).toBe(false);
});

it('should work with observables directly', () => {
testScheduler.run(({ expectObservable }) => {
const state = setupState({ initialState: initialPrimitiveState });
Expand Down
55 changes: 37 additions & 18 deletions libs/state/src/lib/rx-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ import {
} from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';

export type ProjectStateFn<T> = (oldState: T) => Partial<T>;
export type ProjectValueFn<T, K extends keyof T> = (oldState: T) => T[K];
export type ProjectStateFn<T> = (oldState: Readonly<T>) => Partial<T>;
export type ProjectValueFn<T, K extends keyof T> = (
oldState: Readonly<T>
) => T[K];

export type ProjectStateReducer<T, V> = (oldState: T, value: V) => Partial<T>;
export type ProjectStateReducer<T, V> = (
oldState: Readonly<T>,
value: V
) => Partial<T>;

export type ProjectValueReducer<T, K extends keyof T, V> = (
oldState: T,
oldState: Readonly<T>,
value: V
) => T[K];

Expand Down Expand Up @@ -105,9 +110,9 @@ export class RxState<T extends object> implements OnDestroy, Subscribable<T> {
* doStuff();
* }
*
* @return T
* @return Readonly<T>
*/
get(): T;
get(): Readonly<T>;

/**
* @description
Expand All @@ -123,33 +128,36 @@ export class RxState<T extends object> implements OnDestroy, Subscribable<T> {
*
* const foo = state.get('bar', 'foo');
*
* @return T | T[K1] | T[K1][K2]
* @return Readonly<T> | Readonly<T[K1]> | Readonly<T[K1][K2]>
*/

get<K1 extends keyof T>(k1: K1): T[K1];
get<K1 extends keyof T>(k1: K1): Readonly<T[K1]>;
/** @internal **/
get<K1 extends keyof T, K2 extends keyof T[K1]>(k1: K1, k2: K2): T[K1][K2];
get<K1 extends keyof T, K2 extends keyof T[K1]>(
k1: K1,
k2: K2
): Readonly<T[K1][K2]>;
/** @internal **/
get<K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]>(
k1: K1,
k2: K2,
k3: K3
): T[K1][K2][K3];
): Readonly<T[K1][K2][K3]>;
/** @internal **/
get<
K1 extends keyof T,
K2 extends keyof T[K1],
K3 extends keyof T[K1][K2],
K4 extends keyof T[K1][K2][K3]
>(k1: K1, k2: K2, k3: K3, k4: K4): T[K1][K2][K3][K4];
>(k1: K1, k2: K2, k3: K3, k4: K4): Readonly<T[K1][K2][K3][K4]>;
/** @internal **/
get<
K1 extends keyof T,
K2 extends keyof T[K1],
K3 extends keyof T[K1][K2],
K4 extends keyof T[K1][K2][K3],
K5 extends keyof T[K1][K2][K3][K4]
>(k1: K1, k2: K2, k3: K3, k4: K4, k5: K5): T[K1][K2][K3][K4][K5];
>(k1: K1, k2: K2, k3: K3, k4: K4, k5: K5): Readonly<T[K1][K2][K3][K4][K5]>;
/** @internal **/
get<
K1 extends keyof T,
Expand All @@ -158,7 +166,14 @@ export class RxState<T extends object> implements OnDestroy, Subscribable<T> {
K4 extends keyof T[K1][K2][K3],
K5 extends keyof T[K1][K2][K3][K4],
K6 extends keyof T[K1][K2][K3][K4][K5]
>(k1: K1, k2: K2, k3: K3, k4: K4, k5: K5, k6: K6): T[K1][K2][K3][K4][K5][K6];
>(
k1: K1,
k2: K2,
k3: K3,
k4: K4,
k5: K5,
k6: K6
): Readonly<T[K1][K2][K3][K4][K5][K6]>;
/** @internal **/
get<
K1 extends keyof T,
Expand All @@ -175,14 +190,15 @@ export class RxState<T extends object> implements OnDestroy, Subscribable<T> {
| [K1, K2, K3, K4]
| [K1, K2, K3, K4, K5]
| [K1, K2, K3, K4, K5, K6]
):
): Readonly<
| T
| T[K1]
| T[K1][K2]
| T[K1][K2][K3]
| T[K1][K2][K3][K4]
| T[K1][K2][K3][K4][K5]
| T[K1][K2][K3][K4][K5][K6] {
| T[K1][K2][K3][K4][K5][K6]
> {
const hasStateAnyKeys = Object.keys(this.accumulator.state).length > 0;
if (!!keys && keys.length) {
return safePluck(this.accumulator.state, keys);
Expand Down Expand Up @@ -230,7 +246,7 @@ export class RxState<T extends object> implements OnDestroy, Subscribable<T> {
* @param {ProjectValueFn<T, K>} projectSlice
* @return void
*/
set<K extends keyof T, O>(key: K, projectSlice: ProjectValueFn<T, K>): void;
set<K extends keyof T>(key: K, projectSlice: ProjectValueFn<T, K>): void;
/**
* @internal
*/
Expand Down Expand Up @@ -489,7 +505,7 @@ export class RxState<T extends object> implements OnDestroy, Subscribable<T> {
*/
select<K extends keyof T, V>(
keys: K[],
fn: (slice: PickSlice<T, K>) => V,
fn: (slice: Readonly<PickSlice<T, K>>) => V,
keyCompareMap?: KeyCompareMap<Pick<T, K>>
): Observable<V>;
/**
Expand All @@ -503,7 +519,10 @@ export class RxState<T extends object> implements OnDestroy, Subscribable<T> {
*
* @return Observable<V>
*/
select<K extends keyof T, V>(k: K, fn: (val: T[K]) => V): Observable<V>;
select<K extends keyof T, V>(
k: K,
fn: (val: Readonly<T[K]>) => V
): Observable<V>;
/**
* @description
* Access a single property of the state by providing keys.
Expand Down