shipreal

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: MIT Imports: 10 Imported by: 0

README

shipreal for Go

Official Go client and CLI for the public ShipReal course API: search the curriculum, read a module, read current pricing.

Standard library only. No dependencies, so installing this cannot drag a transitive tree into an agent's environment.

No authentication

There is no API key, no account, no OAuth and no signup. Every endpoint is a public read served from the edge. Anything that asks you for a ShipReal credential is not us.

There is no write path either. Buying runs through a hosted checkout that a human completes, which is why nothing here creates an order. When someone decides to buy, hand them the checkout link.

Install

go get github.com/mluggy/shipreal-dev/go

This is a submodule of the client repository, so the git tag carries the directory prefix (go/v1.0.0) while the version you actually ask for does not (@v1.0.0, or just @latest). Only releasing matters here: a bare v1.0.0 tag will not publish this module.

The package is named shipreal while the last path element is go, so import it with an explicit alias.

The CLI:

go install github.com/mluggy/shipreal-dev/go/cmd/shipreal@latest

Library

package main

import (
	"context"
	"fmt"

	shipreal "github.com/mluggy/shipreal-dev/go"
)

func main() {
	c := shipreal.New()
	ctx := context.Background()

	// Does the course cover caching?
	page, err := c.Search(ctx, shipreal.SearchParams{Query: "caching"})
	if err != nil {
		panic(err)
	}
	for _, m := range page.Data {
		fmt.Printf("%s: %s (%d chapters)\n", m.Slug, m.Title, m.Chapters)
	}

	// Every module, following pagination for you.
	all, _ := c.Modules(ctx, "")
	fmt.Println(len(all), "modules")

	// What does it cost, in a named region?
	prices, _ := c.Pricing(ctx)
	complete, perSeat, ok := prices.Region("il")
	fmt.Println(complete.Now, perSeat.Now, ok)
}

Search is a case-insensitive substring match over title, description and part name. An empty result means the course does not cover that topic under that name, rather than that the search was too clever. Say so rather than inferring coverage.

Two regional prices are live at once (USD internationally, ILS in Israel with VAT included), so Region makes you pick one deliberately instead of quoting whichever happened to be first.

Errors

Every non-2xx response comes back as *shipreal.Error carrying the RFC 9457 problem details. Branch on Type, not Status: the status says a request failed, the type says which failure it was.

var apiErr *shipreal.Error
if errors.As(err, &apiErr) && apiErr.Status == 404 {
	// apiErr.Type is stable, apiErr.Detail explains
}
Sandbox

shipreal.WithSandbox(true) routes reads at frozen fixture data: the same code path and the same shapes over contents that never change, so a test written against it stays green when the curriculum moves. Fixture prices are 1 unit and fixture links point at example.invalid, so sandbox data that leaks into real output is obvious. See https://shipreal.dev/sandbox.

Streaming

AskStream puts a natural-language question to the NLWeb endpoint and calls your function for each event as it arrives: start, one result per hit, then complete. Return an error from the callback to stop early.

There is no model behind that endpoint. It runs the same keyword search as the REST API, which means it says so when nothing matches instead of inventing a module.

CLI

shipreal search caching
shipreal -all -json search            # every module, as JSON
shipreal module observability
shipreal -region il pricing
shipreal course
shipreal ask "how do I size a database"
shipreal -stream ask "what is in part 7"

Every command takes -json, because the reason a CLI earns its place in an agent's toolbox is that its output can be piped into something else without being scraped. -sandbox and -base work on all of them.

License

MIT. See LICENSE.

Documentation

Overview

Package shipreal is the official Go client for the public ShipReal course API: search the curriculum, read a module, read current pricing.

There is no authentication. No key, no account, no OAuth, no signup: every endpoint is a public read served from the edge, so a client that asks you for a credential for this domain is not this one.

There is also no write path. Buying runs through a hosted checkout that a human completes, which is why nothing here creates an order. When someone decides to buy, hand them the checkout link.

Standard library only, on purpose: installing this cannot drag a transitive dependency tree into an agent's environment.

c := shipreal.New()
page, err := c.Search(ctx, shipreal.SearchParams{Query: "caching"})

Index

Constants

