Files
2026-04-17 23:26:01 +00:00

130 lines
3.7 KiB
Markdown

---
title: Use Field Modifiers for Incremental Updates
impact: HIGH
impactDescription: Atomic updates, prevents race conditions, cleaner code
tags: sdk, modifiers, relations, files, numbers, atomic
---
## Use Field Modifiers for Incremental Updates
PocketBase supports `+` and `-` modifiers for incrementing numbers, appending/removing relation IDs, and managing file arrays without replacing the entire value.
**Incorrect (read-modify-write pattern):**
```javascript
// Race condition: two users adding tags simultaneously
async function addTag(postId, newTagId) {
const post = await pb.collection('posts').getOne(postId);
const currentTags = post.tags || [];
// Another user might have added a tag in between!
await pb.collection('posts').update(postId, {
tags: [...currentTags, newTagId] // Might overwrite the other user's tag
});
}
// Inefficient for incrementing counters
async function incrementViews(postId) {
const post = await pb.collection('posts').getOne(postId);
await pb.collection('posts').update(postId, {
views: post.views + 1 // Extra read, race condition
});
}
```
**Correct (using field modifiers):**
```javascript
// Atomic relation append with + modifier
async function addTag(postId, newTagId) {
await pb.collection('posts').update(postId, {
'tags+': newTagId // Appends to existing tags atomically
});
}
// Append multiple relations
async function addTags(postId, tagIds) {
await pb.collection('posts').update(postId, {
'tags+': tagIds // Appends array of IDs
});
}
// Prepend relations (+ prefix)
async function prependTag(postId, tagId) {
await pb.collection('posts').update(postId, {
'+tags': tagId // Prepends to start of array
});
}
// Remove relations with - modifier
async function removeTag(postId, tagId) {
await pb.collection('posts').update(postId, {
'tags-': tagId // Removes specific tag
});
}
// Remove multiple relations
async function removeTags(postId, tagIds) {
await pb.collection('posts').update(postId, {
'tags-': tagIds // Removes all specified tags
});
}
// Atomic number increment
async function incrementViews(postId) {
await pb.collection('posts').update(postId, {
'views+': 1 // Atomic increment, no race condition
});
}
// Atomic number decrement
async function decrementStock(productId, quantity) {
await pb.collection('products').update(productId, {
'stock-': quantity // Atomic decrement
});
}
// File append (for multi-file fields)
async function addImage(albumId, newImage) {
await pb.collection('albums').update(albumId, {
'images+': newImage // Appends new file to existing
});
}
// File removal
async function removeImage(albumId, filename) {
await pb.collection('albums').update(albumId, {
'images-': filename // Removes specific file by name
});
}
// Combined modifiers in single update
async function updatePost(postId, data) {
await pb.collection('posts').update(postId, {
title: data.title, // Replace field
'views+': 1, // Increment number
'tags+': data.newTagId, // Append relation
'tags-': data.oldTagId, // Remove relation
'images+': data.newImage // Append file
});
}
```
**Modifier reference:**
| Modifier | Field Types | Description |
|----------|-------------|-------------|
| `field+` or `+field` | relation, file | Append/prepend to array |
| `field-` | relation, file | Remove from array |
| `field+` | number | Increment by value |
| `field-` | number | Decrement by value |
**Benefits:**
- **Atomic**: No read-modify-write race conditions
- **Efficient**: Single request, no extra read needed
- **Clean**: Expresses intent clearly
**Note:** Modifiers only work with `update()`, not `create()`.
Reference: [PocketBase Relations](https://pocketbase.io/docs/working-with-relations/)