dsx

package module
v0.15.0 Latest Latest
Warning

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

Go to latest
Published: Apr 22, 2026 License: MIT Imports: 11 Imported by: 4

README

dsx

A server-rendered component framework for Go. Built on Templ, DaisyUI, and Datastar.

Browser                          Server (Go)
  |                                |
  |  GET /page ------------------>|  templ renders HTML
  |<---- full HTML page ----------|
  |                                |
  |  SSE @get('/fragment') ------>|  handler returns SSE events
  |<---- patch DOM element -------|  (PatchElements, PatchSignals)
  |                                |
  |  SSE stream /stream --------->|  stream.Relay listens to pub/sub
  |<---- per-domain signal -------|  component reacts by action

Why dsx

  • Server-side rendering with Go and Templ. No Node.js build step.
  • ~15KB of JavaScript total. Datastar handles all frontend interactivity.
  • 70+ pre-built components styled with DaisyUI. Accordions to YAML trees.
  • Real-time updates across all browser tabs via pub/sub-backed SSE streaming.
  • Type-safe Datastar helpers. No string typos in data-on:click attributes.
  • You own the code. Fork it, edit it, ship it. No versioning conflicts.

showcase

Quick start

# Prerequisites: Go 1.24+

# Install dependencies
go tool task install:all

# Generate templ + build Tailwind CSS
go tool templ generate
go tool gotailwind

# Run the showcase
go run ./cmd/showcase

Open http://localhost:3333 to browse all components.

Architecture

┌──────────────────────────────────────────────────────────────┐
│  dsx                                                         │
│                                                              │
│  ui/           70+ DaisyUI components (templ)                │
│  ds/           Type-safe Datastar helpers (frontend + SSE)   │
│  stream/       Reactive SSE relay backed by pub/sub          │
│  layouts/      Base HTML + Dashboard layout                  │
│  utils/        TwMerge, If, RandomID                         │
│  showcase/     Reusable dev server with identity switching   │
│                                                              │
│  External:                                                   │
│  pubsub        Pub/sub interface + adapters (NATS/Redis/Chan)│
│  identity      Multi-tenant identity context                 │
└──────────────────────────────────────────────────────────────┘

Core packages

dsx — Context and middleware

Every request carries a dsx.Context with session, CSRF, theme, and stream state.

import "github.com/laenen-partners/dsx"

r := chi.NewRouter()

// Session + CSRF middleware (cookie-based, no session store needed)
r.Use(dsx.Middleware(dsx.MiddlewareConfig{
    Secret: secret, // 32-byte HMAC key
    Secure: true,   // HTTPS-only cookies
}))
r.Use(dsx.SecurityHeadersMiddleware())

Access in handlers:

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := dsx.FromContext(r.Context())
    ctx.SessionID  // unique per browser
    ctx.CSRFToken  // signed double-submit token
    ctx.Theme      // current DaisyUI theme
    ctx.BasePath   // e.g. "/app"
    ctx.APIPath("/customers/list") // → "/app/customers/list"
}
ds — Datastar helpers

Type-safe helpers for Datastar attributes and SSE operations. Prevents common mistakes like data-on-click (wrong) vs data-on:click (correct).

Frontend attributes
import "github.com/laenen-partners/dsx/ds"

// Event handlers
ds.OnClick(ds.Post("/api/save"))     // data-on:click="@post('/api/save', ...)"
ds.On("keydown", "$value = ''")      // data-on:keydown="$value = ''"

// Data binding
ds.Bind("form1", "email")            // data-bind:form1.email

// Display
ds.Show("$isVisible")                // data-show="$isVisible"
ds.Text("$count + ' items'")         // data-text="$count + ' items'"
ds.ClassToggle("active", "$isOn")    // data-class:active="$isOn"

// Initialization
ds.Init(ds.GetOnce("/api/data"))      // data-init="@get('/api/data')"

// Merge multiple attribute maps
ds.Merge(ds.OnClick(expr), ds.Show("$open"))
SSE backend operations
// Read form signals from request
var signals MyForm
ds.ReadSignals("form-id", r, &signals)

// Patch a templ component into the DOM
ds.Send.Patch(sse, myComponent(data))

