or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration-utilities.mdcore-production.mddraft-management.mdindex.mdpatches-system.md
tile.json

configuration-utilities.mddocs/

0

# Configuration & Utilities

1

2

Configuration options and utility functions for customizing Immer behavior, working with types, and managing immutable state operations.

3

4

## Capabilities

5

6

### setAutoFreeze

7

8

Controls whether Immer automatically freezes all copies created by produce functions. Freezing prevents accidental mutations of the immutable result.

9

10

```typescript { .api }

11

/**

12

* Pass true to automatically freeze all copies created by Immer

13

* @param value - Boolean to enable/disable auto-freezing

14

* @default true - Always freeze by default, even in production mode

15

*/

16

function setAutoFreeze(value: boolean): void;

17

```

18

19

**Usage Examples:**

20

21

```typescript

22

import { produce, setAutoFreeze } from "immer";

23

24

const baseState = { items: [1, 2, 3], meta: { count: 3 } };

25

26

// Default behavior - results are frozen

27

const result1 = produce(baseState, draft => {

28

draft.items.push(4);

29

});

30

31

console.log(Object.isFrozen(result1)); // true

32

console.log(Object.isFrozen(result1.items)); // true

33

34

// Attempting to mutate frozen objects throws in strict mode

35

try {

36

result1.items.push(5); // TypeError: Cannot add property 3, object is not extensible

37

} catch (error) {

38

console.log("Mutation prevented by freezing");

39

}

40

41

// Disable auto-freezing for performance or specific use cases

42

setAutoFreeze(false);

43

44

const result2 = produce(baseState, draft => {

45

draft.items.push(5);

46

});

47

48

console.log(Object.isFrozen(result2)); // false

49

console.log(Object.isFrozen(result2.items)); // false

50

51

// Now mutation is possible (but not recommended)

52

result2.items.push(6); // Works, but breaks immutability contract

53

54

// Re-enable freezing

55

setAutoFreeze(true);

56

57

const result3 = produce(result2, draft => {

58

draft.meta.updated = true;

59

});

60

61

console.log(Object.isFrozen(result3)); // true

62

```

63

64

### setUseStrictShallowCopy

65

66

Controls whether Immer uses strict shallow copy mode, which copies object descriptors such as getters, setters, and non-enumerable properties.

67

68

```typescript { .api }

69

/**

70

* Pass true to enable strict shallow copy

71

* @param value - Boolean or "class_only" to enable strict copying

72

* @default false - By default, Immer does not copy object descriptors

73

*/

74

function setUseStrictShallowCopy(value: boolean | "class_only"): void;

75

```

76

77

**Usage Examples:**

78

79

```typescript

80

import { produce, setUseStrictShallowCopy } from "immer";

81

82

// Object with descriptors

83

const baseState = {};

84

Object.defineProperty(baseState, 'computed', {

85

get() { return this._value * 2; },

86

set(val) { this._value = val; },

87

enumerable: false

88

});

89

Object.defineProperty(baseState, '_value', {

90

value: 10,

91

writable: true,

92

enumerable: false

93

});

94

95

// Default behavior - descriptors are not copied

96

const result1 = produce(baseState, draft => {

97

draft.newProp = 'added';

98

});

99

100

console.log(Object.getOwnPropertyDescriptor(result1, 'computed')); // undefined

101

console.log(result1._value); // undefined

102

103

// Enable strict shallow copy

104

setUseStrictShallowCopy(true);

105

106

const result2 = produce(baseState, draft => {

107

draft.anotherProp = 'also added';

108

});

109

110

console.log(Object.getOwnPropertyDescriptor(result2, 'computed')); // { get: [Function], set: [Function], enumerable: false, ... }

111

console.log(result2._value); // 10

112

console.log(result2.computed); // 20

113

114

// Class-only mode - only copy descriptors for class instances

115

setUseStrictShallowCopy("class_only");

116

117

class MyClass {

118

constructor(public value: number) {}

119

120

get doubled() {

121

return this.value * 2;

122

}

123

}

124

125

const classInstance = new MyClass(5);

126

const plainObject = { prop: 'value' };

127

128

const classResult = produce(classInstance, draft => {

129

draft.value = 10;

130

});

131

132

const plainResult = produce(plainObject, draft => {

133

draft.newProp = 'added';

134

});

135

136

// Descriptors copied for class instances but not plain objects in "class_only" mode

137

console.log(classResult.doubled); // 20 (getter preserved)

138

console.log(Object.getOwnPropertyDescriptor(plainResult, 'prop')); // Basic descriptor only

139

```

140

141

### freeze

142

143

Manually freezes draftable objects. This is primarily used internally by Immer but can be useful for manually freezing objects.

144

145

```typescript { .api }

146

/**

147

* Freezes draftable objects, returns the original object

148

* @param obj - Object to freeze

149

* @param deep - If true, freezes recursively (default: false)

150

* @returns The frozen object

151

*/

152

function freeze<T>(obj: T, deep?: boolean): T;

153

```

154

155

**Usage Examples:**

156

157

