Skip to content

Commit 28b27b0

Browse files
Version Packages (#963)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 6c68442 commit 28b27b0

File tree

5 files changed

+75
-63
lines changed

5 files changed

+75
-63
lines changed

.changeset/etag-support.md

Lines changed: 0 additions & 61 deletions
This file was deleted.

packages/blob/CHANGELOG.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,71 @@
11
# @vercel/blob
22

3+
## 2.1.0
4+
5+
### Minor Changes
6+
7+
- 6c68442: Add ETag support for conditional writes (optimistic concurrency control)
8+
9+
- Return `etag` in all blob responses (put, copy, head, list, multipart)
10+
- Accept `ifMatch` option in put/copy/createMultipartUpload for conditional writes
11+
- Add `BlobPreconditionFailedError` for ETag mismatch (HTTP 412)
12+
13+
## Usage Example: Preventing Lost Updates
14+
15+
When multiple users or processes might update the same blob concurrently, use `ifMatch` to ensure you don't overwrite someone else's changes:
16+
17+
```typescript
18+
import { put, head, BlobPreconditionFailedError } from "@vercel/blob";
19+
20+
// User 1: Read the current blob and get its ETag
21+
const metadata = await head("config.json");
22+
console.log(metadata.etag); // e.g., '"abc123"'
23+
24+
// User 2: Also reads the same blob (same ETag)
25+
const metadata2 = await head("config.json");
26+
27+
// User 1: Updates the blob with ifMatch
28+
// This succeeds because the ETag matches
29+
const result1 = await put(
30+
"config.json",
31+
JSON.stringify({ setting: "user1" }),
32+
{
33+
access: "public",
34+
allowOverwrite: true, // Required when updating existing blobs
35+
ifMatch: metadata.etag, // Only write if ETag still matches
36+
}
37+
);
38+
console.log(result1.etag); // New ETag: '"def456"'
39+
40+
// User 2: Tries to update with their (now stale) ETag
41+
// This fails because User 1 already changed the blob
42+
try {
43+
await put("config.json", JSON.stringify({ setting: "user2" }), {
44+
access: "public",
45+
allowOverwrite: true,
46+
ifMatch: metadata2.etag, // Stale ETag - blob was modified!
47+
});
48+
} catch (error) {
49+
if (error instanceof BlobPreconditionFailedError) {
50+
// The blob was modified since we last read it
51+
// Re-fetch, merge changes, and retry
52+
const freshMetadata = await head("config.json");
53+
await put("config.json", JSON.stringify({ setting: "user2" }), {
54+
access: "public",
55+
allowOverwrite: true,
56+
ifMatch: freshMetadata.etag, // Use fresh ETag
57+
});
58+
}
59+
}
60+
```
61+
62+
### Key Points
63+
64+
- **`allowOverwrite: true`**: Required when updating an existing blob at the same path
65+
- **`ifMatch`**: Only performs the write if the blob's current ETag matches this value
66+
- **Combined**: "Overwrite, but only if the blob hasn't changed since I last read it"
67+
- ETags follow RFC 7232 format with surrounding quotes (e.g., `"abc123"`)
68+
369
## 2.0.1
470

571
### Patch Changes

packages/blob/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vercel/blob",
3-
"version": "2.0.1",
3+
"version": "2.1.0",
44
"description": "The Vercel Blob JavaScript API client",
55
"homepage": "https://vercel.com/storage/blob",
66
"repository": {

test/next/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# vercel-storage-integration-test-suite
22

3+
## 0.3.15
4+
5+
### Patch Changes
6+
7+
- Updated dependencies [6c68442]
8+
- @vercel/blob@2.1.0
9+
310
## 0.3.14
411

512
### Patch Changes

test/next/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vercel-storage-integration-test-suite",
3-
"version": "0.3.14",
3+
"version": "0.3.15",
44
"private": true,
55
"scripts": {
66
"build": "next build",

0 commit comments

Comments
 (0)