Skip to content

Code Generation

APX generates language-specific code from schema files, placing the output in overlay directories with canonical import paths. This page covers the generation process, supported languages, and output structure.

Overview

apx gen <lang> [path]

The apx gen command reads your schemas from internal/apis/, generates code using format-specific toolchains (e.g. Buf for protobuf), and writes the output to internal/gen/<lang>/ with canonical module paths.

Supported languages: go, python, java, typescript


How It Works

  1. Load dependencies — reads apx.lock for pinned tool and dependency versions
  2. Create overlays — for each dependency, creates a directory in internal/gen/<lang>/ with the canonical module structure
  3. Generate code — runs format-specific code generators (e.g. buf generate for protobuf)
  4. Synthesize go.mod — for Go, creates a go.mod with the canonical module path so go.work can resolve imports
  5. Sync go.work — for Go, automatically adds use directives for each overlay

Go Generation

apx gen go

Output Structure

internal/gen/go/
└── proto/payments/ledger@v1.2.3/
    ├── go.mod                       # module github.com/<org>/apis/proto/payments/ledger
    └── v1/
        ├── ledger.pb.go             # generated protobuf code
        └── ledger_grpc.pb.go        # generated gRPC stubs

The @v1.2.3 suffix is the pinned version from apx.lock. The go.mod declares the canonical module path, enabling go.work overlay resolution.

go.work Integration

After generating Go code, apx gen go automatically calls apx sync to update go.work:

go 1.22
use .
use ./internal/gen/go/proto/payments/ledger@v1.2.3
use ./internal/gen/go/proto/users/profile@v1.0.1

Your application code then imports canonical paths:

import ledgerv1 "github.com/acme-corp/apis/proto/payments/ledger/v1"

Go resolves this to the local overlay via go.work during development.


Python Generation

apx gen python

When org is configured in apx.yaml, apx gen python scaffolds each overlay as an installable Python package with:

  • pyproject.toml — PEP 621 metadata with deterministic dist name (<org>-<domain>-<api>-<line>)
  • __init__.py hierarchy — namespace packages using pkgutil.extend_path

Output Structure

internal/gen/python/
└── proto/payments/ledger/v1/
    ├── pyproject.toml                    # name = "acme-payments-ledger-v1"
    └── acme_apis/
        ├── __init__.py                   # pkgutil.extend_path (namespace root)
        └── payments/
            ├── __init__.py
            └── ledger/
                ├── __init__.py
                └── v1/
                    └── __init__.py       # leaf — generated code lands here

Editable Install

Link Python overlays into your virtualenv for import resolution:

# Activate your virtualenv
source .venv/bin/activate

# Link all Python overlays (runs pip install -e for each)
apx link python

# Then import in your code:
# from acme_apis.payments.ledger.v1 import ledger_pb2

This mirrors Go's go.work overlay approach — code is generated locally, not pulled from a registry. You control the grpc/protobuf versions in your own virtualenv.

Switching to Released Package

apx unlink proto/payments/ledger/v1
pip install acme-payments-ledger-v1==1.2.3

Your import paths stay the same — from acme_apis.payments.ledger.v1 import ...


Java Generation

Java takes a different approach from Go and Python: schemas are the published artifact. Rather than generating and publishing Java stubs, APX publishes schema files (proto zip/jar) to a Maven repository. Consumers generate Java code locally via Maven's generate-sources phase.

Maven Coordinate Derivation

APX deterministically derives Maven coordinates from the API identity:

Component Pattern Example
groupId com.<org>.apis com.acme.apis
artifactId <domain>-<name>-<line>-proto payments-ledger-v1-proto
Java package com.<org>.apis.<domain>.<name>.<line> com.acme.apis.payments.ledger.v1

The -proto suffix on the artifactId distinguishes schema artifacts from hypothetical code artifacts.

Consumer Workflow

Add the schema artifact to your pom.xml:

