npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wcstack/fetch

v1.5.2

Published

Declarative fetch component for Web Components. Framework-agnostic async data fetching via wc-bindable-protocol.

Readme

@wcstack/fetch

@wcstack/fetch is a headless fetch component for the wcstack ecosystem.

It is not a visual UI widget. It is an I/O node that connects HTTP requests to reactive state.

With @wcstack/state, <wcs-fetch> can be bound directly through path contracts:

  • input / command surface: url, body, trigger
  • output state surface: value, loading, error, status

This means async communication can be expressed declaratively in HTML, without writing fetch(), async/await, or loading/error glue code in your UI layer.

@wcstack/fetch follows the HAWC architecture:

  • Core (FetchCore) handles HTTP, abort, and async state
  • Shell (<wcs-fetch>) connects that state to the DOM
  • frameworks and binding systems consume it through wc-bindable-protocol

Why this exists

In many frontend apps, the hardest part to migrate is not the template — it is the async logic: HTTP requests, loading flags, errors, retries, and lifecycle cleanup.

@wcstack/fetch moves that async logic into a reusable component and exposes the result as bindable state.

With @wcstack/state, the flow becomes:

  1. state computes url
  2. <wcs-fetch> executes the request
  3. async results return as value, loading, error, status
  4. UI binds to those paths with data-wcs

This turns async communication into state transitions, not imperative UI code.

Install

npm install @wcstack/fetch

Quick Start

1. Reactive fetch from state

When url changes, <wcs-fetch> automatically runs a new request. If another request is already in flight, it aborts the previous one.

<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
<script type="module" src="https://esm.run/@wcstack/fetch/auto"></script>

<wcs-state>
  <script type="module">
    export default {
      users: [],
      get usersUrl() {
        return "/api/users";
      },
    };
  </script>

  <wcs-fetch data-wcs="url: usersUrl; value: users"></wcs-fetch>

  <ul>
    <template data-wcs="for: users">
      <li data-wcs="textContent: users.*.name"></li>
    </template>
  </ul>
</wcs-state>

This is the default mode:

  • connect url
  • receive value
  • optionally bind loading, error, and status

2. Reactive URL example

A computed URL can drive data fetching automatically:

<wcs-state>
  <script type="module">
    export default {
      filterRole: "",
      users: [],

      get usersUrl() {
        const role = this.filterRole;
        return role ? "/api/users?role=" + role : "/api/users";
      },
    };
  </script>

  <select data-wcs="value: filterRole">
    <option value="">All</option>
    <option value="admin">Admin</option>
    <option value="staff">Staff</option>
  </select>

  <wcs-fetch
    data-wcs="url: usersUrl; value: users; loading: listLoading; error: listError">
  </wcs-fetch>

  <template data-wcs="if: listLoading">
    <p>Loading...</p>
  </template>
  <template data-wcs="if: listError">
    <p>Failed to load users.</p>
  </template>

  <ul>
    <template data-wcs="for: users">
      <li data-wcs="textContent: users.*.name"></li>
    </template>
  </ul>
</wcs-state>

3. Manual execution with trigger

Use manual when you want to prepare inputs first and execute later.

<wcs-state>
  <script type="module">
    export default {
      users: [],
      shouldRefresh: false,

      reload() {
        this.shouldRefresh = true;
      },
    };
  </script>

  <wcs-fetch
    url="/api/users"
    manual
    data-wcs="trigger: shouldRefresh; value: users; loading: listLoading">
  </wcs-fetch>

  <button data-wcs="onclick: reload">Refresh</button>
</wcs-state>

trigger is a one-way command surface:

  • writing true starts fetch()
  • it resets itself to false after completion
  • the reset emits wcs-fetch:trigger-changed
external write:  false → true   No event (triggers fetch)
auto-reset:      true  → false  Dispatches wcs-fetch:trigger-changed

4. POST with reactive body

