Skip to content

ipavlic/peak

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Peak - Generics for Salesforce Apex

Test and Coverage codecov Go Report Card

Peak is a transpiler that brings generic programming back to Salesforce Apex. Write reusable generic classes once, and Peak generates type-safe concrete Apex classes ready for deployment.

// Write once
public class Queue<T> {
    private List<T> items;
    public void enqueue(T item) { items.add(item); }
    public T dequeue() { return items.remove(0); }
}

// Use everywhere
Queue<Integer> numbers = new Queue<Integer>();
Queue<Account> accounts = new Queue<Account>();

Why Peak?

  • Write once, use everywhere - Create a generic like Queue<T> and use it with any type
  • Type safety - Generated classes are strongly typed; no casting, no runtime errors
  • Zero runtime cost - All generics resolve at compile time to concrete classes
  • Future-proof - Minimal syntax transformation ensures compatibility with Apex updates
  • Nested generics - Support for complex types like Queue<List<Integer>>

Quick Start

Installation

# Install from source (requires Go 1.20+)
git clone https://github.com/ipavlic/peak.git
cd peak
go build -o peak ./cmd/peak

# Or install directly
go install github.com/ipavlic/peak/cmd/peak@latest

Usage

peak examples/                              # Transpile directory
peak --watch examples/                      # Auto-recompile on changes
peak --out-dir build/ src/                  # Custom output directory
peak --root-dir . --out-dir build/          # Preserve structure from root
peak --api-version 64.0 src/                # Set API version for meta files

How It Works

Step 1: Define a Generic Template

Create a .peak file with generic type parameters:

// Queue.peak - A generic queue that works with any type
public class Queue<T> {
    private List<T> items;

    public Queue() {
        this.items = new List<T>();
    }

    public void enqueue(T item) {
        items.add(item);
    }

    public T dequeue() {
        return items.remove(0);
    }
}

Step 2: Use the Template

Reference your generic class with concrete types:

// QueueExample.peak - Uses Queue with specific types
public class QueueExample {
    private Queue<Integer> intQueue;
    private Queue<String> stringQueue;

    public QueueExample() {
        this.intQueue = new Queue<Integer>();
        this.stringQueue = new Queue<String>();
    }
}

Step 3: Transpile & Output

Run Peak to generate concrete Apex classes:

peak examples/

