Skip to content

Examples and Recipes

Igor Sazonov edited this page Sep 8, 2026 · 1 revision

This page collects practical starting points for common goldsky-go tasks. Each example uses the SDK’s explicit context and error model. Before adapting any mutation, decide how your application will reconcile a timeout or duplicate attempt.

The repository also includes eight runnable, focused programs under examples/. They are useful for verifying credentials and seeing imports in a complete package.1

Example directory Requirement Purpose
01-list-pipelines GOLDSKY_API_KEY Lists one page of pipelines.
02-paginate-subgraphs GOLDSKY_API_KEY Walks subgraph pages safely.
03-validate-pipeline GOLDSKY_API_KEY Validates a pipeline without creation.
04-create-pipeline API key and GOLDSKY_RUN_MUTATIONS=1 Creates a guarded test pipeline.
05-graphql-query API key and subgraph identifiers Queries a private GraphQL endpoint.
06-edge-rpc Project API key and Edge API key Calls eth_blockNumber.
07-verify-webhook GOLDSKY_WEBHOOK_SECRET Starts a local webhook verifier.
08-handle-errors API key and pipeline name Classifies several error types.

Run one from the repository root:

go run ./examples/01-list-pipelines

Recipe: inject the client into an application service

Create the SDK client once, then pass it to components that need it. This keeps credentials and transport configuration centralized.

type PipelineReader struct {
    client *goldsky.Client
}

func (r PipelineReader) Names(ctx context.Context) ([]string, error) {
    page, err := r.client.Pipelines.List(ctx, goldsky.ListPipelinesOptions{PageSize: 100})
    if err != nil {
        return nil, err
    }
    names := make([]string, 0, len(page.Data))
    for _, pipeline := range page.Data {
        names = append(names, pipeline.Name)
    }
    return names, nil
}

Use a parent request or job context at the boundary, and apply a deadline there. This enables cancellation to reach the underlying HTTP request.

Recipe: wait until a subgraph becomes query-ready

A deployed subgraph can exist before it is healthy and synchronized. The following bounded poller inspects a version or tag and exits when it reaches the readiness condition that this application requires.

func waitForSubgraph(ctx context.Context, client *goldsky.Client, name, version string) (goldsky.Subgraph, error) {
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()

    for {
        page, err := client.Subgraphs.GetVersion(ctx, name, version)
        if err != nil {
            return goldsky.Subgraph{}, err
        }
        for _, item := range page.Data {
            if item.Status == goldsky.SubgraphStatusActive &&
                item.Health == goldsky.SubgraphHealthHealthy &&
                item.Synced {
                return item, nil
            }
        }

        select {
        case <-ctx.Done():
            return goldsky.Subgraph{}, ctx.Err()
        case <-ticker.C:
        }
    }
}

Use a caller deadline such as 10 minutes for a deployment workflow. Do not treat ACTIVE alone as proof that a subgraph has finished indexing.

Recipe: deploy, verify, then promote a tag

A tag promotion is a simple release pattern for stable GraphQL consumers.

const (
    name       = "dex-analytics"
    version    = "1.2.0"
    releaseTag = "prod"
)

bundle, err := os.Open("build.zip")
if err != nil {
    return err
}
defer bundle.Close()

if _, err := client.Subgraphs.Deploy(ctx, name, version, goldsky.DeploySubgraphOptions{
    Bundle: bundle, BundleFilename: "build.zip",
}); err != nil {
    return err
}

if _, err := waitForSubgraph(ctx, client, name, version); err != nil {
    return err
}

if _, err := client.Subgraphs.SetTag(ctx, name, releaseTag, goldsky.SetSubgraphTagRequest{
    TargetVersion: version,
}); err != nil {
    return err
}

The bundle upload is intentionally single-attempt. If it returns a transport error, call GetVersion before deploying again. See Subgraphs.

Recipe: classify a REST error at a boundary

Centralize error classification so that HTTP handlers, workers, and CLI commands have consistent behavior.