```typescript

158

import { freeze, isDraftable } from "immer";

159

160

const mutableObject = {

161

user: { name: "Alice", age: 30 },

162

items: [1, 2, 3],

163

config: { theme: "light" }

164

};

165

166

// Shallow freeze

167

const shallowFrozen = freeze(mutableObject);

168

169

console.log(Object.isFrozen(shallowFrozen)); // true

170

console.log(Object.isFrozen(shallowFrozen.user)); // false (shallow only)

171

172

// Attempting to modify top-level properties fails

173

try {

174

shallowFrozen.newProp = 'fails';

175

} catch (error) {

176

console.log("Top-level mutation prevented");

177

}

178

179

// But nested objects can still be modified

180

shallowFrozen.user.age = 31; // Works because user object is not frozen

181

console.log(shallowFrozen.user.age); // 31

182

183

// Deep freeze

184

const deepFrozen = freeze({

185

user: { name: "Bob", profile: { bio: "Developer" } },

186

data: [{ id: 1, value: "test" }]

187

}, true);

188

189

console.log(Object.isFrozen(deepFrozen)); // true

190

console.log(Object.isFrozen(deepFrozen.user)); // true

191

console.log(Object.isFrozen(deepFrozen.user.profile)); // true

192

console.log(Object.isFrozen(deepFrozen.data)); // true

193

console.log(Object.isFrozen(deepFrozen.data[0])); // true

194

195

// All levels are now immutable

196

try {

197

deepFrozen.user.name = "Charlie"; // TypeError in strict mode

198

} catch (error) {

199

console.log("Deep mutation prevented");

200

}

201

202

// freeze only affects draftable objects

203

const primitive = 42;

204

const frozenPrimitive = freeze(primitive);

205

console.log(frozenPrimitive === primitive); // true (primitives are unchanged)

206

207

// Works with arrays

208

const array = [{ id: 1 }, { id: 2 }];

209

const frozenArray = freeze(array, true);

210

console.log(Object.isFrozen(frozenArray)); // true

211

console.log(Object.isFrozen(frozenArray[0])); // true

212

```

213

214

### castDraft

215

216

Type casting utility that tells TypeScript to treat an immutable type as a draft type. This is a no-op at runtime but helps with type safety.

217

218

```typescript { .api }

219

/**

220

* This function is actually a no-op, but can be used to cast an immutable type

221

* to a draft type and make TypeScript happy

222

* @param value - Value to cast

223

* @returns Same value, typed as Draft<T>

224

*/

225

function castDraft<T>(value: T): Draft<T>;

226

```

227

228

**Usage Examples:**

229

230

```typescript

231

import { produce, castDraft, Draft, Immutable } from "immer";

232

233

// Scenario: Working with immutable data that needs to be treated as draft

234

function updateUserInPlace<T extends { user: { name: string; age: number } }>(

235

state: Immutable<T>,

236

updater: (user: Draft<T['user']>) => void

237

): T {

238

return produce(state, draft => {

239

// TypeScript knows draft.user is already a Draft<T['user']>

240

updater(draft.user);

241

});

242

}

243

244

// But sometimes you need to pass immutable data to a function expecting drafts

245

function processUser(user: Draft<{ name: string; age: number }>) {

246

user.name = user.name.toUpperCase();

247

user.age += 1;

248

}

249

250

const immutableState: Immutable<{ user: { name: string; age: number } }> = {

251

user: { name: "alice", age: 25 }

252

};

253

254

// This would cause TypeScript error without castDraft:

255

// processUser(immutableState.user); // Error: Immutable<> not assignable to Draft<>

256

257

const result = produce(immutableState, draft => {

258

// Cast immutable user to draft type for function that expects drafts

259

const userAsDraft = castDraft(immutableState.user);

260

261

// Now TypeScript accepts it, even though we're not actually using it

262

// (in practice, you'd use draft.user directly)

263

processUser(draft.user);

264

});

265

266

// More practical example: conditional draft processing

267

function conditionalUpdate<T>(

268

state: T,

269

condition: boolean,

270

updater: (draft: Draft<T>) => void

271

): T {

272

if (condition) {

273

return produce(state, updater);

274

} else {

275

// Return original state, but cast to maintain type compatibility

276

return castDraft(state) as T;

277

}

278

}

279

280

// Usage in generic contexts

281

interface Repository<T> {

282

update(updater: (draft: Draft<T>) => void): T;

283

getImmutable(): Immutable<T>;

284

}

285

286

class ImmutableRepository<T> implements Repository<T> {

287

constructor(private data: T) {}

288

289

update(updater: (draft: Draft<T>) => void): T {

290

this.data = produce(this.data, updater);

291

return this.data;

292

}

293

294

getImmutable(): Immutable<T> {

295

// Cast to immutable type for type safety

296

return this.data as Immutable<T>;

297

}

298

299

// Method that needs to work with both draft and immutable versions

300

processData(processor: (data: Draft<T>) => void): void {

301

if (this.isDraft(this.data)) {

302

processor(this.data as Draft<T>);

303

} else {

304

processor(castDraft(this.data));

305

}

306

}

307

308

private isDraft(value: any): boolean {

309

// In real implementation, would use isDraft from immer

310

return false; // Simplified for example

311

}

312

}

313

```

