gorcure

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 13 Imported by: 0

README

Gorcure

Gorcure

Real-time superpowers for Goravel — powered by Mercure & SSE.

Laravel Broadcasting + Echo vibes for Go: publish updates to a Mercure hub in one line.

version go version goravel mercure license

Install · Config · Usage · Running a hub · Roadmap


Gorcure brings real-time capabilities to Goravel through Mercure and Server-Sent Events (SSE) — a Laravel Broadcasting + Echo–like experience. Gorcure is the publisher: your Goravel app pushes updates to a Mercure hub over HTTP, and the hub streams them to subscribed browsers via SSE.

// one line, real-time
facades.Gorcure().Publish("/users/1", map[string]any{"online": true})

Features

  • Real-time updates over SSE through any Mercure hub (Docker / Caddy / Mercure.rocks)
  • HTTP publisher with automatic publisher-JWT minting (HS256)
  • Public and private updates (private flag, multi-topic, custom SSE id/type/retry)
  • First-class Goravel integration: Service Provider, facade, one-command install
  • Mock client for testing

Requirements

  • Go 1.26+
  • Goravel v1.17+
  • A running Mercure hub (see Running a hub)

Installation

One command does everything:

./artisan package:install github.com/eddyjj92/gorcure

It runs go get, then auto-wires the package — no manual edits:

  • registers &gorcure.ServiceProvider{} in your providers list (config/app.go or bootstrap/providers.go, whichever layout you use)
  • creates config/gorcure.go
  • runs go mod tidy

Remove it just as cleanly:

./artisan package:uninstall github.com/eddyjj92/gorcure
Manual installation (no artisan)
go get github.com/eddyjj92/gorcure

Register the provider in config/app.go:

import "github.com/eddyjj92/gorcure"

func Providers() []foundation.ServiceProvider {
    return []foundation.ServiceProvider{
        // ...
        &gorcure.ServiceProvider{},
    }
}

Then create config/gorcure.go with the Configuration keys below.

Configuration

Add to your .env:

# Public hub URL consumed by the frontend (SSE EventSource)
GORCURE_HUB_URL=http://localhost:3000/.well-known/mercure

# URL the server publishes to (server -> hub). Defaults to GORCURE_HUB_URL.
GORCURE_HUB_PUBLISH_URL=http://localhost:3000/.well-known/mercure

# JWT keys (HS256 by default). Raw shared key OR a pre-signed JWT.
GORCURE_JWT_ALG=HS256
GORCURE_PUBLISHER_JWT=!ChangeMe!
GORCURE_SUBSCRIBER_JWT=!ChangeMe!

# Skip TLS verification when publishing (dev hubs, self-signed certs)
GORCURE_PUBLISH_INSECURE=false
Key Env Default Notes
hub_url GORCURE_HUB_URL http://localhost:3000/.well-known/mercure frontend SSE URL
publish_url GORCURE_HUB_PUBLISH_URL falls back to hub_url server publish URL
jwt.alg GORCURE_JWT_ALG HS256 signing algorithm
jwt.publisher GORCURE_PUBLISHER_JWT !ChangeMe! shared key or pre-signed JWT
jwt.subscriber GORCURE_SUBSCRIBER_JWT !ChangeMe! shared key (reserved for private channels)
insecure GORCURE_PUBLISH_INSECURE false skip TLS verify on publish

The publisher token is minted automatically from the shared key (granting publish on all topics). If GORCURE_PUBLISHER_JWT is already a signed JWT (x.y.z), it is used as-is. The shared key must match the hub's publisher JWT key.

Usage

import (
    "github.com/eddyjj92/gorcure/contracts"
    "github.com/eddyjj92/gorcure/facades"
)

// Single topic — non-string data is JSON-encoded automatically
id, err := facades.Gorcure().Publish("/users/1", map[string]any{"online": true})

// Full control: multiple topics, private, SSE id/type/retry
id, err := facades.Gorcure().PublishUpdate(contracts.Update{
    Topics:  []string{"/users/1", "/admins"},
    Data:    `{"online":true}`,
    Private: true,
})

Publish / PublishUpdate return the hub-assigned event id.

Subscribing (frontend)

const url = new URL("http://localhost:3000/.well-known/mercure")
url.searchParams.append("topic", "/users/1")

const es = new EventSource(url)
es.onmessage = (e) => console.log(JSON.parse(e.data))

Running a hub

