Help us shape the future of Go! Take the 2026 Go Developer Survey

thunderstore

package module
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 29 Imported by: 0

README

go-thunderstore

Go


Reads Thunderstore's package index into a compact on-disk listing, with CDN ordering, ror2mm links and manifest.json. Package thunderstore, module github.com/Rethunk-Tech/go-thunderstore.

For installers and mod managers that browse Thunderstore. A community's index is downloaded once, stored as a compact listing, and answered from disk after that: a stale listing is served at once while a rebuild runs behind it. Oversized or hostile answers fail with an error instead of exhausting memory.

Quick start

go get github.com/Rethunk-Tech/go-thunderstore

A worked example and the contracts: HUMANS.md.

Features

  • Client keeps a community's package index on disk, an hour fresh, and refreshes it in the background.
  • Index, Client.Each and Client.Find read it whole or a package at a time; repeated dependency names are held once.
  • Client.Versions answers a package's version list from a small side file, without loading the listing.
  • Uploader uploads a package zip and submits it under a team, as Thunderstore's CLI does.
  • Every answer is size-capped and every index-named chunk address is checked.
  • ParseLink, CDNOrder, OnHost and ParseManifest cover ror2mm:// links, download hosts and manifest.json.

Documentation

Doc Audience
HUMANS.md Running and using the package
AGENTS.md Internals, invariants, and why
CONTRIBUTING.md Process — commits, tests, docs
SECURITY.md Vulnerability disclosure
CHANGELOG.md Release notes

License

MIT — see LICENSE. © 2026 Rethunk.Tech, LLC.

Documentation

Overview

Package thunderstore reads Thunderstore's package index for installers and mod managers. A Client downloads a community's index chunk by chunk into a compact on-disk listing (dependency names held once), answers from it at once however old, and refreshes it in the background. The package also parses ror2mm install links and manifest.json, and orders Thunderstore's download hosts.

Index

Constants

View Source
const (
	// BaseURL is the real site.
	BaseURL = "https://thunderstore.io"
)
View Source
const Scheme = "ror2mm"

Scheme is the one-click install link scheme.

Variables

View Source
var CDNs = []string{
	"https://ccdn.thunderstore.io",
	"https://gcdn.thunderstore.io",
	"https://hcdn-1.hcdn.thunderstore.io",
}

CDNs are Thunderstore's download hosts in the order they are tried.

Functions

func CDNOrder

func CDNOrder(raw string, hosts []string, pinned, lastGood string) []string

CDNOrder is the hosts to try for a download address: only the pinned one when pinned names a host of hosts, else the last one that worked first and the rest in order. Nil when the address is not on one of hosts.

func EachStored

func EachStored(path string, fn func(*Package)) error

EachStored hands fn each package of the stored listing at path in turn, keeping none of them; Dependencies of those packages is empty. A package the site listed under two case-folded ids is handed twice: Package.Outranks picks.

func OnHost

func OnHost(raw, base string) (string, bool)

OnHost is raw with its scheme and host replaced by those of base, the same path and query.

Types

type Category added in v0.3.0

type Category struct {
	Slug, Name string
}

Category is one of a community's package categories. Slug is what Submit files a package under.

type Client

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

Client reads Thunderstore's package indexes through an on-disk cache. A stored listing is answered at once however old: past an hour a rebuild runs in the background, so only a community with nothing stored waits on the download. Use one Client per cache folder; it is safe for concurrent use.

func New

func New(cfg Config) (*Client, error)

New returns a Client for cfg.

func (*Client) Categories added in v0.3.0

func (c *Client) Categories(ctx context.Context, community string) ([]Category, error)

Categories lists the community's package categories. A failed request is a *httpkit.StatusError, as for the index.

func (*Client) Each

func (c *Client) Each(ctx context.Context, community string, fn func(*Package)) error

Each calls fn with each package of the community's listing, from memory when Index holds it, else a package at a time from disk. Packages read from disk carry no Dependencies, and a package listed under two case-folded ids comes twice: Package.Outranks picks.

func (*Client) Find

func (c *Client) Find(ctx context.Context, community, owner, name string) (p Package, ok bool, err error)

Find is the package owner-name in the community's listing, read a package at a time without loading the listing; ok is false when it is not listed. The package carries no Dependencies.