func classifyGoldskyError(err error) (code int, message string) {
    if err == nil {
        return http.StatusOK, "ok"
    }
    if p := goldsky.AsProblem(err); p != nil {
        switch {
        case p.IsValidation():
            return http.StatusBadRequest, "Goldsky rejected the request"
        case p.IsAuthentication(), p.IsPermission():
            return http.StatusBadGateway, "Goldsky authorization failed"
        case p.IsNotFound():
            return http.StatusNotFound, "Goldsky resource not found"
        case p.IsRateLimited(), p.IsServerError():
            return http.StatusServiceUnavailable, "Goldsky is temporarily unavailable"
        }
    }
    if goldsky.AsTransport(err) != nil {
        return http.StatusGatewayTimeout, "Goldsky request could not complete"
    }
    return http.StatusInternalServerError, "unexpected Goldsky integration error"
}

Do not send raw Goldsky Detail, raw response bodies, or credentials to an untrusted caller. Preserve diagnostic detail in secure internal logs.

Recipe: consume every list page without storing it all

The typed pagers provide bounded-memory iteration. Process each resource before fetching the next page.

pager := client.Pipelines.NewPipelinePager(goldsky.ListPipelinesOptions{PageSize: 200})
for {
    page, err := pager.NextPage(ctx)
    if err != nil {
        return err
    }
    for _, pipeline := range page.Data {
        if err := reconcilePipeline(ctx, pipeline); err != nil {
            return err
        }
    }
    if !page.HasMore() {
        break
    }
}

This pattern is preferable to accumulating an unbounded slice in a controller or scheduled job.

Recipe: GraphQL with variables and typed data

response, err := client.GraphQL.QueryPrivate(ctx, projectID, "dex-analytics", "prod", goldsky.GraphQLRequest{
    OperationName: "TradesAfter",
    Query: `query TradesAfter($timestamp: BigInt!) {
      trades(where: { timestamp_gt: $timestamp }, first: 100) {
        id timestamp amount
      }
    }`,
    Variables: map[string]any{"timestamp": "1700000000"},
})
if err != nil {
    return err
}
if response.HasErrors() {
    return fmt.Errorf("GraphQL error: %s", response.Errors[0].Message)
}

var payload struct {
    Trades []struct {
        ID        string `json:"id"`
        Timestamp string `json:"timestamp"`
        Amount    string `json:"amount"`
    } `json:"trades"`
}
if err := json.Unmarshal(response.Data, &payload); err != nil {
    return err
}

Use the exact scalar types from the subgraph schema. The example’s BigInt is only illustrative; query the deployed subgraph’s schema to confirm the field and scalar names.

Recipe: batch independent RPC reads

var chainID, latestBlock string
responses, err := client.RPC.Batch(ctx, 1, []goldsky.RPCBatchCall{
    {Method: "eth_chainId", Result: &chainID},
    {Method: "eth_blockNumber", Result: &latestBlock},
})
if err != nil {
    return err
}
for _, response := range responses {
    if response.Error != nil {
        return response.Error
    }
}
fmt.Println(chainID, latestBlock)

The results remain aligned with the request order even if the server returns its batch response in another order. See Edge RPC.

Recipe: accept a webhook durably

func (s *Server) goldskyWebhook(w http.ResponseWriter, r *http.Request) {
    if !goldsky.VerifyWebhookRequest(r, s.webhookSecret) {
        http.Error(w, "unauthorized", http.StatusUnauthorized)
        return
    }

    var event Event
    if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&event); err != nil {
        http.Error(w, "bad request", http.StatusBadRequest)
        return
    }
    if err := s.eventStore.InsertIfAbsent(r.Context(), event.ID, event); err != nil {
        http.Error(w, "temporary failure", http.StatusServiceUnavailable)
        return
    }
    w.WriteHeader(http.StatusNoContent)
}

The durable InsertIfAbsent step is the important part. It allows a retry to be treated as a successful duplicate and stops the client from receiving an acknowledgement before the work is protected.

References

Clone this wiki locally