NAME
data-client — Async State Management without the Management. REST, GraphQL, SSE, Websockets
SYNOPSIS
INFO
DESCRIPTION
Async State Management without the Management. REST, GraphQL, SSE, Websockets
README
The scalable way to build applications with dynamic data.
Declarative resouce definitons for REST, GraphQL, Websockets+SSE and more
Performant rendering in React, NextJS, React Native, Expo, Vue
Schema driven. Zero updater functions.
📖Read The Docs | 🏁Getting Started | 🤖Codegen
🎮 Demos:
Todo |
Github Social |
NextJS SSR |
Websockets+SSR
Installation
npm install --save @data-client/react @data-client/rest @data-client/test
For more details, see the Installation docs page.
Skills
npx skills add reactive/data-client
Then run skill "data-client-setup"
Usage
Simple TypeScript definition
class User extends Entity { id = ''; username = ''; }class Article extends Entity { id = ''; title = ''; body = ''; author = User.fromJS(); createdAt = Temporal.Instant.fromEpochMilliseconds(0);
static schema = { author: User, createdAt: Temporal.Instant.from, }; }
Create collection of API Endpoints
const UserResource = resource({ path: '/users/:id', schema: User, optimistic: true, });
const ArticleResource = resource({ path: '/articles/:id', schema: Article, searchParams: {} as { author?: string }, optimistic: true, paginationField: 'cursor', });
One line data binding
const article = useSuspense(ArticleResource.get, { id });
return (
<article>
<h2>
{article.title} by {article.author.username}
</h2>
<p>{article.body}</p>
</article>
);
Reactive Mutations
const ctrl = useController();
return (
<>
<CreateArticleForm
onSubmit={article =>
ctrl.fetch(ArticleResource.getList.push, { id }, article)
}
/>
<ProfileForm
onSubmit={user =>
ctrl.fetch(UserResource.update, { id: article.author.id }, user)
}
/>
<button onClick={() => ctrl.fetch(ArticleResource.delete, { id })}>
Delete
</button>
</>
);
Subscriptions
const price = useLive(PriceResource.get, { symbol });
return price.value;
Type-safe Imperative Actions
const ctrl = useController();
await ctrl.fetch(ArticleResource.update, { id }, articleData);
await ctrl.fetchIfStale(ArticleResource.get, { id });
ctrl.expireAll(ArticleResource.getList);
ctrl.invalidate(ArticleResource.get, { id });
ctrl.invalidateAll(ArticleResource.getList);
ctrl.setResponse(ArticleResource.get, { id }, articleData);
ctrl.set(Article, { id }, articleData);
Programmatic queries
const queryTotalVotes = new Query( new Collection([BlogPost]), posts => posts.reduce((total, post) => total + post.votes, 0), );
const totalVotes = useQuery(queryTotalVotes); const totalVotesForUser = useQuery(queryTotalVotes, { userId });
const groupTodoByUser = new Query(
TodoResource.getList.schema,
todos => Object.groupBy(todos, todo => todo.userId),
);
const todosByUser = useQuery(groupTodoByUser);
Powerful Middlewares
class LoggingManager implements Manager { middleware: Middleware = controller => next => async action => { console.log('before', action, controller.getState()); await next(action); console.log('after', action, controller.getState()); };
cleanup() {} }
class TickerStream implements Manager {
middleware: Middleware = controller => {
this.handleMsg = msg => {
controller.set(Ticker, { id: msg.id }, msg);
};
return next => action => next(action);
};
init() {
this.websocket = new WebSocket('wss://ws-feed.myexchange.com');
this.websocket.onmessage = event => {
const msg = JSON.parse(event.data);
this.handleMsg(msg);
};
}
cleanup() {
this.websocket.close();
}
}
Integrated data mocking
const fixtures = [ { endpoint: ArticleResource.getList, args: [{ maxResults: 10 }] as const, response: [ { id: '5', title: 'first post', body: 'have a merry christmas', author: { id: '10', username: 'bob' }, createdAt: new Date(0).toISOString(), }, { id: '532', title: 'second post', body: 'never again', author: { id: '10', username: 'bob' }, createdAt: new Date(0).toISOString(), }, ], }, { endpoint: ArticleResource.update, response: ({ id }, body) => ({ ...body, id, }), }, ];
const Story = () => ( <MockResolver fixtures={options[result]}> <ArticleList maxResults={10} /> </MockResolver> );
...all typed ...fast ...and consistent
For the small price of 9kb gziped. 🏁Get started now
Features
-
Strong Typescript inference
- 🛌 React Suspense support
- 🧵 React 18 Concurrent mode compatible
- 💦 Partial Hydration Server Side Rendering
- 🎣 Declarative API
- 📝 Composition over configuration
- 💰 Normalized caching
- 💥 Tiny bundle footprint
- 🛑 Automatic overfetching elimination
- ✨ Fast optimistic updates
- 🧘 Flexible to fit any API design (one size fits all)
- 🔧 Debugging and inspection via browser extension
- 🌳 Tree-shakable (only use what you need)
- 🔁 Subscriptions
- ♻️ Optional redux integration
- 📙 Storybook mocking
- 📱 React Native support
- 📱 Expo support
- ⚛️ NextJS support
- 🚯 Declarative cache lifetime policy
- 🧅 Composable middlewares
- 💽 Global data consistency guarantees
- 🏇 Automatic race condition elimination
- 👯 Global referential equality guarantees
Examples
API
Reactive Applications
Rendering: useSuspense(), useLive(), useCache(), useDLE(), useQuery(), useLoading(), useDebounce(), useCancelling()
Event handling: useController() returns Controller
Method Subject Fetch ctrl.fetch Endpoint + Args ctrl.fetchIfStale Endpoint + Args Expiry ctrl.expireAll Endpoint ctrl.invalidate Endpoint + Args ctrl.invalidateAll Endpoint ctrl.resetEntireStore Everything Set ctrl.set Schema + Args ctrl.setResponse Endpoint + Args ctrl.setError Endpoint + Args ctrl.resolve Endpoint + Args Subscription ctrl.subscribe Endpoint + Args ctrl.unsubscribe Endpoint + Args Components: <DataProvider/>, <AsyncBoundary/>, <ErrorBoundary/>, <MockResolver/>
Data Mocking: Fixture, Interceptor, renderDataHook()
Middleware: LogoutManager, NetworkManager, SubscriptionManager, PollingSubscription, DevToolsManager
Define Data
Networking definition
| Data Type | Mutable | Schema | Description | Queryable |
|---|---|---|---|---|
| Object | ✅ | Entity, EntityMixin | single unique object | ✅ |
| ✅ | Union(Entity) | polymorphic objects (A | B) | ✅ | |
| 🛑 | Object | statically known keys | 🛑 | |
| Invalidate(Entity) | delete an entity | 🛑 | ||
| List | ✅ | Collection(Array) | growable lists | ✅ |
| 🛑 | Array | immutable lists | 🛑 | |
| All | list of all entities of a kind | ✅ | ||
| Map | ✅ | Collection(Values) | growable maps | ✅ |
| 🛑 | Values | immutable maps | 🛑 | |
| any | Query(Queryable) | memoized custom transforms | ✅ |