Signed requests: X-Nonce header; composite: sync skips unconfirmed writes
CI / Node 22 (push) Successful in 13s
CI / Node 24 (push) Successful in 13s
CI / Node 26 (push) Successful in 14s
CI / Node 22 (pull_request) Successful in 12s
CI / Node 24 (pull_request) Successful in 13s
CI / Node 26 (pull_request) Successful in 14s
CI / Chromium (push) Successful in 46s
CI / Publish to staging registry (push) Successful in 9s
CI / Chromium (pull_request) Successful in 45s
CI / Publish to staging registry (pull_request) Skipped
CI / Node 22 (push) Successful in 13s
CI / Node 24 (push) Successful in 13s
CI / Node 26 (push) Successful in 14s
CI / Node 22 (pull_request) Successful in 12s
CI / Node 24 (pull_request) Successful in 13s
CI / Node 26 (pull_request) Successful in 14s
CI / Chromium (push) Successful in 46s
CI / Publish to staging registry (push) Successful in 9s
CI / Chromium (pull_request) Successful in 45s
CI / Publish to staging registry (pull_request) Skipped
- Requests carry a random X-Nonce, signed as ${timestamp}:${nonce}:${path}:${body}. The
server rejects a nonce the same key already used within the timestamp window. Identical
requests at the same moment (two tabs, parallel reads) are now distinct, replacing the
per-process increasing-timestamp workaround.
- Composite: a sync no longer reverts keys with local writes the source hasn't confirmed
yet (in flight or queued); they briefly disappeared when a sync's scan ran before the
write reached the source. Pending keys are reloaded from a persistent queue on start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
This commit is contained in:
@@ -257,4 +257,29 @@ describe('KVStorageComposite sync', () => {
|
||||
['b', 2],
|
||||
])
|
||||
})
|
||||
|
||||
it('a sync started while a write is on its way to the source does not undo it', async () => {
|
||||
let release!: () => void
|
||||
const gate = new Promise<void>((r) => (release = r))
|
||||
const inner = new KVStorageInMemory()
|
||||
const source = new KVStorageCounting(inner)
|
||||
const writeMany = source.writeMany.bind(source)
|
||||
source.writeMany = async (changes) => {
|
||||
await gate // a slow request
|
||||
return writeMany(changes)
|
||||
}
|
||||
const replica = new KVStorageInMemory()
|
||||
const db = new KVStorageComposite({ source, replicas: [replica] })
|
||||
composites.push(db)
|
||||
await db.sync()
|
||||
const writing = db.set('a', 1)
|
||||
await vi.waitFor(async () => expect(await replica.readMany(['a'])).toEqual([1])) // applied locally, not yet on the source
|
||||
const syncing = db.sync()
|
||||
await new Promise((r) => setTimeout(r, 10))
|
||||
expect(await replica.readMany(['a'])).toEqual([1]) // the sync skips the unconfirmed key
|
||||
release()
|
||||
await Promise.all([writing, syncing])
|
||||
expect(await replica.readMany(['a'])).toEqual([1])
|
||||
expect(await inner.readMany(['a'])).toEqual([1])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user