profiles

package
v0.1.31 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: Apache-2.0 Imports: 43 Imported by: 0

Documentation

Index

Constants

View Source
const ProfileNameConflictCode = "PROFILE_NAME_CONFLICT"
View Source
const SchemaContentType = "application/schema+json"

Variables

View Source
var ErrSnapshotNotFound = errors.New("reconciliation snapshot not found")

ErrSnapshotNotFound distinguishes an id that never existed from one that expired. The two get different HTTP statuses because the browser says different things: one is "come back with a real link", the other is "this aged out, run it again".

Functions

func ConvertLegacyProfile added in v0.1.29

func ConvertLegacyProfile(source []byte) (query.Profile, error)

ConvertLegacyProfile turns one legacy trace-profile document into a Profile.

FileStore migrates a directory in place, which is the right shape for user files on disk. An application whose built-in profiles are embedded has no directory to migrate and needs the same mapping applied at load time, so the conversion is available on its own rather than only as a side effect of opening a store.

func DecodeRequestBody added in v0.1.29

func DecodeRequestBody(ctx context.Context, fallback map[string]any) (map[string]any, error)

DecodeRequestBody is the BodyDecoder for a service hosted by clicky's RPC layer: it reads the in-flight request's JSON body, falling back to the arguments clicky already decoded when there is no request in context (a CLI invocation, or a call made directly in a test).

Every embedder needs exactly this, so it ships here rather than being reimplemented — slightly differently — in each one.

func Import

func Import(ctx context.Context, source Store, target *DBStore) error

func IsReservedParam

func IsReservedParam(key string) bool

reservedParam reports whether a query-string key is a transport concern (paging, format, content-negotiation) rather than a profile filter param.

func WantsSchema

func WantsSchema(r *http.Request) bool

Types

type BodyDecoder

type BodyDecoder func(context.Context, map[string]any) (map[string]any, error)

type ContextProvider

type ContextProvider func() dbcontext.Context

type DBStore

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

func NewDBStore

func NewDBStore(db *gorm.DB) (*DBStore, error)

func (*DBStore) Delete

func (s *DBStore) Delete(ctx context.Context, name string) error

func (*DBStore) Get

func (s *DBStore) Get(ctx context.Context, name string) (query.Profile, error)

func (*DBStore) List

func (s *DBStore) List(ctx context.Context) ([]query.Profile, error)

func (*DBStore) Save

func (s *DBStore) Save(ctx context.Context, profile query.Profile) error

func (*DBStore) Update added in v0.1.29

func (s *DBStore) Update(ctx context.Context, originalName string, profile query.Profile, options UpdateOptions) error

type FileStore

type FileStore struct{ Dir string }

func NewFileStore

func NewFileStore(dir string) (*FileStore, error)

func (*FileStore) Delete

func (s *FileStore) Delete(_ context.Context, name string) error

func (*FileStore) Get

func (s *FileStore) Get(ctx context.Context, name string) (query.Profile, error)

func (*FileStore) List

func (s *FileStore) List(context.Context) ([]query.Profile, error)

func (*FileStore) Save

func (s *FileStore) Save(_ context.Context, profile query.Profile) error

func (*FileStore) Update added in v0.1.29

func (s *FileStore) Update(ctx context.Context, originalName string, profile query.Profile, options UpdateOptions) error

type InspectFlags added in v0.1.30

type InspectFlags struct {
	Params  []string `flag:"param" help:"Profile parameter as key=value (repeatable)"`
	Refresh bool     `flag:"refresh" help:"Refresh cached inspection metadata"`
}

func (InspectFlags) ClickyActionFlags added in v0.1.30

func (InspectFlags) ClickyActionFlags()

type OpenAPIExtension added in v0.1.29

type OpenAPIExtension func(*rpc.OpenAPISpec)

type Options

type Options struct {
	Store             StoreProvider
	Context           ContextProvider
	DecodeBody        BodyDecoder
	Snapshots         SnapshotService
	OpenAPIExtensions []OpenAPIExtension
}

