Tya v0.26 Specification
This document is the specification for Tya v0.26 after v0.25 bit-level operations and byte sequences.
Theme
Tya v0.26 introduces external package management.
A Tya project declares its dependencies in tya.toml, runs tya install
to resolve and pin a coherent set of versions in tya.lock, and from then
on all builds load the same packages reproducibly. This is the foundation
for a Tya ecosystem; it does not yet open a central registry.
The design is modeled on Gleam’s gleam.toml + manifest.toml and
Bundler’s Gemfile + Gemfile.lock, simplified for Tya’s current size.
Goals
- Add a
tya.tomlmanifest as the project description file. - Add a
tya.locklockfile recording reproducible resolutions. - Add Bundler-style single-version-per-package resolution with backtracking.
- Support git and path dependency sources.
- Wire dependencies into module resolution before bundled stdlib lookup.
- Add
tya install,tya update,tya add,tya remove,tya outdatedCLI commands. - Avoid a central registry, native dependencies, workspaces, and feature flags in v0.26.
Included in v0.26
v0.26 includes all v0.25 behavior and adds:
tya.tomlmanifest formattya.locklockfile format- version constraint syntax (
^x.y.z,~x.y.z,>= x.y.z, < a.b.c, exactx.y.z) - backtracking dependency resolver
- git source (clone + checkout tag/rev) with caching
- path source (use a local directory directly)
- import resolution lookup that includes manifest dependencies
tya install/tya update/tya add/tya remove/tya outdated
Not Included in v0.26
v0.26 does not include:
- a central package registry
- workspaces / monorepos with multiple packages
- optional dependencies and feature flags
- native (C library) dependencies
- patches / overrides
- semver-aware type-identity checking
- offline-only mode
- vendoring (committing the dependency directory) as a first-class flow
tya.toml (manifest)
tya.toml lives at the project root and is human-edited.
name = "my-app"
version = "0.1.0"
description = "My Tya application"
authors = ["komagata <[email protected]>"]
license = "MIT"
[dependencies]
colorhash = "^1.2.0"
markdown = { version = ">= 0.5.0, < 1.0.0" }
fancy = { git = "https://github.com/foo/fancy", tag = "v2.1.0" }
shared = { path = "../shared" }
[dev-dependencies]
mock = "^0.3.0"
Required fields
name: package name. Must be a valid Tya identifier (lowercase letters, digits, and_; starts with a letter).version: SemVer 2.0.0 string (e.g."0.1.0").
Optional fields
description: short string.authors: array of strings.license: SPDX identifier or short string.[dependencies]: dict of dependency entries used for build and run.[dev-dependencies]: dict of dependency entries used only for tests.
Dependency entry forms
Short form:
colorhash = "^1.2.0"
is equivalent to
colorhash = { version = "^1.2.0" }
Long forms select a source:
# git source
fancy = { git = "https://github.com/foo/fancy", tag = "v2.1.0" }
fancy = { git = "https://github.com/foo/fancy", branch = "main" }
fancy = { git = "https://github.com/foo/fancy", rev = "abc1234" }
# local path source
shared = { path = "../shared" }
# explicit version constraint with git source
fancy = { version = "^2.1.0", git = "https://github.com/foo/fancy" }
Exactly one of tag, branch, rev is required for git when no version
is given. When version is given, the resolver still locks a specific git
revision in the lockfile.
Version constraint syntax
| Form | Meaning |
|---|---|
"1.2.3" |
exact 1.2.3 |
"^1.2.3" |
>=1.2.3, <2.0.0 (caret) |
"~1.2.3" |
>=1.2.3, <1.3.0 (tilde) |
">= 1.2.3" |
open lower bound |
">= 1.2.3, < 2.0.0" |
comma-separated AND |
Caret of a 0.x.y version is >=0.x.y, <0.(x+1).0 (matching Cargo’s
zero-major rule).
tya.lock (lockfile)
tya.lock is auto-generated by tya install and tya update. It records
the exact versions chosen, their sources, and content checksums so that
every machine resolves the same dependency graph.
# This file is auto-generated. Do not edit manually.
version = 1
[[package]]
name = "colorhash"
version = "1.2.5"
source = "git"
git = "https://github.com/foo/colorhash"
rev = "abc1234567890abc..."
checksum = "sha256:9f3..."
dependencies = []
[[package]]
name = "markdown"
version = "0.5.3"
source = "git"
git = "https://github.com/baz/markdown"
rev = "def4567890..."
checksum = "sha256:7a1..."
dependencies = ["colorhash"]
[[package]]
name = "shared"
version = "0.0.0"
source = "path"
path = "../shared"
checksum = ""
dependencies = []
[requirements]
colorhash = { version = "^1.2.0" }
markdown = { version = ">= 0.5.0, < 1.0.0" }
shared = { path = "../shared" }
Fields per package entry
name,version: SemVersource: one of"git","path"- For git:
git(URL) andrev(the locked commit hash);tagorbranchare recorded only if they were used to selectrev. - For path:
path(relative to the manifest) checksum:sha256:...of the unpacked source tree (path source omits this and uses an empty string).dependencies: array of names of other locked packages this one depends on.
Lockfile lifecycle
tya installreadstya.tomland writes a freshtya.lockif missing, or honors the existing lock if it already satisfies the manifest.tya updateignores the lock and re-resolves from the manifest.tya update <pkg>re-resolves only the named package, holding others fixed.- The lockfile must be checked into version control. Reviewers can read the diff to see version movement.
Resolution
The resolver is single-version-per-package: in any successful resolution there is exactly one chosen version of each named package across the whole dependency graph.
It uses backtracking:
- Build a dependency graph from
tya.toml. - For each unresolved package, list candidate versions sorted from highest to lowest that satisfy the union of requirements seen so far.
- Pick the highest candidate, recursively resolve its declared dependencies.
- On a conflict (no candidate satisfies the merged constraints), backtrack to the previous decision and try the next candidate.
- If all options are exhausted, report an unsolvable diamond conflict with the full path of conflicting requirements.
Equality for source identity is (name, source-info):
- For git,
(name, git-url)must agree across all references; otherwise the resolver reports a name conflict. - For path,
(name, absolute-path)must agree.
Sources
git source
example = { git = "https://github.com/foo/example", tag = "v1.0.0" }
Behavior:
- Clone the repo into
.tya/cache/git/<host>/<owner>/<repo>(shared cache per Tya install). - Check out the chosen
tag,branch, orrev. - The resolver records the resolved commit
revintya.lock. - Subsequent
tya installuses the cached clone and the lockedrev.
The package directory must contain its own tya.toml at the repo root.
path source
shared = { path = "../shared" }
Behavior:
- Read the package directly from the given directory each build (no copy).
- The directory must contain
tya.tomlat its root. - Path sources do not have a checksum recorded; their content is whatever the local directory contains at build time.
- Path sources do not participate in version constraints; their
versionfield comes from the linkedtya.toml.
Package layout
A package directory looks like:
<package>/
├── tya.toml # required, declares name + version + dependencies
├── src/ # required, contains public modules
│ └── <name>.tya
├── tests/ # optional
└── README.md # optional
Public modules
import <name> from a downstream package resolves to
<package>/src/<name>.tya. A package with name foo must expose a top-level
module foo defined in src/foo.tya. Sub-modules at src/foo/bar.tya
are imported as import foo/bar (read as foo/bar).
(The slash form foo/bar is parsed but is also a future-compatibility
hook; v0.26 ships only the top-level import foo form, with sub-paths
deferred to a later minor version unless straightforward to include.)
Manifest of a dependency
A dependency’s tya.toml is read to discover its own dependencies (the
[dependencies] section). Dev-dependencies of dependencies are ignored;
only the top-level project’s dev-dependencies are loaded.
Import resolution
In v0.26, the import lookup order becomes:
- The importing file’s directory.
- Manifest-declared dependencies (via
tya.lock). - Directories listed in
TYA_PATH. - The bundled
stdlib/shipped with Tya.
Step 2 is new in v0.26. The resolver maps a package name to its source
directory under .tya/packages/<name>-<version>/ (git source) or directly
to the path (path source).
Existing stdlib imports such as import string continue to resolve through
step 4. Stdlib names are never overridden by user manifests in v0.26.
CLI
tya install
Reads tya.toml. If tya.lock exists and satisfies the manifest, uses it
as-is, downloading any missing packages. Otherwise resolves from scratch
and writes a new lockfile.
tya install
Exit code is 0 on success. On unsolvable resolution it exits non-zero with a diagnostic listing the conflicting requirements.
tya update
Recomputes the lockfile from the manifest. Pass a package name to update only that package and its transitive dependencies; pass nothing to update everything.
tya update # update all
tya update colorhash # update colorhash, hold others
tya add
Adds an entry under [dependencies] in tya.toml, then runs install.
tya add colorhash # latest published version, caret constraint
tya add colorhash ^1.2.0 # explicit constraint
tya add fancy --git https://github.com/foo/fancy --tag v2.1.0
tya add shared --path ../shared
tya add --dev mock ^0.3.0 # add to [dev-dependencies]
tya remove
Removes an entry from [dependencies] (or [dev-dependencies]), updates
the lockfile.
tya remove colorhash
tya outdated
Reports packages whose latest available version is greater than the currently locked version.
tya outdated
For git sources, “latest” means the highest semver tag reachable from the
configured branch (or the current rev if no branch is configured). For
path sources, the local manifest is consulted directly.
Cache and storage
Tya stores resolved packages in two places:
.tya/cache/git/...— shared git clones, keyed by source URL. Re-used across projects on the same machine if the user shares a~/.tya/cachevia theTYA_CACHEenvironment variable..tya/packages/<name>-<version>/— per-project unpacked package directories used at build time.
Both directories are safe to delete; tya install recreates them.
.tya/packages/ and .tya/cache/ should be added to .gitignore.
tya.lock should be committed.
Diagnostics
v0.26 implementations should report source-oriented errors for:
- malformed
tya.toml(unknown fields, wrong types, invalid SemVer) - malformed
tya.lockor lock that does not satisfy the manifest (with a hint to runtya install) - unsolvable version constraints with the full conflict path
- name collisions between two source identities
- network or git failures with the offending URL
- missing
tya.tomlat a dependency’s root import <name>for a name that is neither a manifest dependency nor a stdlib module- changes to
tya.tomlthat desync fromtya.lock(the lock is recreated on the nexttya install)
Diagnostics should mention the manifest field, the package name, the constraint that failed, and the source identity when relevant.
Examples
Minimal app
# tya.toml
name = "hello-app"
version = "0.1.0"
[dependencies]
greeting = { git = "https://github.com/example/greeting", tag = "v1.0.0" }
# main.tya
import greeting
print greeting.hello("world")
tya install
tya run main.tya
Tracked path dependency for monorepo-lite layout
project/
├── tya.toml
├── main.tya
└── lib/
└── shared/
├── tya.toml
└── src/
└── shared.tya
# project/tya.toml
name = "project"
version = "0.1.0"
[dependencies]
shared = { path = "lib/shared" }