Writing a Rix Package
This guide explains how to design and write a Rix package.
A Rix package is an optional userland library for Vix.cpp projects.
It should be usable independently and, when stable, mountable through the unified Rix facade.
The short version
A Rix package should follow this model:
Registry package -> rix/name
Header -> <rix/name.hpp>
Facade access -> rix.name
Namespace -> rixlib::nameExample for a package named pdf:
Registry package -> rix/pdf
Header -> <rix/pdf.hpp>
Facade access -> rix.pdf
Namespace -> rixlib::pdfThe package should be installable with:
vix add rix/name
vix installand used from vix.app through:
deps = [
"rix/name",
]What a Rix package should be
A Rix package should be:
optional
focused
stable
independent
usable from Vix projects
installable through the Vix Registry
compatible with the unified Rix facadeA package should solve one clear userland problem.
Examples:
rix/auth -> authentication helpers
rix/csv -> CSV parsing and writing
rix/debug -> debug printing, formatting, inspection
rix/pdf -> PDF generationWhat a Rix package should not be
A Rix package should not be:
a CLI
a runtime
a package manager
a replacement for Vix.cpp
part of Vix Core
a standard library built into Vix.cppRix packages are libraries.
Vix.cpp owns the project workflow, build workflow, runtime, registry commands, services, and deployment.
Naming rules
Use the stable Rix naming model.
For a package named json:
Registry package -> rix/json
Header -> <rix/json.hpp>
Facade access -> rix.json
Namespace -> rixlib::jsonAvoid unstable naming styles:
@rix/json
rix_json
RixJson
vix/json
jsonlibUse:
rix/json
<rix/json.hpp>
rix.json
rixlib::jsonRepository shape
A package repository can use this structure:
name/
CMakeLists.txt
README.md
LICENSE
vix.json
include/
rix/
name.hpp
name/
Version.hpp
NameModule.hpp
core/
detail/
src/
NameModule.cpp
examples/
01_basic.cpp
tests/
name_tests.cppExample for rix/pdf:
pdf/
include/
rix/
pdf.hpp
pdf/
PdfModule.hpp
Version.hpp
core/
document/
writer/
src/
PdfModule.cpp
examples/
01_basic.cpp
tests/
pdf_tests.cppPublic header
Every independent package should expose one main public header:
#include <rix/name.hpp>Example:
#include <rix/pdf.hpp>This header should include the public package API.
It should not force users to include many internal headers for normal usage.
Namespace
Use:
namespace rixlib::name
{
}Example:
namespace rixlib::pdf
{
}Public types should live inside the package namespace.
Example:
rixlib::pdf::Document
rixlib::pdf::Page
rixlib::pdf::Table
rixlib::pdf::ColorPublic module
Each package should expose a public module object.
Example shape:
namespace rixlib::name
{
class NameModule
{
public:
std::string version() const;
};
[[nodiscard]] inline constexpr NameModule module() noexcept
{
return NameModule{};
}
}For rix/pdf, this becomes:
namespace rixlib::pdf
{
class PdfModule
{
public:
Document document() const;
PdfStatus save(const Document &document, std::string_view path) const;
};
[[nodiscard]] inline constexpr PdfModule module() noexcept
{
return PdfModule{};
}
}Independent usage:
#include <rix/pdf.hpp>
int main()
{
auto pdf = rixlib::pdf::module();
auto doc = pdf.document();
return pdf.save(doc, "hello.pdf").ok() ? 0 : 1;
}Facade mounting
When the package API is stable, it can be mounted into rix/rix.
Facade access should follow:
rix.nameExample:
rix.pdf.document()
rix.csv.parse(...)
rix.auth.memory()
rix.debug.print(...)The facade should expose the common workflow.
Advanced types can stay in the package namespace.
Independent first
A package should work independently before it is mounted into the facade.
First, this should work:
#include <rix/name.hpp>
int main()
{
auto mod = rixlib::name::module();
return 0;
}Then the facade can later expose:
#include <rix.hpp>
int main()
{
rix.name;
return 0;
}This keeps each package reusable.
Dependency in vix.app
Rix packages are Vix Registry dependencies.
They belong in deps.
Correct:
deps = [
"rix/name",
]Wrong:
packages = [
"rix/name",
]deps is for Vix Registry packages.
packages is for CMake package discovery.
Package manifest
A package should have a clear manifest.
Example shape:
{
"name": "rix/name",
"version": "0.1.0",
"description": "Short description of the package.",
"license": "MIT",
"repository": "https://github.com/rixcpp/name"
}Use the real package name and repository.
Keep the package name stable after publishing.
CMake target
A package should expose a stable CMake target.
Example:
rix::nameFor PDF:
rix::pdfFor the unified facade:
rix::rixKeep target names predictable.
Version header
Each package should expose version helpers.
Example:
namespace rixlib::name
{
inline constexpr int VERSION_MAJOR = 0;
inline constexpr int VERSION_MINOR = 1;
inline constexpr int VERSION_PATCH = 0;
[[nodiscard]] std::string version();
[[nodiscard]] int version_major() noexcept;
[[nodiscard]] int version_minor() noexcept;
[[nodiscard]] int version_patch() noexcept;
[[nodiscard]] int version_number() noexcept;
}The public module can expose the same helpers:
rix.name.version()
rix.name.version_major()
rix.name.version_minor()
rix.name.version_patch()This helps diagnostics and compatibility checks.
Error model
Packages that can fail should use explicit results.
Do not hide normal expected failures behind exceptions.
Example:
auto result = rix.name.do_work(input);
if (result.failed())
{
rix.debug.eprint(
"error:",
rix.name.error.to_string(result.error()),
result.error().message());
return 1;
}For operations that return a value, use a result type.
For operations that only succeed or fail, use a status type.
Result type
A result type should store either a value or an error.
Example shape:
template <typename T>
class NameResult
{
public:
static NameResult success(T value);
static NameResult failure(NameError error);
bool ok() const noexcept;
bool failed() const noexcept;
const T &value() const;
T &value();
const NameError &error() const noexcept;
};Use it for APIs such as:
NameResult<Document>
NameResult<std::string>
NameResult<User>Status type
A status type should represent success or failure without a value.
Example shape:
class NameStatus
{
public:
static NameStatus success();
static NameStatus failure(NameError error);
bool ok() const noexcept;
bool failed() const noexcept;
const NameError &error() const noexcept;
};Use it for APIs such as:
save()
validate()
write_to_file()
logout()Error codes
Use stable error codes.
Example:
enum class NameErrorCode
{
None,
InvalidInput,
InvalidState,
FileOpenFailed,
FileWriteFailed,
Unknown
};Expose:
to_string(NameErrorCode code)and module helpers:
rix.name.error.to_string(error)
rix.name.error.is(error, NameErrorCode::InvalidInput)Stable codes help applications make decisions.
Messages help developers understand what happened.
Keep internals hidden
Public API should be small.
Implementation details should stay in:
detail/
internal/
writer/
adapter/Only expose what applications need.
For example, rix/pdf can expose:
rix.pdf.document()
rix.pdf.save(...)
rix.pdf.write(...)while keeping low-level serialization details behind the writer layer.
Header design
Public headers should be clean.
Prefer:
#include <rix/name.hpp>for normal users.
Use deeper headers for advanced users:
#include <rix/name/core/Type.hpp>
#include <rix/name/detail/Internal.hpp>Do not require internal includes for the basic workflow.
Example package header
#ifndef RIXCPP_NAME_INCLUDE_RIX_NAME_HPP_INCLUDED
#define RIXCPP_NAME_INCLUDE_RIX_NAME_HPP_INCLUDED
#include <rix/name/NameModule.hpp>
#include <rix/name/Version.hpp>
namespace rixlib::name
{
[[nodiscard]] inline constexpr NameModule module() noexcept
{
return NameModule{};
}
}
#endif // RIXCPP_NAME_INCLUDE_RIX_NAME_HPP_INCLUDEDUse include guards with stable names.
Example module header
#ifndef RIXCPP_NAME_INCLUDE_RIX_NAME_NAMEMODULE_HPP_INCLUDED
#define RIXCPP_NAME_INCLUDE_RIX_NAME_NAMEMODULE_HPP_INCLUDED
#include <rix/name/Version.hpp>
#include <string>
namespace rixlib::name
{
class NameModule
{
public:
[[nodiscard]] std::string version() const;
[[nodiscard]] int version_major() const noexcept;
[[nodiscard]] int version_minor() const noexcept;
[[nodiscard]] int version_patch() const noexcept;
[[nodiscard]] int version_number() const noexcept;
};
}
#endif // RIXCPP_NAME_INCLUDE_RIX_NAME_NAMEMODULE_HPP_INCLUDEDExample module source
#include <rix/name/NameModule.hpp>
namespace rixlib::name
{
std::string NameModule::version() const
{
return rixlib::name::version();
}
int NameModule::version_major() const noexcept
{
return rixlib::name::version_major();
}
int NameModule::version_minor() const noexcept
{
return rixlib::name::version_minor();
}
int NameModule::version_patch() const noexcept
{
return rixlib::name::version_patch();
}
int NameModule::version_number() const noexcept
{
return rixlib::name::version_number();
}
}README requirements
A package README should include:
what the package does
how to install it
how to include it
a basic example
facade usage if supported
independent usage
error handling if relevant
license
repository linkStart with a simple example.
Do not start with internals.
Basic README example
# rix/name
`rix/name` is a Rix package for ...
## Install
```bash
vix add rix/name
vix install
```Use
#include <rix/name.hpp>
int main()
{
auto mod = rixlib::name::module();
return 0;
}
## Examples
Every package should have examples.
Example layout:
```txt id="f2v7mc"
examples/
01_basic.cpp
02_errors.cpp
03_options.cppExamples should compile with Vix commands.
Use:
vix run examples/01_basic.cppor from a project:
vix build
vix runTests
A package should have tests.
Example layout:
tests/
name_tests.cppRun tests with:
vix testsTests should cover:
basic workflow
invalid input
error codes
edge cases
version helpers
public headers
independent package usageDocumentation
Docs should start from the public API.
Recommended docs:
index.md
quick-start.md
installation.md
api-reference.md
errors.md
examples.mdFor larger packages:
configuration.md
advanced-usage.md
security.md
adapters.mdAPI reference
The API reference should list public types and methods.
Do not document every private detail.
A useful API reference includes:
module class
core public types
result and status types
error codes
configuration types
version helpers
main functionsDesign principle
A Rix package should keep complexity inside the package.
The public API should stay simple.
Good:
auto doc = rix.pdf.document();
auto saved = rix.pdf.save(doc, "hello.pdf");Less good as a first public API:
PdfObjectWriter writer;
XrefTable xref;
FontRegistry fonts;Low-level APIs can exist, but the common workflow should be short.
Public API checklist
Before publishing, check:
Can the package be installed with vix add rix/name?
Does it belong in deps?
Does it have <rix/name.hpp>?
Does it use namespace rixlib::name?
Does it expose rixlib::name::module()?
Does it have a simple public module?
Does it return explicit results for expected failures?
Does it have version helpers?
Does it have examples?
Does it have tests?
Can it be used independently?
Can it be mounted into rix/rix later?Facade checklist
Before mounting into rix/rix, check:
Is the independent package stable?
Is the facade member name short?
Does it follow rix.name?
Does it expose the common workflow?
Does it avoid exposing internals first?
Does it work with feature macros?
Does it compile when only that feature is enabled?
Does it compile when all features are enabled?Feature macro support
If the package is mounted into the facade, add a feature macro.
Example:
#define RIX_ENABLE_NAME
#include <rix.hpp>If no feature macro is defined, all currently mounted modules are enabled.
If at least one feature macro is defined, only selected modules are mounted.
Example:
#define RIX_ENABLE_PDF
#define RIX_ENABLE_DEBUG
#include <rix.hpp>Facade member example
A facade can mount a package like this:
class Rix
{
public:
#ifdef RIX_ENABLE_NAME
rixlib::name::NameModule name{};
#endif
};In the real facade, keep default behavior in mind:
no feature macro -> enable all mounted modules
one or more feature macros -> enable selected modules onlyPackage versioning
Use semantic versioning.
For early packages:
0.1.0
0.2.0
0.3.0Patch versions should fix bugs.
Minor versions can add APIs.
Breaking changes should be handled carefully, especially after users start depending on the package.
Release checklist
Before release:
update version
update CHANGELOG
run vix build
run vix tests
run examples
check README
check docs
tag release
publish registry metadata
sync registry
test install from a clean projectUse Vix commands:
vix build
vix testsAfter publishing, verify:
vix registry sync
vix add rix/name
vix installDependency rules
A Rix package should keep dependencies minimal.
If it depends on another Rix package, add it explicitly.
Example:
deps = [
"rix/debug",
]Avoid pulling the full facade from an independent package unless there is a strong reason.
Independent packages should not depend on rix/rix by default.
When a package should depend on Vix
A package can depend on Vix when it directly needs Vix runtime APIs.
Example:
HTTP integration
WebSocket integration
Vix application adapters
Vix-specific middlewareIf the package is a general utility, keep it independent from Vix runtime when possible.
Package categories
A package can be:
pure utility package
application-level package
Vix integration package
adapter package
facade packageExamples:
rix/csv -> utility package
rix/pdf -> application-level package
rix/auth -> application-level package
rix/rix -> facade packageSecurity-sensitive packages
If a package handles security-sensitive behavior, document it clearly.
Examples:
auth
tokens
sessions
password hashing
crypto adaptersSecurity docs should include:
safe defaults
what not to log
configuration warnings
production recommendations
limitations
threat model notes when usefulFor auth, do not log:
plain-text passwords
password hashes
raw tokens
session idsError handling examples
Every package with explicit errors should show failure examples.
Example:
auto result = mod.do_work("");
if (result.failed())
{
rix.debug.eprint(
"expected error:",
mod.error.to_string(result.error()),
result.error().message());
return 0;
}This teaches users the correct pattern.
Common mistakes
Starting from the facade before the package works independently
The independent package should work first:
#include <rix/name.hpp>
auto mod = rixlib::name::module();Then mount it into:
#include <rix.hpp>
rix.namePutting Rix dependencies in packages
Wrong:
packages = [
"rix/name",
]Correct:
deps = [
"rix/name",
]Exposing internals as the first API
Avoid making users start from low-level internals.
Start with:
auto mod = rixlib::name::module();or:
rix.nameMaking the package too broad
A package should have one clear responsibility.
Avoid one package doing unrelated things.
Better:
rix/csv
rix/pdf
rix/auththan:
rix/tools
rix/misc
rix/everythingCalling the package part of Vix Core
Rix packages are optional userland packages.
They are not built into Vix Core.
Users install them through the registry.
Forgetting examples
A package without examples is hard to adopt.
Add at least one basic example.
Forgetting errors
If an operation can fail, return an explicit result or status and document the pattern.
What you should remember
A Rix package follows one model:
Registry package -> rix/name
Header -> <rix/name.hpp>
Facade access -> rix.name
Namespace -> rixlib::nameUse deps in vix.app:
deps = [
"rix/name",
]Expose a simple module:
auto mod = rixlib::name::module();Keep the common workflow short.
Keep internals hidden.
Use explicit results for expected failures.
Make the independent package work before mounting it into the facade.
Next step
Continue with the package checklist.
Next: Package checklist