Data, owned

Your application's data should have an owner.

Fetching libraries dedupe requests. State libraries give you a store. Neither says which part of your application owns a piece of data, or what its status means to every consumer.

data-cap treats a piece of data, with the getters, mutators and subscriptions that touch it, as one capability-owned contract that every consumer reads the same synchronous snapshot from.

Give every piece of application data one owner and one shared status vocabulary, without replacing your database, transport or state library.

Pre-1.0 · v0.8.0 · MIT

Quick start
npm install data-cap
npx data-cap init

data-cap sits under or next to TanStack Query, Redux, Zustand or plain fetch and useState. No database, transport or state-library change is needed.

Why it exists

Fetching and state libraries answer different questions.

A fetching library dedupes requests. A state-management library gives you a store. Neither answers which piece of data a component owns versus merely consumes, whether an optimistic value will disagree with what other components expect, or which capability a subscription's transport is shared with.

Where the agreement lives today: each component's own idea of "loading", each component's own idea of "stale", each team's optimistic-update logic — no single source of truth for what the data's status means.

data-cap makes the agreement itself the artifact: a capability declares its fields and the operations that touch them, and every consumer reads the same synchronously available snapshot.

How it works

Full manual control, or batteries included: same primitives.

buildData, createDataStore and coordinator are the same Stable primitives createData composes. Start wherever matches how much control the capability needs.

The same Stable primitives can be composed manually for full control, or used through createData for a batteries-included dedup, status, and optimistic-mutation lifecycle.

Neither path is the one way: pick per capability. Fields are readable synchronously from the moment buildData() or createData() returns, with no throw-until-ready gate, and info is mandatory on every DataState, so consumers never special-case whether metadata exists yet.

See it

Not a mockup: real output from this repository.

Run against examples/application in this repo:

npm run docs
Wrote evidence model: docs/data.evidence.json
[generate-docs] wrote src/generated/data.manifest.ts (1 active capability(ies)), docs/DATA.md, docs/OWNERSHIP.md, 4 flow file(s), and docs/data.evidence.json.

Source: examples/application

A manifest of every active capability, a documentation catalog, and a dependency and ownership report, statically derived from real code, not hand-maintained.

Guarantees

What is guaranteed, and where it stops.

Each claim states its scope and links to the document that defines it, so you can check the boundary before you rely on it.

Ownership is enforced by warning, not by throwing.Scope: An unowned write is dropped and reported instead of crashing the application. data-cap is not an authorization, encryption or compliance system.Read the security model ↗
The build tool never executes your code.Scope: Discovered files are analyzed statically and never imported or evaluated. This covers the build tooling only, not your application at runtime.Read the security model ↗
The two-module-system hazard is checked and documented.Scope: It applies when a dependency tree resolves the package through both ESM and CommonJS, which creates two coordinator instances. The documented behaviour is graceful degradation, not silent corruption.Read the security model ↗

Why not just TanStack Query, Redux, or manual fetch-and-state?

You keep all of them.

data-cap doesn't replace your transport or your store. It adds the layer none of them cover: one capability-owned contract that every consumer reads the same synchronous snapshot from, plus a static-analysis pass that turns it into ownership and dependency evidence.

Where common approaches stop and data-cap begins
Tool What it solves What it doesn't
Manual fetch + useState Gets data on screen No shared status vocabulary, no dedup, no optimistic-update contract; every component re-derives its own version
TanStack Query Fetching, caching, request dedup Says nothing about mutation-driven data your app owns outside a query, or how two consumers agree on ownership
Redux / Zustand / a global store One shared place to put state Doesn't distinguish declared ownership from incidental access; anything can write anywhere

Adoption

From manual control to batteries-included.

Most applications start with whichever primitive matches how much control a capability needs: full manual control (buildData, createDataStore, coordinator) or the batteries-included createData. Neither is the one way.

One capability at a time

  1. Pick the primitive that fits one capability.
  2. Migrate the next capability when it needs more control or less boilerplate. Each is independent.

You probably don't need it when

Your application is small, one team owns the entire data layer, and there's little reason to separate business capabilities from their storage or fetching implementation. A conventional fetch-and-useState pattern, or your existing state-management library alone, may be simpler.

For decision-makers

A one-page brief for your manager

data-cap gives every piece of application data one owner and one shared status, so every part of the application agrees on what the data is doing.

It answers what this is, why it matters, what it costs, what could go wrong and what is being asked, in plain language, on one printable page.

Try it

Trial it on one feature.

Read the README for a first capability, or run an example to see the ownership report it produces.

data-cap is pre-1.0. Per VERSIONING.md, a minor release may still change a Stable API before 1.0, so pin accordingly and read the changelog on every minor upgrade.