// UI feedback
ds.Send.Toast(sse, ds.ToastSuccess, "Saved!")
ds.Send.Drawer(r.Context(), sse, editForm(item))
ds.Send.Modal(r.Context(), sse, confirmDialog())
ds.Send.Confirm(sse, "Delete this?", "/api/delete/42")
ds.Send.HideDrawer(sse)
ds.Send.HideModal(sse)
ds.Send.Redirect(sse, "/dashboard")
ds.Send.Download(sse, "/files/report.pdf", "report.pdf")
stream — DOM-driven watch subscriptions

See the Real-Time Reactive UIs section below for full details, concepts, and examples.

layouts — Page layouts
import "github.com/laenen-partners/dsx/layouts"

// Base layout provides HTML shell with all required containers
templ MyPage() {
    @layouts.Base(layouts.BaseProps{
        Title:     "My App",
        Theme:     "silk",
        CSRFToken: dsxCtx.CSRFToken,
        Head:      myHead(),
    }) {
        // page content
    }
}

The Base layout includes containers for SSE-driven UI:

<head>
    <meta name="stream-url">  ← watch worker reads stream endpoint URL
</head>
<body>
    { children }              ← your page content
    <div id="drawer-panel">   ← ds.Send.Drawer() target
    <div id="modal-panel">    ← ds.Send.Modal() / Confirm() target
    <div id="toast-container"> ← ds.Send.Toast() target
</body>

The watch worker JS (loaded via <script>) automatically manages SSE connections based on data-watch attributes in the DOM.

The Dashboard layout adds a sidebar, navbar, and optional detail panel:

@layouts.Dashboard(layouts.DashboardProps{
    BaseProps: baseProps,
    App:       layouts.AppBranding{Name: "MyApp", Href: "/"},
    Nav:       navGroups,
    CurrentPath: r.URL.Path,
    ThemeToggle: &layouts.ThemeToggleConfig{
        DarkTheme: "dark", LightTheme: "silk",
    },
})

Components

70+ components in ui/, each following the same pattern:

import "github.com/laenen-partners/dsx/ui/button"

@button.Button(button.Props{
    Variant: button.VariantPrimary,
    Size:    button.SizeLg,
    OnClick: ds.Post("/api/action"),
}) {
    Save
}

All components:

  • Use DaisyUI CSS classes for styling
  • Accept optional variadic Props with sensible defaults
  • Support Class for extra Tailwind classes (merged via TwMerge)
  • Support Attributes for arbitrary HTML attributes
  • Use theme tokens, never hardcoded colors

Interactive components (calendar, form, file upload, etc.) include handler.go with SSE endpoints registered via ui.RegisterRoutes().

Showcase server

The showcase package provides a reusable dev server for previewing components:

import (
    "github.com/laenen-partners/dsx/showcase"
    "github.com/laenen-partners/pubsub"
    "github.com/laenen-partners/dsx/stream"
)

showcase.Run(showcase.Config{
    Port: 3333,
    Identities: []showcase.Identity{
        {Name: "Admin", TenantID: "t1", PrincipalID: "admin-1", Roles: []string{"admin"}},
        {Name: "Viewer", TenantID: "t1", PrincipalID: "viewer-1", Roles: []string{"viewer"}},
    },
    Pages: map[string]templ.Component{
        "/": homePage(),
    },
    Setup: func(ctx context.Context, r chi.Router, bus *pubsub.Bus, relay *stream.Relay) error {
        // register fragment routes
        return nil
    },
})

Features:

  • In-process pub/sub (zero external deps)
  • Identity switching with role-based testing
  • Context editor for theme, tenant, workspace
  • CSRF + security headers pre-configured
  • PORT env var support (e.g. PORT=0 for random port)

Real-Time Reactive UIs

dsx provides a complete system for building reactive, real-time UIs where data changes on the server automatically propagate to every connected browser tab. No WebSocket code, no client-side state management, no JavaScript beyond Datastar and an ~80-line watch worker.

Concepts

Domain — A named data category your components care about (e.g. "customers", "invoice", "counter"). Maps to a pub/sub topic.

Action — What happened: "created", "updated", "deleted", "archived", "restored". Published via pubsub.Bus methods.

Watch — A DOM attribute (data-watch) that declares "this element cares about changes to this domain". The watch worker detects these automatically.

Reaction — What to do when a matching event arrives. Currently: reload a URL via @get(). Different reactions can filter by action and entity ID.

How it works
┌─────────────────────────────────────────────────────────────────────┐
│  BROWSER                                                            │
│                                                                     │
│  ┌──────────────────────┐    ┌──────────────────────┐               │
│  │  <div data-watch=    │    │  <div data-watch=    │               │
│  │    "customers"       │    │    "customers"       │               │
│  │    data-effect="...">│    │    data-effect="...">│               │
│  │   Customer List      │    │   Customer Count     │               │
│  └──────────────────────┘    └──────────────────────┘               │
│            ▲                           ▲                            │
│            │ data-effect fires         │ data-effect fires          │
│            │ @get('/api/list')         │ @get('/api/count')         │
│            │                           │                            │
│  ┌─────────┴───────────────────────────┴──────────────────────┐     │
│  │  Watch Worker (MutationObserver)                            │     │
│  │  Scans DOM for data-watch → manages hidden SSE div          │     │
│  │  ┌─────────────────────────────────────────────────┐        │     │
│  │  │  <div id="__ds-watch" style="display:none"      │        │     │
│  │  │    data-init="@get('/stream?watch=customers')">  │        │     │
│  │  └─────────────────────────────────────────────────┘        │     │
│  └─────────────────────────────────────────────────────────────┘     │
│                           │                                         │
│                           │ SSE connection                          │
└───────────────────────────┼─────────────────────────────────────────┘
                            │
                            ▼
┌───────────────────────────────────────────────────────────────────┐
│  SERVER                                                           │
│                                                                   │
│  ┌──────────────────┐    ┌──────────────┐    ┌─────────────────┐  │
│  │  stream.Relay     │◄───│  Pub/Sub     │◄───│  Handler        │  │
│  │  Handler()        │    │  (NATS/Redis │    │  bus.Notify*()  │  │
│  │  pushes signals   │    │   /channels) │    │  after mutation │  │
│  └──────────────────┘    └──────────────┘    └─────────────────┘  │
└───────────────────────────────────────────────────────────────────┘
Step-by-step flow
1. RENDER        Component outputs data-watch="customers" on its wrapper div
                 Watch worker detects it via MutationObserver

2. CONNECT       Watch worker creates hidden div with
                 data-init="@get('/stream?watch=customers')"
                 Datastar opens persistent SSE connection

3. MUTATE        User submits form → handler saves to DB
                 Handler calls bus.NotifyCreated(ctx, "customers", "42")

4. PUBLISH       Bus publishes to pub/sub topic:
                 change.{tenant}.{workspace}.customers.42.created

5. RELAY         stream.Relay receives notification, pushes SSE event:
                 {"_ds_customers": {"id":"42",
                                    "action":"created","ts":1711036800000}}

6. REACT         data-effect on each element evaluates:
                 List:  "created" matches Structural       → @get('/api/list')
                 Count: "created" matches Any              → @get('/api/count')
                 Row:   "created" doesn't match Updated    → no reload

7. RELOAD        Datastar fetches fresh HTML via SSE, morphs the DOM
Setup

Three things are needed: a pub/sub backend, a relay, and the watch worker script.

1. Create relay and bus
import (
    "github.com/laenen-partners/dsx/stream"
    "github.com/laenen-partners/pubsub"
    "github.com/laenen-partners/pubsub/chanpubsub" // or natspubsub, redispubsub
)

// In-process pub/sub (swap for NATS/Redis in production)
ps := chanpubsub.New()

// Pattern resolver — maps watch domains to pub/sub subscription patterns
resolver := func(_ context.Context, watch string) string {
    domain, entityID, hasID := strings.Cut(watch, ".")
    if !hasID || entityID == "" {
        return fmt.Sprintf("%s.%s.change.%s.>", tenantID, workspaceID, domain)
    }
    return fmt.Sprintf("%s.%s.change.%s.%s.>", tenantID, workspaceID, domain, entityID)
}
relay := stream.New(ps, resolver)
bus := pubsub.NewBus(ps, "myapp", pubsub.WithScope(tenantID, workspaceID))