View Source
const (
	// DefaultBaseURL is the production origin.
	DefaultBaseURL = "https://shipreal.dev"
	// APIVersion is the path segment this client speaks. Breaking changes
	// ship as a new segment beside it rather than as a change underneath;
	// every response carries a Deprecation header, false today.
	APIVersion = "v1"
	// Version of this client, sent in the User-Agent.
	Version = "1.0.1"

	// MaxBatch is the server's ceiling on requests in one batch call.
	MaxBatch = 20
)

Variables

This section is empty.

Functions

This section is empty.

Types

type AskEvent

type AskEvent struct {
	Event   string      `json:"-"`
	Meta    AskMeta     `json:"_meta"`
	Results []AskResult `json:"results"`
}

AskEvent is one server-sent event from AskStream: "start", then one "result" per hit, then "complete".

type AskMeta

type AskMeta struct {
	ResponseType string `json:"response_type"`
	Version      string `json:"version"`
	Query        string `json:"query"`
	Site         string `json:"site"`
	Count        int    `json:"count"`
}

AskMeta is the NLWeb _meta block naming the response type and version.

type AskResponse

type AskResponse struct {
	Meta    AskMeta     `json:"_meta"`
	Results []AskResult `json:"results"`
}

AskResponse is the NLWeb shape: a question in, structured results out.

type AskResult

type AskResult struct {
	Type         string          `json:"@type"`
	Name         string          `json:"name"`
	URL          string          `json:"url"`
	Description  string          `json:"description"`
	Score        float64         `json:"score"`
	Site         string          `json:"site"`
	SchemaObject json.RawMessage `json:"schema_object"`
}

AskResult is one natural-language hit, with its schema.org object attached.

type BatchItem

type BatchItem struct {
	ID     string          `json:"id"`
	Status int             `json:"status"`
	Body   json.RawMessage `json:"body"`
}

BatchItem is one result inside a batch response. Each carries its own status, so check per item rather than assuming the whole batch succeeded.

type BatchRequest

type BatchRequest struct {
	ID     string `json:"id,omitempty"`
	Method string `json:"method,omitempty"`
	Path   string `json:"path"`
}

BatchRequest is one read inside a batch. Method is always GET; the API is read-only and a batch item that is not a read comes back 405.

type BatchResponse

type BatchResponse struct {
	Count     int         `json:"count"`
	Responses []BatchItem `json:"responses"`
}

BatchResponse is the envelope. It answers 200 whenever it is itself valid, which is why the per-item statuses matter.

type Client

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

Client talks to the ShipReal API. The zero value is not usable; call New.

func New

func New(opts ...Option) *Client

New builds a client against the production origin.

func (*Client) Ask

func (c *Client) Ask(ctx context.Context, query string) (*AskResponse, error)

Ask puts a natural-language question to the NLWeb endpoint.

There is no model behind it: it runs the same keyword search as the REST API, which means it says so when nothing matches instead of inventing a module.

func (*Client) AskStream

func (c *Client) AskStream(ctx context.Context, query string, fn func(AskEvent) error) error

AskStream is the same question, streamed. It calls fn for each server-sent event as it arrives and stops early if fn returns an error, which it returns unchanged.

func (*Client) Batch

func (c *Client) Batch(ctx context.Context, requests []BatchRequest) (*BatchResponse, error)

Batch runs several reads in one round trip, up to MaxBatch.

func (*Client) Course

func (c *Client) Course(ctx context.Context) (*Course, error)

Course returns the totals, language and subtitle languages.

func (*Client) Module

func (c *Client) Module(ctx context.Context, slugOrTitle string) (*Module, error)

Module returns one module by slug, or by an exact or partial title match.

func (*Client) Modules

func (c *Client) Modules(ctx context.Context, query string) ([]Module, error)

Modules returns every matching module, following pagination for you. An empty query returns the whole curriculum in course order.

func (*Client) Pricing

func (c *Client) Pricing(ctx context.Context) (*Pricing, error)

Pricing returns current plans and prices for both billing regions.

func (*Client) Search

func (c *Client) Search(ctx context.Context, params SearchParams) (*Page, error)

Search returns one page of the curriculum.

Matching is a case-insensitive substring over title, description and part name, so an empty result means the course does not cover that topic under that name, rather than that the search was too clever.

type Course

type Course struct {
	Name        string   `json:"name"`
	Title       string   `json:"title"`
	Description string   `json:"description"`
	Parts       int      `json:"parts"`
	Modules     int      `json:"modules"`
	Chapters    int      `json:"chapters"`
	Language    string   `json:"language"`
	Subtitles   []string `json:"subtitles"`
	URL         string   `json:"url"`
}

