8.3.101-stable Switch to dev

registry

Client and data model for a GreyCat package registry. Query a registry, resolve a version to its archives, download them, and publish new ones – from GCL, speaking the registry’s own types rather than restating them.

@library("registry", "0.0.0");

This library does not implement a registry. It is the client half: the shapes that cross the wire, and a Registry type that calls the endpoints. The server is the registry app itself.

Quick start

var reg = Registry::official();

var latest = reg.latest_version("mqtt", "stable");   // "8.3.1-stable"
if (latest != null) {
    var found = reg.resolve("mqtt", latest, Target::x64_linux);
    for (_, a in found.artifacts) {
        reg.download(a, "dist/${a.filename}");
    }
}

Pointing it somewhere

Call Registry
Registry::official() https://registry.greycat.io, read-only
Registry::official_with(token) the same, authenticated (token may be null)
Registry::from_env() whatever this machine is configured for
Registry { base: url, token: t } anything else – a local instance, a staging one

base is the registry root with no trailing slash; Registry::OFFICIAL holds the official one. token is optional and only needed for writes and for archives on a gated tier. It is sent as a bare Authorization header – no Bearer prefix, which is what GreyCat’s own auth expects.

from_env is the configured one

It reads $GREYCAT_REGISTRY and $GREYCAT_REGISTRY_TOKEN, the same two the runtime resolves for greycat install. You rarely set them yourself: the runtime settles them before any GCL runs, including from the settings file in the install home, so a configured machine reaches this with nothing exported by hand.

# <GREYCAT_HOME|~/.greycat>/registries      (0600 -- it holds credentials)
default = https://registry.greycat.io
https://registry.greycat.io = <token>
https://staging.example.com = <other token>

The origin is settled first (flag, then environment, then the project .env, then default), and the token is then looked up for exactly that origin. So overriding GREYCAT_REGISTRY swaps the credential with it, and from_env is the only constructor whose token is guaranteed to belong to its base. Pairing a hand-built base with $GREYCAT_REGISTRY_TOKEN sends one registry’s credential to another.

It returns null when the variable is unset, empty or legacy – all three select get.greycat.io, whose protocol is paths rather than the JSON-RPC this client speaks. Decide what that means for your script rather than being pointed somewhere silently:

var reg = Registry::from_env() ?? Registry::official();

Reading

Call Returns
resolve(name, version, target) ResolveResult – the install set for one version
resolve_many(requests, target) Array<ResolveResult>, in request order
latest_version(name, branch) String?, e.g. "8.3.1-stable"
artifact_url(name, version, target) String?, relative to base
latest_artifact_url(name, branch, target) the two calls above in one round-trip
list_all_versions(name) Array<BranchVersions> – every branch, every version
get_library(name) PackageView? – the catalog entry
download(artifact, path) writes the bytes to path

resolve is the install call. It answers with the noarch archive plus the target-specific one, whichever exist, so a library that ships both is fetched in a single round-trip:

var found = reg.resolve("std", "8.3.1-stable", Target::x64_linux);
println(found.status);              // "ok"
println(found.artifacts.size());    // 1 or 2
println(found.yanked);              // reported, not enforced

A lookup miss is a status, not an exception – a package manager hits “not found” on its normal path, and a batched resolve needs a verdict per entry:

status Meaning
ok artifacts holds the install set
invalid_version not M.m.p-BRANCH
unknown_library no such library
unknown_branch the version’s branch suffix names no branch of this library
unknown_version the branch exists, this version does not
no_artifacts the version exists but ships neither noarch nor this target

download is the opposite: it throws on any status but 200, and removes the partial file, so a gated archive the caller may not read fails loudly instead of writing an error body to disk.

Publishing

Publishing is two steps, and the order matters: the bytes go up first, into a scratch directory the server derives from the call arguments, and are then declared. No path ever crosses the wire, so a caller can only ever publish from its own space.

var reg = Registry::official_with(ci_token);

var linux = reg.upload("ci", Kind::lib, "mylib", "1.2.3-stable", Target::x64_linux, "dist/x64-linux.zip");
var noarch = reg.upload("ci", Kind::lib, "mylib", "1.2.3-stable", Target::noarch, "dist/noarch.zip");

reg.publish(Kind::lib, "mylib", "1.2.3-stable", Array<ArtifactInput> { linux, noarch }, null);

That trailing null is the README override, and leaving it unset is the normal case: the registry reads README.md out of the archive itself, which is where zip -ry lib/<name> has already put it. Send a string only to override it, which is what an asset needs – a bare .vsix, .jar or wheel has no such layout and yields nothing to read.

upload hashes the local file, PUTs it to /files/<user>/_tmp/<kind>/<name>/<branch>/<M.m.p>/<filename> and hands back the ArtifactInput describing it – filename, target and the sha256 the server verifies before committing. user is the caller’s own identity name: the scratch space is rooted in that user’s /files/ directory.

Publishing is immutable: republishing an existing (kind, name, M.m.p-branch) is rejected, so a client that caches by version can trust the bytes never change under it. The scratch directory is wiped by the server whether the publish lands or throws.

Versions

A version is MAJOR.MINOR.PATCH-BRANCH, e.g. 7.8.162-stable, 1.0.0-dev, 0.6.4-my-feature. The branch is the suffix, so no call takes it separately when it already has a version.

var sv = Semver::parse("1.2.3-my-feature")!!;
sv.major;   // 1
sv.branch;  // "my-feature" -- everything after the patch, separators and all
Semver::key(sv.major, sv.minor, sv.patch);                    // "1.2.3"
Semver::format(sv.major, sv.minor, sv.patch, sv.branch);      // "1.2.3-my-feature"
Semver::greater(1, 2, 0, 1, 1, 9);                            // true

parse returns null for anything malformed, including a branch that could not be a directory name: the branch names a directory under every version that carries it, so it is limited to A-Za-z0-9._- and may not be . or ...

The data model

Type What it is
Kind lib (installed by greycat install) or asset (anything else the registry hosts)
Target the platform an archive targets: noarch, x64-linux, arm64-apple, …
PackageView a catalog entry: kind, name, tier, summary, preferred latest, every branch
BranchView one branch with its latest version, version count and a ready download URL
VersionView M.m.p-branch, publish time, publisher, yank flag, the targets it ships
ArtifactView one published file: filename, target, relative url, size, sha256
TierView the /files/<name> bucket the archives live in, and whether it is gated
OwnerView an identity id and the name it resolved to
ArtifactInput what publish is given for each file
ResolveResult the outcome of one resolve: status, yanked, artifacts

Target is a closed enum: the registry serves a fixed set of supported platforms. Supporting a new one means adding an entry here and publishing this library, not sending a label the server has never heard of.

kind_dir(Kind::lib);          // "lib"
parse_kind("asset");          // Kind::asset, null for anything else
parse_target("x64-linux");    // Target::x64_linux, null for anything else
default_target();             // Target::x64_linux

Gated tiers

A package sits in a distribution tier, and the tier is the /files/<tier>/ bucket its archives live in. The catalog is public either way – a gated package is listed, described and linked to anyone who asks. Only the bytes are restricted, to the users granted read on that bucket:

var pkg = reg.get_library("solar")!!;
pkg.tier.name;    // "pro"
pkg.tier.gated;   // true -- readable entry, restricted archives

Downloading one without a grant throws from download. That is the tier working, not a bug.