Storage
The two tiers an environment keeps state in: the session it holds while it is open, and the interaction store that outlives the visit.
@internote/canvas-sdk#Functions
<S = unknown>(
declaration: CanvasStorageDeclaration | undefined,
session: EnvironmentSessionState<S>,
db?: KeyValueStorage,
): CanvasStorage<S><T>(equal?: TransientEqual<T>): TransientCollection<T>The snapshot is rebuilt on write and shared between reads, which is not a detail: useSyncExternalStore compares the snapshot by identity and spins forever on a getter that builds a new array each time.
<T>(equal?: TransientEqual<T>): TransientValue<T>Declared at module scope beside the surface that publishes it, exactly as the five private stores were: the channel is the contract between an environment's surface and its own footer, and nothing outside that pair should be reading it.
(identity: EnvironmentIdentity): string(store: InteractionStore, elementHash: string): KeyValueStorage#Classes
typeof SessionStore#Constants
InteractionStore that keeps nothing: reads return null and saves resolve without storing.InteractionStoreKeyValueStorage#Interfaces
dbtiers: 'session+db' types; a no-op store elsewhere so type code can be written unconditionally.KeyValueStoragesessioncontinue:, never persisted.EnvironmentSessionState<S>storage field.initState() => unknownserializeForContinuecontinue: successor. Defaults to identity.(state: unknown) => unknowntierssessionsession+dbSession state is keyed by all three, so an environment keeps its state across scroll-away and a continue: successor can find its source.
environmentIdstringinternoteIdstringsceneIdstringclearvoidgetundefined before anything was written.S | undefinedsetvoidsubscribe() => voidupdatevoidKeying by content means the same authored element finds its state again after a reload. The host supplies the implementation.
getInteractionnull when it stored nothing.GetInteractionsaveInteractionSaveInteractionapi.storage.Every element gets a separate namespace, so keys never collide between elements. It is the storage surface plugins get.
deletePromise<void>getnull when there is none.Promise<T | null>setPromise<void>setmemberKey, or null to take that member's entry away.voidsnapshotreadonly T[]subscribe() => voidgetT | nullsetnull to withdraw. Subscribers wake only on a real change.voidsubscribe() => void#Types
EnvironmentContext, the identity itself, or the store key.string | EnvironmentIdentity | { identity: EnvironmentIdentity }The bare key is there for the marks. A heatmap or a choropleth sits deep inside a surface's tree and is handed its environment's key as a prop — threading the whole identity down to it would be ceremony for a value that is already the thing this store is keyed by.
(elementHash: string) => unknown(elementHash: string, content: unknown) => Promise<void>'session' for state that lasts while the page is open, or 'session+db' to also save each reader's answers.sessionsession+db(previous: T, next: T) => boolean