Help us shape the future of Go! Take the 2026 Go Developer Survey

client

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package client is the runtime of the Go clients the generator writes (D19, D67): the REST requests with the API key (D20), the error body, the Idempotency-Key (D16), the export trailer (D60), the stored filters with the recovery of an evicted one (D54), the filter document builders (D12), and the one socket that carries every subscription of a Client, with reconnect and resubscribe. A generated package `<app>client` wraps it with one typed method per declaration; this package knows no declaration and is usable on its own with routes and wire parameters.

A Client serves the identity its API key names. A 4401 close (the identity is gone, D7) or a socket handshake refused with 401 ends its live side for good: every subscription ends with an *EndedError and Subscribe refuses after; its REST requests go on and answer what the server answers. Everything in the package is safe for concurrent use.

Example

Example reads a list with the runtime directly; a generated client's GetNotes does the same with typed parameters.

package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"

	"github.com/govengers/livewire"
	"github.com/govengers/livewire/client"
)

type note struct {
	ID   string `json:"id"`
	Text string `json:"text"`
}

// Example reads a list with the runtime directly; a generated client's GetNotes does the same
// with typed parameters.
func main() {
	server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		fmt.Println(request.Method, request.URL.Path, request.URL.RawQuery, request.Header.Get("Authorization"))
		_, _ = io.WriteString(writer, `{"limit":10,"offset":0,"total":1,"items":[{"id":"1","text":"hello"}]}`)
	}))
	defer server.Close()
	notes, err := client.New(&client.Options{BaseURL: server.URL + "/api/v1", APIKey: "secret"})
	if err != nil {
		panic(err)
	}
	parameters := client.Parameters{}
	parameters.Set("limit", 10)
	var page livewire.ListResponse[note]
	if err := notes.Read(context.Background(), "/notes", parameters, &page); err != nil {
		panic(err)
	}
	fmt.Println(page.Items[0].Text)
}
Output:
GET /api/v1/notes limit=10 Bearer secret
hello

Index

Examples

Constants

View Source
const (
	IdempotencyKeyHeader = "Idempotency-Key"
	ExportRowsTrailer    = "Livewire-Export-Rows"
)

The headers the client sends and reads; a test pins them to the REST adapter's.

Variables

This section is empty.

Functions

func NewIdempotencyKey

func NewIdempotencyKey() string

NewIdempotencyKey is a fresh Idempotency-Key: a UUID v7, monotonic within the process.

func NewUUIDv4

func NewUUIDv4() uuid.UUID

NewUUIDv4 is a fresh random UUID version 4.

func NewUUIDv7

func NewUUIDv7() uuid.UUID

NewUUIDv7 is a fresh UUID version 7, monotonic within the process: the client id of a create that takes one (D18).

func Strings

func Strings[Text ~string](values []Text) []string

Strings converts a list of typed strings (generated sort terms) for Set.

func Subscribe

func Subscribe[Data any](ctx context.Context, client *Client, target string, parameters Parameters) (<-chan Push[Data], error)

Subscribe subscribes to a live read on the client's socket (D2, D46): target is the declaration's name with the read kind after a dot ("parts", "parts.item"), parameters its wire parameters. The socket opens with the first subscription of the client and closes after the last; after a reconnect every subscription subscribes again and starts with a snapshot. The channel delivers the pushes in order and closes when ctx ends or after a push of kind error; a consumer that falls behind gets the current state instead of every step. A subscribe answered 404 for a filterId the client stored or registered without a name (D54) subscribes once more with the filter inline. Subscribe itself fails only when the live side ended (*EndedError).

Types

type Call

type Call struct {
	Method         string
	Path           string
	Parameters     Parameters
	Body           any
	IdempotencyKey string
}

Call is one mutation as Client.Call sends it: the method, the route, its parameters, the JSON body (nil for none) and the Idempotency-Key ("" for a fresh one).

type CallOptions

type CallOptions struct {
	IdempotencyKey string
}

CallOptions are the options of one mutation, upload or batch. IdempotencyKey is the key the server records the call under (D16), a UUID (the server refuses another text, so the client does before sending); empty, the call gets a fresh one. Send the same key on every retry of one call, so a call that ran answers its recorded response.

