diff --git a/.changeset/few-eyes-remember.md b/.changeset/few-eyes-remember.md new file mode 100644 index 000000000000..1e1acf867498 --- /dev/null +++ b/.changeset/few-eyes-remember.md @@ -0,0 +1,7 @@ +--- +'@data-client/normalizr': patch +'@data-client/rest': patch +'@data-client/ssr': patch +--- + +docs: Update readme diff --git a/packages/normalizr/README.md b/packages/normalizr/README.md index f50402936c6f..6416414c659a 100644 --- a/packages/normalizr/README.md +++ b/packages/normalizr/README.md @@ -2,7 +2,7 @@ [![CircleCI](https://circleci.com/gh/reactive/data-client/tree/master.svg?style=shield)](https://circleci.com/gh/data-clientdata-clients) [![Coverage Status](https://img.shields.io/codecov/c/gh/reactive/data-client/master.svg?style=flat-square)](https://app.codecov.io/gh/data-clientdata-clients?branch=master) -[![npm version](https://img.shields.io/npm/v/@data-client/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/data-clients/normalizr) [![npm downloads](https://img.shields.io/npm/dmdata-clientks/normalizr.svg?style=flat-square)](https://www.npmjs.com/packagdata-clientoks/normalizr) +[![npm version](https://img.shields.io/npm/v/@data-client/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/data-clients/normalizr) [![npm downloads](https://img.shields.io/npm/dm/@data-client/normalizr.svg?style=flat-square)](https://www.npmjs.com/packagdata-clientoks/normalizr) ## Install diff --git a/packages/rest/README.md b/packages/rest/README.md index 49ec5bfec0fd..a35ff9e6b31e 100644 --- a/packages/rest/README.md +++ b/packages/rest/README.md @@ -1,4 +1,4 @@ -# Data Client for REST +# TypeScript HTTP definitions [![CircleCI](https://circleci.com/gh/reactive/data-client/tree/master.svg?style=shield)](https://circleci.com/gh/reactive/data-client) [![Coverage Status](https://img.shields.io/codecov/c/gh/reactive/data-client/master.svg?style=flat-square)](https://app.codecov.io/gh/reactive/data-client?branch=master) @@ -8,76 +8,106 @@ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Chat](https://img.shields.io/discord/768254430381735967.svg?style=flat-square&colorB=758ED3)](https://discord.gg/35nb8Mz) -Extensible CRUD patterns for REST APIs. -
-**[📖Read The Docs](https://dataclient.io/rest)**  |  [🎮Github Demo](https://stackblitz.com/github/reactive/data-client/tree/master/examples/github-app?file=src%2Fresources%2FIssue.tsx) +**[📖Read The Docs](https://dataclient.io/rest)**  |  [🎮Todo Demo](https://stackblitz.com/github/reactive/data-client/tree/master/examples/todo-app?file=src%2Fresources%2FTodoResource.ts)  |  [🎮Github Demo](https://stackblitz.com/github/reactive/data-client/tree/master/examples/github-app?file=src%2Fresources%2FIssue.tsx)
-### Simple TypeScript definition +## RestEndpoint + +Simplify TypeScript fetch functions with [RestEndpoint](https://dataclient.io/rest/api/RestEndpoint) ```typescript -import { Entity, createResource } from '@data-client/rest'; +const getTodo = new RestEndpoint({ + urlPrefix: 'https://jsonplaceholder.typicode.com', + path: '/todos/:id', +}); +``` -class Article extends Entity { - id: number | undefined = undefined; - title = ''; - body = ''; +[RestEndpoint](https://dataclient.io/rest/api/RestEndpoint) infers [path-to-regex](https://github.com/pillarjs/path-to-regexp#compile-reverse-path-to-regexp) +argument types, enabling enforcement of function calls - pk() { - return this.id; - } -} -const ArticleResource = createResource({ - path: '/articles/:id', - schema: Article, -}); +```typescript +// signature requires id! +const todo = await getTodo({ id: 5 }); ``` -[Entity](https://dataclient.io/rest/api/Entity) defines a data model. -[createResource](https://dataclient.io/rest/api/createResource) creates a [collection](https://dataclient.io/rest/api/createResource#members) -of six [RestEndpoints](https://dataclient.io/rest/api/RestEndpoint) +It automatically handles REST concepts like JSON serialization, consolidated error handling and more. -[RestEndpoints](https://dataclient.io/rest/api/RestEndpoint) are functions (and more) that return a Promise. -Both call parameters and return value are [automatically inferred](https://dataclient.io/rest/api/RestEndpoint#typing) from -the options used to construct them. +## Resources -`path` is a templating language using [path-to-regex compile](https://github.com/pillarjs/path-to-regexp#compile-reverse-path-to-regexp). +Simplify related CRUD endpoints with [Resources](https://dataclient.io/rest/api/createResource) -### [Standard CRUD Endpoints](https://dataclient.io/rest/api/createResource#members) +[Resources](https://dataclient.io/rest/api/createResource) are a collection of `methods` for a given `data model`. -#### Reads +[Entities](https://dataclient.io/rest/api/Entity) and [Schemas](https://dataclient.io/concepts/normalization) declaratively define the _data model_. +[RestEndpoints](https://dataclient.io/rest/api/RestEndpoint) are the [_methods_]() on +that data. ```typescript -const article = useSuspense(ArticleResource.get, { id: 5 }); -const articles = useSuspense(ArticleResource.getList); +class Todo extends Entity { + id = 0; + userId = 0; + title = ''; + completed = false; + pk() { + return `${this.id}`; + } +} +const TodoResource = createResource({ + urlPrefix: 'https://jsonplaceholder.typicode.com', + path: '/todos/:id', + searchParams: {} as { userId?: string | number }, + schema: Todo, + paginationField: 'page', +}); ``` +One Resource defines [seven endpoints](https://dataclient.io/rest/api/createResource#members): + ```typescript -const [article, setArticle] = useState(); -useEffect(() => { - setArticle(await ArticleResource.get({ id: 5 })); -}, []); +// GET https://jsonplaceholder.typicode.com/todos/5 +let todo5 = await TodoResource.get({ id: 5 }); +// GET https://jsonplaceholder.typicode.com/todos +const todoList = await TodoResource.getList(); +// GET https://jsonplaceholder.typicode.com/todos?userId=1 +const userOneTodos = await TodoResource.getList({ userId: 1 }); +// POST https://jsonplaceholder.typicode.com/todos +const newTodo = await TodoResource.getList.push({ title: 'my todo' }); +// POST https://jsonplaceholder.typicode.com/todos?userId=1 +const newUserOneTodo = await TodoResource.getList.push({ userId: 1 }, { title: 'my todo' }); +// GET https://jsonplaceholder.typicode.com/todos?userId=1&page=2 +const nextPageOfTodos = await TodoResource.getList.getPage({ userId: 1, page: 2 }); +// PUT https://jsonplaceholder.typicode.com/todos/5 +todo5 = await TodoResource.update({ id: 5 }, { title: 'my todo' }); +// PATCH https://jsonplaceholder.typicode.com/todos/5 +todo5 = await TodoResource.partialUpdate({ id: 5 }, { title: 'my todo' }); +// DELETE https://jsonplaceholder.typicode.com/todos/5 +await TodoResource.delete({ id: 5 }); ``` -#### Mutates +## Free React Integration + +No need for any custom hooks. All endpoints are 100% compatible with +the fastest way to fetch and mutate REST data: [Reactive Data Client](https://dataclient.io) + +#### [Rendering](https://dataclient.io/docs/getting-started/data-dependency) ```typescript -const ctrl = useController(); -const updateArticle = data => ctrl.fetch(ArticleResource.update, { id }, data); -const partialUpdateArticle = data => - ctrl.fetch(ArticleResource.partialUpdate, { id }, data); -const createArticle = data => ctrl.fetch(ArticleResource.getList.push, data); -const deleteArticle = data => ctrl.fetch(ArticleResource.delete, { id }); +const todo = useSuspense(TodoResource.get, { id: 5 }); +const todoList = useSuspense(TodoResource.getList); ``` -### Use with Node +#### [Mutations](https://dataclient.io/docs/getting-started/mutations) ```typescript -const article = await ArticleResource.get({ id: 5 }); -const articles = await ArticleResource.getList(); +const ctrl = useController(); +const updateTodo = data => ctrl.fetch(TodoResource.update, { id }, data); +const partialUpdateTodo= data => + ctrl.fetch(TodoResource.partialUpdate, { id }, data); +const createTodo = data => ctrl.fetch(TodoResource.getList.push, data); +const deleteTodo = data => ctrl.fetch(TodoResource.delete, { id }); ``` ### [Programmatic queries](https://dataclient.io/rest/api/Query) @@ -102,8 +132,6 @@ const articlesDescending = useCache(sortedArticles, { asc: false }); TypeScript is optional, but will only work with 4.0 or above. 4.1 is needed for stronger types as it supports inferring argument types from the path templates. -Version 5.x can be used for older TypeScript versions. - ### Prior Art - [Backbone Model](https://backbonejs.org/#Model) diff --git a/packages/ssr/README.md b/packages/ssr/README.md index e5b359f959ca..3df48c349eb1 100644 --- a/packages/ssr/README.md +++ b/packages/ssr/README.md @@ -1,9 +1,8 @@ -# Data Client Server Side Rendering helpers +# [![Reactive Data Client](./data_client_logo_and_text.png?sanitize=true)](https://dataclient.io) [![CircleCI](https://circleci.com/gh/reactive/data-client/tree/master.svg?style=shield)](https://circleci.com/gh/reactive/data-client) [![Coverage Status](https://img.shields.io/codecov/c/gh/reactive/data-client/master.svg?style=flat-square)](https://app.codecov.io/gh/reactive/data-client?branch=master) [![npm downloads](https://img.shields.io/npm/dm/@data-client/ssr.svg?style=flat-square)](https://www.npmjs.com/package/@data-client/ssr) -[![bundle size](https://img.shields.io/bundlephobia/minzip/@data-client/ssr?style=flat-square)](https://bundlephobia.com/result?p=@data-client/ssr) [![npm version](https://img.shields.io/npm/v/@data-client/ssr.svg?style=flat-square)](https://www.npmjs.com/package/@data-client/ssr) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Chat](https://img.shields.io/discord/768254430381735967.svg?style=flat-square&colorB=758ED3)](https://discord.gg/35nb8Mz) @@ -11,81 +10,24 @@
**[📖Read The Docs](https://dataclient.io/docs/guides/ssr)**  |  -[🎮NextJS SSR Demo](https://stackblitz.com/github/reactive/data-client/tree/master/examples/nextjs?file=pages%2FAssetPrice.tsx) +[🎮NextJS Demo](https://stackblitz.com/github/reactive/data-client/tree/master/examples/nextjs?file=pages%2FAssetPrice.tsx)
-Hydrate/dehydration utilities for [Data Client](https://dataclient.io) +Hydrate/dehydration utilities for Server Side Rendering with the [Reactive Data Client](https://dataclient.io) -## Server side +## Usage -```tsx -import express from 'express'; -import { renderToPipeableStream } from 'react-dom/server'; -import { - createPersistedStore, - createServerDataComponent, -} from '@data-client/ssr'; +Integration with -const rootId = 'react-root'; +- [NextJS](https://dataclient.io/docs/guides/ssr#nextjs) +- Anansi + ```bash + npx @anansi/cli hatch my-project + ``` +- [ExpressJS](https://dataclient.io/docs/guides/ssr#express-js) -const app = express(); -app.get('/*', (req: any, res: any) => { - const [ServerCacheProvider, useReadyCacheState, controller] = - createPersistedStore(); - const ServerDataComponent = createServerDataComponent(useReadyCacheState); - - controller.fetch(NeededForPage, { id: 5 }); - - const { pipe, abort } = renderToPipeableStream( - ]} - rootId={rootId} - > - {children} - , - - { - onCompleteShell() { - // If something errored before we started streaming, we set the error code appropriately. - res.statusCode = didError ? 500 : 200; - res.setHeader('Content-type', 'text/html'); - pipe(res); - }, - onError(x: any) { - didError = true; - console.error(x); - res.statusCode = 500; - pipe(res); - }, - }, - ); - // Abandon and switch to client rendering if enough time passes. - // Try lowering this to see the client recover. - setTimeout(abort, 1000); -}); - -app.listen(3000, () => { - console.log(`Listening at ${PORT}...`); -}); -``` - -## Client - -```tsx -import { hydrateRoot } from 'react-dom'; -import { awaitInitialData } from '@data-client/ssr'; - -const rootId = 'react-root'; - -awaitInitialData().then(initialState => { - hydrateRoot( - document.getElementById(rootId), - {children}, - ); -}); -``` +For more details, see the [Server Side Rendering docs page](https://dataclient.io/docs/guides/ssr). ## NextJS @@ -191,6 +133,78 @@ export default class MyDocument extends DataClientDocument { +## Express JS + +### Server side + +```tsx +import express from 'express'; +import { renderToPipeableStream } from 'react-dom/server'; +import { + createPersistedStore, + createServerDataComponent, +} from '@data-client/ssr'; + +const rootId = 'react-root'; + +const app = express(); +app.get('/*', (req: any, res: any) => { + const [ServerCacheProvider, useReadyCacheState, controller] = + createPersistedStore(); + const ServerDataComponent = createServerDataComponent(useReadyCacheState); + + controller.fetch(NeededForPage, { id: 5 }); + + const { pipe, abort } = renderToPipeableStream( + ]} + rootId={rootId} + > + {children} + , + + { + onCompleteShell() { + // If something errored before we started streaming, we set the error code appropriately. + res.statusCode = didError ? 500 : 200; + res.setHeader('Content-type', 'text/html'); + pipe(res); + }, + onError(x: any) { + didError = true; + console.error(x); + res.statusCode = 500; + pipe(res); + }, + }, + ); + // Abandon and switch to client rendering if enough time passes. + // Try lowering this to see the client recover. + setTimeout(abort, 1000); +}); + +app.listen(3000, () => { + console.log(`Listening at ${PORT}...`); +}); +``` + +### Client + +```tsx +import { hydrateRoot } from 'react-dom'; +import { awaitInitialData } from '@data-client/ssr'; + +const rootId = 'react-root'; + +awaitInitialData().then(initialState => { + hydrateRoot( + document.getElementById(rootId), + {children}, + ); +}); +``` + ## API ### createPersistedStore(managers) => [ServerCacheProvider, useReadyCacheState, controller, store] diff --git a/packages/ssr/data_client_logo_and_text.png b/packages/ssr/data_client_logo_and_text.png new file mode 100644 index 000000000000..29147f41dbb1 Binary files /dev/null and b/packages/ssr/data_client_logo_and_text.png differ