fix(core): commit state before finalize publishes (#38983)
This commit is contained in:
parent
65d2a4e00c
commit
1f2c59a1b6
2 changed files with 25 additions and 3 deletions
|
|
@ -59,7 +59,11 @@ export interface Options<State, DraftApi> {
|
||||||
readonly initial: () => State
|
readonly initial: () => State
|
||||||
/** Wraps mutable state in a domain-specific draft API. */
|
/** Wraps mutable state in a domain-specific draft API. */
|
||||||
readonly draft: MakeDraft<State, DraftApi>
|
readonly draft: MakeDraft<State, DraftApi>
|
||||||
/** Runs after all active transforms and before the rebuilt state becomes visible. */
|
/**
|
||||||
|
* Runs after the rebuilt state becomes visible. Update events published here
|
||||||
|
* act as read barriers: subscribers refetching on the event observe the
|
||||||
|
* committed state.
|
||||||
|
*/
|
||||||
readonly finalize?: (draft: DraftApi) => Effect.Effect<void>
|
readonly finalize?: (draft: DraftApi) => Effect.Effect<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,9 +85,8 @@ export function create<State, DraftApi>(options: Options<State, DraftApi>): Inte
|
||||||
const semaphore = Semaphore.makeUnsafe(1)
|
const semaphore = Semaphore.makeUnsafe(1)
|
||||||
|
|
||||||
const commit = Effect.fn("State.commit")(function* (next: State) {
|
const commit = Effect.fn("State.commit")(function* (next: State) {
|
||||||
const api = options.draft(next)
|
|
||||||
if (options.finalize) yield* options.finalize(api)
|
|
||||||
state = next
|
state = next
|
||||||
|
if (options.finalize) yield* options.finalize(options.draft(next))
|
||||||
})
|
})
|
||||||
|
|
||||||
const apply = (transform: TransformCallback<DraftApi>, draft: DraftApi) =>
|
const apply = (transform: TransformCallback<DraftApi>, draft: DraftApi) =>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,25 @@ describe("State", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("commits rebuilt state before finalize runs", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const observed: string[][] = []
|
||||||
|
const state: State.Interface<{ values: string[] }, { add: (item: string) => void }> = State.create({
|
||||||
|
initial: () => ({ values: [] as string[] }),
|
||||||
|
draft: (draft) => ({ add: (item: string) => draft.values.push(item) }),
|
||||||
|
finalize: () => Effect.sync(() => observed.push([...state.get().values])),
|
||||||
|
})
|
||||||
|
|
||||||
|
yield* state.transform((draft) => {
|
||||||
|
draft.add("value")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update events publish from finalize, so consumers reading on the event
|
||||||
|
// must observe the rebuilt state, not the previous one.
|
||||||
|
expect(observed).toEqual([["value"]])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("runs transforms during every reload", () =>
|
it.effect("runs transforms during every reload", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
let value = "first"
|
let value = "first"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue