130 lines
3.7 KiB
Markdown
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/)
|