-
Notifications
You must be signed in to change notification settings - Fork 20
Open
Labels
Description
Optimistic store rewrite
Big picture
startUpdate
(later, startOptimisticUpdate
?) and optimistic responses will make use of the same machinery: a stack of mergeable updates. startUpdate
by itself will be substantially simpler than doing the whole thing. But let's make sure that, while working on startUpdate
, we do stuff in a way that's compatible with the bigger picture.
There can be any number of optimistic responses, and they can be interspersed with network responses. Let's model that as a doubly-linked list of optimistic nodes.
Example: making optimistic responses
The state of the store might be as follows
base
// optimistic update made
base <- opt1
// another optimistic response
base <- opt1 <- opt2
// opt1 cleared
base <- opt2
// network response received
base <- opt2 <- NR
// network response received, and is merged into the previously received network response
base <- opt2 <- NR
// opt2 is cleared, network response is merged into parent
base
Modeling the stack
Something like
type NetworkResponseNode = {
kind: 'NetworkResponseNode';
childNode: OptimisticNode | null;
parent: OptimisticNode | null;
data: DataLayer;
};
type OptimisticNode = {
kind: 'OptimisticNode';
childNode: OptimisticNode | NetworkResponseNode | null;
parentNode: OptimisticNode | NetworkResponseNode | BaseNode;
data: DataLayer;
};
- So, we are ensuring that: a
NetworkResponseNode
must be the parent - No two
NetworkResponseNode
s can be adjacent
We could refine this to ensure that the base node's data has Query: { __ROOT: {} }
. I don't know if it's worth it.
The Isograph environment
The environment only needs a pointer the bottom-most node in the stack.
Reading
- Reading is always done from the bottom of the stack. If the scalar or linked field is present in the current item and is not
undefined
, then use that value, otherwise, try again in the next item in the stack
Proxies
startUpdate
(and optimistic updaters) are passed a function with signature(proxy: TUpdatableData) => void
. Call this function anupdater
- This
proxy
(call it aStoreProxy
) starts with an emptyDataLayer
. - Writes will mutate that
DataLayer
.- If we write
proxy.user.name = "John"
and in the stack, that user already has the name"John"
, then we still writeuser.name = "John
to theDataLayer
. - This is important, because you can have
base <- opt1
, whereopt1
haduser.name = "John"
. Then we later introduceopt2
, where we calluser.name = "John"
. Then the user disposes ofopt1
. We want theuser.name
to still beJohn
- If we write
- Reads go through that
DataLayer
, and later through the bottom item in the stack.- So, writes are immediately visible inside of the
updater
.
- So, writes are immediately visible inside of the
Creating an optimistic update
- Calling the updater and passing a proxy, thus generating a
DataLayer
- adding an empty
OptimisticNode
to the bottom of the stack, which contains thatDataLayer
- calling subscriptions
- returning a destructor that removes the optimistic node from the stack and calls subscriptions
- (later?) returning a function that converts the
OptimisticNode
to aNetworkResponseNode
Removing an item and upgrading to a NetworkResponseNode
- If we do remove an optimistic node or upgrade, then we have to merge the stack to enforce that there are no adjacent
NetworkResponseNode
s
Receiving a network response
- If we receive a network response, we add a
NetworkResponseNode
to the stack and merge, and call subscriptions
startUpdate
- Call the updater and pass a proxy, thus generate a
DataLayer
- Add a
NetworkResponseNode
and merge - call subscriptions
Subscriptions
- As a first pass, when we add/remove from the stack, we can trigger all subscriptions with overlapping records in that
DataLayer
(which is effectively what we do when we receive a network response anyway). - Each subscription is "smart" in that it re-reads and short circuits if nothing changed. So, triggering a few extra subscriptions is probably fine (e.g. if you remove
opt1
inbase <- opt1 <- opt2
, the records thatopt1
modified might be shadowed byopt2
).
What it takes to implement startUpdate
(but not optimistic updaters)
- There is no stack, since
startUpdate
creates a network response node and that is immediately merged, so there is only ever one item - But proxies need to read through a DataLayer and then the base layer
- But TBH I don't think the stuff to support optimistic updates is that much extra work
Misc: startOptimisticUpdate
and (non-optimistic) updaters
- It should be extremely easy to also add a
startOptimisticUpdate
function that does the exact same stuff, but creates anOptimisticNode
and also returns a destructor (& promoter) - Non-optimistic updaters (which are called when the network response is received) should also be fairly straightforward
Misc: can updaters be called multiple times?
- Yeah. Ideally, we should call optimistic updaters (and even the
startUpdate
call) many times! E.g. if we havebase <- opt1 <- startUpdate
, we could re-call thestartUpdate
afteropt1
is removed - But TBH I think we can add this after the fact