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

@geoleaf-plugins/realtime-layer

v1.0.8

Published

GeoLeaf Realtime Layer Plugin — Generic real-time layer updates via polling, WebSocket and SSE

Readme

@geoleaf-plugins/realtime-layer

Real-time updates for a GeoJSON layer of GeoLeaf JS — over HTTP polling, WebSocket or SSE, with pluggable decoding and eviction of stale features.

MIT licensed (LICENSE).


Installation

npm install @geoleaf-plugins/realtime-layer

Note — Prerequisite: @geoleaf/core must be loaded before this plugin.

For websocket sources only: @geoleaf-plugins/websocket must be loaded before this one. It is an optional dependency, declared as such in the plugin registry — polling and sse sources do not need it.


What the plugin does on its own

The plugin bootstraps from the profile. On geoleaf:app:ready it scans the layers and starts a stream for each one whose configuration carries data.realtime.enabled: true. In the common case there is nothing to call.

The public API exists for the two other cases: starting a layer declared enabled: false (opt-in), and stopping a stream.

{
    "id": "bus-positions",
    "data": {
        "realtime": {
            "enabled": true,
            "source": "polling",
            "url": "https://example.test/gtfs-rt/vehicles",
            "decoder": "gtfs-rt",
            "intervalMs": 15000,
            "idField": "vehicle_id",
            "updateMode": "upsert",
            "staleTimeoutMs": 90000,
            "staleAction": "remove"
        }
    }
}

API — GeoLeaf.RealtimeLayer

import "@geoleaf-plugins/realtime-layer";

// Opt-in: a layer declared `enabled: false` does not start at boot
GeoLeaf.RealtimeLayer.start("bus-positions");

// Current state of a layer
const status = GeoLeaf.RealtimeLayer.getStatus("bus-positions");

// Stop one stream, or all of them
GeoLeaf.RealtimeLayer.stop("bus-positions");
GeoLeaf.RealtimeLayer.stopAll();

| Member | Role | | ------------------------------------ | -------------------------------------------------------------------------- | | start(layerId) | Starts a layer's stream. Called automatically at boot when enabled: true | | stop(layerId) | Stops a layer's stream | | stopAll() | Stops every active stream | | getStatus(layerId) | Returns { active, source, lastUpdateAt, staleCount } | | registerDecoder(name, decoder) | Registers a decoder — before GeoLeaf.boot() | | registerStaleAction(name, handler) | Registers a stale-feature action — before GeoLeaf.boot() | | version | Plugin version |

Warning — active: true does not mean data is arriving. A polling source whose endpoint is unreachable stays active with a frozen lastUpdateAt. Read the two fields together.

registerDecoder and registerStaleAction must be called before GeoLeaf.boot(): the profile scan resolves decoder names at startup, and a name registered afterwards is never seen.


Configuration — a layer's data.realtime block

Validated at boot. A layer whose block is invalid is skipped with a message naming the layer; it does not bring down the rest of the profile.

| Key | Type | Default | Role | | ---------------- | ------------------------------------------- | ---------- | -------------------------------------------------------------------- | | enabled | boolean | — | Required. Automatic start at boot | | source | "polling" \| "websocket" \| "sse" | — | Required. Transport | | decoder | string | — | Required. Built in: "json", "gtfs-rt", plus registered ones | | updateMode | "upsert" \| "replace" \| "merge" | "upsert" | How decoded features are applied to the layer | | idField | string | — | GeoJSON property used as the key — required for upsert and merge | | staleTimeoutMs | number | — | Delay after which a feature that was not refreshed becomes stale | | staleAction | string | "remove" | Built in: "remove", "dim", plus registered ones | | url | string | — | Required for polling and sse | | intervalMs | number | 30000 | Polling period — polling only | | fallbackUrl | string | — | Snapshot served while url is failing — polling only | | channel | string | — | Required for websocket — passed to GeoLeaf.Ws.subscribe() | | mapping | { idField?, delayField?, targetLayerId? } | — | Hints for the GTFS-RT decoder |

fallbackUrl — what it does exactly

When url returns a non-2xx response or raises a network error, the plugin serves the fallbackUrl snapshot once for the duration of the outage, keeps polling url every intervalMs, and switches back on the first success. The snapshot is typically a static file served from the same origin as the profile.

mapping.targetLayerId — the layer that actually receives

A realtime block attached to one layer can feed another layer. Everything after decoding — writing the features and the whole staleness cycle — applies to that target, never to the layer carrying the configuration. Without targetLayerId, the two are the same.


Extending the plugin

Two registrable extension points — a decoder and a stale-feature action. The entry point re-exports the types that describe them: IDecoder and DecodedUpdate, StaleActionHandler, plus IRealtimeSource.

Note — IRealtimeSource is exported without a registration point. The three transports (polling, websocket, sse) are wired into the plugin factory; the type is there to implement one in a fork or a derived plugin, not to plug one in from a profile.

import "@geoleaf-plugins/realtime-layer";

// Before GeoLeaf.boot()
// decode() returns an ARRAY of updates, one per feature.
GeoLeaf.RealtimeLayer.registerDecoder("my-format", {
    decode(raw) {
        return [
            { id: "v-42", properties: { delay: 120 } },
            { id: "v-43", geometry: { type: "Point", coordinates: [2.35, 48.85] } },
            { id: "v-44", action: "delete" },
        ];
    },
});

// (layerId, featureId, feature) => void — called ONCE per feature per staleness event,
// not repeatedly while it stays stale.
GeoLeaf.RealtimeLayer.registerStaleAction("report", (layerId, featureId, feature) => {
    console.warn(`[realtime] ${layerId}: ${featureId} is stale`, feature);
});

Load order

  1. @geoleaf/core
  2. @geoleaf-plugins/websocket — only if a profile declares source: "websocket"
  3. @geoleaf-plugins/realtime-layer
  4. GeoLeaf.boot() — after every registerDecoder / registerStaleAction

© 2026 Mattieu Pottier — MIT