Gorcure publishes to a Mercure hub; you run the hub separately. The quickest dev setup is the official Docker image:

docker run -d --name mercure -p 3000:80 \
  -e MERCURE_PUBLISHER_JWT_KEY='!ChangeMe!' \
  -e MERCURE_SUBSCRIBER_JWT_KEY='!ChangeMe!' \
  -e 'MERCURE_EXTRA_DIRECTIVES=cors_origins "http://localhost:3000"' \
  dunglas/mercure

The hub is then at http://localhost:3000/.well-known/mercure. Keep the JWT keys in sync with GORCURE_PUBLISHER_JWT / GORCURE_SUBSCRIBER_JWT. For production see the Mercure docs.

The Mercure hub itself is licensed AGPL-3.0; running the official image as a standalone service keeps it separate from your application. Gorcure (this package) never links the hub — it only talks to it over HTTP — so Gorcure stays MIT.

Testing

A mock implementing contracts.Gorcure is provided. It records every update so you can assert on what your code published:

import "github.com/eddyjj92/gorcure/mocks"

m := mocks.New() // returns a *mocks.Gorcure (ID defaults to "mock-event-id")

id, err := m.Publish("/users/1", map[string]any{"online": true})

m.Count()        // -> 1
last, _ := m.Last() // -> the recorded contracts.Update

// Force an error path:
m.Err = errors.New("hub down")
_, err = m.Publish("/users/1", "x") // err == "hub down", nothing recorded

Public API

type Gorcure interface {
    Publish(topic string, data any) (string, error)
    PublishUpdate(update Update) (string, error)
}

type Update struct {
    Topics  []string // at least one required
    Data    string   // serialized payload (usually JSON)
    Private bool      // only authorized subscribers receive it
    ID      string    // optional SSE event id
    Type    string    // optional SSE event type
    Retry   int       // optional reconnection time (ms)
}

Status

Version Scope State
v0.1.0 Basic publisher (external hub)
v0.2.0 Broadcastable events planned
v0.3.0 Private channels + subscriber tokens planned
v0.4.0 Broadcasting driver planned
v0.5.0 Inertia integration planned

License

Gorcure is released under the MIT License.

SPDX-License-Identifier: MIT
Copyright (c) 2026 eddyjj92

Gorcure talks to a Mercure hub over HTTP and does not link any hub code, so this package is MIT. The Mercure hub you run separately is AGPL-3.0 — running the official Docker image as a standalone service keeps that boundary clean.

Documentation

Index

Constants

View Source
const Binding = "gorcure"

Variables

App holds the application instance so facades can resolve bindings.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client publishes updates to a Mercure hub over HTTP.

func NewClient

func NewClient(cfg Config) (*Client, error)

NewClient builds a publisher from the resolved configuration. The publisher JWT is minted once (granting publish on all topics) and reused for every request.

func (*Client) Publish

func (c *Client) Publish(topic string, data any) (string, error)

Publish sends data to a single topic. Non-string data is JSON-encoded.

func (*Client) PublishUpdate

func (c *Client) PublishUpdate(update contracts.Update) (string, error)

PublishUpdate posts a fully described update and returns the hub-assigned event id.

type Config

type Config struct {
	// HubURL is the public hub URL the frontend connects to for SSE.
	HubURL string
	// PublishURL is the URL the server publishes updates to.
	PublishURL string
	JWTAlg     string
	// PublisherJWT is the shared key (or a pre-signed JWT) used to publish.
	PublisherJWT string
	// SubscriberJWT is the shared key used to mint subscriber tokens (private channels).
	SubscriberJWT string
	// Insecure skips TLS verification when publishing (dev hubs with self-signed certs).
	Insecure bool
}

Config holds the resolved Gorcure settings read from the "gorcure" config store.

func LoadConfig

func LoadConfig(cfg config.Config) Config

LoadConfig resolves the Gorcure configuration from Goravel's config store.

type ServiceProvider

type ServiceProvider struct{}

ServiceProvider registers Gorcure with a Goravel application.

func (*ServiceProvider) Boot

func (r *ServiceProvider) Boot(app foundation.Application)

Boot runs after all providers are registered. Installation (provider registration + config file) is automated by `artisan package:install` via setup/setup.go, so there is nothing to wire here.

func (*ServiceProvider) Register

func (r *ServiceProvider) Register(app foundation.Application)

Register binds the publisher into the container.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL