Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 61 additions & 14 deletions docs/docs/guide/codegen/generating-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ language. This page is the practical guide: what it produces, how to say where i
goes, and how to generate more than one version at once.

Status: this reflects the current CLI. `rust` and `typescript` are the
generators (both `code` mode; `rust` also does `lib`); the configuration
surface below is stable.
generators, and both do `code` and `lib`; the configuration surface below is
stable.

## The two files

Expand Down Expand Up @@ -38,28 +38,41 @@ Each entry is a bare `language#lang_version`. `lang_version` selects a
version-specific generator (e.g. Rust 1.70's output vs a later edition's). The
`= {}` is required and takes no options.

A declared language is a *capability*: "bindings for this language make sense for
this package." It does not force anyone to generate it, and it carries no output
path.
Declaring a language is what makes it generated: `comline generate` emits every
language listed here. The entry carries no output path — where the code lands is
the consumer's business, below — and a consumer who wants fewer says so with
`only`.

## Configuring output — `comline.toml`

```toml
[generate]
out = "generated" # output root, relative to comline.toml
layout = "{{language}}/{{namespace}}.{{ext}}" # path of each file under the root
mode = "code" # code | lib | dylib (only `code` today)
mode = "code" # code | lib (dylib is not built yet)
package_versions = "latest" # latest | all | ["0.3.0", "0.4.0"]
only = ["rust"] # generate just these, out of the declared languages

# Optional: one block per target you want to pin or override.
# Optional: one block per declared language you want to configure.
[[generate.target]]
language = "rust"
lang_version = "1.70.0" # defaults to what config.idp declared
out = "src/generated" # this target only
out = "src/generated" # this language only
```

With no `comline.toml` (or an all-commented one) the defaults above apply and the
targets come from `config.idp`'s `code_generation.languages`.
With no `comline.toml` (or an all-commented one) the defaults above apply to
every language `config.idp` declares.

**A block configures a language; it doesn't choose which languages are
generated.** That comes from `config.idp` alone. A declared language with no
block takes the `[generate]` defaults, several blocks for one language give
several outputs, and a block naming a language the congregation doesn't declare
is an error rather than a silent no-op.

To generate fewer languages than the package declares — because this checkout
only needs some of them — list the ones you want in `[generate] only`, or pass
`--target` for a single run. A name `config.idp` doesn't declare is an error
there too, so a typo can't quietly stop generating something.

**Precedence**, lowest to highest:

Expand Down Expand Up @@ -159,11 +172,45 @@ mode wires that module tree up itself (`pub mod shared;` in `src/lib.rs`);
cross-schema references in `code` mode needs to declare matching `mod`
statements itself.

## Modes (planned)
## What TypeScript output looks like

| Schema | TypeScript |
|---|---|
| `struct` | `export interface` |
| `error` | `export interface` (the payload) + `export class <Name>Error` |
| `enum` | `export enum` with string values |
| `protocol` | a client, a dispatcher, and a `serve<Proto>` helper against `@comline/runtime` |

A cross-schema reference is emitted as an `import type` from the schema that
declares it, by relative specifier:

```ts
import type { Greeting } from "./types.js";
```

An alias imports the name the other module actually exports
(`use types::Greeting as G` → `import type { Greeting as G }`), and a `use`
nothing refers to produces no import at all.

The specifier is relative, so `code` mode assumes your `layout` keeps each
schema's namespace in the **file-name** position — every layout above does,
including the default. A layout that puts the namespace in a directory
position instead (`"{{namespace}}/index.{{ext}}"`) moves the files relative to
each other and the imports won't resolve.

In `lib` mode the namespace tree becomes the package's shape, so both of these
work:

```ts
import { types } from "my-api"; // types.Greeting
import { Greeting } from "my-api/types";
```

## Modes

`mode` selects the emit form. Only `code` — plain source text you drop into your
project — exists today. `lib` (a buildable crate skeleton) and `dylib` (that,
compiled, for a language runtime to load) are planned.
`mode` selects the emit form. `code` (plain source text you drop into your
project) and `lib` (a buildable package) both work, for both generators;
`dylib` — that, compiled, for a language runtime to load — is planned.
[Library generation](library-generation.md) explains each mode and when to reach
for it.

Expand Down
6 changes: 3 additions & 3 deletions docs/docs/guide/codegen/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ detail that the generated code is pleasant to use directly rather than only as a
full guide, and [`comline.toml`](../../reference/congregation/comline-toml.md) is where a
consumer sets the output location, layout and mode.

