0
# Extension System
1
2
Advanced features for creating isolated contexts and adding custom commands. The extension system allows you to add your own commands to extend immutability-helper's functionality.
3
4
## Capabilities
5
6
### Context Class
7
8
The Context class provides an isolated environment for update operations with custom commands and equality functions.
9
10
```typescript { .api }
11
class Context {
12
/**
13
* Create a new Context instance with isolated command set
14
*/
15
constructor();
16
17
/**
18
* Add a custom command to this context
19
* @param directive - Command name (with $ prefix)
20
* @param fn - Function that implements the command
21
*/
22
extend<T>(directive: string, fn: (param: any, old: T) => T): void;
23
24
/**
25
* Update function for this context
26
* @param object - Object to update
27
* @param spec - Update specification
28
* @returns Updated object
29
*/
30
update<T, C extends CustomCommands<object> = never>(
31
object: T,
32
$spec: Spec<T, C>
33
): T;
34
35
/**
36
* Get the equality function used for change detection
37
*/
38
get isEquals(): (x: any, y: any) => boolean;
39
40
/**
41
* Set a custom equality function for change detection
42
*/
43
set isEquals(value: (x: any, y: any) => boolean);
44
}
45
```
46
47
**Usage Examples:**
48
49
```typescript
50
import { Context } from "immutability-helper";
51
52
// Create isolated context
53
const myContext = new Context();
54
55
// Add custom command
56
myContext.extend('$addTax', function(tax, original) {
57
return original + (tax * original);
58
});
59
60
// Use custom context
61
const price = { amount: 100 };
62
const withTax = myContext.update(price, {
63
amount: { $addTax: 0.2 }
64
});
65
// Result: { amount: 120 }
66
67
// Custom equality function
68
myContext.isEquals = (a, b) => {
69
// Custom deep equality logic
70
return JSON.stringify(a) === JSON.stringify(b);
71
};
72
```
73
74
### Global Extend Function
75
76
Add custom commands to the default global context.
77
78
```typescript { .api }
79
/**
80
* Add a custom command to the global context
81
* @param directive - Command name (with $ prefix)
82
* @param fn - Function that implements the command
83
*/
84
function extend<T>(directive: string, fn: (param: any, old: T) => T): void;
85
```
86
87
**Usage Examples:**
88
89
```typescript
90
import update, { extend } from "immutability-helper";
91
92
// Add global custom command
93
extend('$addTax', function(tax, original) {
94
return original + (tax * original);
95
});
96
97
// Use globally
98
const result = update({ price: 100 }, {
99
price: { $addTax: 0.15 }
100
});
101
// Result: { price: 115 }
102
103
// String manipulation command
104
extend('$capitalize', function(_, original) {
105
return typeof original === 'string'
106
? original.charAt(0).toUpperCase() + original.slice(1).toLowerCase()
107
: original;
108
});
109
110
const text = { title: 'hello world' };
111
const capitalized = update(text, {
112
title: { $capitalize: null }
113
});
114
// Result: { title: 'Hello world' }
115
```
116
117
## Advanced Extension Examples
118
119
### Mathematical Operations
120
121
```typescript
122
import { extend } from "immutability-helper";
123
124
// Add mathematical operations
125
extend('$multiply', (factor, original) => original * factor);
126
extend('$power', (exponent, original) => Math.pow(original, exponent));
127
extend('$round', (decimals, original) => {
128
const multiplier = Math.pow(10, decimals || 0);
129
return Math.round(original * multiplier) / multiplier;
130
});
131
132
const data = { value: 3.14159 };
133
const processed = update(data, {
134
value: { $round: 2 }
135
});
136
// Result: { value: 3.14 }
137
```
138
139
### Array Manipulation Commands
140
141
```typescript
142
import update, { extend } from "immutability-helper";
143
144
// Custom array operations
145
extend('$shuffle', (_, original) => {
146
if (!Array.isArray(original)) return original;
147
const shuffled = [...original];
148
for (let i = shuffled.length - 1; i > 0; i--) {
149
const j = Math.floor(Math.random() * (i + 1));
150
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
151
}
152
return shuffled;
153
});
154
155
extend('$take', (count, original) => {
156
return Array.isArray(original) ? original.slice(0, count) : original;
157
});
158
159
const numbers = { list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] };
160
const taken = update(numbers, {
161
list: { $take: 3 }
162
});
163
// Result: { list: [1, 2, 3] }
164
```
165
166
### Conditional Operations
167
168
```typescript
169
import { extend } from "immutability-helper";
170
171
// Conditional update command
172
extend('$if', (condition, original) => {
173
const { test, then: thenSpec, else: elseSpec } = condition;
174
const shouldUpdate = typeof test === 'function' ? test(original) : test;
175
176
if (shouldUpdate && thenSpec) {
177
return update(original, thenSpec);
178
} else if (!shouldUpdate && elseSpec) {
179
return update(original, elseSpec);
180
}
181
182
return original;
183
});
184
185
const user = { age: 17, status: 'minor' };
186
const updated = update(user, {
187
$if: {
188
test: (obj) => obj.age >= 18,
189
then: { status: { $set: 'adult' } },
190
else: { status: { $set: 'minor' } }
191
}
192
});
193
```
194
195
### Autovivification Commands
196
197
Autovivification is the automatic creation of new arrays and objects when needed. JavaScript doesn't have this feature natively, which makes deep nested updates challenging. These custom commands solve this problem:
198
199
```typescript
200
import update, { extend } from "immutability-helper";
201
202
// Auto-create missing nested structures
203
extend('$auto', function(value, object) {
204
return object ? update(object, value) : update({}, value);
205
});
206
207
extend('$autoArray', function(value, object) {
208
return object ? update(object, value) : update([], value);
209
});
210
211
// Usage example - building deep structures from empty state
212
const state = {};
213
const result = update(state, {
214
users: { $autoArray: {
215
0: { $auto: {
216
name: { $set: 'Alice' },
217
posts: { $autoArray: { $push: ['Hello World'] } }
218
}}
219
}}
220
});
221
// Result: { users: [{ name: 'Alice', posts: ['Hello World'] }] }
222
223
// Alternative approach without custom commands (manual autovivification)
224
const state2 = {};
225
const desiredState = {
226
foo: [{ bar: ['x', 'y', 'z'] }]
227
};
228
229
const manualResult = update(state2, {
230
foo: foo =>
231
update(foo || [], {
232
0: fooZero =>
233
update(fooZero || {}, {
234
bar: bar => update(bar || [], { $push: ["x", "y", "z"] })
235
})
236
})
237
});
238
// Result matches desiredState
239
```
240
241
## Custom Command Function Signature
242
243
Custom command functions receive three parameters:
244
245
```typescript
246
type CommandFunction<T> = (
247
param: any, // The parameter passed to the command
248
nextObject: T, // The current object being updated
249
spec: any, // The full specification object
250
originalObject: T // The original object before any updates
251
) => T;
252
```
253
254
**Advanced Command Example:**
255
256
```typescript
257
extend('$increment', function(amount, nextObject, spec, originalObject) {
258
// param: amount to increment
259
// nextObject: current value during update chain
260
// spec: the full spec object containing $increment
261
// originalObject: the original value before any updates
262
263
if (typeof nextObject === 'number') {
264
return nextObject + (amount || 1);
265
}
266
return nextObject;
267
});
268
```
269
270
## Type Safety with Custom Commands
271
272
For TypeScript users, you can create type-safe custom commands:
273
274
```typescript
275
import update, { CustomCommands, Spec } from "immutability-helper";
276
277
// Define custom command types
278
interface MyCommands {
279
$addTax: number;
280
$capitalize: null;
281
}
282
283
// Create typed update function
284
function myUpdate<T>(object: T, spec: Spec<T, CustomCommands<MyCommands>>) {
285
return update(object, spec);
286
}
287
288
// Usage with full type safety
289
const result = myUpdate({ price: 100, name: 'product' }, {
290
price: { $addTax: 0.2 }, // TypeScript knows this expects a number
291
name: { $capitalize: null } // TypeScript knows this expects null
292
});
293
```
294
295
## Error Handling
296
297
- Custom command functions should handle their own validation
298
- Use the `invariant` function for consistent error messages
299
- Commands that don't modify data should return the original object reference
300
301
```typescript
302
import { invariant } from "immutability-helper";
303
304
extend('$safeIncrement', function(amount, original) {
305
invariant(
306
typeof original === 'number',
307
() => `$safeIncrement expects a number, got ${typeof original}`
308
);
309
310
invariant(
311
typeof amount === 'number',
312
() => `$safeIncrement amount must be a number, got ${typeof amount}`
313
);
314
315
return original + amount;
316
});
317
```
318
319
## Performance Considerations
320
321
- Custom commands should preserve reference equality when no changes occur
322
- Avoid expensive operations in frequently called commands
323
- Consider using the fourth parameter (`originalObject`) for optimization decisions
324
- Custom equality functions affect performance - keep them fast and consistent