8000 feat(signals): define SignalStore members as readonly by Chiorufarewerin · Pull Request #4713 · ngrx/platform · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat(signals): define SignalStore members as readonly #4713

New issue

Have a question about this project? Sign up for a free GitHub account 8000 to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
33 changes: 22 additions & 11 deletions modules/signals/entities/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
DidMutate,
EntityChanges,
EntityId,
EntityMap,
EntityPredicate,
EntityState,
SelectEntityId,
Expand All @@ -10,6 +11,11 @@ import {
declare const ngDevMode: unknown;
const defaultSelectId: SelectEntityId<{ id: EntityId }> = (entity) => entity.id;

type EntityStateMutable<Entity = any> = {
entityMap: Record<EntityId, Entity>;
ids: EntityId[];
};

export function getEntityIdSelector(config?: {
selectId?: SelectEntityId<any>;
}): SelectEntityId<any> {
Expand Down Expand Up @@ -79,8 +85,8 @@ export function addEntityMutably(
return DidMutate.None;
}

state.entityMap[id] = entity;
state.ids.push(id);
(state as EntityStateMutable).entityMap[id] = entity;
(state as EntityStateMutable).ids.push(id);

return DidMutate.Both;
}
Expand Down Expand Up @@ -111,12 +117,12 @@ export function setEntityMutably(
const id = selectId(entity);

if (state.entityMap[id]) {
state.entityMap[id] = entity;
(state as EntityStateMutable).entityMap[id] = entity;
return DidMutate.Entities;
}

state.entityMap[id] = entity;
state.ids.push(id);
(state as EntityStateMutable).entityMap[id] = entity;
(state as EntityStateMutable).ids.push(id);

return DidMutate.Both;
}
Expand Down Expand Up @@ -152,13 +158,15 @@ export function removeEntitiesMutably(

for (const id of ids) {
if (state.entityMap[id]) {
delete state.entityMap[id];
delete (state as EntityStateMutable).entityMap[id];
didMutate = DidMutate.Both;
}
}

if (didMutate === DidMutate.Both) {
state.ids = state.ids.filter((id) => id in state.entityMap);
(state as EntityStateMutable).ids = state.ids.filter(
(id) => id in state.entityMap
);
}

return didMutate;
Expand All @@ -182,13 +190,16 @@ export function updateEntitiesMutably(
if (entity) {
const changesRecord =
typeof changes === 'function' ? changes(entity) : changes;
state.entityMap[id] = { ...entity, ...changesRecord };
(state as EntityStateMutable).entityMap[id] = {
...entity,
...changesRecord,
};
didMutate = DidMutate.Entities;

const newId = selectId(state.entityMap[id]);
if (newId !== id) {
state.entityMap[newId] = state.entityMap[id];
delete state.entityMap[id];
(state as EntityStateMutable).entityMap[newId] = state.entityMap[id];
delete (state as EntityStateMutable).entityMap[id];

newIds = newIds || {};
newIds[id] = newId;
Expand All @@ -197,7 +208,7 @@ export function updateEntitiesMutably(
}

if (newIds) {
state.ids = state.ids.map((id) => newIds[id] ?? id);
(state as EntityStateMutable).ids = state.ids.map((id) => newIds[id] ?? id);
didMutate = DidMutate.Both;
}

Expand Down
8 changes: 4 additions & 4 deletions modules/signals/entities/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import { Signal } from '@angular/core';

export type EntityId = string | number;

export type EntityMap<Entity> = Record<EntityId, Entity>;
export type EntityMap<Entity> = Readonly<Record<EntityId, Entity>>;

export type EntityState<Entity> = {
entityMap: EntityMap<Entity>;
ids: EntityId[];
readonly entityMap: EntityMap<Entity>;
readonly ids: readonly EntityId[];
};

export type NamedEntityState<Entity, Collection extends string> = {
[K in keyof EntityState<Entity> as `${Collection}${Capitalize<K>}`]: EntityState<Entity>[K];
};

export type EntityProps<Entity> = {
entities: Signal<Entity[]>;
entities: Signal<readonly Entity[]>;
};

export type NamedEntityProps<Entity, Collection extends string> = {
Expand Down
32 changes: 16 additions & 16 deletions modules/signals/spec/types/signal-store.types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'Store',
'Type<{ foo: Signal<string>; bar: Signal<number[]>; } & StateSource<{ foo: string; bar: number[]; }>>'
'Type<{ readonly foo: Signal<string>; readonly bar: Signal<number[]>; } & StateSource<{ foo: string; bar: number[]; }>>'
);
});

Expand Down Expand Up @@ -64,7 +64,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store',
'{ user: DeepSignal<{ age: number; details: { first: string; flags: boolean[]; }; }>; } & StateSource<{ user: { age: number; details: { first: string; flags: boolean[]; }; }; }>'
'{ readonly user: DeepSignal<{ age: number; details: { first: string; flags: boolean[]; }; }>; } & StateSource<{ user: { age: number; details: { first: string; flags: boolean[]; }; }; }>'
);

expectSnippet(snippet).toInfer(
Expand Down Expand Up @@ -265,7 +265,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store',
'{ foo: Signal<number | { s: string; }>; bar: DeepSignal<{ baz: { b: boolean; } | null; }>; x: DeepSignal<{ y: { z: number | undefined; }; }>; } & StateSource<{ foo: number | { ...; }; bar: { ...; }; x: { ...; }; }>'
'{ readonly foo: Signal<number | { s: string; }>; readonly bar: DeepSignal<{ baz: { b: boolean; } | null; }>; readonly x: DeepSignal<{ y: { z: number | undefined; }; }>; } & StateSource<...>'
);

expectSnippet(snippet).toInfer('foo', 'Signal<number | { s: string; }>');
Expand Down Expand Up @@ -305,7 +305,7 @@ describe('signalStore', () => {

expectSnippet(snippet1).toInfer(
'Store',
'Type<{ name: DeepSignal<{ x: { y: string; }; }>; arguments: Signal<number[]>; call: Signal<boolean>; } & StateSource<{ name: { x: { y: string; }; }; arguments: number[]; call: boolean; }>>'
'Type<{ readonly name: DeepSignal<{ x: { y: string; }; }>; readonly arguments: Signal<number[]>; readonly call: Signal<boolean>; } & StateSource<{ name: { x: { y: string; }; }; arguments: number[]; call: boolean; }>>'
);

const snippet2 = `
Expand All @@ -322,7 +322,7 @@ describe('signalStore', () => {

expectSnippet(snippet2).toInfer(
'Store',
'Type<{ apply: Signal<string>; bind: DeepSignal<{ foo: string; }>; prototype: Signal<string[]>; } & StateSource<{ apply: string; bind: { foo: string; }; prototype: string[]; }>>'
'Type<{ readonly apply: Signal<string>; readonly bind: DeepSignal<{ foo: string; }>; readonly prototype: Signal<string[]>; } & StateSource<{ apply: string; bind: { foo: string; }; prototype: string[]; }>>'
);

const snippet3 = `
Expand All @@ -338,7 +338,7 @@ describe('signalStore', () => {

expectSnippet(snippet3).toInfer(
'Store',
'Type<{ length: Signal<number>; caller: Signal<undefined>; } & StateSource<{ length: number; caller: undefined; }>>'
'Type<{ readonly length: Signal<number>; readonly caller: Signal<undefined>; } & StateSource<{ length: number; caller: undefined; }>>'
);
});

Expand Down Expand Up @@ -393,7 +393,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store',
'{ bar: DeepSignal<{ baz?: number | undefined; }>; x: DeepSignal<{ y?: { z: boolean; } | undefined; }>; } & StateSource<{ bar: { baz?: number | undefined; }; x: { y?: { z: boolean; } | undefined; }; }>'
'{ readonly bar: DeepSignal<{ baz?: number | undefined; }>; readonly x: DeepSignal<{ y?: { z: boolean; } | undefined; }>; } & StateSource<{ bar: { baz?: number | undefined; }; x: { ...; }; }>'
);

expectSnippet(snippet).toInfer(
Expand Down Expand Up @@ -503,7 +503,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store1',
'{ count: Signal<number>; } & StateSource<{ count: number; }>'
'{ readonly count: Signal<number>; } & StateSource<{ count: number; }>'
);

expectSnippet(snippet).toInfer('state1', '{ count: number; }');
Expand All @@ -515,7 +515,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store2',
'{ count: Signal<number>; } & StateSource<{ count: number; }>'
'{ readonly count: Signal<number>; } & StateSource<{ count: number; }>'
);

expectSnippet(snippet).toInfer('state2', '{ count: number; }');
Expand Down Expand Up @@ -548,7 +548,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store1',
'{ count: Signal<number>; } & StateSource<{ count: number; }>'
'{ readonly count: Signal<number>; } & StateSource<{ count: number; }>'
);

expectSnippet(snippet).toInfer('state1', '{ count: number; }');
Expand All @@ -560,7 +560,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store2',
'{ count: Signal<number>; } & StateSource<{ count: number; }>'
'{ readonly count: Signal<number>; } & StateSource<{ count: number; }>'
);

