# Varavel Definition Language > VDL is an open-source, type-safe, multi-language, and easily extensible schema definition language and code generation toolchain. This file contains the LLM-readable Markdown content for the full site. --- Source: https://vdl.varavel.com/ Markdown: https://vdl.varavel.com/index.md Content file: content/_index.md # Varavel Definition Language > VDL is an open-source, type-safe, multi-language, and easily extensible schema definition language and code generation toolchain. Canonical HTML: https://vdl.varavel.com/ Markdown: https://vdl.varavel.com/index.md {{ header_base( show_title=false, container="lg", menu="Overview|/docs/about/,Language|/docs/language/start-here/,Plugins|/docs/guides/plugins/,Reference|/docs/reference/spec/,GitHub|https://github.com/varavelio/vdl", cta_text="Install", cta_url="/docs/guides/installation/" ) }} {{ hero_split( container="lg", eyebrow="Contract-first generation", title="One human-readable schema. Type-safe code across your stack.", description="VDL lets teams write compact contracts that humans can review and machines can validate. From one .vdl file, generate type-safe models, RPC clients and servers, schemas, docs, and custom artifacts for multiple languages.", action_1_text="Get started", action_1_url="/docs/", action_2_text="See available plugins", action_2_url="/docs/guides/plugins/", caption="Open source, deterministic, and designed for normal development and CI workflows.", panel_label="How it works", panel_title="Readable contract to type-safe outputs", panel_desc="A VDL project starts with declarations, not framework code. The analyzer enforces the model, then plugins generate the files each application needs.", metric_1_value=".vdl", metric_1_label="source files", metric_2_value="Plugin", metric_2_label="transforms IR", metric_3_value="Code", metric_3_label="type-safe outputs", check_1="Write contracts that are easy to read in code review", check_2="Validate references, naming, spreads, literals, and required type relationships", check_3="Generate type-safe code for frontend, backend, services, and tooling" ) }} {{ metrics_strip( container="lg", label="What VDL gives you", stat_1_value="Readable", stat_1_label="contracts for humans", stat_2_value="Type-safe", stat_2_label="generated code", stat_3_value="Multi-lang", stat_3_label="plugin outputs", stat_4_value="CI-ready", stat_4_label="format and checks" ) }} {{ features_grid( container="lg", eyebrow="The core idea", title="Keep the contract simple. Move the output logic into plugins.", description="VDL separates what your systems agree on from how each toolchain consumes it. The source stays readable, while generated artifacts stay precise and type-safe.", columns="3", item_1_icon="braces", item_1_title="Describe the shared model", item_1_desc="Use type, enum, const, include, docstrings, arrays, maps, inline objects, references, and spreads to describe the contract itself in a review-friendly format.", item_2_icon="workflow", item_2_title="Add domain intent", item_2_desc="Use annotations such as @rpc, @event, @deprecated, or your own team-specific tags without changing the language grammar.", item_3_icon="plug", item_3_title="Generate type-safe outputs", item_3_desc="Official and custom easy to write plugins receive the resolved IR and generate language-specific code, schemas, docs, or internal artifacts." ) }} {{ steps_cards( container="lg", eyebrow="Workflow", title="A straightforward path from schema to artifacts", description="The day-to-day loop is intentionally small: write the contract, configure plugins, and generate checked output.", columns="3", item_1_icon="terminal", item_1_title="Install the CLI", item_1_desc="Install VDL with the shell installer, Homebrew, PowerShell, npm, Docker, or release binaries.", item_1_href="/docs/guides/installation/", item_1_link="Install VDL", item_2_icon="braces", item_2_title="Model the contract", item_2_desc="Create readable .vdl files for shared types, HTTP RPC operations, event payloads, documentation, and metadata.", item_2_href="/docs/language/start-here/", item_2_link="Learn the language", item_3_icon="zap", item_3_title="Run generation", item_3_desc="Point vdl.config.vdl at your schema and plugins, then run vdl generate locally or in CI.", item_3_href="/docs/guides/configuration/", item_3_link="Configure a project" ) }} {{ features_grid( container="lg", bg="base-200", eyebrow="Use cases", title="Use VDL anywhere systems need to agree on data", description="VDL is most useful when several parts of a product need the same contract, but each part needs it in its own language or format.", columns="3", item_1_icon="box", item_1_title="Share types across apps", item_1_desc="Generate type-safe models for frontend, backend, workers, and microservices from the same source contract.", item_2_icon="code-xml", item_2_title="Build type-safe HTTP RPC", item_2_desc="Model request and response shapes once, then generate typed clients, servers, and OpenAPI documents for frontend-to-backend communication.", item_3_icon="workflow", item_3_title="Coordinate microservices", item_3_desc="Keep service boundaries explicit with shared schemas, generated packages, and stable contracts that can be checked in CI.", item_4_icon="book-open", item_4_title="Document and validate data", item_4_desc="Generate JSON Schema, schema explorers, metadata exports, and documentation from docstrings and resolved types.", item_5_icon="plug", item_5_title="Standardize event contracts", item_5_desc="Use @event declarations to generate subject builders and catalogs for event-driven systems.", item_6_icon="terminal", item_6_title="Private team artifacts", item_6_desc="Write custom plugins for your framework conventions, templates, catalogs, policies, or platform-specific glue code." ) }} {{ resource_cards( container="lg", eyebrow="Read next", title="Choose the next step that matches your goal", description="The documentation starts with concepts, then moves into installation, configuration, plugin usage, and reference details.", item_1_type="Overview", item_1_title="About VDL", item_1_desc="Understand the project philosophy, the plugin-first model, and common use cases.", item_1_href="/docs/about/", item_1_meta="Read overview", item_2_type="Language", item_2_title="Start Here", item_2_desc="Learn the VDL syntax in order, from files and comments to naming and validation.", item_2_href="/docs/language/start-here/", item_2_meta="Learn syntax", item_3_type="Project setup", item_3_title="Project Configuration", item_3_desc="Configure vdl.config.vdl, plugin sources, output directories, remotes, hooks, and lock files.", item_3_href="/docs/guides/configuration/", item_3_meta="Configure generation", item_4_type="Extension", item_4_title="Creating Plugins", item_4_desc="Build custom JavaScript or TypeScript plugins that consume the resolved VDL IR.", item_4_href="/docs/guides/creating-plugins/", item_4_meta="Write a plugin" ) }} {{ cta_banner( container="lg", eyebrow="Ready to try it", title="Install VDL, write one schema, and generate your first outputs.", description="Start small: define a type, add a plugin, and run the generator. The same workflow scales to shared models, RPC services, event contracts, documentation, and private platform tooling.", action_1_text="Install VDL", action_1_url="/docs/guides/installation/", action_2_text="Open the language guide", action_2_url="/docs/language/start-here/", caption="Generated files are regular files: inspect them, test them, commit them, or regenerate them in CI." ) }} {{ footer_simple( container="lg", links="Docs|/docs/,GitHub|https://github.com/varavelio/vdl,Varavel|https://varavel.com", show_github="false" ) }} --- Source: https://vdl.varavel.com/docs/ Markdown: https://vdl.varavel.com/docs/index.md Content file: content/docs/_index.md # Documentation > Documentation for the VDL language, CLI, plugin system, and reference behavior. Canonical HTML: https://vdl.varavel.com/docs/ Markdown: https://vdl.varavel.com/docs/index.md --- Source: https://vdl.varavel.com/docs/guides/ Markdown: https://vdl.varavel.com/docs/guides/index.md Content file: content/docs/guides/_index.md # Guides > Practical guides for installing, configuring, and extending VDL. Canonical HTML: https://vdl.varavel.com/docs/guides/ Markdown: https://vdl.varavel.com/docs/guides/index.md Use these guides to install the CLI, configure generation, connect editors, and work with official or custom plugins. --- Source: https://vdl.varavel.com/docs/guides/installation/ Markdown: https://vdl.varavel.com/docs/guides/installation/index.md Content file: content/docs/guides/installation.md # Installation > Complete installation options for the VDL CLI. Canonical HTML: https://vdl.varavel.com/docs/guides/installation/ Markdown: https://vdl.varavel.com/docs/guides/installation/index.md ## Overview Choose the installation method that best fits your operating system and workflow. | Platform | Method | Command | | ----------------- | ------------ | -------------------------------------------------------------- | | **Linux / macOS** | Shell | curl -fsSL https://get.varavel.com/vdl | sh | | **Linux / macOS** | Homebrew | `brew install varavelio/tap/vdl` | | **Windows** | PowerShell | irm https://get.varavel.com/vdl.ps1 | iex | | **Any** | NPM (local) | `npm install --save-dev @varavel/vdl` | | **Any** | NPM (global) | `npm install --global @varavel/vdl` | | **Any** | Docker | `docker run --rm varavel/vdl` | | **Any** | Manual | [Download binaries](https://github.com/varavelio/vdl/releases) | After installation, verify that the CLI is available: ```bash vdl --version ``` ## Linux and macOS ### Shell Installer The shell installer is the quickest way to install VDL on Linux and macOS. ```bash curl -fsSL https://get.varavel.com/vdl | sh ``` For more installation options using this installer, visit [https://get.varavel.com/vdl](https://get.varavel.com/vdl). Install a specific version: ```bash curl -fsSL https://get.varavel.com/vdl | VERSION=vx.x.x sh ``` Replace `vx.x.x` with the release tag you want to install, for example `v0.1.0`. ### Homebrew Use Homebrew if you prefer managing CLI tools through taps. ```bash brew install varavelio/tap/vdl ``` Install the latest prerelease: ```bash brew install varavelio/tap/vdl-next ``` Install a specific version: ```bash brew install varavelio/tap/vdl@x.x.x ``` Replace `x.x.x` with the version you want to install. ## Windows Use the PowerShell installer on Windows. ```powershell irm https://get.varavel.com/vdl.ps1 | iex ``` For more installation options using this installer, visit [https://get.varavel.com/vdl.ps1](https://get.varavel.com/vdl.ps1). Install a specific version: ```powershell $env:VERSION = "vx.x.x"; irm https://get.varavel.com/vdl.ps1 | iex ``` Replace `vx.x.x` with the release tag you want to install, for example `v0.1.0`. ## NPM The npm package is cross-platform and works well when VDL should be pinned per project. ### Local Project Install This is the recommended npm workflow for teams because it keeps every developer and CI job on the same VDL version. ```bash npm install --save-dev @varavel/vdl ``` Then call VDL from package scripts, for example: ```json { "scripts": { "vdl:generate": "vdl generate", "vdl:format": "vdl format" } } ``` ### Global Install Use a global install when you want `vdl` available system-wide. ```bash npm install --global @varavel/vdl ``` Install the latest prerelease: ```bash npm install --global @varavel/vdl@next ``` Install a specific version: ```bash npm install --global @varavel/vdl@x.x.x ``` For package details, visit the [npm package page](https://www.npmjs.com/package/@varavel/vdl). ## Docker The official VDL image is available on both Docker Hub and the GitHub Container Registry. It provides a minimal, multi-arch container with the `vdl` binary at `/usr/local/bin/vdl`. | Registry | Image | | ------------------------- | ------------------------------------------------------------------------------ | | Docker Hub | [`varavel/vdl`](https://hub.docker.com/r/varavel/vdl) | | GitHub Container Registry | [`ghcr.io/varavelio/vdl`](https://github.com/varavelio/vdl/pkgs/container/vdl) | The image supports `linux/amd64` and `linux/arm64`. ### Run directly Call `vdl` commands without installing anything on your host, to work with files in your current directory, mount it as a volume: ```bash docker run --rm -v "$(pwd):/workspace" -w /workspace varavel/vdl:latest generate docker run --rm -v "$(pwd):/workspace" -w /workspace varavel/vdl:latest format ``` ### Shell alias If you prefer the native `vdl` experience without installing anything, add an alias to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.): ```bash alias vdl='docker run --rm -v "$(pwd):/workspace" -w /workspace varavel/vdl:latest' ``` Once the alias is in place you can use `vdl` as if it were installed locally: ```bash vdl init vdl generate vdl format ``` ### Pin a version Replace `latest` with a specific version tag to ensure reproducible builds across your team and CI: ```bash docker run --rm varavel/vdl:0.1.0 version ``` ```bash alias vdl='docker run --rm -v "$(pwd):/workspace" -w /workspace varavel/vdl:0.1.0' ``` ### Copy the binary into your own images VDL is useful inside CI pipelines and builder images that need to invoke code generation. Copy the binary directly from the official image without compiling or installing anything: ```dockerfile FROM your-base-image COPY --from=varavel/vdl:latest /usr/local/bin/vdl /usr/local/bin/vdl ``` This places `vdl` on the default `PATH` of your image so it is immediately usable in downstream steps. ### Use the GitHub Container Registry The same image is also published to GHCR. Prefer this registry when you need tighter integration with GitHub Actions or when Docker Hub pull rate limits are a concern: ```bash docker run --rm ghcr.io/varavelio/vdl:latest format ``` ```dockerfile FROM your-base-image COPY --from=ghcr.io/varavelio/vdl:latest /usr/local/bin/vdl /usr/local/bin/vdl ``` ## Manual Downloads You can download prebuilt binaries from the [VDL releases page](https://github.com/varavelio/vdl/releases). Manual installation is useful when you need to: - install VDL in an environment without package managers - mirror binaries internally - pin a binary in a custom CI image - inspect release assets before installing Download the archive for your operating system and architecture, extract it, and place the `vdl` binary somewhere on your `PATH`. ## Choosing A Method - Use the **shell installer** for the fastest Linux/macOS setup. - Use **Homebrew** if you already manage developer tools with Homebrew. - Use the **PowerShell installer** on Windows. - Use **local npm install** for project-pinned VDL versions in Node-based repositories. - Use **Docker** when you want to run VDL without installing anything locally, or when you need to embed the VDL binary inside your own container images. - Use **manual downloads** for custom distribution, offline environments, or CI images. --- Source: https://vdl.varavel.com/docs/guides/cli/ Markdown: https://vdl.varavel.com/docs/guides/cli/index.md Content file: content/docs/guides/cli.md # CLI Commands > Complete reference for the VDL command-line interface. Canonical HTML: https://vdl.varavel.com/docs/guides/cli/ Markdown: https://vdl.varavel.com/docs/guides/cli/index.md ## Overview The `vdl` CLI is the primary way to interact with VDL projects. It covers the full lifecycle: creating a project, writing and formatting schemas, generating code through plugins, compiling to inspect the intermediate representation, and running the language server for editor integration. If you have not installed VDL yet, start with the [installation guide](/docs/guides/installation/). ## Quick Reference | Command | Purpose | | -------------- | ----------------------------------------------- | | `vdl init` | Create a new VDL project with schema and config | | `vdl format` | Format `.vdl` files in place | | `vdl generate` | Run code generation from `vdl.config.vdl` | | `vdl compile` | Compile a `.vdl` file and print its IR as JSON | | `vdl lsp` | Start the VDL language server | | `vdl version` | Show VDL version information | ## Global Behavior A few things work everywhere, no matter which subcommand you use. ### Help Every command supports `--help` and `-h`: ```bash vdl --help vdl init --help vdl generate --help ``` Help output includes the VDL logo plus a description of the command and its flags. ### Version Use `--version`, `-v`, or the `version` subcommand to see the installed VDL version: ```bash vdl --version vdl version ``` ### Default Output Running `vdl` with no subcommand shows the VDL logo and version. ## `vdl init` Initialize a new VDL project in a directory. ```bash vdl init vdl init ./my-project ``` ### What It Creates Two files are written into the target directory: | File | Purpose | | ---------------- | ------------------------------------------------- | | `schema.vdl` | A sample schema with types, constants, and RPCs | | `vdl.config.vdl` | A commented config file ready for code generation | The sample config includes commented-out plugin examples so you can uncomment the ones you need or add more as needed. ### Arguments | Argument | Required | Description | | -------- | -------- | ---------------------------------------------------------- | | `path` | no | Target directory. Defaults to the current directory (`.`). | ## `vdl format` Format `.vdl` files in place. ```bash vdl format vdl format ./schemas/**/*.vdl vdl format ./schemas ./other vdl format --verbose ``` ### How It Works The formatter uses VDL's own lexer and parser to normalize whitespace, indentation, inline spacing, and blank lines so every file follows a consistent style. Files are overwritten in place. ### Arguments | Argument | Required | Description | | ----------- | -------- | ----------------------------------------------------------- | | `patterns` | no | Glob patterns or directory paths. Defaults to `./**/*.vdl`. | | `--verbose` | no | Print each file path as it is formatted. | ### Pattern Behavior - Plain paths like `./schemas` are treated as directories and expanded to `./schemas/**/*.vdl` automatically. - Standard glob patterns use double-star syntax (`**`) for recursive matching. - Files without a `.vdl` extension are skipped. - Duplicate matches are deduplicated, so the same file is never formatted twice. ### Examples Format every VDL file in the current project: ```bash vdl format ``` Format a specific set of files with feedback: ```bash vdl format ./api/*.vdl ./shared/*.vdl --verbose ``` Format everything inside a directory: ```bash vdl format ./schemas ``` ## `vdl generate` Run code generation from a `vdl.config.vdl` project. ```bash vdl generate vdl generate ./my-project vdl generate --check ``` ### What It Does The generation pipeline works like this: 1. VDL finds and reads `vdl.config.vdl`. 2. Pre-generation hooks run (if configured). The first failure stops the pipeline. 3. Each configured plugin source is resolved: - **Local `.js` files** are loaded directly. - **HTTPS URLs** are fetched and cached. - **GitHub shorthands** like `owner/vdl-plugin-go@v0.1.3` resolve to a remote `dist/index.js` artifact, which is fetched and cached. 4. Each plugin's configured `.vdl` schema is analyzed and converted into the Intermediate Representation (IR). 5. All plugins execute concurrently in an embedded JavaScript runtime. 6. Output file paths are validated to stay inside their configured `outDir`. 7. Conflicting writes between plugins are detected and fail the run. 8. Output directories are cleaned (default) or merged, and generated files are written. 9. Post-generation hooks run (failures print warnings but do not roll back files). 10. The `vdl.lock` file is updated with remote plugin hashes. ### Arguments | Argument | Required | Description | | --------- | -------- | ------------------------------------------------------------------- | | `path` | no | Directory or path to `vdl.config.vdl`. Defaults to `.` (cwd). | | `--check` | no | Run the full pipeline but skip writing output files. Useful for CI. | ### Config File Discovery When `path` points to a directory, VDL looks for a file named exactly `vdl.config.vdl` inside it. When `path` points directly to a file, that file is used. The config file must declare a `const config` with the generation settings. For all configuration options, see the [project configuration guide](/docs/guides/configuration/). ### `--check` Mode Use `--check` in CI to verify that generation would succeed without producing side effects: ```bash vdl generate --check ``` In check mode, VDL runs the full pipeline—hooks, plugin resolution, schema analysis, plugin execution, and output validation—but skips writing files, updating `vdl.lock` and executing post generation hooks. If the pipeline fails, the command exits with a non-zero code, making it suitable for linting and CI workflows. ### Lock File Remote plugin artifacts are cached and their content hashes are recorded in `vdl.lock`. Commit this file when your project depends on remote plugins. VDL uses it to detect unexpected changes in cached plugins. ## `vdl compile` Compile a `.vdl` file and print its Intermediate Representation (IR) as formatted JSON to stdout. ```bash vdl compile ./schema.vdl ``` ### What It Produces The IR is a machine-readable description of everything VDL understands about your schema: types, enums, constants, annotations, documentation, and relationships. It is the same IR that plugins receive as `input.ir`. ### Arguments | Argument | Required | Description | | -------- | -------- | ----------------------------------- | | `file` | yes | Path to the `.vdl` file to compile. | ### Use Cases - Inspect what your schema looks like after analysis, before generation. - Debug why a plugin produces unexpected output. - Pipe the IR into custom tooling: ```bash vdl compile ./schema.vdl | jq '.types | length' vdl compile ./schema.vdl > schema-ir.json ``` ### Error Handling If the schema has errors, diagnostics are printed to stderr and the command exits with code 1. No partial JSON is emitted. ## `vdl lsp` Start the VDL language server. ```bash vdl lsp vdl lsp --log-path ``` ### How It Works The language server speaks the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) over stdin and stdout. It provides editor features for `.vdl` files: - Go-to-definition - Hover information - Find references - Rename - Completions - Document symbols - Document links You typically do not run `vdl lsp` directly. Your editor plugin starts it and communicates with it over stdio. See the [VS Code extension](https://github.com/varavelio/vdl/blob/main/integrations/editors/vscode/README.md) or [Neovim setup](https://github.com/varavelio/vdl/blob/main/integrations/editors/neovim/README.md) for editor-specific instructions. ### Arguments | Argument | Required | Description | | ------------ | -------- | ------------------------------------------------------------------ | | `--log-path` | no | Print the path to the LSP log file instead of starting the server. | ### Log Files The LSP writes detailed diagnostic logs to a file under the VDL logs directory (`~/.vdl/logs/` by default). Use `--log-path` to find the log file location, then inspect it when debugging editor behavior. ```bash vdl lsp --log-path # /home/you/.vdl/logs/vdl-lsp.log cat /home/you/.vdl/logs/vdl-lsp.log ``` ## `vdl version` Show VDL version information. ```bash vdl version vdl --version vdl -v ``` The output includes the VDL logo, version number, and links to the repository. ## The VDL Home Directory VDL stores caches and logs under a home directory outside your project. ### Location By default, the VDL home directory is `~/.vdl`. You can override it with the `VDL_HOME` environment variable: ```bash export VDL_HOME=/custom/path/.vdl ``` If no user home directory is available, VDL falls back to your system temp directory. ### Contents | Directory | Purpose | | --------- | -------------------------------------------------- | | `cache/` | Cached remote plugin artifacts for reproducibility | | `logs/` | Log files written by the language server | ## Environment Variables | Variable | Purpose | | ------------------------- | -------------------------------------------------------------------------------- | | `VDL_HOME` | Override the VDL home directory (default: `~/.vdl`). | | `VDL_INSECURE_ALLOW_HTTP` | Allow `http://` URLs for local plugin development. Set to `true`. | | `VDL_SKIP_HOST_HOOKS` | Skip pre-generation and post-generation hooks. Set to `true`. | | `VDL_CLOUD` | Skip host hooks (for cloud CI environments where shell hooks are not supported). | ## Common Workflows ### Start A New Project ```bash vdl init ./my-service cd ./my-service # Edit schema.vdl and vdl.config.vdl vdl generate ``` ### Format In CI ```bash vdl format --verbose git diff --exit-code || echo "Unformatted VDL files detected" ``` ### Check Generation In CI ```bash vdl generate --check ``` ### Explore A Schema ```bash # See how many types a schema has vdl compile ./schema.vdl | jq '.types | length' # List type names vdl compile ./schema.vdl | jq '.types[].name' # Inspect enum values vdl compile ./schema.vdl | jq '.enums[] | {name, values: [.members[].name]}' ``` ### Debug Plugin Behavior ```bash # Compare the IR a plugin will receive vdl compile ./schema.vdl > debug-ir.json # Run generation in check mode vdl generate --check ``` ### Repeated Generation Workflow ```bash # Format first, then generate vdl format vdl generate ``` ## Troubleshooting ### `vdl generate` cannot find the config file Make sure the file is named exactly `vdl.config.vdl` and that you are running the command from the correct directory. Pass an explicit path if needed: ```bash vdl generate ./path/to/vdl.config.vdl ``` ### `vdl generate` fails on a remote plugin Check that the plugin version exists and that the repository is public (or configured with the correct `remotes` block). Remote plugins are cached under the VDL cache directory—clear it with: ```bash rm -rf ~/.vdl/cache ``` ### `vdl format` does not find any files By default, `vdl format` looks for `./**/*.vdl`. If your VDL files use a different extension or are in an unexpected location, pass explicit patterns: ```bash vdl format ./my-schemas/**/*.vdl ``` ### The language server is not responding Check the LSP logs: ```bash vdl lsp --log-path # find the log file cat # inspect it ``` Make sure your editor extension is configured to launch `vdl lsp` and not a different binary. --- Source: https://vdl.varavel.com/docs/guides/editors/ Markdown: https://vdl.varavel.com/docs/guides/editors/index.md Content file: content/docs/guides/editors.md # Editor Integration > Set up VDL support in your code editor. Canonical HTML: https://vdl.varavel.com/docs/guides/editors/ Markdown: https://vdl.varavel.com/docs/guides/editors/index.md ## Overview VDL works with any editor that supports the Language Server Protocol (LSP). First-class integrations are available for Visual Studio Code and it's forks. ## Visual Studio Code The official VDL extension provides full language support for Visual Studio Code and any VSCode-based editor (VSCodium, Cursor, Windsurf, and others). ### Installation Install the extension from either marketplace: - [Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=varavel.vdl) - [Open VSX Registry](https://open-vsx.org/extension/varavel/vdl) The extension is plug and play. The only requirement is having the `vdl` binary installed on your system. If you have not installed the CLI yet, follow the [installation guide](/docs/guides/installation/). Extension source code: [integrations/editors/vscode](https://github.com/varavelio/vdl/tree/main/integrations/editors/vscode) ### Features Once installed, opening any `.vdl` file gives you: - Syntax highlighting - Error highlighting and diagnostics - Code autocompletion - Auto-formatting on save - Go-to-definition, hover info, find references, and rename - Code snippets for types, fields, enums, constants, annotations, and more For configuration options, available commands, and troubleshooting, see the extension page on the [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=varavel.vdl) or [Open VSX](https://open-vsx.org/extension/varavel/vdl). ## Neovim VDL's built-in LSP works with Neovim's native LSP client. The language server provides go-to-definition, hover, references, rename, completions, document symbols, and document links for `.vdl` files. Syntax highlighting is not included through the LSP and may be added in a future release. The `vdl` binary must be installed and available on your `PATH`. For setup instructions, see the [Neovim integration README](https://github.com/varavelio/vdl/tree/main/integrations/editors/neovim). ## Other Editors The `vdl lsp` command speaks the standard Language Server Protocol over stdin and stdout, so any editor with LSP support can integrate with it. Point your editor's LSP client to `vdl lsp` for `.vdl` files. VDL's LSP does not provide syntax highlighting on its own. Some editors require a separate TextMate grammar ([vdl.tmLanguage.json](https://github.com/varavelio/vdl/blob/main/integrations/syntax/textmate/vdl.tmLanguage.json)) or Tree-sitter parser for highlighting. Syntax highlighting for editors beyond VSCode is planned for a future release. To get started, check your editor's documentation on configuring a [custom language server](https://microsoft.github.io/language-server-protocol/implementors/servers/). --- Source: https://vdl.varavel.com/docs/guides/configuration/ Markdown: https://vdl.varavel.com/docs/guides/configuration/index.md Content file: content/docs/guides/configuration.md # Project Configuration > Configure VDL generation with vdl.config.vdl. Canonical HTML: https://vdl.varavel.com/docs/guides/configuration/ Markdown: https://vdl.varavel.com/docs/guides/configuration/index.md ## What `vdl.config.vdl` Does `vdl.config.vdl` tells `vdl generate` which plugins to run, which schema each plugin should read, where generated files should be written, and which plugin-specific options should be passed in. The file is itself written in VDL. VDL reads a constant named `config` from this file. ```vdl const config = { version 1 plugins [ { src "varavelio/vdl-plugin-ts@v0.1.4" schema "./schema.vdl" outDir "./gen/ts" } ] } ``` The file must be named exactly: ```text vdl.config.vdl ``` ## Minimal Configuration ```vdl const config = { version 1 plugins [ { src "varavelio/vdl-plugin-json-schema@v0.1.0" schema "./schema.vdl" outDir "./gen/json-schema" } ] } ``` This configuration: - uses config format version `1` - runs one plugin - analyzes `./schema.vdl` - writes generated files under `./gen/json-schema` ## Top-Level Fields | Field | Required | Description | | ------------- | -------- | --------------------------------------------------------------- | | `version` | yes | Configuration format version. Currently use `1`. | | `cleanOutDir` | no | Whether VDL cleans output directories before writing new files. | | `plugins` | no | List of plugin runs to execute. | | `remotes` | no | Authentication settings for private plugin hosts. | | `hooks` | no | Host shell commands run before or after generation. | ## `version` Set `version` to `1`. ```vdl const config = { version 1 } ``` VDL rejects unsupported config versions. ## `plugins` `plugins` is an array of plugin configurations. ```vdl const config = { version 1 plugins [ { src "varavelio/vdl-plugin-go@v0.1.3" schema "./schema.vdl" outDir "./gen/go" options { package "contracts" } } ] } ``` Each plugin entry has these fields: | Field | Required | Description | | ---------------- | -------- | ------------------------------------------------------------------- | | `src` | yes | Plugin JavaScript source. | | `schema` | yes | Path to the `.vdl` schema this plugin should process. | | `outDir` | yes | Directory where plugin output files should be written. | | `generateHeader` | no | Whether VDL adds generated-file header comments. Default is `true`. | | `options` | no | Plugin-specific string options passed through as-is. | ## Plugin Sources `src` can use one of three forms. ### GitHub Shorthand ```vdl src "varavelio/vdl-plugin-go@v0.1.3" ``` This resolves to: ```text https://raw.githubusercontent.com/varavelio/vdl-plugin-go/v0.1.3/dist/index.js ``` Rules: - use `owner/repo@ref` - the repository name must start with `vdl-plugin-` - the plugin artifact must exist at `dist/index.js` - prefer release tags or full commit hashes for reproducibility ### Local JavaScript File ```vdl src "./plugins/my-plugin.js" ``` Local plugin paths must point to `.js` files and must start with `.` or `/`. Use this while developing a plugin locally or for private in-repo generators. ### Remote HTTPS URL ```vdl src "https://example.com/plugins/my-plugin/index.js" ``` Remote plugin URLs must point to `.js` files. HTTPS is required by default. For local development, insecure HTTP can be enabled with `VDL_INSECURE_ALLOW_HTTP=true`. ## `schema` `schema` points to the VDL file the plugin should compile. ```vdl schema "./schema.vdl" ``` The path is resolved relative to `vdl.config.vdl` and must end with `.vdl`. Different plugins can process the same schema: ```vdl const config = { version 1 plugins [ { src "varavelio/vdl-plugin-go@v0.1.3" schema "./schema.vdl" outDir "./gen/go" } { src "varavelio/vdl-plugin-json-schema@v0.1.0" schema "./schema.vdl" outDir "./gen/json-schema" } ] } ``` ## `outDir` `outDir` is the root directory for files returned by a plugin. ```vdl outDir "./gen/go" ``` Plugins return relative file paths like `types.go` or `models/user.ts`. VDL writes them under `outDir`. VDL validates generated paths so plugins cannot write outside `outDir`. If two plugin outputs try to write the same final file path in one generation run, generation fails. ## `options` `options` is a map of string values passed to the plugin. ```vdl options { package "contracts" strict "true" importExtension "js" } ``` All option values are strings. If a plugin option behaves like a boolean, pass values such as `"true"` or `"false"` according to that plugin's documentation. Options are plugin-specific. Always check the plugin README or [available plugins guide](/docs/guides/plugins/). ## `generateHeader` By default, VDL adds generated-file headers when writing plugin outputs. ```vdl const config = { version 1 plugins [ { src "varavelio/vdl-plugin-go@v0.1.3" schema "./schema.vdl" outDir "./gen/go" generateHeader false } ] } ``` Use `generateHeader false` only when a target format cannot safely contain comments or when the plugin already handles headers. ## `cleanOutDir` By default, VDL replaces configured output directories with the newly generated files. ```vdl const config = { version 1 cleanOutDir true plugins [ { src "varavelio/vdl-plugin-ts@v0.1.4" schema "./schema.vdl" outDir "./gen/ts" } ] } ``` Set `cleanOutDir false` to merge generated files into existing directories without deleting files that are not part of the current output. ```vdl const config = { version 1 cleanOutDir false plugins [ { src "./plugins/custom.js" schema "./schema.vdl" outDir "./gen/custom" } ] } ``` Use merge mode carefully. Old generated files may remain if a plugin stops producing them. ## `remotes` Use `remotes` when a plugin is hosted behind authentication. ```vdl const config = { version 1 remotes [ { host "github.com/my-org/private-vdl-plugin" auth { github { tokenEnv "GITHUB_TOKEN" } } } ] plugins [ { src "my-org/private-vdl-plugin@v1.0.0" schema "./schema.vdl" outDir "./gen/private" } ] } ``` `host` should not include `https://` or `http://`. When multiple remotes match a plugin URL, VDL uses the most specific host. Supported authentication forms: - GitHub token - custom header - bearer token - basic auth Each remote auth entry should define exactly one authentication method. ### GitHub Token ```vdl remotes [ { host "github.com/my-org/private-vdl-plugin" auth { github { tokenEnv "GITHUB_TOKEN" } } } ] ``` VDL reads the token from the named environment variable and sends it as a bearer token. ### Custom Header ```vdl remotes [ { host "plugins.example.com" auth { header { nameEnv "PLUGIN_HEADER_NAME" valueEnv "PLUGIN_HEADER_VALUE" } } } ] ``` ### Bearer Token ```vdl remotes [ { host "plugins.example.com" auth { bearer { tokenEnv "PLUGIN_TOKEN" } } } ] ``` ### Basic Auth ```vdl remotes [ { host "plugins.example.com" auth { basic { userEnv "PLUGIN_USER" passEnv "PLUGIN_PASS" } } } ] ``` ## `hooks` Hooks run shell commands on the host machine. ```vdl const config = { version 1 hooks { preGenerate ["npm run check"] postGenerate ["npm run format"] } plugins [ { src "varavelio/vdl-plugin-ts@v0.1.4" schema "./schema.vdl" outDir "./gen/ts" } ] } ``` Hook behavior: - `preGenerate` commands run before plugins. The first failure stops generation. - `postGenerate` commands run after files are written. Failures are printed as warnings and do not roll back generated files. - commands run from the directory containing `vdl.config.vdl` - hooks are skipped when `VDL_SKIP_HOST_HOOKS` or `VDL_CLOUD` is truthy ## Lock File Remote plugin downloads are cached and recorded in `vdl.lock`. The lock file stores hashes for remote plugin artifacts so VDL can detect unexpected changes. Commit `vdl.lock` when your project depends on remote plugins. ## Complete Example ```vdl const config = { version 1 cleanOutDir true hooks { preGenerate ["npm run check"] postGenerate ["npm run format"] } plugins [ { src "varavelio/vdl-plugin-go@v0.1.3" schema "./schema.vdl" outDir "./gen/go" options { package "contracts" strict "true" } } { src "varavelio/vdl-plugin-json-schema@v0.1.0" schema "./schema.vdl" outDir "./gen/json-schema" options { outFile "schema.json" root "User" } } ] } ``` ## Common Patterns Generate data models and documentation from the same schema: ```vdl const config = { version 1 plugins [ { src "varavelio/vdl-plugin-ts@v0.1.4" schema "./schema.vdl" outDir "./generated/types" } { src "varavelio/vdl-plugin-explorer@v0.1.1" schema "./schema.vdl" outDir "./generated/docs" } ] } ``` Generate Go models and Go RPC code into separate packages: ```vdl const config = { version 1 plugins [ { src "varavelio/vdl-plugin-go@v0.1.3" schema "./schema.vdl" outDir "./internal/contracts" options { package "contracts" } } { src "varavelio/vdl-plugin-rpc-go@v0.1.2" schema "./schema.vdl" outDir "./internal/rpcclient" options { target "client" package "rpcclient" typesImport "example.com/project/internal/contracts" } } ] } ``` ## Troubleshooting If `vdl generate` cannot find the config file, make sure the file is named exactly `vdl.config.vdl`. If a plugin source fails to resolve, check whether `src` is a local `.js` path, an HTTPS `.js` URL, or a valid GitHub shorthand. If a private plugin fails to download, check the matching `remotes` entry and the required environment variables. If generated files disappear, check `cleanOutDir`. The default behavior is to replace output directories. --- Source: https://vdl.varavel.com/docs/guides/plugins/ Markdown: https://vdl.varavel.com/docs/guides/plugins/index.md Content file: content/docs/guides/plugins.md # Available Plugins > Official VDL plugins and what each one generates. Canonical HTML: https://vdl.varavel.com/docs/guides/plugins/ Markdown: https://vdl.varavel.com/docs/guides/plugins/index.md ## Overview VDL generates output through plugins. Each plugin receives the resolved VDL IR plus the plugin options from `vdl.config.vdl`, then returns files for VDL to write into the configured `outDir`. This page summarizes the current official plugin ecosystem. ## Data Model Plugins ### `varavelio/vdl-plugin-go` Generates Go data-model code from VDL types, enums, and constants. Use it when you want Go structs, enum-like types, constants, strict JSON validation, optional-field pointer handling, and `datetime` mapped to `time.Time`. Typical outputs include `types.go`, `enums.go`, `constants.go`, and `pointers.go`. Common options: | Option | Purpose | | ----------------- | ------------------------------------------------------ | | `package` | Go package name. | | `genConsts` | Enable or disable constant generation. | | `strict` | Enable stricter JSON validation. | | `genPointerUtils` | Generate `Ptr`, `Val`, and `Or` helpers. | | `jsonPackage` | Use a compatible JSON package such as `goccy/go-json`. | Example: ```vdl { src "varavelio/vdl-plugin-go@v0.1.3" schema "./schema.vdl" outDir "./gen/go" options { package "contracts" } } ``` Repository: [varavelio/vdl-plugin-go](https://github.com/varavelio/vdl-plugin-go) ### `varavelio/vdl-plugin-ts` Generates TypeScript data-model code from VDL types, enums, and constants. Use it when you want typed interfaces, enum helpers, constants, `Date` hydration for `datetime`, and optional runtime validation helpers. Typical outputs include `types.ts`, `enums.ts`, `index.ts`, and optionally `constants.ts`. Common options: | Option | Purpose | | ----------------- | ------------------------------------------- | | `genConsts` | Enable or disable constant generation. | | `strict` | Generate runtime `validate(...)` helpers. | | `importExtension` | Use `.js`, `.ts`, or extensionless imports. | Example: ```vdl { src "varavelio/vdl-plugin-ts@v0.1.4" schema "./schema.vdl" outDir "./gen/ts" options { importExtension "js" } } ``` Repository: [varavelio/vdl-plugin-ts](https://github.com/varavelio/vdl-plugin-ts) ### `varavelio/vdl-plugin-json-schema` Generates JSON Schema Draft 2020-12 from VDL types and enums. Use it when you need a standard machine-readable schema for validators, form builders, documentation systems, API gateways, or external integrations. The plugin emits all top-level VDL types and enums under `$defs`, supports references between definitions, maps non-optional fields to JSON Schema `required`, and carries documentation and `@deprecated` metadata into the generated schema. Common options: | Option | Purpose | | --------- | ------------------------------------------------------ | | `outFile` | Output JSON filename. | | `id` | Top-level JSON Schema `$id`. | | `root` | Top-level `$ref` pointing to one generated definition. | Example: ```vdl { src "varavelio/vdl-plugin-json-schema@v0.1.0" schema "./schema.vdl" outDir "./gen/json-schema" options { outFile "schema.json" root "Account" } } ``` Repository: [varavelio/vdl-plugin-json-schema](https://github.com/varavelio/vdl-plugin-json-schema) ## RPC Plugins RPC support is annotation-based. These plugins look for `@rpc` service types and `@proc` or `@stream` operation fields. ### `varavelio/vdl-plugin-rpc-go` Generates Go RPC clients or servers from annotation-based VDL RPC services. This plugin is RPC-only. It does not generate data models; pair it with `varavelio/vdl-plugin-go` and point `typesImport` to the generated Go type package when the RPC output is in a separate package. For `target "client"`, it emits typed client builders, procedure and stream execution APIs, headers, interceptors, retry and timeout policies, reconnect policies, and stream hooks. For `target "server"`, it emits typed handler registration APIs, middleware hooks, stream configuration, error handling, and `net/http` adapter helpers. Common options: | Option | Purpose | | ------------- | ------------------------------------------------------------ | | `target` | Required. `client` or `server`. | | `typesImport` | Go import path for the package generated by `vdl-plugin-go`. | | `package` | Generated Go package name. | | `jsonPackage` | Compatible JSON package imported as `json`. | Example: ```vdl { src "varavelio/vdl-plugin-rpc-go@v0.1.2" schema "./schema.vdl" outDir "./internal/client" options { target "client" package "client" typesImport "example.com/project/internal/types" } } ``` Repository: [varavelio/vdl-plugin-rpc-go](https://github.com/varavelio/vdl-plugin-rpc-go) ### `varavelio/vdl-plugin-rpc-ts` Generates TypeScript RPC clients or servers from annotation-based VDL RPC services. This plugin is RPC-only. It expects data models from `varavelio/vdl-plugin-ts` and references them through `typesImport`. For `target "client"`, it emits a typed client with procedure and stream builders, headers, interceptors, retries, timeouts, reconnect policies, and stream controls. For `target "server"`, it emits a typed server with procedure and stream registration APIs, middleware, error handlers, ping configuration, and Node.js plus Fetch-compatible HTTP adapters. Common options: | Option | Purpose | | ----------------- | -------------------------------------------------------- | | `target` | Required. `client` or `server`. | | `typesImport` | Required import path to the generated TypeScript models. | | `importExtension` | Internal generated import extension behavior. | Example: ```vdl { src "varavelio/vdl-plugin-rpc-ts@v0.1.0" schema "./schema.vdl" outDir "./generated/client" options { target "client" typesImport "../types/index.js" importExtension "js" } } ``` Repository: [varavelio/vdl-plugin-rpc-ts](https://github.com/varavelio/vdl-plugin-rpc-ts) ### `varavelio/vdl-plugin-rpc-openapi` Generates OpenAPI 3.0 documents for VDL RPC procedures and streams. Use it when you want to publish an OpenAPI contract for your VDL RPC layer, import requests into tools such as Postman, or feed OpenAPI-compatible docs, gateway, validation, or generation workflows. It emits one `POST` path per operation, groups operations by service, emits request and response envelopes, and includes non-RPC VDL types and enums under `components.schemas`. Common options: | Option | Purpose | | ---------------- | --------------------------------------------------------- | | `outFile` | OpenAPI output file, usually `.yaml`, `.yml`, or `.json`. | | `playgroundFile` | Optional standalone HTML docs file. | | `playgroundUi` | `swagger-ui`, `scalar`, or `stoplight-elements`. | | `title` | OpenAPI `info.title`. | | `version` | OpenAPI `info.version`. | | `description` | OpenAPI `info.description`. | | `baseUrl` | One or more comma-separated server URLs. | Example: ```vdl { src "varavelio/vdl-plugin-rpc-openapi@v0.1.3" schema "./schema.vdl" outDir "./gen/openapi" options { outFile "openapi.yaml" title "Messaging API" version "1.0.0" } } ``` Repository: [varavelio/vdl-plugin-rpc-openapi](https://github.com/varavelio/vdl-plugin-rpc-openapi) ## Event Plugins ### `varavelio/vdl-plugin-events-go` Generates Go subject builders and a centralized event catalog from VDL types annotated with `@event`. This plugin is event-focused and only processes `@event` object types. It does not generate event payload structs; pair it with `varavelio/vdl-plugin-go` for payload models. It validates event subject templates, checks `{fieldName}` placeholders against top-level primitive fields, supports repeated placeholders, formats non-string placeholders, and emits a typed `VDLEventCatalog` with `BuildSubject` functions. Common options: | Option | Purpose | | --------- | -------------------------- | | `package` | Generated Go package name. | | `outFile` | Generated Go filename. | Example: ```vdl { src "varavelio/vdl-plugin-events-go@v0.1.3" schema "./schema.vdl" outDir "./gen/events" options { package "events" } } ``` Repository: [varavelio/vdl-plugin-events-go](https://github.com/varavelio/vdl-plugin-events-go) ## Documentation And Metadata Plugins ### `varavelio/vdl-plugin-explorer` Generates a standalone HTML explorer for VDL docs, RPCs, types, enums, and constants. Use it when you want to share schema documentation with teammates, clients, or partners without running a server. The generated HTML embeds the schema data directly, includes search and navigation, supports light and dark themes, and works as a static file. Common options: | Option | Purpose | | ------------ | ----------------------------------------------------- | | `outFile` | Output HTML filename. | | `rpcBaseUrl` | Base URL shown for generated RPC operation endpoints. | Example: ```vdl { src "varavelio/vdl-plugin-explorer@v0.1.1" schema "./schema.vdl" outDir "./docs" options { outFile "schema-explorer.html" } } ``` Repository: [varavelio/vdl-plugin-explorer](https://github.com/varavelio/vdl-plugin-explorer) ### `varavelio/vdl-plugin-meta` Exports the VDL Intermediate Representation as JSON. Use it when you need reflection-like schema metadata at runtime, for internal tools, dynamic forms, custom validators, explorers, or downstream generators. Common options: | Option | Purpose | | ------------------ | ----------------------------------------------- | | `outFile` | Output JSON filename. | | `includePositions` | Include source file, line, and column metadata. | Example: ```vdl { src "varavelio/vdl-plugin-meta@v1.0.1" schema "./schema.vdl" outDir "./gen/meta" options { outFile "vdl-meta.json" includePositions "false" } } ``` Repository: [varavelio/vdl-plugin-meta](https://github.com/varavelio/vdl-plugin-meta) ## Combining Plugins You can run several plugins against the same schema in one `vdl generate` call. ```vdl const config = { version 1 plugins [ { src "varavelio/vdl-plugin-go@v0.1.3" schema "./schema.vdl" outDir "./gen/go" options { package "contracts" } } { src "varavelio/vdl-plugin-json-schema@v0.1.0" schema "./schema.vdl" outDir "./gen/json-schema" } { src "varavelio/vdl-plugin-explorer@v0.1.1" schema "./schema.vdl" outDir "./docs/schema" } ] } ``` Use separate `outDir` values unless you intentionally want related generated files in the same package. VDL prevents two plugin outputs from writing the same file path in one generation run. ## Version Note The examples above use the versions documented in each plugin README at the time this page was written. Check each plugin repository for the latest release tag before pinning a new project. --- Source: https://vdl.varavel.com/docs/guides/creating-plugins/ Markdown: https://vdl.varavel.com/docs/guides/creating-plugins/index.md Content file: content/docs/guides/creating-plugins.md # Creating Plugins > A practical guide to writing VDL plugins. Canonical HTML: https://vdl.varavel.com/docs/guides/creating-plugins/ Markdown: https://vdl.varavel.com/docs/guides/creating-plugins/index.md > **Note:** You do not need Node.js or any JavaScript runtime installed on your machine. VDL executes plugin JavaScript code through [Goja](https://github.com/dop251/goja), an embedded ECMAScript runtime built into the VDL binary. ## The Short Version A VDL plugin is just a JavaScript file that exports a `generate(input)` function. ```js exports.generate = (input) => { return { files: [ { path: "summary.txt", content: `This schema has ${input.ir.types.length} types.\n`, }, ], }; }; ``` Then reference it from `vdl.config.vdl`: ```vdl const config = { version 1 plugins [ { src "./plugins/summary.js" schema "./schema.vdl" outDir "./gen" } ] } ``` Run: ```bash vdl generate ``` VDL compiles `schema.vdl`, calls your plugin function, and writes the returned files under `./gen`. ## How Plugins Work The generation flow is simple. 1. VDL reads `vdl.config.vdl`. 2. VDL resolves each configured plugin source. 3. VDL analyzes the configured `schema` file. 4. VDL converts the valid schema into the generator-facing IR. 5. VDL executes the plugin's `generate(input)` function in a JavaScript runtime. 6. The plugin returns generated files or structured errors. 7. VDL validates output paths and writes files inside `outDir`. The plugin input is defined by the canonical [`schemas/plugin_input.vdl`](https://github.com/varavelio/vdl/blob/main/schemas/plugin_input.vdl) contract and has this shape: ```ts type PluginInput = { version: string; ir: IrSchema; options: Record; }; ``` The plugin output is defined by the canonical [`schemas/plugin_output.vdl`](https://github.com/varavelio/vdl/blob/main/schemas/plugin_output.vdl) contract and has this shape: ```ts type PluginOutput = { files?: Array<{ path: string; content: string; }>; errors?: Array<{ message: string; position?: { file: string; line: number; column: number; }; }>; }; ``` `input.version` is the VDL version. `input.ir` is the fully resolved Intermediate Representation of the schema. `input.options` contains the key-value options from the plugin block in `vdl.config.vdl`. Options are strings in the config schema. If you need booleans, numbers, lists, or enums, parse and validate them in your plugin. ## Minimal JavaScript Plugin Create `plugins/models-list.js`: ```js exports.generate = (input) => { const lines = []; lines.push("# VDL Types"); lines.push(""); for (const typeDef of input.ir.types) { lines.push(`- ${typeDef.name}`); } return { files: [ { path: "models.md", content: `${lines.join("\n")}\n`, }, ], }; }; ``` Configure it: ```vdl const config = { version 1 plugins [ { src "./plugins/models-list.js" schema "./schema.vdl" outDir "./gen/docs" } ] } ``` Run: ```bash vdl generate ``` Result: ```text gen/docs/models.md ``` ## Export Formats VDL accepts any of these forms: ```js exports.generate = (input) => ({ files: [] }); ``` ```js module.exports.generate = (input) => ({ files: [] }); ``` ## Important Runtime Rules - The plugin runs as JavaScript, not as a full Node.js process. - Keep the release artifact self-contained; do not rely on runtime filesystem or npm package loading. - `console.log`, `console.info`, `console.warn`, and `console.error` are available for plugin logs. - Returned file paths must be relative and must stay inside `outDir`. - If the plugin returns `errors`, VDL stops and does not write generated files. - Throwing an unexpected error fails the plugin run. - Remote plugins should be pinned to a release tag or full commit hash for reproducibility. ## Plugin Sources `src` in `vdl.config.vdl` can point to several kinds of plugin artifact. | Source kind | Example | Notes | | ---------------- | ----------------------------------------- | ----------------------------------------- | | Local `.js` file | `./plugins/my-plugin.js` | Must start with `.` or `/`. | | HTTPS `.js` URL | `https://example.com/vdl-plugin/index.js` | Must point to a `.js` file. | | GitHub shorthand | `varavelio/vdl-plugin-go@v0.1.3` | Resolves to `dist/index.js` in that repo. | GitHub shorthand repositories must be named with the `vdl-plugin-` prefix. VDL caches remote plugin files and records hashes in `vdl.lock`. ## Authenticated Remote Plugins Private plugin hosts can be configured through `remotes` in `vdl.config.vdl`. VDL matches each plugin URL against the most specific configured remote host and reads credentials from environment variables. ```vdl const config = { version 1 remotes [ { host "github.com/my-org/private-vdl-plugin" auth { github { tokenEnv "GITHUB_TOKEN" } } } ] plugins [ { src "my-org/private-vdl-plugin@v1.0.0" schema "./schema.vdl" outDir "./gen/private" } ] } ``` Supported auth styles are GitHub token, custom header, bearer token, and basic auth. ## Using The Plugin SDK Plain JavaScript is enough for small plugins. For serious plugins, use [`@varavel/vdl-plugin-sdk`](https://github.com/varavelio/vdl-plugin-sdk). The SDK gives you: - typed `definePlugin(...)` authoring API - typed access to the VDL IR and plugin input/output contracts - structured error helpers such as `PluginError`, `fail`, and `assert` - utility imports for strings, arrays, RPC validation, and other common generator tasks - testing builders for realistic IR fixtures - a `vdl-plugin` CLI for type-checking and bundling plugins - TypeScript configuration presets for app and test code Minimal SDK plugin: ```ts import { definePlugin } from "@varavel/vdl-plugin-sdk"; export const generate = definePlugin((input) => { return { files: [ { path: "hello.txt", content: `Hello from VDL ${input.version}\n`, }, ], }; }); ``` > **How exports work:** SDK plugins use `export const generate`, not `exports.generate`. The `vdl-plugin build` command converts the ESM export into `exports.generate` automatically when bundling into `dist/index.js`. The final artifact that VDL loads always uses the CommonJS `exports.generate` form. Check and build: ```bash npx vdl-plugin check npx vdl-plugin build ``` `check` runs TypeScript without emitting files. `build` bundles the plugin into `dist/index.js`, which is the artifact VDL consumes for GitHub shorthand plugins. ## Starting From The Template The fastest way to start a production-ready plugin is [`varavelio/vdl-plugin-template`](https://github.com/varavelio/vdl-plugin-template). The template includes: - `src/index.ts` with a minimal `definePlugin(...)` entrypoint - the VDL plugin SDK - TypeScript config - `@varavel/gen` for structured text/code generation - Biome and dprint for linting and formatting - Vitest for tests - a devcontainer - a GitHub Actions CI workflow - `dist/index.js` as the distributable plugin bundle Basic workflow: ```bash npm install npm run check npm run build npm run test ``` When releasing a GitHub-hosted plugin: 1. Build with `npm run build`. 2. Commit source files and `dist/index.js`. 3. Create a release tag such as `v0.1.0`. 4. Users can reference `owner/vdl-plugin-name@v0.1.0` from `vdl.config.vdl`. VDL plugins do not need to be published to npm. The GitHub release plus committed `dist/index.js` is enough for VDL to fetch the plugin. ## Error Handling Return structured errors when the user's schema or options are invalid. ```ts import { definePlugin, fail } from "@varavel/vdl-plugin-sdk"; export const generate = definePlugin((input) => { const root = input.options.root; if (!root) { fail('Missing required option "root".'); } const typeDef = input.ir.types.find((item) => item.name === root); if (!typeDef) { fail(`Unknown root type "${root}".`); } return { files: [{ path: "root.txt", content: `${typeDef.name}\n` }], }; }); ``` For schema-specific errors, attach a source position when possible. VDL will print the diagnostic with file, line, and column information. ## Testing Plugins The SDK testing entry point helps you build plugin inputs without manually writing a full IR object. ```ts import { field, objectType, pluginInput, primitiveType, schema, typeDef, } from "@varavel/vdl-plugin-sdk/testing"; const input = pluginInput({ options: { root: "User" }, ir: schema({ types: [ typeDef("User", objectType([field("id", primitiveType("string"))])), ], }), }); ``` Pass the input to your plugin handler and assert generated files or returned errors. ## Practical Checklist - Validate plugin options before generating output. - Validate annotation models before assuming a shape such as RPC or events. - Generate deterministic output so diffs stay clean. - Keep generated file paths relative to `outDir`. - Return structured errors for user mistakes. - Use the SDK for TypeScript, typed IR access, bundling, and tests. - Commit `dist/index.js` for GitHub-hosted plugins. - Pin plugin versions in consuming projects. --- Source: https://vdl.varavel.com/docs/language/ Markdown: https://vdl.varavel.com/docs/language/index.md Content file: content/docs/language/_index.md # Language > Learn how to write VDL schemas from first principles. Canonical HTML: https://vdl.varavel.com/docs/language/ Markdown: https://vdl.varavel.com/docs/language/index.md This guide teaches the VDL language step by step. Read the pages in order if you are new to VDL, or jump directly to the topic you need. --- Source: https://vdl.varavel.com/docs/language/start-here/ Markdown: https://vdl.varavel.com/docs/language/start-here/index.md Content file: content/docs/language/start-here.md # Start Here > Learn how to write VDL schemas from first principles. Canonical HTML: https://vdl.varavel.com/docs/language/start-here/ Markdown: https://vdl.varavel.com/docs/language/start-here/index.md ## What You Will Learn This guide teaches the VDL language step by step. It is written for readers who are new to VDL and want to understand how to write schemas confidently. VDL is small on purpose. A schema is made from a few building blocks: - files and comments - `include` statements - docstrings - `type` declarations - field types - `enum` declarations - `const` declarations - annotations - spreads and references Once you understand these pieces, you can model data contracts, RPC services, events, configuration files, JSON schemas, generated code packages, and custom internal contracts. ## A Tiny Schema ```vdl """ Represents a user account. """ type User { id string email string displayName? string createdAt datetime } enum UserStatus { Active = "active" Suspended = "suspended" } const defaultPageSize = 25 ``` This file defines three things: - `User`, an object type with fields - `UserStatus`, a finite set of allowed values - `defaultPageSize`, a reusable constant value ## How To Read VDL Syntax VDL intentionally avoids punctuation that is not needed. Fields do not use colons: ```vdl type Product { id string price float } ``` Object literal entries do not use colons or commas: ```vdl const serverConfig = { host "localhost" port 8080 tls false } ``` Array literal items are separated by whitespace: ```vdl const roles = ["admin" "editor" "viewer"] ``` If you come from JSON, TypeScript, Go, or YAML, this is the main habit to learn: VDL is whitespace-friendly and declaration-oriented. ## Top-Level Declarations At the top level, a `.vdl` file can contain: ```vdl include "./shared.vdl" """ Standalone documentation. """ type User { id string } enum Status { Active } const version = "1.0.0" ``` Only `type`, `enum`, and `const` create named symbols. Includes and standalone docstrings help compose and document the schema. ## Recommended Learning Path Read these pages in order: 1. [Files and Comments](/docs/language/files/) 2. [Includes](/docs/language/includes/) 3. [Docstrings](/docs/language/docstrings/) 4. [Types](/docs/language/types/) 5. [Field Types](/docs/language/field-types/) 6. [Enums](/docs/language/enums/) 7. [Constants and Literals](/docs/language/constants/) 8. [Annotations](/docs/language/annotations/) 9. [Spreads and References](/docs/language/spreads-references/) 10. [Naming and Validation](/docs/language/naming-validation/) For exact grammar-level detail, see the [VDL specification](/docs/reference/spec/). --- Source: https://vdl.varavel.com/docs/language/files/ Markdown: https://vdl.varavel.com/docs/language/files/index.md Content file: content/docs/language/files.md # Files and Comments > How VDL files are structured and how comments work. Canonical HTML: https://vdl.varavel.com/docs/language/files/ Markdown: https://vdl.varavel.com/docs/language/files/index.md ## File Extension VDL schemas are written in files ending with `.vdl`. ```text schema.vdl auth.vdl events.vdl ``` Regular schema files should use lowercase names with numbers or underscores only: ```text user.vdl order_events.vdl api_v1.vdl ``` The special file name `vdl.config.vdl` is reserved for project generation configuration. ## File Contents A VDL file can contain: - `include` statements - standalone docstrings - `type` declarations - `enum` declarations - `const` declarations Example: ```vdl include "./shared.vdl" """ Documentation for this schema. """ type User { id string } enum UserStatus { Active Suspended } const apiVersion = "1.0.0" ``` ## Single-Line Comments Use `//` for a comment that runs to the end of the line. ```vdl // User-facing account data. type Account { id string // Stable account identifier. email string } ``` Comments are ignored by the compiler. They are useful for readers, but they do not appear in the generated IR as documentation. Use docstrings when you want documentation to be part of the schema model. ## Block Comments Use `/* ... */` for multi-line comments. ```vdl /* This schema is shared by the billing and support systems. Keep field names stable because generated clients depend on them. */ type Customer { id string email string } ``` Block comments can appear anywhere whitespace can appear. ## Comments vs Docstrings Use comments for notes to humans reading the source file: ```vdl // Internal note: this name must match the external system. type ExternalAccount { id string } ``` Use docstrings for documentation that plugins should see: ```vdl """ Public account information returned to clients. """ type PublicAccount { id string displayName string } ``` Plugins receive docstrings through the VDL IR. Comments are discarded. ## Whitespace VDL is flexible with whitespace. These are equivalent: ```vdl type User { id string email string } ``` ```vdl type User { id string email string } ``` Prefer the second style. It is easier to read and matches the formatter. --- Source: https://vdl.varavel.com/docs/language/includes/ Markdown: https://vdl.varavel.com/docs/language/includes/index.md Content file: content/docs/language/includes.md # Includes > Split VDL schemas across files with include statements. Canonical HTML: https://vdl.varavel.com/docs/language/includes/ Markdown: https://vdl.varavel.com/docs/language/includes/index.md ## Why Includes Exist As schemas grow, it is useful to split them into focused files. For example: ```text schema.vdl shared.vdl events.vdl ``` Use `include` to bring definitions from another `.vdl` file into the same compilation context. ## Basic Syntax ```vdl include "./shared.vdl" type UserSession { token string account Account } ``` The included file can define types, enums, constants, or documentation used by the current file. Example `shared.vdl`: ```vdl type Account { id string email string } ``` Example `schema.vdl`: ```vdl include "./shared.vdl" type Session { account Account token string } ``` `Session.account` can use `Account` because `schema.vdl` includes `shared.vdl`. ## Relative Paths Include paths are resolved relative to the file that contains the `include` statement. ```vdl include "../shared/types.vdl" include "./events/user_events.vdl" ``` This makes each file self-contained: moving the entry point does not change how nested includes are resolved. ## Include Graphs Includes are recursive. If `schema.vdl` includes `orders.vdl`, and `orders.vdl` includes `money.vdl`, all three files are analyzed together. ```vdl include "./orders.vdl" type Checkout { order Order } ``` Each file is processed once. Circular includes are reported as diagnostics. ## File Name Rules Regular included schema files should have names matching this pattern: ```text [a-z0-9_]+.vdl ``` Good names: ```text shared.vdl order_events.vdl api_v1.vdl ``` Avoid names like: ```text Shared.vdl order-events.vdl schema.backup.vdl ``` ## Config Files Are Not Included `vdl.config.vdl` is the project generation configuration file. It cannot be included as a normal schema file. Keep language schemas and generation configuration separate: ```text schema.vdl # Your contracts vdl.config.vdl # Plugin generation configuration ``` ## Practical Advice - Put shared primitives, aliases, and base objects in files like `shared.vdl`. - Put event contracts in files like `events.vdl` or `user_events.vdl`. - Keep the main entry file small and readable. - Prefer shallow include graphs when possible. --- Source: https://vdl.varavel.com/docs/language/docstrings/ Markdown: https://vdl.varavel.com/docs/language/docstrings/index.md Content file: content/docs/language/docstrings.md # Docstrings > Document VDL schemas with inline and external Markdown docs. Canonical HTML: https://vdl.varavel.com/docs/language/docstrings/ Markdown: https://vdl.varavel.com/docs/language/docstrings/index.md ## What Docstrings Are Docstrings are triple-quoted documentation blocks. ```vdl """ Represents a user account in the product. """ type User { id string } ``` Unlike comments, docstrings are part of the schema model. Plugins can use them to generate docs, comments, JSON Schema descriptions, explorers, and metadata. ## Declaration Docstrings Place a docstring immediately before a `type`, `enum`, or `const` to attach it to that declaration. ```vdl """ A product that can be listed in the catalog. """ type Product { id string name string } """ Lifecycle state of a product. """ enum ProductStatus { Draft Published } """ Current public API version. """ const apiVersion = "1.0.0" ``` ## Field Docstrings Place a docstring immediately before a field to document that field. ```vdl type User { """ Stable unique identifier. """ id string """ Email address used for account notifications. """ email string } ``` Field docstrings also work inside inline objects. ```vdl type Checkout { payment { """ Amount charged in cents. """ amountCents int } } ``` ## Enum Member Docstrings Enum members can have docstrings too. ```vdl enum InvoiceStatus { """ The invoice has been created but not sent. """ Draft """ The invoice has been paid in full. """ Paid } ``` The docstring attaches to the named member that follows it. ## Standalone Docstrings A docstring at the top level can stand alone as schema documentation. ```vdl """ # Billing Schema This schema contains shared billing contracts used by services and jobs. """ type Invoice { id string } ``` A blank line separates a standalone docstring from the next declaration. ## Organizing Field Docstrings Inside object type bodies, a docstring attaches to the field that follows it. This can also be used to make groups of fields easier to scan, but remember that the docstring belongs to the next field. ```vdl type Product { """ Identifiers """ id string sku string """ Public catalog data """ name string description? string } ``` In this example, `""" Identifiers """` documents `id`, and `""" Public catalog data """` documents `name`. Use grouped wording sparingly. Most fields should be documented directly with field-specific descriptions. ## External Markdown Files If a docstring contains only a relative `.md` path, VDL treats it as a reference to an external Markdown file. ```vdl """ ./docs/product.md """ type Product { id string name string } ``` The path is resolved relative to the `.vdl` file that contains the docstring. Standalone external docs are also supported: ```vdl """ ./docs/overview.md """ type Account { id string } ``` ## Good Docstring Style - Explain what a contract means, not just its name. - Mention units when fields are numeric. - Mention formats when strings carry structured values. - Keep generated-code readers in mind. Good: ```vdl type Payment { """ Amount charged in the smallest currency unit, such as cents. """ amountMinor int } ``` Less useful: ```vdl type Payment { """ Amount. """ amountMinor int } ``` --- Source: https://vdl.varavel.com/docs/language/types/ Markdown: https://vdl.varavel.com/docs/language/types/index.md Content file: content/docs/language/types.md # Types > Define object types, aliases, arrays, maps, and inline objects. Canonical HTML: https://vdl.varavel.com/docs/language/types/ Markdown: https://vdl.varavel.com/docs/language/types/index.md ## What Types Are `type` declarations define reusable data shapes. The most common form is an object type: ```vdl type User { id string email string } ``` But a type can also be an alias for another type expression: ```vdl type UserId string type Tags string[] type Metadata map[string] ``` ## Object Types Object types use braces and contain fields. ```vdl type Product { id string name string price float active bool } ``` Each field is written as: ```text fieldName TypeName ``` There are no colons and no commas. ## Optional Fields Add `?` after the field name to make a field optional. ```vdl type UserProfile { userId string displayName? string avatarUrl? string } ``` Optional means the field may be absent in generated representations, depending on the target plugin. ## Type Aliases A type can name any valid field type. ```vdl type UserId string type UserIds string[] type UserMetadata map[string] ``` Aliases are useful when you want semantic names without repeating raw primitives everywhere. ```vdl type EmailAddress string type User { id string email EmailAddress } ``` ## Object Types Can Reference Other Types ```vdl type Address { line1 string line2? string city string country string } type Customer { id string billingAddress Address shippingAddress? Address } ``` References must point to declared types or enums. ## Inline Objects Use an inline object when a nested shape is only useful in one place. ```vdl type CheckoutSession { id string payment { provider string amountCents int currency string } } ``` Inline objects can contain fields, nested inline objects, and spreads. ## Empty Objects An object type can be empty, but it is rarely useful. ```vdl type EmptyObject { } ``` Prefer adding fields when the contract has meaningful data. ## Field Names Must Be Unique Within an object or inline object, each field name must be unique. Good: ```vdl type Product { id string sku string } ``` Invalid: ```vdl type Product { id string id int } ``` ## Naming Type names should use `PascalCase`: ```vdl type UserProfile { displayName string } ``` Field names should use `camelCase`: ```vdl type UserProfile { displayName string createdAt datetime } ``` The analyzer reports diagnostics when names do not follow these conventions. --- Source: https://vdl.varavel.com/docs/language/field-types/ Markdown: https://vdl.varavel.com/docs/language/field-types/index.md Content file: content/docs/language/field-types.md # Field Types > Learn primitives, arrays, maps, custom types, enums, and inline object field types. Canonical HTML: https://vdl.varavel.com/docs/language/field-types/ Markdown: https://vdl.varavel.com/docs/language/field-types/index.md ## Field Type Overview Every field has a type. ```vdl type Example { name string count int } ``` VDL supports: - primitive types - custom types - enums - arrays - maps - inline objects ## Primitive Types | Type | JSON Equivalent | Description | | ---------- | --------------- | -------------------------- | | `string` | string | UTF-8 encoded text | | `int` | integer | 64-bit signed integer | | `float` | number | 64-bit floating point | | `bool` | boolean | Logical value (true/false) | | `datetime` | string | RFC 3339 date-time string | > **Note:** `datetime` values must be RFC 3339 formatted strings. RFC 3339 is a strict subset of ISO 8601 chosen to ensure consistent parsing across implementations. ```vdl type PrimitiveExample { name string age int score float active bool createdAt datetime } ``` ## Custom Types Fields can reference other declared types. ```vdl type Address { city string country string } type User { id string address Address } ``` ## Enum Fields Fields can reference enums. ```vdl enum OrderStatus { Pending Paid Shipped } type Order { id string status OrderStatus } ``` ## Arrays Arrays use `[]` after the element type. ```vdl type Collection { tags string[] scores int[] orders Order[] } type Order { id string } ``` Multidimensional arrays repeat the suffix: ```vdl type MatrixData { values float[][] } ``` ## Maps Maps use `map[T]`, where `T` is the value type. ```vdl type Metrics { counters map[int] labels map[string] } ``` Maps can contain custom types: ```vdl type User { id string email string } type UserDirectory { usersById map[User] } ``` Maps can also contain arrays or nested maps: ```vdl type ComplexMapExample { tagsByGroup map[string[]] matrixByName map[float[][]] } ``` ## Inline Objects Inline objects are anonymous structured field types. ```vdl type SearchResult { id string metadata { score float source string } } ``` Use inline objects when the nested shape does not need a reusable name. Use a named type when the shape is reused or important enough to document on its own. ## Optional Field Types Optionality belongs to the field name, not the type. ```vdl type User { displayName? string tags? string[] profile? { bio? string } } ``` The `?` always appears after the field name. ## Recursive Types VDL can model recursive structures when a cycle is broken by an optional field. ```vdl type TreeNode { id string children? TreeNode[] } ``` Direct required cycles are rejected because they cannot be represented as finite data. Invalid: ```vdl type A { b B } type B { a A } ``` Make one side optional to model a nullable or absent link. --- Source: https://vdl.varavel.com/docs/language/enums/ Markdown: https://vdl.varavel.com/docs/language/enums/index.md Content file: content/docs/language/enums.md # Enums > Define finite sets of named values. Canonical HTML: https://vdl.varavel.com/docs/language/enums/ Markdown: https://vdl.varavel.com/docs/language/enums/index.md ## What Enums Are Enums define a named set of possible values. ```vdl enum OrderStatus { Pending Paid Shipped } ``` Use enums when a field should accept only one value from a known list. ```vdl type Order { id string status OrderStatus } ``` ## Implicit String Values When a member has no explicit value, its value is the member name. ```vdl enum Status { Active Disabled } ``` This is equivalent to: ```vdl enum Status { Active = "Active" Disabled = "Disabled" } ``` ## Explicit String Values Use explicit strings when the wire value should differ from the member name. ```vdl enum ProductStatus { Draft = "draft" Published = "published" Archived = "archived" } ``` This is common when generated code should use nice member names while serialized data uses lowercase values. ## Integer Enums Enums can use integer values. ```vdl enum Priority { Low = 1 Medium = 2 High = 3 } ``` For integer enums, every member must have an explicit integer value. Invalid: ```vdl enum Priority { Low = 1 Medium High = 3 } ``` ## Do Not Mix Value Kinds An enum must be consistently string-based or integer-based. Invalid: ```vdl enum MixedStatus { Active = "active" Disabled = 2 } ``` ## Unique Names And Values Member names must be unique. Invalid: ```vdl enum Status { Active Active = "active" } ``` Member values must also be unique. Invalid: ```vdl enum Status { Active = "enabled" Enabled = "enabled" } ``` ## Enum Member Docstrings ```vdl enum InvoiceStatus { """ The invoice is editable and has not been sent. """ Draft """ The invoice has been paid in full. """ Paid } ``` ## Enum Member Annotations ```vdl enum FeatureState { @deprecated("Use Enabled instead.") Active = "active" Enabled = "enabled" } ``` Annotations on enum members are available to plugins. ## Enum Spreads Enums can reuse members from another enum with `...Name`. ```vdl enum StandardRole { Viewer = "viewer" Editor = "editor" } enum WorkspaceRole { ...StandardRole Owner = "owner" } ``` Enum spreads must reference another enum and must use the `Name` form, not `Name.Member`. ## Naming Enum names and member names should use `PascalCase`. ```vdl enum PaymentStatus { Pending Completed Failed } ``` --- Source: https://vdl.varavel.com/docs/language/constants/ Markdown: https://vdl.varavel.com/docs/language/constants/index.md Content file: content/docs/language/constants.md # Constants and Literals > Define reusable dynamic literal values with const declarations. Canonical HTML: https://vdl.varavel.com/docs/language/constants/ Markdown: https://vdl.varavel.com/docs/language/constants/index.md ## What Constants Are Constants define reusable literal values. ```vdl const apiVersion = "1.0.0" const maxRetries = 3 const featureEnabled = true ``` Constants do not have explicit type annotations. VDL infers their value kind from the literal. Correct: ```vdl const timeoutMs = 2500 ``` Invalid: ```text const timeoutMs int = 2500 ``` ## Scalar Literals VDL supports these scalar literal forms: ```vdl const textValue = "hello" const intValue = 42 const floatValue = 3.14 const trueValue = true const falseValue = false ``` Strings use double quotes. ## Object Literals Object literals use braces and space-separated key/value entries. ```vdl const serverConfig = { host "localhost" port 8080 tls false } ``` There are no colons and no commas. Object keys must be unique inside the same object. ## Nested Object Literals ```vdl const appConfig = { server { host "localhost" port 8080 } logging { level "info" } } ``` ## Array Literals Array items are separated by whitespace. ```vdl const roles = ["admin" "editor" "viewer"] const retryBackoffMs = [100 250 500] ``` All items in an array literal must have the same value kind. Invalid: ```vdl const mixedValues = ["admin" 1 true] ``` ## Constant References Constants can reference other constants. ```vdl const defaultTimeoutMs = 5000 const requestTimeoutMs = defaultTimeoutMs ``` References use the constant name directly. ## Enum Member References Constants can reference enum members with `EnumName.MemberName`. ```vdl enum ProductStatus { Draft = "draft" Published = "published" } const defaultStatus = ProductStatus.Draft ``` The referenced enum and member must exist. ## Object Spreads Object literals can spread another constant object. ```vdl const baseConfig = { host "localhost" port 8080 } const productionConfig = { ...baseConfig host "api.example.com" } ``` Only constants whose value is an object literal can be used in object spreads. Spreads use the `Name` form. `Name.Member` is not valid for object spreads. ## Constants In Annotations Because annotation arguments are data literals, constants can also help share annotation values. ```vdl const owner = "platform" @meta({ team owner }) type ServiceConfig { name string } ``` Whether a plugin understands a particular annotation is plugin-specific. ## Naming Constant names should use `camelCase`. ```vdl const apiVersion = "1.0.0" const maxPageSize = 100 ``` --- Source: https://vdl.varavel.com/docs/language/annotations/ Markdown: https://vdl.varavel.com/docs/language/annotations/index.md Content file: content/docs/language/annotations.md # Annotations > Attach metadata to VDL declarations, fields, and enum members. Canonical HTML: https://vdl.varavel.com/docs/language/annotations/ Markdown: https://vdl.varavel.com/docs/language/annotations/index.md ## What Annotations Are Annotations attach metadata to schema nodes. ```vdl @deprecated type OldUser { id string } ``` The VDL core does not hardcode most domain behavior. Plugins read annotations and decide what they mean. For example, one plugin may interpret `@event`, another may interpret `@rpc`, and another may interpret `@deprecated`. ## Basic Syntax An annotation starts with `@` and a name. ```vdl @internal type InternalUser { id string } ``` Annotations can also have one argument. ```vdl @deprecated("Use NewUser instead.") type OldUser { id string } ``` The argument must be a data literal: string, number, boolean, object, array, constant reference, or enum member reference. ## Multiple Annotations You can stack annotations. ```vdl @internal @owner("platform") type InternalConfig { name string } ``` Annotations attach to the declaration or member that immediately follows them. ## Declaration Annotations Annotations can be attached to `type`, `enum`, and `const` declarations. ```vdl @event("users.created.{userId}") type UserCreated { userId string email string } @stable enum Region { Europe = "eu" America = "us" } @config const serviceName = "billing" ``` ## Field Annotations Fields can have annotations. ```vdl type User { @id id string @deprecated("Use displayName instead.") name? string displayName string } ``` ## Enum Member Annotations Enum members can have annotations. ```vdl enum Plan { Free = "free" @deprecated("Use Team instead.") Startup = "startup" Team = "team" } ``` ## Object Arguments Object arguments are useful for structured metadata. ```vdl @meta({ owner "platform" tier "gold" }) type Account { id string } ``` Object entries use VDL literal syntax: key followed by value, no colon, no comma. ## Array Arguments ```vdl @tags(["public" "billing" "stable"]) type Invoice { id string } ``` Array literal items must be the same kind. ## Annotation Names Annotation names should use `camelCase`. ```vdl @generateClient type PublicApi { name string } ``` Avoid underscores or PascalCase annotation names. ## Annotations Are Plugin Contracts An annotation has no effect unless some tool or plugin understands it. This is a feature. It lets teams create domain-specific contracts without changing the VDL grammar. Examples: - `@rpc` can mark a type as an RPC service. - `@proc` can mark a request/response RPC operation. - `@stream` can mark a server-sent stream operation. - `@event("subject.{field}")` can mark an event payload and routing subject. - `@deprecated` can mark generated code or schema output as deprecated. Always check the plugin documentation for the annotations it supports. --- Source: https://vdl.varavel.com/docs/language/spreads-references/ Markdown: https://vdl.varavel.com/docs/language/spreads-references/index.md Content file: content/docs/language/spreads-references.md # Spreads and References > Reuse fields, enum members, and literal values safely. Canonical HTML: https://vdl.varavel.com/docs/language/spreads-references/ Markdown: https://vdl.varavel.com/docs/language/spreads-references/index.md ## References A reference points to a named VDL symbol. References appear in several places: - field types - spreads - constant values - annotation arguments ## Type References Fields can reference declared types. ```vdl type UserId string type User { id UserId } ``` Fields can also reference enums. ```vdl enum Status { Active Disabled } type User { status Status } ``` ## Constant References Constants can reference other constants by name. ```vdl const defaultLimit = 100 const pageSize = defaultLimit ``` ## Enum Member References Use `EnumName.MemberName` to reference an enum member in literal values. ```vdl enum Status { Active = "active" Disabled = "disabled" } const defaultStatus = Status.Active ``` ## Type Spreads Type spreads copy fields from another object type. ```vdl type AuditFields { createdAt datetime updatedAt datetime } type Article { ...AuditFields id string title string } ``` After analysis, plugins see `Article` as if it had all fields directly. Only object types can be spread into object types. Invalid: ```vdl type UserId string type User { ...UserId name string } ``` ## Inline Object Spreads Inline objects can also spread object types. ```vdl type Money { amount int currency string } type Order { id string payment { ...Money provider string } } ``` ## Field Conflicts Spreads cannot introduce a field that already exists in the same object. Invalid: ```vdl type BaseUser { id string } type User { ...BaseUser id string } ``` Rename one field or avoid the spread. ## Enum Spreads Enum spreads copy members from another enum. ```vdl enum StandardRole { Viewer = "viewer" Editor = "editor" } enum ProjectRole { ...StandardRole Owner = "owner" } ``` Enum spreads must reference enums, not enum members. Invalid: ```vdl enum Role { Viewer = "viewer" } enum ProjectRole { ...Role.Viewer Owner = "owner" } ``` ## Object Literal Spreads Object literals can spread constant objects. ```vdl const baseConfig = { retries 3 timeoutMs 5000 } const productionConfig = { ...baseConfig timeoutMs 10000 } ``` Only object constants can be used this way. ## Spread Cycles Spreads cannot form cycles. Invalid: ```vdl type A { ...B a string } type B { ...A b string } ``` Keep spread hierarchies simple and acyclic. ## Name Forms VDL supports two reference forms: | Form | Used for | | ------------- | --------------------------------------------- | | `Name` | Types, constants, spreads | | `Name.Member` | Enum member references in literal expressions | Spreads must use `Name`, not `Name.Member`. --- Source: https://vdl.varavel.com/docs/language/naming-validation/ Markdown: https://vdl.varavel.com/docs/language/naming-validation/index.md Content file: content/docs/language/naming-validation.md # Naming and Validation > Learn the naming conventions and validation rules VDL applies. Canonical HTML: https://vdl.varavel.com/docs/language/naming-validation/ Markdown: https://vdl.varavel.com/docs/language/naming-validation/index.md ## Why Naming Matters VDL schemas are used to generate code in different languages. Consistent names make generated output predictable and pleasant to use. The analyzer reports diagnostics when names do not follow the expected convention. ## Naming Conventions | Element | Convention | Examples | | ------------ | ------------ | ---------------------------- | | Types | `PascalCase` | `User`, `OrderItem` | | Enums | `PascalCase` | `OrderStatus`, `Region` | | Enum members | `PascalCase` | `Pending`, `InProgress` | | Fields | `camelCase` | `userId`, `createdAt` | | Constants | `camelCase` | `apiVersion`, `maxPageSize` | | Annotations | `camelCase` | `deprecated`, `generateCode` | ## PascalCase PascalCase starts with an uppercase letter and does not use underscores. Good: ```vdl type UserProfile { displayName string } enum OrderStatus { Pending } ``` Avoid: ```vdl type user_profile { displayName string } ``` ## camelCase camelCase starts with a lowercase letter and does not use underscores. Good: ```vdl type UserProfile { displayName string createdAt datetime } const maxPageSize = 100 ``` Avoid: ```vdl type UserProfile { display_name string } const MaxPageSize = 100 ``` ## File Names Regular schema files should match: ```text [a-z0-9_]+.vdl ``` Examples: ```text schema.vdl shared_types.vdl events_v1.vdl ``` The configuration file must be named: ```text vdl.config.vdl ``` ## Global Name Uniqueness Top-level type, enum, and constant names share one global namespace. Invalid: ```vdl type Status string enum Status { Active } ``` Use unique names across declarations. ## Duplicate Fields Fields must be unique within each object or inline object. Invalid: ```vdl type User { id string id int } ``` ## Undefined References Every type reference must point to a declared type, enum, or primitive. Invalid: ```vdl type User { address Address } ``` Fix it by declaring `Address` or including the file that declares it. ```vdl type Address { city string } type User { address Address } ``` ## Cycles VDL rejects required type cycles. Invalid: ```vdl type Parent { child Child } type Child { parent Parent } ``` Break the cycle with an optional field. ```vdl type Parent { child Child } type Child { parent? Parent } ``` ## Practical Checklist Before generating code, check that: - file names are lowercase with underscores when needed - types and enums use PascalCase - fields and constants use camelCase - all referenced types and enums exist - object fields are unique - enum member names and values are unique - required type references do not form cycles - spreads do not conflict or form cycles --- Source: https://vdl.varavel.com/docs/protocols/ Markdown: https://vdl.varavel.com/docs/protocols/index.md Content file: content/docs/protocols/_index.md # VDL Plugin Protocols > Specification of standard plugin protocols in the VDL ecosystem. Canonical HTML: https://vdl.varavel.com/docs/protocols/ Markdown: https://vdl.varavel.com/docs/protocols/index.md --- Source: https://vdl.varavel.com/docs/protocols/introduction/ Markdown: https://vdl.varavel.com/docs/protocols/introduction/index.md Content file: content/docs/protocols/introduction.md # Introduction > What VDL Plugin Protocols are, why they exist, and how they keep the ecosystem consistent. Canonical HTML: https://vdl.varavel.com/docs/protocols/introduction/ Markdown: https://vdl.varavel.com/docs/protocols/introduction/index.md ## VDL Plugin Protocols A **VDL Plugin Protocol** (**VPP**) is a written standard for a family of VDL plugins. It defines what a plugin is allowed to understand, what it must validate, and what kind of output it must produce. In practical terms, a VPP turns a convention into a shared contract for the whole VDL ecosystem. VDL itself is intentionally small. The core language gives you a compact way to describe schemas with declarations such as types, enums, constants and annotations. It does not try to hard-code every possible domain into the compiler. Instead, VDL grows indefinitely through plugins. The plugin-first model is powerful, but it needs discipline. Without shared protocols, every plugin could interpret the same annotation differently, produce incompatible APIs, or apply different validation rules. VPPs prevent that fragmentation. Plugins are not required to use a VPP. A plugin can be completely ad hoc, especially when it exists for one project, one team, or one private workflow. VPPs become useful when the same pattern appears across projects often enough that it is worth standardizing. At that point, a protocol lets multiple plugins implement the same idea across different languages and stacks, so VDL continues to feel familiar no matter what environment you are working in. ## Why VPPs Exist VPPs exist so VDL can stay small while the ecosystem remains consistent. The VDL compiler should focus on the language itself: parsing files, building an intermediate representation, resolving declarations, and exposing structured data to plugins. Domain-specific behavior belongs outside the compiler, where plugins can evolve independently. A VPP defines the rules for that domain-specific behavior. For example, if a protocol introduces an annotation, the protocol must explain what the annotation means, where it can be used, what arguments it accepts, what invalid usage looks like, and how compliant plugins should reflect it in generated artifacts. This gives VDL three important properties. 1. **A small core:** the compiler does not grow every time the ecosystem needs a new capability. 2. **Portable semantics:** the same VDL file can be understood consistently by plugins for different languages and different purposes. 3. **Composable tooling:** plugins can build on top of the same conventions instead of inventing private dialects. ## What a VPP Defines A VPP does not define new VDL syntax. It defines meaning on top of the existing language. Protocols are built from standard VDL features such as annotations, declaration names, metadata, literals, and the resolved intermediate representation passed to plugins. The protocol says how those pieces must be interpreted. A good VPP clearly specifies these areas. 1. **Vocabulary:** the annotations, metadata fields, declaration shapes, and names that belong to the protocol. 2. **Allowed targets:** where that vocabulary can appear, such as types, fields, enum members, constants, or protocol-specific containers. 3. **Validation rules:** what a compliant plugin must check before generating output. 4. **Output expectations:** what generated code, documentation, diagnostics, or metadata should look like. 5. **Interoperability rules:** how the protocol should compose with other protocols and plugins. The goal is not to force every language target to generate identical source code. Go, TypeScript, Rust, Java, and other ecosystems have different idioms. The goal is to guarantee that all compliant plugins preserve the same semantics while generating idiomatic output for their target language. ## How VPPs Keep Plugins Compatible Plugins are free to specialize, but they should not redefine shared meaning. If a VPP says that an annotation has a specific behavior, a compliant plugin must follow that behavior. It may generate language-specific APIs, add useful helper files, or integrate with a framework, but it must not silently change the meaning of the protocol. This is especially important when plugins are chained together. A foundational plugin may generate basic models, while another plugin may generate services, events, policies, adapters, or documentation that references those models. The only way that composition stays reliable is if each plugin can depend on stable protocol semantics. In other words, a VPP is the agreement that lets independently developed plugins behave like part of one coherent system. ## What VPPs Are Not A VPP is not a compiler feature request. A VPP should not expand the VDL grammar. Most ecosystem capabilities should be modeled with existing VDL declarations and annotations, then implemented by plugins. A VPP is also not a single implementation. Multiple plugins can implement the same protocol for different languages, frameworks, or runtime environments. The protocol is the standard and each plugin is one implementation of that standard. ## Defining a Protocol When defining or proposing a VPP, write it as a contract for implementers. The document should be precise enough that two independent plugin authors can read it, implement it separately, and produce compatible behavior. It should explain the intent in plain language, then define the exact rules that make the behavior testable. Any person, team, or organization can define their own VPPs for internal use. Private protocols are useful when a company wants consistent behavior across its own plugins without waiting for an ecosystem-wide standard. Official VDL protocols are different. To become an official VPP, a proposal must go through a formal community discussion on GitHub. That discussion is where the vocabulary, validation rules, edge cases, and long-term compatibility concerns are refined before the protocol becomes part of the public VDL standard set. At minimum, a protocol should answer these questions. 1. What problem does this protocol solve? 2. Which VDL constructs does it use? 3. Which annotations or metadata fields does it define? 4. Where are those annotations or fields allowed? 5. Which argument types are accepted? 6. What must happen when invalid input is found? 7. What should compliant generators produce? 8. How should the protocol interact with other VPPs? The best VPPs are narrow, explicit, and composable. They define one domain well, avoid leaking implementation details into the language, and leave room for plugins to generate idiomatic code in their target ecosystem. --- Source: https://vdl.varavel.com/docs/protocols/core/ Markdown: https://vdl.varavel.com/docs/protocols/core/index.md Content file: content/docs/protocols/vpp-0-core.md # VPP-0: Core > Foundational plugins for generating the primitive VDL model: types, enums, and constants. Canonical HTML: https://vdl.varavel.com/docs/protocols/core/ Markdown: https://vdl.varavel.com/docs/protocols/core/index.md **VPP-0: Core** defines the expectations for foundational plugins that generate the most primitive VDL artifacts for a target language. A Core plugin is responsible for turning plain VDL declarations into idiomatic source code. It should generate the basic model layer that the rest of the ecosystem can trust: types, enums, and constants. Core plugins are intentionally narrow. They do not implement other plugn behavior like RPC, event routing, authorization policy, etc. Their job is to produce a stable, high-quality foundation that other plugins can build on. ## Scope A VPP-0 Core plugin handles exactly these VDL declaration families. 1. **Types:** structured object declarations and their fields. 2. **Enums:** named sets of allowed values. 3. **Constants:** named literal values declared with `const`. Everything outside those primitives belongs to another protocol or to an extension plugin. For example, a Core plugin may generate a Go struct, a TypeScript interface, a Rust struct, an enum type, or exported constants. It should not generate HTTP handlers, event publishers, policy evaluators, or application-specific code. ## Purpose Core plugins exist for two reasons. First, they are useful on their own. Many projects only need strongly typed models, enums, and constants generated from a shared schema. A good Core plugin should make that workflow simple, predictable, and idiomatic. Second, they provide the base layer for higher-level plugins. Other protocols often need to refer to the generated representation of a VDL type, enum, or constant. A stable Core output gives those plugins something concrete to import, reference, extend, or wrap. This makes Core plugins the foundation of the VDL plugin ecosystem. ## Language Idioms Core output must be idiomatic for the target language. The protocol defines semantic expectations, not one universal code shape. A TypeScript Core plugin should feel natural to TypeScript users. A Go Core plugin should feel natural to Go users. A Rust Core plugin should feel natural to Rust users. Idiomatic output includes language-appropriate naming, file organization, visibility rules, literal representation, enum modeling, documentation comments, and serialization tags when the target ecosystem conventionally expects them. The generated code should look like code a skilled developer in that language would be comfortable maintaining, even though it is generated. ## Plugin Naming Core plugins should use the name `vdl-plugin-`, where `` is the target language generated by the plugin (or a commonly recognized abbreviation). Examples include `vdl-plugin-go` and `vdl-plugin-ts`. This naming convention should be respected as much as possible so users can quickly identify the foundational plugin for a target language and other plugins can refer to it consistently. ## Stability Requirements Core plugins must prioritize stable, deterministic output. Given the same input schema and configuration, a Core plugin should produce the same files, names, and exported symbols every time. This allows downstream plugins, application code, tests, and generated documentation to reference Core artifacts safely. A compliant Core plugin should avoid unnecessary renames, unstable file splitting, random ordering, environment-dependent output, or hidden behavior that makes generated artifacts difficult to depend on. ## Extension Points Core plugins should be easy for other plugins to build on. Higher-level plugins need a reliable way to know where Core artifacts were generated and how to reference them. A Core plugin should therefore expose clear configuration for its output location and use predictable import paths, package names, module names, or file names according to the target language. Other plugins should be able to say, in effect: "the Core model layer was generated here; import or reference it from there." This does not require every Core plugin to share the same directory layout. It does require each Core plugin to make its layout deterministic, documented, and configurable enough for composition. ## Relationship With Other Protocols VPP-0 Core is the base layer. Other protocols may depend on it, but Core must not depend on them. A Core plugin may preserve protocol metadata in comments, annotations, generated metadata files, or language-specific attributes when doing so is useful and non-invasive. However, it must not implement the behavior of other protocols as part of Core generation. For example, if a declaration is marked as deprecated under VPP-1, a Core plugin may emit the appropriate language-level deprecation comment, attribute, or feature to preserve the semantics defined by that protocol. That is still compatible with Core because it affects the generated representation of a primitive declaration. By contrast, generating an RPC server from `@rpc` belongs to the RPC protocol, not to VPP-0 Core. ## Expected Behavior A compliant Core plugin should follow these rules. 1. Generate declarations for VDL types, enums, and constants only. 2. Preserve the semantic meaning of VDL primitives in the target language. 3. Produce idiomatic code for the target ecosystem. 4. Keep output deterministic and stable across runs. 5. Provide clear, documented output locations for downstream plugins. 6. Avoid implementing higher-level protocol behavior directly. 7. Respect cross-cutting standards, such as deprecation metadata, when they apply to generated primitive artifacts. VPP-0 Core is deliberately simple. Its strength comes from being small, predictable, and dependable enough to serve as the foundation for everything else. --- Source: https://vdl.varavel.com/docs/protocols/deprecations/ Markdown: https://vdl.varavel.com/docs/protocols/deprecations/index.md Content file: content/docs/protocols/vpp-1-deprecations.md # VPP-1: Deprecations > Global deprecation metadata for every VDL plugin and protocol. Canonical HTML: https://vdl.varavel.com/docs/protocols/deprecations/ Markdown: https://vdl.varavel.com/docs/protocols/deprecations/index.md **VPP-1: Deprecations** defines the standard meaning of the `@deprecated` annotation across the entire VDL ecosystem. This protocol is different from protocols that describe a family of generators. VPP-1 does not exist so people can build "deprecation plugins". It exists so every plugin, protocol, SDK, code generator, documentation generator, editor tool, and internal workflow can understand deprecation metadata in the same way. If a plugin claims compatibility with VPP-1, it must preserve the semantics defined in this document. ## Why This Protocol Exists Deprecation is a universal concern. Schemas evolve. Fields get replaced. Enum members stop being recommended. Constants become legacy. Protocol-specific operations may remain supported while users migrate to newer shapes. Generated APIs often need to keep old symbols available without encouraging new code to use them. Without a shared standard, every plugin could invent its own way to mark deprecation. One plugin might use `@deprecated`, another might use `@legacy`, another might require structured metadata, and another might ignore the concept entirely. That would make VDL projects harder to understand and harder to compose. VPP-1 keeps the meaning simple and global. When users write `@deprecated`, every VPP-1 compatible plugin should understand the same intent: this symbol is still part of the schema, but it should be treated as deprecated wherever the generated target can express that idea. ## The `@deprecated` Annotation Deprecation is expressed with the `@deprecated` annotation. It can be used as a flag. ```vdl @deprecated type LegacyUser { id string } ``` It can also receive one optional string argument. ```vdl @deprecated("Use UserProfile instead.") type UserSummary { id string displayName string } ``` The presence of `@deprecated` is what marks the symbol as deprecated. The optional string is only additional human-readable guidance. ## Accepted Forms VPP-1 compatible plugins must recognize these forms. ```vdl @deprecated ``` ```vdl @deprecated("Use another symbol instead.") ``` The first form means the symbol is deprecated with no message. The second form means the symbol is deprecated with a message. ## Message Semantics The optional string argument is a human-facing note. It can contain a reason, migration hint, replacement suggestion, timeline note, warning, or any other text that helps a person understand what to do next. ```vdl @deprecated("Use primaryEmail instead.") email string ``` VPP-1 does not assign machine-readable meaning to the message. Plugins should not parse the message to infer replacement symbols, dates, severity levels, owners, or migration rules. Future protocols may define structured deprecation metadata, but VPP-1 only standardizes the flag and the optional string message. ## Invalid or Future Arguments If `@deprecated` receives an argument that is not a string, the symbol is still deprecated because the annotation is present. However, the non-string argument must not be treated as the deprecation message. ```vdl @deprecated({ reason "Use AccountV2" }) type AccountV1 { id string } ``` In this example, `AccountV1` is deprecated, but the object argument is ignored for VPP-1 message purposes. Plugins may emit a warning for unsupported deprecation arguments when that fits their diagnostic model, but they must not reinterpret a non-string value as the VPP-1 message. This rule keeps current behavior predictable while leaving room for the ecosystem to extend deprecation metadata in a later protocol revision. ## Allowed Targets The `@deprecated` annotation can be used wherever VDL annotations are valid and where a consuming plugin can preserve or surface the meaning. Common targets include these constructs. 1. Top-level `type` declarations. 2. Top-level `enum` declarations. 3. Top-level `const` declarations. 4. Type fields. 5. Named enum members. 6. Any future or protocol-specific construct that supports annotations through normal VDL metadata. VPP-1 is intentionally global. It is not limited to any specific protocol family, target language, transport, runtime, or generator category. ## Schema Semantics Deprecation is metadata. It does not make a declaration invalid. It does not remove a symbol from the schema. It does not require a plugin to stop generating that symbol. It does not change the type, value, validation rules, or reference behavior of the deprecated construct. A deprecated symbol remains part of the VDL program unless another rule, plugin, or configuration explicitly removes or rejects it. The purpose of `@deprecated` is to communicate lifecycle status, not to change schema correctness. ## Required Behavior for Compatible Plugins A plugin may call itself VPP-1 compatible only if it follows these rules. 1. It must recognize `@deprecated` as a deprecation marker. 2. It must recognize `@deprecated("message")` as a deprecation marker with a human-readable message. 3. It must treat the annotation as the source of deprecation, regardless of whether a valid message is present. 4. It must ignore non-string arguments as VPP-1 messages. 5. It must not make a schema invalid only because a symbol is deprecated. 6. It must preserve, expose, or reflect deprecation metadata whenever doing so is reasonable for its output or tool behavior. The exact output is target-specific. A CLI tool, language generator, editor extension, documentation generator, runtime adapter, and internal company plugin may all expose deprecation differently. The semantic interpretation must remain the same. ## Recommended Output Behavior Plugins should surface deprecation information in the most idiomatic way available to their target. Generated source code may use language-level deprecation comments, attributes, decorators, annotations, doc comments, metadata tables, generated catalogs, or framework-specific markers. Generated documentation should make deprecated symbols visible and should show the optional message when one exists. Editor tooling may show hover metadata, completion labels, diagnostics, warnings, or visual markers for deprecated symbols. Runtime or framework integrations may forward deprecation metadata into generated registries, manifests, schemas, or descriptors when those artifacts have a natural place for it. Plugins should avoid hiding deprecated symbols by default. Deprecation tells users to migrate away from a symbol, it does not mean the symbol has already disappeared. ## Examples Deprecating a type: ```vdl @deprecated("Use AccountV2 instead.") type Account { id string } ``` Deprecating a field: ```vdl type User { id string @deprecated("Use displayName instead.") name string } ``` Deprecating an enum member: ```vdl enum Status { Active @deprecated("Use Disabled instead.") Inactive } ``` Deprecating a constant: ```vdl @deprecated const legacyTimeoutMs = 12000 ``` Deprecating a protocol-specific construct: ```vdl @rpc type ServiceContract { @proc @deprecated("Use createUserV2 instead.") createUser { input { email string } output { id string } } } ``` The final example intentionally avoids relying on one specific protocol. The rule is the same anywhere annotations are valid: if a VPP-1 compatible plugin understands the annotated construct, it must preserve the deprecation meaning. ## Compatibility Statement VPP-1 is intentionally small because it is meant to be universal. The standard exists so every part of the VDL ecosystem can share one deprecation vocabulary. A deprecated model, field, enum member, constant, generated API, documented symbol, or protocol-specific construct should communicate the same lifecycle status everywhere. A plugin may call itself **VPP-1 compatible** only if it respects that shared meaning. --- Source: https://vdl.varavel.com/docs/protocols/rpc/ Markdown: https://vdl.varavel.com/docs/protocols/rpc/index.md Content file: content/docs/protocols/vpp-2-rpc.md # VPP-2: RPC > Standard RPC modeling and HTTP/SSE transport behavior for VDL plugins. Canonical HTML: https://vdl.varavel.com/docs/protocols/rpc/ Markdown: https://vdl.varavel.com/docs/protocols/rpc/index.md **VPP-2: RPC** defines the standard way to model remote procedure calls in VDL and the standard HTTP transport behavior that compatible plugins must implement. This protocol covers two operation styles. 1. **Procedures:** request-response operations completed within a single HTTP transaction. 2. **Streams:** server-pushed event flows delivered over Server-Sent Events (SSE). VPP-2 does not add syntax to VDL. It uses normal VDL declarations, inline object types, and annotations. The purpose of the protocol is to give plugin authors a shared contract for generating clients, servers, handlers, SDKs, documentation, adapters, and test tools from the same RPC model. If a plugin claims compatibility with VPP-2, it must preserve the annotation semantics, operation model, HTTP behavior, SSE behavior, and envelope format defined in this document. ## Why This Protocol Exists RPC systems often look different across languages, frameworks, and runtimes. One stack may generate controller classes, another may generate function handlers, another may generate typed clients, and another may generate route metadata. VDL should not hard-code any of those framework choices into the compiler. The compiler should keep producing a language-neutral representation of schemas and annotations. Plugins can then turn that representation into idiomatic code for each target ecosystem. VPP-2 exists so those plugins still agree on the important parts: how RPC services are modeled, how operations are named, how input and output are represented, how HTTP requests are formed, how errors are returned, and how streams behave over SSE. The result is a portable RPC contract. A VDL service should feel familiar whether the generated target is Go, TypeScript, Rust, Java, or an internal platform plugin. VPP-2 is specifically the standard RPC model for HTTP-based transport. It exists because HTTP procedures and SSE streams are a common, practical baseline that many projects need. This does not mean VDL can only model RPC over HTTP. VDL remains transport-agnostic at the language level. Teams can define their own RPC conventions with normal VDL types, annotations, and plugins when they need another transport or a custom protocol shape. Those custom models are valid VDL workflows, but they are not VPP-2 compatible unless they preserve the HTTP and SSE behavior defined here. ## JSON Transport Model VPP-2 uses JSON as the standard payload format. VDL is designed so its data model can be represented as JSON: objects, arrays, maps, strings, numbers, booleans, nullability, constants, enums, and structured inline types all have natural JSON representations. Because of that, JSON is the default wire format for RPC inputs, outputs, and errors. Compatible plugins may map VDL types into richer language-native types in generated code, but the HTTP payloads defined by this protocol are JSON payloads. ## Annotation Vocabulary VPP-2 defines three annotations. 1. `@rpc` marks a service container. 2. `@proc` marks a request-response operation. 3. `@stream` marks a server-pushed stream operation. ### `@rpc` The `@rpc` annotation is attached to a `type` declaration. That type becomes an RPC service container. The name of the annotated type is the **RPC name** used by the transport protocol. ```vdl @rpc type Users { // operations go here } ``` ### `@proc` The `@proc` annotation is attached to a member inside an `@rpc` service container. That member becomes a procedure. The member name is the **procedure name** used by the transport protocol. Procedures use request-response HTTP behavior. ### `@stream` The `@stream` annotation is attached to a member inside an `@rpc` service container. That member becomes a stream. The member name is the **stream name** used by the transport protocol. Streams use HTTP to open the subscription and SSE to receive emitted events. ## Schema Shape RPC services are modeled with normal VDL type declarations and inline object members. ```vdl @rpc type Users { @proc getUser { input { userId string } output { id string email string } } @stream userEvents { input { userId string } output { eventName string } } } ``` The RPC model uses these structural rules. 1. `@rpc` is attached to a `type` declaration. 2. `@proc` and `@stream` are attached to members inside the `@rpc` type. 3. Operation members are regular fields whose type is an inline object. 4. `input` and `output` are regular field names inside the operation object. 5. When present, `input` and `output` must be inline object types. Because operation bodies, `input`, and `output` are normal VDL object shapes, they can use standard language features supported by object fields, including docstrings and spreads. ## Optional Input and Output Both `input` and `output` are optional. Some operations only need to trigger work. Some only need to return data. Some only need to establish a transactional command without input or output data. VPP-2 supports those cases directly. ```vdl @rpc type Maintenance { @proc ping {} @proc rotateKeys { output { rotatedAt datetime } } @proc clearCache { input { namespace string } } } ``` If `input` is omitted, the operation does not require an application-level input payload. If `output` is omitted, a successful operation does not return application-level output data. When either field is present, it must be an inline object. Plugins must not reinterpret scalar fields, arrays, maps, or references named `input` or `output` as valid VPP-2 operation payload definitions. ## Input Validation Scope VPP-2 requires structural input validation. A compatible plugin must ensure that incoming input matches the shape defined by the user. Required fields must be present, optional fields may be omitted, and every provided value must match the VDL type declared for that field. This validation is part of the RPC contract. A VPP-2 compatible implementation must not pass invalid input to user handlers as if it were valid. VPP-2 does not define application-specific business validation. Rules such as "this user can access this account", "this amount must be lower than the current balance", "this transition is allowed only for administrators", or "this date must be inside an active subscription period" belong to application code, middleware, another protocol, or user-defined validation layers. In short: VPP-2 guarantees that the input has the structure and types declared in VDL. It does not decide whether that input is meaningful for a specific business domain. ## Service and Operation Names The HTTP route is derived from VDL names. 1. `RPCName` is the name of the `type` annotated with `@rpc`. 2. `ProcedureName` is the name of the member annotated with `@proc`. 3. `StreamName` is the name of the member annotated with `@stream`. Procedures use this URL structure. ```text // ``` Streams use this URL structure. ```text // ``` For example, a `getUser` procedure inside an `@rpc type Users` service may be called at this URL when the configured base URL is `https://api.example.com/rpc`. ```text https://api.example.com/rpc/Users/getUser ``` ## Transport Overview VPP-2 uses HTTP as the standard transport. Procedures send a JSON request body and receive a single JSON response body. Streams send a JSON request body to open the subscription and receive a persistent SSE response where each emitted event contains a JSON envelope. Application-level success or failure is represented by envelopes (the JSON response). When the server successfully processes the RPC transport flow, the HTTP status is `200 OK` even if the application result is an error represented by `ok: false`. Non-`200` HTTP status codes are reserved for transport-level failures or situations where the RPC transport itself could not be completed. Examples include connection errors, timeouts, malformed transport requests, unavailable servers, framework-level failures, or infrastructure errors. Generated clients must account for this distinction: `ok: false` is an application error envelope, while a non-`200` status is a transport failure. Middleware may run before, during, or after operation handling. Authentication, authorization, validation, logging, metrics, tracing, rate limiting, and similar concerns may be implemented by generated or user-provided middleware. Middleware must not change the wire contract defined by this protocol. ## Envelope Model VPP-2 represents application-level operation results with JSON envelopes. A success envelope has `ok: true` and may include `output` when the operation defines output data. ```json { "ok": true, "output": { "id": "user-123", "email": "john.doe@example.com" } } ``` A failure envelope has `ok: false` and includes an `error` object. ```json { "ok": false, "error": { "message": "User not found.", "category": "NotFound", "code": "USER_NOT_FOUND", "details": { "userId": "user-123" } } } ``` The `error` object uses these fields. 1. `message` (`string`): human-readable description. 2. `category` (`string`, optional): error class. 3. `code` (`string`, optional): machine-readable code. 4. `details` (`object`, optional): structured context. For procedures, the envelope is the HTTP response body. For streams, each SSE `data:` payload is a JSON envelope. Stream error events are normal SSE events whose envelope has `ok: false`. ## Procedures Procedures represent request-response RPC operations. A procedure starts when application code invokes a generated procedure client function. The generated client builds an HTTP request, sends JSON input when input exists, and decodes one JSON response envelope. ### HTTP Request The client sends an HTTP `POST` request. 1. **Method:** `POST`. 2. **URL structure:** `//`. 3. **Headers:** `Content-Type: application/json` and `Accept: application/json`. 4. **Body:** JSON-encoded procedure input when `input` is defined. Example request body: ```json { "userId": "user-123" } ``` If the procedure does not define `input`, the operation has no application-level input payload. Generated clients and servers should still preserve the same HTTP method, URL, and response semantics. ### Server Handling The server processes a procedure request in three stages. 1. **Routing:** the URL path is mapped to an RPC service and procedure handler. 2. **Deserialization and validation:** the JSON body is decoded and validated against the operation input when input exists. 3. **Handler execution:** the user-defined handler executes with validated input, or without input when the operation does not define input. Middleware hooks may run custom logic such as authentication, authorization, validation, logging, metrics, tracing, or rate limiting. ### HTTP Response The server returns one HTTP response. 1. **Status code:** `200 OK` for successfully processed requests, even when `ok` is `false` and the envelope contains an application error. 2. **Headers:** `Content-Type: application/json`. 3. **Body:** a success or failure envelope. Application-level success or failure is represented by the response envelope. Clients must inspect the envelope to distinguish successful output from application errors; they must not treat HTTP `200 OK` as application success by itself. Success envelope: ```json { "ok": true, "output": { "id": "user-123", "email": "john.doe@example.com" } } ``` Failure envelope: ```json { "ok": false, "error": { "message": "User not found.", "category": "NotFound", "code": "USER_NOT_FOUND", "details": { "userId": "user-123" } } } ``` If the procedure does not define `output`, a successful response still uses the success envelope, but it does not need to include application-level output data. ### Client Handling The generated client decodes the envelope and returns output or error to application code. 1. It parses the JSON response body. 2. If `ok` is `true`, it returns the decoded output when output exists. 3. If `ok` is `false`, it returns the decoded error. 4. Transport failures may use retry policies such as exponential backoff, depending on client configuration. ## Streams Streams represent server-pushed RPC operations. A stream starts when application code subscribes through a generated stream client. The generated client opens an HTTP request with JSON input when input exists. If the server accepts the subscription, the response becomes a persistent SSE connection. The stream handler may emit multiple output or error envelopes over time. ### HTTP Request The client opens the stream with an HTTP `POST` request. 1. **Method:** `POST`. 2. **URL structure:** `//`. 3. **Headers:** `Accept: text/event-stream` and `Content-Type: application/json`. 4. **Body:** JSON-encoded stream input when `input` is defined. Example request body: ```json { "chatId": "room-42" } ``` If the stream does not define `input`, the subscription does not require an application-level input payload. Browser implementations need one extra consideration. Native browser `EventSource` only supports `GET` requests and does not support sending a JSON request body. VPP-2 compatible browser clients must therefore use a client implementation that supports `POST`, request bodies, and incremental `text/event-stream` parsing, such as `fetch` with the Streams API or an equivalent abstraction. ### Server Handling The server validates input and initializes the SSE channel. 1. **Validation:** invalid input returns a single JSON error response. 2. **Connection setup:** accepted streams return `200 OK`, `Content-Type: text/event-stream`, `Cache-Control: no-cache`, and `Connection: keep-alive`. 3. **Handler execution:** the stream handler runs with an `emit` function. Middleware hooks are available for cross-cutting behavior in stream flows. ### Event Emission Handlers call `emit` to push events. Each emitted event is serialized as a JSON envelope and written as SSE `data`. An emitted stream error is also an event. It uses the same SSE framing as any other emitted item, but its JSON envelope has `ok: false` and an `error` object instead of successful output. JSON payloads must be single-line for SSE framing safety. Newline-sensitive formatting must be handled before transmission. Success event: ```text data: {"ok":true,"output":{"messageId":"msg-abc","text":"Hello world!"}} ``` Error event: ```text data: {"ok":false,"error":{"message":"You do not have permission to view this chat."}} ``` If the stream does not define `output`, emitted success envelopes do not need to include application-level output data. ### Stream Control Events VPP-2 uses SSE control events for stream lifecycle signals that are not application output. Keep-alive pings are SSE comments. They carry no application data and are ignored by generated clients. ```text : ping ``` Clean stream completion is represented by the reserved `close` SSE event. ```text event: close data: {} ``` When a stream handler completes successfully, the server must send this `close` event before closing the underlying HTTP connection. Generated clients must consume the `close` event internally, stop the subscription loop, and avoid triggering reconnection logic for that stream. The `close` event is not an application output, not an error envelope, and must not be delivered to application code. ### Keep-Alive Events Servers send periodic SSE comments to keep connections alive. 1. **Format:** `: ping\n\n`. 2. **Interval:** server-configurable, commonly 30 seconds. 3. **Client behavior:** generated clients ignore ping comments and do not surface them to application code. ### Client Event Loop The generated client processes stream events continuously. 1. It parses SSE frames. 2. It ignores comment ping frames. 3. It handles `event: close` as clean stream completion. 4. It deserializes JSON payloads from application data events. 5. It delivers output or error envelopes to application code. Stream errors are delivered from `ok: false` event envelopes; they are not the same thing as transport failures. ### Stream Termination A stream can end by client cancellation, handler completion, or network interruption. When the handler completes successfully, the server must send the `event: close` control event before closing the connection. If the connection ends without a `close` event, generated clients may treat it as a transport interruption and reconnect automatically with configurable retry behavior, re-sending the original subscription request when applicable. ## Required Behavior for Compatible Plugins A plugin may call itself VPP-2 compatible only if it follows these rules. 1. It must recognize `@rpc`, `@proc`, and `@stream`. 2. It must treat an `@rpc` type as an RPC service container. 3. It must treat `@proc` members as request-response HTTP procedures. 4. It must treat `@stream` members as SSE stream subscriptions. 5. It must derive route names from the VDL service and operation names. 6. It must use the URL structures defined by this protocol. 7. It must encode operation input as JSON when input exists. 8. It must validate operation input against the VDL-declared shape before handler execution. 9. It must decode operation output and errors through the envelope model. 10. It must preserve the optional nature of `input` and `output`. 11. It must preserve the distinction between procedure responses, stream events, application errors, and transport failures. 12. It must return HTTP `200 OK` for successfully processed procedure requests, including application errors represented by `ok: false`. 13. When generating stream server implementations, it must preserve clean stream completion semantics with the reserved `event: close` control event. 14. When generating browser stream clients, it must use a `POST`-capable incremental `text/event-stream` reader instead of relying on native `EventSource`. 15. It must not reinterpret VPP-2 streams as WebSockets, polling, or another transport while claiming VPP-2 wire compatibility. 16. It must not reinterpret VPP-2 procedures as `GET`, query-parameter calls, or non-envelope responses while claiming VPP-2 wire compatibility. Plugins may provide additional framework integrations, generated helpers, runtime adapters, middleware APIs, testing utilities, or documentation output. Those additions must not change the protocol semantics described here. ## Recommended Output Behavior VPP-2 compatible plugins should generate target-idiomatic artifacts while preserving the same RPC model. Depending on the target, useful outputs may include typed clients, server interfaces, handler stubs, route registration code, input and output models, stream subscriber APIs, documentation, mock servers, integration tests, or protocol manifests. Plugins should also preserve relevant cross-protocol metadata when it applies. For example, if a VPP-2 operation is marked with a global deprecation annotation, generated clients, handlers, and documentation should surface that lifecycle information where the target ecosystem supports it. ## Non-Goals VPP-2 intentionally does not define every part of an application stack. This protocol does not define authentication models, authorization policies, business-specific validation rules, retry policy defaults, handler business logic, framework-specific router internals, database access, persistence, OpenAPI output shape, binary transports, WebSocket transports, message broker behavior, or custom non-HTTP RPC transports. Those concerns may be handled by other protocols, plugin configuration, generated framework integrations, or user code. ## Compatibility Statement VPP-2 is the standard RPC contract for VDL plugins. A plugin may call itself **VPP-2 compatible** only if it preserves the annotation vocabulary, schema shape, optional input/output behavior, JSON payload model, HTTP procedure behavior, SSE stream behavior, and envelope semantics defined in this document. --- Source: https://vdl.varavel.com/docs/protocols/policy/ Markdown: https://vdl.varavel.com/docs/protocols/policy/index.md Content file: content/docs/protocols/vpp-3-policy.md # VPP-3: Policy > Type-safe authorization checks compiled into native boolean functions. Canonical HTML: https://vdl.varavel.com/docs/protocols/policy/ Markdown: https://vdl.varavel.com/docs/protocols/policy/index.md **VPP-3: Policy** defines a standard way to model authorization checks in VDL and compile them into native boolean functions. The model is intentionally small. A policy is a normal VDL `type` annotated with `@policy`. The fields of that type define the data available to the authorization check, and the annotation defines the boolean expressions that allow or deny the action. VPP-3 can model Role-Based Access Control (RBAC), Attribute-Based Access Control (ABAC), and combinations of both. In RBAC, access is decided from roles, groups, permissions, or capabilities carried by the principal or context. In ABAC, access is decided from attributes of the principal, resource, and context. Most real systems mix both styles, such as allowing Finance users to approve invoices only when MFA is verified and the invoice is not locked. VPP-3 does not treat RBAC and ABAC as separate features. Both are expressed as type-checked boolean conditions over the policy inputs. ```vdl @policy({ allow "principal.role == 'Admin'" }) type CanCreateUser { principal User } ``` A VPP-3 compatible plugin validates the policy expressions against the VDL schema before generating code. The generated function returns `true` when access is allowed and `false` when access is denied. ## Why This Protocol Exists Authorization logic is easy to scatter across handlers, services, jobs, UI flows, and framework hooks. Once that happens, it becomes difficult to audit, test, and keep consistent across languages. VPP-3 gives VDL projects a shared, type-safe way to describe authorization decisions while still generating idiomatic code for each target language. The goal is not to create a dynamic policy engine inside VDL. The goal is to define authorization checks in schemas, verify them before code is emitted, and generate simple functions that application code can call anywhere. This gives policy plugins three important properties. 1. **Static safety:** invalid field references, invalid enum values, unsafe optional field access, and incompatible comparisons are rejected before code generation. 2. **Runtime safety:** generated code or adapters must fail closed when runtime values do not match the expected shape. 3. **Simple integration:** the primary generated API is a boolean function, not a policy runtime that applications must host or configure. ## Core Concepts VPP-3 uses three policy inputs. 1. **Principal:** the identity attempting to perform something. A principal can be a user, service account, worker, API client, process, or any other authenticated identity represented by a VDL type. 2. **Resource:** the object the principal wants to access or modify. Not every policy needs a resource. 3. **Context:** transient data for the current decision, such as MFA status, tenant, client IP, time window, session state, or request metadata. Not every policy needs context. The action being authorized is represented by the name of the `@policy` type itself. There is no special `action` variable inside policy expressions. VPP-3 does not require a special identity field such as `id`. A policy may compare any fields declared by the principal, resource, and context types, as long as the expressions are type-safe. ## The `@policy` Annotation VPP-3 defines one annotation: `@policy`. The annotation is attached to a `type` declaration. That annotated type is the authorization check. ```vdl @policy({ allow "principal.id == resource.ownerId" }) type CanReadInvoice { principal User resource Invoice } ``` The `@policy` annotation receives one object argument with these fields. 1. `allow` (`string`, required): a non-empty boolean expression that can allow the check. 2. `deny` (`string`, optional): a non-empty boolean expression that can explicitly deny the check. The `allow` expression is required. Empty strings and strings containing only whitespace are invalid. If a policy should allow everything explicitly, write `allow "true"`. The `deny` expression is optional. If present, it must be non-empty. If a policy should deny everything explicitly, write `deny "true"`. ## Policy Type Shape The annotated type defines the inputs available to the policy expression. A VPP-3 policy type must use these field names. 1. `principal` is required. 2. `resource` is optional. 3. `context` is optional. No other fields are part of VPP-3 v1. ```vdl @policy({ allow "principal.role == 'Admin'" }) type CanAccessAdminPanel { principal User } ``` ```vdl @policy({ allow "principal.id == resource.ownerId" }) type CanReadInvoice { principal User resource Invoice } ``` ```vdl @policy({ allow "principal.role == 'Admin' && context.mfaVerified == true" }) type CanRunMaintenance { principal User context SessionContext } ``` ```vdl @policy({ allow "principal.id == resource.ownerId && context.mfaVerified == true" }) type CanApproveInvoice { principal User resource Invoice context SessionContext } ``` The `principal`, `resource`, and `context` fields must not be optional. If a policy needs a resource or context, it should declare that field as required. If it does not need one, it should omit the field entirely. The `principal`, `resource`, and `context` fields must reference named VDL `type` declarations. They must not be scalar fields, enum fields, arrays, maps, inline objects, or references to non-object values. If a policy needs scalar input, wrap that value in a small named context or resource type. The fields should be declared in the order `principal`, `resource`, `context` for readability. Plugins must generate function parameters in that canonical order, omitting roots that are not declared. ## Policy Naming The annotated type name is the canonical name of the authorization check. Policy type names should be predicate-style names and should normally start with `Can`. ```text CanCreateUser CanApproveInvoice CanAccessAdminPanel CanRunMaintenanceJob CanPublishInvoiceEvent ``` This convention makes policy types read like boolean questions and avoids collisions with request types, event types, resource types, and command payloads. Plugins must derive generated function names from the annotated type name. They may adapt casing or export style to the target language, but they must not add semantic prefixes or remove suffixes. Examples: | Policy type | TypeScript | Go | Rust | | :--------------------- | :--------------------- | :--------------------- | :------------------------ | | `CanCreateUser` | `canCreateUser` | `CanCreateUser` | `can_create_user` | | `CanApproveInvoice` | `canApproveInvoice` | `CanApproveInvoice` | `can_approve_invoice` | | `CanRunMaintenanceJob` | `canRunMaintenanceJob` | `CanRunMaintenanceJob` | `can_run_maintenance_job` | Plugins must not reject a policy only because its type name does not start with `Can`. The prefix is the idiomatic VDL convention, not a validity requirement. ## Evaluation Semantics The generated policy function returns a boolean. 1. `true` means the action is allowed. 2. `false` means the action is denied. VPP-3 uses this evaluation order. 1. Validate runtime values when the target language or adapter cannot guarantee their shape statically. 2. If `deny` exists and evaluates to `true`, return `false`. 3. Evaluate `allow`. 4. If `allow` evaluates to `true`, return `true`. 5. Otherwise, return `false`. This means explicit deny wins, allow enables access, and the default decision is deny. In pseudocode: ```ts function canApproveInvoice(principal, resource, context): boolean { if (!runtimeValuesAreValid(principal, resource, context)) { return false; } if (denyExpression(principal, resource, context) === true) { return false; } return allowExpression(principal, resource, context) === true; } ``` ## Complete Example ```vdl type User { id string name string role string department? string disabled bool } enum InvoiceStatus { Locked Unlocked } type Invoice { ownerId string status InvoiceStatus } type SessionContext { mfaVerified bool } @policy({ allow " principal.id == resource.ownerId || ( has(principal.department) && principal.department == 'Finance' && context.mfaVerified == true ) " deny " principal.disabled == true || resource.status == 'Locked' " }) type CanApproveInvoice { principal User resource Invoice context SessionContext } ``` This policy allows a user to approve an invoice when the user owns the invoice, or when the user belongs to the Finance department and has verified MFA. It denies the check when the principal is disabled or when the invoice is locked. Because `deny` is evaluated first, a disabled Finance user cannot approve a locked invoice even if the `allow` expression would otherwise match. ## Condition Language `allow` and `deny` strings contain VPP-3 condition expressions. Expressions may span multiple lines because VDL string literals can contain multiline text. Whitespace outside literals is not semantically meaningful. The condition language is deliberately small so plugins can parse it, type-check it, and compile it to many target languages. ### Roots Expressions may reference only the roots declared by the policy type. 1. `principal`: available in every valid policy. 2. `resource`: available only when the policy type declares a `resource` field. 3. `context`: available only when the policy type declares a `context` field. Field access uses dot notation. ```text principal.id resource.ownerId context.mfaVerified ``` Field paths may traverse object fields declared in VDL. They must not use dynamic property access, array indexing, map indexing, method calls, implicit database lookups, or implicit entity lookups. ### Literals Expressions support these literals. 1. Booleans: `true`, `false`. 2. Strings: single-quoted values such as `'Finance'`. 3. Numbers: `100`, `10.5`. 4. Arrays: comma-separated literal arrays such as `['admin', 'owner']`. String literals use single quotes so expressions can be embedded naturally inside VDL double-quoted strings. Plugins must support escaping `\'` and `\\` inside expression string literals. When a plugin receives an `allow` or `deny` expression, VDL has provided the outer double-quoted string content; the VPP-3 expression parser must still interpret escapes inside inner expression string literals. For example, `'Owner\'s Team'` represents `Owner's Team`, and a literal backslash should be written as `\\`. ### Enum Values Enum fields are compared by value using string literals. ```vdl enum InvoiceStatus { Locked Unlocked } ``` ```text resource.status == 'Locked' ``` When an enum member has an explicit value, the string literal must match that value. When an enum member does not have an explicit value, the string literal must match the member name. A plugin must validate enum string literals at generation time. If `resource.status` is `InvoiceStatus`, then `resource.status == 'Archived'` is invalid unless `Archived` is a valid value of that enum. Plugins may generate target-native enum comparisons even though the policy expression uses a string literal. ### Operators VPP-3 condition expressions support these operators. 1. Logical operators: `&&`, `||`, `!`. 2. Comparison operators: `==`, `!=`, `>`, `>=`, `<`, `<=`. 3. Membership operator: `in`. 4. Grouping: parentheses. Examples: ```text principal.id == resource.ownerId principal.department == 'Finance' && context.mfaVerified == true principal.role in ['admin', 'owner'] principal.id in resource.allowedUserIds !(resource.status == 'Locked') ``` The `in` operator requires the right side to be an array expression or an array-typed field. The element type must be compatible with the left side. Operator precedence is defined as follows. 1. `!` 2. `==`, `!=`, `>`, `>=`, `<`, `<=`, `in` 3. `&&` 4. `||` Parentheses override precedence. ### Unsupported Expressions VPP-3 v1 intentionally does not support arbitrary expression features. Plugins must reject function calls other than `has(...)`, arithmetic, regex matching, string interpolation, array indexing, map indexing, method calls, async operations, imports, dynamic property access, external lookups, and custom user-defined functions. These restrictions keep policies portable and easy to compile into native code. ## Optional Fields and `has` Optional VDL fields require explicit presence checks. The `has(path)` function returns `true` when an optional field is present at runtime. ```text has(principal.department) ``` A plugin must reject unsafe optional field access. If `department` is optional, this expression is valid: ```text has(principal.department) && principal.department == 'Finance' ``` This expression is invalid: ```text principal.department == 'Finance' ``` `has(...)` only guards access in the expression branch where it is known to be true. For example, in `A && B`, a positive `has(path)` proven in `A` may guard access to `path` in `B`. In `A || B`, each branch must be safe on its own because either branch may evaluate independently. If a plugin cannot prove optional field access is safe, it must reject the policy before generating code. ## Static Validation Static validation is the core safety guarantee of VPP-3. Before generating files, a compatible plugin must parse and validate every `allow` and `deny` expression against the VDL IR. The plugin must reject the policy if any of these checks fail. 1. `@policy` is attached to something other than a `type` declaration. 2. The policy type does not declare a required `principal` field. 3. `principal`, `resource`, or `context` is marked optional. 4. `principal`, `resource`, or `context` does not reference a named VDL object type. 5. The policy type declares fields other than `principal`, `resource`, and `context`. 6. `allow` is missing, empty, or not a string. 7. `deny` is present but empty or not a string. 8. An expression references a root that the policy type does not declare. 9. A referenced field does not exist. 10. A referenced enum value does not exist. 11. A comparison uses incompatible types. 12. An `in` expression compares incompatible element types. 13. A condition does not evaluate to boolean. 14. An optional field is accessed without a safe `has(...)` guard. 15. An unsupported operator, function, literal, or expression form is used. If validation fails, the plugin must stop generation and return diagnostics tied to the source `@policy` annotation or policy type. When possible, diagnostics should point to the expression and subexpression that failed validation. ## Runtime Validation and Fail-Closed Behavior Static validation proves that the policy definition is valid. It does not always prove that runtime values are trustworthy. Generated code or generated adapters must validate runtime values when the target language, boundary, or integration receives untrusted data. This is especially important for dynamic languages, HTTP handlers, message consumers, plugin boundaries, or any place where values may come from outside the target language type system. Runtime validation must fail closed. If `principal`, `resource`, or `context` does not match the expected VDL shape at runtime, the generated authorization function must not return `true`. It should return `false` or use a target-idiomatic checked wrapper that reports the validation error while preserving a deny decision. ## Generated Function Shape The primary generated API is a pure boolean function. The generated function name is derived from the annotated policy type name, adapted to the target language naming style. The parameter order is always `principal`, `resource`, then `context`, omitting roots that are not declared. Examples: ```vdl @policy({ allow "principal.role == 'Admin'" }) type CanAccessAdminPanel { principal User } ``` ```ts function canAccessAdminPanel(principal: User): boolean ``` ```vdl @policy({ allow "principal.id == resource.ownerId" }) type CanReadInvoice { principal User resource Invoice } ``` ```ts function canReadInvoice(principal: User, resource: Invoice): boolean ``` ```vdl @policy({ allow "principal.role == 'Admin' && context.mfaVerified == true" }) type CanRunMaintenance { principal User context SessionContext } ``` ```ts function canRunMaintenance(principal: User, context: SessionContext): boolean ``` ```vdl @policy({ allow "principal.id == resource.ownerId && context.mfaVerified == true" }) type CanApproveInvoice { principal User resource Invoice context SessionContext } ``` ```ts function canApproveInvoice( principal: User, resource: Invoice, context: SessionContext, ): boolean ``` Plugins must detect generated name collisions and report diagnostics instead of silently overwriting generated symbols. ## Generated Code Requirements Generated policy code must be deterministic, pure, and local. The generated decision must not require network calls, database access, local configuration files, runtime expression parsing, or a dynamic policy store. Plugins may generate helper types, checked wrappers, test utilities, explanation helpers, or diagnostics helpers. Those additions must not change the canonical boolean decision semantics. Plugins should use VPP-0 generated model types or compatible target-native model types when available. ## Integration With Other Protocols VPP-3 is transport agnostic. A generated policy function can protect RPC handlers, event consumers, CLI actions, background jobs, UI actions, feature flags, maintenance tasks, or in-memory workflows. Other protocols may call VPP-3 generated functions, but VPP-3 does not depend on those protocols. For example, a VPP-2 RPC handler named `createUser` can call a generated `canCreateUser(...)` policy function, but the policy itself remains independent from RPC. Plugins should preserve cross-protocol metadata when useful. For example, if a policy type is marked as deprecated through the global deprecation standard, generated policy documentation or code comments should surface that lifecycle information where the target supports it. ## Required Behavior for Compatible Plugins A plugin may call itself VPP-3 compatible only if it follows these rules. 1. It must recognize `@policy` on `type` declarations. 2. It must require a non-optional `principal` field. 3. It must support optional `resource` and `context` fields, and require them to be non-optional when present. 4. It must require `principal`, `resource`, and `context` to reference named VDL object types. 5. It must reject fields other than `principal`, `resource`, and `context` in policy types. 6. It must require non-empty `allow` and support optional non-empty `deny`. 7. It must parse and type-check the condition language before generating code. 8. It must enforce root availability based on the policy type fields. 9. It must validate enum string literals against enum values. 10. It must enforce safe optional field access through `has(...)`. 11. It must evaluate `deny` before `allow`. 12. It must return `false` when `deny` evaluates to `true`. 13. It must return `true` only when `allow` evaluates to `true` and no `deny` matched. 14. It must return `false` when no rule allows the check. 15. It must generate a boolean authorization function derived from the policy type name. 16. It must fail closed when runtime values are invalid. 17. It must not require a runtime policy interpreter to evaluate the canonical decision. ## Recommended Output Behavior VPP-3 compatible plugins should generate target-idiomatic authorization APIs while preserving the same model. Useful outputs may include policy functions, modules, test helpers, generated documentation, decision explanation helpers, mock fixtures, or typed adapters for framework integration. Plugins should make policy logic easy to test. A generated function should be callable directly with principal, resource, and context values without needing to boot an application server. ## Non-Goals VPP-3 intentionally does not define every part of an authorization system. This protocol does not define authentication, identity providers, session management, database loading, resource fetching, dynamic policy stores, admin UIs, audit log formats, network transport, RPC integration, event integration, role storage, relationship graph traversal, or application-specific business workflows. Those concerns may be handled by other protocols, plugin configuration, generated adapters, infrastructure, or application code. ## Compatibility Statement VPP-3 is the standard type-safe policy contract for VDL plugins. A plugin may call itself **VPP-3 compatible** only if it preserves the `@policy` annotation model, policy type shape, naming rules, condition language, static validation rules, fail-closed runtime behavior, generated boolean API, and evaluation semantics defined in this document. --- Source: https://vdl.varavel.com/docs/protocols/events/ Markdown: https://vdl.varavel.com/docs/protocols/events/index.md Content file: content/docs/protocols/vpp-4-events.md # VPP-4: Events > Transport-agnostic event payloads and routing subjects with the @event annotation. Canonical HTML: https://vdl.varavel.com/docs/protocols/events/ Markdown: https://vdl.varavel.com/docs/protocols/events/index.md **VPP-4: Events** defines a standard way to model asynchronous event payloads and routing subjects in VDL. The model is intentionally transport agnostic. An event can be published through NATS, RabbitMQ, Kafka, HTTP webhooks, a database outbox, an internal queue, or any other event delivery system. VPP-4 only defines the schema contract: the payload type, the routing subject template, and the rules that connect them. ```vdl @event("auth.userCreated.{userId}") type UserCreatedEvent { userId string email string timestamp datetime } ``` A VPP-4 compatible plugin turns this declaration into a typed payload representation and subject-building utilities for the target language. ## Why This Protocol Exists Event-driven systems often depend on routing strings: subjects, topics, routing keys, webhook paths, stream names, or other delivery identifiers. Those strings are easy to mistype and easy to separate from the payload data they describe. VPP-4 keeps the routing subject and payload schema together. If a subject contains a dynamic placeholder such as `{userId}`, the payload must contain a matching field named `userId`. This makes events self-contained: when an event is logged, persisted, replayed, forwarded, or delivered through a transport without rich routing metadata, the payload still contains the values needed to identify and route it. ## The `@event` Annotation VPP-4 defines one annotation: `@event`. The annotation is attached to a `type` declaration and receives one string argument: the routing subject template. ```vdl @event("orders.statusChanged.{orderId}") type OrderStatusChanged { orderId string status string } ``` The annotated type is the event payload. Its fields define the data that publishers emit and subscribers consume. The subject template is a literal string. Static segments are copied as-is. Dynamic segments are written as placeholders in `{fieldName}` form. ## Subject Templates A subject template is the routing string attached to an event declaration. ```text auth.userCreated.{userId} ``` The template may contain zero or more placeholders. Each placeholder references a field in the annotated payload type. Examples: ```vdl @event("audit.login") type UserLoginAudit { userId string at datetime } @event("orders.{orderId}.statusChanged") type OrderStatusChanged { orderId string status string } @event("tenant.{tenantId}.users.{userId}.created") type TenantUserCreated { tenantId string userId string email string } ``` A template with no placeholders is valid. In that case, the subject builder takes no placeholder arguments and returns the static subject string. ## Placeholder Rules Placeholders make routing dynamic, so they must be statically validated. A VPP-4 compatible plugin must enforce these rules. 1. Every placeholder must exactly match a field declared on the annotated event type. 2. Placeholder fields must be top-level fields of the payload type. 3. Placeholder fields must be primitive scalar fields. 4. Nested placeholder paths are not allowed. 5. Object, array, map, and other non-primitive placeholder fields are not allowed. 6. The same placeholder may appear multiple times in the subject template. Valid example: ```vdl @event("orders.statusChanged.{orderId}") type OrderStatusChanged { orderId string status string } ``` Invalid example, because `region` is not declared in the payload: ```vdl @event("logistics.{region}.delivery_failed") type DeliveryFailed { deliveryId string } ``` Valid example, because repeated placeholders reuse the same field value: ```vdl @event("audit.{tenantId}.users.{tenantId}.created") type UserAuditCreated { tenantId string } ``` Invalid example, because nested placeholder paths are not allowed: ```vdl @event("accounts.{user.id}.created") type AccountCreated { user { id string } } ``` The plugin must reject invalid placeholders before generating code. ## Payload Semantics The annotated type is the event payload. VPP-4 does not define a separate envelope. The event payload is exactly the data structure represented by the annotated VDL type. Because placeholder values must also exist in the payload, events remain self-contained. Consumers can inspect the payload without relying on transport-specific routing metadata being preserved forever. VPP-4 does not require every payload field to appear in the subject. Only fields needed for routing or partitioning should be included in the template. ## Subject Builder Semantics Plugins must generate a way to build the final subject string for each event. A subject builder always returns a `string`. All placeholder values are rendered as string segments when composing the final subject. If a placeholder field is not a string, the subject builder must convert that primitive value using deterministic target-appropriate formatting. A placeholder field's type controls the generated builder parameter type and validation, the final subject segment is always text. When a placeholder appears more than once, the builder should accept that field once and reuse the same value for every occurrence. For deterministic APIs, placeholder parameters should follow the order of first appearance in the template. Example template: ```text tenant.{tenantId}.users.{userId}.created ``` Conceptual generated builder: ```ts function buildTenantUserCreatedSubject(tenantId: string, userId: string): string { return `tenant.${tenantId}.users.${userId}.created`; } ``` ## Generated API Shape Generators transform an `@event` declaration into two primary artifacts. 1. A payload type for the target language. 2. Subject formatting utilities for the event subject template. Depending on the target, plugins may also emit catalogs, registries, documentation, publish helpers, subscribe helpers, test fixtures, or broker-specific adapters. ### TypeScript Example ```ts export interface UserCreatedEvent { userId: string; email: string; timestamp: string; } export function buildUserCreatedEventSubject(userId: string): string { return `auth.userCreated.${userId}`; } ``` ### Go Example ```go type UserCreatedEvent struct { UserId string `json:"userId"` Email string `json:"email"` Timestamp time.Time `json:"timestamp"` } func BuildUserCreatedEventSubject(userId string) string { return "auth.userCreated." + userId } ``` These examples are illustrative. Each plugin should generate code that is idiomatic for its target while preserving the same payload and subject semantics. ## Event Catalogs Plugins should aggregate event declarations into a centralized event catalog when that is useful for the target ecosystem. Catalogs help applications inspect available events, route incoming messages, generate documentation, configure subscribers, or connect generated code to framework-specific registries. If a plugin emits event catalog metadata, the literal subject template must be exposed under the `Subject` key. Conceptual catalog shape: ```go type EventMetadata struct { Name string // e.g. "UserCreatedEvent" Subject string // e.g. "auth.userCreated.{userId}" } ``` The `Subject` value is the template from the VDL annotation, not a formatted runtime subject. ## Required Behavior for Compatible Plugins A plugin may call itself VPP-4 compatible only if it follows these rules. 1. It must recognize `@event` on `type` declarations. 2. It must require the `@event` argument to be a string subject template. 3. It must treat the annotated type as the event payload. 4. It must validate every placeholder against the payload type. 5. It must reject placeholders that reference missing fields. 6. It must reject nested placeholder paths. 7. It must reject non-primitive placeholder fields. 8. It must allow repeated placeholders and reuse the same field value. 9. It must generate or expose subject formatting behavior that returns a string. 10. It must preserve the literal subject template for generated metadata and catalogs. ## Recommended Output Behavior VPP-4 compatible plugins should generate target-idiomatic event APIs while preserving the same event model. Useful outputs may include payload models, subject builders, event catalogs, publish helpers, subscriber helpers, broker adapters, generated documentation, mock event fixtures, or routing metadata. Plugins should preserve relevant cross-protocol metadata when useful. For example, if an event payload type is marked as deprecated through the global deprecation standard, generated payload types, docs, and catalogs should surface that lifecycle information where the target supports it. ## Non-Goals VPP-4 intentionally does not define every part of an event platform. This protocol does not define broker selection, delivery guarantees, retry policies, dead-letter handling, consumer groups, partitions, transactions, schemas for broker configuration, authentication, authorization, persistence, serialization formats beyond the generated payload model, or runtime publish/subscribe behavior. Those concerns may be handled by other protocols, other plugins, generated adapters, infrastructure, or application code. ## Compatibility Statement VPP-4 is the standard event contract for VDL plugins. A plugin may call itself **VPP-4 compatible** only if it preserves the `@event` annotation model, payload semantics, subject template rules, placeholder validation, subject builder behavior, and catalog metadata requirements defined in this document. --- Source: https://vdl.varavel.com/docs/reference/ Markdown: https://vdl.varavel.com/docs/reference/index.md Content file: content/docs/reference/_index.md # Reference > Reference material for VDL language behavior, formatting, annotations, RPCs, and events. Canonical HTML: https://vdl.varavel.com/docs/reference/ Markdown: https://vdl.varavel.com/docs/reference/index.md Reference pages define the exact behavior of the VDL language, formatter, and official annotation models. --- Source: https://vdl.varavel.com/docs/reference/spec/ Markdown: https://vdl.varavel.com/docs/reference/spec/index.md Content file: content/docs/reference/spec.md # Language Specification > Varavel Definition Language (VDL) specification. Canonical HTML: https://vdl.varavel.com/docs/reference/spec/ Markdown: https://vdl.varavel.com/docs/reference/spec/index.md ## Overview VDL is a schema-first IDL for defining typed contracts that drive code generation across multiple targets. A VDL document is built from a compact core of declarations and extended through annotations. The language is designed to keep parsing stable while allowing domain semantics to evolve without changing the core grammar. ## Language Model The parser recognizes three declaration forms at the top level: `type`, `const`, and `enum`. Includes and docstrings are also valid top-level nodes. Domain semantics are expressed with annotations. An annotation can be attached to declarations, fields, and enum members where supported by grammar. ## Core Syntax ```vdl include "./shared.vdl" """ Standalone documentation """ @tag @meta({ owner "core" }) type User { """ Field documentation """ id string email? string } @config const maxPageSize = 100 enum Status { Active Disabled Archived = "archived" } ``` ## Comments VDL supports both single-line and multi-line comments. ```vdl // A single-line comment /* A multi-line comment that can span multiple lines */ type Example { // Comments can appear everywhere value string } ``` Comments are ignored by the parser and have no effect on schema semantics, generated code, or runtime behavior. ## Naming Conventions VDL enforces naming conventions through formatter and tooling to keep generated code predictable. | Element | Convention | Example | | :-------------- | :----------- | :--------------------------- | | Types and Enums | `PascalCase` | `UserProfile`, `OrderStatus` | | Enum Members | `PascalCase` | `Pending`, `InProgress` | | Type Members | `camelCase` | `userId`, `createdAt` | | Constants | `camelCase` | `maxPageSize`, `apiVersion` | | Annotations | `camelCase` | `rpc`, `internal` | ## Includes Includes compose schemas across files. ```vdl // auth.vdl type Session { token string expiresAt datetime } // main.vdl include "./auth.vdl" type AuthInfo { session Session } ``` Included files are resolved by relative path from the current file. Definitions are loaded into the same compilation context, and each file is processed once. ## Declarations ### Type Declarations `type` defines reusable structured data. ```vdl type Product { id string title string price float tags? string[] } ``` Type bodies accept field members (`[?] `) and spread members (`...`). Field members can have attached docstrings and annotations. Type spreads reference complete type declaration names. ```vdl type AuditMetadata { createdAt datetime updatedAt datetime } type Article { ...AuditMetadata title string content string } ``` ### Constant Declarations `const` defines immutable values. ```vdl const apiVersion = "1.2.0" const maxRetries = 5 const featureEnabled = true ``` Constants are dynamic values: ```vdl const timeoutMs = 2500 const serviceName = "billing" ``` ### Enum Declarations `enum` defines named finite sets. Members can be implicit string values, explicit strings, or explicit integers, as long as the enum remains type-consistent. ```vdl enum OrderStatus { Pending Processing Delivered } enum Priority { Low = 1 Medium = 2 High = 3 } ``` Enum bodies support spread entries: ```vdl enum AllRoles { ...StandardRoles Admin = "admin" } ``` Enum spreads reference complete enum declaration names. Enum members also support docstrings and annotations on named members. ## Type System ### Primitive Types | Type | JSON Equivalent | Description | | :--------- | :-------------- | :------------------------- | | `string` | string | UTF-8 encoded text | | `int` | integer | 64-bit signed integer | | `float` | number | 64-bit floating point | | `bool` | boolean | Logical value (true/false) | | `datetime` | string | RFC 3339 date-time string | > **Note:** `datetime` values must be RFC 3339 formatted strings. RFC 3339 is a strict subset of ISO 8601 chosen to ensure consistent parsing across implementations. ### Arrays Arrays are written with `[]` suffix. ```vdl type Example { ids string[] matrix int[][] } ``` ### Maps Maps use bracket syntax. ```vdl type Metrics { counters map[int] usersById map[User] } ``` ### Inline Object Types Inline objects are anonymous structured types. ```vdl type LocationEnvelope { location { latitude float longitude float } } ``` ## Annotations Annotations are metadata nodes with optional argument values. ```vdl @flag @meta({ owner "platform" tier "gold" }) type User { @id id string } ``` Annotation syntax: ```vdl @name @name() ``` Grammar supports annotation attachment on `type`, `const`, and `enum` declarations, on fields inside type members, and on named enum members. Multiple annotations are allowed. ## Data Literals Data literals are used by `const` values and annotation arguments. ### Scalar Literals ```vdl "text" 42 3.14 true false defaultTimeout // Constant reference Color.Red // Enum member reference ``` ### Object Literals Object entries are space-separated key/value pairs. Commas are not part of the syntax. ```vdl const appConfig = { host "localhost" port 8080 tls true } ``` Object spreads are valid and reference complete constants by name. ```vdl const prodConfig = { ...baseConfig port 443 } ``` ### Array Literals Array elements are also space-separated. ```vdl const roles = ["admin" "editor" "viewer"] const retryBackoffMs = [100 250 500 1000] ``` All elements in a data literal array must be of the same type. ## References References are identifier-based values used in spreads and scalar literals. ```vdl const defaultStatus = Status.Active const retryLimit = maxRetries type Session { ...BaseSession } ``` A reference supports `Name` and `Name.Member` forms. Spread members use the `Name` form. `Name.Member` is reserved for enum member references in constant values and constant data literals. ## Documentation ### Docstrings Docstrings can be attached to declarations and members or used as standalone documentation nodes. ```vdl """ Represents a user in the system. """ type User { """ Unique user identifier. """ id string } ``` A blank line separates a standalone docstring from the next declaration in the same scope. Inside type bodies, docstrings attach to the following field. Enum bodies attach docstrings to the following named member. Type and enum bodies do not support docstring-only entries. ### External Documentation Files A docstring containing only a relative `.md` path is treated as an external documentation reference. ```vdl """ ./docs/welcome.md """ """ ./docs/user.md """ type User { id string } ``` Paths are resolved relative to the `.vdl` file containing the docstring. ## Complete Example ```vdl include "./common/types.vdl" """ ./docs/catalog.md """ const apiVersion = "1.0.0" const defaultStatus = ProductStatus.Draft enum ProductStatus { Draft Published Archived = "archived" } type Product { ...Entity id string name string price float status ProductStatus tags string[] } type ProductPage { items Product[] total int } const defaultProduct = { ...baseProduct name "Sample" price 9.99 status ProductStatus.Draft } const sampleTags = ["featured" "popular" "seasonal"] ``` ## Related Specifications - RPC modeling and request lifecycle are documented in [RPC Annotations](/docs/reference/rpc/). - Event annotation rules are documented in [Event Annotations](/docs/reference/events/). - Deprecation annotation rules are documented in [Deprecation Annotations](/docs/reference/deprecations/). --- Source: https://vdl.varavel.com/docs/reference/formatting-guide/ Markdown: https://vdl.varavel.com/docs/reference/formatting-guide/index.md Content file: content/docs/reference/formatting-guide.md # Formatting Guide > Standard formatting conventions for VDL. Canonical HTML: https://vdl.varavel.com/docs/reference/formatting-guide/ Markdown: https://vdl.varavel.com/docs/reference/formatting-guide/index.md This guide defines how to keep VDL files easy to read, stable in diffs, and predictable across teams. > **Note:** These conventions are enforced by the official formatter. Run `vdl format ./schema.vdl` or use editor format-on-save through the VDL LSP. ## 1. General Principles - Format for consistency, not personal style. - Prefer the smallest readable layout. - Keep related items close; separate unrelated items with one blank line. - Use UTF-8, LF (`\n`), no trailing whitespace, and a final newline. ## 2. Indentation - Use **2 spaces** per indentation level. - Do not use tabs. ```vdl type Example { field string } ``` ## 3. Top-Level Elements Top-level elements are `include`, `type`, `const`, `enum`, comments, and standalone docstrings. - Group `include` statements at the top with no blank lines inside the group. - Use one blank line between top-level declarations. - Avoid multiple consecutive blank lines. ```vdl include "./common.vdl" include "./auth.vdl" const maxPageSize = 100 type Order { id string total float } enum OrderStatus { Pending Paid } ``` ## 4. Blocks and Fields ### 4.1 Block Structure - Put `{` on the same line as the declaration, with one space before it. - Put block members on their own lines. - Align `}` with the declaration start. ```vdl type User { id string name string } ``` ### 4.2 Field Members - Keep one member per line. - Prefer no blank lines between plain fields. - Add one blank line around field docstrings when it improves scanning. - Place spreads before regular fields when possible. ```vdl type User { ...AuditMetadata """ The user's display name. """ name string """ The user's email address. """ email string age? int } ``` ### 4.3 Annotation Placement Annotations carry domain semantics. Keep placement strict and vertical for readability. - Put annotations directly above the declaration or member they target. - Use one annotation per line. - Keep docstring above annotations when both are present. - Keep a single blank line between logical groups of annotated members. ```vdl """ User operations. """ @rpc type Users { @proc GetUser { input { userId string } output { id string email string } } @stream UserEvents { input { userId string } output { event string at datetime } } } ``` ## 5. Spacing | Context | Rule | Example | | :--------------------- | :-------------------------------- | :------------------------ | | Field syntax | `[?] ` | `email? string` | | Assignment | One space around `=` | `const retries = 3` | | Braces (`{}`) | One space before `{` | `type User {` | | Arrays (`[]`) | No spaces inside or before suffix | `string[]`, `int[][]` | | Maps (`map[...]`) | No spaces inside brackets | `map[int]`, `map[User]` | | Annotation call | No space before `(` | `@meta({ owner "core" })` | | Object literal entries | Space-separated key/value | `{ host "localhost" }` | | Array literal entries | Space-separated elements | `["a" "b" "c"]` | | Enum member assignment | One space around `=` | `Archived = "archived"` | ## 6. Comments VDL supports two comment styles, and formatter preserves comment content exactly as written. - **Single-line:** `// ...` - **Multi-line:** `/* ... */` - Comments can appear at top level or inside blocks. ```vdl // Top-level single-line comment /* Top-level multi-line comment */ type Example { // Inside block field string } ``` ## 7. Docstrings - Place docstrings immediately above the element they document. - Enclose in triple quotes (`"""`), preserving internal newlines and formatting. - Prefer concise, purpose-first text. - Use a blank line after a standalone docstring node. ```vdl """ Represents a user in the system. """ type User { """ The unique identifier. """ id string """ The user's full name. """ name string } ``` ## 8. Naming Conventions The formatter automatically enforces the following naming conventions: | Element | Convention | Example | | :-------------- | :----------- | :--------------------------- | | Types and Enums | `PascalCase` | `UserProfile`, `OrderStatus` | | Enum Members | `PascalCase` | `Pending`, `InProgress` | | Type Members | `camelCase` | `userId`, `createdAt` | | Constants | `camelCase` | `maxPageSize`, `apiVersion` | | Annotations | `camelCase` | `rpc`, `proc`, `deprecated` | ### Acronym Handling Acronyms longer than two letters are treated as regular words: ```vdl // Correct type HttpRequest { ... } type JsonParser { ... } // Incorrect type HTTPRequest { ... } type JSONParser { ... } ``` --- Source: https://vdl.varavel.com/docs/about/ Markdown: https://vdl.varavel.com/docs/about/index.md Content file: content/docs/about.md # About VDL > Learn what VDL is, what it provides, and why the core language stays small. Canonical HTML: https://vdl.varavel.com/docs/about/ Markdown: https://vdl.varavel.com/docs/about/index.md ## A Definition Language For Data Contracts VDL is a schema-first language and toolchain for defining structured data contracts once and generating useful artifacts from them. The core language models data, documentation, constants, enums, annotations, and composition. API, RPC, event, documentation, schema-generation, and custom internal workflows are built on top through plugins. ## What VDL Provides VDL is built around three parts. 1. **A compact definition language:** `.vdl` files describe typed data with `type`, `enum`, `const`, `include`, annotations, arrays, maps, inline objects, literals, documentation blocks, and spreads. 2. **Developer tooling:** the CLI formats schemas, compiles them to JSON IR, runs plugin generation, and exposes an LSP for editor diagnostics, completion, hover, definitions, references, rename, document symbols, formatting, and links. 3. **A plugin-first generator runtime:** plugins receive the resolved VDL IR and generate the target artifacts your project needs. ## Why The Core Is Small The language keeps domain semantics out of the grammar. Instead, annotations describe intent and plugins decide what to do with that intent. For example: - `@rpc` can mark a type as an RPC service. - `@proc` and `@stream` can mark RPC operations. - `@event("subject.{id}")` can mark event payload contracts. - `@deprecated` can be interpreted by a JSON Schema plugin. - Your own annotations can drive your own internal generators. This lets VDL support many domains without turning the parser into a collection of framework-specific features. ## Common Use Cases - Generate shared Go and TypeScript data models. - Export JSON Schema for validation, forms, docs, or external integrations. - Generate RPC clients, servers, and OpenAPI documents from annotation-based service contracts. - Generate event subject builders and event catalogs. - Publish static schema explorers for internal or external documentation. - Export VDL IR as JSON for runtime metadata and custom tooling. - Build private plugins that encode your team's framework conventions. ## Project Philosophy - **Schema as source of truth:** contracts should be defined once and reused everywhere. - **Small language, extensible semantics:** core syntax stays stable while plugins evolve domain behavior. - **Readable contracts:** schemas should be understandable during code review without generator knowledge. - **Strong tooling:** diagnostics, formatting, LSP features, and deterministic IR are core to the workflow. - **Practical generation:** generated files are regular text files that can be inspected, tested, committed, and integrated into existing stacks.