This is the abridged developer documentation for Vertex --- # Vertex > A statically typed language for Salesforce. Compiles to Apex, runs locally too. Vertex is very early The language, standard library, and tooling are all still moving. Expect breaking changes between releases, pin to a specific version if you use it on something real, and please [file issues](https://github.com/vertex-run/vertex/issues/new/choose) when things break or feel wrong. Feedback right now is what shapes where Vertex goes next. Here is a taste: ```vertex fn validate_age(age: Int): Result { if age < 0 { Error("age cannot be negative") } else if age > 150 { Error("age is unrealistically large") } else { Ok(age) } } fn label(age: Int): String { if age < 18 { "minor" } else { "adult" } } debug match validate_age(25) { Ok(a) => "${label(a)}, age ${a}", Error(e) => "invalid: ${e}", } // adult, age 25 ``` Sum types, exhaustive pattern matching, string interpolation, and a `Result` the compiler makes you deal with. Paste it into a file and run it with `vertex run`, no org required. ## What you get [Section titled “What you get”](#what-you-get) If it compiles, it runs Static types, exhaustive `match`, `Option` in place of `null`, and `Result` in place of exceptions. The compiler catches a lot of what ships as a production bug in Apex. Features from this decade Sum types, pattern matching, `Option`, `Result`, a pipe operator, type inference, immutability by default. The things Apex developers have wanted for a while. One way to do things No class inheritance, no static/instance split, no implicit null. Fewer ways for a program to surprise you. Local dev loop Write, run, and test without touching a Salesforce org. The JIT runs your code locally in milliseconds. When you’re ready, `vertex build` emits Apex classes you deploy the usual way. Errors that actually help Every diagnostic has a title, notes, a `help:` line, and spans on the relevant places. Readable by a human and structured enough for an LLM pair. ## A few quick comparisons [Section titled “A few quick comparisons”](#a-few-quick-comparisons) No more null pointers. Absence is `Option` and you have to handle both cases to get at the value: ```vertex type User { User(name: String) } fn greet(user: Option): String { user .map(fn(u: User): String { "Hello, ${u.name}!" }) .unwrapOr("Hello, stranger!") } debug greet(Some(User(name: "Alice"))) // Hello, Alice! debug greet(None) // Hello, stranger! ``` No more untyped `throw`. Fallible functions return `Result`, and `?` propagates the error when you want the happy path to read cleanly: ```vertex fn check_positive(n: Int): Result { if n < 0 { Error("negative: ${n}") } else { Ok(n) } } fn process(raw: Int): Result { let n = check_positive(raw)? Ok(if n < 18 { "minor" } else { "adult" }) } debug process(25) // Ok(value: adult) debug process(-1) // Error(error: negative: -1) ``` And no more waiting on a deploy every time you want to try something: ```plaintext $ vertex run hello.vtx Hello, Alice! ``` ## About [Section titled “About”](#about) Vertex is a statically typed language that compiles to Salesforce Apex. It borrows from Gleam (`Result`, `Option`, the pipe), Kotlin (named parameters, records), Rust (`?`, explicit mutability), F# (pipeline thinking), and TypeScript (type inference feel), over a syntactic baseline that should look familiar if you write Apex. It targets the Salesforce platform; it is not a general-purpose language. Existing Apex is meant to stay as Apex. Vertex is for new code and incremental adoption via a strong FFI. Head to [Getting Started](/getting-started/) to install and run your first program, or dig into the [Language Tour](/tour/01-hello-world/). If you prefer reading a real codebase, there is a sample library management app at [vertex-run/vertex-library-management](https://github.com/vertex-run/vertex-library-management). --- # Why Vertex? > What Vertex actually gives you over Apex, with examples. Apex is fine. It does the job, it runs on every Salesforce org, and if you have been writing it for a decade you know where the sharp edges are. The sharp edges are the problem. Vertex is an attempt to fix a specific handful of them while keeping the code deployable as Apex. What follows is the list of things you get from Vertex that you do not get from Apex, each with an example. ## The compiler catches more [Section titled “The compiler catches more”](#the-compiler-catches-more) Vertex is statically typed, `match` is checked for exhaustiveness, there is no `null`, and fallible things return `Result`. Put together, a lot of what usually surfaces as a production bug in Apex just fails to compile. ```vertex type Payment { Card(last4: String), Bank(routing: String, account: String), Cash, } fn describe(p: Payment): String { match p { Card(last4:) => "card ending ${last4}", Bank(routing:, account:) => "bank ${routing}/${account}", Cash => "cash", } } debug describe(Card(last4: "1234")) // card ending 1234 ``` Add a `Crypto` variant next year and every `match` that forgot to handle it stops compiling. You find out at `vertex build` time instead of in production. See [Sum Types](/reference/sum-types/), [Pattern Matching](/reference/pattern-matching/), [Result](/reference/result/), and [Option](/reference/option/). ## Features Apex has been missing [Section titled “Features Apex has been missing”](#features-apex-has-been-missing) Apex has been on the Salesforce platform since 2008. Vertex lets you write what you have probably been wishing the language had for a while: ```vertex let total = [1, 2, 3, 4, 5] .filter(fn(n: Int): Bool { n > 2 }) .map(fn(n: Int): Int { n * n }) .fold(0, fn(acc: Int, n: Int): Int { acc + n }) debug total // 50 ``` That is a list literal, the pipe-friendly method chain, higher-order functions, closures, generics inferred end to end, and immutable bindings. You will not find yourself typing `List` three times to do this. See [Functions](/reference/functions/) and [Collections](/reference/collections/). ## Less to remember [Section titled “Less to remember”](#less-to-remember) Vertex picks one way to do each thing and skips the rest: * No class inheritance. Composition with interfaces. * No static vs instance split. There are no static methods; module-level functions exist, and that is the whole story. * No `null`. `Option` handles absence and the compiler checks you handle it. * No exceptions in user code. `Result` handles failure. * Immutability by default. `mutable` is a keyword you type on the declarations that need to change. * File = module. No header, no `package`, no manifest. Fewer features is fine if what you remove were the features that caused the bugs. Most of this list is “remove something that bit us”. See [Modules & Imports](/reference/imports/) and [Bindings](/reference/bindings/). ## A local dev loop [Section titled “A local dev loop”](#a-local-dev-loop) Apex development lives on the platform. Every iteration wants a deploy; every test wants test classes on the org; the feedback loop runs in minutes. Vertex has a JIT. Write a file, run it, see what happens: ```plaintext $ cat hello.vtx debug "hello, vertex!" $ vertex run hello.vtx hello, vertex! ``` Tests work the same way, with a simulated DML layer so even data-access code runs without an org: ```plaintext $ vertex test src/billing/invoice_test.vtx ✓ invoice totals include tax ✓ zero-line invoice is rejected 2 passed, 0 failed (47ms) ``` When you are ready to deploy, `vertex build` emits Apex class files your existing Salesforce tooling picks up directly. See [The vertex CLI](/tooling/cli/) and [Testing](/reference/testing/). ## Errors that actually help [Section titled “Errors that actually help”](#errors-that-actually-help) Most compilers write messages for the compiler author. Vertex writes them for the reader, who is almost always stuck. Every diagnostic comes with a title in plain English, notes about what the compiler saw, an actionable `help:` line, and spans on both the mistake and the declaration that conflicts with it: ```plaintext error[E203]: cannot assign to immutable binding at hello.vtx:3:3 | 2 | let counter = 0 3 | counter = counter + 1 | ^^^^^^^ 'counter' cannot be reassigned notes: 'counter' was declared with 'let', which makes it immutable. help: Change 'let' to 'mutable let' to allow reassignment: mutable let counter = 0 related: 'counter' first declared here at hello.vtx:2:5 ``` The format is deliberate, and one of the reasons is that an LLM coding assistant can parse it cleanly: the error code identifies the category, the related span tells the model which other line to look at, and the `help:` section gives a literal replacement it can apply. See [Compiler Diagnostics](/reference/diagnostics/). ## What Vertex does not try to do [Section titled “What Vertex does not try to do”](#what-vertex-does-not-try-to-do) * It is not a general-purpose language. It targets the Salesforce platform and emits Apex. * It is not an Apex replacement. Existing Apex stays as Apex; Vertex is for new code and incremental adoption. * It is not a scripting language, even though `vertex run` feels like one. It is statically typed and compiled (to Apex) or JIT-executed locally. ## Where it borrows from [Section titled “Where it borrows from”](#where-it-borrows-from) | Language | What Vertex takes from it | | ---------- | -------------------------------------------------------------------------------- | | Gleam | `Result` / `Option`, pipe-friendly method chaining, immutability, no inheritance | | Kotlin | Named parameters, record-style types, exhaustive `match` | | TypeScript | Type-inference feel, string interpolation | | Rust | `?` error propagation, explicit mutability | | F# | Railway Oriented Programming, pipeline thinking | | Apex | Syntactic baseline, `@Annotation` syntax, interface names | If this sounds like something you want to try, head to [Getting Started](/getting-started/). If you want the full walkthrough, start with the [Language Tour](/tour/01-hello-world/). --- # Coming from Apex > A side-by-side cheatsheet for Apex developers learning Vertex. If you already know Apex, you already know most of what Vertex looks like: braces, named arguments, `@Annotations`, `public`, and so on. This page is a quick translation table for the constructs that differ. For the full walkthrough, start with the [Language Tour](/tour/01-hello-world/). ## Primitive types [Section titled “Primitive types”](#primitive-types) | Apex | Vertex | | --------- | --------- | | `Integer` | `Int` | | `Long` | `Long` | | `Double` | `Double` | | `Decimal` | `Decimal` | | `String` | `String` | | `Boolean` | `Bool` | | `Id` | `Id` | | `void` | `Void` | ## Bindings and constants [Section titled “Bindings and constants”](#bindings-and-constants) | Apex | Vertex | | ------------------------------------------ | ----------------------------------- | | `String name = 'Alice';` | `let name = "Alice"` | | `Integer x = 1; x = 2;` *(mutable)* | `mutable let x = 1; x = 2` | | `public static final String ENV = 'prod';` | `const ENV = "prod"` (module level) | | `// unused` variable warning | prefix with `_`: `let _tmp = ...` | Vertex bindings are immutable by default. Add `mutable` when you really need reassignment, so mutation is always visible in the source. ## Strings [Section titled “Strings”](#strings) | Apex | Vertex | | ------------------------ | ---------------------------------- | | `'Hello, ' + name + '!'` | `"Hello, ${name}!"` | | `String.valueOf(x)` | `"${x}"` *(interpolation coerces)* | | `s.toUpperCase()` | `s.toUpperCase()` | | `s.contains('foo')` | `s.contains("foo")` | Strings are always double-quoted. Single quotes are not string literals. ## Control flow [Section titled “Control flow”](#control-flow) Apex: ```apex String label; if (age < 18) label = 'minor'; else if (age < 65) label = 'adult'; else label = 'senior'; ``` Vertex (blocks are expressions, so `if` returns a value): ```vertex let label = if age < 18 { "minor" } else if age < 65 { "adult" } else { "senior" } ``` There is no ternary operator. An `if` expression is the ternary. ## Loops [Section titled “Loops”](#loops) Apex uses C-style `for`. Vertex uses `for..in`. ```apex for (Integer i : numbers) { System.debug(i); } ``` ```vertex for n in numbers { debug n } ``` There is no `while` in user code; idiomatic Vertex expresses iteration with `for..in`, recursion, or collection combinators (`map`, `filter`, `fold`). ## Collections [Section titled “Collections”](#collections) Apex: ```apex List names = new List{ 'Alice', 'Bob' }; Set ids = new Set{ 1, 2, 3 }; Map ages = new Map{ 'Alice' => 30, 'Bob' => 25 }; ``` Vertex: ```vertex let names: List = ["Alice", "Bob"] let ids: Set = #{1, 2, 3} let ages: Map = {"Alice": 30, "Bob": 25} ``` Vertex collections come with the combinators you expect (`map`, `filter`, `fold`, `any`, `all`), so a lot of explicit for-loops go away. See [Collections](/reference/collections/). ## Absence: no null [Section titled “Absence: no null”](#absence-no-null) Apex uses `null` for absence. Vertex has no `null`: use `Option`. ```apex String name = lookup(id); if (name != null) { System.debug('Found: ' + name); } else { System.debug('Missing'); } ``` ```vertex match lookup(id) { Some(name) => debug "Found: ${name}", None => debug "Missing", } ``` Shorthand: `.unwrapOr(default)`, `.map(...)`, `.flatMap(...)`. See [Option](/reference/option/). ## Failure: no exceptions [Section titled “Failure: no exceptions”](#failure-no-exceptions) Apex uses `throw` / `try` / `catch`. Vertex uses `Result`. ```apex public static Integer safeDivide(Integer a, Integer b) { if (b == 0) { throw new IllegalArgumentException('cannot divide by zero'); } return a / b; } ``` ```vertex fn safe_divide(a: Int, b: Int): Result { if b == 0 { Error("cannot divide by zero") } else { Ok(a / b) } } ``` Propagate with the `?` operator: ```vertex fn halve_then_double(a: Int, b: Int): Result { let q = safe_divide(a, b)? // returns early on Error Ok(q * 2) } ``` See [Result](/reference/result/). ## Functions vs classes and statics [Section titled “Functions vs classes and statics”](#functions-vs-classes-and-statics) Apex lives inside classes. Vertex does not have static methods: a `.vtx` file *is* a module, and its `pub fn` declarations are its API. ```apex // Apex public class MathUtils { public static Integer add(Integer a, Integer b) { return a + b; } } Integer sum = MathUtils.add(1, 2); ``` math\_utils.vtx ```vertex pub fn add(a: Int, b: Int): Int { a + b } ``` ```vertex // in another file import math_utils let sum = math_utils.add(1, 2) ``` See [Functions](/reference/functions/) and [Modules & Imports](/reference/imports/). ## Records and sum types [Section titled “Records and sum types”](#records-and-sum-types) Apex has classes (with fields, getters/setters, constructors) and enums. Vertex has `type`, which covers both. ```apex public class Account { public String name; public Decimal revenue; public Account(String name, Decimal revenue) { this.name = name; this.revenue = revenue; } } ``` ```vertex type Account { Account(name: String, revenue: Decimal) } let a = Account(name: "ACME", revenue: 1000000.0d) debug a.name ``` Multi-variant types (sum types) have no Apex equivalent; the closest thing is an `enum`, but sum types carry data per variant: ```vertex type Payment { Card(last4: String), Bank(routing: String, account: String), Cash, } ``` See [Sum Types](/reference/sum-types/) and [Pattern Matching](/reference/pattern-matching/). ## SObjects and DML [Section titled “SObjects and DML”](#sobjects-and-dml) ```apex Account a = new Account(Name = 'ACME'); insert a; ``` ```vertex @SObject type Account { Account(Name: String) } let acc = Account(Name: "ACME") let result = Database.insert(acc) // Result ``` Apex’s “throws on failure” becomes an explicit `Result` you must match on. `Database.insert`, `update`, `delete`, and `undelete` all return `Result`. See [Salesforce Integration](/reference/salesforce/integration/). ## `@AuraEnabled` [Section titled “@AuraEnabled”](#auraenabled) ```apex @AuraEnabled(cacheable=true) public static String getAccountName(Id id) { ... } ``` ```vertex @AuraEnabled(cacheable: true) fn getAccountName(id: String): Result { ... } ``` The function must return `Result`. `Ok` is returned to the LWC; `Error` is translated into an `AuraHandledException` at the Apex boundary. See [Annotations](/reference/annotations/). ## Tests [Section titled “Tests”](#tests) ```apex @IsTest private class InvoiceTest { @IsTest static void totals_include_tax() { System.assertEquals(110, Invoice.total(100, 10)); } } ``` invoice\_test.vtx ```vertex @Test fn totals_include_tax(): Void { assert Invoice.total(100, 10) == 110 } ``` Test files end in `_test.vtx`. Run them with `vertex test` (see [Testing](/reference/testing/)). ## Sharing [Section titled “Sharing”](#sharing) ```apex public without sharing class AdminJob { ... } ``` ```vertex @WithoutSharing pub fn run(): Void { ... } ``` The sharing annotation goes at the top of the file. Default is `@WithSharing`, which matches Apex security best practices. ## Annotations reference [Section titled “Annotations reference”](#annotations-reference) | Apex | Vertex | | ------------------------------ | ------------------------------- | | `@IsTest` *(method)* | `@Test` | | `@AuraEnabled` | `@AuraEnabled` | | `@AuraEnabled(cacheable=true)` | `@AuraEnabled(cacheable: true)` | | `with sharing` | `@WithSharing` | | `without sharing` | `@WithoutSharing` | | `inherited sharing` | `@InheritedSharing` | ## Calling existing Apex [Section titled “Calling existing Apex”](#calling-existing-apex) You do not have to rewrite anything. Vertex can call Apex through `extern` declarations: ```vertex extern type Messaging.SingleEmailMessage extern new Messaging.SingleEmailMessage(): Messaging.SingleEmailMessage extern method Messaging.SingleEmailMessage.setSubject( self: Messaging.SingleEmailMessage, subject: String, ): Void let msg = Messaging.SingleEmailMessage.new() msg.setSubject("Hello from Vertex") ``` See [Apex FFI](/reference/salesforce/apex-ffi/). ## Next steps [Section titled “Next steps”](#next-steps) * [Getting Started](/getting-started/): install the toolchain and run your first program. * [Language Tour](/tour/01-hello-world/): a guided walk through the language from the top. * [Guides](/guides/): task-oriented how-tos (error handling, testing, pattern matching). --- # FizzBuzz > A complete FizzBuzz example in Vertex using recursive functions and if expressions. A classic FizzBuzz implementation showing recursive functions and `if` as an expression. ```vertex fn fizzbuzz( n: Int, ): String { let fizz = n % 3 == 0 let buzz = n % 5 == 0 if fizz && buzz { "FizzBuzz" } else if fizz { "Fizz" } else if buzz { "Buzz" } else { "${n}" } } fn run( n: Int, limit: Int, ): Void { if n <= limit { debug fizzbuzz(n) run(n + 1, limit) } } run(1, 20) ``` Key concepts demonstrated: * `if` as an expression that produces a value * String interpolation with `${}` * Recursive functions * `Void`-returning functions that use `if` for control flow --- # Library Management (sample app) > A full multi-module Vertex Salesforce app you can read, clone, and deploy. The examples on this site are mostly focused snippets. If you want to see what a real multi-module Vertex codebase looks like, there is a dedicated sample repo: **[github.com/vertex-run/vertex-library-management](https://github.com/vertex-run/vertex-library-management)** The app models a virtual library: catalog browsing, member checkouts, return history, inventory limits. The Vertex source lives in [`src/`](https://github.com/vertex-run/vertex-library-management/tree/main/src), organized into `domain/` (pure business logic + tests), `controllers/` (`@AuraEnabled` entry points), and top-level modules for the `@SObject` types. It is a good thing to read after the [Language Tour](/tour/01-hello-world/) when you want to see how the pieces fit together at scale: domain types, DML through `Result`, tests that run locally without an org, and LWC-facing controllers. ## Running it yourself [Section titled “Running it yourself”](#running-it-yourself) Clone the repo and follow the README. The short version: ```plaintext $ git clone https://github.com/vertex-run/vertex-library-management.git $ cd vertex-library-management $ sf org create -f config/project-scratch-def.json -a vertex-lib $ vertex build $ sf project deploy start --target-org vertex-lib ``` For the day-to-day development loop (running tests locally, deploying when you are ready), see the [Building for Salesforce](/guides/salesforce-projects/) guide. --- # Result and Closures > A complete example combining Result error handling, the ? operator, and first-class functions. A complete example combining `Result` error handling, the `?` propagation operator, and first-class functions (closures and `make_adder`). ```vertex fn validate_age( age: Int, ): Result { if age < 0 { Error("age cannot be negative") } else if age > 150 { Error("age is unrealistically large") } else { Ok(age) } } fn label_age( age: Int, ): String { if age < 18 { "minor" } else { "adult" } } fn process_age( raw: Int, ): Result { let age = validate_age(raw)? Ok(label_age(age) + " (age " + age + ")") } fn make_adder( base: Int, ): fn(Int): Int { fn(x: Int): Int { base + x } } debug match process_age(25) { Ok(s) => s, Error(e) => "invalid: " + e, } // adult (age 25) let add10 = make_adder(10) debug add10(5) // 15 debug 5 |> add10 // 15 ``` Key concepts demonstrated: * `Result` for error handling without exceptions * `?` operator for early error propagation in a function returning `Result` * Closures capturing their enclosing scope (`base` in `make_adder`) * `|>` pipe operator as an alternative to function call syntax * `match` on a `Result` to handle both cases --- # Getting Started > Install Vertex, run your first program, and set up your editor. Welcome. This section gets you from zero to a running Vertex program in a few minutes. It covers installing the `vertex` binary, running your first file, scaffolding a Salesforce project, and wiring up your editor. If you just want to read code, head to the [Language Tour](/tour/01-hello-world/) instead. For a full sample application you can clone and deploy, see the [Library Management sample](/examples/library-management/). ## In this section [Section titled “In this section”](#in-this-section) Install Vertex Download the `vertex` binary for macOS, Linux, or Windows, and put it on your `PATH`. [Read more](/getting-started/install/) Your first program Write a `.vtx` file, run it with `vertex run`, and see output in milliseconds. No Salesforce org required. [Read more](/getting-started/first-program/) Your first Salesforce project Scaffold a multi-file project, build it to Apex classes, and deploy to your scratch org. [Read more](/getting-started/first-salesforce-project/) Editor setup Syntax highlighting, diagnostics, and completions via the Zed extension and the built-in LSP. [Read more](/getting-started/editor-setup/) --- # Editor setup > Syntax highlighting, diagnostics, and completions via the built-in LSP. Vertex ships with a language server (LSP). Any editor that speaks LSP can use it for real-time diagnostics, completions, hover, and go-to-definition. The fastest path today is the Zed extension, which wires everything up for you. ## Zed (recommended) [Section titled “Zed (recommended)”](#zed-recommended) Install the **Vertex** extension from Zed’s extensions pane. It provides: * Syntax highlighting via the Vertex tree-sitter grammar. * Real-time diagnostics as you type. * Completions for keywords, built-in types, stdlib methods, and identifiers in scope. * Hover and go-to-definition for declarations. The extension automatically downloads the correct `vertex` binary for your platform. If you already have `vertex` on your `PATH`, it uses that. Source: [vertex-run/zed-vertex](https://github.com/vertex-run/zed-vertex). ## Other editors [Section titled “Other editors”](#other-editors) Any editor that can run an LSP server can drive the Vertex LSP. The command to launch it is: ```plaintext vertex lsp ``` This reads and writes LSP protocol on stdin/stdout. Exact wiring depends on your editor’s LSP client; the things you will need to configure: * **Language ID**: `vertex` * **File pattern**: `*.vtx` * **Server command**: `vertex lsp` * **Initialization options**: none required For syntax highlighting outside Zed, the tree-sitter grammar lives at [vertex-run/tree-sitter-vertex](https://github.com/vertex-run/tree-sitter-vertex). ## Formatter [Section titled “Formatter”](#formatter) A formatter is not yet available. The language uses braces `{}`, no semicolons, and a 4-space indent as its baseline style; following the examples in the [tour](/tour/01-hello-world/) and [reference](/reference/) will keep you close to idiomatic. ## Next [Section titled “Next”](#next) Now that you are set up, start the [Language Tour](/tour/01-hello-world/). --- # Your first Vertex program > Write a .vtx file, run it locally, and see output in milliseconds. This page assumes you have already [installed Vertex](/getting-started/install/) and `vertex --version` prints a version. ## Hello, Vertex [Section titled “Hello, Vertex”](#hello-vertex) Create a file called `hello.vtx`: ```vertex debug "hello, vertex!" ``` Run it: ```plaintext $ vertex run hello.vtx hello, vertex! ``` That is it. No `main` function, no class wrapper, no `sfdx-project.json`, no deploy. Vertex executes top-level statements in order, top to bottom. ## `debug` is the output function [Section titled “debug is the output function”](#debug-is-the-output-function) `debug` is the only way to produce observable output. It is an **expression**: it evaluates its argument, prints it, and returns the value so you can inspect mid-pipeline. ```vertex debug "hello" debug 42 debug true let sum = debug 1 + 2 // prints 3, then binds sum = 3 ``` ## Bindings [Section titled “Bindings”](#bindings) ```vertex let name = "Alice" let age = 30 debug "Hello, ${name}! You are ${age}." ``` All bindings are immutable by default. If you need to reassign: ```vertex mutable let counter = 0 counter = counter + 1 counter = counter + 1 debug counter // 2 ``` The visible `mutable` keyword is deliberate. Reassignment is possible but it stands out. See [Bindings](/reference/bindings/). ## Doing something useful [Section titled “Doing something useful”](#doing-something-useful) Here is a complete program that pitches several of the ideas the tour will cover in more depth: ```vertex fn label(age: Int): String { if age < 18 { "minor" } else { "adult" } } fn describe(name: String, age: Int): String { "${name} is ${label(age)} (age ${age})" } debug describe("Alice", 30) debug describe("Bob", 15) ``` Run it: ```plaintext $ vertex run greet.vtx Alice is adult (age 30) Bob is minor (age 15) ``` ## What just happened [Section titled “What just happened”](#what-just-happened) * **No deploy.** Vertex ran your code locally via its built-in JIT. * **Compile-time guarantees.** The types of `name`, `age`, and the return of `label` were all checked before anything ran. * **Millisecond feedback.** No org, no build step, no waiting. ## Next [Section titled “Next”](#next) * [Your first Salesforce project](/getting-started/first-salesforce-project/): move from single files to a proper multi-file project that builds to Apex and deploys. * [Language Tour](/tour/01-hello-world/): a guided walk through every language feature. --- # Your first Salesforce project > Scaffold a Vertex project, build it to Apex, and deploy to a scratch org. This page walks you from a single-file Vertex script to a real project that builds to Apex class files and deploys to a Salesforce org. It assumes you have: * Vertex [installed](/getting-started/install/). * The [Salesforce CLI (`sf`)](https://developer.salesforce.com/tools/salesforcecli) installed and authenticated to a scratch org or sandbox. ## Project layout [Section titled “Project layout”](#project-layout) A Vertex project is any directory containing an `sfdx-project.json` file. The file tells the Salesforce tooling where your Apex source lives; Vertex uses the same file to locate your Vertex source. The conventional layout: ```plaintext my-project/ ├── sfdx-project.json ├── src/ # .vtx source goes here │ └── hello.vtx └── force-app/ └── main/ └── default/ └── classes/ # vertex build writes .cls files here ``` A minimal `sfdx-project.json`: ```json { "packageDirectories": [ { "path": "force-app", "default": true } ], "sourceApiVersion": "66.0" } ``` ## Write an entry point [Section titled “Write an entry point”](#write-an-entry-point) Vertex programs that run as real Apex classes expose a `pub fn run` entry point. Create `src/hello.vtx`: ```vertex pub fn run(): Void { debug "Hello from Vertex on Salesforce!" } ``` This compiles to a `public static void vtx_run()` method on a generated Apex class. ## Build [Section titled “Build”](#build) From the project root: ```plaintext $ vertex build Success! Built 1 class(es) to force-app/main/default/classes/ ``` Look inside `force-app/main/default/classes/` and you will see a `.cls` file and a `.cls-meta.xml` file ready for deployment. ## Deploy [Section titled “Deploy”](#deploy) ```plaintext $ sf project deploy start --target-org ``` Use the alias you set up with `sf org login`. Do not pass `--source-dir` or `--metadata`: the `sf` CLI tracks local changes internally. ## Run it [Section titled “Run it”](#run-it) Create `run-hello.apex`: ```apex Hello.vtx_run(); ``` Then: ```plaintext $ sf apex run --target-org --file run-hello.apex ``` You should see your debug line in the Apex log. ## The full development loop [Section titled “The full development loop”](#the-full-development-loop) For day-to-day iteration, you rarely need the org. The loop is: 1. Edit a `.vtx` file. 2. `vertex run ` to execute it locally (millisecond feedback). 3. `vertex test` to run your `*_test.vtx` files. 4. `vertex build` and `sf project deploy start` when you are ready to ship. See [The vertex CLI](/tooling/cli/) for the full command reference and [Testing](/reference/testing/) for the test runner. ## Next [Section titled “Next”](#next) * [Editor setup](/getting-started/editor-setup/): get syntax highlighting, diagnostics, and completions in your editor. * [Guides: Building for Salesforce](/guides/salesforce-projects/): a deeper look at SObjects, DML, and `@AuraEnabled`. --- # Install Vertex > Download the vertex binary and put it on your PATH. Vertex ships as a single binary per platform. Grab the one for your machine from the [latest release](https://github.com/vertex-run/vertex/releases/latest) and put it on your `PATH`. Vertex is very early Before you install: Vertex is in active early development. The language, stdlib, and tooling are still moving and **you should expect breaking changes between releases**. Pin to a specific release tag if you care, and please [file issues](https://github.com/vertex-run/vertex/issues/new/choose) when something breaks, confuses you, or just feels wrong. Early feedback is how this gets less rough. ## Download and install [Section titled “Download and install”](#download-and-install) * macOS (Apple Silicon) ```bash curl -Lo vertex https://github.com/vertex-run/vertex/releases/latest/download/vertex-macos-arm64 chmod +x vertex sudo mv vertex /usr/local/bin/ ``` * macOS (Intel) ```bash curl -Lo vertex https://github.com/vertex-run/vertex/releases/latest/download/vertex-macos-x86_64 chmod +x vertex sudo mv vertex /usr/local/bin/ ``` * Linux (x86\_64) ```bash curl -Lo vertex https://github.com/vertex-run/vertex/releases/latest/download/vertex-linux-x86_64 chmod +x vertex sudo mv vertex /usr/local/bin/ ``` * Windows (x86\_64) Download `vertex-windows-x86_64.exe` from the [releases page](https://github.com/vertex-run/vertex/releases/latest), rename it to `vertex.exe`, and place it in a folder on your `PATH`. ## Verify [Section titled “Verify”](#verify) ```plaintext vertex --version ``` You should see a version string. If you see `command not found`, the binary is not on your `PATH`; double-check the install location. ## What you get [Section titled “What you get”](#what-you-get) The `vertex` binary is everything you need to: * **Run** `.vtx` files locally: `vertex run hello.vtx` * **Test** your code: `vertex test` * **Build** Apex classes for deployment: `vertex build` * **Serve** the language server for your editor: `vertex lsp` See [The vertex CLI](/tooling/cli/) for the full command reference. ## Optional: Salesforce tooling [Section titled “Optional: Salesforce tooling”](#optional-salesforce-tooling) To deploy the Apex classes that `vertex build` emits, you will also want the [Salesforce CLI (`sf`)](https://developer.salesforce.com/tools/salesforcecli). It is not required for writing or running Vertex locally; only for pushing the generated code to an org. ## Next [Section titled “Next”](#next) Run your first program: [Your first Vertex program](/getting-started/first-program/). --- # Guides > Task-oriented how-to guides for common Vertex workflows. The [Language Tour](/tour/01-hello-world/) and [Reference](/reference/) answer *what is in Vertex?*. This section answers *how do I do X?*. Each guide is self-contained and assumes you are comfortable with everything up to tour Chapter 8 (Option and Result). ## In this section [Section titled “In this section”](#in-this-section) Error handling with Result How to design fallible APIs, when to use `?`, and how to build up error types that compose. [Read more](/guides/error-handling/) Pattern matching in practice Real patterns beyond the basics: guards, nested destructuring, list patterns, and when to reach for `let assert`. [Read more](/guides/pattern-matching/) Testing your code Writing `@Test` functions, structuring test files, and using the simulated DML layer to test Salesforce code without an org. [Read more](/guides/testing/) Interop with existing Apex Calling Apex classes, wrapping the parts of `System.*` you need, and handling values of type `Apex.Object`. [Read more](/guides/apex-interop/) Building for Salesforce Putting it all together: `@SObject`, DML, `@AuraEnabled`, and sharing modifiers in a single project. [Read more](/guides/salesforce-projects/) --- # Interop with existing Apex > Calling Apex classes from Vertex, wrapping System APIs, and handling Apex.Object. Vertex is designed for incremental adoption. You should be able to drop a Vertex file into an existing Apex codebase without rewriting anything. This guide walks through the three tools that make that possible: `extern` declarations, `@SObject` types, and the `Apex.Object` escape hatch. For the full reference, see [Apex FFI](/reference/salesforce/apex-ffi/) and [Apex.Object](/reference/salesforce/apex-object/). ## Calling an Apex class [Section titled “Calling an Apex class”](#calling-an-apex-class) You declare the Apex surface you want to use with `extern`. A minimal example: send a single email via `Messaging.SingleEmailMessage`. ```vertex extern type Messaging.SingleEmailMessage extern new Messaging.SingleEmailMessage(): Messaging.SingleEmailMessage extern method Messaging.SingleEmailMessage.setSubject( self: Messaging.SingleEmailMessage, subject: String, ): Void extern method Messaging.SingleEmailMessage.setPlainTextBody( self: Messaging.SingleEmailMessage, body: String, ): Void ``` Then use it like any other type: ```vertex let msg = Messaging.SingleEmailMessage.new() msg.setSubject("Hello") msg.setPlainTextBody("from Vertex") ``` ## Only declare what you use [Section titled “Only declare what you use”](#only-declare-what-you-use) The temptation is to wrap the entire Apex class at once. Don’t. You only need declarations for the methods your Vertex code actually calls. The generated Apex references `Messaging.SingleEmailMessage` directly, so unused methods on the Apex side are still available to your Apex callers. This keeps the Vertex codebase tidy and the surface area small. ## Group `extern` declarations per Apex class [Section titled “Group extern declarations per Apex class”](#group-extern-declarations-per-apex-class) A common pattern is one Vertex module per wrapped Apex class: ```plaintext src/ ├── apex/ │ ├── messaging.vtx # extern wrappers for Messaging.* │ ├── http.vtx # extern wrappers for Http / HttpRequest / HttpResponse │ └── schema.vtx # extern wrappers for Schema.describe* └── billing/ └── invoice.vtx # business logic imports from apex/* ``` Your Vertex business logic stays decoupled from the Apex types; the FFI details live in the wrapper modules. ## Static methods on Apex classes [Section titled “Static methods on Apex classes”](#static-methods-on-apex-classes) Some Apex APIs are static (`Database.query`, `Http.send`, etc.). Use `extern fn` in a module whose name matches the Apex type: apex/json.vtx ```vertex extern fn JSON.serialize(o: Apex.Object): String extern fn JSON.deserializeUntyped(s: String): Apex.Object ``` ## Dealing with `Apex.Object` [Section titled “Dealing with Apex.Object”](#dealing-with-apexobject) Some Apex APIs take or return the universal `Object` type (the root of Apex’s type hierarchy). `JSON.deserializeUntyped` is the canonical example: you hand it a JSON string and it gives back an untyped `Map`, `List`, or scalar. Vertex has a dedicated type `Apex.Object` for this. You convert from a typed Vertex value into `Apex.Object` with `from`, and back out with `as`: ```vertex let raw = JSON.serialize(Apex.Object.from(inv)) let parsed = JSON.deserializeUntyped(raw) let recovered: Result = parsed.as() ``` See [Apex.Object](/reference/salesforce/apex-object/) for the full conversion semantics, including how it handles nulls and type mismatches. ## What does not work in JIT mode [Section titled “What does not work in JIT mode”](#what-does-not-work-in-jit-mode) Any call that goes through an `extern` declaration is **not executed** when you run your code with `vertex run` (JIT). The JIT has no Salesforce runtime to call. In practice this means: * Business logic that only uses Vertex features: testable locally. * Code that calls Apex (DML aside): must be tested on the org. DML calls are a special case: `Database.insert`, `update`, `delete`, and `undelete` are simulated in JIT mode so that most business logic remains testable without an org. ## Two-way interop [Section titled “Two-way interop”](#two-way-interop) Vertex-generated Apex classes can be called from existing Apex code. `pub fn` declarations become `public static` methods with a `vtx_` prefix on the generated class. greet.vtx ```vertex pub fn say_hello(name: String): String { "Hello, ${name}!" } ``` From Apex: ```apex String greeting = Greet.vtx_say_hello('Alice'); ``` This is how a team adopts Vertex incrementally: new code is Vertex; existing Apex callers reach in through the generated API. ## Checklist [Section titled “Checklist”](#checklist) * Declare only the Apex methods you actually call. * Keep `extern` declarations in dedicated wrapper modules. * Use `Apex.Object` when (and only when) the Apex API itself takes or returns `Object`. * Remember that `extern` calls do not run in JIT mode; plan your testing strategy accordingly. --- # Error handling with Result > Designing fallible APIs in Vertex, using the ? operator, and building error types that compose. Vertex has no exceptions in user code. Every fallible operation returns `Result`. This guide is about the design patterns that turn that raw primitive into APIs that are pleasant to use. If you have not met `Result` yet, start with [tour Chapter 8](/tour/08-option-and-result/) and [the Result reference](/reference/result/). ## The core idea [Section titled “The core idea”](#the-core-idea) The compiler’s rule is simple: if your function might fail, it returns `Result`. The type signature is the API contract. Every caller has to handle both the success and the failure case, usually with `match` or the `?` operator. ```vertex fn validate_age(age: Int): Result { if age < 0 { Error("age cannot be negative") } else if age > 150 { Error("age is unrealistically large") } else { Ok(age) } } ``` ## Use `?` when you are forwarding the error [Section titled “Use ? when you are forwarding the error”](#use--when-you-are-forwarding-the-error) The `?` operator is how you say *“propagate this error unchanged if it occurred; otherwise give me the success value.”* It is how you keep the happy path readable. ```vertex fn greet(age: Int): Result { let a = validate_age(age)? Ok(if a < 18 { "hi, kid" } else { "hello" }) } ``` If `validate_age` returns `Error(e)`, `greet` returns `Error(e)` at that point and the rest of the function body does not run. If it returns `Ok(a)`, `a` is bound to the inner value and execution continues. ## Designing error types [Section titled “Designing error types”](#designing-error-types) For small programs, `Result` is fine. For anything that survives a code review, introduce a real error type. A good error type is a sum type with one variant per *kind* of failure: ```vertex type InvoiceError { NegativeAmount(line: Int), ZeroLineItems, InvalidCustomerId(id: String), } fn total_invoice(inv: Invoice): Result { ... } ``` Pattern matching on the call site becomes type-safe and exhaustive: ```vertex match total_invoice(inv) { Ok(total) => debug "${total}", Error(NegativeAmount(line:)) => debug "line ${line} was negative", Error(ZeroLineItems) => debug "no items on invoice", Error(InvalidCustomerId(id:)) => debug "bad customer: ${id}", } ``` ## Crossing error-type boundaries [Section titled “Crossing error-type boundaries”](#crossing-error-type-boundaries) When you call a helper that returns `Result<_, HelperError>` from a function that returns `Result<_, CallerError>`, you need `mapError`: ```vertex fn pricing_job(age: Int): Result { let a = validate_age(age).mapError(fn(msg: String): JobError { InvalidInput(msg) })? // rest of the computation uses `a` Ok(0.0d) } ``` The pattern is: *`helper_call().mapError(wrap_into_my_error_type)?`*. `mapError` converts the error variant only; the success value flows through unchanged. ## Do not reach for exceptions through FFI [Section titled “Do not reach for exceptions through FFI”](#do-not-reach-for-exceptions-through-ffi) Calling Apex can throw. If you are bridging an Apex library, catch the exception at the boundary and convert it to a `Result`: ```vertex fn safe_call(): Result { // inside a well-defined extern wrapper match Database.insert(acc) { Ok(id) => Ok(acc.withId(id)), Error(dmlErr) => Error(dmlErr), } } ``` Do not surface exceptions up through your Vertex code. The point of `Result` is that the caller knows, from the signature, that the operation can fail. Thrown-across-FFI exceptions defeat that. ## `let assert` is a last resort [Section titled “let assert is a last resort”](#let-assert-is-a-last-resort) `let assert` is Vertex’s way of saying *“I know this cannot fail; if I’m wrong, crash.”* It is useful in tests and one-off scripts; it is rarely correct in production code. ```vertex // OK in a test: fixture setup you know is valid let assert Ok(acc) = Database.insert(Account(Name: "Test")) // Not OK in production: you have just traded a Result for a crash ``` When you find yourself wanting `let assert` in real code, consider returning `Result` from the enclosing function instead. ## Checklist [Section titled “Checklist”](#checklist) When reviewing code that uses `Result`: * Does every fallible function say so in its return type? * Are error variants specific enough that a caller can decide what to do, not just log and give up? * Is the `?` operator used where the intent is “forward the error unchanged”? * Is `mapError` used when crossing error-type boundaries? * Is `let assert` reserved for tests and known-valid invariants? If all five answers are yes, the code is in good shape. --- # Pattern matching in practice > Real patterns beyond the basics, including guards, nested destructuring, and list patterns. `match` is Vertex’s main decision-making construct. Once you are comfortable with the basics (Chapter 6 of the tour), you will start reaching for it instead of `if` chains in almost every case. This guide collects the patterns that are not obvious from the reference. ## Matching is the default, `if` is the exception [Section titled “Matching is the default, if is the exception”](#matching-is-the-default-if-is-the-exception) A rough heuristic: if the decision involves *values*, use `if`; if it involves *shapes*, use `match`. ```vertex // Values: a simple inequality if total > 1000 { apply_discount() } else { full_price() } // Shapes: a sum-type variant match payment { Card(last4:) if fraud_check_failed(last4) => Error(Fraud), Card(..) => Ok(Authorized), Bank(..) => Ok(PendingClearance), Cash => Ok(Authorized), } ``` An `if` chain that dispatches on the variant of a sum type is almost always a `match` waiting to happen. ## Guards let you combine values and shapes [Section titled “Guards let you combine values and shapes”](#guards-let-you-combine-values-and-shapes) Guards are the `if` inside an arm. They let one arm match “a Card variant whose `last4` looks suspicious” without carving it out as a separate type. ```vertex match transaction { Sale(amount:) if amount > limit => Error(OverLimit), Sale(amount:) => Ok(amount), Refund(amount:) => Ok(-amount), } ``` Order matters: the first arm that matches wins. Put the guarded arm *before* the unguarded arm for the same variant. ## Destructure records at the call site [Section titled “Destructure records at the call site”](#destructure-records-at-the-call-site) Single-variant record types destructure cleanly: ```vertex type Invoice { Invoice(id: Id, customer: Id, total: Decimal, tax: Decimal) } fn describe(inv: Invoice): String { let Invoice(customer:, total:, tax:, ..) = inv "customer ${customer} owes ${total} plus ${tax} tax" } ``` The `..` silently ignores the unmentioned fields (here, `id`). If you want different names, use the long form `field: binding`: ```vertex match inv { Invoice(total: t, tax:, ..) => debug "${t + tax}", } ``` ## Nested patterns [Section titled “Nested patterns”](#nested-patterns) Patterns nest. Match a `Result` of a sum type variant in one step: ```vertex match charge_card(amount) { Ok(Authorized) => receipt(), Ok(PendingClearance) => "wait for it", Error(CardDecline(Insufficient)) => "try a different card", Error(CardDecline(Expired)) => "card expired", Error(NetworkFailure) => "we'll retry in a bit", } ``` Each layer is a separate pattern. This tends to replace two or three levels of nested `match` in other languages. ## List patterns [Section titled “List patterns”](#list-patterns) Lists match by position, with `..` collecting the tail. ```vertex match events { [] => "nothing happened", [e] => "one event: ${e.name}", [first, ...rest] => "first: ${first.name}, then ${rest.size()} more", } ``` ## OR patterns for shared arms [Section titled “OR patterns for shared arms”](#or-patterns-for-shared-arms) When two variants take the same action, group them with `|`: ```vertex match state { Pending | InReview => debug "waiting", Approved => debug "green light", Rejected => debug "blocked", } ``` OR patterns cannot bind different shapes (you cannot `Card(x) | Bank(x)`) because the bound names would have different meanings. ## When exhaustiveness bites [Section titled “When exhaustiveness bites”](#when-exhaustiveness-bites) The compiler checks `match` arms for exhaustiveness. If you add a new variant to a sum type, every `match` that forgot to handle it fails to compile. That is the point. When you genuinely do not care about the other variants, use a wildcard: ```vertex match status { Approved => ship(), _ => hold(), } ``` The trap: a careless wildcard swallows the variant you added last month. Use `_` only when “everything else” really is one action. ## `let assert` for invariants [Section titled “let assert for invariants”](#let-assert-for-invariants) `let assert` checks at runtime that a pattern matches and binds the inner values, crashing if it doesn’t. It is the right tool for assertions that are *not* user input: ```vertex let assert Ok(config) = load_config() // dev-time invariant let assert [first, ..] = required_list // provable elsewhere ``` For real user input, return `Result` and match properly. ## Reading order for the reference [Section titled “Reading order for the reference”](#reading-order-for-the-reference) If you want the full rulebook: * [Pattern Matching reference](/reference/pattern-matching/): the authoritative list of pattern forms. * [Sum Types reference](/reference/sum-types/): how variants are declared and what you can destructure. * [Control Flow reference](/reference/control-flow/): how `match` fits alongside `if` and `for..in`. --- # Building for Salesforce > A worked example using SObjects, DML, @AuraEnabled, and sharing modifiers in one project. This guide walks through building a small but realistic Salesforce feature in Vertex: a function that creates a new Account, tags it with a classification, and exposes the operation to a Lightning component. Along the way it pulls together every Salesforce-specific construct Vertex offers. Before you start, make sure your project is set up per [Your first Salesforce project](/getting-started/first-salesforce-project/). ## Define the SObject [Section titled “Define the SObject”](#define-the-sobject) src/accounts/types.vtx ```vertex @SObject pub type Account { Account( Name: String, Industry: Option, Rating: Option, ) } ``` Three things are happening here: 1. `@SObject` tells the Apex codegen this type is an Account record. Every `@SObject` type also gets an implicit `Id: Option` field. 2. `Industry` and `Rating` are `Option` so they can be omitted at construction time: constructor fields of an `Option` type default to `None`. 3. `pub` exports the type from the module so other Vertex files can import it. ## Core business logic [Section titled “Core business logic”](#core-business-logic) src/accounts/classify.vtx ```vertex import accounts.types type ClassifyError { InsertFailed(reason: String) MissingName } pub fn create_classified_account( name: String, industry: String, ): Result { if name.trim() == "" { Error(MissingName) } else { let acc = types.Account( Name: name, Industry: Some(industry), Rating: Some(pick_rating(industry)), ) match Database.insert(acc) { Ok(id) => Ok(id), Error(dmlErr) => Error(InsertFailed(dmlErr.message)), } } } fn pick_rating(industry: String): String { match industry { "Technology" | "Healthcare" => "Hot", "Retail" => "Warm", _ => "Cold", } } ``` Notes on this file: * **Error types are explicit.** Callers know at compile time what can go wrong. * **`Database.insert` returns `Result`.** We translate the generic `DmlError` into our own domain error. * **`pick_rating` is private.** No `pub` means it is module-local. ## Expose it to Lightning [Section titled “Expose it to Lightning”](#expose-it-to-lightning) src/accounts/aura.vtx ```vertex import accounts.classify @AuraEnabled pub type ClassifyResult { ClassifyResult(accountId: String, success: Bool) } @AuraEnabled pub fn classify_for_lwc( name: String, industry: String, ): Result { match classify.create_classified_account(name: name, industry: industry) { Ok(id) => Ok(ClassifyResult(accountId: id.toString(), success: true)), Error(classify.MissingName) => Error(AuraError("Name is required")), Error(classify.InsertFailed(msg)) => Error(AuraError("Could not save: ${msg}")), } } ``` Notes: * **`@AuraEnabled` on the type** makes every field serializable to LWC. * **`@AuraEnabled` on the function** exposes it, and the return type must be `Result<_, AuraError>`. `Ok` becomes the value passed to LWC; `Error` becomes an `AuraHandledException` with your message. * **The boundary is narrow.** The rich `ClassifyError` lives inside Vertex; the LWC sees only a single-line message. ## Sharing and security [Section titled “Sharing and security”](#sharing-and-security) By default, every Vertex module compiles to `public with sharing`, the safe Salesforce default. To opt into a different sharing mode, add an annotation at the top of the file: src/accounts/admin\_job.vtx ```vertex @WithoutSharing import accounts.classify // ... ``` You almost never want this. Use it only when you have a specific reason (a scheduled job that needs to see all records) and audit the code accordingly. ## Building and deploying [Section titled “Building and deploying”](#building-and-deploying) From the project root: ```plaintext $ vertex build Success! Built 3 class(es) to force-app/main/default/classes/ ``` ```plaintext $ sf project deploy start --target-org ``` `sf` tracks local changes on its own; do not pass `--source-dir` or `--metadata`. ## Testing before deploying [Section titled “Testing before deploying”](#testing-before-deploying) src/accounts/classify\_test.vtx ```vertex import accounts.classify import accounts.types @Test fn rejects_blank_name(): Void { let result = classify.create_classified_account(name: " ", industry: "Retail") let assert Error(classify.MissingName) = result } @Test fn returns_id_on_success(): Void { let result = classify.create_classified_account(name: "Acme", industry: "Retail") let assert Ok(_) = result } ``` Run: ```plaintext $ vertex test ✓ rejects blank name ✓ returns id on success 2 passed, 0 failed (8ms) ``` The second test exercises `Database.insert` without a real org. In JIT mode, the DML layer is simulated and `Database.insert` returns a stub `Id`. The test verifies *your code* does the right thing; org-side behaviour is verified with an actual deploy. ## Calling from LWC [Section titled “Calling from LWC”](#calling-from-lwc) In your Lightning component: ```javascript import classifyForLwc from '@salesforce/apex/Aura.vtx_classify_for_lwc'; classifyForLwc({ name: 'Acme', industry: 'Retail' }) .then(result => { /* { accountId, success } */ }) .catch(error => { /* AuraHandledException message */ }); ``` The Apex method name is `vtx_` + the Vertex function name. ## What to remember [Section titled “What to remember”](#what-to-remember) 1. **Keep domain logic in pure Vertex modules.** `@SObject` types and `Database.*` calls are fine, but keep `@AuraEnabled` boundaries thin. 2. **Error types communicate the API contract.** Don’t collapse them to strings until you are exiting the Vertex world. 3. **Test before you deploy.** The simulated DML layer covers most of your code; a quick deploy covers the rest. 4. **Default to `with sharing`.** Only change it with a reason. ## See it in a real project [Section titled “See it in a real project”](#see-it-in-a-real-project) [vertex-run/vertex-library-management](https://github.com/vertex-run/vertex-library-management) is a sample app that applies the patterns in this guide end to end: `@SObject` types, `Database.*` through `Result`, `@AuraEnabled` controllers, and a `domain/` module with tests that run on the JIT. Browse the source in [`src/`](https://github.com/vertex-run/vertex-library-management/tree/main/src). --- # Testing your code > Writing @Test functions, structuring test files, and testing Salesforce code without an org. Vertex tests run locally. You do not need a Salesforce org to iterate on tests, not even for DML-heavy code. This guide covers the day-to-day workflow. For the definitive rules, see [the Testing reference](/reference/testing/). ## Your first test [Section titled “Your first test”](#your-first-test) Create a file whose name ends in `_test.vtx`: invoice\_test.vtx ```vertex import billing.invoice @Test fn totals_include_tax(): Void { let inv = invoice.new_line_item(100) assert invoice.total_with_tax(inv: inv, tax_rate: 10) == 110 } ``` Run it: ```plaintext $ vertex test src/billing/invoice_test.vtx ✓ totals include tax 1 passed, 0 failed (12ms) ``` Or discover and run everything: ```plaintext $ vertex test ``` ## Structure tests around behaviour, not methods [Section titled “Structure tests around behaviour, not methods”](#structure-tests-around-behaviour-not-methods) A test name reads best as a full sentence. The compiler does not care; your future self does. ```vertex // Good @Test fn totals_include_tax(): Void { ... } @Test fn zero_line_invoice_is_rejected(): Void { ... } // Less good @Test fn test_calculate_total_1(): Void { ... } ``` Group tests by the behaviour they describe, not by the function they happen to call. ## Assertions [Section titled “Assertions”](#assertions) Use `assert` for individual checks. The failure message includes the expression and the runtime value: ```vertex @Test fn adds_two_numbers(): Void { assert 1 + 1 == 2 assert "hello".length() == 5 } ``` For checks you want to phrase as a one-liner with a richer message, fall back to pattern assertions: ```vertex @Test fn user_is_found(): Void { let assert Some(user) = find_user(id: "42") assert user.name == "Alice" } ``` ## DML-heavy tests without an org [Section titled “DML-heavy tests without an org”](#dml-heavy-tests-without-an-org) In JIT mode, DML operations are simulated: * `Database.insert(sobj)` returns `Ok(stub_id)` and prints a notice to stderr. * `Database.update`, `delete`, and `undelete` also return `Ok(...)`. Your logic runs the same way it would on an org, but without the latency and without touching real data. ```vertex @Test fn creates_and_updates_in_sequence(): Void { let acc = Account(Name: "Test Co") let assert Ok(id) = Database.insert(acc) let updated = Account(Name: "Renamed", Id: Some(id)) let assert Ok(_) = Database.update(updated) } ``` The assertion tells you *your code* does the right sequence of calls; it does not tell you the org accepted them. When that matters, run the same test on the org via [the build+deploy flow](/guides/salesforce-projects/). ## Filtering during development [Section titled “Filtering during development”](#filtering-during-development) ```plaintext $ vertex test --filter invoice ``` Runs only tests whose names contain `invoice`. Useful while you are iterating on one bug. ## Test modules are separate from production modules [Section titled “Test modules are separate from production modules”](#test-modules-are-separate-from-production-modules) The compiler enforces it: non-test modules may not import test modules. This keeps your test helpers out of the Apex classes that `vertex build` emits. Production imports test: compile error. ## What is not yet supported [Section titled “What is not yet supported”](#what-is-not-yet-supported) * **Mocking Apex FFI calls.** `extern`-declared functions do not run in JIT mode at all. Tests that need an Apex API must either avoid it in the unit under test or run on the org. * **Test fixtures.** There is no before/after hook yet; each `@Test` function is self-contained. Factor out a helper function when the setup is shared. * **Parameterised tests.** There is no built-in table-driven test support. A `for n in cases { assert check(n) }` loop is an acceptable substitute. See the [Testing reference](/reference/testing/) for the exact rules enforced by the checker. --- # Annotations > Annotations in Vertex, @Test for testing, AuraEnabled, SObject, sharing modifiers. ## `@Test` on functions [Section titled “@Test on functions”](#test-on-functions) Marks a zero-parameter function as a test case. The `vertex test` command discovers and runs all `@Test` functions in `*_test.vtx` files. ```vertex @Test fn add_two_numbers(): Void { assert 1 + 1 == 2 } ``` Rules enforced by the checker: * The function must have **no parameters** (error E136). * `@Test` may only appear in files whose name ends in `_test.vtx` (error E137). * Non-test modules may not import test modules (error E138). `@Test` functions are exempt from the unused-private-function warning (W005) because the test runner calls them externally. See the [Testing reference](/reference/testing/) for the full testing guide. ## Module-level sharing annotations [Section titled “Module-level sharing annotations”](#module-level-sharing-annotations) Three annotations control the Apex sharing modifier on the generated outer class. They are placed at the very top of a `.vtx` file, before any `import` or declaration: ```vertex @WithoutSharing import lang.db pub fn run(): Void { db.insertAccount(...) } ``` | Annotation | Generated Apex class header | | ------------------- | ---------------------------------------------------- | | *(none)* | `public with sharing class Module_Name { ... }` | | `@WithSharing` | `public with sharing class Module_Name { ... }` | | `@WithoutSharing` | `public without sharing class Module_Name { ... }` | | `@InheritedSharing` | `public inherited sharing class Module_Name { ... }` | **Default is `with sharing`**. This is the safe default per Apex security best practices and aligns with PMD’s `ApexSharingViolations` rule. Rules: * At most one sharing annotation per module. A second one is an error (`E141`). * Sharing annotations take no arguments. Arguments are an error (`E142`). * Unknown module-level annotation names are an error (`E140`). * Sharing annotations are a no-op in JIT mode and in anonymous-Apex paths. * Inner classes inherit sharing from the outer class. ## `@AuraEnabled` on functions [Section titled “@AuraEnabled on functions”](#auraenabled-on-functions) Marks a function as callable from Lightning components. The function must return `Result`. The codegen unwraps the `Result` at the Apex boundary: `Ok(value)` returns `value` directly; `Error(AuraError(msg))` throws `new AuraHandledException(msg)`. ```vertex @AuraEnabled fn getContacts( ): Result, AuraError> { ... } @AuraEnabled(cacheable: true) fn getAccountName( id: String, ): Result { ... } ``` ## `@AuraEnabled` on types [Section titled “@AuraEnabled on types”](#auraenabled-on-types) Marks a single-variant record type as an LWC data contract. The codegen emits `@AuraEnabled` on every field and drops `final` from fields (required for LWC serialization/deserialization). The type must be a single-variant record (variant name matches the type name). Applying `@AuraEnabled` with arguments or to a multi-variant type is an error. ```vertex @AuraEnabled pub type BookDto { BookDto(name: String, authorName: String, price: Decimal) } @AuraEnabled pub fn getBook(id: String): Result { ... } ``` Generates: ```apex public class BookDto { @AuraEnabled public String name; @AuraEnabled public String authorName; @AuraEnabled public Decimal price; public BookDto(String name, String authorName, Decimal price) { this.name = name; this.authorName = authorName; this.price = price; } } ``` --- # Assertions > Runtime invariant checks with assert. `assert` is a **statement** that checks a Boolean condition at runtime. If the condition is `true`, execution continues silently. If it is `false`, the program panics immediately with a descriptive failure message. ```vertex let x = 42 assert x > 0 // passes. Silent assert x == 42 // passes. Silent ``` ## Syntax [Section titled “Syntax”](#syntax) ```plaintext assert ``` The expression must be of type `Bool`. The checker rejects any other type at compile time (error `E100`). ```vertex assert true // ok assert 1 == 1 // ok assert "hello" // E100: type mismatch. Expected Bool, found String ``` ## Failure messages [Section titled “Failure messages”](#failure-messages) When an assertion fails, Vertex prints a rich message that includes: * The source location (file, line, column). * The rendered source of the condition. * For comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`): the actual values of the left-hand and right-hand sides. **Comparison failure example:** ```plaintext assertion failed at examples/jit/lang/assertions.vtx:7:1 source: x == 42 left: 5 right: 42 ``` **Non-comparison failure example:** ```plaintext assertion failed at examples/jit/lang/assertions.vtx:11:1 source: list.contains(target) ``` ## `assert` vs `let assert` [Section titled “assert vs let assert”](#assert-vs-let-assert) These are two distinct constructs that share the `assert` keyword: | | `assert` | `let assert` | | --------------- | ---------------------- | --------------------------------------------- | | Purpose | Check a Bool invariant | Destructure a value that must match a pattern | | Form | `assert expr` | `let assert Pat = expr` | | Failure message | `assertion failed: …` | `let assert pattern did not match: …` | Both cause a panic on failure and are appropriate during development and in tests. ## Apex codegen [Section titled “Apex codegen”](#apex-codegen) `assert expr` compiles to `System.Assert.isTrue(expr, 'assertion failed at …')`. The message is a compile-time string. No runtime decomposition occurs on the Apex side. Assertions are **never stripped** from emitted Apex; there is no release-build mode that removes them. --- # Bindings and constants > let, mutable let, and const in Vertex, plus scoping and shadowing rules. Vertex has three binding forms: `let` (immutable local), `mutable let` (mutable local), and `const` (module-level compile-time constant). ## `let`: immutable local bindings [Section titled “let: immutable local bindings”](#let-immutable-local-bindings) ```vertex let name = "Alice" // immutable; cannot be reassigned let count: Int = 42 // optional explicit type annotation ``` Reassigning an immutable binding is a compile-time error (`E101`). Type annotations are optional when the type can be inferred from the initializer. ### Scoping [Section titled “Scoping”](#scoping) `let` bindings are lexically scoped. They are visible from the point of declaration to the end of the enclosing block. ```vertex fn compute(x: Int): Int { let doubled = x * 2 // visible for the rest of compute if doubled > 100 { let big = true // visible only inside this if-block debug big } // 'big' is not visible here doubled } ``` ### Shadowing [Section titled “Shadowing”](#shadowing) Declaring a new `let` with a name already in scope shadows the outer binding for the remainder of the block. Shadowing is allowed, common, and often preferred to mutation. ```vertex let x = "42" let x = Int.parse(x) // a new binding; outer x is shadowed ``` ### Unused-binding warnings [Section titled “Unused-binding warnings”](#unused-binding-warnings) If you declare a `let` you do not use, the compiler emits `W002`. To silence the warning on a binding you genuinely do not need, prefix the name with `_`: ```vertex let _unused = compute() // no warning ``` ## `mutable let`: mutable local bindings [Section titled “mutable let: mutable local bindings”](#mutable-let-mutable-local-bindings) ```vertex mutable let total = 0 total = total + 1 total = total + 1 debug total // 2 ``` Reassignment is allowed only on `mutable let`. The `mutable` keyword is part of the declaration, not a modifier on later assignments; every reassignment without that keyword on the original declaration is a compile error. `mutable` applies to the binding, not to the value: you cannot “mutate” an immutable list via a mutable binding. Collections are themselves immutable; mutation happens by binding a new value. ## `const`: module-level constants [Section titled “const: module-level constants”](#const-module-level-constants) Module-level constants are declared with `const`. Their right-hand side is evaluated at compile time, so only compile-time-constant expressions are allowed: scalar literals, arithmetic on constants, collection literals of constants, and references to other constants in the same module. ```vertex const max_retries = 3 const api_version: String = "v2" pub const discount_rate = 10 // visible to importers ``` ### Scalar literals [Section titled “Scalar literals”](#scalar-literals) Any of `Int`, `Long`, `Double`, `Decimal`, `Bool`, or `String` may appear on the right-hand side directly. ```vertex const enabled = true const pi: Double = 3.14 const greeting = "Hello" ``` String interpolation is not permitted in constant strings. ### Arithmetic and references [Section titled “Arithmetic and references”](#arithmetic-and-references) Binary `+`, `-`, `*`, `/`, `%` are allowed on numeric constants. `+` on two `String` operands performs concatenation. Unary minus is allowed. A constant may reference any other `const` in the same module, even one declared later. Cycles are an error. ```vertex const doubled = base * 2 // forward reference: fine const base = 10 ``` ### Collection literals [Section titled “Collection literals”](#collection-literals) Lists, maps, and sets of constants can be `const` values. ```vertex const statuses = ["Draft", "Active", "Closed"] const codes = {200: "OK", 404: "Not Found"} const flags = #{true, false} ``` Empty collections need an explicit type annotation so the element type is known. ```vertex const empty: List = [] ``` ### Visibility [Section titled “Visibility”](#visibility) * **Private** (`const ...`): accessible within the same module only. * **Public** (`pub const ...`): exported in the module’s signature and accessible via `module.name`. Selective import of a `pub const` by name is not yet supported. Use a bare `import foo` and qualify with `foo.constant`. ### Codegen [Section titled “Codegen”](#codegen) * **Class-file Apex.** Emits as `[public|private] final static Type name = value;`. * **Anonymous Apex.** Emits as `final Type name = value;` at the top of the block, before any body statements. The emitted value is always the fully-evaluated compile-time literal, never a reference or expression. ## Picking the right binding [Section titled “Picking the right binding”](#picking-the-right-binding) A rule of thumb: * If the value does not change: `let`. * If the value changes during the function: `mutable let`, only after ruling out a rewrite that avoids mutation. * If the value is a module-level configuration shared across functions: `const`. --- # Collections > List, Map, and Set collection types in Vertex. ## `List` [Section titled “List\”](#listt) ```vertex let names: List = ["Alice", "Bob", "Carol"] let empty: List = [] ``` An empty list `[]` requires a type annotation whenever the element type cannot be determined from context: ```vertex let empty: List = [] // OK. Annotation provides T = String let empty = [] // error: cannot infer element type ``` When an empty list is passed directly to a function whose parameter type is known, the annotation can be omitted: ```vertex fn greet(names: List): Void { ... } greet([]) // OK, T inferred from the parameter type ``` Spread syntax combines lists: ```vertex let combined = [...list1, ...list2, "extra"] ``` `...expr` (three dots) is the spread form and only appears in list *expressions*. The two-dot `..` is a pattern-only form for silently discarding the rest of a list or the unmentioned fields of a variant. See [Pattern Matching](/reference/pattern-matching/). `..` is never valid inside a list literal. Lists can be iterated with `for..in` and pattern-matched with list patterns. There is no subscript syntax (`list[i]`). Use `get(i)` for index access. ### Instance methods [Section titled “Instance methods”](#instance-methods) | Method | Signature | Returns | Notes | | ------------------------------------- | --------------- | ----------------- | --------------------------------------------------------------------- | | `size()` | `List` | `Int` | Number of elements | | `isEmpty()` | `List` | `Bool` | `true` when size is 0 | | `get(i: Int)` | `List` | `Option` | `None` on out-of-bounds; no panic | | `add(item: T)` | `List` | `List` | Pure. Returns a new list with `item` appended | | `first()` | `List` | `Option` | `None` on empty list | | `last()` | `List` | `Option` | `None` on empty list | | `contains(item: T)` | `List` | `Bool` | Value equality for primitives; reference equality for class instances | | `map(f: fn(T): U)` | `List` | `List` | Pure. Returns a new list | | `filter(f: fn(T): Bool)` | `List` | `List` | Pure. Returns a new list | | `fold(init: U, f: fn(U, T): U)` | `List` | `U` | Accumulator-first callback convention | | `any(f: fn(T): Bool)` | `List` | `Bool` | Short-circuits on first match; `false` on empty | | `all(f: fn(T): Bool)` | `List` | `Bool` | Short-circuits on first non-match; `true` on empty (vacuous truth) | | `enumerate()` | `List` | `List<#(Int, T)>` | Pairs each element with its zero-based index | | `reverse()` | `List` | `List` | Pure. Returns a new list in reversed order | | `prepend(item: T)` | `List` | `List` | Pure. Returns a new list with `item` at the front | | `concat(other: List)` | `List` | `List` | Pure. Returns a new list with `other` appended | | `take(n: Int)` | `List` | `List` | Pure. Returns the first `n` elements; `n ≤ 0` returns `[]` | | `drop(n: Int)` | `List` | `List` | Pure. Skips the first `n` elements; `n ≤ 0` returns a copy | | `unique()` | `List` | `List` | Pure. Removes duplicates, preserving first-occurrence order | | `toSet()` | `List` | `Set` | Converts to a `Set`, removing duplicates | | `flatten()` | `List>` | `List` | Concatenates all inner lists into a single list | | `join(sep: String)` | `List` | `String` | Joins all strings with `sep` | | `filterMapSome(f: fn(T): Option)` | `List` | `List` | Applies `f`, keeps only `Some` values, discards `None` | | `filterMapOk(f: fn(T): Result)` | `List` | `List` | Applies `f`, keeps only `Ok` values, discards errors | | `indexOf(item: T)` | `List` | `Option` | Returns `Some(index)` of first match, or `None` | | `firstWhere(f: fn(T): Bool)` | `List` | `Option` | Returns `Some(first element)` where `f` is true, or `None` | ```vertex let xs: List = [1, 2, 3, 4, 5] let doubled = xs.map(fn(n) { n * 2 }) // (2, 4, 6, 8, 10) let evens = xs.filter(fn(n) { n % 2 == 0 }) // (2, 4) let sum = xs.fold(0, fn(acc, n) { acc + n }) // 15 let hasOdd = xs.any(fn(n) { n % 2 != 0 }) // true let allPos = xs.all(fn(n) { n > 0 }) // true // Safe index access let assert Some(value: third) = xs.get(2) // third == 3 let missing = xs.get(99) // None // enumerate: iterate with index let words: List = ["a", "b", "c"] for #(i, w) in words.enumerate() { debug i // 0, 1, 2 } ``` ### Factory functions [Section titled “Factory functions”](#factory-functions) | Signature | Returns | Notes | | ------------------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `List.generate(count: Int, f: fn(Int): T)` | `List` | Calls `f` with each zero-based index `0..count-1`. `count ≤ 0` returns `[]`. | | `List.filled(count: Int, value: T)` | `List` | Creates `count` copies of `value`. `count ≤ 0` returns `[]`. Note: all entries reference the same object. Use `List.generate` for independent instances. | ```vertex // Unique items using the index: let squares: List = List.generate(5, fn(i: Int): Int { i * i }) // squares == (0, 1, 4, 9, 16) // Repeated sentinel value: let zeros: List = List.filled(4, 0) // zeros == (0, 0, 0, 0) ``` *** ## `Map` [Section titled “Map\”](#mapk-v) ```vertex let scores: Map = {"Alice": 95, "Bob": 87} let empty: Map = Map.empty() ``` There is no subscript syntax (`map["key"]`). Use `get(key)` for key access, which returns `Option`. ### Instance methods [Section titled “Instance methods”](#instance-methods-1) | Method | Receiver | Returns | Notes | | ----------------------- | ---------- | --------------- | ---------------------------------------- | | `size()` | `Map` | `Int` | Number of key-value pairs | | `isEmpty()` | `Map` | `Bool` | `true` when the map has zero entries | | `containsKey(key: K)` | `Map` | `Bool` | `true` if the key exists | | `get(key: K)` | `Map` | `Option` | `Some(value)` if found, `None` otherwise | | `put(key: K, value: V)` | `Map` | `Map` | Pure. Returns a new map with the key set | | `remove(key: K)` | `Map` | `Map` | Pure. Returns a new map without the key | | `keys()` | `Map` | `List` | All keys as a list | | `values()` | `Map` | `List` | All values as a list | | `entries()` | `Map` | `List<#(K, V)>` | Key-value pairs as a list of tuples | ### Factory functions [Section titled “Factory functions”](#factory-functions-1) `Map.empty()`. Creates an empty map. A type annotation is required: ```vertex let m: Map = Map.empty() ``` `Map.fromList(list: List<#(K, V)>): Map`. Constructs a map from a list of key-value tuple pairs. If the list contains duplicate keys, the **last** entry wins. ```vertex let m: Map = Map.fromList([#("a", 1), #("b", 2)]) // Round-trip with entries(): let original: Map = {"x": 10, "y": 20} let copy: Map = Map.fromList(original.entries()) ``` ### Iteration [Section titled “Iteration”](#iteration) ```vertex for k in scores.keys() { debug k } for #(key, value) in scores.entries() { debug "${key}: ${value}" } ``` *** ## `Set` [Section titled “Set\”](#sett) ```vertex let tags: Set = #{"apex", "salesforce"} ``` Sets are immutable; all mutating methods return a new `Set`. ### Instance methods [Section titled “Instance methods”](#instance-methods-2) | Method | Receiver | Returns | Description | | ----------------------------- | -------- | --------- | ------------------------------------------- | | `size()` | `Set` | `Int` | Number of elements | | `isEmpty()` | `Set` | `Bool` | `true` if the set has no elements | | `contains(item: T)` | `Set` | `Bool` | `true` if `item` is in the set | | `add(item: T)` | `Set` | `Set` | New set with `item` included | | `remove(item: T)` | `Set` | `Set` | New set with `item` excluded | | `toList()` | `Set` | `List` | Converts to a list (order unspecified) | | `map(f: fn(T): U)` | `Set` | `Set` | Transforms each element with `f` | | `filter(f: fn(T): Bool)` | `Set` | `Set` | Keeps elements for which `f` returns `true` | | `union(other: Set)` | `Set` | `Set` | All elements from both sets | | `intersection(other: Set)` | `Set` | `Set` | Elements present in both sets | | `difference(other: Set)` | `Set` | `Set` | Elements in receiver but not in `other` | ```vertex let nums: Set = #{1, 2, 3} debug nums.size() // 3 debug nums.contains(2) // true let s2 = nums.add(4) debug s2.size() // 4 let evens = nums.filter(fn(x: Int): Bool { x % 2 == 0 }) debug evens.size() // 1 let u = #{1, 2}.union(#{2, 3, 4}) debug u.size() // 4 let inter = #{1, 2, 3}.intersection(#{2, 3, 4}) debug inter.size() // 2 let diff = #{1, 2, 3}.difference(#{2, 3}) debug diff.size() // 1 ``` --- # Control Flow > if/else, if let, block expressions, and return in Vertex. ### `if` / `else`: an expression, produces a value [Section titled “if / else: an expression, produces a value”](#if--else-an-expression-produces-a-value) ```vertex let grade = if score >= 90 { "A" } else if score >= 80 { "B" } else { "F" } ``` An `if` without `else` is only valid where a `Void` value is acceptable. ### `if let`: conditional variant matching [Section titled “if let: conditional variant matching”](#if-let-conditional-variant-matching) `if let` checks whether a value matches a pattern and, if so, binds the pattern’s variables in the then-block. It is the idiomatic way to handle a single variant without writing an exhaustive `match`. ```vertex // Basic: execute only when the Option is Some if let Some(user) = findUser(42) { debug user } // With else let name = if let Some(user) = findUser(42) { user.name } else { "anonymous" } // Works for any sum type, not just Option/Result type ApiResponse { Success(data: String), Loading, Failed(error: String) } if let Success(data:) = response { process(data) } ``` ```vertex // Chaining if let Circle(radius: r) = shape { debug r } else if let Square(side: s) = shape { debug s } else { debug "other" } ``` **Scoping:** Pattern bindings are scoped to the **then-block only**. They are not visible in the `else` branch or after the `if let` expression. **When to use each construct:** | Construct | Intent | On mismatch | | ------------ | ------------------------------------ | ----------------- | | `match` | Handle **all** variants | N/A (exhaustive) | | `if let` | Conditionally handle **one** variant | Branches or skips | | `let assert` | Assert a specific variant | **Panics** | **Valid patterns:** `VariantPat`, `TuplePat`, `IdentPat`, `WildcardPat`. `LiteralPat`, `ListPat`, and `OrPat` are not valid (use `match` for those). ### Block expressions [Section titled “Block expressions”](#block-expressions) A block’s value is its trailing expression. Without a trailing expression it evaluates to `Void`. ```vertex let result = { let x = 5 x * x } ``` ### `return`: early exit from a function [Section titled “return: early exit from a function”](#return-early-exit-from-a-function) ```vertex fn divide( a: Int, b: Int, ): Result { if b == 0 { return Error("division by zero") } Ok(a / b) } ``` --- # Compiler diagnostics > Every error and warning code the Vertex compiler emits, with an example and a fix. Vertex takes diagnostics seriously. Every error has a title in plain English, notes about what the compiler knows, an actionable `help:` section, and where relevant a second span pointing at the conflicting declaration. This page is the index: each entry links the error code to an example and a one-line fix. For the format philosophy, see [Why Vertex: errors that teach](/why-vertex/#5-errors-that-teach). ## Errors [Section titled “Errors”](#errors) ### `E100`: Type mismatch [Section titled “E100: Type mismatch”](#e100-type-mismatch) The expression’s inferred type does not match what the context expects. ```vertex let n: Int = "hello" ``` **To fix:** convert the value or change the declaration. If you meant the declaration to match the value, drop the annotation and let the compiler infer. ### `E101`: Assignment to an immutable binding [Section titled “E101: Assignment to an immutable binding”](#e101-assignment-to-an-immutable-binding) You tried to reassign a `let`. ```vertex let x = 0 x = 1 ``` **To fix:** change `let` to `mutable let`, or rebind with a new name: `let y = 1`. ### `E102`: Undefined name [Section titled “E102: Undefined name”](#e102-undefined-name) The name is not in scope at the point of use. ```vertex debug grand_total ``` **To fix:** declare the binding or check the spelling. If the name lives in another module, add an `import`. ### `E103`: Wrong number of arguments [Section titled “E103: Wrong number of arguments”](#e103-wrong-number-of-arguments) The call passes too many or too few arguments. **To fix:** match the function’s parameter list, including any required named parameters. ### `E104`: Pattern not valid in this position [Section titled “E104: Pattern not valid in this position”](#e104-pattern-not-valid-in-this-position) A pattern form is not allowed in the context (for example, an OR pattern that binds different shapes). **To fix:** split the pattern into separate arms, or use a wildcard and match on fields inside the arm body. ### `E105`: Non-exhaustive `match` [Section titled “E105: Non-exhaustive match”](#e105-non-exhaustive-match) One or more variants of the matched type are not covered. ```vertex type Status { Active, Inactive, Archived } fn label(s: Status): String { match s { Active => "active", Inactive => "inactive", } } ``` **To fix:** add an arm for the missing variant, or a `_` wildcard if you genuinely mean “anything else”. The compiler message lists the missing variants by name. ### `E106`: `?` applied to a non-`Result` [Section titled “E106: ? applied to a non-Result”](#e106--applied-to-a-non-result) `?` only works on `Result` expressions. **To fix:** return a `Result` from the subexpression, or match on it directly if it is an `Option`. ### `E107`: Error types don’t align in `?` propagation [Section titled “E107: Error types don’t align in ? propagation”](#e107-error-types-dont-align-in--propagation) The enclosing function’s error type does not match the inner expression’s error type. **To fix:** convert with `.mapError(...)` before `?`. See the [error handling guide](/guides/error-handling/#crossing-error-type-boundaries). ### `E108`: Class does not implement all interface methods [Section titled “E108: Class does not implement all interface methods”](#e108-class-does-not-implement-all-interface-methods) A type declared to implement an interface is missing one or more methods. **To fix:** implement the missing methods listed by the compiler. ### `E109`: Name declared twice [Section titled “E109: Name declared twice”](#e109-name-declared-twice) A name was declared twice in the same scope. **To fix:** rename one of them, or delete the duplicate. ### `E110`: `...expr` spread on a non-`List` [Section titled “E110: ...expr spread on a non-List”](#e110-expr-spread-on-a-non-list) The spread operator `...` inside a list literal was applied to a value that is not a `List`. **To fix:** convert to a list first, or use a regular element expression. ### `E111`: Method does not exist on this type [Section titled “E111: Method does not exist on this type”](#e111-method-does-not-exist-on-this-type) The method name is not defined for the receiver’s type. **To fix:** check the spelling, or consult the reference for the available methods on the type. ### `E136`: `@Test` function must take no parameters [Section titled “E136: @Test function must take no parameters”](#e136-test-function-must-take-no-parameters) A function marked `@Test` has one or more parameters. **To fix:** remove the parameters, or move the data into the test body. ### `E137`: `@Test` only in `_test.vtx` files [Section titled “E137: @Test only in \_test.vtx files”](#e137-test-only-in-_testvtx-files) A function marked `@Test` is in a non-test file. **To fix:** rename the file to end in `_test.vtx`, or remove the annotation. ### `E138`: Non-test module imports a test module [Section titled “E138: Non-test module imports a test module”](#e138-non-test-module-imports-a-test-module) A non-test module cannot depend on a test module. **To fix:** move the imported symbol to a non-test module, or remove the import. ### `E140`: Unknown module-level annotation [Section titled “E140: Unknown module-level annotation”](#e140-unknown-module-level-annotation) The file starts with an annotation that the compiler does not recognise as a module-level annotation. **To fix:** check the spelling against [Annotations](/reference/annotations/), or move the annotation to the appropriate declaration. ### `E141`: Multiple sharing annotations [Section titled “E141: Multiple sharing annotations”](#e141-multiple-sharing-annotations) A module has more than one sharing annotation. **To fix:** keep at most one of `@WithSharing`, `@WithoutSharing`, or `@InheritedSharing`. ### `E142`: Sharing annotation takes arguments [Section titled “E142: Sharing annotation takes arguments”](#e142-sharing-annotation-takes-arguments) A sharing annotation was called with parentheses or arguments. **To fix:** remove the arguments. Sharing annotations are bare. ### `E203`: Reassignment of `let` [Section titled “E203: Reassignment of let”](#e203-reassignment-of-let) Deeper form of E101 with the original declaration’s location as a related span. **To fix:** same as E101. Use `mutable let` on the original declaration. ## Warnings [Section titled “Warnings”](#warnings) ### `W001`: `debug` in source [Section titled “W001: debug in source”](#w001-debug-in-source) A `debug` expression was left in source code. **To fix:** remove it before shipping, or accept the warning if this is a `vertex run` script. ### `W002`: Unused local binding [Section titled “W002: Unused local binding”](#w002-unused-local-binding) A local `let` was declared but its value is never read. **To fix:** remove the binding, or prefix it with `_` if the side effect of the RHS is what you wanted (`let _ = compute()`). ### `W003`: Unused parameter [Section titled “W003: Unused parameter”](#w003-unused-parameter) A function or closure parameter is never used. **To fix:** remove the parameter, or prefix the name with `_`. ### `W004`: `mutable let` never reassigned [Section titled “W004: mutable let never reassigned”](#w004-mutable-let-never-reassigned) A binding was declared `mutable let` but never reassigned. **To fix:** drop the `mutable` keyword. ### `W005`: Private function never called [Section titled “W005: Private function never called”](#w005-private-function-never-called) A private (non-`pub`) top-level function is never referenced. **To fix:** remove it, mark it `pub` if it is part of the public API, or prefix the name with `_`. ### `W006`: Private type or alias never used [Section titled “W006: Private type or alias never used”](#w006-private-type-or-alias-never-used) A private type declaration is never referenced. **To fix:** remove it, or mark it `pub`. ### `W007`: Private constant never used [Section titled “W007: Private constant never used”](#w007-private-constant-never-used) A private `const` is never referenced. **To fix:** remove it, or mark it `pub const`. ### `W008`: Variant never constructed [Section titled “W008: Variant never constructed”](#w008-variant-never-constructed) A sum-type variant of an internally-constructed type is never created in source. **To fix:** remove the variant, or use it. If a future call site is planned, leave a comment; the warning stays until the variant is constructed. ### `W009`: Imported name never used [Section titled “W009: Imported name never used”](#w009-imported-name-never-used) A name imported selectively is never used. **To fix:** remove the import, or use the name. ## Silencing unused-declaration warnings [Section titled “Silencing unused-declaration warnings”](#silencing-unused-declaration-warnings) Prefix a name with `_` to opt out of W002 through W007. This is the convention for intentionally-unused bindings. ```vertex fn process(_ignored: String): Int { 42 } // W003 suppressed let _temp = expensive_computation() // W002 suppressed fn _helper(): Void { } // W005 suppressed ``` The underscore convention does not apply to selective imports (W009): the imported name is defined elsewhere. Remove unused imports instead. --- # for..in Loop > Iterating over a list with for..in in Vertex. `for..in` is Vertex’s iteration statement. It walks a `List`, binding each element to a pattern. ```vertex for n in [1, 2, 3] { debug n } ``` There is no `while` statement. Iteration is expressed with `for..in`, recursion, or collection combinators (`map`, `filter`, `fold`). ## Over a list [Section titled “Over a list”](#over-a-list) ```vertex let names = ["Alice", "Bob", "Carol"] for name in names { debug "hello, ${name}" } ``` The loop variable is immutable inside the body. To accumulate a result across iterations, declare a `mutable let` outside the loop, or prefer `fold`: ```vertex // Imperative style mutable let total = 0 for n in [1, 2, 3] { total = total + n } debug total // 6 ``` ```vertex // Functional style, no mutation let total = [1, 2, 3].fold(0, fn(acc: Int, n: Int): Int { acc + n }) debug total // 6 ``` ## Destructuring in the loop head [Section titled “Destructuring in the loop head”](#destructuring-in-the-loop-head) The loop head accepts any pattern, so you can destructure tuples, records, or variants without an intermediate binding: ```vertex // Tuple destructuring for #(index, value) in pairs { debug "${index}: ${value}" } // Variant destructuring (matches only shapes of this variant) for Circle(radius: r) in shapes { debug "radius ${r}" } // Wildcard for _ in items { counter = counter + 1 } ``` Caution A variant pattern in the loop head only binds successfully when every element of the list matches that variant. Mixing variants means skipping elements that do not match; use `match` inside the loop if you need to dispatch per-element: ```vertex for shape in shapes { match shape { Circle(radius: r) => ..., Rectangle(width: w, height: h) => ..., } } ``` ## Iterating with an index [Section titled “Iterating with an index”](#iterating-with-an-index) `List.enumerate()` pairs each element with its zero-based index, returning a `List<#(Int, T)>` you can destructure: ```vertex for #(i, name) in names.enumerate() { debug "${i}. ${name}" } ``` ## Why no `for..in` over Map or Set? [Section titled “Why no for..in over Map or Set?”](#why-no-forin-over-map-or-set) Iteration order over a `Map` or `Set` is not guaranteed. Convert the collection to a list first to make the order explicit: ```vertex for #(k, v) in entries.toList() { ... } // Map.toList(): List<#(K, V)> for item in set.toList() { ... } // Set.toList(): List ``` This keeps the iteration order visible in the code rather than depending on hash-order surprises. ## Codegen [Section titled “Codegen”](#codegen) `for..in` emits as a Java-style `for (T name : collection) { ... }` in the generated Apex, with pattern destructuring expanded into the loop body. --- # Functions > Module-level functions, closures, higher-order functions, and the pipe operator in Vertex. ### Module-level declarations [Section titled “Module-level declarations”](#module-level-declarations) ```vertex fn add( a: Int, b: Int, ): Int { a + b } ``` * The last expression in the body is the return value. * Explicit `return` is for early exits only. * All module-level `fn` declarations are hoisted. Mutual recursion works. * Trailing commas are allowed in parameter lists and argument lists. ### Return type inference [Section titled “Return type inference”](#return-type-inference) The return type annotation is **optional** on private functions. When omitted, the compiler infers it from the body: ```vertex fn add(a: Int, b: Int) { a + b } // inferred: Int fn greet(name: String) { "Hello" } // inferred: String fn noop() { } // inferred: Void ``` **`pub fn` must always have an explicit return type.** This keeps module boundaries explicit and self-documenting: ```vertex pub fn run(): Void { ... } // OK. Explicit pub fn bad() { ... } // Error E135: pub fn missing return type ``` If two private functions with omitted return types reference each other (mutual recursion with both unannotated), the compiler emits **E136** and requires at least one of them to carry an explicit annotation. ### Named arguments at the call site [Section titled “Named arguments at the call site”](#named-arguments-at-the-call-site) ```vertex let r = add(a: 1, b: 2) // named let r = add(1, 2) // positional ``` ### Default parameter values [Section titled “Default parameter values”](#default-parameter-values) ```vertex fn greet( name: String, prefix: String = "Hello", ): String { "${prefix}, ${name}!" } ``` ### Parameter destructuring [Section titled “Parameter destructuring”](#parameter-destructuring) A parameter may be a pattern instead of a plain name. The pattern is bound at the start of the function body, exactly like a `let` destructuring. **Variant / record destructuring:** ```vertex type Point { Point(x: Double, y: Double) } fn sumSquares(Point(x:, y:): Point): Double { x * x + y * y } ``` **Tuple destructuring:** ```vertex fn addPair(#(a, b): #(Int, Int)): Int { a + b } ``` Mixed plain and destructured parameters are allowed: ```vertex fn scale(n: Double, Point(x:, y:): Point): Double { let sum = x + y n * sum } ``` **Restrictions:** * Destructured parameters are **positional only**. They have no call-site label and cannot be passed by name. * Default values (`= expr`) are not allowed on destructured parameters. * Closures do not support parameter destructuring; use a `let` binding inside the body instead. ### Closures / anonymous functions [Section titled “Closures / anonymous functions”](#closures--anonymous-functions) ```vertex let add5 = fn( x: Int, ): Int { x + 5 } fn make_adder( base: Int, ): fn(Int): Int { fn( x: Int, ): Int { base + x } } ``` Closures are first-class values. They capture their enclosing lexical environment. ### Higher-order functions [Section titled “Higher-order functions”](#higher-order-functions) ```vertex fn apply( f: fn(Int): Int, v: Int, ): Int { f(v) } fn compose( f: fn(Int): Int, g: fn(Int): Int, ): fn(Int): Int { fn( x: Int, ): Int { f(g(x)) } } ``` ### Function type aliases [Section titled “Function type aliases”](#function-type-aliases) Use `type` to give a name to a function type: ```vertex type IntOp = fn(Int): Int type Predicate = fn(Int): Bool fn apply_op(f: IntOp, x: Int): Int { f(x) } fn apply_pred(p: Predicate, x: Int): Bool { p(x) } ``` Generic function type aliases are supported: ```vertex type Transform = fn(A): B fn apply(f: Transform, x: Int): String { f(x) } ``` Aliases are **transparent**: `fn(Int): Int` and `IntOp` are interchangeable. Function type aliases are compile-time only. They have no representation in generated Apex. ### `|>` pipe operator [Section titled “|> pipe operator”](#-pipe-operator) Passes the left value as the **first argument** of the right-hand function: ```vertex debug 3 |> double // 6 debug 3 |> double |> inc // 7 debug 4 |> square |> double // 32 ``` Use `_` as a placeholder to control where the piped value lands: ```vertex fn add(a: Int, b: Int): Int { a + b } fn mul(a: Int, b: Int): Int { a * b } debug 2 |> add(_, 3) // 5 . Same as add(2, 3) debug 2 |> add(_, 3) |> mul(10, _) // 50. Mul(10, add(2, 3)) fn greet(greeting: String, name: String): String { greeting + ", " + name + "!" } debug "world" |> greet("Hello", _) // Hello, world! ``` Without `_`, the piped value is prepended as the first argument. With `_`, the piped value replaces each `_` in the argument list and no implicit prepend occurs. ### Generic functions [Section titled “Generic functions”](#generic-functions) ```vertex fn identity( x: T, ): T { x } fn first( items: List, ): Option { ... } ``` --- # Imports > Vertex's file-based module system and import forms. Vertex has a file-based module system: each `.vtx` file is a module. Imports are resolved by both `vertex build` (for compiling to Apex classes) and `vertex run` (for local JIT execution and `--sf` anonymous Apex runs). ## Import forms [Section titled “Import forms”](#import-forms) **Bare import**. Access the module’s public symbols via a qualifier: ```vertex import accounts import billing.invoices // nested path: qualifier is last segment let acc = accounts.findById("001") let inv = invoices.create() // qualifier is the last segment ``` **Aliased import**. Choose your own qualifier: ```vertex import billing.invoices as inv let i = inv.create() ``` **Selective import**. Bring specific symbols directly into scope: ```vertex import accounts.{ findById, Account } let acc = findById("001") // bare call, no qualifier needed ``` ## Module path → Apex class name [Section titled “Module path → Apex class name”](#module-path--apex-class-name) The module path maps to an Apex class name: | Module path | Apex class name | | ------------------ | ------------------ | | `accounts` | `Accounts` | | `billing/invoices` | `Billing_Invoices` | | `lang/greeter` | `Lang_Greeter` | The generated outer class always includes an Apex sharing modifier (see [Annotations](/reference/annotations/)). The default is `with sharing`: ```apex public with sharing class Accounts { ... } public with sharing class Billing_Invoices { ... } ``` Cross-module function calls emit as `TargetClass.fnName(args)`. Selectively imported types emit as `TargetClass.TypeName` in Apex. ## `vertex run` and imports [Section titled “vertex run and imports”](#vertex-run-and-imports) `vertex run` supports imports directly. No build step required for local scripts. **No imports → no project needed.** A file with no `import` declarations runs anywhere, just like `dart run` on a standalone Dart file. **With imports → project discovery.** When the entry file contains imports, `vertex run` walks upward from the entry file’s directory looking for `sfdx-project.json`. The directory that contains that file becomes the project root, and `/src/` is the module tree. Only the transitively reachable modules are loaded; unrelated files in `src/` are never touched. ```plaintext my-project/ sfdx-project.json src/ greeter.vtx ← loaded (imported by the script) unrelated.vtx ← skipped scripts/ hello.vtx ← entry file ``` scripts/hello.vtx ```vertex import greeter.{ greet } debug greet("World") ``` ```plaintext vertex run scripts/hello.vtx ``` **`--sf` with imports.** When using `--sf` with an entry script that has imports, the workspace must already be built and deployed before running: ```plaintext vertex build sf project deploy start --target-org vertex run --sf --org scripts/hello.vtx ``` The generated anonymous Apex references the deployed Apex classes directly (e.g. `Greeter.greet("World")`). If a referenced class is not deployed, the org returns a runtime “unknown class” error. **The entry file is a script, not a module.** It may contain top-level statements and a trailing expression. It is excluded from the module graph even if it lives under `src/`. Other modules cannot import the entry file. ## Visibility [Section titled “Visibility”](#visibility) Only `pub` declarations are importable. Calling a non-`pub` symbol from another module is a compile error (`E124`). ## Error codes [Section titled “Error codes”](#error-codes) | Code | Meaning | | ------ | ---------------------------------------------------------------- | | `E122` | Module not found (no matching `.vtx` file) | | `E123` | Symbol not found in module’s public API | | `E124` | Attempted access to a private declaration | | `E125` | Cyclic import detected | | `E126` | Name conflict: same symbol selectively imported from two modules | | `E127` | Variant constructor called on opaque type outside its module | | `E128` | Field accessed on opaque type outside its module | | `E129` | Variant pattern matched on opaque type outside its module | | `E130` | Record update used on opaque type outside its module | --- # Current limitations > Constructs that are partially implemented or not yet available. Caution This page lists constructs that are **parsed but not yet executable**, or **not yet implemented at all**. It reflects the current state of the language. Anything not listed here is expected to work. | Limitation | Why | Workaround | | -------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | Record update syntax `...record` | Parser accepts it; runtime throws. | Construct a new record explicitly with the fields you want to change. | | `extern` calls in JIT mode | JIT has no Salesforce runtime. | Run these code paths with `vertex run --sf --org ` or from a deployed Apex test. | | SOQL in Vertex source | SOQL is not yet part of the language. | Wrap the specific queries you need with `extern fn` around `Database.query` / `queryWithBindings`. | | `while` and `do`/`while` statements | Intentional: iteration is `for..in`, recursion, or combinators. | Use `for..in`, `List.fold`, or recursion. | | Mutating methods on collections | All collection operations are pure. | Rebind: `let xs = xs.add(item)`. | | `null` in user code | Intentional: absence is `Option`. | Use `Option` with `Some` / `None`. | | `throw` / `try` / `catch` in user code | Intentional: failure is `Result`. | Return `Result` and propagate with `?`. | | Class inheritance | Intentional: composition over inheritance. | Model shared behaviour with interfaces; share implementation via module-level functions. | | Static methods on types | Intentional: module-level `fn` only. | Put “static” helpers at module level; readers see them as regular functions. | | Parameterised tests | Not yet supported. | Loop inside an `@Test` function over a list of cases. | | Before/after hooks in tests | Not yet supported. | Factor shared setup into a helper function called at the top of each test. | | Formatter | Not yet available. | Follow the examples in the [tour](/tour/01-hello-world/) for idiomatic style. | | Raw string literals | Not yet supported. | Use explicit escape sequences. | | VS Code / JetBrains extensions | Not yet available. | Any LSP-capable editor can drive `vertex lsp`. Zed has a purpose-built extension. | ## When a limitation bites [Section titled “When a limitation bites”](#when-a-limitation-bites) If you hit a limitation in a real project, file an issue at [vertex-run/vertex](https://github.com/vertex-run/vertex/issues) with a minimal reproducing example. The list above is curated; a “thing I tried and it didn’t work” that is not listed is almost always a bug worth reporting. --- # Opaque Types > Encapsulated sum types whose internals are hidden from importing modules. A `pub opaque type` is a sum type whose **constructors, fields, and variant patterns are private to its defining module**. Outside that module the type is visible and can be used in annotations, as function parameters, and as return types, but callers cannot inspect or construct its internal representation directly. ```vertex // in module user_ids.vtx pub opaque type UserId { UserId(value: String) } // Smart constructor. The only public way to create a UserId. pub fn make(s: String): UserId { UserId(value: s) } // Public accessor. Exposes the value without leaking the constructor. pub fn unwrap(uid: UserId): String { match uid { UserId(value: v) => v, } } ``` ### What is restricted outside the defining module [Section titled “What is restricted outside the defining module”](#what-is-restricted-outside-the-defining-module) | Operation | Error | | ------------------------------------------------------ | ------ | | `UserId(value: "x")`. Variant constructor call | `E127` | | `uid.value`. Field access | `E128` | | `match uid { UserId(value: v) => v }`. Variant pattern | `E129` | | Record update syntax on the opaque type | `E130` | ### What is allowed outside the defining module [Section titled “What is allowed outside the defining module”](#what-is-allowed-outside-the-defining-module) * Using the type in annotations: `fn foo(uid: UserId): UserId` * Passing values through: `fn passThrough(uid: UserId): UserId { uid }` * Wildcard match: `match uid { _ => 0 }` * Calling the module’s public smart constructors and accessors ```vertex // in module consumer.vtx import user_ids.{UserId, make, unwrap} fn run(): String { let uid = make("abc") // OK. Uses the smart constructor unwrap(uid) // OK. Uses the public accessor } fn bad(): UserId { UserId(value: "x") } // E127 fn bad2(uid: UserId): String { uid.value } // E128 ``` ### Exhaustiveness [Section titled “Exhaustiveness”](#exhaustiveness) Because variant internals are hidden, `match` on an opaque type from outside its defining module requires a **wildcard arm** (`_ => ...`). Any variant pattern arm will be rejected with `E129`, and a match with no wildcard and no valid arms is non-exhaustive (`E105`). ```vertex // OK. Wildcard covers the opaque type match uid { _ => "some value", } ``` ### Syntax [Section titled “Syntax”](#syntax) `opaque` is only valid in combination with `pub`: ```plaintext pub opaque type Name { ... } // OK opaque type Name { ... } // parse error. Opaque requires pub pub opaque fn foo() {} // parse error. Opaque only applies to types ``` --- # Operators > Arithmetic, comparison, logical, and unary operators in Vertex. ### Arithmetic [Section titled “Arithmetic”](#arithmetic) | Operator | Supported types | | -------- | ------------------------------------------------------------ | | `+` | `Int`, `Long`, `Double`, `Decimal`, `String` | | `-`, `*` | `Int`, `Long`, `Double`, `Decimal` | | `/` | `Int` (truncating), `Long` (truncating), `Double`, `Decimal` | | `%` | `Int`, `Long` | No implicit widening. Both sides must be the same numeric type. `String + ` is valid: the right-hand side is coerced to `String` automatically. ```vertex "adult (age " + age + ")" // age is an Int. Coerced automatically ``` ### Comparison [Section titled “Comparison”](#comparison) `==`, `!=`, `<`, `<=`, `>`, `>=`. Work on all numeric types and on `Bool`/`String` for equality. ### Logical [Section titled “Logical”](#logical) `&&`, `||` (both short-circuit), `!` (unary). ### Unary [Section titled “Unary”](#unary) `-` (numeric negation), `!` (boolean negation). --- # Option > The Option type for representing optional values without null in Vertex. `Option` is a built-in sum type with two variants: `Some` and `None`. ```vertex fn find_user( id: Int, ): Option { if id == 1 { Some("Alice") } else { None } } ``` `Some(value)` wraps a value. `None` is a unit variant. No call syntax. ```vertex match find_user(42) { Some(name) => "Hello, ${name}", None => "not found", } ``` ### `Option` fields in constructors [Section titled “Option\ fields in constructors”](#optiont-fields-in-constructors) Any field declared `Option` in a variant constructor is optional at the call site. Omitting it is equivalent to passing `None`: ```vertex type Contact { Contact(name: String, phone: Option) } let c = Contact(name: "Alice") // phone defaults to None let c2 = Contact(name: "Bob", phone: None) // same as above ``` Bare `None` is assignable to any `Option` field regardless of `T`. ### Combinator methods [Section titled “Combinator methods”](#combinator-methods) | Method | Signature | Returns | Description | | ------------------------------ | ----------- | ------------- | --------------------------------------------------------------------------------- | | `isSome()` | `Option` | `Bool` | `true` if this is `Some`. | | `isNone()` | `Option` | `Bool` | `true` if this is `None`. | | `unwrapOr(default: T)` | `Option` | `T` | Returns the inner value, or `default` if `None`. The default is always evaluated. | | `unwrapOrElse(f: fn(): T)` | `Option` | `T` | Returns the inner value, or calls `f()` if `None`. | | `or(other: Option)` | `Option` | `Option` | Returns the receiver if `Some`, otherwise `other`. | | `map(f: fn(T): U)` | `Option` | `Option` | Applies `f` to the inner value if `Some`. `None` passes through. | | `flatMap(f: fn(T): Option)` | `Option` | `Option` | Applies `f` if `Some` and returns the resulting `Option` directly. | | `toResult(error: E)` | `Option` | `Result` | `Some(v)` → `Ok(v)`, `None` → `Error(error)`. | ```vertex let name: Option = find_user(42) // unwrap with default let n: String = name.unwrapOr("guest") // transform if present let upper: Option = name.map(fn(s: String): String { s.toUpperCase() }) // pipeline: map then unwrap let label: String = name .map(fn(s: String): String { "Hello, " + s }) .unwrapOr("Hello, guest") // convert to Result let result: Result = name.toResult("not found") ``` --- # Output > How to produce observable output from a Vertex program. `debug` is the only way to produce observable output in Vertex. It is an **expression**: it evaluates its argument, prints it, and returns the value. It can appear anywhere an expression is valid. ```vertex debug "hello world" debug 42 debug factorial(10) ``` Because `debug` returns its argument, you can drop it into the middle of an expression to inspect an intermediate value without rearranging the code: ```vertex let total = debug compute_base() + compute_tax() // prints base, then adds tax ``` ## Stringification [Section titled “Stringification”](#stringification) `debug` converts its argument to a string before printing, following these rules: | Type | Representation | | -------------- | ---------------------------------------------------- | | `Int`, `Long` | Decimal digits, no leading zeros; negatives with `-` | | `Double` | Shortest decimal form that round-trips | | `Decimal` | Exact decimal form, scale preserved | | `Bool` | `true` or `false` | | `String` | The raw characters, no surrounding quotes | | `Option` | `Some(value)` or `None` | | `Result` | `Ok(value)` or `Error(value)` | | `List` | `[elem1, elem2, ...]` | | `Map` | `{k: v, ...}` (iteration order) | | Sum types | `Variant(field1, field2)` or just `Variant` for unit | ## Output streams [Section titled “Output streams”](#output-streams) In JIT mode, `debug` writes to **stdout**. Every other diagnostic the compiler or JIT produces (warnings, DML notices, test output headers) goes to **stderr**. A program that prints via `debug` and a runner that reads stdout see each other cleanly. In Salesforce mode, `debug` translates to Apex `System.debug(...)`. Output appears in the Apex debug log, not in your terminal. `vertex run --sf` filters the log and prints only the lines that correspond to Vertex’s `debug` calls. ## `W001`: the debug-in-source warning [Section titled “W001: the debug-in-source warning”](#w001-the-debug-in-source-warning) The checker emits a `W001` warning wherever `debug` appears in your code: ```plaintext warning[W001]: `debug` in source at billing.vtx:18:5 | 18 | debug "in compute_total" | ^^^^^ debug output is a dev tool; remove before shipping ``` `debug` is a development aid, not a logging API. `W001` is the reminder to remove it before committing. The warning is suppressed for scripts executed via `vertex run` (where `debug` is the point) but not in `vertex build`. Caution If you find yourself wanting a real log, go through Apex’s logging facilities via [Apex FFI](/reference/salesforce/apex-ffi/). `debug` is intentionally limited to developer iteration. ## Why not `print` or `println`? [Section titled “Why not print or println?”](#why-not-print-or-println) There is one way to produce output. No `print`, no `println`, no `System.out.*`. `debug` is the one way, and the `W001` warning means you see a reminder every time you leave one in. --- # Pattern Matching > Exhaustive match expressions, let assert, and pattern syntax in Vertex. ### `match`: exhaustive, checked by the compiler [Section titled “match: exhaustive, checked by the compiler”](#match-exhaustive-checked-by-the-compiler) ```vertex fn area( s: Shape, ): Double { match s { Circle(radius: r) => 3.14159 * r * r, Rectangle(width: w, height: h) => w * h, Triangle(base: b, height: h) => 0.5 * b * h, } } ``` ### Positional destructuring [Section titled “Positional destructuring”](#positional-destructuring) ```vertex match s { Circle(r) => 3.14159 * r * r, Rectangle(w, h) => w * h, } ``` ### Destructuring shorthand (field punning) [Section titled “Destructuring shorthand (field punning)”](#destructuring-shorthand-field-punning) When the binding variable has the same name as the field, a trailing colon (`field:`) can be used instead of repeating the name (`field: field`): ```vertex // Long form let Book(title: title, author: author, genre: genre) = b // Shorthand. Equivalent let Book(title:, author:, genre:) = b ``` Shorthand and explicit rename can be freely mixed: ```vertex let Book(title:, author: a, genre:) = b // title and genre use shorthand ``` The shorthand is valid in all pattern positions: `let`, `let assert`, `match` arms, and `for` loop heads. ### Discarding unmentioned fields in variant patterns [Section titled “Discarding unmentioned fields in variant patterns”](#discarding-unmentioned-fields-in-variant-patterns) A trailing `..` inside a variant or record pattern means “I don’t care about the remaining fields”. Without it, you must bind every field the type declares. ```vertex match b { Book(title:, ..) => debug title, // ignore author, genre, isbn, ... } ``` `..` is only valid at the end of the field list and binds nothing. If you want to *capture* something, use `...`; if you want to *discard*, use `..`. ### Match guards [Section titled “Match guards”](#match-guards) ```vertex match score { n if n >= 90 => "A", n if n >= 80 => "B", _ => "F", } ``` ### Wildcard `_` [Section titled “Wildcard \_”](#wildcard-_) Matches anything, binds nothing. ```vertex match weekday { Sat => true, Sun => true, _ => false, } ``` ### Literal patterns [Section titled “Literal patterns”](#literal-patterns) ```vertex match status { "active" => "go", "paused" => "wait", _ => "unknown", } ``` ### Multi-subject match [Section titled “Multi-subject match”](#multi-subject-match) ```vertex let msg = match isAdmin, isActive { true, true => "Active admin", true, false => "Inactive admin", false, _ => "User", } ``` ### OR-patterns [Section titled “OR-patterns”](#or-patterns) Multiple alternatives in a single arm. All alternatives must bind the same names with the same types. ```vertex match ticket { VIP(price: p) | Bleachers(price: p) | GeneralAdmission(price: p) => p, FreePass => 0, } ``` Literal alternatives work too: ```vertex match status { "active" | "pending" => true, _ => false, } ``` OR-patterns can have guards; the guard applies to the whole arm: ```vertex match ticket { VIP(price: p) | Bleachers(price: p) if p > 50 => "premium", _ => "standard", } ``` OR-patterns are only valid as the top-level arm pattern in a single-subject `match`. They are not valid in `let`, `let assert`, `for`, or multi-subject match arms. ### List patterns (inside `match` only) [Section titled “List patterns (inside match only)”](#list-patterns-inside-match-only) ```vertex match items { [] => "empty", [head, ...rest] => "starts with ${head}", [a, b, ..] => "at least two elements", } ``` **Rule of thumb:** `...` binds or spreads; `..` discards. Three dots does something with the rest; two dots throws it away. The same rule applies to list patterns (`[head, ...rest]` vs `[a, b, ..]`) and to variant patterns (`Book(title:, ..)`) , `...` has no form in variant patterns since variant fields are named, not positional. ### `let assert`: runtime-checked single-pattern extraction [Section titled “let assert: runtime-checked single-pattern extraction”](#let-assert-runtime-checked-single-pattern-extraction) Caution Panics at runtime if the pattern does not match. Use only when you know the variant is guaranteed. ```vertex let assert Ok(value: n) = someResult let assert Circle(radius: r) = shape ``` Valid patterns in `let assert`: variant, tuple, identifier, wildcard. List patterns and literal patterns are **not** valid in `let assert`. --- # Primitive Types > Vertex's built-in primitive types and their literal syntax. | Type | Literal syntax | Example | | --------- | ----------------------------- | ---------- | | `Int` | plain integer | `42` | | `Long` | integer with `L` suffix | `100L` | | `Double` | decimal point, no suffix | `3.14` | | `Decimal` | decimal point with `d` suffix | `9.99d` | | `Bool` | `true` / `false` | `true` | | `String` | double-quoted | `"hello"` | | `Id` | no literal. Runtime only | (from DML) | | `Void` | the keyword `Void` | `Void` | `Void` is a real first-class value. A function that only produces side effects declares `: Void` and ends with `Void` as its trailing expression. ```vertex fn log( message: String, ): Void { debug message Void } ``` No implicit widening between numeric types. Both sides of an arithmetic expression must be the same type. --- # Result > The Result type for representing success or failure without exceptions in Vertex. `Result` is a built-in sum type with `Ok` and `Error` variants. ```vertex fn safe_divide( a: Int, b: Int, ): Result { if b == 0 { Error("cannot divide by zero") } else { Ok(a / b) } } ``` ```vertex match safe_divide(10, 2) { Ok(v) => "result: " + v, Error(e) => "error: " + e, } ``` ### Combinator methods [Section titled “Combinator methods”](#combinator-methods) | Method | Signature | Returns | Description | | -------------------------------- | ------------- | ------------- | --------------------------------------------------------------------------------- | | `isOk()` | `Result` | `Bool` | `true` if this is `Ok`. | | `isError()` | `Result` | `Bool` | `true` if this is `Error`. | | `unwrapOr(default: T)` | `Result` | `T` | Returns the `Ok` value, or `default` if `Error`. The default is always evaluated. | | `unwrapOrElse(f: fn(E): T)` | `Result` | `T` | Returns the `Ok` value, or calls `f(error)` if `Error`. | | `map(f: fn(T): U)` | `Result` | `Result` | Applies `f` to the `Ok` value. `Error` passes through. | | `mapError(f: fn(E): F)` | `Result` | `Result` | Applies `f` to the `Error` value. `Ok` passes through. | | `flatMap(f: fn(T): Result)` | `Result` | `Result` | Applies `f` if `Ok` and returns the resulting `Result` directly. | | `toOption()` | `Result` | `Option` | `Ok(v)` → `Some(v)`, `Error(_)` → `None`. | ```vertex let r: Result = safe_divide(10, 2) // unwrap with default let n: Int = r.unwrapOr(0) // transform the Ok value let doubled: Result = r.map(fn(x: Int): Int { x * 2 }) // convert Error to a different type let reMapped: Result = r.mapError(fn(e: String): Int { e.length() }) // chain operations let final: Int = r .flatMap(fn(x: Int): Result { Ok(x + 1) }) .unwrapOr(0) // convert to Option (discards error) let opt: Option = r.toOption() ``` ### `?` operator: early error propagation [Section titled “? operator: early error propagation”](#-operator-early-error-propagation) Applied to a `Result` expression inside a function that also returns `Result<_, E>`. If the value is `Error(e)`, propagates immediately; otherwise unwraps the `Ok` value. ```vertex fn process( raw: Int, ): Result { let age = validate_age(raw)? Ok(label_age(age)) } ``` --- # Salesforce > SObjects, DML, AuraEnabled, Apex FFI, and the Apex.Object escape hatch. Vertex is a Salesforce-first language. This section is the reference for everything that touches the Apex runtime: SObject types, DML, Lightning components, and calling into existing Apex code. ## Reading order [Section titled “Reading order”](#reading-order) If you are new to the Salesforce side of Vertex, read these pages in order. Each one builds on the previous. 1. **[Salesforce Integration](/reference/salesforce/integration/)**: `@SObject` types, the `Database.*` DML API, and `@AuraEnabled` functions and types. Start here for day-to-day Salesforce work. 2. **[Apex FFI](/reference/salesforce/apex-ffi/)**: `extern type`, `extern new`, `extern method`, `extern field`, and `extern enum` for calling existing Apex code from Vertex. Read this when you need to reuse Apex libraries or wrap `System.*` APIs. 3. **[Apex.Object](/reference/salesforce/apex-object/)**: the escape hatch for Apex’s universal `Object` type. Read this when you are working with dynamic Apex APIs that return or accept `Object`. ## Related [Section titled “Related”](#related) Annotations `@Test`, `@AuraEnabled`, `@SObject`, and sharing modifiers. [Annotations reference](/reference/annotations/) Testing `@Test`, the simulated DML layer, and running tests locally before deploying. [Testing reference](/reference/testing/) Building for Salesforce Task-oriented guide to building a complete Salesforce project. [Guide: Building for Salesforce](/guides/salesforce-projects/) --- # Apex FFI, Extern Declarations > Referencing existing Apex types, methods, and fields from Vertex code. Vertex can reference existing Apex types through `extern` declarations. Extern declarations are **compile-time metadata only**. They tell the type checker that a foreign type exists, and the codegen emits references to it verbatim. No Apex class is generated for an extern declaration. ## `extern type`: Opaque Apex class [Section titled “extern type: Opaque Apex class”](#extern-type-opaque-apex-class) ```vertex extern type Messaging.SingleEmailMessage extern type Schema.DescribeSObjectResult pub extern type Limits ``` `extern type` declares an Apex class as an opaque Vertex type. The type can be used in parameter and return positions and as generic type arguments, but it has no constructors until `extern new` is declared. ```vertex extern type Messaging.SingleEmailMessage fn wrapAll( msg: Messaging.SingleEmailMessage, ): List { [msg] } ``` Generates: ```apex private static List wrapAll(Messaging.SingleEmailMessage msg) { return new List{msg}; } ``` ## `extern fn`: Apex static method [Section titled “extern fn: Apex static method”](#extern-fn-apex-static-method) ```vertex extern fn Limits.getCpuTime(): Int extern fn Messaging.reserveSingleEmailCapacity(count: Int): Void ``` `extern fn` declares an Apex static method as a callable Vertex function. ```vertex extern fn Limits.getCpuTime(): Int fn logCpu(): Int { Limits.getCpuTime() } ``` Generates: ```apex private static Integer logCpu() { return Limits.getCpuTime(); } ``` ### `= "apexName"` override [Section titled “= "apexName" override”](#-apexname-override) When two Apex overloads share the same name but differ by arity, each can be declared as a distinct `extern fn` with a `= "..."` override: ```vertex extern fn Messaging.sendEmail( mails: List, ): Void extern fn Messaging.sendEmailChecked( mails: List, allOrNothing: Bool, ): Void = "Messaging.sendEmail" ``` Caution Extern fns are not callable in JIT (`vertex run`) mode. They require the Salesforce runtime. Calling an extern fn in JIT mode produces a runtime error. ## `extern new`: Apex constructor [Section titled “extern new: Apex constructor”](#extern-new-apex-constructor) ```vertex extern type Messaging.SingleEmailMessage extern new Messaging.SingleEmailMessage(): Messaging.SingleEmailMessage ``` Vertex code calls it using `TypePath.new()`: ```vertex let msg = Messaging.SingleEmailMessage.new() ``` Generates: `Messaging.SingleEmailMessage msg = new Messaging.SingleEmailMessage();` ### Named constructor overloads [Section titled “Named constructor overloads”](#named-constructor-overloads) ```vertex extern new Messaging.SingleEmailMessage(): Messaging.SingleEmailMessage extern new Messaging.SingleEmailMessage.withSubject( subject: String, ): Messaging.SingleEmailMessage ``` Call sites: ```vertex let plain = Messaging.SingleEmailMessage.new() let subj_msg = Messaging.SingleEmailMessage.withSubject("Hello") ``` Generates: ```apex new Messaging.SingleEmailMessage() new Messaging.SingleEmailMessage('Hello') ``` ## `extern method`: Apex instance method [Section titled “extern method: Apex instance method”](#extern-method-apex-instance-method) ```vertex extern method Messaging.SingleEmailMessage.setSubject( self: Messaging.SingleEmailMessage, subject: String, ): Void ``` The first parameter must be named `self` and carry the receiver type. It is excluded from the call-site signature. ```vertex let msg = Messaging.SingleEmailMessage.new() msg.setSubject("Hello") ``` When the Apex method name differs from the Vertex name, use a `= "..."` override: ```vertex extern method Messaging.InboundEmail.getFrom( self: Messaging.InboundEmail, ): String = "getFromAddress" ``` ## `extern field`: Read-only Apex field [Section titled “extern field: Read-only Apex field”](#extern-field-read-only-apex-field) ```vertex extern field Messaging.InboundEmail.subject: String ``` Vertex code can read the field via dot access; writes are rejected by the checker (error `E120`). ## `extern var`: Read-write Apex field [Section titled “extern var: Read-write Apex field”](#extern-var-read-write-apex-field) ```vertex extern var Messaging.SingleEmailMessage.optOutPolicy: String ``` Reads are always allowed. Writes require the receiver to be declared `mutable let`; writing through an immutable binding is error `E119`. ```vertex fn configurePolicy(msg: Messaging.SingleEmailMessage): Void { mutable let m = msg m.optOutPolicy = "SEND_OPT_OUT" // ok. Local binding is mutable } ``` Field assignment syntax: `receiver.field = value` emits `receiver.field = value;` verbatim in Apex. ## `extern enum`: Apex enum type [Section titled “extern enum: Apex enum type”](#extern-enum-apex-enum-type) ```vertex extern enum Schema.DisplayType { Address, Currency, Phone, Picklist, Reference } ``` Maps an Apex enum type into Vertex as a sum type with unit variants. ```vertex let dt: Schema.DisplayType = Address ``` Match arms use `==` comparison in Apex: ```vertex fn label(dt: Schema.DisplayType): String { match dt { Address => "mailing address", Phone => "phone number", Reference => "lookup to another object", _ => "other", } } ``` Generates: ```apex if (dt == Schema.DisplayType.Address) { return 'mailing address'; } else if (dt == Schema.DisplayType.Phone) { return 'phone number'; } else if (dt == Schema.DisplayType.Reference) { return 'lookup to another object'; } else { return 'other'; } ``` Extern enum variants can be constructed and matched in JIT mode without a Salesforce connection. ## FFI safety rules [Section titled “FFI safety rules”](#ffi-safety-rules) ### `Option` return: null-check wrapper [Section titled “Option\ return: null-check wrapper”](#optiont-return-null-check-wrapper) When an extern declaration returns `Option`, the codegen wraps every call site with a null check: ```vertex extern fn Limits.getHeapSize(): Option let heap = Limits.getHeapSize() // heap: Option ``` Generated Apex: ```apex Integer vtxFfiRaw0 = Limits.getHeapSize(); Option vtxFfiResult0 = vtxFfiRaw0 != null ? (Option) new Some(vtxFfiRaw0) : (Option) new None(); final Option heap = vtxFfiResult0; ``` The programmer is responsible for knowing whether the Apex API can return `null`. If declared non-`Option` and the Apex side returns `null`, a `NullPointerException` propagates in Apex. ### `Result` return: try/catch wrapper [Section titled “Result\ return: try/catch wrapper”](#resultt-apexexception-return-trycatch-wrapper) When an extern declaration returns `Result`, the codegen wraps every call site with a `try`/`catch`. ```vertex extern fn Database.countQuery(soql: String): Result let result = Database.countQuery("SELECT COUNT() FROM Account") match result { Ok(n) => debug "count: " + n.toString(), Error(e) => debug "error: " + e.message, } ``` The `ApexException` type carries: | Field | Type | Description | | ------------ | ----------------------- | ------------------------- | | `message` | `String` | Exception message | | `typeName` | `String` | Apex exception class name | | `stackTrace` | `String` | Stack trace string | | `cause` | `Option` | Cause if present | ## Calling Apex APIs that use `Object` [Section titled “Calling Apex APIs that use Object”](#calling-apex-apis-that-use-object) Some Apex APIs, such as `Database.queryWithBindings`, `JSON.deserializeUntyped`, and parts of `Schema.describe*`, take or return Apex’s universal `Object` type. See [`Apex.Object`](/reference/salesforce/apex-object/) for the interop escape hatch that makes those APIs reachable without breaking Vertex’s nominal type system. --- # Apex.Object, Interop Escape Hatch > Bridging Vertex's nominal type system to Apex APIs that require the universal `Object` type. `Apex.Object` is a **nominal, opaque interop type** that lets Vertex code call Apex APIs whose signatures name Apex’s universal base type `Object`. For example `Database.queryWithBindings(query, Map, accessLevel)` and `JSON.deserializeUntyped(json)`. Interop only `Apex.Object` exists **only** as an escape hatch for Apex FFI. It should not appear in idiomatic Vertex code. The type is deliberately *inert*: it has no methods, no fields, no pattern-matching support, and no operators. The only meaningful things you can do with an `Apex.Object` value are pass it back to an `extern` function or narrow it with `Apex.Object.as`. By design. ## The two operations [Section titled “The two operations”](#the-two-operations) `Apex.Object` has exactly two operations, both compiler intrinsics: | Operation | Signature | Purpose | | ----------------------------------- | -------------------------------------------- | ----------------------------------------------- | | `Apex.Object.from(x: T)` | `(T) -> Apex.Object` | Wrap any typed Vertex value into `Apex.Object`. | | `Apex.Object.as(x: Apex.Object)` | `(Apex.Object) -> Result` | Runtime-check and narrow to `T`. | ### `Apex.Object.from(value)`: wrap [Section titled “Apex.Object.from\(value): wrap”](#apexobjectfromtvalue-wrap) Takes any Vertex value and wraps it into the opaque interop type. At runtime this is a no-op. The value is already `Object` on the Apex side. ```vertex let raw: Apex.Object = Apex.Object.from(42) let also: Apex.Object = Apex.Object.from("Acme") ``` Calling `from` on a value that is already `Apex.Object` is a compile-time error. The wrapper call would be a no-op. ### `Apex.Object.as(value)`: narrow [Section titled “Apex.Object.as\(value): narrow”](#apexobjectastvalue-narrow) Runtime-checks the wrapped value and returns `Result`: `Ok(narrowedValue)` on match, `Err(CastError(...))` on mismatch. This is the *only* way to pull a typed value back out. ```vertex let raw: Apex.Object = Apex.Object.from(42) match Apex.Object.as(raw) { Ok(n) => debug n, // prints 42 Error(e) => debug e.expectedType, } ``` #### Allowed narrow-target types [Section titled “Allowed narrow-target types”](#allowed-narrow-target-types) | Category | Examples | | ---------------- | --------------------------------------------------------------------------------------- | | Primitives | `Int`, `Long`, `Double`, `Decimal`, `Bool`, `String`, `Id`, `Date`, `Datetime` | | SObjects | `Account`, `Contact`, custom `@SObject` types | | Extern types | anything declared via `extern type` | | Container shapes | `List`, `Set`, `Map` where `K` is a primitive | Unsupported targets (e.g. `List`, `Option`, tuples, user sum types, function types) are rejected with a `help:` message telling you to narrow to `List` and walk the elements. ## `Apex.CastError` [Section titled “Apex.CastError”](#apexcasterror) The error value returned by `Apex.Object.as` has the shape (built in, not user-declarable): ```plaintext Apex.CastError { CastError(expectedType: String, actualType: String) } ``` You never construct `Apex.CastError` yourself. It is produced only by the generated narrowing code. You can read its fields after pattern-matching on the `Err` arm. ## Inertness rules [Section titled “Inertness rules”](#inertness-rules) These operations are **compile-time errors** on a value of type `Apex.Object`: | Operation | Error | Reason | | ------------------------ | ----- | ------------------------------- | | `==`, `!=` | E148 | No user-visible equality. | | `<`, `<=`, `>`, `>=` | E149 | No user-visible ordering. | | `+`, `-`, `*`, `/`, `%` | E150 | No arithmetic or string concat. | | `match x { ... }` | E151 | No variants, no structure. | | `x.field` | E152 | No user-visible fields. | | `"${x}"` (interpolation) | E153 | No user-visible string form. | | `Map` | E154 | Not a valid hashable key type. | Each error message points you at `Apex.Object.as` as the way forward. Binding, passing, and collection-insertion are allowed: ```vertex // All fine: Apex.Object flowing through the FFI seam. let x: Apex.Object = Apex.Object.from(42) let xs: List = [x, Apex.Object.from("hi")] let m: Map = { "n": x, "s": Apex.Object.from("hi") } ``` ## Motivating example: `Database.queryWithBindings` [Section titled “Motivating example: Database.queryWithBindings”](#motivating-example-databasequerywithbindings) The primary use case is calling Apex APIs whose signatures require a `Map`. Declare the extern with `Apex.Object` in the map value position, then build the map at the call site: ```vertex pub extern fn Database.queryWithBindings( query: String, bindings: Map, accessLevel: System.AccessLevel, ): Result, ApexException> fn recentNamed(name: String, since: Datetime): List { let bindings: Map = { "name": Apex.Object.from(name), "since": Apex.Object.from(since), } Database.queryWithBindings( "SELECT Id, Name FROM Account WHERE Name = :name AND CreatedDate > :since", bindings, System.AccessLevel.SYSTEM_MODE, ).unwrapOr([]) } ``` `Apex.Object.from` erases to a no-op in generated Apex. The value is already `Object` in the Apex world: ```apex Map bindings = new Map{ 'name' => name, 'since' => since }; ``` ## Walking a JSON-like tree [Section titled “Walking a JSON-like tree”](#walking-a-json-like-tree) `JSON.deserializeUntyped` returns `Apex.Object` whose runtime shape is typically `Map` with nested maps, lists, and primitives. The walk uses `Apex.Object.as` at each step: ```vertex pub extern fn JSON.deserializeUntyped(json: String): Result fn userName(json: String): Option { JSON.deserializeUntyped(json).toOption().flatMap( fn(root: Apex.Object): Option { Apex.Object.as>(root).toOption().flatMap( fn(m: Map): Option { m.get("user").flatMap( fn(u: Apex.Object): Option { Apex.Object.as>(u).toOption().flatMap( fn(userMap: Map): Option { userMap.get("name").flatMap( fn(n: Apex.Object): Option { Apex.Object.as(n).toOption() } ) } ) } ) } ) } ) } ``` The verbosity is **intentional**. Every step returns a `Result` or `Option`, keeping the parse-don’t-cast discipline explicit at every level. --- # Salesforce Integration > SObject types, DML operations, Id type, and AuraError in Vertex. ## `@SObject`: Salesforce Object types [Section titled “@SObject: Salesforce Object types”](#sobject-salesforce-object-types) The `@SObject` annotation marks a single-variant `type` as a Salesforce Object. `@SObject` types are emitted as native Apex SObject constructor calls instead of class instances. ```vertex @SObject type Account { Account(Name: String) } let acc = Account(Name: "ACME") ``` The compiler implicitly adds an `Id: Option` field to every `@SObject` type. This field maps to the Salesforce record `Id` field and enables the insert-then-update pattern: ```vertex @SObject type Account { Account(Name: String) } fn insert_and_update( name: String, ): Result { let acc = Account(Name: name) let insert_result = Database.insert(acc) let assert Ok(inserted_id) = insert_result let updated_acc = Account(Name: "Updated: ${name}", Id: Some(inserted_id)) Database.update(updated_acc) } ``` * `Id: None` or omitting `Id` → no `Id` field in the emitted Apex (record has no Id) * `Id: Some(record_id)` → emits `Id = record_id` in the Apex SObject constructor ## `DmlError`: DML error type [Section titled “DmlError: DML error type”](#dmlerror-dml-error-type) `DmlError` is a built-in type with a single `message: String` field. It is the error type returned by all `Database.*` DML operations. ```vertex let result: Result = Database.insert(acc) ``` Pattern match on `DmlError`: ```vertex match result { Ok(id) => debug "Created with id: ${id.toString()}", Error(err) => { let DmlError(message: msg) = err debug "Failed: ${msg}" }, } ``` ## `Database.*`: DML operations [Section titled “Database.\*: DML operations”](#database-dml-operations) All DML operations return `Result`: | Operation | Return type | | ----------------------------- | --------------------------------------------------------- | | `Database.insert(sobj)` | `Result`, `Ok` holds the new record Id | | `Database.update(sobj)` | `Result` | | `Database.delete(sobj)` | `Result` | | `Database.undelete(sobj)` | `Result` | | `Database.upsert(sobj)` | `Result`, `Ok` holds the upserted record Id | | `Database.insertAll(sobjs)` | `List>` | | `Database.updateAll(sobjs)` | `List>` | | `Database.deleteAll(sobjs)` | `List>` | | `Database.undeleteAll(sobjs)` | `List>` | | `Database.upsertAll(sobjs)` | `List>` | `Database.update`, `Database.delete`, `Database.undelete`, and `Database.upsert` require the SObject argument to have a Salesforce Id set (except `upsert` on new records). Calling them on a record with no Id fails on the org. In JIT mode, DML operations are simulated: `Database.insert` and `Database.upsert` return stub `Id` values with incrementing counters; all operations print a notice to stderr. ## `Id`: Salesforce record identifier [Section titled “Id: Salesforce record identifier”](#id-salesforce-record-identifier) `Id` is a primitive type representing a Salesforce record Id. It is distinct from `String`. There is no implicit coercion in either direction. `Id` values are produced by: * `Database.insert` / `Database.upsert` results (`Ok(id)`) * `Id.from(string)`. User-supplied construction * SOQL query results ### Static factory [Section titled “Static factory”](#static-factory) | Call | Returns | Description | | -------------------- | ------------ | ---------------------------------------------------------------------------------------------------------- | | `Id.from(s: String)` | `Option` | Constructs an `Id` from a string. Always returns `Some`, Apex validates the format at assignment/DML time. | Use `let assert` when the string is a known-good literal: ```vertex let assert Some(id) = Id.from("001000000000001AAA") ``` Use `match` when handling the `None` path is meaningful (e.g. user input): ```vertex match Id.from(raw_string) { Some(id) => useId(id), None => handleInvalid(), } ``` ### Instance methods [Section titled “Instance methods”](#instance-methods) | Method | Returns | Description | | ------------------- | -------- | --------------------------------------------------- | | `.toString()` | `String` | Explicit conversion to the raw 15/18-char ID string | | `.getSObjectType()` | `String` | SObject API name (e.g. `"Account"`, `"Contact"`) | ### Example [Section titled “Example”](#example) ```vertex @SObject type Account { Account(Name: String) } let result = Database.insert(Account(Name: "ACME")) match result { Ok(id) => { debug id // Id(000000000000001AAA) in JIT; raw Id in Apex debug id.toString() // raw string: "000000000000001AAA" debug id.getSObjectType() // "Account" }, Error(_) => debug "failed", } // Constructing an Id from a known literal let assert Some(known_id) = Id.from("001000000000001AAA") debug known_id.toString() ``` In JIT mode, `debug id` uses the tagged form `Id(...)` so you can distinguish it from a plain `String`. In Apex, `System.debug(id)` outputs the raw Id value. --- # Standard Library > Overview of the Vertex standard library and which types to use when. The Vertex standard library is small on purpose. Most of its surface area is built around the core types you already meet in the [Language Tour](/tour/01-hello-world/): primitives, `Option`, `Result`, and collections. This section documents the supporting types that are also available globally. ## By domain [Section titled “By domain”](#by-domain) ### Numbers [Section titled “Numbers”](#numbers) Built-in numeric primitives (`Int`, `Long`, `Double`, `Decimal`) each carry a small set of methods on their instance (conversion, arithmetic helpers, parsing from `String`). See [Numeric methods](/reference/stdlib/numeric/). | Use | Type | | --------------------------- | --------- | | Whole numbers, 32-bit range | `Int` | | Whole numbers, 64-bit range | `Long` | | Fractional, IEEE-754 | `Double` | | Fractional, precise (money) | `Decimal` | ### Strings [Section titled “Strings”](#strings) `String` values carry the usual assortment of query and transform methods (`length`, `toUpperCase`, `contains`, `split`, `trim`, …). See [String methods](/reference/stdlib/string/). ### Dates and times [Section titled “Dates and times”](#dates-and-times) There are three date-related types, each with a different job: | Use | Type | | ----------------------------------------------- | ------------ | | A calendar date (no time-of-day) | `Date` | | A specific moment, including time-of-day | `DateTime` | | Formatting a `Date` or `DateTime` to a `String` | `DateFormat` | See [Date](/reference/stdlib/date/), [DateTime](/reference/stdlib/datetime/), and [DateFormat](/reference/stdlib/date-format/). ### Collections [Section titled “Collections”](#collections) Full documentation for `List`, `Map`, and `Set` lives in the main reference under [Collections](/reference/collections/). They are not repeated here. ### Error handling [Section titled “Error handling”](#error-handling) `Option` and `Result` are the core types for absence and failure. See [Option](/reference/option/) and [Result](/reference/result/). ## Conventions across the stdlib [Section titled “Conventions across the stdlib”](#conventions-across-the-stdlib) * **Methods are camelCase.** `toUpperCase`, `unwrapOr`, `filterMapSome`. * **Methods that might not produce a value return `Option`.** `List.first()`, `List.get(i)`, `Map.get(k)`, `String.indexOf(...)`. * **Methods that might fail return `Result`.** Parsing, construction with validation. * **Pure by default.** Collection methods return new values; they do not mutate the receiver. The `mutable` keyword is reserved for reassignment, not for hidden in-place mutation. * **No overloading.** One name, one signature. If you see a second form, it is a different method with a different name. ## What is not in the stdlib [Section titled “What is not in the stdlib”](#what-is-not-in-the-stdlib) * **No I/O beyond `debug`.** Reading files, network calls, and environment variables are Apex-runtime concerns; use `extern` declarations to call the appropriate Apex APIs. * **No threading primitives.** Apex’s execution model is synchronous within a transaction; Vertex reflects that. * **No random.** If you need randomness, go through `System.Crypto` or similar via [Apex FFI](/reference/salesforce/apex-ffi/). --- # Date > The Date type. Calendar dates with no time or timezone component. `Date` represents a calendar date (year, month, day) with no time or timezone component. ## Factory methods [Section titled “Factory methods”](#factory-methods) | Factory | Returns | Notes | | --------------------------- | -------------- | -------------------------------------------------- | | `Date.of(year, month, day)` | `Date` | All args `Int` | | `Date.today()` | `Date` | Current local date | | `Date.parse(s: String)` | `Option` | Accepts `yyyy-MM-dd` only; `None` on invalid input | ## Instance methods [Section titled “Instance methods”](#instance-methods) | Method | Returns | Notes | | ------------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------- | | `.year()` | `Int` | | | `.month()` | `Int` | | | `.day()` | `Int` | | | `.toDatetime()` | `Datetime` | Midnight on that date (local time) | | `.toString()` | `String` | Format: `yyyy-MM-dd 00:00:00` (matches `System.debug(Date)`) | | `.add(years:, months:, days:)` | `Date` | All args optional (default 0), at least one required. Negative values subtract. Month-end clamping applied. | | `.daysUntil(other: Date)` | `Int` | Whole calendar days from `self` to `other`. Positive when `other` is later, negative when earlier. | | `.format(DateFormat)` | `String` | Format the date using a `DateFormat` value. | ## Parsing [Section titled “Parsing”](#parsing) `Date.parse(s: String)` returns `Option`. Only the format `yyyy-MM-dd` is accepted (matching Apex’s `Date.valueOf`). Any other format returns `None`. ```vertex let d = Date.parse(s: "2024-03-15") // Some(value: 2024-03-15 00:00:00) let x = Date.parse(s: "15/03/2024") // None ``` ## Arithmetic [Section titled “Arithmetic”](#arithmetic) `date.add(...)` returns a new `Date` shifted by the given calendar units. All parameters are labeled; any subset may be provided (at least one required). Negative values subtract: ```vertex date.add(days: 7) // + 7 days date.add(months: -1) // - 1 month date.add(years: 1, months: 2, days: 3) ``` Month-end clamping: `Date.of(year: 2024, month: 1, day: 31).add(months: 1)` → `2024-02-29`. ## Operators [Section titled “Operators”](#operators) `==`, `!=`, `<`, `<=`, `>`, `>=`. Direct date comparison. --- # DateFormat > Formatting Date and Datetime values as strings using DateFormat in Vertex. `DateFormat` is a built-in opaque type that describes how to render a `Date` or `Datetime` as a `String`. Values are created via named factory constructors or the `custom` escape hatch. `DateFormat` values may be stored in variables and passed around freely. ## Named constructors [Section titled “Named constructors”](#named-constructors) Each constructor produces a locale-specific pattern using en\_US conventions. | Constructor | Example output | Pattern (en\_US) | | ------------------------- | ------------------------ | ----------------- | | `DateFormat.yMd()` | `3/15/2024` | `M/d/y` | | `DateFormat.yMMMd()` | `Mar 15, 2024` | `MMM d, y` | | `DateFormat.yMMMMd()` | `March 15, 2024` | `MMMM d, y` | | `DateFormat.yMMMMEEEEd()` | `Friday, March 15, 2024` | `EEEE, MMMM d, y` | | `DateFormat.yMMMEd()` | `Fri, Mar 15, 2024` | `EEE, MMM d, y` | | `DateFormat.MMMd()` | `Mar 15` | `MMM d` | | `DateFormat.MMMMd()` | `March 15` | `MMMM d` | | `DateFormat.MMMMEEEEd()` | `Friday, March 15` | `EEEE, MMMM d` | | `DateFormat.yMMM()` | `Mar 2024` | `MMM y` | | `DateFormat.yMMMM()` | `March 2024` | `MMMM y` | | `DateFormat.yM()` | `3/2024` | `M/y` | | `DateFormat.y()` | `2024` | `y` | | `DateFormat.jm()` | `10:30 AM` | `h:mm a` | | `DateFormat.jms()` | `10:30:00 AM` | `h:mm:ss a` | | `DateFormat.Hm()` | `10:30` | `HH:mm` | | `DateFormat.Hms()` | `10:30:00` | `HH:mm:ss` | ## Custom patterns [Section titled “Custom patterns”](#custom-patterns) `DateFormat.custom(pattern: String)` accepts a raw Java `SimpleDateFormat` pattern string. ```vertex dt.format(DateFormat.custom(pattern: "yyyy-MM-dd")) // 2024-03-15 d.format(DateFormat.custom(pattern: "dd/MM/yyyy")) // 15/03/2024 ``` The `pattern:` argument is required and must be labeled. ## Usage [Section titled “Usage”](#usage) ```vertex let dt = Datetime.of(year: 2024, month: 3, day: 15, hour: 10, minute: 30, second: 0) let d = Date.of(year: 2024, month: 3, day: 15) debug dt.format(DateFormat.yMMMd()) // Mar 15, 2024 debug dt.format(DateFormat.yMMMMEEEEd()) // Friday, March 15, 2024 debug dt.format(DateFormat.Hms()) // 10:30:00 debug dt.format(DateFormat.custom(pattern: "yyyy-MM-dd")) // 2024-03-15 debug d.format(DateFormat.MMMd()) // Mar 15 debug d.format(DateFormat.yMMM()) // Mar 2024 ``` `format` always returns `String`. Format patterns are programmer-written source code, not user input, so failures are considered programmer error rather than something to handle at runtime. --- # Datetime > The Datetime type. Point-in-time values with local and UTC support. `Datetime` represents a point in time (year, month, day, hour, minute, second). Values are **local-timezone** by default, matching Apex’s `Datetime.newInstance` semantics. ## Factory methods [Section titled “Factory methods”](#factory-methods) | Factory | Returns | Notes | | -------------------------------------------------------- | ------------------ | ----------------------------------------------------------- | | `Datetime.of(year, month, day, hour, minute, second)` | `Datetime` | All args `Int`, local time | | `Datetime.now()` | `Datetime` | Current local datetime | | `Datetime.ofUtc(year, month, day, hour, minute, second)` | `Datetime` | All args `Int`, UTC time | | `Datetime.nowUtc()` | `Datetime` | Current datetime (UTC) | | `Datetime.parse(s: String)` | `Option` | Accepts `yyyy-MM-dd HH:mm:ss` only; `None` on invalid input | ## Instance methods [Section titled “Instance methods”](#instance-methods) | Method | Returns | Notes | | ---------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------- | | `.year()` | `Int` | Local year component | | `.month()` | `Int` | Local month component | | `.day()` | `Int` | Local day component | | `.hour()` | `Int` | Local hour component | | `.minute()` | `Int` | Local minute component | | `.second()` | `Int` | Local second component | | `.toDate()` | `Date` | Drops time component (local) | | `.toString()` | `String` | Format: `yyyy-MM-dd HH:mm:ss` (local time) | | `.yearUtc()` | `Int` | UTC year component | | `.monthUtc()` | `Int` | UTC month component | | `.dayUtc()` | `Int` | UTC day component | | `.hourUtc()` | `Int` | UTC hour component | | `.minuteUtc()` | `Int` | UTC minute component | | `.secondUtc()` | `Int` | UTC second component | | `.toDateUtc()` | `Date` | Drops time component (UTC) | | `.toStringUtc()` | `String` | Format: `yyyy-MM-dd HH:mm:ss` (UTC) | | `.add(years:, months:, days:, hours:, minutes:, seconds:)` | `Datetime` | All args optional (default 0), at least one required. Negative values subtract. | | `.format(DateFormat)` | `String` | Format the datetime using a `DateFormat` value. | ## Parsing [Section titled “Parsing”](#parsing) `Datetime.parse(s: String)` returns `Option`. Only the format `yyyy-MM-dd HH:mm:ss` is accepted (matching Apex’s `Datetime.valueOf`). Any other format returns `None`. ```vertex let dt = Datetime.parse(s: "2024-03-15 10:30:00") // Some(value: 2024-03-15 10:30:00) let x = Datetime.parse(s: "2024-03-15T10:30:00Z") // None ``` ## Arithmetic [Section titled “Arithmetic”](#arithmetic) `datetime.add(...)` returns a new `Datetime` shifted by the given units: ```vertex datetime.add(hours: 2) datetime.add(days: 1, hours: -3) datetime.add(years: 1, months: 2, days: 3, hours: 4) ``` ## Operators [Section titled “Operators”](#operators) `==`, `!=`, `<`, `<=`, `>`, `>=`. Direct datetime comparison. ## `--compare` and timezone [Section titled “--compare and timezone”](#--compare-and-timezone) --- # Numeric > Standard library methods for Int, Long, Double, Decimal, and Bool in Vertex. ## `Int` [Section titled “Int”](#int) | Method | Returns | | -------------- | --------- | | `.toDecimal()` | `Decimal` | | `.toDouble()` | `Double` | | `.toLong()` | `Long` | | `.abs()` | `Int` | | `.toString()` | `String` | ## `Long` [Section titled “Long”](#long) | Method | Returns | | -------------- | --------- | | `.toDecimal()` | `Decimal` | | `.toDouble()` | `Double` | | `.toInt()` | `Int` | | `.abs()` | `Long` | | `.toString()` | `String` | ## `Double` [Section titled “Double”](#double) | Method | Returns | | -------------- | ----------------- | | `.toDecimal()` | `Decimal` | | `.toInt()` | `Int` (truncates) | | `.abs()` | `Double` | | `.toString()` | `String` | ## `Decimal` [Section titled “Decimal”](#decimal) | Method | Returns | | ------------- | ----------------- | | `.toInt()` | `Int` (truncates) | | `.abs()` | `Decimal` | | `.toString()` | `String` | ## `Bool` [Section titled “Bool”](#bool) | Method | Returns | | ------------- | -------- | | `.toString()` | `String` | --- # String > Standard library methods for the String type in Vertex. | Method | Returns | Notes | | ----------------------------- | --------------------------- | ------------------------------------------- | | `.length()` | `Int` | | | `.isEmpty()` | `Bool` | | | `.toUpperCase()` | `String` | | | `.toLowerCase()` | `String` | | | `.capitalise()` | `String` | First char upper, rest lower | | `.trim()` | `String` | | | `.trimStart()` | `String` | | | `.trimEnd()` | `String` | | | `.contains(s: String)` | `Bool` | | | `.startsWith(s: String)` | `Bool` | | | `.endsWith(s: String)` | `Bool` | | | `.indexOf(s: String)` | `Option` | `None` if not found | | `.substring(start, end: Int)` | `Option` | `None` if out of bounds | | `.replace(from, to: String)` | `String` | Replaces all occurrences | | `.split(sep: String)` | `List` | | | `.splitOnce(sep: String)` | `Option<#(String, String)>` | `None` if separator not found | | `.reverse()` | `String` | | | `.first()` | `Option` | First character, `None` if empty | | `.last()` | `Option` | Last character, `None` if empty | | `.toOption()` | `Option` | `None` if empty string, `Some(s)` otherwise | --- # String interpolation > Embedding expressions in string literals with ${...} in Vertex. String literals support interpolation via `${...}`. Any expression is valid inside the braces. ```vertex let name = "Alice" let age = 30 let greeting = "Hello, ${name}!" let summary = "${name} is ${age} years old" let calc = "${a} * ${b} = ${a * b}" ``` The embedded expression is evaluated and converted to a `String` via the same stringification rules used by [`debug`](/reference/output/). ## Any expression works [Section titled “Any expression works”](#any-expression-works) ```vertex "path = ${home.join("/")}" // method call "factorial(5) = ${factorial(5)}" // function call "${if x > 0 { "positive" } else { "negative" }}" // if-expression "${match status { Ok(v) => v.toString(), Error(_) => "err" }}" // match ``` If the expression is itself a string, interpolation recurses naturally; no special handling is required. ## Escapes [Section titled “Escapes”](#escapes) String literals support the usual escapes plus one for the interpolation metacharacter: | Escape | Produces | | ------ | --------------- | | `\"` | double-quote | | `\\` | backslash | | `\n` | newline | | `\r` | carriage return | | `\t` | tab | | `\$` | literal `$` | ```vertex let literal = "price is \$${price}" // price is $10 ``` ## Nesting and quoting [Section titled “Nesting and quoting”](#nesting-and-quoting) Double quotes inside an interpolated expression do not need escaping; the interpolation delimiter is `}`, not `"`. ```vertex debug "name=${user.map(fn(u) { u.name }).unwrapOr("anonymous")}" ``` ## Raw strings [Section titled “Raw strings”](#raw-strings) Raw string literals (without escape processing) are not currently supported. Use explicit escapes where needed. ## Codegen [Section titled “Codegen”](#codegen) Interpolations desugar into string concatenation at compile time. The generated Apex does not use `String.format`; it uses the `+` operator with stringified operands, which matches Apex’s own preferred idiom. --- # Sum Types > Algebraic sum types (tagged unions) in Vertex, declared with the type keyword. Declared with the `type` keyword. Each variant may carry named fields or no fields (unit variant). ```vertex type Direction { North, South, East, West, } type Shape { Circle(radius: Double), Rectangle(width: Double, height: Double), Triangle(base: Double, height: Double), } ``` ### Constructing variants [Section titled “Constructing variants”](#constructing-variants) ```vertex let c = Circle( radius: 5.0, ) // named fields let c2 = Circle( 5.0, ) // positional (declaration order) let n = North // unit variant. No parentheses ``` Unit variants are referenced by name alone. Writing `North()` is a compile error. There is only one way to construct a unit variant. No type qualifier is needed. Variant names are globally unique in a module. ### Field access on known variants [Section titled “Field access on known variants”](#field-access-on-known-variants) When the static type is a specific variant (not the erased parent type), direct field access is valid without `match`: ```vertex let c = Circle( radius: 5.0, ) debug c.radius // 5.0 ``` ### Field access on the parent type (common fields) [Section titled “Field access on the parent type (common fields)”](#field-access-on-the-parent-type-common-fields) When **every** variant of a sum type carries a field with the same name and type, that field can be accessed directly on a value of the parent type without `match`: ```vertex type TicketBundle { VIP(price: Int, available: Int, perks: String), Bleachers(price: Int, available: Int), GeneralAdmission(price: Int, available: Int), } fn bundleAvailable(b: TicketBundle): Int { b.available // valid. All variants have 'available: Int' } fn bundlePrice(b: TicketBundle): Int { b.price // valid. All variants have 'price: Int' } ``` If any variant is missing the field (including unit variants), the access is a compile error. Use `match` in that case. --- # Testing > Writing and running tests in Vertex with @Test and vertex test. ## Writing tests [Section titled “Writing tests”](#writing-tests) Mark any zero-parameter function with `@Test` to declare it as a test case: ```vertex @Test fn add_two_numbers(): Void { assert 1 + 1 == 2 } ``` Rules: * `@Test` functions must have **no parameters** (E136). * `@Test` may only appear in files whose name ends in `_test.vtx` (E137). * Non-test modules may not import test modules (E138). Use `assert` for inline assertions. A failing assertion reports the source location and the condition that was false: ```plaintext ---- add_two_numbers ---- math_utils_test.vtx:5:5: assertion failed: 1 + 1 == 3 ``` Use `let assert` to destructure an expected variant and fail fast if it doesn’t match: ```vertex @Test fn divide_succeeds(): Void { let result = safe_divide(10, 2) let assert Ok(value) = result assert value == 5 } ``` ## Naming convention [Section titled “Naming convention”](#naming-convention) Test files must end in `_test.vtx`. The codegen emits a class-level `@IsTest` for every `_test.vtx` source file, making the class deployable as a Salesforce Apex test class. ## Running tests [Section titled “Running tests”](#running-tests) ### Single file [Section titled “Single file”](#single-file) ```sh vertex test path/to/module_test.vtx ``` Runs all `@Test` functions in the file. Exit code 0 if all pass, 1 on any failure. ### Entire project [Section titled “Entire project”](#entire-project) Run from the directory that contains `sfdx-project.json`: ```sh vertex test ``` Discovers all `*_test.vtx` files under `/src/` recursively and runs them in alphabetical order. Exit code 0 only if every test in every file passes. ### Filter [Section titled “Filter”](#filter) ```sh vertex test --filter divide ``` Runs only the test functions whose names contain the filter string (substring match). Exit code 1 if no tests match the filter. ## CLI flags [Section titled “CLI flags”](#cli-flags) | Flag | Description | | ----------------- | ------------------------------------------------- | | `--filter ` | Run only tests whose name contains `` | | `--src ` | Override the source root (default: `/src/`) | | `--no-color` | Disable ANSI color in output | | `--help`, `-h` | Show usage | ## Output format [Section titled “Output format”](#output-format) Test output follows the Cargo/Rust test style: ```plaintext running 7 tests test add_returns_sum ... ok test subtract_returns_difference ... ok test multiply_returns_product ... ok test safe_divide_returns_ok_for_nonzero_divisor ... ok test safe_divide_returns_error_for_zero_divisor ... ok test clamp_keeps_value_in_range ... ok test is_even_identifies_even_and_odd ... ok test result: ok. 7 passed; 0 failed ``` When a test fails, the failure details appear after the summary: ```plaintext running 2 tests test passes ... ok test fails_on_bad_value ... FAILED failures: ---- fails_on_bad_value ---- math_utils_test.vtx:14:5: assertion failed: result == 99 test result: FAILED. 1 passed; 1 failed ``` ## Isolation model [Section titled “Isolation model”](#isolation-model) Each `@Test` function runs in a **fresh interpreter context**. Module-level `const` bindings are re-evaluated for every test; no state leaks between tests. This matches the Apex test isolation model where each test method runs in a rolled-back transaction. ## Salesforce parity [Section titled “Salesforce parity”](#salesforce-parity) `@Test` functions compile to `@IsTest private static void` Apex methods. The file-level `@IsTest` class annotation is added automatically by the codegen for any `*_test.vtx` source file: ```apex @IsTest public with sharing class Lang_MathUtilsTest { @IsTest private static void add_returns_sum() { System.Assert.isTrue(Lang_MathUtils.add(2, 3) == 5, 'assertion failed at 5:5: add(2, 3) == 5'); } } ``` The assertion message is embedded in the generated `System.Assert.isTrue` call so failures reported by the Salesforce Test Runner point back to the original Vertex source location. ## Language compatibility [Section titled “Language compatibility”](#language-compatibility) All Vertex features are supported in test files. The only restriction is that test files cannot be imported from non-test modules. The following constructs are commonly used in tests: | Construct | Notes | | --------------------------- | ------------------------------------------------------- | | `assert ` | Inline assertion; fails with source location | | `let assert = ` | Destructuring assertion; fails if pattern doesn’t match | | `debug ` | Still works in tests; printed to stdout | | `import` | Full cross-module imports supported | Caution `Database.*` DML operations are **simulated** in JIT mode (stub Ids, stderr notice). They execute for real on the Salesforce org. Design tests that work with stub Ids or avoid DML in unit tests where possible. --- # Tuples > Fixed-size heterogeneous tuples in Vertex, with the A tuple groups a fixed number of values of possibly-different types into one. Unlike a record, a tuple’s elements are positional: you reach them by their position, not by a field name. Tuples are written with the `#(...)` syntax. The hash distinguishes them from parenthesized expressions. ## Creating tuples [Section titled “Creating tuples”](#creating-tuples) ```vertex let point = #(1.0, 2.0) // #(Double, Double) let tagged = #("active", 42) // #(String, Int) let nested = #(#(0, 0), #(1, 1)) // tuple of tuples ``` There is no one-element tuple. `#(x)` is not a valid syntax; a single value is just the value. ## Destructuring [Section titled “Destructuring”](#destructuring) Tuples destructure in `let`, in `for..in` heads, in `match` arms, and in function parameters whose types are tuples. ```vertex let #(x, y) = point debug "x=${x}, y=${y}" // Ignore an element with _ let #(_, y) = point // In a for loop for #(index, value) in entries { debug "${index}: ${value}" } // In match fn describe(p: #(Int, Int)): String { match p { #(0, 0) => "origin", #(_, 0) => "on x-axis", #(0, _) => "on y-axis", #(x, y) => "point (${x}, ${y})", } } ``` ## Where tuples are useful [Section titled “Where tuples are useful”](#where-tuples-are-useful) * **Returning two values.** `(name, age)`, `(line, column)`, `(ok, reason)`. * **Map entries.** `Map.of([#("Alice", 30), #("Bob", 25)])` uses a list of 2-tuples as key-value pairs. * **Enumerated iteration.** `list.enumerate()` returns `List<#(Int, T)>`. If the elements are meaningfully named, prefer a `type` with named fields instead: ```vertex // Prefer a named record when the fields have meaning type Location { Location(line: Int, column: Int) } // Tuple is fine when the role of each slot is obvious from context fn minMax(xs: List): Option<#(Int, Int)> { ... } ``` ## Tuples are values [Section titled “Tuples are values”](#tuples-are-values) Tuples are compared by structural equality (element-wise): two tuples of the same type with equal elements are equal. ```vertex let a = #(1, "x") let b = #(1, "x") debug a == b // true ``` ## Codegen [Section titled “Codegen”](#codegen) Tuples emit as a Vertex-internal record in the generated Apex. You rarely need to reason about this; they behave as ordinary values at both the Vertex and Apex levels. --- # Type aliases > Giving an existing type a new name with type X = Y. A type alias introduces a new name for an existing type. It is not a new type; `AccountId` and `String` are interchangeable at every level. ```vertex type AccountId = String type Price = Decimal type Pair = #(Int, Int) ``` ## Transparent, not nominal [Section titled “Transparent, not nominal”](#transparent-not-nominal) Aliases are **transparent**: the compiler treats `AccountId` and `String` as the same type. A function that takes `String` accepts an `AccountId`, and vice versa. ```vertex fn greet_by_id(id: AccountId): String { "hello, ${id}" } let s: String = "005000000000001" debug greet_by_id(s) // OK: AccountId is just String ``` If you want a type that is *incompatible* with its underlying type at the API boundary, use an [opaque type](/reference/opaque-types/) instead. Opaque types are nominal: the compiler keeps them distinct. ## Good uses [Section titled “Good uses”](#good-uses) * **Naming intent at the call site.** `Id`, `AccountId`, `CaseId` all alias `String`, but the name at the call site tells the reader what the caller expects. * **Shortening long generic types.** ```vertex type DmlResult = Result fn insert(acc: Account): DmlResult { ... } ``` * **Giving function types a name.** For function-valued parameters and fields, a function type alias reads better than the inline form. See [function type aliases](/reference/functions/#function-type-aliases) for the specific syntax. ## No generic aliases without a parameter [Section titled “No generic aliases without a parameter”](#no-generic-aliases-without-a-parameter) `type AccountId = String` is fine. `type Box = List` (partial application) is not; aliases do not carry type parameters unless the alias itself is parameterized: ```vertex type Box = List // OK: both sides take one parameter fn empty(): Box { [] } ``` ## Codegen [Section titled “Codegen”](#codegen) Aliases have **no representation** in generated Apex. They are resolved during type-checking and disappear in the emitted code; `AccountId` in Vertex becomes `String` in Apex, not a wrapper class. --- # CLI & Tooling > The vertex command-line tool, project structure, and editor integration. Everything you need to drive Vertex from the command line and your editor. ## In this section [Section titled “In this section”](#in-this-section) The vertex CLI `vertex run`, `vertex test`, `vertex build`, `vertex lsp`: full command reference with flags and examples. [Read more](/tooling/cli/) Project structure How Vertex locates source, what `sfdx-project.json` controls, and where generated Apex lands. [Read more](/tooling/project-structure/) Editor integration LSP capabilities, tree-sitter grammar, and what each feature (diagnostics, completions, hover) depends on. [Read more](/tooling/editor-integration/) --- # The vertex CLI > Command reference for vertex run, vertex test, vertex build, vertex check, vertex docs, and vertex lsp. The `vertex` binary is the entire toolchain: runner, test runner, compiler, and language server. This page is the command reference. ## Overview [Section titled “Overview”](#overview) ```plaintext vertex [options] ``` | Command | Purpose | | ------- | ------------------------------------------------------- | | `run` | Run a `.vtx` file via JIT or on a Salesforce org | | `test` | Run `@Test` functions in `*_test.vtx` files | | `build` | Compile `.vtx` files in the project to Apex class files | | `check` | Parse and type-check a `.vtx` file without executing it | | `docs` | Browse the Vertex language reference offline | | `lsp` | Start the Language Server on stdio | Flags: | Flag | Effect | | ----------------- | ------------ | | `--help`, `-h` | Show help | | `--version`, `-v` | Show version | ## `vertex run` [Section titled “vertex run”](#vertex-run) ```plaintext vertex run [options] ``` Runs a `.vtx` file. Default mode is JIT (local execution); add `--sf` to run on a Salesforce org instead. | Flag | Effect | | --------------- | --------------------------------------------------------------------------- | | *(none)* | JIT mode. Execute locally, print `debug` output to stdout. | | `--sf` | Salesforce mode. Generate Anonymous Apex and execute on the org. | | `--org ` | Salesforce org alias. Requires `--sf`. | | `--compare` | Run both JIT and Salesforce and diff output line-for-line. `--sf` required. | | `--dump-apex` | Print the generated Anonymous Apex to stdout and exit. | | `--help`, `-h` | Show help for this command. | **Standalone files.** A file with no imports runs anywhere, no project required: ```plaintext $ vertex run hello.vtx hello, vertex! ``` **Files that import other modules.** When a file has `import` statements, Vertex looks up to find an `sfdx-project.json` and uses the project’s `src/` directory as the module root: ```plaintext my-project/ ├── sfdx-project.json └── src/ ├── greet.vtx └── main.vtx # import greet ``` Then: ```plaintext $ vertex run src/main.vtx ``` See [Project structure](/tooling/project-structure/) for the full layout rules. **`--compare` in practice.** This is a development aid: it runs your file once via the JIT, once on the org, and diffs the two outputs. It exits 0 only when the output matches line-for-line. ```plaintext $ vertex run --sf --org scratch --compare examples/hello.vtx outputs match (1 line(s)) ``` ## `vertex test` [Section titled “vertex test”](#vertex-test) ```plaintext vertex test [options] [] ``` Runs `@Test` functions. With no file argument, discovers every `*_test.vtx` file in the project’s `src/` and runs them all. | Flag | Effect | | ----------------- | -------------------------------------------- | | `--filter ` | Only run tests whose name contains ``. | | `--src ` | Source directory to search (default: `src`). | | `--no-color` | Disable ANSI color in output. | | `--help`, `-h` | Show help for this command. | **Rules:** * Test files must end in `_test.vtx`. * Test functions are marked `@Test` and take no parameters. * Exit code is 0 when all tests pass, 1 when any test fails. Zero discovered tests is still a pass. ```plaintext $ vertex test src/billing/invoice_test.vtx ✓ invoice totals include tax ✓ zero-line invoice is rejected 2 passed, 0 failed (47ms) ``` ```plaintext $ vertex test --filter invoice ``` See [Testing](/reference/testing/) for the full guide. ## `vertex build` [Section titled “vertex build”](#vertex-build) ```plaintext vertex build [options] ``` Compiles every `.vtx` file in the project’s source directory to Apex class files. Writes the `.cls` + `.cls-meta.xml` pair for each file to `force-app/main/default/classes/`. | Flag | Effect | | -------------- | ---------------------------------- | | `--src ` | Source directory (default: `src`). | | `--help`, `-h` | Show help for this command. | Run from the project root: ```plaintext $ vertex build Success! Built 3 class(es) to force-app/main/default/classes/ ``` Then deploy with: ```plaintext $ sf project deploy start --target-org ``` ## `vertex check` [Section titled “vertex check”](#vertex-check) ```plaintext vertex check ``` Parses, resolves imports, and type-checks a `.vtx` file, then exits. No code is generated, no program is executed. This is the fast feedback loop for editors and for LLM agents writing Vertex: the same Elm-style diagnostics as `vertex run`, minus the run step. Semantically it is to `vertex run` what `cargo check` is to `cargo run`. | Flag | Effect | | -------------- | ---------------------------------- | | `--no-color` | Disable ANSI color in diagnostics. | | `--help`, `-h` | Show help for this command. | **Exit codes.** `0` when the file is valid (warnings are printed but do not fail the command). `1` when any parse, import, or type error is reported. ```plaintext $ vertex check src/billing/invoices.vtx $ echo $? 0 ``` ```plaintext $ vertex check broken.vtx broken.vtx:3:14 error[E100]: type mismatch: expected Int, found String --> 3:14 | 3 | let n: Int = "oops" ^ $ echo $? 1 ``` The `file:line:col` header on the first line is deterministic, so an agent can parse diagnostics without regex-matching the colored body. ## `vertex docs` [Section titled “vertex docs”](#vertex-docs) ```plaintext vertex docs # print the curated index vertex docs list # one slug per line (machine-readable) vertex docs # print a single reference page vertex docs full # print the full concatenated reference vertex docs small # print the compact reference (smaller context budget) vertex docs bnf # print the formal grammar vertex docs search # substring search across every page ``` The full Vertex language reference, baked into the compiler binary. No network required. Useful both for humans at a terminal and for agents that want to feed a topic into a language model without a WebFetch round trip. Content is regenerated from this site on every release, so the output of `vertex docs` always matches the docs site as of the release the binary was cut from. Each command prints a footer with the source commit SHA so drift against [vertex-run.github.io](https://vertex-run.github.io) is visible. **Topic resolution.** Slugs match the URL paths on this site. A bare slug (`pattern-matching`) resolves to a unique full slug (`reference/pattern-matching`) when there is exactly one. When a slug is ambiguous (e.g. `pattern-matching` matches both `reference/pattern-matching` and `guides/pattern-matching`), the command lists the candidates and exits 1. Typos fall through to a fuzzy match with a “did you mean” suggestion. ```plaintext $ vertex docs reference/stdlib/string $ vertex docs search 'exhaustive match' $ vertex docs list | grep stdlib ``` The web has the mirror artifacts at [vertex-run.github.io/llms.txt](https://vertex-run.github.io/llms.txt) and [vertex-run.github.io/llms-full.txt](https://vertex-run.github.io/llms-full.txt), produced by `starlight-llms-txt` per [llmstxt.org](https://llmstxt.org/). ## `vertex lsp` [Section titled “vertex lsp”](#vertex-lsp) ```plaintext vertex lsp ``` Starts the Language Server on stdin/stdout. This is what your editor invokes when you open a `.vtx` file. You do not usually run it directly. See [Editor integration](/tooling/editor-integration/). ## Exit codes [Section titled “Exit codes”](#exit-codes) All commands follow the standard convention: | Code | Meaning | | ---- | ------------------------------------------------------- | | 0 | Success. | | 1 | Failure (compile error, test failure, DML error, etc.). | --- # Editor integration > LSP capabilities, the Zed extension, and the tree-sitter grammar. Vertex ships a Language Server (LSP) and a tree-sitter grammar. Any editor that can drive an LSP server and load a tree-sitter grammar can provide a first-class Vertex experience. The Zed extension is the reference integration. For step-by-step install instructions, see [Editor setup](/getting-started/editor-setup/). This page documents what the LSP actually does. ## LSP capabilities [Section titled “LSP capabilities”](#lsp-capabilities) | Capability | Status | Notes | | ------------------ | ------- | --------------------------------------------------------------------------------- | | Diagnostics (push) | Yes | Real-time as you type. Every checker error and warning flows through. | | Completions | Yes | Keywords, built-in types, annotation names, stdlib members, identifiers in scope. | | Hover | Partial | Shows types for identifiers with a meaningful declaration site. | | Go to definition | Partial | For top-level declarations with a resolvable source. | | Formatting | No | Formatter not yet available. | | Rename | No | Not yet supported. | Diagnostic spans are first-class: a red squiggle lands exactly at the offending token, and multi-location errors (for example, conflicting declarations) highlight every related span. ## The Zed extension [Section titled “The Zed extension”](#the-zed-extension) The extension at [vertex-run/zed-vertex](https://github.com/vertex-run/zed-vertex) wires everything up automatically: * Downloads the `vertex` binary for your platform, or uses the one on your `PATH`. * Launches `vertex lsp` for each `.vtx` buffer. * Loads the [tree-sitter-vertex](https://github.com/vertex-run/tree-sitter-vertex) grammar for highlighting. Install it from Zed’s extensions pane. ## Using the LSP with other editors [Section titled “Using the LSP with other editors”](#using-the-lsp-with-other-editors) Launch command: ```plaintext vertex lsp ``` This reads and writes the LSP protocol on stdin/stdout. No arguments, no initialization options. Client configuration varies by editor. In general you need: * **Language ID**: `vertex` * **File pattern**: `*.vtx` * **Server command**: `vertex lsp` (with `vertex` on `PATH`) * **Workspace root**: the nearest ancestor containing `sfdx-project.json` ## Tree-sitter grammar [Section titled “Tree-sitter grammar”](#tree-sitter-grammar) The grammar at [vertex-run/tree-sitter-vertex](https://github.com/vertex-run/tree-sitter-vertex) is the source of truth for syntax highlighting. Editors that load tree-sitter grammars directly (Neovim with `nvim-treesitter`, Helix, Zed) can use it without the LSP at all, though you lose diagnostics and completions. Highlight queries cover keywords, types, string interpolation, operators, annotations, and comments. ## Reporting issues [Section titled “Reporting issues”](#reporting-issues) Editor problems often come from one of three places: 1. **The LSP itself**, for diagnostics, completions, or hover bugs. File against [vertex-run/vertex](https://github.com/vertex-run/vertex/issues). 2. **The grammar**, for highlighting glitches or wrong tokenization. File against [vertex-run/tree-sitter-vertex](https://github.com/vertex-run/tree-sitter-vertex/issues). 3. **The extension**, for install or binary-download issues. File against [vertex-run/zed-vertex](https://github.com/vertex-run/zed-vertex/issues). A one-line reduced example and the LSP log (if your editor exposes it) is usually enough to identify which one it is. --- # Project structure > How Vertex locates source files, and how modules map to files. A “Vertex project” is any directory that contains an `sfdx-project.json` file. Vertex reuses the standard Salesforce project layout so the output of `vertex build` can be deployed with the `sf` CLI without extra config. ## The minimum [Section titled “The minimum”](#the-minimum) ```plaintext my-project/ ├── sfdx-project.json └── src/ └── hello.vtx ``` `sfdx-project.json` is the project marker. `src/` (the default) holds your Vertex source. You can choose a different source directory with `--src`. `sfdx-project.json`: ```json { "packageDirectories": [ { "path": "force-app", "default": true } ], "sourceApiVersion": "66.0" } ``` ## After `vertex build` [Section titled “After vertex build”](#after-vertex-build) ```plaintext my-project/ ├── sfdx-project.json ├── src/ │ └── hello.vtx └── force-app/ └── main/ └── default/ └── classes/ ├── Hello.cls └── Hello.cls-meta.xml ``` Each `.vtx` file produces one Apex class. The class name is the file name capitalized (`hello.vtx` becomes `Hello.cls`). ## Modules and imports [Section titled “Modules and imports”](#modules-and-imports) Each `.vtx` file is a module. The module name is the file path relative to `src/`, without the extension, using `/` as the separator. ```plaintext src/billing/invoice.vtx # module: billing/invoice src/shared/utils.vtx # module: shared/utils ``` Import another module by its module path: ```vertex import billing.invoice import shared.utils let i = invoice.total(...) ``` See [Modules & Imports](/reference/imports/) for the full rules (public/private visibility, cyclic-import detection, re-export rules). ## Test files [Section titled “Test files”](#test-files) Files ending in `_test.vtx` are test modules. They may import non-test modules, but non-test modules may not import them. ```plaintext src/ ├── invoice.vtx └── invoice_test.vtx # @Test functions live here ``` Run them with `vertex test` (see [The vertex CLI](/tooling/cli/)). ## Anon scripts directory (convention) [Section titled “Anon scripts directory (convention)”](#anon-scripts-directory-convention) For real-project style examples, a common convention is to keep an `anon-scripts/` folder with one `.apex` file per public entry point: ```plaintext my-project/ ├── src/ │ └── hello.vtx # pub fn run() └── anon-scripts/ └── hello.apex # Hello.vtx_run(); ``` Then: ```plaintext $ sf apex run --target-org --file anon-scripts/hello.apex ``` This is not enforced; it is just a tidy way to invoke your entry points against the org. ## Where generated classes go [Section titled “Where generated classes go”](#where-generated-classes-go) `vertex build` writes to `force-app/main/default/classes/`. This is the directory `sf project deploy start` picks up by default. If your `sfdx-project.json` specifies a different default package directory, the output still lands inside it under `main/default/classes/`. --- # Hello, World > Running your first Vertex program and producing output. ## Running Vertex [Section titled “Running Vertex”](#running-vertex) Vertex programs are run locally with: ```plaintext vertex run ``` There is no `main` function. Vertex programs execute top-level statements in order, top to bottom. ## Your first output [Section titled “Your first output”](#your-first-output) `debug` is the only way to produce observable output in Vertex. It is an **expression**. It evaluates its argument, prints it, and returns the value. ```vertex debug "hello, world!" debug 42 debug true ``` Output: ```plaintext hello, world! 42 true ``` You can use `debug` anywhere an expression is valid. In the middle of a calculation, inside a `match` arm, as a function argument. Caution `debug` is a development tool. The compiler emits warning `W001` whenever it appears in source code. Remove `debug` calls before shipping to production. ## Bindings [Section titled “Bindings”](#bindings) Use `let` to bind a name to a value: ```vertex let name = "Alice" let answer = 42 debug "Hello, ${name}! The answer is ${answer}." ``` Output: ```plaintext Hello, Alice! The answer is 42. ``` All bindings are **immutable by default**. To allow reassignment, use `mutable let`: ```vertex mutable let count = 0 count = count + 1 count = count + 1 debug count // 2 ``` Trying to reassign an immutable `let` binding is a compile-time error. ## Next steps [Section titled “Next steps”](#next-steps) * [Primitive Types](/reference/primitive-types/): the built-in types available in Vertex * [Bindings](/reference/bindings/): full binding reference * [Output](/reference/output/), `debug` in depth --- # Primitive Types > Vertex's built-in types. Numbers, booleans, strings, and Void. Vertex has eight built-in types: | Type | Literal | Example | | --------- | ------------------------------------- | --------- | | `Int` | plain integer | `42` | | `Long` | integer with `L` suffix | `100L` | | `Double` | decimal point, no suffix | `3.14` | | `Decimal` | decimal with `d` suffix | `9.99d` | | `Bool` | `true` or `false` | `true` | | `String` | double-quoted | `"hello"` | | `Id` | no literal. Comes from Salesforce DML | , | | `Void` | the keyword `Void` | `Void` | ## Numbers [Section titled “Numbers”](#numbers) ```vertex let age: Int = 30 let population: Long = 8000000000L let price: Decimal = 19.99d let ratio: Double = 0.75 debug age + 5 // 35 debug price * 2.0d // 39.98 ``` Caution **No implicit widening.** Both sides of an arithmetic expression must be the same numeric type. `42 + 3.14` is a compile error. You must explicitly convert first. ## Strings [Section titled “Strings”](#strings) Strings use double quotes and support interpolation: ```vertex let name = "Alice" let age = 30 debug "Hello, ${name}! You are ${age} years old." ``` Any expression is valid inside `${}`: ```vertex debug "${2 + 2}" // 4 debug "area: ${3.14 * 5.0 * 5.0}" ``` `String +` coerces the right-hand side automatically: ```vertex debug "count: " + 42 // "count: 42" ``` ## Void [Section titled “Void”](#void) `Void` is a first-class value. Not just an absence of a return type. A function that only performs side effects declares `: Void` and ends with `Void` as its last expression: ```vertex fn log(message: String): Void { debug message Void } ``` ## Type annotations [Section titled “Type annotations”](#type-annotations) Type annotations on local bindings are optional when the type can be inferred: ```vertex let x = 42 // Int, inferred let y: Int = 42 // Int, explicit. Same thing ``` ## Next steps [Section titled “Next steps”](#next-steps) * [Primitive Types reference](/reference/primitive-types/) * [String Interpolation](/reference/string-interpolation/) * [Operators](/reference/operators/) --- # Bindings and Constants > Immutable bindings, mutable bindings, and module-level constants in Vertex. ## Bindings [Section titled “Bindings”](#bindings) A **binding** connects a name to a value. In Vertex, bindings are immutable by default: ```vertex let greeting = "Hello" let count: Int = 42 // explicit type annotation ``` Attempting to reassign an immutable binding is a compile-time error: ```vertex let x = 10 x = 20 // error E101: cannot assign to immutable binding 'x' ``` For values that need to change, use `mutable let`: ```vertex mutable let total = 0 total = total + 10 total = total + 20 debug total // 30 ``` The `mutable` keyword is intentionally verbose. Mutation should feel deliberate. ## Constants [Section titled “Constants”](#constants) Module-level constants are declared with `const` and must be scalar literals: ```vertex const max_retries = 3 const api_version: String = "v2" const pi: Double = 3.14159 pub const discount_rate = 10 // visible to other modules ``` Constants are always immutable. No `mutable const` is allowed. Public constants can be imported by other modules: ```vertex import config.{ discount_rate } debug discount_rate // 10 ``` ## Underscore prefix: silencing unused warnings [Section titled “Underscore prefix: silencing unused warnings”](#underscore-prefix-silencing-unused-warnings) Prefixing a name with `_` tells the compiler you intentionally don’t use it: ```vertex fn process(_ignored: String): Int { 42 } // W003 suppressed let _temp = expensive_setup() // W002 suppressed ``` The bare `_` wildcard in patterns is a different concept. It discards a value entirely: ```vertex match result { Ok(value) => value, Error(_) => 0, // _ discards the error; not a named binding } ``` ## Next steps [Section titled “Next steps”](#next-steps) * [Bindings reference](/reference/bindings/) * [Constants reference](/reference/bindings/#constants) * [Compiler Diagnostics](/reference/diagnostics/), W002, W003, W004 --- # Control Flow > if/else expressions, if let, block expressions, and early return in Vertex. ## `if` is an expression [Section titled “if is an expression”](#if-is-an-expression) In Vertex, `if` produces a value. You can use it anywhere an expression is expected: ```vertex let score = 85 let grade = if score >= 90 { "A" } else if score >= 80 { "B" } else { "F" } debug grade // "B" ``` Every branch must produce the same type. An `if` without `else` is only valid where `Void` is acceptable. ## Block expressions [Section titled “Block expressions”](#block-expressions) A block `{ ... }` is also an expression. Its value is its **trailing expression**: ```vertex let result = { let x = 5 let y = 3 x * y // trailing expression. This is the block's value } debug result // 15 ``` ## `if let`: conditional pattern matching [Section titled “if let: conditional pattern matching”](#if-let-conditional-pattern-matching) `if let` checks whether a value matches a pattern, and if so, binds the pattern’s variables in the then-block. It is the idiomatic way to handle a single variant: ```vertex fn find_user(id: Int): Option { if id == 1 { Some("Alice") } else { None } } if let Some(name) = find_user(42) { debug "Found: ${name}" } // (nothing printed. Find_user(42) returns None) // With else: let greeting = if let Some(name) = find_user(1) { "Hello, ${name}!" } else { "Hello, stranger!" } debug greeting // "Hello, Alice!" ``` Pattern bindings are scoped to the then-block only. Not visible in the `else` branch. ## Early return [Section titled “Early return”](#early-return) Use `return` for early exit from a function: ```vertex fn divide(a: Int, b: Int): Int { if b == 0 { return 0 } a / b } ``` For fallible operations, prefer returning `Result` rather than a sentinel value: ```vertex fn safe_divide(a: Int, b: Int): Result { if b == 0 { return Error("division by zero") } Ok(a / b) } ``` ## Next steps [Section titled “Next steps”](#next-steps) * [Control Flow reference](/reference/control-flow/): full `if let` spec * [Pattern Matching](/reference/pattern-matching/), `match` for exhaustive dispatch * [Result\](/reference/result/): the right way to model failure --- # Functions > Defining functions, named arguments, default values, closures, and the pipe operator in Vertex. ## Defining functions [Section titled “Defining functions”](#defining-functions) ```vertex fn add(a: Int, b: Int): Int { a + b } ``` * The last expression in the body is the return value (no `return` needed). * All module-level functions are hoisted. You can call them before they’re defined. * Trailing commas are allowed in parameter lists and argument lists. ## Named arguments [Section titled “Named arguments”](#named-arguments) Every parameter can be passed by name at the call site: ```vertex fn greet(name: String, prefix: String): String { "${prefix}, ${name}!" } debug greet(name: "Alice", prefix: "Hello") // Hello, Alice! debug greet(prefix: "Hi", name: "Bob") // Hi, Bob!. Any order debug greet("Carol", "Hey") // positional also works ``` ## Default parameter values [Section titled “Default parameter values”](#default-parameter-values) ```vertex fn greet(name: String, prefix: String = "Hello"): String { "${prefix}, ${name}!" } debug greet(name: "Alice") // Hello, Alice! debug greet(name: "Bob", prefix: "Hi") // Hi, Bob! ``` ## Return type inference [Section titled “Return type inference”](#return-type-inference) Private functions don’t need an explicit return type. The compiler infers it from the body: ```vertex fn double(n: Int) { n * 2 } // inferred: Int fn noop() { } // inferred: Void ``` **`pub fn` must always have an explicit return type**. Module boundaries must be self-documenting. ## Closures [Section titled “Closures”](#closures) Functions are first-class values. Anonymous functions (closures) capture their surrounding scope: ```vertex let add5 = fn(x: Int): Int { x + 5 } debug add5(10) // 15 fn make_adder(base: Int): fn(Int): Int { fn(x: Int): Int { base + x } // captures 'base' from enclosing scope } let add10 = make_adder(10) debug add10(3) // 13 debug add10(7) // 17 ``` ## The pipe operator `|>` [Section titled “The pipe operator |>”](#the-pipe-operator-) `|>` passes the left-hand value as the first argument to the right-hand function: ```vertex fn double(n: Int): Int { n * 2 } fn inc(n: Int): Int { n + 1 } debug 5 |> double // 10 debug 5 |> double |> inc // 11 ``` Use `_` to control where the piped value lands: ```vertex fn add(a: Int, b: Int): Int { a + b } debug 3 |> add(_, 10) // add(3, 10) = 13 debug 3 |> add(10, _) // add(10, 3) = 13 ``` ## Generic functions [Section titled “Generic functions”](#generic-functions) ```vertex fn identity(x: T): T { x } debug identity(42) // 42 debug identity("hello") // hello ``` ## Next steps [Section titled “Next steps”](#next-steps) * [Functions reference](/reference/functions/): closures, destructured parameters, function type aliases * [Control Flow](/reference/control-flow/): using `return` for early exit --- # Sum Types and Pattern Matching > Defining algebraic sum types and using exhaustive match expressions in Vertex. ## Defining sum types [Section titled “Defining sum types”](#defining-sum-types) Sum types are declared with the `type` keyword. Each variant either carries named fields or is a plain unit variant: ```vertex type Direction { North, South, East, West, } type Shape { Circle(radius: Double), Rectangle(width: Double, height: Double), } ``` ## Constructing variants [Section titled “Constructing variants”](#constructing-variants) ```vertex let dir = North // unit variant. No parentheses let c = Circle(radius: 5.0) // named fields let c2 = Circle(5.0) // positional. Same as above ``` Unit variants are referenced by name alone. `North()` is a compile error. ## Exhaustive `match` [Section titled “Exhaustive match”](#exhaustive-match) `match` is Vertex’s primary tool for working with sum types. The compiler verifies that every variant is covered: ```vertex fn area(s: Shape): Double { match s { Circle(radius: r) => 3.14159 * r * r, Rectangle(width: w, height: h) => w * h, } } ``` ## Field shorthand (punning) [Section titled “Field shorthand (punning)”](#field-shorthand-punning) When the binding name matches the field name, use `field:` instead of `field: field`: ```vertex // Long form match s { Circle(radius: radius) => radius * 2.0 } // Shorthand. Equivalent match s { Circle(radius:) => radius * 2.0 } ``` ## Match guards [Section titled “Match guards”](#match-guards) ```vertex match score { n if n >= 90 => "A", n if n >= 80 => "B", _ => "F", // wildcard: matches anything } ``` ## Multi-subject match [Section titled “Multi-subject match”](#multi-subject-match) ```vertex let msg = match isAdmin, isActive { true, true => "Active admin", true, false => "Inactive admin", false, _ => "User", } ``` ## OR-patterns [Section titled “OR-patterns”](#or-patterns) ```vertex match status { "active" | "pending" => true, _ => false, } ``` ## Direct field access [Section titled “Direct field access”](#direct-field-access) When the compiler knows the exact variant statically, you can access fields directly: ```vertex let c = Circle(radius: 5.0) debug c.radius // 5.0. No match needed; static type is Circle ``` When every variant shares a field with the same name and type, you can access it on the parent type too: ```vertex type Ticket { VIP(price: Int, available: Int), Standard(price: Int, available: Int), } fn getAvailable(t: Ticket): Int { t.available // valid. Both variants have 'available: Int' } ``` ## `let assert`: unsafe extraction [Section titled “let assert: unsafe extraction”](#let-assert-unsafe-extraction) Caution Panics at runtime if the pattern doesn’t match. Use only when you know the variant is guaranteed. ```vertex let assert Ok(value) = safe_divide(10, 2) ``` Prefer `if let` for conditional extraction or `match` for exhaustive dispatch. ## Next steps [Section titled “Next steps”](#next-steps) * [Sum Types reference](/reference/sum-types/) * [Pattern Matching reference](/reference/pattern-matching/): list patterns, OR-patterns, `let assert` * [Option\](/reference/option/) and [Result\](/reference/result/). The built-in sum types --- # Collections > Working with List, Map, and Set in Vertex. Vertex has three built-in collection types: `List`, `Map`, and `Set`. ## List [Section titled “List”](#list) Lists are the most common collection. They are ordered and generic: ```vertex let names: List = ["Alice", "Bob", "Carol"] let empty: List = [] debug names.size() // 3 debug names.isEmpty() // false ``` ### Transforming lists [Section titled “Transforming lists”](#transforming-lists) ```vertex let numbers: List = [1, 2, 3, 4, 5] let doubled = numbers.map(fn(n) { n * 2 }) // (2, 4, 6, 8, 10) let evens = numbers.filter(fn(n) { n % 2 == 0 }) // (2, 4) let sum = numbers.fold(0, fn(acc, n) { acc + n }) // 15 ``` ### Safe access [Section titled “Safe access”](#safe-access) There is no subscript syntax (`list[i]`). Index access returns `Option`: ```vertex let xs = [10, 20, 30] debug xs.get(1) // Some(value: 20) debug xs.get(99) // None debug xs.first() // Some(value: 10) debug xs.last() // Some(value: 30) ``` ### Spread to combine [Section titled “Spread to combine”](#spread-to-combine) ```vertex let a = [1, 2, 3] let b = [4, 5, 6] let combined = [...a, ...b, 7] // (1, 2, 3, 4, 5, 6, 7) ``` ### Iterating with `for..in` [Section titled “Iterating with for..in”](#iterating-with-forin) ```vertex for name in names { debug name } // With index for #(i, name) in names.enumerate() { debug "${i}: ${name}" } ``` ### Factories [Section titled “Factories”](#factories) ```vertex let squares = List.generate(5, fn(i: Int): Int { i * i }) // (0, 1, 4, 9, 16) let zeros = List.filled(4, 0) // (0, 0, 0, 0) ``` ## Map [Section titled “Map”](#map) Maps are key-value stores. There is no subscript syntax. Use `get(key)`: ```vertex let scores: Map = {"Alice": 95, "Bob": 87} let empty: Map = Map.empty() debug scores.get("Alice") // Some(value: 95) debug scores.get("Carol") // None let updated = scores.put("Carol", 92) // returns a new map. Original unchanged for #(name, score) in updated.entries() { debug "${name}: ${score}" } ``` ## Set [Section titled “Set”](#set) Sets are unordered collections with no duplicates, using `#{}` literal syntax: ```vertex let tags: Set = #{"apex", "salesforce", "vertex"} debug tags.contains("apex") // true debug tags.size() // 3 let extended = tags.add("Dart") let filtered = tags.filter(fn(t) { t.length() > 4 }) let a = #{1, 2, 3} let b = #{3, 4, 5} debug a.union(b) // #{1, 2, 3, 4, 5} debug a.intersection(b) // #{3} debug a.difference(b) // #{1, 2} ``` ## Next steps [Section titled “Next steps”](#next-steps) * [Collections reference](/reference/collections/): full method tables for List, Map, Set * [for..in Loop](/reference/for-in/): pattern destructuring in loop heads --- # Option and Result > Handling absence and failure safely with Option and Result in Vertex. ## No null: use `Option` [Section titled “No null: use Option\”](#no-null-use-optiont) Vertex has no `null`. Absence of a value is expressed as `Option`, which is a sum type with two variants: * `Some(value)`. A value is present * `None`. No value ```vertex fn find_user(id: Int): Option { if id == 1 { Some("Alice") } else { None } } match find_user(42) { Some(name) => debug "Found: ${name}", None => debug "Not found", } ``` ### Working with Option [Section titled “Working with Option”](#working-with-option) ```vertex let name = find_user(1) // Unwrap with a default let n: String = name.unwrapOr("anonymous") // Transform if present let upper = name.map(fn(s: String): String { s.toUpperCase() }) // Pipeline let label = name .map(fn(s: String): String { "Hello, " + s }) .unwrapOr("Hello, stranger") // Convert to Result let result = name.toResult("user not found") ``` ## No exceptions: use `Result` [Section titled “No exceptions: use Result\”](#no-exceptions-use-resultt-e) Vertex has no `throw`/`try`/`catch`. Fallible operations return `Result`: * `Ok(value)`. Success * `Error(e)`. Failure ```vertex fn safe_divide(a: Int, b: Int): Result { if b == 0 { Error("cannot divide by zero") } else { Ok(a / b) } } match safe_divide(10, 2) { Ok(v) => debug "result: ${v}", Error(e) => debug "error: ${e}", } ``` ### The `?` operator [Section titled “The ? operator”](#the--operator) `?` is the error propagation operator. Apply it to a `Result` expression: * `Ok(value)` → unwraps to `value`, execution continues * `Error(e)` → immediately returns `Error(e)` from the enclosing function ```vertex fn validate_age(age: Int): Result { if age < 0 { Error("age cannot be negative") } else { Ok(age) } } fn process(raw: Int): Result { let age = validate_age(raw)? // propagates Error if validation fails Ok(if age < 18 { "minor" } else { "adult" }) } debug match process(25) { Ok(s) => s, Error(e) => "invalid: " + e } // adult debug match process(-1) { Ok(s) => s, Error(e) => "invalid: " + e } // invalid: age cannot be negative ``` ### Combinators [Section titled “Combinators”](#combinators) ```vertex let r: Result = safe_divide(10, 2) let doubled = r.map(fn(x: Int): Int { x * 2 }) // Result let remapped = r.mapError(fn(e: String): Int { e.length() }) // Result let final = r.flatMap(fn(x: Int): Result { Ok(x + 1) }).unwrapOr(0) let opt = r.toOption() // Option ``` ## Optional fields in constructors [Section titled “Optional fields in constructors”](#optional-fields-in-constructors) Any field typed `Option` in a constructor can be omitted. It defaults to `None`: ```vertex type Contact { Contact(name: String, phone: Option) } let c1 = Contact(name: "Alice") // phone: None let c2 = Contact(name: "Bob", phone: Some("+1 555")) // phone: Some ``` ## Next steps [Section titled “Next steps”](#next-steps) * [Option\ reference](/reference/option/) * [Result\ reference](/reference/result/) * [Pattern Matching](/reference/pattern-matching/): match on both types --- # Modules and Imports > The file-based module system, import forms, visibility, and constants in Vertex. ## File = module [Section titled “File = module”](#file--module) In Vertex, every `.vtx` file is a module. No explicit module declaration is needed. The module name is derived from the filename: * `accounts.vtx` → module `accounts` * `billing/invoices.vtx` → module `billing.invoices` ## Visibility [Section titled “Visibility”](#visibility) Declarations are private by default. Use `pub` to export: accounts.vtx ```vertex pub fn findById(id: String): Option { ... } // exported fn validate(a: Account): Bool { ... } // private ``` ## Import forms [Section titled “Import forms”](#import-forms) **Bare import**. Access symbols via a qualifier: ```vertex import accounts let acc = accounts.findById("001") ``` For nested modules, the qualifier is the last path segment: ```vertex import billing.invoices let inv = invoices.create() ``` **Aliased import**. Choose your own qualifier: ```vertex import billing.invoices as inv let i = inv.create() ``` **Selective import**. Bring specific symbols directly into scope: ```vertex import accounts.{ findById, Account } let acc = findById("001") // no qualifier needed ``` ## Apex class names [Section titled “Apex class names”](#apex-class-names) Each module maps to an Apex class when compiled: | Module path | Apex class | | ------------------ | ------------------ | | `accounts` | `Accounts` | | `billing/invoices` | `Billing_Invoices` | | `lang/greeter` | `Lang_Greeter` | ## Error codes [Section titled “Error codes”](#error-codes) | Code | Meaning | | ------ | ----------------------------------------- | | `E122` | Module not found | | `E123` | Symbol not found in module’s public API | | `E124` | Attempted access to a private declaration | | `E125` | Cyclic import detected | | `E126` | Name conflict from two selective imports | ## Running scripts with imports [Section titled “Running scripts with imports”](#running-scripts-with-imports) `vertex run` supports imports without a build step. Point it at any entry script and it discovers the project automatically by walking upward from the file’s directory to find `sfdx-project.json`: ```plaintext vertex run scripts/hello.vtx ``` Only the transitively reachable modules are loaded. Files in `src/` that are not imported are never touched. A file with no imports runs anywhere. No project required. See the [Imports reference](/reference/imports/#vertex-run-and-imports) for the full discovery rules and `--sf` preconditions. ## Next steps [Section titled “Next steps”](#next-steps) * [Imports reference](/reference/imports/): full spec including opaque type error codes * [Constants reference](/reference/bindings/#constants), `pub const` for exported values * [Annotations](/reference/annotations/): controlling Apex sharing modifiers per module --- # Salesforce Integration > SObject types, DML operations, @AuraEnabled, and Apex FFI in Vertex. ## `@SObject`: Salesforce Object types [Section titled “@SObject: Salesforce Object types”](#sobject-salesforce-object-types) Mark a single-variant `type` with `@SObject` to treat it as a Salesforce SObject: ```vertex @SObject type Account { Account(Name: String) } let acc = Account(Name: "ACME") ``` Every `@SObject` type automatically gets an `Id: Option` field. This enables the insert-then-update pattern: ```vertex fn create_and_rename(name: String): Result { let acc = Account(Name: name) let assert Ok(new_id) = Database.insert(acc) let updated = Account(Name: "Updated: ${name}", Id: Some(new_id)) Database.update(updated) } ``` ## DML operations [Section titled “DML operations”](#dml-operations) All database operations return `Result`: ```vertex let result = Database.insert(Account(Name: "ACME")) match result { Ok(id) => debug "Created: ${id.toString()}", Error(err) => debug "Failed: ${err.message}", } ``` | Operation | Success type | | ------------------------- | ------------ | | `Database.insert(sobj)` | `Id` | | `Database.update(sobj)` | `Void` | | `Database.delete(sobj)` | `Void` | | `Database.undelete(sobj)` | `Void` | ## `@AuraEnabled`: Lightning components [Section titled “@AuraEnabled: Lightning components”](#auraenabled-lightning-components) Mark a function as callable from LWC with `@AuraEnabled`. It must return `Result`: ```vertex @AuraEnabled(cacheable: true) fn getAccountName(id: String): Result { // On Ok, the value is returned directly to LWC // On Error, AuraHandledException is thrown automatically Ok("ACME") } ``` For LWC data transfer objects, annotate the type too: ```vertex @AuraEnabled pub type AccountDto { AccountDto(name: String, revenue: Decimal) } ``` ## Apex FFI [Section titled “Apex FFI”](#apex-ffi) Vertex can call existing Apex code through `extern` declarations: ```vertex // Declare an Apex class as a Vertex type extern type Messaging.SingleEmailMessage // Declare an Apex constructor extern new Messaging.SingleEmailMessage(): Messaging.SingleEmailMessage // Declare an Apex instance method extern method Messaging.SingleEmailMessage.setSubject( self: Messaging.SingleEmailMessage, subject: String, ): Void // Use it let msg = Messaging.SingleEmailMessage.new() msg.setSubject("Hello from Vertex") ``` Caution `extern` declarations are not callable in JIT mode. They require the Salesforce runtime. Calling an extern fn locally produces a runtime error. ## Sharing modifiers [Section titled “Sharing modifiers”](#sharing-modifiers) Control the Apex sharing modifier at the top of any `.vtx` file: ```vertex @WithoutSharing // or @WithSharing, @InheritedSharing import lang.db pub fn run(): Void { ... } ``` The default is `with sharing`. The safe Salesforce security default. ## Next steps [Section titled “Next steps”](#next-steps) * [Salesforce Integration reference](/reference/salesforce/integration/): `@SObject`, `Database.*`, `Id` type * [Apex FFI reference](/reference/salesforce/apex-ffi/): `extern type`, `extern fn`, `extern method`, `extern field`, `extern enum` * [Annotations reference](/reference/annotations/), `@AuraEnabled`, sharing modifiers