// Wire the SSE endpoint
r.Get("/stream", relay.Handler())
2. Set StreamURL in middleware
r.Use(func(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        dsxCtx := dsx.FromContext(r.Context())
        dsxCtx.StreamURL = "/stream"
        next.ServeHTTP(w, r.WithContext(dsxCtx.WithContext(r.Context())))
    })
})

The base layout renders <meta name="stream-url" content="/stream"/> — the watch worker reads this to know where to connect.

3. Load watch worker script
<script src="/assets/js/watch-worker.js"></script>

That's it. No other wiring needed.

Templates — declaring watches

Use stream.Watch() to declare what a component cares about. It returns templ.Attributes with data-watch and data-effect.

Watch all actions (dashboard widget)
// Reloads on ANY change to customers (created, updated, deleted, ...)
<div id="customer-count"
    { ds.Init(ds.GetOnce(wxctx.APIPath("/customers/count")))... }
    { stream.Watch(ctx, "customers",
        stream.Any.Get(wxctx.APIPath("/customers/count")))... }>
    —
</div>
Watch structural changes only (list)
// Only reloads when customers are created or deleted (not on updates)
<div { stream.Watch(ctx, "customers",
    stream.Structural.Get(wxctx.APIPath("/customers/list")))... }>
    <table>...</table>
</div>
Watch a specific entity (row/detail)
// Only reloads when this specific customer is updated
<div id={fmt.Sprintf("customer-row-%d", c.ID)}
    { stream.Watch(ctx, "customers",
        stream.Updated.ID(c.ID).Get(
            wxctx.APIPath(fmt.Sprintf("/customers/%d/row", c.ID))))... }>
    // row content
</div>
Multiple reactions on one element
// One element, two reactions: structural reload + count reload
<div id="customer-panel"
    { stream.Watch(ctx, "customers",
        stream.Structural.Get(wxctx.APIPath("/customers/list")),
        stream.Any.Get(wxctx.APIPath("/customers/count")))... }>
</div>
Handlers — publishing changes

After mutating data, call the appropriate Bus.Notify* method:

func (h *handler) createCustomer(w http.ResponseWriter, r *http.Request) {
    customer := saveToDatabase(r)

    // Publish — all watching browsers react
    h.bus.NotifyCreated(r.Context(), "customers", strconv.Itoa(customer.ID))

    sse := datastar.NewSSE(w, r)
    ds.Send.HideDrawer(sse)
    ds.Send.Toast(sse, ds.ToastSuccess, "Customer created")
}

func (h *handler) updateCustomer(w http.ResponseWriter, r *http.Request) {
    customer := updateInDatabase(r)
    h.bus.NotifyUpdated(r.Context(), "customers", strconv.Itoa(customer.ID))
    datastar.NewSSE(w, r)
}

func (h *handler) deleteCustomer(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    deleteFromDatabase(id)
    h.bus.NotifyDeleted(r.Context(), "customers", id)
    datastar.NewSSE(w, r)
}
Use cases
Live counter (shared across tabs)
Tab A                        Server                    Tab B
  |                            |                          |
  | click "+1"                 |                          |
  |  @get('/increment') ------>|                          |
  |                            | counter++ = 42           |
  |                            | bus.NotifyUpdated(       |
  |                            |   "counter", "shared")   |
  |                            |                          |
  |  _ds_counter: ◄───────────|───────────► _ds_counter: |
  |   action: updated          |   action: updated        |
  |                            |                          |
  | @get('/api/counter') ─────>|◄── @get('/api/counter') ─|
  | <── <span>42</span>        |     <span>42</span> ────>|
// Template
<div { stream.Watch(ctx, "counter",
    stream.Updated.ID("shared").Get(wxctx.APIPath("/stream/counter")))... }>
    <span id="stream-counter-value"
        { ds.Init(ds.GetOnce(wxctx.APIPath("/stream/counter")))... }>—</span>
</div>

// Handler
func (s *streamHandlers) increment() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        s.counter.Add(1)
        s.bus.NotifyUpdated(r.Context(), "counter", "shared")
        datastar.NewSSE(w, r)
    }
}
Customer CRUD (list + count + drawer form)
User clicks "Add Customer"
  |
  ▼