Peak generates:

  1. Transpiled usage files - QueueExample.cls with generic references replaced:

    public class QueueExample {
        private QueueInteger intQueue;    // Queue<Integer> → QueueInteger
        private QueueString stringQueue;  // Queue<String> → QueueString
        // ...
    }
  2. Concrete class files - Type-specific classes from templates:

    • QueueInteger.cls - all T replaced with Integer
    • QueueString.cls - all T replaced with String
  3. Templates skipped - Queue.peak is not compiled (it's a template)

All .cls files are ready to deploy to Salesforce!

Configuration

CLI Flags

--help, -h                   Display help message
--watch, -w                  Watch for changes and auto-recompile
--out-dir, -o <dir>          Output directory (overrides config)
--root-dir, -r <dir>         Root directory for preserving structure
--api-version, -a <version>  Salesforce API version for .cls-meta.xml (default: 65.0)

Config File (peakconfig.json)

Create peakconfig.json in your source directory:

{
  "compilerOptions": {
    "outDir": "build/classes",
    "rootDir": ".",
    "apiVersion": "65.0",
    "verbose": false,
    "instantiate": {
      "classes": {
        "Queue": ["Integer", "String", "Boolean"],
        "Optional": ["Double", "Decimal"]
      },
      "methods": {
        "Repository.get": ["Account", "Contact", "String"],
        "Repository.put": ["Account", "Contact"]
      }
    }
  }
}

Config Options:

  • outDir - Output directory for generated files (default: co-located with source)
  • rootDir - Root directory to preserve relative paths when using outDir. When set with outDir, preserves directory structure relative to this root instead of the source directory.
  • apiVersion - Salesforce API version for .cls-meta.xml files (default: "65.0")
  • verbose - Enable detailed logging (default: false)
  • instantiate.classes - Force generation of specific class instantiations
  • instantiate.methods - Force generation of specific method instantiations (format: "ClassName.methodName": ["Type1", "Type2"])

Priority: CLI flags > Config file > Defaults

Example - Directory Structure Preservation:

# Without rootDir: src/utils/Queue.peak → build/classes/utils/Queue.cls
peak --out-dir build/classes src/

# With rootDir: src/utils/Queue.peak → build/classes/src/utils/Queue.cls
peak --root-dir . --out-dir build/classes src/

Features

Type Parameter Rules

Type parameters must be single uppercase letters (T, K, V, etc.):

class Queue<T>              // Good - single letter
✓ class Dict<K, V>            // Good - multiple single letters
✗ class Queue<Type>           // Error - multi-letter not allowed
✗ class Dict<T, T>            // Error - duplicate parameters

Built-in Generics

Apex's native List<T>, Set<T>, and Map<K,V> remain unchanged. Only custom generic classes are transformed.

Multiple Type Parameters

Define classes with multiple type parameters:

public class Dict<K, V> {
    private List<K> keys;
    private List<V> values;

    public void put(K key, V value) {
        keys.add(key);
        values.add(value);
    }

    public V get(K key) {
        Integer index = keys.indexOf(key);
        return index >= 0 ? values.get(index) : null;
    }
}

// Use with any key-value combination
Dict<String, Integer> scores = new Dict<String, Integer>();
Dict<Integer, Account> accountMap = new Dict<Integer, Account>();

Nested Generics

Generic types can be nested to any depth:

Queue<List<Integer>> batchQueue = new Queue<List<Integer>>();
Dict<String, Queue<Account>> accountQueues = new Dict<String, Queue<Account>>();

Generates concrete classes like QueueListInteger.cls and DictStringQueueAccount.cls.

Generic Methods

Define generic methods that work with any type:

public class Repository {
    public <T> T get(String key) { ... }
    public <T> void put(String key, T value) { ... }
}

Configure concrete method generation in peakconfig.json:

{
  "compilerOptions": {
    "instantiate": {
      "methods": {
        "Repository.get": ["Account", "Contact", "String"],
        "Repository.put": ["Account", "Contact"]
      }
    }
  }
}

Generates concrete methods:

public Account getAccount(String key) { ... }
public Contact getContact(String key) { ... }
public void putAccount(String key, Account value) { ... }

Naming: methodName + type (e.g., getString, putAccount)

Error Handling

Peak provides clear error messages with line/column info. Files with errors are reported but don't block other files from compiling.

Queue.peak:5:14: error: type parameter must be a single letter, got: Type

Examples

See examples/ directory:

Templates:

  • Queue.peak - Generic queue <T>
  • Dict.peak - Generic dictionary <K, V>
  • Repository.peak - Generic methods

Usage:

  • QueueExample.peak - Basic usage
  • NestedGenericsExample.peak - Nested types (Queue<List<Integer>>)
  • ComplexExample.peak - Advanced patterns

Run peak examples/ to try it out!

Development

go build -o peak ./cmd/peak    # Build
go test ./...                   # Test

Structure:

  • cmd/peak/ - CLI application
  • pkg/parser/ - Generic syntax parser
  • pkg/transpiler/ - Template instantiation
  • examples/ - Example files

Limitations

Not yet supported:

  • Type constraints: class Queue<T extends SObject>
  • Variance annotations: class Queue<out T>

Note: Generated class names use simple concatenation (Queue<List<Integer>>QueueListInteger), which can create long names for deeply nested generics.

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

License

MIT License - see LICENSE file for details.


Questions or Issues? Open an issue or check the examples/ directory for working code.