Skip to content

Editing

Two ways to update documents: save() for full content replacement, and edit() for targeted changes with MongoDB-style operators.

save() — full replacement

Pass the complete document content. The store computes the minimal diff automatically:

typescript
const [errors, newHash] = await store.save(uid, hash, {
    title: 'Updated Title',
    url: 'https://example.com',
    content: 'New content here',
});

Good for form editors where you have the full state.

edit() — targeted changes

Apply specific changes using update operators:

typescript
const [errors, newHash] = await store.edit(uid, { hash }, {
    $set: { title: 'New Title' }
});

Operators

$set — set field values

typescript
await store.edit(uid, { hash }, {
    $set: { title: 'New Title', status: 'archived' }
});

$unset — remove fields

typescript
await store.edit(uid, { hash }, {
    $unset: { description: '' }
});

$inc — increment numbers

typescript
await store.edit(uid, { hash }, {
    $inc: { views: 1 }
});

$push — add to array

typescript
await store.edit(uid, { hash }, {
    $push: { tags: 'new-tag' }
});

$pull — remove from array

typescript
// Remove by value
await store.edit(uid, { hash }, {
    $pull: { tags: 'old-tag' }
});

// Remove by object match
await store.edit(uid, { hash }, {
    $pull: { members: { userId: someUid } }
});

// Dot-path matching for nested fields
await store.edit(uid, { hash }, {
    $pull: { photos: { 'file.hash': fileHash } }
});

$pullAll — remove all matching values

typescript
await store.edit(uid, { hash }, {
    $pullAll: { tags: ['tag1', 'tag2'] }
});

$addToSet — add unique to array

typescript
await store.edit(uid, { hash }, {
    $addToSet: { tags: 'unique-tag' }
});

// Add multiple
await store.edit(uid, { hash }, {
    $addToSet: { tags: { $each: ['tag1', 'tag2'] } }
});

$delete — soft delete

typescript
await store.edit(uid, { hash }, {
    $delete: true
});

This creates an edit that syncs to peers — they'll delete their copy too.

Positional $ — update matched array element

The filter in edit() can match an array element, and $ in the update path refers to that element:

typescript
// Update a specific member's role
await store.edit(uid, { hash, 'members.userId': bobUid }, {
    $set: { 'members.$.role': 'admin' }
});

// Update a nested field on a matched element
await store.edit(uid, { hash, 'file.id': 'b' }, {
    $set: { 'file.$.name': 'Bee' }
});

Works with $set, $unset, and $pull on nested arrays:

typescript
// Remove a value from a sub-array on a matched element
await store.edit(uid, { hash, 'members.id': 'b' }, {
    $pull: { 'members.$.tags': 'old-tag' }
});

Operator ordering

$pull runs before $push in the same operation. This lets you do remove-then-add:

typescript
await store.edit(uid, { hash }, {
    $pull: { assignee: oldUser },
    $push: { assignee: newUser }
});

Version history

Every edit is stored. You can get the full chain:

typescript
const { original, versions, edits } = await store.versions(hash);

// original: the first version
// versions: computed state after each edit
// edits: raw edit documents with operators and timestamps

When to use which

ScenarioMethodWhy
Form submissionsave()You have the full state, let the store diff it
Toggle a flagedit()Targeted $set, no need to fetch first
Add to a listedit()$push without reading the current list
Remove from a listedit()$pull by value or object match
Delete a documentedit() or delete()Both create a synced $delete edit