┌─────────────────────┐
│  Drawer opens with   │
│  customer form       │
│  ┌────────────────┐  │
│  │ Name: [____]   │  │
│  │ Email: [____]  │  │
│  │ [Save]         │  │
│  └────────────────┘  │
└─────────────────────┘
  |
  ▼  form submit → handler
  |
  bus.NotifyCreated(ctx, "customers", "42")
  |
  ▼  _ds_customers signal arrives at all tabs
  |
  ┌─────────────────────────────────────────┐
  │ List wrapper:                            │
  │   Watch("customers",                    │
  │     Structural.Get("/api/customers/list"))│
  │   → "created" matches → reloads list    │
  ├─────────────────────────────────────────┤
  │ Count widget:                            │
  │   Watch("customers",                    │
  │     Any.Get("/api/customers/count"))     │
  │   → Any matches everything → reloads    │
  ├─────────────────────────────────────────┤
  │ Row (if existed):                        │
  │   Watch("customers", Updated.ID(42)     │
  │     .Get("/api/customers/42/row"))       │
  │   → "created" ≠ "updated" → NO reload   │
  └─────────────────────────────────────────┘
Collaborative editing (stale banner)
// Show a "content changed" banner — let the user decide when to reload
<div id="stale-banner" style="display:none"
    { stream.Watch(ctx, "document",
        stream.Updated.ID(doc.ID).Get(
            "javascript:document.getElementById('stale-banner').style.display='block'"))... }>
    <div class="alert alert-warning">
        Content was updated by another user.
        <button data-on:click={ds.Get(fmt.Sprintf("/api/documents/%d", doc.ID))}>
            Load latest
        </button>
    </div>
</div>
Watch scope vs action filtering

stream.Watch() generates two attributes that work together:

  • data-watch — controls the SSE subscription scope (what events arrive at the browser)
  • data-effect — controls which actions trigger a reload (client-side filtering via the action entry point)
stream.Watch(ctx, "customers",
    stream.Structural.Get("/api/list"))

Generates:
  data-watch="customers"
  data-signals="{_ds_customers: {id: '', action: '', ts: 0}}"
  data-effect="if($_ds_customers.ts > 0
    && ['created','deleted','connected'].includes($_ds_customers.action)) { @get('/api/list') }"

data-watch is a coarse server-side filter. Actions (Created, Updated, Deleted, Any, Structural) are a fine client-side filter. Both are set by stream.Watch() — you never write them separately.

data-watch — what events arrive
data-watch value SSE receives
customers ALL changes for any customer (any ID, any action)
customers.42 Changes for customer 42 only (any action)
Actions — what triggers a reload
SSE pushes event to browser          data-effect evaluates
(scoped by data-watch)               (filtered by action)
         |                                     |
         v                                     v
data-watch="customers"          stream.Structural.Get(url)
  receives: created ─────────> matches "created"  -> reload
  receives: updated ─────────> doesn't match      -> ignore
  receives: deleted ─────────> matches "deleted"   -> reload

data-watch="customers.42"      stream.Updated.ID(42).Get(url)
  receives: updated id=42 ──> matches             -> reload
  ignores:  updated id=99     (never arrives, SSE filtered)
  receives: created id=42 ──> "created" != "updated" -> ignore
Action Triggers on Use case
stream.Any Any action Counts, dashboards
stream.Structural Created + Deleted Lists, tables
stream.Updated In-place changes Rows, detail views
stream.Updated.ID(42) Specific entity update Single row, single card
Pub/sub adapters
Adapter Package Use case
Go channels pubsub/chanpubsub Development, testing (zero deps)
NATS pubsub/natspubsub Production (recommended, wraps *nats.Conn)
Redis pubsub/redispubsub Production (wraps *redis.Client)

All adapters support dot-separated topics with wildcards: * matches one segment, > matches the rest. Swap adapters without changing any application code.