func (*Client) Held added in v0.1.1

func (c *Client) Held(community string) bool

Held reports whether Index holds the community's listing in memory.

func (*Client) Index

func (c *Client) Index(ctx context.Context, community string) (*Index, error)

Index is the community's package listing, loaded whole and kept in memory until nothing has asked for it for ten minutes. A listing holds tens of megabytes for a large community: prefer Each or Find for one pass or one package.

func (*Client) ListingFile

func (c *Client) ListingFile(ctx context.Context, community string) (string, error)

ListingFile is the path of the community's stored listing, built first when the community has none. The file is a private format: read it with EachStored.

func (*Client) Refresh

func (c *Client) Refresh(community string)

Refresh rebuilds the community's stored listing now, without waiting for the refresh window; readers keep answering from the stored one until the rebuild lands.

func (*Client) Snapshot

func (c *Client) Snapshot(ctx context.Context, community string) (*Index, error)

Snapshot is the community's index for one pass of a check that runs often: the copy Index holds when it holds one, else read from disk and left to the collector.

func (*Client) Stored added in v0.1.1

func (c *Client) Stored(_ context.Context, community string, fn func(*Package)) (bool, error)

Stored calls fn with each package of the community's listing without touching the network: from memory when Index holds it, else a package at a time from the stored file, however old it is. It reports false, having called nothing, when the community has no listing stored. It never downloads, builds or starts a refresh. Packages from memory carry Dependencies; those read from the file do not, and a case-folded twin may come twice, as with Each.

func (*Client) StoredVersions added in v0.1.1

func (c *Client) StoredVersions(community, owner, name string) ([]string, bool, error)

StoredVersions is Versions without the network: the package's version numbers from the side file or the stored listing, however old, and false when the community has nothing stored or the package is not in it. It never downloads, builds or starts a refresh, and writes nothing.

func (*Client) Versions

func (c *Client) Versions(ctx context.Context, community, owner, name string) ([]string, error)

Versions lists the package's version numbers in the community's index, newest first. The answer comes from a small side file when the stored index is the one it was read from, however old that is: the refresh a stale index needs runs in the background, not on the caller's path. A miss reads the stored listing a package at a time.

func (*Client) Wait

func (c *Client) Wait()

Wait blocks until the background refreshes started so far have finished, so a caller may remove the cache folder.

type Config

type Config struct {
	// CacheDir is required. The stored listings live in its thunderstore folder.
	CacheDir string
	// HTTP sends every request; nil means http.DefaultClient. Redirects from https to another scheme are refused.
	HTTP *http.Client
	// BaseURL is the site; empty means BaseURL. Chunk addresses the index names must be on this host or on
	// thunderstore.io.
	BaseURL string
	// UserAgent is sent with every request.
	UserAgent string
	// Now is the clock the hourly refresh reads; nil means time.Now.
	Now func() time.Time
	// Refreshed is told the community when a background refresh stored a changed listing. It runs on the refresh's
	// goroutine.
	Refreshed func(community string)
	// Built is told the community and the path of each listing newly stored, before Index sees it; the path stays valid
	// for EachStored until the next build replaces it. It runs on the build's goroutine.
	Built func(community, path string)
}

Config is what a Client is built from.

type Dependency added in v0.3.0

type Dependency struct {
	Namespace, Name, Version string
}

Dependency is one entry of a manifest's dependencies: a package version named "Namespace-Name-1.2.3".

func ParseDependency added in v0.3.0

func ParseDependency(s string) (Dependency, error)

ParseDependency splits "Namespace-Name-1.2.3". The namespace is the text before the first hyphen and the version the text after the last; the name between them holds no hyphen, since Thunderstore names use underscores.

func (Dependency) String added in v0.3.0

func (d Dependency) String() string

String is the "Namespace-Name-Version" form ParseDependency reads.

type Index

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

Index is one community's package listing, loaded whole. Dependency names, version numbers, owners, categories and repositories are held once however many packages repeat them.

func (*Index) Each

func (x *Index) Each(fn func(*Package))

Each calls fn with each package in listing order.

func (*Index) Find

func (x *Index) Find(owner, name string) (Package, bool)

Find is the package owner-name, compared case-insensitively.

func (*Index) Packages

func (x *Index) Packages() []Package

