In this page
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.