Architecture notes
  • DOM-drivendata-watch attributes on elements ARE the subscriptions. No render-time accumulation, no context wiring needed.
  • MutationObserver — The watch worker (~80 lines of JS) scans for data-watch changes, debounces (300ms), and reconnects SSE when watches change.
  • One SSE connection — per browser tab, managed by Datastar (reconnects automatically).
  • Structured events{domain, id, action, ts} instead of boolean flags. Components can distinguish creates from updates from deletes.
  • Action filtering — A list watches Structural (created + deleted), a count watches Any (everything), a row watches Updated with a specific ID. Fine-grained control over what triggers a reload.
  • Backpressure — 64-message internal buffer. Slow clients drop excess events (the next event catches up).
  • Max 64 watches — per SSE connection, to prevent resource exhaustion.
  • Multi-tenant — the app provides a PatternResolver that maps watch domains to pub/sub subscription patterns, giving full control over tenant/workspace scoping.
Watch inspector

Enable the built-in watch debugger to see active watches, connection state, live signals, and an event log:

@layouts.Base(layouts.BaseProps{
    // ...
    ShowWatchInspector: true, // floating panel, bottom-right
})

Best practices

Component design
// DO: Use optional variadic props with zero-value defaults
templ MyComponent(props ...Props) {
    {{ var p Props }}
    if len(props) > 0 {
        {{ p = props[0] }}
    }
    // ...
}

// DO: Use TwMerge for class composition
class := utils.TwMerge("btn btn-primary", p.Class)

// DO: Use theme tokens
"bg-base-200 text-base-content border-base-300"

// DON'T: Hardcode colors
"bg-gray-100 text-gray-900 border-gray-300"
SSE handlers
// DO: Use ds.ReadSignals for type-safe form handling
var signals MyForm
if err := ds.ReadSignals("form-id", r, &signals); err != nil { ... }

// DO: Close SSE response on mutation handlers
func increment(w http.ResponseWriter, r *http.Request) {
    counter.Add(1)
    bus.NotifyUpdated(r.Context(), "counter", "shared")
    datastar.NewSSE(w, r) // important: closes the SSE cleanly
}

// DON'T: Send PatchElements without a target element
// (causes browser error when no matching ID exists)
Stream watches
// DO: Use action-aware reactions
stream.Watch(ctx, "customers",
    stream.Structural.Get("/api/customers"))                          // list: structural only
stream.Watch(ctx, "customers",
    stream.Any.Get("/api/customers/count"))                           // count: any change
stream.Watch(ctx, "customers",
    stream.Updated.ID(42).Get("/api/customers/42/row"))               // row: specific ID

// DO: Use bus.NotifyCreated/Updated/Deleted for semantic notifications
bus.NotifyCreated(ctx, "customer", "42")
bus.NotifyUpdated(ctx, "invoice", "123")
bus.NotifyDeleted(ctx, "order", "99")
Security
// DO: Always use dsx.Middleware for CSRF protection
r.Use(dsx.Middleware(dsx.MiddlewareConfig{
    Secret: secret,
    Secure: true, // set true in production
}))

// DO: Add security headers
r.Use(dsx.SecurityHeadersMiddleware())

// ds.Post/Put/Delete automatically include X-CSRF-Token header
ds.OnClick(ds.Post("/api/save"))

Project structure

dsx/
  ui/                   70+ DaisyUI components
    button/
      button.templ      component template
      button_templ.go   generated (do not edit)
    form/
      form.templ        component template
      handler.go        SSE handler
      routes.go         route registration
  ds/                   Datastar helpers
    ds.go               frontend attributes
    signals.go          signal reading
    send*.go            SSE operations (toast, drawer, modal, etc.)
  stream/               DOM-driven watch subscriptions
    stream.go           Relay, Watch, Actions
  layouts/              Base + Dashboard layouts
  utils/                TwMerge, If, RandomID
  showcase/             reusable dev server
  cmd/showcase/         main dsx component gallery
  docs/                 reference documentation
  static/css/           Tailwind CSS + DaisyUI

Commands

go tool templ generate       # Generate Go from .templ files
go tool templ fmt .          # Format .templ files
go tool gotailwind           # Build Tailwind CSS
go tool task install:all     # Install all dependencies
go build ./...               # Build everything
go test ./...                # Run all tests
go run ./cmd/showcase        # Run the component showcase

License

See LICENSE.

Documentation

Overview

Provides context for dsx templates

Index

Constants

This section is empty.

Variables

View Source
var Static embed.FS

Functions

func Middleware

func Middleware(cfg MiddlewareConfig) func(http.Handler) http.Handler

