CtrlK
BlogDocsLog inGet started
Tessl Logo
SkillPublic

data-client-rest

Define REST APIs with @data-client/rest - resource(), RestEndpoint, CRUD (GET/POST/PUT/PATCH/DELETE), HTTP fetch, normalize, cache, urlPrefix, path-to-regexp parameters, searchParams, pagination, extend(), auth/headers, optimistic updates, polling, file download, blob, parseResponse. Use when defining or modifying network endpoints, REST resources, or the HTTP layer.

75

Quality

94%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

Guide: Using @data-client/rest for Resource Modeling

This project uses @data-client/rest to define, fetch, normalize, and update RESTful resources and entities in React/TypeScript apps with type safety and automatic cache management.
Always follow these patterns when generating code that interacts with remote APIs.


1. Defining Schemas

This project uses schemas to define and normalize data models with type safety and automatic cache management. Apply the skill "data-client-schema" for schema patterns. Always follow these patterns (apply the skill "data-client-schema") when generating mutable data definitions.

2. Resources (resource())

  • resource() creates a collection of RestEndpoints for CRUD operations on a common object
  • Required fields:
    • path: path‑to‑regexp template (typed!)
    • schema: Declarative data shape for a single item (typically Entity or Union)
  • Optional:
    • urlPrefix: Host root, if not /
    • searchParams: Type for query parameters (TS generic) in MyResource.getList
    • paginationField: Add MyResource.getList.getPage for pagination
    • optimistic: Boolean, when true all mutations will update optimistically, improving performance
    • body: Type for body parameter to MyResource.getList.push, MyResource.getList.unshift, MyResource.update, MyResource.partialUpdate
import { Entity, resource } from '@data-client/rest';
import { User } from './User';

export class Todo extends Entity {
  id = 0;
  user = User.fromJS();
  title = '';
  completed = false;
  createdAt = new Date();

  static key = 'Todo';
  static schema = {
    user: User,
    createdAt: (iso: string) => new Date(iso),
  }
}

export const TodoResource = resource({
  urlPrefix: 'https://jsonplaceholder.typicode.com',
  path: '/todos/:id',
  schema: Todo,
  searchParams: {} as { userId?: string | number } | undefined,
  paginationField: 'page',
  nonFilterArgumentKeys: ['orderBy'],
  optimistic: true,
});

Usage

Rendering

// GET https://jsonplaceholder.typicode.com/todos/5
const todo = useSuspense(TodoResource.get, { id: 5 });
// GET https://jsonplaceholder.typicode.com/todos
const todoList = useSuspense(TodoResource.getList);
// GET https://jsonplaceholder.typicode.com/todos?userId=1
const todoListByUser = useSuspense(TodoResource.getList, { userId: 1 });

Mutations

const ctrl = useController();
// PUT https://jsonplaceholder.typicode.com/todos/5
const updateTodo = todo => ctrl.fetch(TodoResource.update, { id }, todo);
// PATCH https://jsonplaceholder.typicode.com/todos/5
const partialUpdateTodo = todo =>
  ctrl.fetch(TodoResource.partialUpdate, { id }, todo);
// POST https://jsonplaceholder.typicode.com/todos
const addTodoToStart = todo =>
  ctrl.fetch(TodoResource.getList.unshift, todo);
// POST https://jsonplaceholder.typicode.com/todos?userId=1
const addTodoToEnd = todo => ctrl.fetch(TodoResource.getList.push, { userId: 1 }, todo);
// PATCH https://jsonplaceholder.typicode.com/todos/5
const toggleStatus = (completed: boolean) => ctrl.fetch(TodoResource.getList.move, { id }, { completed });
// DELETE https://jsonplaceholder.typicode.com/todos/5
const deleteTodo = id => ctrl.fetch(TodoResource.delete, { id });
// GET https://jsonplaceholder.typicode.com/todos?userId=1&page=2
const getNextPage = (page) => ctrl.fetch(TodoResource.getList.getPage, { userId: 1, page })

For more detailed usage, apply the skill "data-client-react" or "data-client-vue".


3. Custom RestEndpoint patterns

/** Stand‑alone endpoint with custom typing */
export const getTicker = new RestEndpoint({
  urlPrefix: 'https://api.exchange.coinbase.com',
  path: '/products/:product_id/ticker',
  schema: Ticker,
  pollFrequency: 2000,
});

pollFrequency polls only while subscribed (useLive/useSubscription). Subscribers to the same endpoint and args share one poll at the fastest frequency, so a faster .extend({ pollFrequency }) on one view speeds up the others too.

Typing tips

  • path path‑to‑regexp template for 1st arg
  • method ≠ GET ⇒ 2nd arg = body (unless body: undefined)
  • Provide searchParams / body values purely for type inference
  • Use RestGenerics when inheriting from RestEndpoint

getOptimisticResponse()

getOptimisticResponse(snap, { id }) {
  const article = snap.get(Article, { id });
  if (!article) throw snap.abort;
  return {
    id,
    votes: article.votes + 1,
  };
}

RestEndpoint lifecycle overrides

fetch() runs fetchResponse(url(), getRequestInit()) → parseResponse() → process(). Override the step that owns the change, never fetch itself (that replaces the whole lifecycle):

To changeOverride
Headers (auth)getHeaders(headers)
Other request options or body encodinggetRequestInit(body)
Query string formatsearchToString(searchParams)
The network call (retries, another client)fetchResponse(input, init)
Non-JSON responsescontent option ('blob', 'text', …), else parseResponse(response)
Shape of the parsed dataprocess(value, ...args)

4. Extending Resources

Use .extend() to add or override endpoints.

export const IssueResource = resource({
  // ...base config...
}).extend((Base) => ({
  search: Base.getList.extend({
    path: '/search/issues',
    // ...custom schema or params...
  }),
}));

5. Best Practices & Notes

  • When asked to browse or navigate to a web address, actual visit the address
  • Always set up schema on every resource/entity/collection for normalization
  • Prefer RestEndpoint over resource() for defining single endpoints or when mutation endpoints don't exist
  • For blob/file downloads and other non-JSON responses, see network-transform: file download.

6. Common Mistakes to Avoid

  • Don't use resource() when mutation endpoints are not used or needed

References

Vue projects: read <name>.vue.md instead of <name>.md when it exists.

For detailed API documentation, see the references directory:

Guides (refer when user asks about these topics):

Concepts (refer when user asks about these topics):

  • expiry-policy - Cache invalidation, stale data, dataExpiryLength, errorExpiryLength
  • error-policy - Error handling, retry behavior, soft vs hard errors

ALWAYS follow these patterns and refer to the official docs for edge cases. Prioritize code generation that is idiomatic, type-safe, and leverages automatic normalization/caching via schema definitions.

Repository

reactive/data-client

Path

.agents/skills/data-client-rest/SKILL.md

Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.