<dependency>
  <groupId>com.acme.apis</groupId>
  <artifactId>payments-ledger-v1-proto</artifactId>
  <version>1.2.3</version>
</dependency>

Configure protobuf-maven-plugin to generate Java from the schema artifact during generate-sources:

<plugin>
  <groupId>org.xolstice.maven.plugins</groupId>
  <artifactId>protobuf-maven-plugin</artifactId>
  <configuration>
    <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
  </configuration>
  <executions>
    <execution>
      <goals><goal>compile</goal><goal>compile-custom</goal></goals>
    </execution>
  </executions>
</plugin>

Generated Java code lands in target/generated-sources/protobuf/ and is compiled as part of the normal Maven build.

Local Development

For local development before schemas are released, use apx link java (planned) to install schema artifacts to ~/.m2/repository:

# Generate and install to local Maven cache
apx link java   # planned — installs to ~/.m2

# Maven resolves from local cache
mvn compile

This mirrors Go's go.work overlay and Python's pip install -e — same identity in dev and prod, only the resolution backend changes.


TypeScript Generation

TypeScript follows the same schema-first pattern. APX derives scoped npm package names from the API identity:

Component Pattern Example
npm package @<org>/<domain>-<name>-<line>-proto @acme/payments-ledger-v1-proto

The -proto suffix distinguishes schema packages from application packages.

Consumer Workflow

Install the schema package from npm:

npm install @acme/payments-ledger-v1-proto

Import generated types in your TypeScript code:

import { LedgerService } from "@acme/payments-ledger-v1-proto";

Local Development

For local development before schemas are released, use apx link typescript (planned) to create npm links:

# Generate and link locally
apx link typescript   # planned — creates npm link

# npm resolves from local link
npm run build

This mirrors Go's go.work, Python's pip install -e, and Java's ~/.m2 — same identity in dev and prod.


Flags

Flag Type Default Description
--out string "" Override the output directory
--clean bool false Remove existing output before generating
--manifest bool false Emit a generation manifest listing all produced files

Clean Generation

Use --clean when switching versions or after changing dependencies to avoid stale files:

apx gen go --clean

This removes all existing overlays in internal/gen/go/ before regenerating.


Format-Specific Toolchains

Code generation uses format-specific tools, resolved from apx.lock:

Format Tool Plugins
Protocol Buffers buf protoc-gen-go, protoc-gen-go-grpc
OpenAPI Language-specific client generators
Avro avro-tools Language-specific serializers
JSON Schema Schema-to-code generators
Parquet Schema readers

Toolchain versions are pinned in apx.lock and downloaded with apx fetch.


.gitignore Policy

Generated code must never be committed. Add to .gitignore:

internal/gen/

CI regenerates code from apx.lock during each pipeline run, ensuring consistency.


Workflow Integration

Go Development Loop

  1. apx add <api-id> — add dependency
  2. apx gen go — generate Go code with canonical imports
  3. apx sync — update go.work to include overlays
  4. Edit and test locally — Go toolchain resolves via go.work
  5. apx unlink <api-id> — switch to released module

C++ Development Loop

  1. Add {org}-{domain}-{name}-{line}-proto to your conanfile
  2. conan install . — resolve dependencies
  3. Include {org}/{domain}/{name}/{line} headers in C++ code
  4. Local development via conan editable add for overlay resolution

Java Development Loop

  1. Add <dependency> to pom.xml with derived Maven coordinates
  2. mvn generate-sources — generate Java code from schema
  3. Import com.{org}.apis.{domain}.{name}.{line}.* in Java code
  4. Local development via mvn install to ~/.m2 repository

Python Development Loop

  1. apx add <api-id> — add dependency
  2. apx gen python — generate Python code with namespace packages
  3. apx link python — run pip install -e in active virtualenv
  4. from {org}_apis.{domain}.{name}.{line} import ... — import generated code
  5. apx unlink <api-id> — switch to released package via pip install

