4.0.8 • Published 9 months ago

@rest-hooks/endpoint v4.0.8

Weekly downloads
10,420
License
Apache-2.0
Repository
github
Last release
9 months ago

TypeScript Standard Endpoints

CircleCI Coverage Status npm downloads bundle size npm version PRs Welcome

Declarative, strongly typed, reusable network definitions for networking libraries.

📖Read The Docs

Usage

1) Take any class and async functions

export class Todo {
  id = 0;
  userId = 0;
  title = '';
  completed = false;
}

export const getTodo = (id: string) =>
  fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then(res => res.json());

export const getTodoList = () =>
  fetch('https://jsonplaceholder.typicode.com/todos').then(res => res.json());

export const updateTodo = (id: string, body: Partial<Todo>) =>
  fetch(`https://jsonplaceholder.typicode.com/todos/${id}`, {
    method: 'PUT',
    body: JSON.stringify(body),
  }).then(res => res.json());

2) Turn them into Resources

import { schema, Endpoint } from '@rest-hooks/endpoint';
import { Todo, getTodoList, updateTodo } from './existing';

export const TodoEntity = schema.Entity(Todo, { key: 'Todo' });

export const TodoResource = {
  get: new Endpoint(getTodo, {
    schema: TodoEntity,
  }),
  getList: new Endpoint(getTodoList, {
    schema: [TodoEntity],
  }),
  update: new Endpoint(updateTodo, {
    schema: TodoEntity,
    sideEffect: true,
  }),
};

3) Reuse with different hooks

import { useSuspense, useController } from '@rest-hooks/react';

function TodoEdit() {
  const todo = useSuspense(TodoResource.get, '5');
  const ctrl = useController();
  const updateTodo = (data) => ctrl.fetch(TodoResource.update, id, data);

  return <TodoForm todo={todo} onSubmit={updateTodo} />
}

4) Or call directly in node

const todo = await TodoResource.get('5')
console.log(todo);

Why

There is a distinction between

  • What are networking API is
    • How to make a request, expected response fields, etc.
  • How it is used
    • Binding data, polling, triggering imperative fetch, etc.

Thus, there are many benefits to creating a distinct seperation of concerns between these two concepts.

With TypeScript Standard Endpoints, we define a standard for declaring in TypeScript the definition of a networking API.

  • Allows API authors to publish npm packages containing their API interfaces
  • Definitions can be consumed by any supporting library, allowing easy consumption across libraries like Vue, React, Angular
  • Writing codegen pipelines becomes much easier as the output is minimal
  • Product developers can use the definitions in a multitude of contexts where behaviors vary
  • Product developers can easily share code across platforms with distinct behaviors needs like React Native and React Web

What's in an Endpoint

  • A function that resolves the results
  • A function to uniquely store those results
  • Optional: information about how to store the data in a normalized cache
  • Optional: whether the request could have side effects - to prevent repeat calls

API

@rest-hooks/endpoint defines a standard interface

interface EndpointInterface {
    (params?: any, body?: any): Promise<any>;
    key(parmas?: any): string;
    schema?: Readonly<S>;
    sideEffects?: true;
    // other optionals like 'optimistic'
}

as well as a helper class to make construction easier.

class Endpoint<F extends () => Promise<any>> {
  constructor(fetchFunction: F, options: EndpointOptions);

  key(...args: Parameters<F>): string;

  readonly sideEffect?: true;

  readonly schema?: Schema;

  fetch: F;

  extend(options: EndpointOptions): Endpoint;
}

export interface EndpointOptions extends EndpointExtraOptions {
  key?: (params: any) => string;
  sideEffect?: true | undefined;
  schema?: Schema;
}

EndpointOptions

key: (params) => string

Serializes the parameters. This is used to build a lookup key in global stores.

Default:

`${this.fetch.name} ${JSON.stringify(params)}`

sideEffect: true | undefined

Disallows usage in hooks like useSuspense() since they might call fetch an unpredictable number of times. Use this for APIs with mutation side-effects like update, create, deletes.

Defaults to undefined meaning no side effects.

schema: Schema

Declarative definition of where Entities appear in the fetch response.

Not providing this option means no entities will be extracted.

import { Entity } from '@rest-hooks/normalizr';
import { Endpoint } from '@rest-hooks/endpoint';

class User extends Entity {
  readonly id: string = '';
  readonly username: string = '';

  pk() { return this.id;}
}

const UserDetail = new Endpoint(
    ({ id }) ⇒ fetch(`/users/${id}`),
    { schema: User }
);

Endpoint