type OverlayStore added in v0.1.29

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

func NewOverlayStore added in v0.1.29

func NewOverlayStore(base Store, virtual VirtualStore) (*OverlayStore, error)

func (*OverlayStore) Delete added in v0.1.29

func (s *OverlayStore) Delete(ctx context.Context, name string) error

func (*OverlayStore) Get added in v0.1.29

func (s *OverlayStore) Get(ctx context.Context, name string) (query.Profile, error)

func (*OverlayStore) IsVirtual added in v0.1.29

func (s *OverlayStore) IsVirtual(name string) bool

func (*OverlayStore) List added in v0.1.29

func (s *OverlayStore) List(ctx context.Context) ([]query.Profile, error)

func (*OverlayStore) Peek added in v0.1.29

func (s *OverlayStore) Peek(ctx context.Context, name string) (query.Profile, error)

func (*OverlayStore) Save added in v0.1.29

func (s *OverlayStore) Save(ctx context.Context, profile query.Profile) error

func (*OverlayStore) Update added in v0.1.29

func (s *OverlayStore) Update(ctx context.Context, name string, profile query.Profile, options UpdateOptions) error

type ProfileNameConflictError added in v0.1.29

type ProfileNameConflictError struct {
	Source string
	Target string
}

func (ProfileNameConflictError) Error added in v0.1.29

func (e ProfileNameConflictError) Error() string

type ReconcileFlags added in v0.1.29

type ReconcileFlags struct {
	Dest          string   `flag:"dest" help:"Profile to reconcile against; required unless the source profile stores a reconcile block"`
	KeyCEL        string   `flag:"key-cel" help:"CEL expression evaluated against a row on either side to derive the join key"`
	KeyColumns    []string `` /* 126-byte string literal not displayed */
	TimeColumn    string   `flag:"time-column" help:"Row key holding each side's event time; defaults to the profile's timestamp column"`
	KeyFrom       string   `flag:"key-from" help:"Reconcile keys at or after this one; empty starts at the first key"`
	KeyTo         string   `flag:"key-to" help:"Reconcile keys before this one; empty runs to the last key"`
	SourceFilters []string `flag:"source-filter" help:"Source profile filter as key=value (repeatable)"`
	DestFilters   []string `flag:"dest-filter" help:"Destination profile filter as key=value (repeatable)"`
	Outcome       string   `flag:"outcome" help:"Return one result outcome: matched, only_source, only_dest, or ambiguous"`
	SnapshotAge   string   `flag:"snapshot-age" help:"Idle expiry for the reconciliation snapshot; cannot exceed the server maximum"`
}

ReconcileFlags are the flags of the profiles `reconcile` action. The entity id is the source profile; every flag overrides the corresponding field of the reconcile the source profile stores, so a saved join runs with no flags at all and an ad-hoc one supplies its own.

func (ReconcileFlags) ClickyActionFlags added in v0.1.29

func (ReconcileFlags) ClickyActionFlags()

type ReconcileMaterializeOptions added in v0.1.29

type ReconcileMaterializeOptions struct {
	SnapshotID string   `flag:"snapshot" help:"Reconciliation snapshot id"`
	Profile    string   `flag:"profile" help:"Snapshot profile to transform or project"`
	CEL        string   `flag:"cel" help:"CEL expression returning the rows to materialize"`
	Columns    []string `flag:"column" help:"Output column to include, in order (repeatable)"`
}

func (ReconcileMaterializeOptions) ClickyActionFlags added in v0.1.29

func (ReconcileMaterializeOptions) ClickyActionFlags()

type ReconcileSnapshotDescriptor added in v0.1.29

