This document describes how a Coaction store is created, initialized, and
mutated inside packages/core.
create() has three runtime outcomes:
- local store
- main/shared store
- client/shared mirror
The creation path is the same until transport handling splits:
- Infer store mode from
transport,clientTransport,worker, andworkerType - Allocate the store shell and internal runtime state
- Install base store methods:
setState(),getState(),subscribe(),destroy(),apply(),getPureState() - Infer
isSliceStorefromsliceMode - Apply middlewares in order
- Materialize the initial state through
getInitialState() - Materialize the raw state and bound methods through
getRawState() - Wrap the store for callable usage
- If needed, attach main-side transport listeners or create a client mirror
The important detail is step 5: middlewares run before the initial state is
finalized. A middleware may therefore influence initialization behavior by
wrapping store methods such as setState(), apply(), or destroy().
Coaction has three store authority categories:
- Local store
store.share === false- mutation authority is local
- no transport is attached
- Main store
store.share === 'main'- mutation authority is local to the main runtime
- outgoing updates are emitted over
store.transport
- Client store
store.share === 'client'- mutation authority lives elsewhere
- direct
setState()is rejected - store methods become transport-backed async calls
The authority model is strict:
- only local/main stores may execute
setState() - client stores may only request execution through a bound store method
- all shared state convergence is driven by the main store sequence counter
getInitialState() is responsible for turning createState into an object
shape that the runtime can consume.
It supports:
- a single Coaction state factory
- a pre-built object
- a third-party store exposing
getState() - a function-returning-store form used by some adapters
- binder-backed state objects tagged by
defineExternalStoreAdapter()orcreateBinder()
For slice stores, this work happens per slice key.
getRawState() then derives two parallel representations:
internal.rootState- raw serializable state without method descriptors
internal.module- user-facing state object with methods/getters rebound to the latest state
This separation is what allows Coaction to serialize state while keeping method binding and getter behavior stable.
There are two mutation paths.
This is the fast path for local stores when patches are not enabled and the runtime does not need mutable-instance integration.
Flow:
setState()mutates or shallow-merges a new root snapshot- listeners are notified directly
- no patch sequence is emitted
This path is used when:
- the store is shared
enablePatchesis enabled- an integration requires patch handling
Flow:
setState()enters batching mode- Mutative produces
patchesandinversePatches - optional middleware compatibility hook
store.patch()can transform them store.apply()commits the final patches tointernal.rootState- immutable listeners are notified
- shared stores emit the patch sequence over the transport
Mutable-instance integrations add one extra phase. Actions may execute against a
draft, and the runtime later calls finalizeDraft() to obtain the patch pair.
That finalized patch pair still goes through the same store.patch() and
store.apply() pipeline before it is emitted.
Methods exposed through getState() or slice state always execute against the
latest user-facing state object. This is why patterns such as destructuring a
method and then calling it later continue to work even when the method relies on
this.
Middlewares are reduced left-to-right:
- the base store shell is created
middlewares[0]receives that storemiddlewares[1]receives the previous middleware result- the final store-like object is copied back onto the base store shell
- state initialization runs against the middleware-enhanced store
Implications:
- initialization-time
setState()ordestroy()behavior can be affected by a middleware - middlewares must preserve the full store contract
- replacing the store object is supported, but the returned object must still be store-like
Officially supported runtime combinations at the core layer:
- local single store
- local slices store
- main/shared single store
- main/shared slices store
- client/shared single store
- client/shared slices store
Officially unsupported combinations at the core layer:
- direct
setState()in client mode - binder-backed state inside slices mode
- shared mode with patch generation explicitly disabled
Those boundaries are enforced partly by runtime checks and partly by adapter or middleware-specific contracts.