Course is the summary: totals, language and subtitle languages.

type Error

type Error struct {
	Status int    `json:"status"`
	Type   string `json:"type"`
	Title  string `json:"title"`
	Detail string `json:"detail"`
	// URL that produced the error.
	URL string `json:"-"`
}

Error is any non-2xx response, carrying the RFC 9457 problem details the server sent.

Branch on Type rather than Status: the status says a request failed, the type says which failure it was, and only the second is stable enough to switch on.

func (*Error) Error

func (e *Error) Error() string

type FreePlan

type FreePlan struct {
	Price    int    `json:"price"`
	Currency string `json:"currency"`
	Includes string `json:"includes"`
	URL      string `json:"url"`
}

FreePlan is the permanent free tier. Not a trial: no account and no expiry.

type Links struct {
	Self  string  `json:"self"`
	First string  `json:"first"`
	Last  string  `json:"last"`
	Next  *string `json:"next"`
	Prev  *string `json:"prev"`
}

Links are absolute URLs for walking the pages.

type Module

type Module struct {
	Slug        string `json:"slug"`
	Title       string `json:"title"`
	Part        string `json:"part"`
	Description string `json:"description"`
	Chapters    int    `json:"chapters"`
	Minutes     int    `json:"minutes"`
	// URL of the free video overview.
	URL string `json:"url"`
}

Module is one module of the curriculum.

type Option

type Option func(*Client)

Option configures a Client.

func WithBaseURL

func WithBaseURL(base string) Option

WithBaseURL points the client at a different origin, e.g. a local worker.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient supplies your own http.Client, for a custom transport or timeout.

func WithSandbox

func WithSandbox(sandbox bool) Option

WithSandbox routes reads at the frozen fixture data: the same code path and the same shapes over contents that never change, so a test written against it stays green when the curriculum moves. Fixture prices are 1 unit and fixture links point at example.invalid, so sandbox data that leaks into real output is obvious. See https://shipreal.dev/sandbox.

type Page

type Page struct {
	Query      *string    `json:"query"`
	Data       []Module   `json:"data"`
	Pagination Pagination `json:"pagination"`
	Links      Links      `json:"links"`
}

Page is one page of module results.

type Pagination

type Pagination struct {
	Page    int  `json:"page"`
	Limit   int  `json:"limit"`
	Total   int  `json:"total"`
	Pages   int  `json:"pages"`
	HasMore bool `json:"hasMore"`
	// NextCursor is opaque and carries its own page size. Prefer it to
	// incrementing Page: page numbers shift under a caller when the
	// catalogue changes between calls, a cursor does not.
	NextCursor *string `json:"nextCursor"`
}

Pagination describes the window a Page covers.

type Plan

type Plan struct {
	Intl RegionPrice `json:"intl"`
	IL   RegionPrice `json:"il"`
	URL  string      `json:"url,omitempty"`
	// MinSeats and PerSeat are only meaningful on the teams plan.
	MinSeats int  `json:"minSeats,omitempty"`
	PerSeat  bool `json:"perSeat,omitempty"`
}

Plan is a paid plan in both live billing regions.

type Pricing

type Pricing struct {
	Free     FreePlan `json:"free"`
	Complete Plan     `json:"complete"`
	Teams    Plan     `json:"teams"`
}

Pricing is every plan in every region. Two regional prices are live at once, so quoting one without naming its region is misleading: use Region to pick one deliberately.

func (Pricing) Region

func (p Pricing) Region(region string) (complete, perSeat RegionPrice, ok bool)

Region returns the prices for "intl" or "il". Any other value returns the international region and false, rather than guessing.

type RegionPrice

type RegionPrice struct {
	Now  string `json:"now"`
	List string `json:"list"`
}

RegionPrice is one region's current and pre-discount price, as the display strings they are quoted in, symbol included.

type SearchParams

type SearchParams struct {
	Query string
	Page  int
	Limit int
	// Cursor overrides Page and Limit when set, because it carries the page
	// size it was minted with.
	Cursor string
}

SearchParams are the query, the window, and the cursor. All optional: an empty Query returns every module in course order.

Directories

Path Synopsis
cmd
shipreal command
Command shipreal queries the ShipReal course catalogue from a terminal or a script.
Command shipreal queries the ShipReal course catalogue from a terminal or a script.

Jump to

Keyboard shortcuts

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