expectSnippet(snippet).toInfer('state2', '{ count: number; }');
Expand Down Expand Up @@ -593,14 +593,14 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store1',
'{ count: Signal<number>; } & WritableStateSource<{ count: number; }>'
'{ readonly count: Signal<number>; } & WritableStateSource<{ count: number; }>'
);

expectSnippet(snippet).toInfer('state1', '{ count: number; }');

expectSnippet(snippet).toInfer(
'store2',
'{ count: Signal<number>; } & WritableStateSource<{ count: number; }>'
'{ readonly count: Signal<number>; } & WritableStateSource<{ count: number; }>'
);

expectSnippet(snippet).toInfer('state2', '{ count: number; }');
Expand Down Expand Up @@ -762,7 +762,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store',
'{ ngrx: Signal<string>; x: DeepSignal<{ y: string; }>; signals: Signal<number[]>; mgmt: (arg: boolean) => number; } & StateSource<{ ngrx: string; x: { y: string; }; }>'
'{ readonly ngrx: Signal<string>; readonly x: DeepSignal<{ y: string; }>; readonly signals: Signal<number[]>; readonly mgmt: (arg: boolean) => number; } & StateSource<...>'
);
});

Expand Down Expand Up @@ -790,7 +790,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store',
'{ foo: Signal<number>; bar: Signal<string>; baz: (x: number) => void; } & StateSource<{ foo: number; }>'
'{ readonly foo: Signal<number>; readonly bar: Signal<string>; readonly baz: (x: number) => void; } & StateSource<{ foo: number; }>'
);
});

