Tether

Go API reference

The public engine, context, storage, and profiler APIs at a glance.

Import the engine as github.com/recodeorg/tether. Call every Register*, Set*, and CreateTable method during startup, before you serve requests. They aren't safe to call while the engine is handling traffic.

Engine

APIPurpose
NewEngine(db *gorm.DB, options ...EngineOption) (*Engine, error)Installs tracking and starts background work; SQLite or PostgreSQL required
CreateTable(model interface{}) errorRuns GORM AutoMigrate for a model
RegisterQuery(name string, fn func(*QueryCtx) (any, error), opts ...Option)Registers a read-only live query
RegisterMutation(name string, fn func(*MutationCtx) (any, error), opts ...Option)Registers a writable command
RegisterGuard(name string, fn func(*GuardCtx) (any, error), opts ...GuardOption)Registers a reactive permission check or authorized data lookup
Internal() OptionHides a query or mutation from client calls
SetAuth(auth Auth)Installs the token verifier
SetAllowedOrigins(origins []string)Sets exact allowed browser origins for WebSockets and storage
SetCheckOrigin(check func(*http.Request) bool)Sets a custom origin policy
Handle(w http.ResponseWriter, r *http.Request)Serves the WebSocket endpoint
SetStorage(adapter storage.StorageAdapter, basePath string, defaults ...storage.UploadOption) errorConfigures storage once
StorageHandler(w http.ResponseWriter, r *http.Request)Serves the full storage path prefix
RegisterCron(name, expression, mutation string, params map[string]interface{}) (string, error)Creates or updates a named recurring task
Profiler() *ProfilerReturns the engine profiler
EphemeralID() stringIdentifies this engine process instance, not a stable application ID
Close()Stops engine background work; safe to call repeatedly

Registering two queries, two mutations, or two guards with the same name panics. EngineOption and GuardOption are placeholders for future options; Tether currently doesn't define any.

There's no ExecuteQuery or ExecuteMutation API for calling handlers from your own code. Only the scheduler and profiler can run mutations internally. To share logic, use ordinary Go functions.

Handler contexts

CapabilityQueryCtxMutationCtxGuardCtx
DB *gorm.DBRead-onlyRead/writeRead-only
Params map[string]interface{}Client JSONClient or scheduled JSONGuard params
Auth *AuthCtxIdentity and guardsIdentity and guards for client callsIdentity only
TrackCollection(table, column string, value interface{})Yes—Yes
TrackTable(table string)Yes—Yes
Storage *StorageCtxDownload URL onlyUpload, download, delete—
Scheduler *SchedulerCtx—Yes—
Profiler *Profiler—Yes—

JSON numbers decode as float64. Always use the two-value form of type assertions (v, ok := ctx.Params["x"].(string)) on client params. The single-value form panics on unexpected input.

Auth

Implement:

type Auth interface {
    VerifyToken(context.Context, *gorm.DB, string) (string, time.Time, error)
}

VerifyToken receives an empty string when the client hasn't set a token. Return no error to accept anonymous visitors, or an error to require sign-in.

  • AuthCtx.GetIdentity() (string, error) returns "" for anonymous callers and ErrNoCaller in scheduled or internal executions. In a query, it makes the result user-specific and prevents sharing it across users.
  • AuthCtx.ExecuteGuard(name string, params map[string]interface{}) (interface{}, error) runs a guard and returns its JSON-normalized result. It isn't available inside guards or in executions without a caller.

See Authentication and Guards for details.

Scheduler

  • RunAfter(delay time.Duration, mutation string, params map[string]interface{}) (string, error)
  • Cancel(taskID string) bool

These are fields on SchedulerCtx. Tasks are saved outside the current database transaction and run without a caller. See Scheduling.

Storage

Fields on StorageCtx:

  • GetUploadURL(opts ...storage.UploadOption) (storage.UploadInfo, error)
  • GetDownloadURL(fileID string) (string, error)
  • DeleteFile(fileID string) error

UploadInfo has FileID and UploadURL fields serialized as fileID and uploadURL. Options are storage.WithMaxBytes(int64) and storage.WithExpiresIn(time.Duration).

Adapters: local.New(directory string) (*local.Storage, error) and s3.New(context.Context, s3.Config) (*s3.Storage, error). Import their packages under github.com/recodeorg/tether/storage/. See File storage.

Profiler

  • Start() error and StartWithCallback(interval time.Duration, mutation string) error
  • IsActive() bool, Stop(), Add(Metric)
  • DumpMetricsAndFlush() []Metric
  • tether.SanitizeMetrics([]Metric) ProfileReport
  • ProfileReport.Slowest(n int) []ProfiledExecution and ProfileReport.String() string

See Profiling for buffer limits and capture lifecycle.

Exported operational models and errors

TetherTask, TetherStorage, and TetherDownloadToken are the models for tables the engine manages. Read them for inspection and debugging, but don't modify task claims or tokens yourself.

ErrorReturned when
tether.ErrEngineClosedYou schedule or configure work after Close
tether.ErrNoCallerGetIdentity() is called in a mutation with no caller, such as a scheduled one
tether.ErrProfilerRunningYou start a profiler that's already running

Check the errors returned during startup, migration, storage, and scheduling instead of discarding them.

Last updated on

On this page