Farfetched Data Fetching Library

repository·main·Indexed 42 days ago

https://github.com/effector/farfetched

A declarative, framework-agnostic, and transport-agnostic data fetching library with first-class TypeScript support. It allows developers to express computation logic without describing control flow and integrates with various frameworks (React, Solid, Vue, Svelte, Angular) and protocols (REST, GraphQL). Includes support for schema validation contracts via integrations with io-ts, JSON Schema, Runtypes, Superstruct, typed-contracts, and Valibot.

Tokens
61.7K
Snippets
200
Records
274
Agent score
53%

What's inside Farfetched

  1. Overview of Farfetched

    main

    Farfetched is an advanced data fetching tool for web applications designed to be both transport-agnostic and framework-agnostic. It allows you to express the logic of a computation declaratively without describing its control flow.

    Key characteristics include:

    • Protocol Agnostic: Works with REST, GraphQL, Promises, or any other backend.
    • Framework Agnostic: Integrates with React, Solid, Vue, Svelte, Angular, etc.
    • TypeScript First: Provides first-class TypeScript support out of the box.
    • Declarative: Focuses on what to fetch rather than how to manage the control flow.
  2. Introduction to Farfetched

    main
    Farfetched is a data-fetching library for frontend applications designed to simplify fetching, caching, synchronizing, and updating server state. It reduces boilerplate by providing built-in mechanisms for response validation (via contracts), automatic retries on network errors, and state synchronization.
  3. Farfetched Release Roadmap and Versioning Strategy

    main

    Farfetched is currently in a feature freeze phase (since v0.12) to focus on stability and performance. The project aims to reach version 1.0 by the end of 2024, with new versions released every 2-3 months.

    Upcoming Breaking Changes (v0.14)

    Be prepared for the following removals in version 0.14:

    • Removal of attachOperation.
    • Removal of the concurrency field in createJsonQuery and createJsonMutation.

    Planned Changes (v0.x - Pre-1.0)

    Before the 1.0 release, the following improvements are planned:

    • Standardized behavior specification for operators applied to single operations.
    • Merging of contract and validate fields across all built-in factories.
    • Developer Experience (DX) improvements for the update operator.

    Future Features (v1.x - Post-1.0)

    Post-1.0 development will focus on:

    • API schema-based code generation.
    • Native support for GraphQL and REST APIs.
    • Support for Server-Sent Events (SSE) and Web Sockets.
  4. What is a Query in Farfetched

    main

    A Query is the fundamental primitive for working with remote data in Farfetched. It encapsulates an asynchronous operation and manages its own state.

    A Query consists of:

    1. A handler: An asynchronous function that fetches data from a remote source.
    2. Internal storage: It stores the received data, any errors encountered, and the current execution status.
    3. Parameters: It can be started with specific parameters passed to the handler.

    Because Farfetched is built on reactive principles, a Query exposes its data and status via Stores.

  5. What is a Contract and how does it work

    main

    In Farfetched, a Contract is a simple object used to validate remote data before it is used in your application. This follows the principle that frontend applications should never trust remote data blindly.

    A Contract must implement two properties:

    • isData: A function that takes any data and returns true if the data is valid, or false otherwise. If using TypeScript, this function should be a type predicate to allow for type narrowing.
    • getErrorMessages: A function that takes the invalid data and returns an array of strings describing why the validation failed. This is only called if isData returns false.

    By attaching a Contract to a factory (like createQuery), Farfetched will automatically validate the response and treat it as a success or failure based on the contract's result.

    import { type Contract } from '@farfetched/core';
    
    const numberContract: Contract<unknown, number> = {
      isData: (data): data is number => typeof data === 'number',
      getErrorMessages: (data) => {
        return [`Expected number, got ${typeof data}`];
      },
    };
  6. What is a Mutation and how does it work

    main

    A Mutation is an abstraction used to send data to a remote source (e.g., performing a POST or PUT request). Unlike a Query, which is used for retrieving data, a Mutation is designed for operations that change state on the remote server.

    Key characteristics of a Mutation:

    1. It uses a handler to perform the operation on the remote source.
    2. It does not store data internally; it only provides lifecycle Events to communicate progress and results.
    3. It can be started with specific parameters via the .start() method.
  7. What is a Barrier and how to use it

    main

    A Barrier is an object used to postpone the execution of a Query or Mutation. It acts as a gatekeeper that prevents asynchronous operations from starting until certain conditions are met.

    To use a Barrier, you must:

    1. Create it using the createBarrier factory.
    2. Apply it to a Query or Mutation using the applyBarrier operator.

    When the Barrier is in an active state, any Query or Mutation it is applied to will be held in a pending state until the Barrier is deactivated.

  8. Use Performers in a Barrier

    main

    The perform field in createBarrier accepts an array of Performers. A Performer is any of the following:

    1. A Query that accepts no parameters.
    2. A Mutation that accepts no parameters.
    3. An Effector Effect that accepts no parameters.
    4. An object { start, end } where start and end are Effector Events that accept no parameters. The start event is called when the Barrier is activated, and the end event is expected to be called when the performer finishes.
  9. Filter retries based on error type or external conditions

    main

    The filter option in the retry operator allows you to control when a retry should occur. You can use it to:

    1. Retry only specific errors: Use error guards like isHttpErrorCode(code) from @farfetched/core.
    2. Retry based on external state: Use a Sourced value (like an Effector Store or a boolean) to enable/disable retries based on application state (e.g., if a specific route is open).
    3. Combine conditions: Use a function to check both the error type and the external state.

    In Farfetched, a Sourced field can be a static value, a Store, a function, or a combination of these.

    import { isHttpErrorCode } from '@farfetched/core';
    
    // 1. Retry only 500 errors
    retry(characterQuery, {
      times: 5,
      delay: 500,
      filter: isHttpErrorCode(500),
    });
    
    // 2. Retry only if a specific route is open (using Atomic Router example)
    retry(characterQuery, {
      times: 5,
      delay: 500,
      filter: characterRoute.$isOpened,
    });
    
    // 3. Combine: Retry only 500 errors AND if the route is open
    retry(characterQuery, {
      times: 5,
      delay: 500,
      filter: {
        source: characterRoute.$isOpened,
        fn: ({ error, params }, isCharacterRouteOpened) => 
          isCharacterRouteOpened && isHttpErrorCode(500)({ error }),
      },
    });