extend(EndpointOptions): Endpoint

Can be used to further customize the endpoint definition

const UserDetail = new Endpoint(({ id }) ⇒ fetch(`/users/${id}`));


const UserDetailNormalized = UserDetail.extend({ schema: User });

Index

export interface IndexInterface<S extends typeof Entity> {
  key(parmas?: Readonly<IndexParams<S>>): string;
  readonly schema: S;
}
import { Entity } from '@rest-hooks/normalizr';
import { Index } from '@rest-hooks/endpoint';

class User extends Entity {
  readonly id: string = '';
  readonly username: string = '';

  pk() { return this.id;}
  static indexes = ['username'] as const;
}

const UserIndex = new Index(User)

const bob = useCache(UserIndex, { username: 'bob' });

// @ts-expect-error Indexes don't fetch, they just retrieve already existing data
const bob = useSuspense(UserIndex, { username: 'bob' });
4.0.5

9 months ago

4.0.4

9 months ago

4.0.7

9 months ago

4.0.6

9 months ago

4.0.1

10 months ago

4.0.0

10 months ago

4.0.3

9 months ago

4.0.2

10 months ago

4.0.8

9 months ago

3.8.5

10 months ago

3.8.0

1 year ago

3.8.4

11 months ago

3.8.3

1 year ago

3.8.2

1 year ago

3.8.1

1 year ago

3.7.4

1 year ago

3.7.3

1 year ago

3.7.2

1 year ago

3.8.0-next.0

1 year ago

3.7.1

1 year ago

3.7.0

1 year ago

3.6.0

1 year ago

3.5.2

1 year ago

3.5.1

1 year ago

3.5.0

1 year ago

3.4.0

1 year ago

3.4.1

1 year ago

3.2.6

1 year ago

3.2.5

1 year ago

3.2.4

1 year ago

3.2.8

1 year ago

3.2.7

1 year ago

3.3.0

1 year ago

3.2.2

1 year ago

3.2.1

1 year ago

3.2.0

2 years ago

3.0.1

2 years ago

3.2.3

1 year ago

3.1.0

2 years ago

3.0.0

2 years ago

3.0.0-beta.1

2 years ago

3.0.0-beta.0

2 years ago

3.0.0-beta.2

2 years ago

2.3.0

2 years ago

2.3.1

2 years ago

2.2.11

2 years ago

2.2.12

2 years ago

2.2.10

2 years ago

2.2.9

2 years ago

2.2.8

2 years ago

2.2.7

2 years ago

1.2.5

2 years ago

2.2.1

2 years ago

2.2.3

2 years ago

2.2.2

2 years ago

2.2.5

2 years ago

2.2.4

2 years ago

2.2.6

2 years ago

2.2.6-beta.0

2 years ago

2.2.0-beta.3

2 years ago

2.2.0-beta.4

2 years ago

2.2.0

2 years ago

2.2.0-beta.0

2 years ago

2.2.0-beta.1

2 years ago

2.2.0-beta.2

2 years ago

2.1.0

2 years ago

2.0.3

3 years ago

2.0.2

3 years ago

2.0.1

3 years ago

1.2.4

3 years ago

2.0.0

3 years ago

2.0.0-beta.2

3 years ago

2.0.0-beta.1

3 years ago

1.2.3

3 years ago

2.0.0-beta.0

3 years ago

1.2.2

3 years ago

1.2.1

3 years ago

1.2.0

3 years ago

1.1.6

3 years ago

1.1.5

3 years ago

1.1.4

3 years ago

1.1.1

3 years ago

1.1.0

3 years ago

1.1.3

3 years ago

1.1.2

3 years ago

1.0.13-beta.0

3 years ago

1.0.11

3 years ago

1.0.12

3 years ago

1.0.11-beta.0

3 years ago

1.0.10

3 years ago

1.0.9

3 years ago

1.0.8

3 years ago

1.0.7

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.4

3 years ago

1.0.3

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago

0.8.0

3 years ago

0.7.4

3 years ago

0.7.3

3 years ago

0.7.2

4 years ago

0.7.1

4 years ago

0.7.0

4 years ago

0.6.1

4 years ago

0.6.0

4 years ago

0.5.3

4 years ago

0.5.2

4 years ago

0.5.0

4 years ago

0.5.1

4 years ago

0.4.3

4 years ago

0.4.2

4 years ago

0.4.1

4 years ago

0.3.2

4 years ago

0.4.0

4 years ago

0.3.1

4 years ago

0.3.0

4 years ago

0.2.0

4 years ago