Expand Down Expand Up @@ -838,7 +838,7 @@ describe('signalStore', () => {

expectSnippet(snippet).toInfer(
'store',
'{ count1: Signal<number>; doubleCount2: Signal<number>; increment1: () => void; } & StateSource<{ count1: number; }>'
'{ readonly count1: Signal<number>; readonly doubleCount2: Signal<number>; readonly increment1: () => void; } & StateSource<{ count1: number; }>'
);
});

Expand Down
10 changes: 6 additions & 4 deletions modules/signals/src/signal-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ type SignalStoreConfig = { providedIn?: 'root'; protectedState?: boolean };

type SignalStoreMembers<FeatureResult extends SignalStoreFeatureResult> =
Prettify<
OmitPrivate<
StateSignals<FeatureResult['state']> &
FeatureResult['props'] &
FeatureResult['methods']
Readonly<
OmitPrivate<
StateSignals<FeatureResult['state']> &
FeatureResult['props'] &
FeatureResult['methods']
>
>
>;

Expand Down
4 changes: 2 additions & 2 deletions modules/signals/src/state-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ const STATE_WATCHERS = new WeakMap<Signal<object>, Array<StateWatcher<any>>>();
export const STATE_SOURCE = Symbol('STATE_SOURCE');

export type WritableStateSource<State extends object> = {
[STATE_SOURCE]: WritableSignal<State>;
readonly [STATE_SOURCE]: WritableSignal<State>;
};

export type StateSource<State extends object> = {
[STATE_SOURCE]: Signal<State>;
readonly [STATE_SOURCE]: Signal<State>;
};

export type PartialStateUpdater<State extends object> = (
Expand Down
0