Today there are `rust` and `typescript` generators (both `code` mode); `rust`
also does `lib` mode. [Library generation](library-generation.md) covers the
modes and `dylib`, which is still planned.
Today there are `rust` and `typescript` generators, and both do `code` and
`lib` mode. [Library generation](library-generation.md) covers the modes and
`dylib`, which is still planned.
45 changes: 37 additions & 8 deletions docs/docs/guide/codegen/library-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ in [`comline.toml`](../../reference/congregation/comline-toml.md) (`[generate] m
| `mode` | Output | Status |
|---|---|---|
| `code` | bare source files you place in a project yourself | built |
| `lib` | a buildable package (manifest + module tree) | **rust only** |
| `lib` | a buildable package (manifest + module tree) | rust, typescript |
| `dylib` | a package built with a **stable ABI** and compiled to a dynamic library any language's runtime can load | planned |

## `code` — source you wire in yourself
Expand All @@ -40,14 +40,43 @@ A whole buildable package: the manifest with the right dependencies, a `src/`
module tree, and a top-level module listing every schema. The result is
something you `cargo add` / `pip install` / `luarocks install`, or publish.

Implemented for **rust**: `<out>/rust/` gets a `Cargo.toml` (package name from
the congregation, `serde` dependency), `src/lib.rs`, and `src/<namespace>.rs` per
schema — `<language>/` keeps this target's crate from colliding with another
`lib` target sharing the same `out`; a target with `out` to itself can set
**rust**: `<out>/rust/` gets a `Cargo.toml` (package name from the
congregation, `serde` dependency), `src/lib.rs`, and `src/<namespace>.rs` per
schema. A nested namespace (`std/http`) becomes a nested module —
`src/std.rs` declaring `pub mod http;` beside `src/std/http.rs`.

**typescript**: `<out>/typescript/` gets a `package.json` (declaring
`@comline/runtime` when any schema has a protocol), a `tsconfig.json`, and
`src/<namespace>.ts` per schema. Each namespace level also gets an `index.ts`
naming its children, so the package's shape mirrors your namespaces:

```
src/index.ts export * as types from "./types.js";
export * as std from "./std/index.js";
src/std/index.ts export * as http from "./http.js";
```

That is why names don't collide: `std` is implicit in every package, and a
flat barrel re-exporting everything into the package root would make your own
`Request` ambiguous with `std/http`'s — which TypeScript refuses outright
(TS2308). Both entry styles work:

```ts
import { types } from "my-api"; // types.Greeting
import { Greeting } from "my-api/types";
```

One restriction, shared by both generators: a namespace can be a schema or a
parent of deeper schemas, not both. `std.ids` alongside `std/http.ids` leaves
`src/std.rs` (or `export * as std`) with two things to be, so it's refused
with a message naming the pair.

In either language, `<language>/` keeps this target's package from colliding
with another `lib` target sharing the same `out`; a target with `out` to
itself can set
[`flatten = true`](../../reference/congregation/comline-toml.md#flatten) to skip that and
land the crate straight into `out`. `layout` plays no part in any of this —
it only applies to `code` mode. Other languages, multi-version `lib` output,
and a schema whose namespace is nested (`a/b`) are not done yet.
land the package straight into `out`. `layout` plays no part in any of this —
it only applies to `code` mode. Multi-version `lib` output is not done yet.

**Use it when** the schema is shared:

Expand Down
12 changes: 8 additions & 4 deletions docs/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ parse → resolve imports → validate → (freeze into .comline/) → (generate
| `comline add <name> <dir>` / `comline add <name> --git <uri> --commit <sha> --version <v>` | Add a dependency to `config.idp`, imported as `use <name>::…`: resolved first (a git pin is fetched into `.comline/deps-cache/`), then written with its `hash` pinned. `--no-hash` skips the pin. |
| `comline check` | Parse, resolve and validate. No `.comline/` write (beyond fetching git dependencies), no version bump — safe for editors, hooks, CI. |
| `comline build [--release] [--watch]` | Compile, validate, freeze a new immutable version into `.comline/`; print the changelog and the bump. `--release` is currently a no-op. |
| `comline generate [--target <lang>] [--out <dir>] [--layout <tpl>] [--mode <m>] [--external-std] [--watch]` | Validate (no freeze), then write generated code. Location/layout from [`comline.toml`](congregation/comline-toml.md); targets from there or `config.idp`. |
| `comline generate [--target <lang>] [--out <dir>] [--layout <tpl>] [--mode <m>] [--external-std] [--no-external-std] [--dry-run] [--watch]` | Validate (no freeze), then write generated code. Location/layout from [`comline.toml`](congregation/comline-toml.md); targets from there or `config.idp`. |
| `comline diff <old> [<new>]` | Show schema changes between two built versions. Each argument is a version (`0.2.0`), a commit hash (4+ chars), or `HEAD` (`<new>` default). |
| `comline clean [--dry-run]` | Remove generated code (the `generate` output root or files). Never touches `.comline/`. |
| `comline reset [--force] [--dry-run]` | **Destructive.** Delete `.comline/` — the whole version history, no undo — *and* generated code. Next `build` starts over at `0.0.1`. On a terminal it shows how many versions will be lost and makes you type `reset`; without a terminal it refuses (exit `2`) unless `--force`. |
Expand All @@ -45,9 +45,13 @@ parse → resolve imports → validate → (freeze into .comline/) → (generate
`--out` / `--layout` / `--mode` override one target and need `--target` when
several are configured. `COMLINE_GENERATE_OUT` / `_LAYOUT` / `_MODE` do the same
from the environment, for every target. `--external-std` makes generated Rust
reference `comline_std` as its own crate instead of inlining it, overriding
[`comline.toml`](congregation/comline-toml.md)'s `[generate] external_std`.
Today `rust` and `typescript` are the generators; `rust` also does `lib` mode.
reference `comline_std` as its own crate instead of inlining it, and
`--no-external-std` inlines it; either overrides
[`comline.toml`](congregation/comline-toml.md)'s `[generate] external_std`,
which itself defaults to on in `lib` mode and off in `code` mode.
`--dry-run` lists the files each target would write, as a tree, and writes
nothing. Today `rust` and `typescript` are the generators, and both do `code`
and `lib` mode.

## Global flags

Expand Down
Loading