<wcs-state>
  <script type="module">
    export default {
      newUser: {
        name: "",
        email: "",
      },
      submitRequest: false,
      submitResult: null,
      submitError: null,

      submit() {
        this.submitRequest = true;
      },
    };
  </script>

  <input data-wcs="value: newUser.name" placeholder="Name">
  <input data-wcs="value: newUser.email" placeholder="Email">

  <button data-wcs="onclick: submit">Create</button>

  <wcs-fetch
    url="/api/users"
    method="POST"
    manual
    data-wcs="
      body: newUser;
      trigger: submitRequest;
      value: submitResult;
      error: submitError;
      loading: submitLoading
    ">
    <wcs-fetch-header name="Content-Type" value="application/json"></wcs-fetch-header>
  </wcs-fetch>

  <template data-wcs="if: submitLoading">
    <p>Submitting...</p>
  </template>
  <template data-wcs="if: submitError">
    <p>Submit failed.</p>
  </template>
</wcs-state>

State Surface vs Command Surface

<wcs-fetch> exposes two different kinds of properties.

Output state (bindable async state)

These properties represent the result of the current request and are the main HAWC surface:

| Property | Type | Description | |----------|------|-------------| | value | any | Response data | | loading | boolean | true while a request is in flight | | error | WcsFetchHttpError \| Error \| null | HTTP or network error | | status | number | HTTP status code |

Input / command surface

These properties control request execution from HTML, JS, or @wcstack/state bindings:

| Property | Type | Description | |----------|------|-------------| | url | string | Request URL | | body | any | Request body (resets to null after fetch()) | | trigger | boolean | One-way execution trigger | | manual | boolean | Disables auto-fetch on connect / URL change |

Architecture

@wcstack/fetch follows the HAWC architecture.

Core: FetchCore

FetchCore is a pure EventTarget class. It contains:

  • HTTP execution
  • abort control
  • async state transitions
  • wc-bindable-protocol declaration

It can run headlessly in any runtime that supports EventTarget and fetch.

Shell: <wcs-fetch>

<wcs-fetch> is a thin HTMLElement wrapper around FetchCore. It adds:

  • attribute / property mapping
  • DOM lifecycle integration
  • declarative execution helpers such as trigger

This split keeps the async logic portable while allowing DOM-based binding systems such as @wcstack/state to interact with it naturally.

Target injection

The Core dispatches events directly on the Shell via target injection, so no event re-dispatch is needed.

Headless Usage (Core only)

FetchCore can be used standalone without the DOM. Since it declares static wcBindable, you can use @wc-bindable/core's bind() to subscribe to its state — the same way framework adapters work:

import { FetchCore } from "@wcstack/fetch";
import { bind } from "@wc-bindable/core";

const core = new FetchCore();

const unbind = bind(core, (name, value) => {
  console.log(`${name}:`, value);
});

await core.fetch("/api/users");

unbind();

This works in Node.js, Deno, Cloudflare Workers — anywhere EventTarget and fetch are available.

URL Observation

By default, <wcs-fetch> automatically executes a request when:

  1. it is connected to the DOM and url is set
  2. the url changes

If a request is already in flight when the URL changes, the previous request is automatically aborted before the new one starts.

Set the manual attribute to disable auto-fetch and control execution explicitly via fetch() or trigger.

Programmatic Usage

const fetchEl = document.querySelector("wcs-fetch");

// Set body via JS API (takes priority over <wcs-fetch-body>)
fetchEl.body = { name: "Tanaka" };
await fetchEl.fetch();
// Note: body is automatically reset to null after fetch().
// Set it again before each call if needed.

console.log(fetchEl.value);   // response data
console.log(fetchEl.status);  // HTTP status code
console.log(fetchEl.loading); // boolean
console.log(fetchEl.error);   // error info or null
console.log(fetchEl.body);    // null (reset after fetch)

HTML Replace Mode

