datagrid

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package datagrid is the GoFastr data-grid plugin: AG Grid Community's infinite row model inside an opaque-origin sandboxed iframe, with sort, filter and paging executed by the HOST's Go rows source instead of the frame.

Why this shape: every other plugin in this repo moves ONE small document across the postMessage bridge. A grid moves rows by the thousand, and the framed CSP sets connect-src 'none' (the frame can never fetch its own rows), so the plugin is only interesting if the data arrives page-by-page from the host. Server-side sort/filter/paging over a correlated fire-and-forget event pair — requestRows → rowsResult, exactly the richtext requestUpload → uploadResult pattern — IS the product here; a grid that loads all rows up front would prove nothing about the platform.

The canonical doc (schema datagrid-v1) is VIEW STATE ONLY: {columns[], sort, filter, pageSize}. Rows are never part of the doc, never saved into it and never echoed back out of it — the doc round-trips through the hidden form field like every other plugin's, while the data keeps flowing one page per bridge round trip.

Capabilities: data:read + theme:read are always granted; data:write (cell edits and view-state saves) and data:export (CSV egress) are optional, granted exactly when the host supplies the matching handler option — the same shape as pdf's pdf:export. pluginhost.Allow is a capability gate, NOT authentication: it passes for anonymous callers, so any route that WRITES (POST /cell) must be documented as requiring the host to check the session in its own handler. See docs/datagrid.md.

Index

Constants

View Source
const (
	Name             = "datagrid"
	Version          = "0.1.0"
	RoutePrefix      = "/__gofastr/plugin/datagrid"
	GridHTMLURL      = RoutePrefix + "/grid.html"
	GridJSURL        = RoutePrefix + "/grid.js"
	GridCSSURL       = RoutePrefix + "/grid.css"
	AdapterScriptURL = RoutePrefix + "/adapter.js"
	ConfigScriptURL  = RoutePrefix + "/config.js"
	RowsURL          = RoutePrefix + "/rows"
	CellWriteURL     = RoutePrefix + "/cell"
	ExportURL        = RoutePrefix + "/export"
	SaveURL          = RoutePrefix + "/save"
	DemoURL          = "/datagrid"
	SchemaVersion    = "datagrid-v1"

	// CapDataWrite gates POST /cell and POST /save. It is OPTIONAL — it is
	// NOT in [DefaultCapabilities]. [WithCellWriteHandler] appends it (the
	// pdf WithExportHandler pattern), because editing rows is egress the
	// host explicitly turned on.
	CapDataWrite = "data:write"

	// CapDataExport gates POST /export. Optional for the same reason: CSV
	// export produces bytes on the host (a sandboxed frame cannot start a
	// download), and [WithExportHandler] is what turns that on.
	CapDataExport = "data:export"
)

Identity and route constants. Both this plugin and host/adapter.js hard-code these exactly (protocol-v1.md §2/§10). The demo lives at /datagrid.

These mirror the datagrid row in plugins.json; internal/registry tests pin Name + RoutePrefix against that row, so they MUST NOT drift.

Variables

This section is empty.

Functions

func DefaultCapabilities

func DefaultCapabilities() []string

DefaultCapabilities is the always-on grant set advertised to the grid: reading pages of rows and bridging theme tokens. data:write and data:export are deliberately absent — they are optionalCapabilities, appended by WithCellWriteHandler / WithExportHandler.

func Mount

func Mount(cfg MountConfig) render.HTML

Mount renders the generic mount marker plus the hidden input the adapter syncs on docChanged. Drop it into a form. All interpolated values are HTML-escaped inside pluginhost.MountMarker.

func UIHostOption

func UIHostOption() uihost.Option

UIHostOption injects the platform broker, this plugin's config script, and this plugin's adapter (in that order — the adapter reads the config global the config script publishes, and registers with the broker the former defines).

Types

type CellWriteRequest

type CellWriteRequest struct {
	DocID string
	RowID string
	Field string
	Value string
}

CellWriteRequest is one cell edit relayed from the frame via POST /cell.

type Column

