mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix: keep a draft written at a key while a save is moving onto it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vn1jCHUcAdaG9C4NsUehF
This commit is contained in:
co-authored by
Claude Opus 5
parent
981ba6f882
commit
aca3aef0ec
@@ -757,6 +757,9 @@ export function createItemStore(ports: ItemRowPort) {
|
||||
const entries = new Map<string, Entry<any>>()
|
||||
/** The latest save moving onto a key, by that key, and when its move is done. */
|
||||
const moves = new Map<string, { owner: Entry<any>; done: Promise<void> }>()
|
||||
/** Drafts written from outside at a key while a save is moving onto it: newer than that write,
|
||||
* so the entry arriving there takes them rather than clearing the row it finds. */
|
||||
const arrivals = new Map<string, unknown>()
|
||||
|
||||
const internals: StoreInternals = {
|
||||
release(entry) {
|
||||
@@ -775,6 +778,11 @@ export function createItemStore(ports: ItemRowPort) {
|
||||
const displaced = entries.get(to)
|
||||
if (displaced && displaced !== entry) retire(displaced, entry)
|
||||
entries.set(to, entry)
|
||||
const arrived = arrivals.get(to)
|
||||
if (arrived !== undefined) {
|
||||
arrivals.delete(to)
|
||||
entry.applyExternal(arrived)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* A save about to write the item at `key` and move onto it. It goes after the command
|
||||
@@ -804,7 +812,9 @@ export function createItemStore(ports: ItemRowPort) {
|
||||
turn: Promise.all(waits).then(() => (claimant.waitingOn = [])),
|
||||
release() {
|
||||
release()
|
||||
if (moves.get(k) === move) moves.delete(k)
|
||||
if (moves.get(k) !== move) return
|
||||
moves.delete(k)
|
||||
arrivals.delete(k)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -929,8 +939,14 @@ export function createItemStore(ports: ItemRowPort) {
|
||||
|
||||
const bridge = {
|
||||
seed(workspace: string, kind: UserDraftItemKind, path: string, value: unknown): boolean {
|
||||
if (value === undefined || value === null) return false
|
||||
const entry = find(workspace, kind, path)
|
||||
if (!entry || value === undefined || value === null) return false
|
||||
if (!entry) {
|
||||
// Persisted as usual, and handed to the save moving onto this key when it lands.
|
||||
const k = keyString({ workspace, kind: kind as ItemKind, path })
|
||||
if (moves.has(k)) arrivals.set(k, snapshot(value))
|
||||
return false
|
||||
}
|
||||
entry.applyExternal(value)
|
||||
return true
|
||||
},
|
||||
@@ -946,7 +962,10 @@ export function createItemStore(ports: ItemRowPort) {
|
||||
},
|
||||
discard(workspace: string, kind: UserDraftItemKind, path: string): boolean {
|
||||
const entry = find(workspace, kind, path)
|
||||
if (!entry) return false
|
||||
if (!entry) {
|
||||
arrivals.delete(keyString({ workspace, kind: kind as ItemKind, path }))
|
||||
return false
|
||||
}
|
||||
void entry.discard()
|
||||
return true
|
||||
},
|
||||
|
||||
@@ -530,6 +530,32 @@ describe('item store: one entry per key', () => {
|
||||
expect(rows.writes.at(-1)).toEqual({ path: 'u/me/b', value: kept })
|
||||
})
|
||||
|
||||
it('keeps a draft written from outside at the key a save is moving onto', async () => {
|
||||
const rows = fakeRows()
|
||||
const store = createItemStore(rows.port)
|
||||
const gate = deferred()
|
||||
const b = { ...deployedRes, path: 'u/me/b' }
|
||||
const temporary = newItemPath()
|
||||
const { handle: moving } = store.acquire(
|
||||
{ workspace: 'w', kind: 'resource', path: temporary },
|
||||
{ workspace: 'w', path: temporary, template: b },
|
||||
adapter({}, () => gate.promise)
|
||||
)
|
||||
await settle()
|
||||
moving.value = { ...b, args: { a: 2 } }
|
||||
const moved = moving.save()
|
||||
await settle()
|
||||
const chat = { ...b, description: 'written by the chat meanwhile' }
|
||||
// Nobody holds the key yet, so the caller persists it itself.
|
||||
expect(store.bridge.seed('w', 'resource', 'u/me/b', chat)).toBe(false)
|
||||
|
||||
gate.resolve()
|
||||
expect(await moved).toMatchObject({ ok: true, moved: true })
|
||||
expect(moving.deployed).toEqual({ ...b, args: { a: 2 } })
|
||||
expect(moving.value).toEqual(chat)
|
||||
expect(rows.writes.at(-1)).toEqual({ path: 'u/me/b', value: chat })
|
||||
})
|
||||
|
||||
it('keeps a change the draft comparison ignores in an editor a move replaces', async () => {
|
||||
type Sched = { path: string; summary: string; permissioned_as?: string }
|
||||
const rows = fakeRows()
|
||||
|
||||
Reference in New Issue
Block a user