diff --git a/package.json b/package.json index e7a09d8..f94eb08 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@angular/compiler-cli": "^18.1.0", "@angular/core": "^18.1.0", "@ngrx/component-store": "^18.0.0", - "@ngrx/signals": "18.0.0-rc.2", + "@ngrx/signals": "^18.0.0", "@ngrx/store": "^18.0.0", "@types/jest": "^29.5.12", "cpy-cli": "^5.0.0", diff --git a/src/package.json b/src/package.json index 153c477..8537cbf 100644 --- a/src/package.json +++ b/src/package.json @@ -28,9 +28,9 @@ }, "peerDependencies": { "immer": ">= 7.0.0", - "@ngrx/component-store": ">= 13.0.0", - "@ngrx/store": ">= 13.0.0", - "@ngrx/signals": ">= 17.1.1 < 18.0.0" + "@ngrx/component-store": ">= 18.0.0", + "@ngrx/store": ">= 18.0.0", + "@ngrx/signals": ">= 18.0.0" }, "peerDependenciesMeta": { "@ngrx/component-store": { diff --git a/src/signals/index.ts b/src/signals/index.ts index 5a6c45f..223bdd8 100644 --- a/src/signals/index.ts +++ b/src/signals/index.ts @@ -1,4 +1,4 @@ -import { PartialStateUpdater, patchState, StateSignal } from '@ngrx/signals'; +import { PartialStateUpdater, patchState, WritableStateSource, Prettify } from '@ngrx/signals'; import { immerReducer } from 'ngrx-immer'; export type ImmerStateUpdater = (state: State) => void; @@ -12,9 +12,7 @@ function toFullStateUpdater(updater: PartialStateUpdater = { [K in keyof T]: T[K] } & {}; -export function immerPatchState(state: StateSignal, ...updaters: Array> | PartialStateUpdater> | ImmerStateUpdater>>) { +export function immerPatchState(state: WritableStateSource, ...updaters: Array> | PartialStateUpdater> | ImmerStateUpdater>>): void { const immerUpdaters = updaters.map(updater => { if (typeof updater === 'function') { return immerReducer(toFullStateUpdater(updater)) as unknown as PartialStateUpdater; diff --git a/src/signals/tests/immer-patch-state.jest.ts b/src/signals/tests/immer-patch-state.jest.ts index 48e9792..4fc48a2 100644 --- a/src/signals/tests/immer-patch-state.jest.ts +++ b/src/signals/tests/immer-patch-state.jest.ts @@ -2,26 +2,28 @@ import { PartialStateUpdater, signalStore, withComputed, + withMethods, withState, } from '@ngrx/signals'; import { immerPatchState } from 'ngrx-immer/signals'; import { computed, effect } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -const UserState = signalStore( - withState({ - id: 1, - name: { firstname: 'Konrad', lastname: 'Schultz' }, - address: { city: 'Vienna', zip: '1010' }, - }), - withComputed(({ name }) => ({ - prettyName: computed(() => `${name.firstname()} ${name.lastname()}`), - })), -); - -describe('immerPatchState', () => { +describe('immerPatchState (unprotected)', () => { + const UnprotectedUserState = signalStore( + { protectedState: false }, + withState({ + id: 1, + name: { firstname: 'Konrad', lastname: 'Schultz' }, + address: { city: 'Vienna', zip: '1010' }, + }), + withComputed(({ name }) => ({ + prettyName: computed(() => `${name.firstname()} ${name.lastname()}`), + })), + ); + const setup = () => { - return new UserState(); + return new UnprotectedUserState(); }; it('smoketest', () => { @@ -192,3 +194,51 @@ describe('immerPatchState', () => { }); }); }); + +describe('immerPatchState (protected)', () => { + const ProtectedUserState = signalStore( + { protectedState: true }, + withState({ + id: 1, + name: { firstname: 'Konrad', lastname: 'Schultz' }, + address: { city: 'Vienna', zip: '1010' }, + }), + withComputed(({ name }) => ({ + prettyName: computed(() => `${name.firstname()} ${name.lastname()}`), + })), + withMethods((store) => ({ + setName: (name: {firstname:string, lastname:string}) => immerPatchState(store, { name }), + incrementId: () => immerPatchState(store, state => { + state.id++; + }), + })) + ); + + const setup = () => { + return new ProtectedUserState(); + }; + + it('smoketest', () => { + const userState = setup(); + expect(userState.id()).toBe(1); + }); + + it('state is protected and cannot be updated from the outside', () => { + const userState = setup(); + + expect(() => { + // @ts-ignore + immerPatchState(userState, (state) => ({ number: 1 })); + }).toThrow(); + }); + + it('allows patching protected state using withMethods', () => { + const userState = setup(); + + userState.incrementId(); + userState.setName({ firstname: 'Lucy', lastname: 'Sanders' }); + + expect(userState.prettyName()).toBe('Lucy Sanders'); + expect(userState.id()).toBe(2); + }); +});