type Column struct {
	Field  string `json:"field"`
	Header string `json:"header"`
	// Width is the initial column width in px (0 = AG Grid default).
	Width int `json:"width,omitempty"`
	// Type is "text" (default) or "number". Numbers right-align in the
	// frame and sort numerically in the host's rows source.
	Type string `json:"type,omitempty"`
	// Sortable / Editable toggle the obvious frame affordances. Sortable
	// columns trigger server-side sorts; editable ones (only when data:write
	// is granted) open a cell editor whose result round-trips through
	// POST /cell.
	Sortable bool `json:"sortable,omitempty"`
	// Filterable is accepted for symmetry with the doc shape; the demo
	// filter box filters every column, so it is currently unused by the
	// frame.
	Filterable bool `json:"filterable,omitempty"`
	Editable   bool `json:"editable,omitempty"`
}

Column describes one grid column. Columns are part of the canonical (view-state) doc: the host declares the schema, the frame renders it, and the rows requests carry it back so the host's rows source knows which fields are numeric when sorting server-side.

type Doc

type Doc struct {
	SchemaVersion string      `json:"schemaVersion"`
	Columns       []Column    `json:"columns"`
	Sort          []SortModel `json:"sort,omitempty"`
	Filter        string      `json:"filter,omitempty"`
	PageSize      int         `json:"pageSize,omitempty"`
}

Doc is the canonical datagrid-v1 view-state document. It describes HOW the table is viewed — never the rows themselves.

type ExportRequest

type ExportRequest struct {
	DocID  string
	Format string // always "csv" today
	// Columns the export covers, normalised by /export before the scan.
	Columns []Column
	Sort    []SortModel
	Filter  string
	// CSV streams the export body. The scan pages through the rows source
	// in 5,000-row chunks and spills to a temp file as it goes, so peak
	// memory stays at one chunk regardless of table size, and a mid-scan
	// source error aborts the whole export (an error response, never a
	// short file). The handler MUST fully consume CSV before returning —
	// the underlying temp file is removed once the handler returns.
	CSV io.Reader
	// RowCount is the number of data rows in CSV (the header is excluded).
	RowCount int
}

ExportRequest is handed to the host's export handler: the CSV the plugin generated host-side from the rows source under the request's sort/filter, plus the view-state context. The handler stores the CSV (io.Copy or io.ReadAll into whatever storage the host uses) and returns a URL the host page can download from — the frame cannot download.

type MountConfig

type MountConfig struct {
	// DocID is the persistence key for the view-state doc.
	DocID string
	// Doc is the initial view-state doc JSON server-rendered into the
	// marker (data-fui-plugin-doc) — the frame reads columns, sort, filter
	// and pageSize out of it on init.
	Doc string
	// Field is the hidden-input name the adapter mirrors the current
	// view-state doc into on docChanged, so a normal form POST round-trips
	// it. Defaults to "datagrid_doc". The name is published on the marker
	// as data-fui-plugin-field, which is how the adapter finds it — the
	// input itself is rendered after the marker, not inside it.
	Field string
	// MinHeight is the initial iframe height before the grid sizes itself.
	MinHeight string
	// Capabilities is an optional CSV grant override.
	Capabilities string
}

MountConfig configures Mount.

type Option

type Option func(*Plugin)

Option configures a Plugin.

func WithCapabilities

func WithCapabilities(caps ...string) Option

WithCapabilities overrides the grant set advertised to the grid. Default: DefaultCapabilities. data:write / data:export are appended separately by their handler options even when this fully replaces the set — the gate is on egress the host explicitly enabled, so silently dropping it would just break editing with a 403.

Grants are matched with the framework's scope grammar at runtime, so wildcards are legal here: "data:*" and "*:*" imply data:write and data:export. New requires the matching handler for anything the grant set implies — a wildcard that implies an optional capability without its handler is a construction panic, exactly like the literal capability.

func WithCellWriteHandler

func WithCellWriteHandler(fn func(ctx context.Context, req CellWriteRequest) error) Option

WithCellWriteHandler installs the persistence hook behind POST /cell AND opts the plugin into the optional data:write capability (appended to the grant set if not already present — the pdf WithExportHandler pattern). The handler is the host's chance to authorize the write against the real session: pluginhost.Allow is a capability gate, not authentication, and it passes for anonymous callers.

func WithDemoDoc

func WithDemoDoc(doc Doc) Option

WithDemoDoc sets the view state the demo page mounts when nothing has been saved yet — the demo's column set, since the plugin itself owns no schema.