Packages is every package, one per case-folded id. The slice and what it holds are shared: do not modify them.

type Manifest

type Manifest struct {
	Name         string   `json:"name"`
	Version      string   `json:"version_number"`
	Namespace    string   `json:"namespace"`
	Author       string   `json:"author"`
	Dependencies []string `json:"dependencies"`
}

Manifest is the part of a package's manifest.json that installers read.

func ParseManifest

func ParseManifest(b []byte) (Manifest, error)

ParseManifest reads a manifest.json. Thunderstore accepts manifests saved with a UTF-8 BOM, and many packages ship one, but encoding/json refuses it.

type Package

type Package struct {
	Owner     string `json:"owner"`
	Name      string `json:"name"`
	Summary   string `json:"summary"`
	Icon      string `json:"icon"`
	URL       string `json:"url"`
	Updated   string `json:"updated"`
	Created   string `json:"created"`
	Rating    int    `json:"rating"`
	Downloads int    `json:"downloads"`
	// Repo is the GitHub repository the newest version's website links, as "owner/repo"; empty when it links none.
	Repo string `json:"repo,omitempty"`
	// Donation is the author's donation page when it is an http or https address.
	Donation string `json:"donation,omitempty"`
	// Deprecated packages are still listed so a dependency on one resolves.
	Deprecated bool `json:"hidden,omitempty"`
	// Adult marks a package the site flags as NSFW.
	Adult      bool      `json:"adult,omitempty"`
	Versions   []Version `json:"versions"`
	Categories []string  `json:"categories,omitempty"`
	// contains filtered or unexported fields
}

Package is one package listing, its versions newest first as the site orders them.

func (Package) Dependencies

func (p Package) Dependencies(v Version) []string

Dependencies is the "Owner-Name-1.2.3" strings one of p's versions depends on. A package decoded by Client.Each or Client.Find carries none: only an Index holds the names.

func (Package) ID

func (p Package) ID() string

ID is the package's lowercased "owner-name", the key the site treats names by.

func (Package) Outranks

func (p Package) Outranks(q Package) bool

Outranks reports whether p wins over q, a case-folded twin listed after it: a live listing over a deprecated one, then the more recently updated, then the first.

func (Package) VersionNumbers

func (p Package) VersionNumbers() []string

VersionNumbers is the package's version numbers, newest first.

type Ref

type Ref struct {
	Namespace, Name, Version string
}

Ref names one package version in a ror2mm link.

func ParseLink(raw string) (Ref, error)

ParseLink reads ror2mm://v1/install/thunderstore.io/<namespace>/<name>/<version>/. Namespace and name are letters, digits and underscores, the version is three dot-separated numbers; anything else, including a query, fragment or userinfo, is refused.

type Uploader added in v0.2.0

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

Uploader publishes a package zip to Thunderstore the way its CLI does: the zip goes to the site's storage in the parts the site asks for, then is submitted under a team in a community.

func NewUploader added in v0.2.0

func NewUploader(client *http.Client, site, token string) *Uploader

NewUploader returns an Uploader for the site (such as BaseURL) that authenticates with token. A nil client means http.DefaultClient.

func (*Uploader) Submit added in v0.2.0

func (u *Uploader) Submit(ctx context.Context, uuid, team, community string, categories []string) (string, error)

Submit publishes the upload uuid under team in community, filed under categories, and returns the package's page URL in that community, or "" when the site names none.

func (*Uploader) Upload added in v0.2.0

func (u *Uploader) Upload(ctx context.Context, zipPath string) (string, error)

Upload sends the zip at zipPath to Thunderstore's storage and returns the upload's UUID, to pass to Submit. A failed part aborts the upload.

type Version

type Version struct {
	Number string
	Size   int64
	// Created is when the version was published, as Unix seconds; 0 when the site gave no readable date.
	Created int64
	// contains filtered or unexported fields
}

Version is one published version of a package.

func (Version) MarshalJSON

func (v Version) MarshalJSON() ([]byte, error)

MarshalJSON writes the compact form the stored listing uses.

func (*Version) UnmarshalJSON

func (v *Version) UnmarshalJSON(b []byte) error

UnmarshalJSON reads the compact form the stored listing uses.

Jump to

Keyboard shortcuts

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