Elegantly enforce immutability in your JavaScript and TypeScript states.
ImmuView is a lightweight utility designed to provide a readonly view of your JavaScript objects. With ImmuView, you can ensure that your state remains immutable, while still allowing controlled mutations through a dedicated API.
ImmuView doesn't just create a proxy for your object; it does so lazily. This means that nested objects within your state are only proxied when they're accessed, ensuring optimal performance and minimal overhead.
ImmuView supports a wide range of value types:
Primitives: Numbers, Strings, Booleans - not realy supported, but you can wrap them in an object to make them work.- Objects: Plain objects, nested objects
- Arrays: Including nested arrays and arrays of objects
- Functions: Functions are bound to the original object, ensuring
this
behaves as expected. - Dates: Special handling ensures that you can't mutate dates through methods like
setDate
orsetFullYear
.
Want to validate your state before it's updated? ImmuView provides custom validation hooks. You can also customize the error handling behavior, making it flexible enough to fit into any application architecture.
Whether you're working with functional components, class-based components, or just plain JavaScript, ImmuView can be easily integrated. It's framework-agnostic, so you can use it with React, Vue, Angular, or even without a framework!
- Deep Immutability: Ensure that nested objects and arrays are also immutable.
- Lazy Initialization: Nested objects are only proxied when they're accessed.
- WeakMap Caching: Proxies are cached using a WeakMap, ensuring optimal performance.
- Custom Validation: Set custom validation rules for your state.
- Error Handling: Handle errors gracefully with custom error handlers.
- TypeScript Support: Fully typed for a great developer experience.
To get started with ImmuView, you first need to install it:
npm install immuview --save
Here's a quick example to get you started:
import { readonly, DirectMutationError, ValidationError } from 'immuview';
const state = readonly({ count: 5, nested: { value: 10 } });
// This will throw a DirectMutationError
state.value.count = 10;
// This will also throw a DirectMutationError
delete (state.value.nested as any).value;
For those who prefer a class-based approach, ImmuView
can be seamlessly integrated into your classes. Here's a quick example:
import { readonly, DirectMutationError, ValidationError } from 'immuview';
class Counter {
private _state: ReturnType<typeof readonly>;
constructor(initialValue: { count: number }) {
this._state = readonly(initialValue);
}
// Getter to access the readonly state
get state() {
return this._state.value;
}
// Method to internally mutate the state
increment() {
const newValue = { count: this._state.value.count + 1 };
this._state.internalSet(newValue);
}
// Another example method to internally mutate the state
reset() {
this._state.internalSet({ count: 0 });
}
}
const counter = new Counter({ count: 0 });
console.log(counter.state.count); // Outputs: 0
counter.increment();
console.log(counter.state.count); // Outputs: 1
// This will throw a DirectMutationError
counter.state.count = 5;
counter.reset();
console.log(counter.state.count); // Outputs: 0
In the example above, the Counter class encapsulates the ImmuView logic, allowing internal mutations via methods like increment and reset, while ensuring that external direct mutations throw errors.
You can provide additional options when creating a readonly state:
const options = {
validator: (value) => value.count < 10,
errorHandler: (error) => console.error(error.message),
validationErrorMessage: 'Count should be less than 10',
};
const state = readonly({ count: 5 }, options);
// This will throw a ValidationError with the message 'Count should be less than 10'
state.internalSet({ count: 15 });
Creates a new readonly state.
initialValue
: The initial value of the state.options
: Optional configuration.validator
: A function that returns a boolean indicating whether the state is valid or not. Defaults to() => true
.errorHandler
: A function that handles errors thrown by the state. Defaults to() => {}
.validationErrorMessage
: The error message to be thrown when the state is invalid. Defaults to ``.
Error thrown when trying to mutate the state directly.
Error thrown when the state does not pass validation.
We welcome contributions! Please see our contributing guidelines for more details.
Thanks to all contributors and users for making ImmuView a reality!