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

@geektr/vue-fetch-event-source

v0.1.0

Published

useEventSource from @vueuse/core, powered by @microsoft/fetch-event-source (POST / custom headers / custom fetch)

Readme

@geektr/vue-fetch-event-source

Vue composable for Server-Sent Events with a useEventSource-compatible API, powered by @microsoft/fetch-event-source.

Enables POST requests, custom headers, and custom fetch implementations — features not available with the native EventSource API.

Install

pnpm add @geektr/vue-fetch-event-source

Usage

import { useFetchEventSource } from '@geektr/vue-fetch-event-source'

const { data, event, status, error, close, open } = useFetchEventSource(
  '/api/sse',
  ['chat', 'notification'], // named events to listen for
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query: 'hello' }),
  },
)

With custom fetch

const { data, status } = useFetchEventSource('/api/sse', [], {
  fetch: myCustomFetch,
  credentials: 'include',
})

Manual connect

const { data, open } = useFetchEventSource('/api/sse', [], {
  immediate: false,
})

// Connect later
open()

onEvent — streaming without lost events

The data ref holds the latest value. If a single SSE chunk contains multiple events, intermediate values are silently overwritten. For streaming consumers, use the onEvent callback — it fires synchronously for every event, in order:

const logs = ref<string[]>([])

useFetchEventSource('/api/logs', [], {
  onEvent(msg) {
    // msg = { event: string | null, data: T, id: string | null }
    logs.value.push(msg.data)
  },
})

onEvent receives the same deserialized data as the data ref (serializer is not called twice). Named events are only delivered if they match the events whitelist, same as data. Errors thrown inside onEvent are caught and logged — they do not break the connection.

Dynamic request parameters

body and headers accept MaybeRefOrGetter, so they can be reactive refs or getters. Values are resolved with toValue() each time a connection (or reconnection) is established:

const token = ref('initial-token')

const { open } = useFetchEventSource('/api/sse', [], {
  method: 'POST',
  headers: () => ({ Authorization: `Bearer ${token.value}` }),
  body: () => JSON.stringify({ timestamp: Date.now() }),
})

You can also pass one-time overrides to open() — useful for form submissions with immediate: false:

const { open } = useFetchEventSource('/api/sse', [], {
  method: 'POST',
  immediate: false,
})

// Submit form
open({
  body: JSON.stringify(formState),
  headers: { 'X-Idempotency-Key': uuid() },
})

Overrides persist across automatic reconnects until the next open() call.

Reconnect & non-idempotent requests

autoReconnect default depends on the HTTP method:

| Method | Default autoReconnect | |---|---| | GET, HEAD | true (infinite, 1s delay) | | POST, PUT, PATCH, DELETE | false |

This prevents accidental duplicate side effects (e.g. re-triggering a certificate issuance). To opt in:

useFetchEventSource('/api/trigger', [], {
  method: 'POST',
  autoReconnect: { retries: 3, delay: 2000 },
})

A dev-mode warning is emitted if autoReconnect: true is set on non-idempotent methods.

Error handling — SseResponseError

Non-2xx responses and wrong content-types throw SseResponseError, which is exported:

import { useFetchEventSource, SseResponseError } from '@geektr/vue-fetch-event-source'

const { error } = useFetchEventSource('/api/sse')

watchEffect(() => {
  if (error.value instanceof SseResponseError) {
    console.log(error.value.status)  // 404
    console.log(error.value.body)    // parsed JSON or raw text
    console.log(error.value.message) // "not found" (from body.error / body.message / "HTTP {status}")
  }
})
  • 4xx errors (400–499) are never retried, even with autoReconnect enabled — the throw terminates the connection immediately.
  • 5xx errors follow the normal autoReconnect strategy.
  • The default onopen reads the response body as text, attempting JSON.parse for structured error payloads.

Differences from @vueuse/core useEventSource

| Feature | useEventSource | useFetchEventSource | |---|---|---| | Underlying API | EventSource | fetch | | POST / custom headers | No | Yes | | Custom fetch | No | Yes (fetch option) | | Connection instance | eventSource: ShallowRef<EventSource> | controller: ShallowRef<AbortController> | | Error type | ShallowRef<Event> | ShallowRef<unknown> (SseResponseError for HTTP errors) | | autoReconnect default | false | true for GET, false for non-GET | | openWhenHidden default | N/A | true (keeps connection alive when tab hidden) | | withCredentials | Supported | Use credentials: 'include' (standard RequestInit) | | Streaming events | data ref only (may lose events) | onEvent callback (no loss) + data ref | | Reactive params | N/A | body/headers accept MaybeRefOrGetter |

Options

All options from UseEventSourceOptions (except autoReconnect and withCredentials) and FetchEventSourceInit (except signal, onmessage, onclose, onerror, body, headers) are merged into a flat object.

autoReconnect

// Default: true for GET/HEAD (infinite retries, 1s delay), false for other methods
autoReconnect?: boolean | {
  retries?: number | (() => boolean) // default: -1 (infinite)
  delay?: number                      // default: 1000
  onFailed?: () => void
}

body / headers

Accept MaybeRefOrGetter. Resolved with toValue() on each connection/reconnection.

onEvent

onEvent?: (msg: SseMessage<Data>) => void

interface SseMessage<Data = unknown> {
  event: string | null   // null for unnamed / "message" events
  data: Data
  id: string | null
}

open(overrides?)

open: (overrides?: { body?: BodyInit | null; headers?: Record<string, string> }) => void

openWhenHidden

Default: true. Set to false to abort connection when the page becomes hidden (like EventSource default behavior in some browsers).

Return

Same as useEventSource except:

  • controller (ShallowRef<AbortController | null>) replaces eventSource
  • error is ShallowRef<unknown> instead of ShallowRef<Event>
  • open accepts optional { body?, headers? } overrides

License

MIT