Middleware reads or creates session/CSRF/theme cookies and populates Context on each request. On mutating methods it validates the signed double-submit CSRF token.

func Post

func Post(url string) string

Post returns a Datastar expression that performs a POST request to the given URL.

func SecurityHeadersMiddleware

func SecurityHeadersMiddleware(secure ...bool) func(http.Handler) http.Handler

SecurityHeadersMiddleware sets common security response headers. When secure is true (i.e. HTTPS / production), HSTS is also added.

Types

type Context

type Context struct {
	SessionID string
	CSRFToken string
	Theme     string
	BasePath  string // prefix for all SSE handler routes (e.g. "/showcase")
	StreamURL string // URL for the reactive SSE stream endpoint (e.g. "/showcase/stream")
}

Context carries request-scoped state through the middleware chain and into templ components. Retrieve it with FromContext.

func FromContext

func FromContext(ctx context.Context) *Context

FromContext extracts the dsx Context from a context.Context. If none is present, a new empty Context is returned.

func NewContext

func NewContext() *Context

NewContext returns an empty Context.

func (*Context) APIPath

func (c *Context) APIPath(path string) string

APIPath returns the full path for a component SSE handler by prepending the BasePath. For example, with BasePath "/showcase" and path "/calendar/navigate", it returns "/showcase/calendar/navigate".

func (*Context) WithContext

func (c *Context) WithContext(ctx context.Context) context.Context

WithContext stores the dsx Context into a context.Context.

type MiddlewareConfig

type MiddlewareConfig struct {
	Secret []byte // HMAC-SHA256 key, minimum 32 bytes — panics if shorter
	Secure bool   // Secure flag on cookies (true for HTTPS / production)
}

MiddlewareConfig configures the dsx middleware.

Directories

