# Templates (/docs/architecture/legacy-drafts/10-templates)



# Templates [#templates]

Templates define **how** domain definitions are transformed into output code. They are the "rules" that AI agents and the generation engine use to produce final output.

## Key Decisions [#key-decisions]

| Decision            | Choice                                       |
| ------------------- | -------------------------------------------- |
| **Template format** | Text files (Scriban), NOT compiled C#        |
| **Location**        | External files, editable without compilation |
| **AI access**       | AI can create, modify, and consume templates |
| **Separation**      | Templates separate from definitions          |
| **Traceability**    | Generated code includes generation metadata  |

## Previous Approach (Replaced) [#previous-approach-replaced]

The original implementation used C# classes with `StringBuilder` to build output:

```csharp
public IOutput Create()
{
    var sb = new StringBuilder();
    sb.Al("namespace MyNamespace;");
    sb.B();
    sb.Al($"public class {_entity.Name}");
    sb.Al("{");
    foreach (var field in _entity.Fields)
    {
        sb.I(1).Al($"public {field.DataType} {field.Name} {{ get; set; }}");
    }
    sb.Al("}");
    return new File($"{_entity.Name}.cs", sb.ToString());
}
```

This approach was replaced with Scriban because:

| Issue                    | Impact                                                 |
| ------------------------ | ------------------------------------------------------ |
| **Hard to read**         | Output structure not visible at a glance               |
| **Verbose**              | Lots of boilerplate (`.Al()`, `.I()`, `.B()`)          |
| **Mixed concerns**       | Logic and output intertwined                           |
| **Requires compilation** | Can't modify templates without rebuilding              |
| **Language-specific**    | Hard to create templates for multiple output languages |
| **Not reusable**         | Similar patterns repeated across templates             |

***

## Current Approach: Scriban [#current-approach-scriban]