type ReconcileSnapshotDescriptor struct {
	ID           string               `json:"id"`
	Connection   string               `json:"connection"`
	ConnectionID string               `json:"connection_id"`
	Profile      string               `json:"profile"`
	Surface      string               `json:"surface"`
	URL          string               `json:"url"`
	Columns      []query.ColumnDef    `json:"columns"`
	RowCount     int                  `json:"row_count"`
	Stats        query.ReconcileStats `json:"stats"`
	Source       string               `json:"source"`
	Dest         string               `json:"dest"`

	// Reconcile is how this snapshot was produced — the config the run used and
	// what each side asked its backend. It is stored with the rows so a results
	// page opened days later shows the query that produced them rather than one
	// re-derived from a profile that has since changed.
	Reconcile *ReconcileSnapshotProvenance `json:"reconcile,omitempty"`

	SourceLimited bool          `json:"source_truncated,omitempty"`
	DestLimited   bool          `json:"dest_truncated,omitempty"`
	CreatedAt     time.Time     `json:"created_at"`
	IdleAge       time.Duration `json:"idle_age"`
	ExpiresAt     time.Time     `json:"expires_at"`
}

func (ReconcileSnapshotDescriptor) ColumnNames added in v0.1.29

func (d ReconcileSnapshotDescriptor) ColumnNames() []string

type ReconcileSnapshotFlags added in v0.1.29

type ReconcileSnapshotFlags struct {
	SnapshotID string `flag:"snapshot" help:"Reconciliation snapshot id"`
}

func (ReconcileSnapshotFlags) ClickyActionFlags added in v0.1.29

func (ReconcileSnapshotFlags) ClickyActionFlags()

type ReconcileSnapshotProvenance added in v0.1.29

type ReconcileSnapshotProvenance struct {
	Config    query.ReconcileConfig      `json:"config"`
	Execution *query.ReconcileProvenance `json:"execution,omitempty"`
}

ReconcileSnapshotProvenance pairs what a run was asked to do with what it did. The two are separate fields rather than one flattened object because they answer different questions and only the first can be replayed.

type ReplayFlags added in v0.1.29

type ReplayFlags struct {
	Params  []string `flag:"param" help:"Profile filter param as key=value (repeatable)"`
	Select  []string `flag:"select" help:"Pick the row to replay as column=value (repeatable); must match exactly one row"`
	Target  string   `flag:"target" help:"Send to this target: a connection reference or an absolute URL"`
	Method  string   `flag:"method" help:"Override the resolved HTTP method"`
	URL     string   `flag:"url" help:"Override the resolved request URL or path"`
	Body    string   `flag:"body" help:"Override the request body" stdin:"true"`
	Header  []string `flag:"header" help:"Set a request header as key=value (repeatable)"`
	Execute bool     `flag:"execute" help:"Actually send the request; without this the action only previews it"`
	Hash    string   `flag:"preview-hash" help:"Refuse to send unless the rebuilt request still hashes to this value"`
}

ReplayFlags are the flags of the profiles `replay` action. They become cobra flags on the CLI and the request body over HTTP, from one declaration.

func (ReplayFlags) ClickyActionFlags added in v0.1.29

func (ReplayFlags) ClickyActionFlags()

type ReplayResult added in v0.1.29

type ReplayResult struct {
	Preview  *query.ReplayPreview       `json:"preview"`
	Executed *query.ReplayExecuteResult `json:"executed,omitempty"`
}

ReplayResult carries the preview always and the response only when the request was actually sent, so preview and execute share one response type.

func (ReplayResult) Pretty added in v0.1.29

func (r ReplayResult) Pretty() api.Text

type ResolvedProfile added in v0.1.23

type ResolvedProfile struct {
	Profile           query.Profile
	ConnectionProfile string
}

func Resolve added in v0.1.23

func Resolve(ctx context.Context, store Store, name string) (ResolvedProfile, error)

func ResolveWithoutTouch added in v0.1.29

func ResolveWithoutTouch(ctx context.Context, store Store, name string) (ResolvedProfile, error)

type RunFlags added in v0.1.29

type RunFlags struct {
	Limit  int      `flag:"limit" help:"Rows per page; defaults to the profile's page size"`
	Offset int      `flag:"offset" help:"Skip this many rows before the page (requires a declared order)"`
	Cursor string   `flag:"cursor" help:"Resume after the position a previous page reported (requires a declared order)"`
	All    bool     `flag:"all" help:"Read forward through every page, stopping at the profile's export ceiling"`
	Params []string `flag:"param" help:"Profile filter param as key=value (repeatable)"`
}