Path Synopsis
cmd
icongen command
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
showcase command
showcase/internal/components
templ: version: v0.3.1001
templ: version: v0.3.1001
showcase/internal/handlers
templ: version: v0.3.1001
templ: version: v0.3.1001
showcase/internal/layouts
templ: version: v0.3.1001
templ: version: v0.3.1001
showcase/internal/pages
templ: version: v0.3.1001
templ: version: v0.3.1001
Package ds provides typed helpers for Datastar HTML attributes.
Package ds provides typed helpers for Datastar HTML attributes.
templ: version: v0.3.1001
templ: version: v0.3.1001
Package ref provides static reference data for ISO countries, currencies, languages, and timezones, along with locale-aware formatting for numbers, money, dates, percentages, IBANs, and phone numbers.
Package ref provides static reference data for ISO countries, currencies, languages, and timezones, along with locale-aware formatting for numbers, money, dates, percentages, IBANs, and phone numbers.
templ: version: v0.3.1001
templ: version: v0.3.1001
example command
templ: version: v0.3.1001
templ: version: v0.3.1001
Package stream provides DOM-driven SSE subscriptions backed by pub/sub.
Package stream provides DOM-driven SSE subscriptions backed by pub/sub.
ui
accordion
templ: version: v0.3.1001
templ: version: v0.3.1001
aichat
templ: version: v0.3.1001
templ: version: v0.3.1001
alert
templ: version: v0.3.1001
templ: version: v0.3.1001
avatar
templ: version: v0.3.1001
templ: version: v0.3.1001
badge
templ: version: v0.3.1001
templ: version: v0.3.1001
breadcrumbs
templ: version: v0.3.1001
templ: version: v0.3.1001
briefing
templ: version: v0.3.1001
templ: version: v0.3.1001
button
templ: version: v0.3.1001
templ: version: v0.3.1001
calendar
templ: version: v0.3.1001
templ: version: v0.3.1001
card
templ: version: v0.3.1001
templ: version: v0.3.1001
carousel
templ: version: v0.3.1001
templ: version: v0.3.1001
chat
templ: version: v0.3.1001
templ: version: v0.3.1001
codeview
templ: version: v0.3.1001
templ: version: v0.3.1001
combobox
templ: version: v0.3.1001
templ: version: v0.3.1001
commandbar
templ: version: v0.3.1001
templ: version: v0.3.1001
dock
templ: version: v0.3.1001
templ: version: v0.3.1001
drawer
templ: version: v0.3.1001
templ: version: v0.3.1001
dropdown
templ: version: v0.3.1001
templ: version: v0.3.1001
fab
templ: version: v0.3.1001
templ: version: v0.3.1001
feed
templ: version: v0.3.1001
templ: version: v0.3.1001
feeditem
templ: version: v0.3.1001
templ: version: v0.3.1001
fieldset
templ: version: v0.3.1001
templ: version: v0.3.1001
fileinput
templ: version: v0.3.1001
templ: version: v0.3.1001
fileupload
templ: version: v0.3.1001
templ: version: v0.3.1001
filter
templ: version: v0.3.1001
templ: version: v0.3.1001
footer
templ: version: v0.3.1001
templ: version: v0.3.1001
form
templ: version: v0.3.1001
templ: version: v0.3.1001
hovergallery
templ: version: v0.3.1001
templ: version: v0.3.1001
indicator
templ: version: v0.3.1001
templ: version: v0.3.1001
join
templ: version: v0.3.1001
templ: version: v0.3.1001
jsonview
templ: version: v0.3.1001
templ: version: v0.3.1001
kbd
templ: version: v0.3.1001
templ: version: v0.3.1001
label
templ: version: v0.3.1001
templ: version: v0.3.1001
link
templ: version: v0.3.1001
templ: version: v0.3.1001
list
templ: version: v0.3.1001
templ: version: v0.3.1001
loading
templ: version: v0.3.1001
templ: version: v0.3.1001
markdown
templ: version: v0.3.1001
templ: version: v0.3.1001
menu
templ: version: v0.3.1001
templ: version: v0.3.1001
mockupcode
templ: version: v0.3.1001
templ: version: v0.3.1001
modal
templ: version: v0.3.1001
templ: version: v0.3.1001
money
templ: version: v0.3.1001
templ: version: v0.3.1001
moneyinput
templ: version: v0.3.1001
templ: version: v0.3.1001
multiselect
templ: version: v0.3.1001
templ: version: v0.3.1001
navbar
templ: version: v0.3.1001
templ: version: v0.3.1001
pagination
templ: version: v0.3.1001
templ: version: v0.3.1001
progress
templ: version: v0.3.1001
templ: version: v0.3.1001
radialprogress
templ: version: v0.3.1001
templ: version: v0.3.1001
radio
templ: version: v0.3.1001
templ: version: v0.3.1001
rangeinput
templ: version: v0.3.1001
templ: version: v0.3.1001
rating
templ: version: v0.3.1001
templ: version: v0.3.1001
scrollstrip
templ: version: v0.3.1001
templ: version: v0.3.1001
selectinput
templ: version: v0.3.1001
templ: version: v0.3.1001
separator
templ: version: v0.3.1001
templ: version: v0.3.1001
skeleton
templ: version: v0.3.1001
templ: version: v0.3.1001
sparkline
templ: version: v0.3.1001
templ: version: v0.3.1001
stack
templ: version: v0.3.1001
templ: version: v0.3.1001
stat
templ: version: v0.3.1001
templ: version: v0.3.1001
status
templ: version: v0.3.1001
templ: version: v0.3.1001
steps
templ: version: v0.3.1001
templ: version: v0.3.1001
tab
templ: version: v0.3.1001
templ: version: v0.3.1001
table
templ: version: v0.3.1001
templ: version: v0.3.1001
textarea
templ: version: v0.3.1001
templ: version: v0.3.1001
textrotate
templ: version: v0.3.1001
templ: version: v0.3.1001
themecontroller
templ: version: v0.3.1001
templ: version: v0.3.1001
timeline
templ: version: v0.3.1001
templ: version: v0.3.1001
toast
templ: version: v0.3.1001
templ: version: v0.3.1001
toggle
templ: version: v0.3.1001
templ: version: v0.3.1001
tooltip
templ: version: v0.3.1001
templ: version: v0.3.1001
validator
templ: version: v0.3.1001
templ: version: v0.3.1001
watchdebugger
templ: version: v0.3.1001
templ: version: v0.3.1001
yamltree
templ: version: v0.3.1001
templ: version: v0.3.1001
iox

Jump to

Keyboard shortcuts

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