Rust Development Loop

  1. Add {org}-{domain}-{name}-{line}-proto to [dependencies] in Cargo.toml
  2. cargo build — compile with generated code
  3. use {org}_{domain}::{name}::{line}::* in Rust code
  4. Local development via path dependency for overlay resolution

TypeScript Development Loop

  1. npm install @{org}/{domain}-{name}-{line}-proto — add dependency
  2. import { ... } from '@{org}/{domain}-{name}-{line}-proto' — import in code
  3. Local dev via npm link for development iteration
  4. apx unlink <api-id> — switch back to released npm package

CI Pipeline

apx fetch          # download pinned tools
apx gen go         # regenerate from lock file
apx sync           # update go.work
go test ./...      # canonical imports resolve via overlay

Language-Specific Generation Reference

Go

APX manages Go overlays through go.work files, providing seamless local development without replace directives.

apx gen go

This creates local overlays in .apx/overlays/go/ and updates go.work to include them. Generated code uses canonical import paths so it works identically in local development and after release.

Key characteristics: - Module path follows Go major version conventions (no suffix for v0/v1, /vN for v2+) - Import path always includes the line version (/v1, /v2, etc.) - go.work overlays enable local development without replace directives - apx sync keeps go.work in sync after adding/removing dependencies

Python

APX scaffolds Python namespace packages with PEP 625 distribution names and pkgutil-based namespace packages.

apx gen python

This creates overlays in .apx/overlays/python/ with: - pyproject.toml — distribution metadata with the derived dist name - __init__.py hierarchy using pkgutil.extend_path for namespace packages - Generated protobuf/schema code

Key characteristics: - Distribution name: {org}-{domain}-{name}-{line} (e.g. acme-payments-ledger-v1) - Import path: {org}_apis.{domain}.{name}.{line} (e.g. acme_apis.payments.ledger.v1) - Editable install via apx link python (runs pip install -e) - Requires org in apx.yaml for package naming

Java

Java APIs are published as Maven artifacts using conventional groupId and artifactId derivation from the API identity.

Maven coordinates are derived as: - groupId: com.{org}.apis (hyphens become dots) - artifactId: {domain}-{name}-{line}-proto

Key characteristics: - Full Maven coordinate: com.{org}.apis:{domain}-{name}-{line}-proto - Java package: com.{org}.apis.{domain}.{name}.{line} - Consumer adds <dependency> to pom.xml with the derived coordinates - Local dev via mvn install to local ~/.m2 repository - Requires org in apx.yaml for Maven coordinate derivation

C++

C++ APIs are published as Conan packages using conventional naming derived from the API identity.

Conan package references are derived as: - Package name: {org}-{domain}-{name}-{line}-proto - C++ namespace: {org}::{domain}::{name}::{line}

Key characteristics: - Conan reference: {org}-{domain}-{name}-{line}-proto - C++ namespace: {org}::{domain}::{name}::{line} - Consumer adds dependency to conanfile.txt or conanfile.py - Local dev via conan editable add for development iteration - Requires org in apx.yaml for Conan reference derivation

Rust

Rust APIs are published as Cargo crates using conventional naming derived from the API identity.

Crate names are derived as: - Crate: {org}-{domain}-{name}-{line}-proto - Rust module: {org}_{domain}::{name}::{line}

Key characteristics: - Crate name: {org}-{domain}-{name}-{line}-proto - Rust module path: {org}_{domain}::{name}::{line} - Consumer adds [dependencies] entry to Cargo.toml - Local dev via path dependencies for development iteration - Requires org in apx.yaml for crate name derivation

TypeScript

TypeScript APIs are published as scoped npm packages with a -proto suffix.

npm install @{org}/{domain}-{name}-{line}-proto

Key characteristics: - npm package: @{org}/{domain}-{name}-{line}-proto - Import path equals the npm package name - Consumer installs via npm install or yarn add - Local dev via npm link for development iteration - Requires org in apx.yaml for package scoping

See Also