Skip to content

DockerStateStore

Domain state for one Docker endpoint, shared by the TUI (DockerState wraps it) and the VS Code extension (DockerService and ContainerWatcherService). It owns the refresh and Docker-event policy, so both surfaces behave the same.

Usage

import { DockerClient, DockerStateStore, EventWatcher } from 'sidekick-docker-shared';

const client = new DockerClient();
const store = new DockerStateStore({
  client,
  cwd: process.cwd(),                       // where to read the compose file; omit for none
  onChange: (change) => render(store.getSnapshot()),
});

await store.refresh();                      // first full pass

// The store is passive: the caller owns the event stream and any interval.
const watcher = new EventWatcher(client, { onEvent: (e) => store.processEvent(e) });
watcher.start();
const timer = setInterval(() => void store.refresh(), 30_000);

Constructor Options

Option Default Description
client — The DockerClient to read from
cwd none Directory to read compose file config from; undefined skips it
composeReader CachedComposeFileReader Source of compose file config (see Compose)
cliEnv none Endpoint overrides for the default compose reader (see dockerCliEnv)
include everything Which lists to fetch: { images?, volumes?, networks?, compose? }; containers are always fetched. The VS Code tree uses a containers-only store
eventDebounceMs 500 Coalescing window for event-triggered refreshes
refreshTimeoutMs 20000 A pass that takes longer is abandoned, keeping the previous data
onChange — Called only when something actually changed, with a StoreChange (cause: 'refresh' \| 'event', plus the removed container/image IDs)
log — Debug sink for swallowed errors

Methods

Method Description
getSnapshot() DockerStateSnapshot: containers, images, volumes, networks, composeProjects, composeFileConfig, daemonConnected, lastRefresh, resourceErrors. Arrays are replaced, never mutated
refresh({ reloadCompose? }) Resolves after a pass that started after the call commits; never rejects. reloadCompose bypasses the compose cache
requestRefresh() Debounced refresh
processEvent(event) Classify the event, patch state immediately, and request a refresh when the effect needs one
isContainerRunning(id) true/false, or undefined when the container is unknown — what FollowStream's isTargetRunning expects
isComposeTargetRunning(project, service) The same for a Compose project or service
getRunningContainerIds() IDs of running containers
dispose() Cancel pending work

Behaviour

  • A pass always commits. Events that arrive while a pass is fetching are journalled and replayed over its result, so a stale listing cannot resurrect a destroyed container, and a steady event stream cannot starve refreshes.
  • One container listing per pass, via DockerClient.listResources(), which also feeds volume and network usage.
  • Deadline. A pass that outlives refreshTimeoutMs is abandoned: previous data is kept, resourceErrors is set per kind, and the next pass is free to run.
  • Partial failures keep the last good data for the failed kind and record why in resourceErrors.

Event Policy

classifyEvent(event) and applyEffect(containers, effect) are pure functions, exported for testing and reuse:

Events Effect
healthcheck exec_*, attach, resize, kill, oom, volume mount/unmount, daemon events Ignored
health_status Patched in place
start, unpause, restart Patched, then re-synced with a refresh
stop, die, pause Patched
destroy Removed
create, rename, update, unknown verbs; image/volume/network changes Debounced refresh