type Client

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

Client talks to one livewire API as the identity of one API key: REST requests and one socket for every subscription, opened on the first and closed after the last. Build it with New; it needs no Close. It is safe for concurrent use.

func New

func New(options *Options) (*Client, error)

New validates and copies options (DES-001).

func (*Client) Batch

func (c *Client) Batch(ctx context.Context, calls []livewire.BatchCall, options *CallOptions) ([]livewire.BatchResult, error)

Batch runs several mutations in one transaction, all or nothing (D33, D47), under one Idempotency-Key, and answers one result per call in order.

func (*Client) Call

func (c *Client) Call(ctx context.Context, call *Call, response any) error

Call sends a mutation with its Idempotency-Key and decodes the answer into response (nil, or no content, decodes nothing).

func (*Client) DeleteFilter

func (c *Client) DeleteFilter(ctx context.Context, id string) error

DeleteFilter removes one of the caller's filters; one that is not there is no error.

func (*Client) Export

func (c *Client) Export(ctx context.Context, path string, parameters Parameters, writer io.Writer) (int, error)

Export streams GET path (D35) into writer and answers the rows the server wrote. An export the server cut (D60) fails: its body breaks off or it ends without the Livewire-Export-Rows trailer; writer then holds part of the rows. A 404 on an evicted unnamed filterId is retried inline once (D54), before anything is written.

func (*Client) ListFilters

func (c *Client) ListFilters(ctx context.Context, resource string) (*livewire.ListResponse[livewire.FilterItem], error)

ListFilters answers the caller's named filters of a resource (or "name.history"), of every one for "".

func (*Client) Read

func (c *Client) Read(ctx context.Context, path string, parameters Parameters, data any) error

Read sends GET path with parameters and decodes the answer into data (a pointer). A 404 on a filterId the client stored or registered without a name (the server evicted it, D54) is retried once with the filter inline. A refusal is an *Error; a failure to reach the server wraps the http.Client's error.

func (*Client) SaveFilter

func (c *Client) SaveFilter(ctx context.Context, filterRequest *livewire.FilterRequest) (*livewire.FilterItem, error)

SaveFilter stores a filter (D13). An unnamed one is remembered as it was sent, so a read of its id the server evicted sends it inline once (D54).

func (*Client) Upload

func (c *Client) Upload(ctx context.Context, upload *Upload, response any) error

Upload sends a file as a raw body with its Content-Length, so the server streams it to the handler (D60), and decodes the answer into response.

type EndedError

type EndedError struct {
	// CloseCode is 4401 for a close, 401 for a refused handshake.
	CloseCode int
}

EndedError ends the live side of a Client for good: the server closed its socket with 4401 or refused the socket's handshake with 401, so the API key's identity is gone (D7, D66). Every subscription ends with it and Subscribe answers it after; make a new Client once a valid key is at hand.

func (*EndedError) Error

func (e *EndedError) Error() string

Error says that the live side ended.

type Error

type Error struct {
	Status  int
	Code    livewire.Code
	Message string
	Fields  map[string]string
}

Error is a refusal or failure the server answered (section 3): the HTTP status and the error body. Code is from the closed set: a body that is not livewire's (a proxy's error page) gets the code of its status, and an error push, which carries no status, gets the status of its code. Match it with errors.As.

func (*Error) Error

func (e *Error) Error() string

Error names the status, the code and the message.

type Node

type Node[Field ~string] struct {
	// contains filtered or unexported fields
}

Node is one node of a filter document over the fields Field names (D12): a condition, or and, or or not over nodes. Field is the generated field type of one resource, so the nodes of two resources do not mix. Build conditions with the generated <Resource><Field><Operator> functions (or Condition) and combine them with And, Or and Not; the zero Node is no valid node.

func And

func And[Field ~string](nodes ...Node[Field]) Node[Field]

And matches the rows every node matches.

func Condition

func Condition[Field ~string](field Field, operator query.Operator, value any) Node[Field]

Condition is the condition `{field, op, value}`. The generated functions call it with the operand type of the field and operator; called directly, the server checks the operator and the value (400 invalid_filter).