[Scriban](https://github.com/scriban/scriban) is a fast, powerful, and safe text templating engine for .NET.

### Why Scriban [#why-scriban]

| Feature                  | Benefit                                           |
| ------------------------ | ------------------------------------------------- |
| **Text-based templates** | Edit without recompilation                        |
| **Fast & lightweight**   | Minimal overhead, GC-friendly                     |
| **Full scripting**       | `if`/`else`/`for`/`while`, expressions, functions |
| **Liquid-compatible**    | Can parse Liquid syntax if needed                 |
| **Extensible**           | Custom functions, member renaming                 |
| **Safe sandbox**         | Control what objects are exposed                  |
| **VS Code extension**    | Syntax highlighting available                     |
| **Async support**        | `Template.RenderAsync` for async operations       |

### Template Syntax [#template-syntax]

```liquid
{{~ # Scriban uses {{ }} delimiters ~}}
namespace {{ enterprise.namespace }};

public class {{ entity.name }}
{
{{~ for field in entity.fields ~}}
    public {{ field.data_type }} {{ field.name }} { get; set; }
{{~ end ~}}
}
```

### Installation [#installation]

```bash
dotnet add package Scriban
```

### Basic Usage [#basic-usage]

```csharp
using Scriban;

// Load template from file (no compilation needed!)
var templateText = File.ReadAllText("templates/entity.scriban");
var template = Template.Parse(templateText);

// Render with domain model
var output = template.Render(new {
    Entity = entity,
    Enterprise = enterprise
});
```

***

## Generation Metadata (Traceability) [#generation-metadata-traceability]

Generated code must contain enough information to regenerate it. This enables:

* Re-running generation when definitions change
* Understanding what created a file
* Knowing whether a file can be safely overwritten

### Current Header Pattern [#current-header-pattern]

The existing `Header` template provides this (keep this pattern!):

```csharp
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a Modeller template:
//     Template: csharp/entity.scriban
//     Definition: definitions/booking/entity.yaml
//     Generated: 2024-12-03T10:30:00Z
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
```

### Scriban Header Template [#scriban-header-template]

```liquid
{{~ # _header.scriban - Include at top of every generated file ~}}
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a Modeller template:
//     Template: {{ generation.template_path }}
//     Definition: {{ generation.definition_path }}
//     Generated: {{ generation.timestamp | date.to_string '%Y-%m-%dT%H:%M:%SZ' }}
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

#nullable {{ options.nullable | default 'enable' }}
```

### Generation Context [#generation-context]

Every template receives a `generation` object:

```yaml
generation:
  template_path: "templates/csharp/entity.scriban"
  template_version: "1.0.0"
  definition_path: "definitions/booking/entity.yaml"
  timestamp: "2024-12-03T10:30:00Z"
  can_overwrite: true
```

***

## Template Organisation [#template-organisation]

Templates are stored in a dedicated folder, separate from definitions:

```
project/
├── definitions/              # Domain definitions (DSL/YAML)
│   ├── booking/
│   │   ├── entity.yaml
│   │   └── commands.yaml
│   └── ...
│
├── templates/                # Scriban templates (editable files)
│   ├── _shared/              # Shared includes
│   │   ├── _header.scriban
│   │   ├── _property.scriban
│   │   └── _using.scriban
│   │
│   ├── csharp/               # C# output templates
│   │   ├── template.yaml     # Template metadata
│   │   ├── entity.scriban
│   │   ├── repository.scriban
│   │   └── command-handler.scriban
│   │
│   ├── typescript/           # TypeScript output templates
│   │   ├── template.yaml
│   │   ├── interface.scriban
│   │   └── service.scriban
│   │
│   ├── sql/                  # SQL output templates
│   │   ├── template.yaml
│   │   └── create-table.scriban
│   │
│   └── documentation/        # Documentation templates
│       ├── template.yaml
│       └── entity-docs.scriban
│
└── output/                   # Generated files (with metadata headers)
    └── ...
```

***

## Template Composition [#template-composition]

Small, reusable template fragments:

````
templates/
├── csharp/
│   ├── _header.scriban
│   ├── _property.scriban
│   ├── _class.scriban
│   ├── entity.scriban        # uses _class, _property
│   └── repository.scriban
├── typescript/
│   ├── _property.scriban
│   ├── interface.scriban
│   └── service.scriban


---

## Template Definition Format

Templates should be described in a discoverable format:

```yaml
# templates/csharp/entity.template.yaml
template: CSharpEntity
version: 1.0
description: Generates a C# entity class from a domain entity

input:
  requires:
    - entity
  optional:
    - enterprise

output:
  type: file
  extension: .cs
  naming: "{{ entity.name }}.cs"

options:
  nullable: true
  use_records: false
  generate_constructor: true

files:
  - entity.scriban

includes:
  - _header.scriban
  - _property.scriban
````

***

## Multi-Language Support [#multi-language-support]

Same domain definition, multiple output languages:

```yaml
# Template set for an Entity
template_set: Entity
description: Entity representations across languages

variants:
  - language: csharp
    template: csharp/entity.scriban
    extension: .cs

  - language: typescript
    template: typescript/interface.scriban
    extension: .ts

  - language: python
    template: python/dataclass.scriban
    extension: .py

  - language: sql
    template: sql/create-table.scriban
    extension: .sql
```

### Language-Specific Helpers [#language-specific-helpers]

Each language gets type mappings and conventions:

```yaml
# languages/csharp.yaml
language: csharp
file_extension: .cs

type_mappings:
  text: string
  integer: int
  decimal: decimal
  boolean: bool
  date: DateOnly
  datetime: DateTime
  guid: Guid

conventions:
  class_naming: PascalCase
  property_naming: PascalCase
  field_naming: _camelCase

nullable_syntax: "{{ type }}?"
collection_syntax: "List<{{ type }}>"
```

***

## Example: Entity Template (Scriban) [#example-entity-template-scriban]

```liquid
{{~ # Entity template for C# ~}}
{{~ include '_header.scriban' ~}}

namespace {{ enterprise.namespace }}.{{ service.name }}.Entities;

/// <summary>
/// {{ entity.description }}
/// </summary>
public class {{ entity.name }}
{
{{~ for field in entity.fields ~}}
    /// <summary>
    /// {{ field.description }}
    /// </summary>
    public {{ field | to_csharp_type }} {{ field.name }} { get; set; }{{ if field.default_value }} = {{ field.default_value }};{{ end }}

{{~ end ~}}
{{~ if entity.belongs_to ~}}
    // Navigation property
    public {{ entity.belongs_to }} {{ entity.belongs_to }} { get; set; } = default!;
{{~ end ~}}
}
```

***

## Template Functions [#template-functions]

Custom functions available in templates:

| Function             | Description               | Example                             |
| -------------------- | ------------------------- | ----------------------------------- |
| `to_csharp_type`     | Convert domain type to C# | `{{ field \| to_csharp_type }}`     |
| `to_typescript_type` | Convert to TypeScript     | `{{ field \| to_typescript_type }}` |
| `pascal_case`        | Convert to PascalCase     | `{{ name \| pascal_case }}`         |
| `camel_case`         | Convert to camelCase      | `{{ name \| camel_case }}`          |
| `snake_case`         | Convert to snake\_case    | `{{ name \| snake_case }}`          |
| `pluralize`          | Pluralize a word          | `{{ name \| pluralize }}`           |
| `singularize`        | Singularize a word        | `{{ name \| singularize }}`         |

***

## AI and Templates [#ai-and-templates]

AI agents can interact with templates in two ways:

### 1. Consume Templates (as Rules) [#1-consume-templates-as-rules]

AI uses existing templates to generate code from definitions:

```
AI Agent → reads definition → applies template → produces output
```

The template acts as a "rule" the AI follows for consistent output.

### 2. Create/Modify Templates [#2-createmodify-templates]

AI can author new templates or improve existing ones:

```
User: "Create a template for generating Python dataclasses from entities"
AI: Creates templates/python/dataclass.scriban
```

This enables rapid creation of output formats without manual template authoring.

***

## Remaining Questions [#remaining-questions]

1. **Versioning**: How to handle template version upgrades? Semantic versioning?

2. **Validation**: How to validate templates produce syntactically correct output?

3. **Testing**: How to test templates in isolation? Golden file comparisons?

4. **Discovery**: How does the engine find and list available templates?

5. **Dependencies**: Can templates depend on other templates (beyond includes)?
