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 ¶
- Constants
- func NewIdempotencyKey() string
- func NewUUIDv4() uuid.UUID
- func NewUUIDv7() uuid.UUID
- func Strings[Text ~string](values []Text) []string
- func Subscribe[Data any](ctx context.Context, client *Client, target string, parameters Parameters) (<-chan Push[Data], error)
- type Call
- type CallOptions
- type Client
- func (c *Client) Batch(ctx context.Context, calls []livewire.BatchCall, options *CallOptions) ([]livewire.BatchResult, error)
- func (c *Client) Call(ctx context.Context, call *Call, response any) error
- func (c *Client) DeleteFilter(ctx context.Context, id string) error
- func (c *Client) Export(ctx context.Context, path string, parameters Parameters, writer io.Writer) (int, error)
- func (c *Client) ListFilters(ctx context.Context, resource string) (*livewire.ListResponse[livewire.FilterItem], error)
- func (c *Client) Read(ctx context.Context, path string, parameters Parameters, data any) error
- func (c *Client) SaveFilter(ctx context.Context, filterRequest *livewire.FilterRequest) (*livewire.FilterItem, error)
- func (c *Client) Upload(ctx context.Context, upload *Upload, response any) error
- type EndedError
- type Error
- type Node
- type Options
- type Parameters
- type Push
- type Time
- type Upload
Examples ¶
Constants ¶
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 NewUUIDv7 ¶
NewUUIDv7 is a fresh UUID version 7, monotonic within the process: the client id of a create that takes one (D18).
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 (*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 ¶
Call sends a mutation with its Idempotency-Key and decodes the answer into response (nil, or no content, decodes nothing).
func (*Client) DeleteFilter ¶
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 ¶
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).
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.
type Error ¶
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.
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 Condition ¶
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 (Node[Field]) MarshalJSON ¶
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 ¶
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 Relative ¶
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 ¶
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).