func WithDemoPage

func WithDemoPage() Option

WithDemoPage registers the self-contained themed demo page at DemoURL.

func WithDemoRoute

func WithDemoRoute(path string) Option

WithDemoRoute overrides where WithDemoPage mounts the demo (default DemoURL, "/datagrid").

func WithDevGrantAll

func WithDevGrantAll() Option

WithDevGrantAll short-circuits the capability gate (Phase-0 demo / tests). It bypasses BOTH gate sides, so the write routes behind it must still fail closed on an unwired handler (a clear error, never a panic) — see handlers.go.

func WithExportHandler

func WithExportHandler(fn func(ctx context.Context, req ExportRequest) (string, error)) Option

WithExportHandler installs the export hook behind POST /export AND opts the plugin into the optional data:export capability. The plugin generates the CSV host-side from the rows source (streamed through ExportRequest's CSV reader); the handler stores it and returns a URL the host page turns into a download.

func WithRowsSource

func WithRowsSource(fn func(ctx context.Context, q RowsQuery) (RowsPage, error)) Option

WithRowsSource installs the server-side data source backing POST /rows and the CSV export scan. There is NO default: unlike pdf (which ships a sample document), a grid has no meaningful embedded dataset, so New panics without this option. The function runs in the host process with the caller's context — sorting, filtering and paging are the point, so the source must apply them, not return everything.

func WithSaveHandler

func WithSaveHandler(fn func(ctx context.Context, req SaveRequest) error) Option

WithSaveHandler overrides the view-state persistence hook. The default stores the canonical doc JSON in an in-memory map keyed by DocID.

type Plugin

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

Plugin is the data-grid plugin. It implements framework.Plugin and mirrors the mermaid/pdf shape: opaque-origin sandboxed iframe, protocol v1 over postMessage, go:embed'd frame bundle, capability gate, host-side RPC routes. The difference is the traffic profile — many small correlated event round trips instead of one document push.

func New

func New(opts ...Option) *Plugin

func (*Plugin) Capabilities

func (p *Plugin) Capabilities() []string

Capabilities returns the grant set this plugin advertises to the grid.

func (*Plugin) Init

func (p *Plugin) Init(app *framework.App) error

Init registers every asset and RPC route on the app's router.

func (*Plugin) LoadDoc

func (p *Plugin) LoadDoc(ctx context.Context, docID string) (docJSON string, ok bool)

LoadDoc returns the last-saved view-state JSON for docID (demo round-trip). ok is false when the doc has never been saved.

func (*Plugin) Manifest

func (p *Plugin) Manifest() pluginhost.Manifest

func (*Plugin) Name

func (p *Plugin) Name() string

type Row

type Row struct {
	ID    string            `json:"id"`
	Cells map[string]string `json:"cells"`
}

Row is one data row as it crosses the bridge: a stable id plus string cells keyed by column field. Cell typing is a host concern — the bridge carries display strings, and the rows source sorts its own typed data.

type RowsPage

type RowsPage struct {
	Rows    []Row `json:"rows"`
	LastRow int   `json:"lastRow"`
}

RowsPage is one server-side page. LastRow is the total row count under the current sort/filter, or -1 when unknown (AG Grid's infinite model contract).

type RowsQuery

type RowsQuery struct {
	DocID    string
	StartRow int
	EndRow   int
	Sort     []SortModel
	Filter   string
	Columns  []Column
}

RowsQuery is one server-side page request, the Go twin of the frame's requestRows params. Columns ride along so the source can sort typed columns correctly (numbers numerically) without the plugin hard-coding a schema.

type SaveRequest

type SaveRequest struct {
	DocID string
	Doc   Doc
	// DocJSON is the canonical JSON of the VALIDATED, normalised doc — the
	// same shape /save hands the save handler. The raw request body's JSON
	// is never persisted: a doc that failed a bound (page size, sort keys,
	// filter length) cannot be saved verbatim and reloaded later.
	DocJSON       string
	SchemaVersion string
}

SaveRequest is the view-state persist signal (POST /save).

type SortModel

type SortModel struct {
	Field string `json:"field"`
	Dir   string `json:"dir"` // "asc" | "desc"
}

SortModel is one active server-side sort: a column field plus a direction.

Jump to

Keyboard shortcuts

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