func Not

func Not[Field ~string](node Node[Field]) Node[Field]

Not matches the rows node does not match.

func Or

func Or[Field ~string](nodes ...Node[Field]) Node[Field]

Or matches the rows any node matches.

func (Node[Field]) MarshalJSON

func (n Node[Field]) MarshalJSON() ([]byte, error)

MarshalJSON writes the node as the filter document spells it.

type Options

type Options struct {
	BaseURL               string
	APIKey                string
	HTTP                  *http.Client
	SocketURL             string
	RequestTimeout        time.Duration
	ReconnectDelay        time.Duration
	MaximumReconnectDelay time.Duration
	ConnectFailed         func(err error)
}

Options configures a Client. BaseURL is the API's root, http or https, the registry's BasePath included ("https://example.com/api/v1"): every route is relative to it. APIKey is the key the server's API-key resolver takes (D20), sent as `Authorization: Bearer`. Both are required.

HTTP sends the REST requests (default: a new http.Client without a timeout; bound each request with its context). SocketURL is the socket endpoint, ws or wss (default: BaseURL with the ws scheme and "/socket" appended). RequestTimeout bounds the answer of a subscribe and the socket's handshake (default 30 s); ReconnectDelay is the first wait before a reconnect (default 1 s), doubling with jitter up to MaximumReconnectDelay (default 30 s). No duration may be negative. ConnectFailed, when set, is told every failed connect of the socket (the loop keeps reconnecting), from the loop's goroutine; it must return quickly. A library logs nothing, so this is how a program reports it.

type Parameters

type Parameters map[string]string

Parameters are the parameters of one request in their wire form, as both transports read them: one text per name, a list as one comma-separated text, a filter document as JSON. A route's `{name}` placeholders take their values from it, the rest is the query string (REST) or the frame's params (socket).

func (Parameters) Set

func (p Parameters) Set(name string, value any)

Set puts value under name unless it is the zero value of its type, which a server reads as an absent parameter anyway: a nil pointer, "", 0, false, the nil uuid, the zero time or an empty list. It takes string, bool, int, int64, uuid.UUID and time.Time (RFC 3339 with nanoseconds), each also as a pointer, and []string; another type panics, a programming error of the caller (the generated code passes these types only).

func (Parameters) SetJSON

func (p Parameters) SetJSON(name string, document any) error

SetJSON puts document under name as JSON (an inline filter document).

func (Parameters) SetPath

func (p Parameters) SetPath(name string, value any)

SetPath puts a path parameter under name, its zero value included; the route refuses an empty one.

type Push

type Push[Data any] struct {
	Kind   livewire.PushKind
	Data   Data
	Change *livewire.ListChange
	Err    error
}

Push is one change of a subscription as Subscribe delivers it (section 4). Kind is the push's kind; Data is the read's state after it, so a consumer may ignore the kinds: a snapshot's or a replace's payload, a list with a diff applied. Change is the diff as it arrived, for kind diff only. A push of kind error is the last: Err is an *Error (the server ended the subscription with an error push, or refused or did not answer the subscribe), an *EndedError (the client's live side ended) or the failure to decode the state into Data; the channel closes after it.

type Time

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

Time is the operand of a condition on a time field (D12): an instant, or a time relative to the database clock of the read, which makes a subscription re-run on the safety tick.

func At

func At(instant time.Time) Time

At is the instant, written as RFC 3339 with nanoseconds.

func Relative

func Relative(offset string) Time

Relative is a time relative to the read, such as "-7d" or "2h" (the units of D12); the server checks the form.

func (Time) MarshalJSON

func (t Time) MarshalJSON() ([]byte, error)

MarshalJSON writes the instant as a string, a relative time as `{"relative": offset}`.

type Upload

type Upload struct {
	Path           string
	Parameters     Parameters
	File           livewire.File
	IdempotencyKey string
}

Upload is one upload as Client.Upload sends it (D17): the route, its parameters, the file (its Body is read once and sent raw with its Size as the Content-Length and its ContentType, which must be one the upload declares; Filename is not sent) and the Idempotency-Key ("" for a fresh one).

Jump to

Keyboard shortcuts

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