<wcs-fetch> can also replace a target element's innerHTML when target is set.

<div id="content">Initial content</div>
<wcs-fetch url="/api/partial" target="content"></wcs-fetch>

This mode is useful for simple fragment loading, but it is separate from the main state-driven usage with @wcstack/state.

Optional DOM Triggering

If autoTrigger is enabled (default), clicking an element with data-fetchtarget triggers the corresponding <wcs-fetch> element:

<button data-fetchtarget="user-fetch">Load Users</button>
<wcs-fetch id="user-fetch" url="/api/users"></wcs-fetch>

Event delegation is used — works with dynamically added elements. The closest() API handles nested elements (e.g., icon inside a button).

If the target id does not match any element, or the matched element is not a <wcs-fetch>, the click is silently ignored.

This is a convenience feature. In wcstack applications, state-driven triggering via trigger is usually the primary pattern.

Elements

<wcs-fetch>

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | url | string | — | Request URL | | method | string | GET | HTTP method | | target | string | — | DOM element id for HTML replace mode | | manual | boolean | false | Disable auto-fetch |

| Property | Type | Description | |----------|------|-------------| | value | any | Response data | | loading | boolean | true while request is in flight | | error | WcsFetchHttpError \| Error \| null | Error info | | status | number | HTTP status code | | body | any | Request body (resets to null after fetch()) | | trigger | boolean | Set to true to execute fetch | | manual | boolean | Explicit execution mode |

| Method | Description | |--------|-------------| | fetch() | Execute the HTTP request | | abort() | Cancel the in-flight request |

<wcs-fetch-header>

Defines a request header. Place it as a child of <wcs-fetch>.

| Attribute | Type | Description | |-----------|------|-------------| | name | string | Header name | | value | string | Header value |

<wcs-fetch-body>

Defines the request body. Place it as a child of <wcs-fetch>.

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | type | string | application/json | Content-Type |

The body content is taken from the element's text content.

Example:

<wcs-fetch url="/api/users" method="POST">
  <wcs-fetch-header name="Authorization" value="Bearer token123"></wcs-fetch-header>
  <wcs-fetch-header name="Accept" value="application/json"></wcs-fetch-header>
  <wcs-fetch-body type="application/json">
    {"name": "Tanaka", "email": "[email protected]"}
  </wcs-fetch-body>
</wcs-fetch>

wc-bindable-protocol

Both FetchCore and <wcs-fetch> declare wc-bindable-protocol compliance, making them interoperable with any framework or component that supports the protocol.

Core (FetchCore)

FetchCore declares the bindable async state that any runtime can subscribe to:

static wcBindable = {
  protocol: "wc-bindable",
  version: 1,
  properties: [
    { name: "value",   event: "wcs-fetch:response",
      getter: (e) => e.detail.value },
    { name: "loading", event: "wcs-fetch:loading-changed" },
    { name: "error",   event: "wcs-fetch:error" },
    { name: "status",  event: "wcs-fetch:response",
      getter: (e) => e.detail.status },
  ],
};

Headless consumers call core.fetch(url) directly — no trigger needed.

Shell (<wcs-fetch>)

The Shell extends the Core declaration with trigger so binding systems can execute fetch declaratively:

static wcBindable = {
  ...FetchCore.wcBindable,
  properties: [
    ...FetchCore.wcBindable.properties,
    { name: "trigger", event: "wcs-fetch:trigger-changed" },
  ],
};

TypeScript Types

import type {
  WcsFetchHttpError, WcsFetchCoreValues, WcsFetchValues
} from "@wcstack/fetch";
// HTTP error (status >= 400)
interface WcsFetchHttpError {
  status: number;
  statusText: string;
  body: string;
}

// Core (headless) — 4 async state properties
// T defaults to unknown; pass a type argument for typed `value`
interface WcsFetchCoreValues<T = unknown> {
  value: T;
  loading: boolean;
  error: WcsFetchHttpError | Error | null;
  status: number;
}

