Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.0] - 2026-01-28

### Added

- State objects can now include methods that operate on `this` - methods are preserved through snapshots, rollback, and reset operations

## [1.1.0] - 2026-01-20

### Changed
Expand Down
20 changes: 19 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const { data, execute, state, rollback, reset } = createSvState(init, actuators?

**Returns:**

- `data` - Deep reactive proxy around the state object
- `data` - Deep reactive proxy around the state object (methods on the object are preserved and callable)
- `execute(params)` - Async function to run the configured action
- `rollback(steps?)` - Undo N steps (default 1), restores state and triggers validation
- `reset()` - Return to initial snapshot, triggers validation
Expand Down Expand Up @@ -140,6 +140,24 @@ effect: ({ snapshot, property }) => {
- Successful action execution resets snapshots with current state as new initial
- `rollback()` and `reset()` trigger validation after restoring state

### Deep Clone System (src/state.svelte.ts)

The `deepClone` function preserves object prototypes using `Object.create(Object.getPrototypeOf(object))`. This allows state objects to include methods that operate on `this`:

```typescript
const createState = () => ({
value: 0,
format() {
return `$${this.value.toFixed(2)}`;
}
});

const { data } = createSvState(createState());
data.format(); // Works — method preserved
```

Methods are preserved through snapshots, rollback, and reset operations.

### Deep Proxy System (src/proxy.ts)

- `ChangeProxy<T>()` wraps objects with recursive Proxy handlers
Expand Down
68 changes: 67 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const customer = $state({
- ⚡ **Fires effects** when any property changes (with full context)
- ⏪ **Snapshots & undo** for complex editing workflows
- 🎯 **Tracks dirty state** automatically
- 🔧 **Supports methods** on state objects for computed values and formatting

```typescript
import { createSvState, stringValidator, numberValidator } from 'svstate';
Expand Down Expand Up @@ -334,6 +335,70 @@ const { data } = createSvState(formData, actuators, {

---

### 6️⃣ State Objects with Methods

State objects can include methods that operate on `this`. Methods are preserved through snapshots and undo operations, making it easy to encapsulate computed values and formatting logic:

```typescript
import { createSvState, numberValidator } from 'svstate';

// Define state with methods
type InvoiceData = {
unitPrice: number;
quantity: number;
subtotal: number;
tax: number;
total: number;
calculateTotals: (taxRate?: number) => void;
formatCurrency: (value: number) => string;
};

const createInvoice = (): InvoiceData => ({
unitPrice: 0,
quantity: 1,
subtotal: 0,
tax: 0,
total: 0,
calculateTotals(taxRate = 0.08) {
this.subtotal = this.unitPrice * this.quantity;
this.tax = this.subtotal * taxRate;
this.total = this.subtotal + this.tax;
},
formatCurrency(value: number) {
return `$${value.toFixed(2)}`;
}
});

const {
data,
state: { errors }
} = createSvState(createInvoice(), {
validator: (source) => ({
unitPrice: numberValidator(source.unitPrice).required().positive().getError(),
quantity: numberValidator(source.quantity).required().integer().min(1).getError()
}),
effect: ({ property }) => {
// Call method directly on state when inputs change
if (property === 'unitPrice' || property === 'quantity') {
data.calculateTotals();
}
}
});

// In template: use methods for formatting
// {data.formatCurrency(data.subtotal)} → "$99.00"
// {data.formatCurrency(data.total)} → "$106.92"
```

**Key features:**

- 🔧 Methods can modify `this` properties (triggers validation/effects)
- 📸 Methods preserved through `rollback()` and `reset()`
- 🎯 Call methods from effects to compute derived values
- 📐 Encapsulate formatting and business logic in state object

---

## 🏗️ Complete Examples

### Example 1: ERP Customer Form with Nested Addresses
Expand Down Expand Up @@ -698,7 +763,7 @@ Creates a supercharged state object.
**Returns:**
| Property | Type | Description |
|----------|------|-------------|
| `data` | `T` | Deep reactive proxy — bind directly |
| `data` | `T` | Deep reactive proxy — bind directly, methods preserved |
| `execute(params?)` | `(P?) => Promise<void>` | Run the configured action |
| `rollback(steps?)` | `(n?: number) => void` | Undo N changes (default: 1) |
| `reset()` | `() => void` | Return to initial state |
Expand Down Expand Up @@ -786,6 +851,7 @@ const { data, state } = createSvState<UserData, UserErrors, object>(
| Undo/Redo | ❌ DIY | ✅ Built-in |
| Dirty tracking | ❌ DIY | ✅ Automatic |
| Action loading states | ❌ DIY | ✅ Built-in |
| State with methods | ⚠️ Manual cloning | ✅ Automatic |

**svstate is for:**

Expand Down
Loading