ADR-006: Repository and Package Structure¶
Status¶
- Proposed by: Ryan on 2026-01-07
- Accepted on: 2026-01-07
Context¶
holoconf is a multi-language configuration library with:
- A Rust core (
holoconf-core) - Rust resolver packages (
holoconf-aws,holoconf-vault, etc.) - Language bindings (Python, JavaScript, Go, etc.)
- Language-specific resolver wrapper packages
We need to define how the repository is organized and how packages relate to each other.
Alternatives Considered¶
Alternative 1: Polyrepo (separate repositories)¶
Each component in its own repository:
- holoconf-core repo
- holoconf-python repo
- holoconf-js repo
- holoconf-aws repo
- Pros: Independent versioning, clear ownership, smaller clones
- Cons: Cross-repo changes are painful, version coordination is complex, harder to ensure consistency
- Rejected: Too much coordination overhead for a project where core changes often require binding updates
Alternative 2: Language-first monorepo¶
Organize by language:
- Pros: Language teams can work independently
- Cons: Duplicates resolver logic across languages, harder to see the full picture
- Rejected: Resolvers are Rust crates shared across languages, not per-language
Decision¶
Component-first monorepo with Cargo workspace
All code lives in a single repository, organized by component type.
Key decisions:
- Conformance tests at top-level in tests/conformance/ with shared fixtures
- Language-specific resolver wrappers in packages/<language>/holoconf-<resolver>/ that re-export Rust resolvers
Design¶
Repository Layout¶
holoconf/
├── docs/ # Documentation (exists)
│ ├── adr/ # Architecture Decision Records
│ ├── specs/features/ # Feature specifications
│ ├── PROBLEM_STATEMENT.md
│ ├── REQUIREMENTS.md
│ └── CONSTRAINTS.md
│
├── crates/ # Rust crates (Cargo workspace)
│ ├── holoconf-core/ # Core library: parsing, merging, resolution
│ ├── holoconf-aws/ # AWS resolvers (SSM, S3, CloudFormation)
│ ├── holoconf-gcp/ # GCP resolvers (future)
│ ├── holoconf-vault/ # HashiCorp Vault resolvers (future)
│ ├── holoconf-python/ # Python bindings (PyO3)
│ └── holoconf-js/ # JavaScript bindings (NAPI-RS)
│
├── packages/ # Language-specific package metadata/wrappers
│ ├── python/ # Python package (pyproject.toml, re-exports)
│ │ ├── holoconf/ # Main Python package
│ │ └── holoconf-aws/ # AWS resolver Python wrapper
│ └── javascript/ # npm packages
│ ├── holoconf/ # Main npm package
│ └── holoconf-aws/ # AWS resolver npm wrapper
│
├── tests/ # Cross-language conformance tests
│ └── conformance/ # Shared test fixtures (YAML in, expected out)
│
├── examples/ # Usage examples per language
│ ├── python/
│ └── javascript/
│
├── Cargo.toml # Cargo workspace root
├── Cargo.lock
└── README.md
Conformance Tests¶
Conformance tests ensure all language bindings behave identically. They live at the top level (tests/conformance/) rather than under each binding because:
- Shared fixtures - Same YAML input files and expected outputs for all languages
- Cross-language validation - Easy to verify Python and JS produce identical results
- Single source of truth - Adding a test case automatically applies to all languages
tests/conformance/
├── fixtures/
│ ├── basic/
│ │ ├── input.yaml
│ │ └── expected.json
│ ├── merging/
│ │ ├── base.yaml
│ │ ├── override.yaml
│ │ └── expected.json
│ └── resolvers/
│ ├── env/
│ └── self-ref/
├── python/
│ └── test_conformance.py # Runs fixtures against Python binding
└── javascript/
└── conformance.test.js # Runs fixtures against JS binding
Language-Specific Resolver Wrappers¶
Resolver wrappers (e.g., holoconf-aws on PyPI) are thin packages that:
1. Depend on the main holoconf package
2. Re-export the Rust resolver registrations
3. Provide language-idiomatic installation (pip install holoconf-aws)
# packages/python/holoconf-aws/src/holoconf_aws/__init__.py
from holoconf._native import aws_resolvers
# Re-export resolver registration functions
ssm = aws_resolvers.ssm
s3 = aws_resolvers.s3
cloudformation = aws_resolvers.cloudformation
# For package-level registration
__holoconf_resolvers__ = {
"ssm": ssm,
"s3": s3,
"cfn": cloudformation,
}
This approach:
- Keeps resolver logic in Rust (consistent behavior)
- Provides familiar package installation patterns per language
- Allows holoconf.register(holoconf_aws) pattern from ADR-002
Package Relationships¶
┌─────────────────────┐
│ holoconf-core │
│ (Rust crate) │
└──────────┬──────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ holoconf-aws │ │holoconf-python│ │ holoconf-js │
│ (Rust crate) │ │ (Rust crate) │ │ (Rust crate) │
│ SSM, S3, CF │ │ PyO3 bindings │ │ NAPI bindings │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
│ ┌───────┴───────┐ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ holoconf │ │ holoconf │ │
│ │ (PyPI) │ │ (npm) │◄────┘
│ └──────┬──────┘ └─────────────┘
│ │
▼ ▼
┌─────────────────────────┐
│ holoconf-aws │
│ (PyPI) - re-exports │
│ Rust AWS resolvers │
└─────────────────────────┘
Package Naming Convention¶
| Component | Rust Crate | PyPI Package | npm Package |
|---|---|---|---|
| Core | holoconf-core |
holoconf |
holoconf |
| Python bindings | holoconf-python |
(built into holoconf) |
- |
| JS bindings | holoconf-js |
- | (built into holoconf) |
| AWS resolvers | holoconf-aws |
holoconf-aws |
@holoconf/aws |
| GCP resolvers | holoconf-gcp |
holoconf-gcp |
@holoconf/gcp |
| Vault resolvers | holoconf-vault |
holoconf-vault |
@holoconf/vault |
Cargo Workspace¶
# /Cargo.toml
[workspace]
members = [
"crates/holoconf-core",
"crates/holoconf-aws",
"crates/holoconf-python",
"crates/holoconf-js",
]
[workspace.package]
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/org/holoconf"
Python Package Structure¶
packages/python/holoconf/
├── pyproject.toml # maturin build config
├── src/
│ └── holoconf/
│ ├── __init__.py # Re-exports from Rust binding
│ └── py.typed # PEP 561 marker
└── tests/
# pyproject.toml
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[project]
name = "holoconf"
requires-python = ">=3.9"
[tool.maturin]
manifest-path = "../../crates/holoconf-python/Cargo.toml"
JavaScript Package Structure¶
packages/javascript/holoconf/
├── package.json
├── index.js # Re-exports from Rust binding
├── index.d.ts # TypeScript definitions
└── tests/
Rationale¶
- Single repo enables atomic changes across core + bindings
crates/directory keeps Rust code together, managed by Cargo workspacepackages/directory holds language-specific packaging (pyproject.toml, package.json)- Top-level conformance tests ensure cross-language consistency
- Resolver crates are separate from core to keep binary size small
- Language bindings are Rust crates that compile to native extensions
- Thin resolver wrappers provide idiomatic installation while keeping logic in Rust
- npm scoped packages (
@holoconf/aws) prevent naming conflicts
Trade-offs Accepted¶
- Larger repository with all languages in exchange for atomic cross-language changes
- More complex CI (must test all languages) in exchange for consistency guarantees
- Single version number across packages in exchange for simpler dependency management
- Thin wrapper packages add indirection in exchange for idiomatic installation patterns
Migration¶
N/A - This is the initial structure decision.
Consequences¶
- Positive: Easy to make coordinated changes, single source of truth, shared CI/CD, conformance testing built-in
- Negative: Contributors need familiarity with monorepo tooling, larger clone size
- Neutral: Requires workspace-aware tooling (Cargo workspaces, potentially nx/turborepo for JS)