// Shell (<wcs-fetch>) — extends Core with trigger
interface WcsFetchValues<T = unknown> extends WcsFetchCoreValues<T> {
  trigger: boolean;
}

Why this works well with @wcstack/state

@wcstack/state uses path strings as the only contract between UI and state. <wcs-fetch> fits this model naturally:

  • state computes url
  • <wcs-fetch> executes the request
  • async results return as value, loading, error, status
  • UI binds to those paths without writing fetch glue code

This makes async processing look like ordinary state updates.

Framework Integration

Since <wcs-fetch> is HAWC + wc-bindable-protocol, it works with any framework through thin adapters from @wc-bindable/*.

React

import { useWcBindable } from "@wc-bindable/react";
import type { WcsFetchValues } from "@wcstack/fetch";

interface User { id: number; name: string; }

function UserList() {
  const [ref, { value: users, loading, error }] =
    useWcBindable<HTMLElement, WcsFetchValues<User[]>>();

  return (
    <>
      <wcs-fetch ref={ref} url="/api/users" />
      {loading && <p>Loading...</p>}
      {error && <p>Error</p>}
      <ul>
        {users?.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
}

Vue

<script setup lang="ts">
import { useWcBindable } from "@wc-bindable/vue";
import type { WcsFetchValues } from "@wcstack/fetch";

interface User { id: number; name: string; }

const { ref, values } = useWcBindable<HTMLElement, WcsFetchValues<User[]>>();
</script>

<template>
  <wcs-fetch :ref="ref" url="/api/users" />
  <p v-if="values.loading">Loading...</p>
  <p v-else-if="values.error">Error</p>
  <ul v-else>
    <li v-for="user in values.value" :key="user.id">{{ user.name }}</li>
  </ul>
</template>

Svelte

<script>
import { wcBindable } from "@wc-bindable/svelte";

let users = $state(null);
let loading = $state(false);
</script>

<wcs-fetch url="/api/users"
  use:wcBindable={{ onUpdate: (name, v) => {
    if (name === "value") users = v;
    if (name === "loading") loading = v;
  }}} />

{#if loading}
  <p>Loading...</p>
{:else if users}
  <ul>
    {#each users as user (user.id)}
      <li>{user.name}</li>
    {/each}
  </ul>
{/if}

Solid

import { createWcBindable } from "@wc-bindable/solid";
import type { WcsFetchValues } from "@wcstack/fetch";

interface User { id: number; name: string; }

function UserList() {
  const [values, directive] = createWcBindable<WcsFetchValues<User[]>>();

  return (
    <>
      <wcs-fetch ref={directive} url="/api/users" />
      <Show when={!values.loading} fallback={<p>Loading...</p>}>
        <ul>
          <For each={values.value}>{(user) => <li>{user.name}</li>}</For>
        </ul>
      </Show>
    </>
  );
}

Vanilla — bind() directly

import { bind } from "@wc-bindable/core";

const fetchEl = document.querySelector("wcs-fetch");

bind(fetchEl, (name, value) => {
  console.log(`${name} changed:`, value);
});

Configuration

import { bootstrapFetch } from "@wcstack/fetch";

bootstrapFetch({
  autoTrigger: true,
  triggerAttribute: "data-fetchtarget",
  tagNames: {
    fetch: "wcs-fetch",
    fetchHeader: "wcs-fetch-header",
    fetchBody: "wcs-fetch-body",
  },
});

Design Notes

  • value, loading, error, and status are output state
  • url, body, and trigger are input / command surface
  • trigger is intentionally one-way: writing true executes, reset emits completion
  • body is reset to null after each fetch() call — set it again before each submission
  • manual is useful when execution timing should be controlled explicitly
  • HTML replace mode is optional; the primary wcstack pattern is state-driven binding

License

MIT