314

315

### castImmutable

316

317

Type casting utility that tells TypeScript to treat a mutable type as an immutable type. This is a no-op at runtime but helps with type safety.

318

319

```typescript { .api }

320

/**

321

* This function is actually a no-op, but can be used to cast a mutable type

322

* to an immutable type and make TypeScript happy

323

* @param value - Value to cast

324

* @returns Same value, typed as Immutable<T>

325

*/

326

function castImmutable<T>(value: T): Immutable<T>;

327

```

328

329

**Usage Examples:**

330

331

```typescript

332

import { produce, castImmutable, Draft, Immutable } from "immer";

333

334

// Scenario: Function that returns data that should be treated as immutable

335

function createImmutableUser(name: string, age: number): Immutable<{ name: string; age: number }> {

336

const user = { name, age }; // Mutable object

337

338

// Cast to immutable type to enforce immutability contract

339

return castImmutable(user);

340

}

341

342

// Usage with state management

343

class StateManager<T> {

344

private _state: T;

345

346

constructor(initialState: T) {

347

this._state = initialState;

348

}

349

350

// Return state as immutable to prevent external mutations

351

getState(): Immutable<T> {

352

return castImmutable(this._state);

353

}

354

355

// Update state and return new immutable version

356

updateState(updater: (draft: Draft<T>) => void): Immutable<T> {

357

this._state = produce(this._state, updater);

358

return castImmutable(this._state);

359

}

360

361

// Unsafe direct access for internal use only

362

private getMutableState(): T {

363

return this._state;

364

}

365

}

366

367

// Using with APIs that expect immutable data

368

interface ImmutableStore<T> {

369

data: Immutable<T>;

370

update(data: Immutable<T>): void;

371

}

372

373

function integrateWithStore<T>(

374

store: ImmutableStore<T>,

375

mutableData: T

376

): void {

377

// Cast mutable data to immutable for store compatibility

378

store.update(castImmutable(mutableData));

379

}

380

381

// Type-safe builder pattern

382

class ImmutableBuilder<T> {

383

private data: Partial<T> = {};

384

385

set<K extends keyof T>(key: K, value: T[K]): this {

386

this.data[key] = value;

387

return this;

388

}

389

390

build(): Immutable<T> {

391

if (!this.isComplete()) {

392

throw new Error("Incomplete data");

393

}

394

395

// Cast completed mutable data to immutable

396

return castImmutable(this.data as T);

397

}

398

399

private isComplete(): this is { data: T } {

400

// Simplified completeness check

401

return Object.keys(this.data).length > 0;

402

}

403

}

404

405

// Usage

406

const immutableUser = new ImmutableBuilder<{ name: string; age: number }>()

407

.set('name', 'Alice')

408

.set('age', 30)

409

.build();

410

411

// Now immutableUser has Immutable<> type, preventing accidental mutations

412

// immutableUser.name = "Bob"; // TypeScript error

413

```

414

415

## Custom Immer Instances

416

417

For advanced use cases, you can create custom Immer instances with specific configurations:

418

419

```typescript

420

import { Immer } from "immer";

421

422

// Create custom instance with specific settings

423

const customImmer = new Immer({

424

autoFreeze: false,

425

useStrictShallowCopy: "class_only"

426

});

427

428

// Use custom instance methods

429

const result = customImmer.produce(baseState, draft => {

430

draft.modified = true;

431

});

432

433

// Custom instance maintains its own configuration

434

console.log(Object.isFrozen(result)); // false (autoFreeze disabled)

435

436

// You can also create multiple instances with different configs

437

const debugImmer = new Immer({ autoFreeze: true });

438

const performanceImmer = new Immer({ autoFreeze: false });

439

440

// Use appropriate instance based on context

441

const debugResult = debugImmer.produce(data, draft => {

442

// Changes for debugging

443

});

444

445

const productionResult = performanceImmer.produce(data, draft => {

446

// Performance-critical changes

447

});

448

```

449

450

## Utility Type Guards

451

452

```typescript

453

import { isDraft, isDraftable, castDraft, castImmutable } from "immer";

454

455

// Type-safe utility functions

456

function safeCastDraft<T>(value: T): Draft<T> | null {

457

return isDraftable(value) ? castDraft(value) : null;

458

}

459

460

function safeCastImmutable<T>(value: T): Immutable<T> | null {

461

return isDraftable(value) ? castImmutable(value) : null;

462

}

463

464

// Generic processor that handles both drafts and regular objects

465

function processAnyValue<T>(

466

value: T,

467

processor: (v: Draft<T>) => void

468

): T {

469

if (isDraft(value)) {

470

processor(value as Draft<T>);

471

return value;

472

} else if (isDraftable(value)) {

473

return produce(value, processor);

474

} else {

475

// Cannot be drafted, return as-is

476

return value;

477

}

478

}

479

```

480

481

Configuration and utility functions provide fine-grained control over Immer's behavior and help maintain type safety in complex TypeScript applications.