RunFlags are the flags of the profiles `run` action: which rows of a profile to read and where to resume from.

They are the CLI's half of the same paging contract the HTTP export serves, and are validated against the same profile RowLimits — a page a caller may not request over HTTP is not one it may request from a terminal either.

func (RunFlags) ClickyActionFlags added in v0.1.29

func (RunFlags) ClickyActionFlags()

type RunResult added in v0.1.29

type RunResult struct {
	Profile string      `json:"profile"`
	Rows    []query.Row `json:"rows"`

	Limit  int `json:"limit,omitempty"`
	Offset int `json:"offset,omitempty"`

	Total      *query.Total `json:"total,omitempty"`
	HasMore    bool         `json:"hasMore,omitempty"`
	NextCursor query.Cursor `json:"nextCursor,omitempty"`

	// Truncated reports that the read stopped short of the whole result — the
	// export ceiling, or a cap the backend applied. A partial answer that says
	// nothing is the failure this whole contract exists to remove.
	Truncated bool `json:"truncated,omitempty"`
	// contains filtered or unexported fields
}

RunResult is one page of a profile, plus everything needed to ask for the next one. The paging facts travel with the rows rather than being printed and discarded, so `--format json` carries the cursor a script needs to resume.

func (*RunResult) Pretty added in v0.1.29

func (r *RunResult) Pretty() api.Text

Pretty renders the rows under a line saying exactly what was read and how to read the rest. A page that does not say it is a page is the terminal's version of a truncated export.

func (*RunResult) Render added in v0.1.29

func (r *RunResult) Render(format string) (string, error)

Render formats the page in the given clicky format.

func (*RunResult) Table added in v0.1.29

func (r *RunResult) Table() api.TextTable

Table renders the page through the profile's declared columns.

type Service

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

func New

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

func (*Service) Delete

func (s *Service) Delete(ctx context.Context, name string) error

func (*Service) Get

func (s *Service) Get(ctx context.Context, name string) (query.Profile, error)

func (*Service) GetReconcileSnapshot added in v0.1.29

func (s *Service) GetReconcileSnapshot(
	ctx context.Context, profileName string, options ReconcileSnapshotFlags,
) (ReconcileSnapshotDescriptor, error)

GetReconcileSnapshot reads a stored reconciliation by id.

The statuses matter more than usual here: the browser opens this URL from a bookmark or a shared link, and "this aged out, run it again" and "that is not a snapshot" are different things to tell someone. The codes match the ones this server already uses for an expired snapshot connection.

func (*Service) Handler

func (s *Service) Handler(prefix string, next http.Handler) (http.Handler, error)

func (*Service) Inspect added in v0.1.30

func (s *Service) Inspect(ctx context.Context, name string, options InspectFlags) (*query.InspectionResult, error)

func (*Service) List

func (s *Service) List(ctx context.Context) ([]query.Profile, error)

func (*Service) MaterializeReconcile added in v0.1.29

func (s *Service) MaterializeReconcile(ctx context.Context, options ReconcileMaterializeOptions) (ReconcileSnapshotDescriptor, error)

func (*Service) OpenAPIHandler

func (s *Service) OpenAPIHandler(root *cobra.Command, config *rpc.Config) (http.Handler, error)

func (*Service) Reconcile added in v0.1.29

func (s *Service) Reconcile(ctx context.Context, name string, options ReconcileFlags) (*query.ReconcileResult, error)

Reconcile runs two profiles and joins their results on a shared key, reporting which records made it across and how long they took.

func (*Service) ReconcileSnapshot added in v0.1.29

func (s *Service) ReconcileSnapshot(ctx context.Context, name string, options ReconcileFlags) (ReconcileSnapshotDescriptor, error)

func (*Service) RegisterClicky

func (s *Service) RegisterClicky()

