@ngxs/store 21.0.0-dev.master-a4d1cfd → 21.0.0-dev.master-31acb66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -6,12 +6,28 @@ const isNumber = (value) => typeof value === 'number';
|
|
|
6
6
|
const invalidIndex = (index) => Number.isNaN(index) || index === -1;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Adds items to the end of an array without mutating the original. Handles
|
|
10
|
+
* the case where the array property does not exist yet, so callers do not
|
|
11
|
+
* need to initialise it before appending. A `null`, `undefined`, or empty
|
|
12
|
+
* `items` argument is treated as a no-op to allow safe pass-through of
|
|
13
|
+
* optional data.
|
|
14
|
+
*
|
|
15
|
+
* @param items - Items to add to the end of the array.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* // Add a new zebra to the end of the list without touching the rest of state.
|
|
20
|
+
* ctx.setState(
|
|
21
|
+
* patch<AnimalsStateModel>({
|
|
22
|
+
* zebras: append<string>([action.payload])
|
|
23
|
+
* })
|
|
24
|
+
* );
|
|
25
|
+
* ```
|
|
10
26
|
*/
|
|
11
27
|
function append(items) {
|
|
12
28
|
return function appendOperator(existing) {
|
|
13
|
-
//
|
|
14
|
-
//
|
|
29
|
+
// Nothing meaningful to append, so preserve the existing reference
|
|
30
|
+
// to avoid invalidating memoized selectors unnecessarily.
|
|
15
31
|
const itemsNotProvidedButExistingIs = (!items || !items.length) && existing;
|
|
16
32
|
if (itemsNotProvidedButExistingIs) {
|
|
17
33
|
return existing;
|
|
@@ -19,12 +35,29 @@ function append(items) {
|
|
|
19
35
|
if (isArray(existing)) {
|
|
20
36
|
return existing.concat(items);
|
|
21
37
|
}
|
|
22
|
-
//
|
|
23
|
-
//
|
|
38
|
+
// The array property was never initialised, so `items` becomes the
|
|
39
|
+
// initial state rather than being appended to a non-existent array.
|
|
24
40
|
return items;
|
|
25
41
|
};
|
|
26
42
|
}
|
|
27
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Chains multiple state operators so they execute left-to-right, each
|
|
46
|
+
* receiving the output of the previous one. Useful when several independent
|
|
47
|
+
* transformations must be applied to the same state slice in a single atomic
|
|
48
|
+
* update, avoiding multiple `setState` calls.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* // Apply two independent array mutations in one atomic setState call.
|
|
53
|
+
* ctx.setState(
|
|
54
|
+
* compose<AnimalsStateModel>(
|
|
55
|
+
* patch({ zebras: append<string>([action.zebraName]) }),
|
|
56
|
+
* patch({ pandas: removeItem<string>(name => name === action.pandaToRemove) })
|
|
57
|
+
* )
|
|
58
|
+
* );
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
28
61
|
function compose(...operators) {
|
|
29
62
|
return function composeOperator(existing) {
|
|
30
63
|
return operators.reduce((accumulator, operator) => operator(accumulator), existing);
|
|
@@ -32,32 +65,51 @@ function compose(...operators) {
|
|
|
32
65
|
}
|
|
33
66
|
|
|
34
67
|
function retrieveValue(operatorOrValue, existing) {
|
|
35
|
-
//
|
|
36
|
-
//
|
|
68
|
+
// Delegate to the operator so a derived transformation can be applied
|
|
69
|
+
// rather than substituting a static value.
|
|
37
70
|
if (isStateOperator(operatorOrValue)) {
|
|
38
71
|
const value = operatorOrValue(existing);
|
|
39
72
|
return value;
|
|
40
73
|
}
|
|
41
|
-
//
|
|
42
|
-
// e.g. `elseOperatorOrValue` is `undefined`
|
|
43
|
-
// then we just return an original value
|
|
74
|
+
// No else branch was provided, so leave the state unchanged.
|
|
44
75
|
if (operatorOrValue === undefined) {
|
|
45
76
|
return existing;
|
|
46
77
|
}
|
|
47
78
|
return operatorOrValue;
|
|
48
79
|
}
|
|
49
80
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* @param
|
|
81
|
+
* Applies one of two operators (or values) based on a condition, keeping
|
|
82
|
+
* conditional logic out of action handlers and inside the state mutation
|
|
83
|
+
* pipeline where it belongs.
|
|
84
|
+
*
|
|
85
|
+
* @param condition - A boolean or a predicate receiving the current state value.
|
|
86
|
+
* Use a predicate when the decision depends on the existing state rather than
|
|
87
|
+
* external data available at dispatch time.
|
|
88
|
+
* @param trueOperatorOrValue - Applied when `condition` is truthy.
|
|
89
|
+
* @param elseOperatorOrValue - Applied when `condition` is falsy. Omit to
|
|
90
|
+
* leave the state unchanged in the false branch.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* // Only add a panda when the list has fewer than 5 — the cap is enforced
|
|
95
|
+
* // inside the operator so the action handler stays free of branching logic.
|
|
96
|
+
* ctx.setState(
|
|
97
|
+
* patch<AnimalsStateModel>({
|
|
98
|
+
* pandas: iif(
|
|
99
|
+
* pandas => pandas.length < 5,
|
|
100
|
+
* append<string>([action.payload])
|
|
101
|
+
* )
|
|
102
|
+
* })
|
|
103
|
+
* );
|
|
104
|
+
* ```
|
|
55
105
|
*/
|
|
56
106
|
function iif(condition, trueOperatorOrValue, elseOperatorOrValue) {
|
|
57
107
|
return function iifOperator(existing) {
|
|
58
|
-
//
|
|
108
|
+
// Normalise to a boolean so both plain booleans and predicates
|
|
109
|
+
// share the same resolution path below.
|
|
59
110
|
let result = !!condition;
|
|
60
|
-
//
|
|
111
|
+
// Predicates receive the current state value so the decision can be
|
|
112
|
+
// based on live state rather than values captured at dispatch time.
|
|
61
113
|
if (isPredicate(condition)) {
|
|
62
114
|
result = condition(existing);
|
|
63
115
|
}
|
|
@@ -69,25 +121,51 @@ function iif(condition, trueOperatorOrValue, elseOperatorOrValue) {
|
|
|
69
121
|
}
|
|
70
122
|
|
|
71
123
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
124
|
+
* Inserts an item into an array without mutating the original, satisfying
|
|
125
|
+
* NGXS's immutability requirement. Handles the case where the array property
|
|
126
|
+
* does not exist yet, so callers do not need to initialise it first.
|
|
127
|
+
*
|
|
128
|
+
* @param value - The item to insert. A `null` or `undefined` value is a no-op
|
|
129
|
+
* so that callers can pass through optional data safely.
|
|
130
|
+
* @param beforePosition - Index before which to insert. Omit (or pass a
|
|
131
|
+
* non-positive number) to prepend to the beginning of the array.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* // Prepend a new zebra (no position = insert at index 0).
|
|
136
|
+
* ctx.setState(
|
|
137
|
+
* patch<AnimalsStateModel>({
|
|
138
|
+
* zebras: insertItem<string>(action.payload)
|
|
139
|
+
* })
|
|
140
|
+
* );
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* // Insert before index 2, shifting subsequent items right.
|
|
146
|
+
* ctx.setState(
|
|
147
|
+
* patch<AnimalsStateModel>({
|
|
148
|
+
* zebras: insertItem<string>(action.payload, 2)
|
|
149
|
+
* })
|
|
150
|
+
* );
|
|
151
|
+
* ```
|
|
74
152
|
*/
|
|
75
153
|
function insertItem(value, beforePosition) {
|
|
76
154
|
return function insertItemOperator(existing) {
|
|
77
|
-
//
|
|
78
|
-
//
|
|
155
|
+
// `== null` covers both `null` and `undefined` while letting falsy
|
|
156
|
+
// values like `0` or `false` through, where `!value` would not.
|
|
79
157
|
if (value == null && existing) {
|
|
80
158
|
return existing;
|
|
81
159
|
}
|
|
82
|
-
//
|
|
160
|
+
// The array property may not have been initialised yet; treat it as
|
|
161
|
+
// empty so callers don't have to guard against that case themselves.
|
|
83
162
|
if (!isArray(existing)) {
|
|
84
163
|
return [value];
|
|
85
164
|
}
|
|
86
165
|
const clone = existing.slice();
|
|
87
166
|
let index = 0;
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
// everything except number will return false here
|
|
167
|
+
// `> 0` rather than `>= 0` intentionally: non-numeric values coerce
|
|
168
|
+
// to NaN and fail this check, so no explicit `isNumber` call is needed.
|
|
91
169
|
if (beforePosition > 0) {
|
|
92
170
|
index = beforePosition;
|
|
93
171
|
}
|
|
@@ -96,6 +174,40 @@ function insertItem(value, beforePosition) {
|
|
|
96
174
|
};
|
|
97
175
|
}
|
|
98
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Applies a partial update to a state object, only cloning it when at least
|
|
179
|
+
* one property actually changes. This preserves referential equality for
|
|
180
|
+
* unchanged states, preventing unnecessary re-renders in `OnPush` components
|
|
181
|
+
* and keeping memoized selectors from recalculating.
|
|
182
|
+
*
|
|
183
|
+
* Each property in `patchObject` can itself be a state operator, enabling
|
|
184
|
+
* nested immutable updates without manually spreading every level of the tree.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* // Add an optional property to a state slice without touching existing ones.
|
|
189
|
+
* ctx.setState(
|
|
190
|
+
* patch<AnimalsStateModel>({ monkeys: [] })
|
|
191
|
+
* );
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* // Deep update — specify explicit types at each level so TypeScript can
|
|
197
|
+
* // catch property name mistakes in nested patches.
|
|
198
|
+
* ctx.setState(
|
|
199
|
+
* patch<AddressStateModel>({
|
|
200
|
+
* country: patch<AddressStateModel['country']>({
|
|
201
|
+
* city: patch<AddressStateModel['country']['city']>({
|
|
202
|
+
* address: patch<AddressStateModel['country']['city']['address']>({
|
|
203
|
+
* line1: action.line1
|
|
204
|
+
* })
|
|
205
|
+
* })
|
|
206
|
+
* })
|
|
207
|
+
* })
|
|
208
|
+
* );
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
99
211
|
function patch(patchObject) {
|
|
100
212
|
return function patchStateOperator(existing) {
|
|
101
213
|
let clone = null;
|
|
@@ -116,6 +228,24 @@ function patch(patchObject) {
|
|
|
116
228
|
};
|
|
117
229
|
}
|
|
118
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Like `patch`, but safe to call when the state slice is `null` or
|
|
233
|
+
* `undefined`. Treats a missing slice as an empty object so the patch is
|
|
234
|
+
* applied against a clean baseline rather than throwing. Useful for lazily
|
|
235
|
+
* initialised state properties or optional sub-states that may not have been
|
|
236
|
+
* set yet.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* // Update a nested preferences slice that starts as null — no prior
|
|
241
|
+
* // null-check needed; safePatch treats null as an empty object.
|
|
242
|
+
* ctx.setState(
|
|
243
|
+
* patch<UserStateModel>({
|
|
244
|
+
* preferences: safePatch<UserPreferences>({ theme: action.theme })
|
|
245
|
+
* })
|
|
246
|
+
* );
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
119
249
|
function safePatch(patchSpec) {
|
|
120
250
|
const patcher = patch(patchSpec);
|
|
121
251
|
return function patchSafely(existing) {
|
|
@@ -124,10 +254,30 @@ function safePatch(patchSpec) {
|
|
|
124
254
|
}
|
|
125
255
|
|
|
126
256
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
257
|
+
* Replaces or transforms a single array element without cloning elements that
|
|
258
|
+
* did not change, preserving referential equality for the rest of the array.
|
|
259
|
+
* Returns the original array reference when nothing changed, keeping
|
|
260
|
+
* memoized selectors and `OnPush` components from re-rendering unnecessarily.
|
|
261
|
+
*
|
|
262
|
+
* @param selector - The index to update, or a predicate used to locate the
|
|
263
|
+
* item. Prefer a predicate when the item's position may have shifted since the
|
|
264
|
+
* index was last known.
|
|
265
|
+
* @param operatorOrValue - The replacement value, or a state operator applied
|
|
266
|
+
* to the existing element when a derived update is needed.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* // Rename a panda — locate it by current name so the index doesn't need
|
|
271
|
+
* // to be known ahead of time.
|
|
272
|
+
* ctx.setState(
|
|
273
|
+
* patch<AnimalsStateModel>({
|
|
274
|
+
* pandas: updateItem<string>(
|
|
275
|
+
* name => name === action.payload.name,
|
|
276
|
+
* action.payload.newName
|
|
277
|
+
* )
|
|
278
|
+
* })
|
|
279
|
+
* );
|
|
280
|
+
* ```
|
|
131
281
|
*/
|
|
132
282
|
function updateItem(selector, operatorOrValue) {
|
|
133
283
|
return function updateItemOperator(existing) {
|
|
@@ -142,8 +292,8 @@ function updateItem(selector, operatorOrValue) {
|
|
|
142
292
|
return existing;
|
|
143
293
|
}
|
|
144
294
|
let value = null;
|
|
145
|
-
//
|
|
146
|
-
//
|
|
295
|
+
// Resolve the new value before touching the array so we can bail out
|
|
296
|
+
// early and skip the clone when nothing actually changed.
|
|
147
297
|
const theOperatorOrValue = operatorOrValue;
|
|
148
298
|
if (isStateOperator(theOperatorOrValue)) {
|
|
149
299
|
value = theOperatorOrValue(existing[index]);
|
|
@@ -151,8 +301,8 @@ function updateItem(selector, operatorOrValue) {
|
|
|
151
301
|
else {
|
|
152
302
|
value = theOperatorOrValue;
|
|
153
303
|
}
|
|
154
|
-
//
|
|
155
|
-
//
|
|
304
|
+
// Return the original reference to prevent memoized selectors and
|
|
305
|
+
// OnPush components from reacting to a no-op update.
|
|
156
306
|
if (value === existing[index]) {
|
|
157
307
|
return existing;
|
|
158
308
|
}
|
|
@@ -163,7 +313,77 @@ function updateItem(selector, operatorOrValue) {
|
|
|
163
313
|
}
|
|
164
314
|
|
|
165
315
|
/**
|
|
166
|
-
*
|
|
316
|
+
* Replaces or transforms every array element that matches the predicate.
|
|
317
|
+
* Unlike `updateItem`, which stops at the first match, this operator walks
|
|
318
|
+
* the entire array so all qualifying elements are updated in one pass.
|
|
319
|
+
*
|
|
320
|
+
* Always returns a new array reference, even when no elements matched or the
|
|
321
|
+
* values produced by the operator are identical to the originals. Use
|
|
322
|
+
* `updateItem` instead when only a single element needs updating and
|
|
323
|
+
* referential equality on no-op updates matters.
|
|
324
|
+
*
|
|
325
|
+
* @param selector - Predicate used to decide which elements to update.
|
|
326
|
+
* @param operatorOrValue - Replacement value, or a state operator applied
|
|
327
|
+
* to each matching element when a derived update is needed.
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* ```ts
|
|
331
|
+
* // Mark every inactive animal as active in one setState call.
|
|
332
|
+
* ctx.setState(
|
|
333
|
+
* patch<AnimalsStateModel>({
|
|
334
|
+
* animals: updateItems<Animal>(
|
|
335
|
+
* animal => !animal.active,
|
|
336
|
+
* patch({ active: true })
|
|
337
|
+
* )
|
|
338
|
+
* })
|
|
339
|
+
* );
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
function updateItems(selector, operatorOrValue) {
|
|
343
|
+
return function updateItemsOperator(existing) {
|
|
344
|
+
if (!Array.isArray(existing)) {
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
else if (existing.length === 0) {
|
|
348
|
+
return existing;
|
|
349
|
+
}
|
|
350
|
+
const clone = existing.slice();
|
|
351
|
+
for (let index = 0; index < clone.length; index++) {
|
|
352
|
+
let value = clone[index];
|
|
353
|
+
if (selector(value)) {
|
|
354
|
+
const theOperatorOrValue = operatorOrValue;
|
|
355
|
+
if (isStateOperator(theOperatorOrValue)) {
|
|
356
|
+
value = theOperatorOrValue(value);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
value = theOperatorOrValue;
|
|
360
|
+
}
|
|
361
|
+
clone[index] = value;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return clone;
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Removes a single element from an array without mutating the original.
|
|
370
|
+
* Returns the original array reference when no matching item is found, so
|
|
371
|
+
* memoized selectors are not invalidated by a no-op removal.
|
|
372
|
+
*
|
|
373
|
+
* @param selector - The index to remove, or a predicate used to locate the
|
|
374
|
+
* item. Prefer a predicate when the item's position is not guaranteed to be
|
|
375
|
+
* stable across concurrent state updates.
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```ts
|
|
379
|
+
* // Remove a panda by name — a predicate is safer than a hard-coded index
|
|
380
|
+
* // because the array order may change between dispatch and execution.
|
|
381
|
+
* ctx.setState(
|
|
382
|
+
* patch<AnimalsStateModel>({
|
|
383
|
+
* pandas: removeItem<string>(name => name === action.payload)
|
|
384
|
+
* })
|
|
385
|
+
* );
|
|
386
|
+
* ```
|
|
167
387
|
*/
|
|
168
388
|
function removeItem(selector) {
|
|
169
389
|
return function removeItemOperator(existing) {
|
|
@@ -193,5 +413,5 @@ function removeItem(selector) {
|
|
|
193
413
|
* Generated bundle index. Do not edit.
|
|
194
414
|
*/
|
|
195
415
|
|
|
196
|
-
export { append, compose, iif, insertItem, isPredicate, isStateOperator, patch, removeItem, safePatch, updateItem };
|
|
416
|
+
export { append, compose, iif, insertItem, isPredicate, isStateOperator, patch, removeItem, safePatch, updateItem, updateItems };
|
|
197
417
|
//# sourceMappingURL=ngxs-store-operators.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngxs-store-operators.mjs","sources":["../../../packages/store/operators/src/utils.ts","../../../packages/store/operators/src/append.ts","../../../packages/store/operators/src/compose.ts","../../../packages/store/operators/src/iif.ts","../../../packages/store/operators/src/insert-item.ts","../../../packages/store/operators/src/patch.ts","../../../packages/store/operators/src/safe-patch.ts","../../../packages/store/operators/src/update-item.ts","../../../packages/store/operators/src/remove-item.ts","../../../packages/store/operators/src/index.ts","../../../packages/store/operators/src/ngxs-store-operators.ts"],"sourcesContent":["import { StateOperator } from './types';\n\nexport const isArray = Array.isArray;\n\nexport type Predicate<T = any> = (value: T | Readonly<T>) => boolean;\n\nconst isFunction = (value: unknown) => typeof value == 'function';\n\nexport const isStateOperator = isFunction as <T>(\n value: T | StateOperator<T>\n) => value is StateOperator<T>;\n\nexport const isPredicate = isFunction as <T>(\n value: Predicate<T> | boolean | number\n) => value is Predicate<T>;\n\nexport const isNumber = (value: unknown): value is number => typeof value === 'number';\n\nexport const invalidIndex = (index: number) => Number.isNaN(index) || index === -1;\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isArray } from './utils';\n\n/**\n * @param items - Specific items to append to the end of an array\n */\nexport function append<T>(items: NoInfer<T[]>): StateOperator<T[]> {\n return function appendOperator(existing: ExistingState<T[]>): T[] {\n // If `items` is `undefined` or `null` or `[]` but `existing` is provided\n // just return `existing`\n const itemsNotProvidedButExistingIs = (!items || !items.length) && existing;\n if (itemsNotProvidedButExistingIs) {\n return existing as unknown as T[];\n }\n\n if (isArray(existing)) {\n return existing.concat(items as unknown as ExistingState<T[]>);\n }\n\n // For example if some property is added dynamically\n // and didn't exist before thus it's not `ArrayLike`\n return items as unknown as T[];\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\nexport function compose<T>(...operators: NoInfer<StateOperator<T>[]>): StateOperator<T> {\n return function composeOperator(existing: ExistingState<T>): T {\n return operators.reduce(\n (accumulator, operator) => operator(accumulator as ExistingState<T>),\n existing as T\n );\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\nimport { isStateOperator, isPredicate, Predicate } from './utils';\n\nfunction retrieveValue<T>(\n operatorOrValue: StateOperator<T> | T,\n existing: ExistingState<T>\n): T {\n // If state operator is a function\n // then call it with an original value\n if (isStateOperator(operatorOrValue)) {\n const value = operatorOrValue(existing);\n return value as T;\n }\n\n // If operator or value was not provided\n // e.g. `elseOperatorOrValue` is `undefined`\n // then we just return an original value\n if (operatorOrValue === undefined) {\n return existing as T;\n }\n\n return operatorOrValue as T;\n}\n\n/**\n * @param condition - Condition can be a plain boolean value or a function,\n * that returns boolean, also this function can take a value as an argument\n * to which this state operator applies\n * @param trueOperatorOrValue - Any value or a state operator\n * @param elseOperatorOrValue - Any value or a state operator\n */\nexport function iif<T>(\n condition: NoInfer<Predicate<T>> | boolean,\n trueOperatorOrValue: NoInfer<StateOperator<T> | T>,\n elseOperatorOrValue?: NoInfer<StateOperator<T> | T>\n): StateOperator<T> {\n return function iifOperator(existing: ExistingState<T>): T {\n // Convert the value to a boolean\n let result = !!condition;\n // but if it is a function then run it to get the result\n if (isPredicate(condition)) {\n result = condition(existing as T);\n }\n\n if (result) {\n return retrieveValue<T>(trueOperatorOrValue as StateOperator<T> | T, existing);\n }\n\n return retrieveValue<T>(elseOperatorOrValue! as StateOperator<T> | T, existing);\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isArray } from './utils';\n\n/**\n * @param value - Value to insert\n * @param [beforePosition] - Specified index to insert value before, optional\n */\nexport function insertItem<T>(value: NoInfer<T>, beforePosition?: number): StateOperator<T[]> {\n return function insertItemOperator(existing: ExistingState<T[]>): T[] {\n // Have to check explicitly for `null` and `undefined`\n // because `value` can be `0`, thus `!value` will return `true`\n if (value == null && existing) {\n return existing as T[];\n }\n\n // Property may be dynamic and might not existed before\n if (!isArray(existing)) {\n return [value as unknown as T];\n }\n\n const clone = existing.slice();\n\n let index = 0;\n\n // No need to call `isNumber`\n // as we are checking `> 0` not `>= 0`\n // everything except number will return false here\n if (beforePosition! > 0) {\n index = beforePosition!;\n }\n\n clone.splice(index, 0, value as unknown as T);\n return clone;\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isStateOperator } from './utils';\n\ntype NotUndefined<T> = T extends undefined ? never : T;\n\nexport type ɵPatchSpec<T> = { [P in keyof T]?: T[P] | StateOperator<NotUndefined<T[P]>> };\n\nexport function patch<T extends Record<string, any>>(\n patchObject: NoInfer<ɵPatchSpec<T>>\n): StateOperator<T> {\n return function patchStateOperator(existing: ExistingState<T>): T {\n let clone = null;\n for (const k in patchObject) {\n const newValue = patchObject[k];\n const existingPropValue = existing?.[k];\n const newPropValue = isStateOperator(newValue)\n ? newValue(<any>existingPropValue)\n : newValue;\n if (newPropValue !== existingPropValue) {\n if (!clone) {\n clone = { ...(<any>existing) };\n }\n clone[k] = newPropValue;\n }\n }\n return clone || existing;\n };\n}\n","import { patch, type ɵPatchSpec } from './patch';\nimport type { ExistingState, NoInfer, StateOperator } from './types';\n\nexport function safePatch<T extends object>(\n patchSpec: NoInfer<ɵPatchSpec<T>>\n): StateOperator<T> {\n const patcher = patch(patchSpec as ɵPatchSpec<T>) as unknown as StateOperator<\n Readonly<NonNullable<T>>\n >;\n return function patchSafely(existing: ExistingState<T>): T {\n return patcher(existing ?? ({} as ExistingState<Readonly<NonNullable<T>>>));\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\nimport { isStateOperator, isPredicate, isNumber, invalidIndex, Predicate } from './utils';\n\n/**\n * @param selector - Index of item in the array or a predicate function\n * that can be provided in `Array.prototype.findIndex`\n * @param operatorOrValue - New value under the `selector` index or a\n * function that can be applied to an existing value\n */\nexport function updateItem<T>(\n selector: number | NoInfer<Predicate<T>>,\n operatorOrValue: NoInfer<T> | NoInfer<StateOperator<T>>\n): StateOperator<T[]> {\n return function updateItemOperator(existing: ExistingState<T[]>): T[] {\n let index = -1;\n\n if (isPredicate(selector)) {\n index = existing.findIndex(selector as Predicate<T>);\n } else if (isNumber(selector)) {\n index = selector;\n }\n\n if (invalidIndex(index)) {\n return existing as T[];\n }\n\n let value: T = null!;\n // Need to check if the new item value will change the existing item value\n // then, only if it will change it then clone the array and set the item\n const theOperatorOrValue = operatorOrValue as T | StateOperator<T>;\n if (isStateOperator(theOperatorOrValue)) {\n value = theOperatorOrValue(existing[index] as ExistingState<T>);\n } else {\n value = theOperatorOrValue;\n }\n\n // If the value hasn't been mutated\n // then we just return `existing` array\n if (value === existing[index]) {\n return existing as T[];\n }\n\n const clone = existing.slice();\n clone[index] = value as T;\n return clone;\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isPredicate, isNumber, invalidIndex, Predicate } from './utils';\n\n/**\n * @param selector - index or predicate to remove an item from an array by\n */\nexport function removeItem<T>(selector: number | NoInfer<Predicate<T>>): StateOperator<T[]> {\n return function removeItemOperator(existing: ExistingState<T[]>): T[] {\n let index = -1;\n\n if (isPredicate(selector)) {\n index = existing.findIndex(selector);\n } else if (isNumber(selector)) {\n index = selector;\n }\n\n if (invalidIndex(index)) {\n return existing as T[];\n }\n\n const clone = existing.slice();\n clone.splice(index, 1);\n return clone;\n };\n}\n","/**\n * @module\n * @description\n * Entry point for all public APIs of this package.\n */\nexport { append } from './append';\nexport { compose } from './compose';\nexport { iif } from './iif';\nexport { insertItem } from './insert-item';\nexport { patch, type ɵPatchSpec } from './patch';\nexport { safePatch } from './safe-patch';\nexport { isStateOperator, isPredicate, type Predicate } from './utils';\nexport { updateItem } from './update-item';\nexport { removeItem } from './remove-item';\nexport type { ExistingState, NoInfer, StateOperator } from './types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngxs-store-operators.mjs","sources":["../../../packages/store/operators/src/utils.ts","../../../packages/store/operators/src/append.ts","../../../packages/store/operators/src/compose.ts","../../../packages/store/operators/src/iif.ts","../../../packages/store/operators/src/insert-item.ts","../../../packages/store/operators/src/patch.ts","../../../packages/store/operators/src/safe-patch.ts","../../../packages/store/operators/src/update-item.ts","../../../packages/store/operators/src/update-items.ts","../../../packages/store/operators/src/remove-item.ts","../../../packages/store/operators/src/index.ts","../../../packages/store/operators/src/ngxs-store-operators.ts"],"sourcesContent":["import { StateOperator } from './types';\n\nexport const isArray = Array.isArray;\n\nexport type Predicate<T = any> = (value: T | Readonly<T>) => boolean;\n\nconst isFunction = (value: unknown) => typeof value == 'function';\n\nexport const isStateOperator = isFunction as <T>(\n value: T | StateOperator<T>\n) => value is StateOperator<T>;\n\nexport const isPredicate = isFunction as <T>(\n value: Predicate<T> | boolean | number\n) => value is Predicate<T>;\n\nexport const isNumber = (value: unknown): value is number => typeof value === 'number';\n\nexport const invalidIndex = (index: number) => Number.isNaN(index) || index === -1;\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isArray } from './utils';\n\n/**\n * Adds items to the end of an array without mutating the original. Handles\n * the case where the array property does not exist yet, so callers do not\n * need to initialise it before appending. A `null`, `undefined`, or empty\n * `items` argument is treated as a no-op to allow safe pass-through of\n * optional data.\n *\n * @param items - Items to add to the end of the array.\n *\n * @example\n * ```ts\n * // Add a new zebra to the end of the list without touching the rest of state.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * zebras: append<string>([action.payload])\n * })\n * );\n * ```\n */\nexport function append<T>(items: NoInfer<T[]>): StateOperator<T[]> {\n return function appendOperator(existing: ExistingState<T[]>): T[] {\n // Nothing meaningful to append, so preserve the existing reference\n // to avoid invalidating memoized selectors unnecessarily.\n const itemsNotProvidedButExistingIs = (!items || !items.length) && existing;\n if (itemsNotProvidedButExistingIs) {\n return existing as unknown as T[];\n }\n\n if (isArray(existing)) {\n return existing.concat(items as unknown as ExistingState<T[]>);\n }\n\n // The array property was never initialised, so `items` becomes the\n // initial state rather than being appended to a non-existent array.\n return items as unknown as T[];\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\n/**\n * Chains multiple state operators so they execute left-to-right, each\n * receiving the output of the previous one. Useful when several independent\n * transformations must be applied to the same state slice in a single atomic\n * update, avoiding multiple `setState` calls.\n *\n * @example\n * ```ts\n * // Apply two independent array mutations in one atomic setState call.\n * ctx.setState(\n * compose<AnimalsStateModel>(\n * patch({ zebras: append<string>([action.zebraName]) }),\n * patch({ pandas: removeItem<string>(name => name === action.pandaToRemove) })\n * )\n * );\n * ```\n */\nexport function compose<T>(...operators: NoInfer<StateOperator<T>[]>): StateOperator<T> {\n return function composeOperator(existing: ExistingState<T>): T {\n return operators.reduce(\n (accumulator, operator) => operator(accumulator as ExistingState<T>),\n existing as T\n );\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\nimport { isStateOperator, isPredicate, Predicate } from './utils';\n\nfunction retrieveValue<T>(\n operatorOrValue: StateOperator<T> | T,\n existing: ExistingState<T>\n): T {\n // Delegate to the operator so a derived transformation can be applied\n // rather than substituting a static value.\n if (isStateOperator(operatorOrValue)) {\n const value = operatorOrValue(existing);\n return value as T;\n }\n\n // No else branch was provided, so leave the state unchanged.\n if (operatorOrValue === undefined) {\n return existing as T;\n }\n\n return operatorOrValue as T;\n}\n\n/**\n * Applies one of two operators (or values) based on a condition, keeping\n * conditional logic out of action handlers and inside the state mutation\n * pipeline where it belongs.\n *\n * @param condition - A boolean or a predicate receiving the current state value.\n * Use a predicate when the decision depends on the existing state rather than\n * external data available at dispatch time.\n * @param trueOperatorOrValue - Applied when `condition` is truthy.\n * @param elseOperatorOrValue - Applied when `condition` is falsy. Omit to\n * leave the state unchanged in the false branch.\n *\n * @example\n * ```ts\n * // Only add a panda when the list has fewer than 5 — the cap is enforced\n * // inside the operator so the action handler stays free of branching logic.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * pandas: iif(\n * pandas => pandas.length < 5,\n * append<string>([action.payload])\n * )\n * })\n * );\n * ```\n */\nexport function iif<T>(\n condition: NoInfer<Predicate<T>> | boolean,\n trueOperatorOrValue: NoInfer<StateOperator<T> | T>,\n elseOperatorOrValue?: NoInfer<StateOperator<T> | T>\n): StateOperator<T> {\n return function iifOperator(existing: ExistingState<T>): T {\n // Normalise to a boolean so both plain booleans and predicates\n // share the same resolution path below.\n let result = !!condition;\n // Predicates receive the current state value so the decision can be\n // based on live state rather than values captured at dispatch time.\n if (isPredicate(condition)) {\n result = condition(existing as T);\n }\n\n if (result) {\n return retrieveValue<T>(trueOperatorOrValue as StateOperator<T> | T, existing);\n }\n\n return retrieveValue<T>(elseOperatorOrValue! as StateOperator<T> | T, existing);\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isArray } from './utils';\n\n/**\n * Inserts an item into an array without mutating the original, satisfying\n * NGXS's immutability requirement. Handles the case where the array property\n * does not exist yet, so callers do not need to initialise it first.\n *\n * @param value - The item to insert. A `null` or `undefined` value is a no-op\n * so that callers can pass through optional data safely.\n * @param beforePosition - Index before which to insert. Omit (or pass a\n * non-positive number) to prepend to the beginning of the array.\n *\n * @example\n * ```ts\n * // Prepend a new zebra (no position = insert at index 0).\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * zebras: insertItem<string>(action.payload)\n * })\n * );\n * ```\n *\n * @example\n * ```ts\n * // Insert before index 2, shifting subsequent items right.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * zebras: insertItem<string>(action.payload, 2)\n * })\n * );\n * ```\n */\nexport function insertItem<T>(value: NoInfer<T>, beforePosition?: number): StateOperator<T[]> {\n return function insertItemOperator(existing: ExistingState<T[]>): T[] {\n // `== null` covers both `null` and `undefined` while letting falsy\n // values like `0` or `false` through, where `!value` would not.\n if (value == null && existing) {\n return existing as T[];\n }\n\n // The array property may not have been initialised yet; treat it as\n // empty so callers don't have to guard against that case themselves.\n if (!isArray(existing)) {\n return [value as unknown as T];\n }\n\n const clone = existing.slice();\n\n let index = 0;\n\n // `> 0` rather than `>= 0` intentionally: non-numeric values coerce\n // to NaN and fail this check, so no explicit `isNumber` call is needed.\n if (beforePosition! > 0) {\n index = beforePosition!;\n }\n\n clone.splice(index, 0, value as unknown as T);\n return clone;\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isStateOperator } from './utils';\n\ntype NotUndefined<T> = T extends undefined ? never : T;\n\nexport type ɵPatchSpec<T> = { [P in keyof T]?: T[P] | StateOperator<NotUndefined<T[P]>> };\n\n/**\n * Applies a partial update to a state object, only cloning it when at least\n * one property actually changes. This preserves referential equality for\n * unchanged states, preventing unnecessary re-renders in `OnPush` components\n * and keeping memoized selectors from recalculating.\n *\n * Each property in `patchObject` can itself be a state operator, enabling\n * nested immutable updates without manually spreading every level of the tree.\n *\n * @example\n * ```ts\n * // Add an optional property to a state slice without touching existing ones.\n * ctx.setState(\n * patch<AnimalsStateModel>({ monkeys: [] })\n * );\n * ```\n *\n * @example\n * ```ts\n * // Deep update — specify explicit types at each level so TypeScript can\n * // catch property name mistakes in nested patches.\n * ctx.setState(\n * patch<AddressStateModel>({\n * country: patch<AddressStateModel['country']>({\n * city: patch<AddressStateModel['country']['city']>({\n * address: patch<AddressStateModel['country']['city']['address']>({\n * line1: action.line1\n * })\n * })\n * })\n * })\n * );\n * ```\n */\nexport function patch<T extends Record<string, any>>(\n patchObject: NoInfer<ɵPatchSpec<T>>\n): StateOperator<T> {\n return function patchStateOperator(existing: ExistingState<T>): T {\n let clone = null;\n for (const k in patchObject) {\n const newValue = patchObject[k];\n const existingPropValue = existing?.[k];\n const newPropValue = isStateOperator(newValue)\n ? newValue(<any>existingPropValue)\n : newValue;\n if (newPropValue !== existingPropValue) {\n if (!clone) {\n clone = { ...(<any>existing) };\n }\n clone[k] = newPropValue;\n }\n }\n return clone || existing;\n };\n}\n","import { patch, type ɵPatchSpec } from './patch';\nimport type { ExistingState, NoInfer, StateOperator } from './types';\n\n/**\n * Like `patch`, but safe to call when the state slice is `null` or\n * `undefined`. Treats a missing slice as an empty object so the patch is\n * applied against a clean baseline rather than throwing. Useful for lazily\n * initialised state properties or optional sub-states that may not have been\n * set yet.\n *\n * @example\n * ```ts\n * // Update a nested preferences slice that starts as null — no prior\n * // null-check needed; safePatch treats null as an empty object.\n * ctx.setState(\n * patch<UserStateModel>({\n * preferences: safePatch<UserPreferences>({ theme: action.theme })\n * })\n * );\n * ```\n */\nexport function safePatch<T extends object>(\n patchSpec: NoInfer<ɵPatchSpec<T>>\n): StateOperator<T> {\n const patcher = patch(patchSpec as ɵPatchSpec<T>) as unknown as StateOperator<\n Readonly<NonNullable<T>>\n >;\n return function patchSafely(existing: ExistingState<T>): T {\n return patcher(existing ?? ({} as ExistingState<Readonly<NonNullable<T>>>));\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\nimport { isStateOperator, isPredicate, isNumber, invalidIndex, Predicate } from './utils';\n\n/**\n * Replaces or transforms a single array element without cloning elements that\n * did not change, preserving referential equality for the rest of the array.\n * Returns the original array reference when nothing changed, keeping\n * memoized selectors and `OnPush` components from re-rendering unnecessarily.\n *\n * @param selector - The index to update, or a predicate used to locate the\n * item. Prefer a predicate when the item's position may have shifted since the\n * index was last known.\n * @param operatorOrValue - The replacement value, or a state operator applied\n * to the existing element when a derived update is needed.\n *\n * @example\n * ```ts\n * // Rename a panda — locate it by current name so the index doesn't need\n * // to be known ahead of time.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * pandas: updateItem<string>(\n * name => name === action.payload.name,\n * action.payload.newName\n * )\n * })\n * );\n * ```\n */\nexport function updateItem<T>(\n selector: number | NoInfer<Predicate<T>>,\n operatorOrValue: NoInfer<T> | NoInfer<StateOperator<T>>\n): StateOperator<T[]> {\n return function updateItemOperator(existing: ExistingState<T[]>): T[] {\n let index = -1;\n\n if (isPredicate(selector)) {\n index = existing.findIndex(selector as Predicate<T>);\n } else if (isNumber(selector)) {\n index = selector;\n }\n\n if (invalidIndex(index)) {\n return existing as T[];\n }\n\n let value: T = null!;\n // Resolve the new value before touching the array so we can bail out\n // early and skip the clone when nothing actually changed.\n const theOperatorOrValue = operatorOrValue as T | StateOperator<T>;\n if (isStateOperator(theOperatorOrValue)) {\n value = theOperatorOrValue(existing[index] as ExistingState<T>);\n } else {\n value = theOperatorOrValue;\n }\n\n // Return the original reference to prevent memoized selectors and\n // OnPush components from reacting to a no-op update.\n if (value === existing[index]) {\n return existing as T[];\n }\n\n const clone = existing.slice();\n clone[index] = value as T;\n return clone;\n };\n}\n","import { type StateOperator, type ExistingState, type NoInfer } from './types';\nimport { isStateOperator, type Predicate } from './utils';\n\n/**\n * Replaces or transforms every array element that matches the predicate.\n * Unlike `updateItem`, which stops at the first match, this operator walks\n * the entire array so all qualifying elements are updated in one pass.\n *\n * Always returns a new array reference, even when no elements matched or the\n * values produced by the operator are identical to the originals. Use\n * `updateItem` instead when only a single element needs updating and\n * referential equality on no-op updates matters.\n *\n * @param selector - Predicate used to decide which elements to update.\n * @param operatorOrValue - Replacement value, or a state operator applied\n * to each matching element when a derived update is needed.\n *\n * @example\n * ```ts\n * // Mark every inactive animal as active in one setState call.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * animals: updateItems<Animal>(\n * animal => !animal.active,\n * patch({ active: true })\n * )\n * })\n * );\n * ```\n */\nexport function updateItems<T>(\n selector: NoInfer<Predicate<T>>,\n operatorOrValue: NoInfer<T> | NoInfer<StateOperator<T>>\n) {\n return function updateItemsOperator(existing: ExistingState<T[]>): T[] {\n if (!Array.isArray(existing)) {\n return [] as T[];\n } else if (existing.length === 0) {\n return existing as T[];\n }\n\n const clone = existing.slice();\n for (let index = 0; index < clone.length; index++) {\n let value = clone[index];\n if (selector(value)) {\n const theOperatorOrValue = operatorOrValue as T | StateOperator<T>;\n if (isStateOperator(theOperatorOrValue)) {\n value = theOperatorOrValue(value as ExistingState<T>);\n } else {\n value = theOperatorOrValue;\n }\n clone[index] = value;\n }\n }\n return clone;\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isPredicate, isNumber, invalidIndex, Predicate } from './utils';\n\n/**\n * Removes a single element from an array without mutating the original.\n * Returns the original array reference when no matching item is found, so\n * memoized selectors are not invalidated by a no-op removal.\n *\n * @param selector - The index to remove, or a predicate used to locate the\n * item. Prefer a predicate when the item's position is not guaranteed to be\n * stable across concurrent state updates.\n *\n * @example\n * ```ts\n * // Remove a panda by name — a predicate is safer than a hard-coded index\n * // because the array order may change between dispatch and execution.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * pandas: removeItem<string>(name => name === action.payload)\n * })\n * );\n * ```\n */\nexport function removeItem<T>(selector: number | NoInfer<Predicate<T>>): StateOperator<T[]> {\n return function removeItemOperator(existing: ExistingState<T[]>): T[] {\n let index = -1;\n\n if (isPredicate(selector)) {\n index = existing.findIndex(selector);\n } else if (isNumber(selector)) {\n index = selector;\n }\n\n if (invalidIndex(index)) {\n return existing as T[];\n }\n\n const clone = existing.slice();\n clone.splice(index, 1);\n return clone;\n };\n}\n","/**\n * @module\n * @description\n * Entry point for all public APIs of this package.\n */\nexport { append } from './append';\nexport { compose } from './compose';\nexport { iif } from './iif';\nexport { insertItem } from './insert-item';\nexport { patch, type ɵPatchSpec } from './patch';\nexport { safePatch } from './safe-patch';\nexport { isStateOperator, isPredicate, type Predicate } from './utils';\nexport { updateItem } from './update-item';\nexport { updateItems } from './update-items';\nexport { removeItem } from './remove-item';\nexport type { ExistingState, NoInfer, StateOperator } from './types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/operators/index.d.ts
CHANGED
|
@@ -55,10 +55,43 @@ type ExistingState<T> = T extends any ? ɵAsReadonly<T> : never;
|
|
|
55
55
|
type StateOperator<T> = (existing: ExistingState<T>) => T;
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
58
|
+
* Adds items to the end of an array without mutating the original. Handles
|
|
59
|
+
* the case where the array property does not exist yet, so callers do not
|
|
60
|
+
* need to initialise it before appending. A `null`, `undefined`, or empty
|
|
61
|
+
* `items` argument is treated as a no-op to allow safe pass-through of
|
|
62
|
+
* optional data.
|
|
63
|
+
*
|
|
64
|
+
* @param items - Items to add to the end of the array.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* // Add a new zebra to the end of the list without touching the rest of state.
|
|
69
|
+
* ctx.setState(
|
|
70
|
+
* patch<AnimalsStateModel>({
|
|
71
|
+
* zebras: append<string>([action.payload])
|
|
72
|
+
* })
|
|
73
|
+
* );
|
|
74
|
+
* ```
|
|
59
75
|
*/
|
|
60
76
|
declare function append<T>(items: NoInfer<T[]>): StateOperator<T[]>;
|
|
61
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Chains multiple state operators so they execute left-to-right, each
|
|
80
|
+
* receiving the output of the previous one. Useful when several independent
|
|
81
|
+
* transformations must be applied to the same state slice in a single atomic
|
|
82
|
+
* update, avoiding multiple `setState` calls.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* // Apply two independent array mutations in one atomic setState call.
|
|
87
|
+
* ctx.setState(
|
|
88
|
+
* compose<AnimalsStateModel>(
|
|
89
|
+
* patch({ zebras: append<string>([action.zebraName]) }),
|
|
90
|
+
* patch({ pandas: removeItem<string>(name => name === action.pandaToRemove) })
|
|
91
|
+
* )
|
|
92
|
+
* );
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
62
95
|
declare function compose<T>(...operators: NoInfer<StateOperator<T>[]>): StateOperator<T>;
|
|
63
96
|
|
|
64
97
|
type Predicate<T = any> = (value: T | Readonly<T>) => boolean;
|
|
@@ -66,17 +99,62 @@ declare const isStateOperator: <T>(value: T | StateOperator<T>) => value is Stat
|
|
|
66
99
|
declare const isPredicate: <T>(value: Predicate<T> | boolean | number) => value is Predicate<T>;
|
|
67
100
|
|
|
68
101
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* @param
|
|
102
|
+
* Applies one of two operators (or values) based on a condition, keeping
|
|
103
|
+
* conditional logic out of action handlers and inside the state mutation
|
|
104
|
+
* pipeline where it belongs.
|
|
105
|
+
*
|
|
106
|
+
* @param condition - A boolean or a predicate receiving the current state value.
|
|
107
|
+
* Use a predicate when the decision depends on the existing state rather than
|
|
108
|
+
* external data available at dispatch time.
|
|
109
|
+
* @param trueOperatorOrValue - Applied when `condition` is truthy.
|
|
110
|
+
* @param elseOperatorOrValue - Applied when `condition` is falsy. Omit to
|
|
111
|
+
* leave the state unchanged in the false branch.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* // Only add a panda when the list has fewer than 5 — the cap is enforced
|
|
116
|
+
* // inside the operator so the action handler stays free of branching logic.
|
|
117
|
+
* ctx.setState(
|
|
118
|
+
* patch<AnimalsStateModel>({
|
|
119
|
+
* pandas: iif(
|
|
120
|
+
* pandas => pandas.length < 5,
|
|
121
|
+
* append<string>([action.payload])
|
|
122
|
+
* )
|
|
123
|
+
* })
|
|
124
|
+
* );
|
|
125
|
+
* ```
|
|
74
126
|
*/
|
|
75
127
|
declare function iif<T>(condition: NoInfer<Predicate<T>> | boolean, trueOperatorOrValue: NoInfer<StateOperator<T> | T>, elseOperatorOrValue?: NoInfer<StateOperator<T> | T>): StateOperator<T>;
|
|
76
128
|
|
|
77
129
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
130
|
+
* Inserts an item into an array without mutating the original, satisfying
|
|
131
|
+
* NGXS's immutability requirement. Handles the case where the array property
|
|
132
|
+
* does not exist yet, so callers do not need to initialise it first.
|
|
133
|
+
*
|
|
134
|
+
* @param value - The item to insert. A `null` or `undefined` value is a no-op
|
|
135
|
+
* so that callers can pass through optional data safely.
|
|
136
|
+
* @param beforePosition - Index before which to insert. Omit (or pass a
|
|
137
|
+
* non-positive number) to prepend to the beginning of the array.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* // Prepend a new zebra (no position = insert at index 0).
|
|
142
|
+
* ctx.setState(
|
|
143
|
+
* patch<AnimalsStateModel>({
|
|
144
|
+
* zebras: insertItem<string>(action.payload)
|
|
145
|
+
* })
|
|
146
|
+
* );
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* // Insert before index 2, shifting subsequent items right.
|
|
152
|
+
* ctx.setState(
|
|
153
|
+
* patch<AnimalsStateModel>({
|
|
154
|
+
* zebras: insertItem<string>(action.payload, 2)
|
|
155
|
+
* })
|
|
156
|
+
* );
|
|
157
|
+
* ```
|
|
80
158
|
*/
|
|
81
159
|
declare function insertItem<T>(value: NoInfer<T>, beforePosition?: number): StateOperator<T[]>;
|
|
82
160
|
|
|
@@ -84,22 +162,140 @@ type NotUndefined<T> = T extends undefined ? never : T;
|
|
|
84
162
|
type ɵPatchSpec<T> = {
|
|
85
163
|
[P in keyof T]?: T[P] | StateOperator<NotUndefined<T[P]>>;
|
|
86
164
|
};
|
|
165
|
+
/**
|
|
166
|
+
* Applies a partial update to a state object, only cloning it when at least
|
|
167
|
+
* one property actually changes. This preserves referential equality for
|
|
168
|
+
* unchanged states, preventing unnecessary re-renders in `OnPush` components
|
|
169
|
+
* and keeping memoized selectors from recalculating.
|
|
170
|
+
*
|
|
171
|
+
* Each property in `patchObject` can itself be a state operator, enabling
|
|
172
|
+
* nested immutable updates without manually spreading every level of the tree.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* // Add an optional property to a state slice without touching existing ones.
|
|
177
|
+
* ctx.setState(
|
|
178
|
+
* patch<AnimalsStateModel>({ monkeys: [] })
|
|
179
|
+
* );
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* // Deep update — specify explicit types at each level so TypeScript can
|
|
185
|
+
* // catch property name mistakes in nested patches.
|
|
186
|
+
* ctx.setState(
|
|
187
|
+
* patch<AddressStateModel>({
|
|
188
|
+
* country: patch<AddressStateModel['country']>({
|
|
189
|
+
* city: patch<AddressStateModel['country']['city']>({
|
|
190
|
+
* address: patch<AddressStateModel['country']['city']['address']>({
|
|
191
|
+
* line1: action.line1
|
|
192
|
+
* })
|
|
193
|
+
* })
|
|
194
|
+
* })
|
|
195
|
+
* })
|
|
196
|
+
* );
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
87
199
|
declare function patch<T extends Record<string, any>>(patchObject: NoInfer<ɵPatchSpec<T>>): StateOperator<T>;
|
|
88
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Like `patch`, but safe to call when the state slice is `null` or
|
|
203
|
+
* `undefined`. Treats a missing slice as an empty object so the patch is
|
|
204
|
+
* applied against a clean baseline rather than throwing. Useful for lazily
|
|
205
|
+
* initialised state properties or optional sub-states that may not have been
|
|
206
|
+
* set yet.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* // Update a nested preferences slice that starts as null — no prior
|
|
211
|
+
* // null-check needed; safePatch treats null as an empty object.
|
|
212
|
+
* ctx.setState(
|
|
213
|
+
* patch<UserStateModel>({
|
|
214
|
+
* preferences: safePatch<UserPreferences>({ theme: action.theme })
|
|
215
|
+
* })
|
|
216
|
+
* );
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
89
219
|
declare function safePatch<T extends object>(patchSpec: NoInfer<ɵPatchSpec<T>>): StateOperator<T>;
|
|
90
220
|
|
|
91
221
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
222
|
+
* Replaces or transforms a single array element without cloning elements that
|
|
223
|
+
* did not change, preserving referential equality for the rest of the array.
|
|
224
|
+
* Returns the original array reference when nothing changed, keeping
|
|
225
|
+
* memoized selectors and `OnPush` components from re-rendering unnecessarily.
|
|
226
|
+
*
|
|
227
|
+
* @param selector - The index to update, or a predicate used to locate the
|
|
228
|
+
* item. Prefer a predicate when the item's position may have shifted since the
|
|
229
|
+
* index was last known.
|
|
230
|
+
* @param operatorOrValue - The replacement value, or a state operator applied
|
|
231
|
+
* to the existing element when a derived update is needed.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* // Rename a panda — locate it by current name so the index doesn't need
|
|
236
|
+
* // to be known ahead of time.
|
|
237
|
+
* ctx.setState(
|
|
238
|
+
* patch<AnimalsStateModel>({
|
|
239
|
+
* pandas: updateItem<string>(
|
|
240
|
+
* name => name === action.payload.name,
|
|
241
|
+
* action.payload.newName
|
|
242
|
+
* )
|
|
243
|
+
* })
|
|
244
|
+
* );
|
|
245
|
+
* ```
|
|
96
246
|
*/
|
|
97
247
|
declare function updateItem<T>(selector: number | NoInfer<Predicate<T>>, operatorOrValue: NoInfer<T> | NoInfer<StateOperator<T>>): StateOperator<T[]>;
|
|
98
248
|
|
|
99
249
|
/**
|
|
100
|
-
*
|
|
250
|
+
* Replaces or transforms every array element that matches the predicate.
|
|
251
|
+
* Unlike `updateItem`, which stops at the first match, this operator walks
|
|
252
|
+
* the entire array so all qualifying elements are updated in one pass.
|
|
253
|
+
*
|
|
254
|
+
* Always returns a new array reference, even when no elements matched or the
|
|
255
|
+
* values produced by the operator are identical to the originals. Use
|
|
256
|
+
* `updateItem` instead when only a single element needs updating and
|
|
257
|
+
* referential equality on no-op updates matters.
|
|
258
|
+
*
|
|
259
|
+
* @param selector - Predicate used to decide which elements to update.
|
|
260
|
+
* @param operatorOrValue - Replacement value, or a state operator applied
|
|
261
|
+
* to each matching element when a derived update is needed.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* // Mark every inactive animal as active in one setState call.
|
|
266
|
+
* ctx.setState(
|
|
267
|
+
* patch<AnimalsStateModel>({
|
|
268
|
+
* animals: updateItems<Animal>(
|
|
269
|
+
* animal => !animal.active,
|
|
270
|
+
* patch({ active: true })
|
|
271
|
+
* )
|
|
272
|
+
* })
|
|
273
|
+
* );
|
|
274
|
+
* ```
|
|
275
|
+
*/
|
|
276
|
+
declare function updateItems<T>(selector: NoInfer<Predicate<T>>, operatorOrValue: NoInfer<T> | NoInfer<StateOperator<T>>): (existing: ExistingState<T[]>) => T[];
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Removes a single element from an array without mutating the original.
|
|
280
|
+
* Returns the original array reference when no matching item is found, so
|
|
281
|
+
* memoized selectors are not invalidated by a no-op removal.
|
|
282
|
+
*
|
|
283
|
+
* @param selector - The index to remove, or a predicate used to locate the
|
|
284
|
+
* item. Prefer a predicate when the item's position is not guaranteed to be
|
|
285
|
+
* stable across concurrent state updates.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* // Remove a panda by name — a predicate is safer than a hard-coded index
|
|
290
|
+
* // because the array order may change between dispatch and execution.
|
|
291
|
+
* ctx.setState(
|
|
292
|
+
* patch<AnimalsStateModel>({
|
|
293
|
+
* pandas: removeItem<string>(name => name === action.payload)
|
|
294
|
+
* })
|
|
295
|
+
* );
|
|
296
|
+
* ```
|
|
101
297
|
*/
|
|
102
298
|
declare function removeItem<T>(selector: number | NoInfer<Predicate<T>>): StateOperator<T[]>;
|
|
103
299
|
|
|
104
|
-
export { append, compose, iif, insertItem, isPredicate, isStateOperator, patch, removeItem, safePatch, updateItem };
|
|
300
|
+
export { append, compose, iif, insertItem, isPredicate, isStateOperator, patch, removeItem, safePatch, updateItem, updateItems };
|
|
105
301
|
export type { ExistingState, NoInfer, Predicate, StateOperator, ɵPatchSpec };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ngxs/store",
|
|
3
|
-
"version": "21.0.0-dev.master-
|
|
3
|
+
"version": "21.0.0-dev.master-31acb66",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"peerDependencies": {
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
"types": "./index.d.ts",
|
|
19
19
|
"default": "./fesm2022/ngxs-store.mjs"
|
|
20
20
|
},
|
|
21
|
-
"./experimental": {
|
|
22
|
-
"types": "./experimental/index.d.ts",
|
|
23
|
-
"default": "./fesm2022/ngxs-store-experimental.mjs"
|
|
24
|
-
},
|
|
25
21
|
"./internals": {
|
|
26
22
|
"types": "./internals/index.d.ts",
|
|
27
23
|
"default": "./fesm2022/ngxs-store-internals.mjs"
|
|
28
24
|
},
|
|
25
|
+
"./experimental": {
|
|
26
|
+
"types": "./experimental/index.d.ts",
|
|
27
|
+
"default": "./fesm2022/ngxs-store-experimental.mjs"
|
|
28
|
+
},
|
|
29
29
|
"./operators": {
|
|
30
30
|
"types": "./operators/index.d.ts",
|
|
31
31
|
"default": "./fesm2022/ngxs-store-operators.mjs"
|