Documentation
¶
Index ¶
- Constants
- Variables
- func ConvertLegacyProfile(source []byte) (query.Profile, error)
- func DecodeRequestBody(ctx context.Context, fallback map[string]any) (map[string]any, error)
- func Import(ctx context.Context, source Store, target *DBStore) error
- func IsReservedParam(key string) bool
- func WantsSchema(r *http.Request) bool
- type BodyDecoder
- type ContextProvider
- type DBStore
- func (s *DBStore) Delete(ctx context.Context, name string) error
- func (s *DBStore) Get(ctx context.Context, name string) (query.Profile, error)
- func (s *DBStore) List(ctx context.Context) ([]query.Profile, error)
- func (s *DBStore) Save(ctx context.Context, profile query.Profile) error
- func (s *DBStore) Update(ctx context.Context, originalName string, profile query.Profile, ...) error
- type FileStore
- func (s *FileStore) Delete(_ context.Context, name string) error
- func (s *FileStore) Get(ctx context.Context, name string) (query.Profile, error)
- func (s *FileStore) List(context.Context) ([]query.Profile, error)
- func (s *FileStore) Save(_ context.Context, profile query.Profile) error
- func (s *FileStore) Update(ctx context.Context, originalName string, profile query.Profile, ...) error
- type InspectFlags
- type OpenAPIExtension
- type Options
- type OverlayStore
- func (s *OverlayStore) Delete(ctx context.Context, name string) error
- func (s *OverlayStore) Get(ctx context.Context, name string) (query.Profile, error)
- func (s *OverlayStore) IsVirtual(name string) bool
- func (s *OverlayStore) List(ctx context.Context) ([]query.Profile, error)
- func (s *OverlayStore) Peek(ctx context.Context, name string) (query.Profile, error)
- func (s *OverlayStore) Save(ctx context.Context, profile query.Profile) error
- func (s *OverlayStore) Update(ctx context.Context, name string, profile query.Profile, options UpdateOptions) error
- type ProfileNameConflictError
- type ReconcileFlags
- type ReconcileMaterializeOptions
- type ReconcileSnapshotDescriptor
- type ReconcileSnapshotFlags
- type ReconcileSnapshotProvenance
- type ReplayFlags
- type ReplayResult
- type ResolvedProfile
- type RunFlags
- type RunResult
- type Service
- func (s *Service) Delete(ctx context.Context, name string) error
- func (s *Service) Get(ctx context.Context, name string) (query.Profile, error)
- func (s *Service) GetReconcileSnapshot(ctx context.Context, profileName string, options ReconcileSnapshotFlags) (ReconcileSnapshotDescriptor, error)
- func (s *Service) Handler(prefix string, next http.Handler) (http.Handler, error)
- func (s *Service) Inspect(ctx context.Context, name string, options InspectFlags) (*query.InspectionResult, error)
- func (s *Service) List(ctx context.Context) ([]query.Profile, error)
- func (s *Service) MaterializeReconcile(ctx context.Context, options ReconcileMaterializeOptions) (ReconcileSnapshotDescriptor, error)
- func (s *Service) OpenAPIHandler(root *cobra.Command, config *rpc.Config) (http.Handler, error)
- func (s *Service) Reconcile(ctx context.Context, name string, options ReconcileFlags) (*query.ReconcileResult, error)
- func (s *Service) ReconcileSnapshot(ctx context.Context, name string, options ReconcileFlags) (ReconcileSnapshotDescriptor, error)
- func (s *Service) RegisterClicky()
- func (s *Service) RegisterDynamic(ctx context.Context) error
- func (s *Service) RegisterFamily()
- func (s *Service) Replay(ctx context.Context, name string, options ReplayFlags) (ReplayResult, error)
- func (s *Service) Run(ctx context.Context, name string, options RunFlags) (*RunResult, error)
- func (s *Service) Save(ctx context.Context, body map[string]any, id string) (query.Profile, error)
- func (s *Service) SetSnapshots(service SnapshotService)
- type SnapshotService
- type Store
- type StoreProvider
- type UpdateOptions
- type VirtualStore
Constants ¶
const ProfileNameConflictCode = "PROFILE_NAME_CONFLICT"
const SchemaContentType = "application/schema+json"
Variables ¶
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
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
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 IsReservedParam ¶
reservedParam reports whether a query-string key is a transport concern (paging, format, content-negotiation) rather than a profile filter param.
func WantsSchema ¶
Types ¶
type ContextProvider ¶
type DBStore ¶
type DBStore struct {
// contains filtered or unexported fields
}
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) IsVirtual ¶ added in v0.1.29
func (s *OverlayStore) IsVirtual(name string) bool
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
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
func ResolveWithoutTouch ¶ added in v0.1.29
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
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.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
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) Inspect ¶ added in v0.1.30
func (s *Service) Inspect(ctx context.Context, name string, options InspectFlags) (*query.InspectionResult, error)
func (*Service) MaterializeReconcile ¶ added in v0.1.29
func (s *Service) MaterializeReconcile(ctx context.Context, options ReconcileMaterializeOptions) (ReconcileSnapshotDescriptor, error)
func (*Service) OpenAPIHandler ¶
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 ¶
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) Save ¶
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 UpdateOptions ¶ added in v0.1.29
type UpdateOptions struct {
ReplaceExisting bool
}
Source Files
¶
- action_flags.go
- execute_post.go
- execution.go
- export_format.go
- family.go
- icons.go
- inspect.go
- legacy_migration.go
- legacy_provider.go
- legacy_search.go
- lookup.go
- openapi.go
- openapi_cache.go
- overlay_store.go
- paging.go
- providers.go
- reconcile.go
- reconcile_snapshot.go
- replay.go
- resolver.go
- run.go
- sample.go
- sample_expression.go
- sample_filter_values.go
- sample_jsonpath.go
- schema_request.go
- service.go
- store.go
- store_update.go