registerProfileEntity registers the YAML-backed profile entity with list + full CRUD on the CLI and over HTTP. Create/Update use the context-aware handlers so the nested profile body (provider/params/columns) survives via rpc.RequestFromContext instead of the executor's flag-flattening. Execution (GET /{name}?params) is served by execHandler and schemas by schemaHandler.

It also registers the profile family, which is the route each individual profile answers on — see RegisterFamily. The family reads the store through the same provider this service does, so swapping the YAML store for the database one at serve time needs no re-registration.

func (*Service) RegisterDynamic

func (s *Service) RegisterDynamic(ctx context.Context) error

RegisterDynamic registers one clicky dynamic entity per stored profile, which is what puts each profile in the generated CLI: GenerateCLI walks the entity registry, so a profile absent from it has no `query profile-<slug>` command.

The registry is a startup snapshot and cannot be otherwise — GenerateCLI has already run by the time a profile is created. That is what RegisterFamily is for: over HTTP a profile is resolved per request and needs no entry here. The two are not alternatives, they serve different consumers.

func (*Service) RegisterFamily added in v0.1.29

func (s *Service) RegisterFamily()

RegisterFamily routes every profile — including one created a moment ago — at {prefix}/profile/{name}.

The entity -> Cobra -> RPC -> mux pipeline is one snapshot taken at startup, so a profile that comes into being while the server runs has no route and no way to acquire one. That is why x-clicky-lookup.url pointed at {prefix}/profile/profile-<slug> and nothing answered there: the request fell through the whole middleware chain to a mux with no such pattern. A family registers one route for the shape and resolves the instance in front of every request, so nothing has to be re-registered or invalidated when a profile is saved.

Paged is deliberately nil, so execHandler keeps serving the rows. clicky's paged transport refuses the clicky-json envelope the interactive table asks for, bounds pages by the library's limits rather than the profile's, and cannot turn a PDF over its ceiling into a status the caller can read. What it does own — resolving ?__lookup=filters from a spec that did not exist at startup — is exactly what was missing, and execHandler already delegates that request rather than answering it.

func (*Service) Replay added in v0.1.29

func (s *Service) Replay(ctx context.Context, name string, options ReplayFlags) (ReplayResult, error)

Replay resolves a profile, runs it, turns the selected row back into the outbound HTTP request its replay block describes, and sends it only when asked to.

Preview is the default because replay re-drives a real side effect into a real system. The two-step preview/execute handshake, guarded by the preview hash, is what stops a caller from approving one request and sending another after the underlying data moved.

func (*Service) Run added in v0.1.29

func (s *Service) Run(ctx context.Context, name string, options RunFlags) (*RunResult, error)

Run reads one page of a profile, or every page under --all.

func (*Service) Save

func (s *Service) Save(ctx context.Context, body map[string]any, id string) (query.Profile, error)

saveProfile decodes the (nested) request body into a Profile and persists it. On update, a path id supplies the profile name when the body omits it.

func (*Service) SetSnapshots added in v0.1.29

func (s *Service) SetSnapshots(service SnapshotService)

type SnapshotService added in v0.1.29

type SnapshotService interface {
	Create(context.Context, *query.ReconcileResult, time.Duration) (ReconcileSnapshotDescriptor, error)
	Materialize(context.Context, ReconcileMaterializeOptions) (ReconcileSnapshotDescriptor, error)
	// Describe returns a stored snapshot's base descriptor. A materialized
	// projection stays reachable through Materialize, which is idempotent by
	// fingerprint — there is deliberately only one read path.
	Describe(context.Context, string) (ReconcileSnapshotDescriptor, error)
}

type StoreProvider

type StoreProvider func() (Store, error)

type UpdateOptions added in v0.1.29

type UpdateOptions struct {
	ReplaceExisting bool
}

type VirtualStore added in v0.1.29

type VirtualStore interface {
	Store
	Peek(context.Context, string) (query.Profile, error)
	IsVirtual(string) bool
}

Jump to

Keyboard shortcuts

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