Genairus logoGenAI-R-Us
Genairus logoGenAI-R-Us

Capacitor Generator Implementation Guide

This document describes how each Capacitor generator implements features for specific databases and ORMs. It provides transparency about what happens under the hood when you target different platforms.

Generator Architecture and SPI

Capacitor uses a pluggable generator architecture that separates the universal data model from database and framework-specific implementations. Generators follow a formal Service Provider Interface (SPI) that enables third-party generator development and composition.

Generator Lifecycle

Every generator follows a defined lifecycle with distinct phases:

discover → configure → validate → resolve → generate → post-process → package → publish

1. Discover: Build system finds generators via plugin declarations in capacitor-build.json

{
  "plugins": {
    "generators": {
      "com.acme.capacitor:generator-django": "1.0.0"
    }
  }
}

2. Configure: Generator receives its configuration block from capacitor-build.json

{
  "generators": {
    "java-jpa-sdk": {
      "generator": "capacitor:java-jpa",
      "target": "production-sql",
      "options": { /* generator-specific options */ }
    }
  }
}

3. Validate: Generator confirms compatibility (framework + database target)

  • Checks if the framework supports the target database
  • Validates that required options are provided
  • Reports errors for unsupported feature combinations

4. Resolve: Generator resolves the semantic model into its internal representation

  • Parses all entities, shapes, enums, repositories
  • Resolves namespace imports and dependencies
  • Builds the complete type graph

5. Generate: Generator emits files (source, config, migration, docs, tests)

  • Produces entity classes, repository interfaces, DTOs
  • Generates database migrations or schemas
  • Creates documentation and test helpers

6. Post-process: Decorators and plugins modify generated output

  • Applies formatting (Prettier, Black, gofmt)
  • Adds cross-cutting concerns (logging, metrics)
  • Injects framework-specific annotations

7. Package: Output is assembled into a publishable artifact

  • Creates JAR, npm package, Python wheel, etc.
  • Includes metadata (pom.xml, package.json, setup.py)
  • Bundles documentation and resources

8. Publish: Artifact is pushed to configured repository

  • Maven Central, npm registry, PyPI, etc.
  • Validates credentials and permissions
  • Creates release tags and changelog

Generator Types

Built-in Generators

Shipped with Capacitor and referenced by name:

  • capacitor:java-jpa - JPA/Hibernate for Java
  • capacitor:typescript-prisma - Prisma for TypeScript
  • capacitor:typescript-typeorm - TypeORM for TypeScript
  • capacitor:python-sqlalchemy - SQLAlchemy for Python
  • capacitor:go-gorm - GORM for Go
  • capacitor:csharp-entity-framework - Entity Framework for C#
  • capacitor:swift-core-data - Core Data for Swift
  • capacitor:typescript-dynamodb - AWS SDK for DynamoDB
  • capacitor:typescript-mongoose - Mongoose for MongoDB

External Generators

Third-party generators installed from repositories:

{
  "plugins": {
    "repositories": [
      "https://plugins.capacitor-lang.dev",
      "https://repo.acme.com/capacitor-plugins"
    ],
    "generators": {
      "com.acme.capacitor:generator-django": "1.0.0",
      "org.capacitor:generator-graphql": "2.1.0",
      "io.micronaut:generator-micronaut-data": "1.5.0"
    }
  }
}

Local Generators

Proprietary or in-development generators:

{
  "plugins": {
    "local": [
      "./generators/custom-oracle-generator",
      "./generators/company-standards"
    ]
  }
}

Generator Decorators

Decorators wrap generators to add cross-cutting functionality without modifying the base generator. Multiple decorators can be chained using the decorator pattern.

Common Decorators

Lombok Decorator (Java):

{
  "generators": {
    "java-jpa-sdk": {
      "generator": "capacitor:java-jpa",
      "decorators": [
        {
          "decorator": "org.capacitor:decorator-lombok",
          "config": {
            "builder": true,
            "data": true,
            "slf4j": true
          }
        }
      ]
    }
  }
}

Adds Lombok annotations to generated entities:

@Data
@Builder
@Slf4j
@Entity
public class User {
    // Generated fields
}

MapStruct Decorator (Java):

{
  "decorator": "org.capacitor:decorator-mapstruct",
  "config": {
    "componentModel": "spring"
  }
}

Generates MapStruct mappers between entities and DTOs:

@Mapper(componentModel = "spring")
public interface UserMapper {
    UserProfile toProfile(User user);
    User fromCreateInput(CreateUserInput input);
}

OpenTelemetry Decorator:

{
  "decorator": "org.capacitor:decorator-opentelemetry",
  "config": {
    "traceRepositoryMethods": true,
    "metricsEnabled": true
  }
}

Adds distributed tracing and metrics to repository methods:

@Repository
public class UserRepository {
    @Traced(operation = "user.findById")
    @Metered(name = "user.repository.findById")
    public Optional<User> findById(UUID id) { ... }
}

Available Decorators

DecoratorTarget LanguagesPurpose
decorator-lombokJavaLombok annotations (@Data, @Builder, etc.)
decorator-mapstructJavaEntity-DTO mapping code generation
decorator-swaggerJava, TypeScript, C#OpenAPI/Swagger annotations
decorator-spring-securityJava@PreAuthorize annotations on repositories
decorator-opentelemetryAllDistributed tracing and metrics
decorator-loggingAllStructured logging injection
decorator-validationAllEnhanced validation annotations
decorator-i18nAllInternationalization support

Feature Support Negotiation

When a generator encounters a feature, it classifies support into one of four categories:

Support Levels

NATIVE (✅): Database natively supports this feature

Example: PostgreSQL enums
CREATE TYPE user_status AS ENUM ('ACTIVE', 'INACTIVE', 'SUSPENDED');

SHIMMED (🔧): Feature requires application-level shim code

Example: MongoDB enums (implemented via validation)
{
  validator: {
    $jsonSchema: {
      properties: {
        status: {
          enum: ["ACTIVE", "INACTIVE", "SUSPENDED"]
        }
      }
    }
  }
}
+ TypeScript enum type + SDK validation

PARTIAL (⚠️): Feature is partially supported with limitations

Example: Full-text search on MySQL (native but limited features vs PostgreSQL)

UNSUPPORTED (❌): Feature cannot be implemented (generation error)

Example: Views on DynamoDB → Generation error with migration suggestions

Compatibility Reports

The build system generates compatibility reports showing how each feature is implemented:

$ capacitor build --generator java-sdk

Compatibility Report for java-sdk (JPA + PostgreSQL 15):
────────────────────────────────────────────────────────
✅ NATIVE:  entities, shapes, enums, relationships, unique constraints,
            check constraints, sequences, views, materialized views,
            repositories, mixins, invariants, documentation

⚡ SHIMMED: soft deletes (JPA interceptor @SQLDelete),
           encryption (@pii - application-level encryption),
           audit trails (JPA @EntityListeners),
           multi-tenancy (Hibernate @Filter),
           fixtures (test helper classes)

⚠️ PARTIAL: full-text search (native tsvector, limited ranking options)

❌ UNSUPPORTED: none

Generated 47 files in ./src/main/java/com/acme/dal

Generator Configuration Options

Each generator accepts standard and custom options:

Standard Options (All Generators)

OptionTypeDescription
targetStringDatabase target to generate for
outputStringOutput directory path
serviceStringService name to generate (filters entities)
includeQueriesBooleanGenerate query methods
includeRepositoriesBooleanGenerate repository interfaces
includeDTOsBooleanGenerate input/output DTOs
includeTestsBooleanGenerate test helpers
includeFixturesBooleanGenerate test fixtures
includeDocumentationBooleanGenerate inline documentation

Language-Specific Options

Java/JPA:

{
  "options": {
    "package": "com.acme.data.users",
    "jakartaEE": true,
    "lombokAnnotations": true,
    "springBoot": {
      "enabled": true,
      "version": "3.2",
      "autoConfiguration": true
    }
  }
}

TypeScript/Prisma:

{
  "options": {
    "esm": true,
    "strictNullChecks": true,
    "zodValidation": true
  }
}

Python/SQLAlchemy:

{
  "options": {
    "package": "acme_data_users",
    "asyncSupport": true,
    "typeHints": true,
    "pydanticModels": true
  }
}

Best Practices for Generator Selection

  • Match Framework to Use Case: Choose JPA for enterprise Java, Prisma for TypeScript backends, SQLAlchemy for Python
  • Validate Compatibility: Check Framework Compatibility Matrix before selecting
  • Use Decorators Sparingly: Only add decorators for truly cross-cutting concerns
  • Test with Multiple Targets: Validate generated code compiles and tests pass
  • Version Dependencies: Pin generator versions for reproducible builds
  • Review Compatibility Reports: Understand which features are shimmed vs native
  • Custom Generators: Only build custom generators when built-in options are insufficient

Creating Custom Generators

Custom generators can be built by implementing the generator SPI. While the reference implementation is in Java, generators can be written in any language that can be invoked by the Capacitor CLI.

Minimal generator interface (conceptual):

interface CapacitorGenerator {
    String id();                                          // Generator ID
    Set<Language> targetLanguages();                      // Supported languages
    Set<DatabaseTarget> supportedDatabases();             // Supported databases

    void initialize(GeneratorConfig config);              // Configure generator
    ValidationResult validate(Model, Target);             // Validate compatibility
    GenerationResult generate(Model, Context);            // Generate code
}

Generator plugin structure:

my-generator/
├── generator.yaml           # Generator metadata
├── templates/               # Code generation templates
│   ├── entity.mustache
│   ├── repository.mustache
│   └── dto.mustache
├── src/                     # Generator implementation
│   └── generator.js         # Main generator logic
└── tests/                   # Generator tests
    └── generator.test.js

For detailed documentation on creating custom generators, see the Capacitor Generator SDK Documentation.


Generator Support Matrix

FeaturePostgreSQLMySQLMongoDBCassandraDynamoDBPrismaTypeORM
Core Features
Entities✅ Native✅ Native✅ Native✅ Native✅ Native✅ Native✅ Native
Shapes✅ Composite✅ JSON🔧 Embedded❌ Flatten🔧 Nested✅ Native✅ Embedded
Enums✅ Native✅ Native🔧 Validation❌ String🔧 Validation✅ Native✅ Native
Nullable Types✅ Native✅ Native✅ Native✅ Native✅ Native✅ Native✅ Native
Constraints
Unique✅ Native✅ Native✅ Native⚠️ Limited🔧 SDK✅ Native✅ Native
Default Values✅ Native✅ Native✅ Native✅ Native🔧 SDK✅ Native✅ Native
Check Constraints✅ Native✅ Native🔧 Validation❌ N/A🔧 SDK⚠️ Limited🔧 Shim
Length/Range✅ Native✅ Native🔧 Validation❌ N/A🔧 SDK⚠️ Limited🔧 Decorator
Relationships
One-to-One✅ FK✅ FK🔧 Reference🔧 Denormalize🔧 Reference✅ Native✅ Native
One-to-Many✅ FK✅ FK🔧 Reference🔧 Denormalize🔧 Reference✅ Native✅ Native
Many-to-Many✅ Join Table✅ Join Table🔧 Array🔧 Multi-Table🔧 GSI✅ Native✅ Native
Cascade Delete✅ Native✅ Native🔧 SDK❌ Manual🔧 Lambda✅ Native✅ Native
Access Patterns
Primary Key✅ Native✅ Native✅ Native✅ Native✅ Native✅ Native✅ Native
Global Index✅ B-Tree✅ B-Tree✅ Index✅ Index✅ GSI✅ Native✅ Native
Composite Index✅ Native✅ Native✅ Compound✅ Composite✅ Composite Key✅ Native✅ Native
Partial/Sparse Index✅ Native⚠️ Limited✅ Sparse❌ N/A❌ N/A⚠️ Limited⚠️ Limited
Full-Text Search✅ Native✅ Native✅ Text Index❌ N/A❌ External⚠️ DB-dependent⚠️ DB-dependent
Advanced Features
Views✅ Native✅ Native✅ Pipeline❌ N/A❌ N/A❌ N/A⚠️ Query
Materialized Views✅ Native🔧 Trigger🔧 Collection⚠️ Auto🔧 Stream+Lambda❌ N/A🔧 Shim
Computed Fields✅ Generated✅ Generated🔧 Virtual❌ N/A🔧 SDK⚠️ Limited✅ Virtual
Sequences✅ Native✅ Auto-Inc🔧 Counter❌ UUID🔧 Atomic✅ Native✅ Native
Soft Delete🔧 Column🔧 Column🔧 Field🔧 Flag🔧 Attribute✅ Native✅ Decorator
Audit Trails🔧 Trigger🔧 Trigger🔧 Change Stream❌ N/A✅ Streams🔧 Middleware🔧 Subscriber
Multi-Tenancy🔧 RLS🔧 Column🔧 Field🔧 Partition🔧 GSI🔧 Middleware🔧 Filter
Streams/CDC✅ Logical Rep✅ Binlog✅ Change Stream❌ N/A✅ Streams❌ N/A🔧 Subscriber
v0.2 Platform Features
Namespaces✅ Schema✅ Schema🔧 DB/Prefix🔧 Keyspace🔧 Prefix✅ Module✅ Module
Repositories✅ Spring Data✅ Spring Data✅ SDK Class🔧 SDK Class✅ SDK Class✅ Native✅ Native
Input DTOs✅ Records✅ Records✅ Interfaces✅ Interfaces✅ Interfaces✅ Native✅ Native
Output DTOs✅ Records✅ Records✅ Interfaces✅ Interfaces✅ Projection✅ Native✅ Native
Mixins✅ @MappedSuperclass✅ @MappedSuperclass🔧 Plugin🔧 Interface🔧 Interface✅ Native✅ Native
Invariants✅ Bean Validation + CHECK✅ Bean Validation + CHECK🔧 Zod + Schema🔧 SDK Only🔧 SDK Only✅ Validation✅ Validation
Fixtures✅ @Sql Scripts✅ @Sql Scripts🔧 JSON + Helpers🔧 CQL + Helpers🔧 JSON + BatchWrite✅ Seed Scripts✅ Seed Scripts
Documentation✅ Javadoc✅ Javadoc✅ JSDoc/TSDoc✅ JSDoc/TSDoc✅ TSDoc✅ TSDoc✅ TSDoc

Legend:

  • Native: Implemented using database/ORM native features
  • 🔧 Shim: Implemented via generated application code or additional infrastructure
  • ⚠️ Limited: Partial support with limitations
  • N/A: Cannot be implemented (generation error)

Documentation Generation

Capacitor generates comprehensive documentation from your schema definitions, including inline code documentation, standalone documentation sites, entity-relationship diagrams, and access pattern analysis.

Documentation Sources

Documentation is extracted from three sources in Capacitor schemas:

1. Triple-Slash Comments (///)

/// Represents a registered user in the system.
///
/// Users are created via the registration API and must have
/// a verified email before they can access premium features.
entity User {
    @identity
    /// System-generated unique identifier. Immutable after creation.
    userId: UUID,

    /// Unique username for the user. Must be 3-50 characters.
    @length(min: 3, max: 50)
    username: String
}

2. @documentation Trait

@documentation(
    title: "User Management System",
    description: "Core entities for user authentication and profile management",
    version: "2.1.0",
    author: "Platform Team",
    tags: ["authentication", "users", "core"]
)
entity User {
    // ...
}

3. @example Trait

@example(
    name: "Create Active User",
    code: """
    {
        "username": "alice",
        "email": "alice@example.com",
        "status": "ACTIVE"
    }
    """
)
input CreateUserInput for User {
    include: [username, email, status]
}

Output Formats

Configure documentation outputs in capacitor-build.json:

{
  "documentation": {
    "enabled": true,
    "outputs": [
      {
        "format": "html",
        "output": "./docs/html",
        "theme": "modern",
        "includeDiagrams": true
      },
      {
        "format": "markdown",
        "output": "./docs/markdown"
      },
      {
        "format": "inline",
        "styles": ["javadoc", "jsdoc", "tsdoc"]
      }
    ]
  }
}

Inline Code Documentation

Generates language-specific documentation comments in all generated code files.

Java (Javadoc)

/**
 * Represents a registered user in the system.
 *
 * <p>Users are created via the registration API and must have
 * a verified email before they can access premium features.</p>
 *
 * <h3>Database Mapping</h3>
 * <ul>
 *   <li>Table: users</li>
 *   <li>Primary Key: user_id (UUID)</li>
 *   <li>Indexes: username (unique), email (unique)</li>
 * </ul>
 *
 * <h3>Access Patterns</h3>
 * <ul>
 *   <li>Find by ID: O(1)</li>
 *   <li>Find by username: O(1)</li>
 *   <li>Find by email: O(1)</li>
 * </ul>
 *
 * @author Platform Team
 * @version 2.1.0
 * @see UserRepository
 * @see CreateUserInput
 * @since 1.0.0
 */
@Entity
@Table(name = "users")
public class User {
    /**
     * System-generated unique identifier. Immutable after creation.
     *
     * <p>This field serves as the primary key and cannot be changed
     * after the user is created.</p>
     */
    @Id
    @Column(name = "user_id", nullable = false, updatable = false)
    private UUID userId;
}

TypeScript/JavaScript (TSDoc/JSDoc)

/**
 * Represents a registered user in the system.
 *
 * Users are created via the registration API and must have
 * a verified email before they can access premium features.
 *
 * **Collection**: `users`
 * **Indexes**:
 * - Primary key: userId (unique)
 * - Unique index: username
 * - Unique index: email
 *
 * **Access Patterns**:
 * - Find by ID: O(1)
 * - Find by username: O(1) via UsernameIndex GSI
 * - Find by email: O(1) via EmailIndex GSI
 *
 * @author Platform Team
 * @version 2.1.0
 * @since 1.0.0
 */
export interface User {
  /**
   * System-generated unique identifier. Immutable after creation.
   *
   * @readonly
   */
  userId: string;

  /**
   * Unique username for the user. Must be 3-50 characters.
   *
   * @minLength 3
   * @maxLength 50
   */
  username: string;
}

Python (Docstrings)

class User(Base):
    """Represents a registered user in the system.

    Users are created via the registration API and must have
    a verified email before they can access premium features.

    Database Mapping:
        Table: users
        Primary Key: user_id (UUID)
        Indexes: username (unique), email (unique)

    Access Patterns:
        - Find by ID: O(1)
        - Find by username: O(1)
        - Find by email: O(1)

    Attributes:
        user_id (UUID): System-generated unique identifier. Immutable after creation.
        username (str): Unique username for the user. Must be 3-50 characters.
        email (str): User's email address. Must be valid and verified.
        status (UserStatus): Current status of the user account.

    Author:
        Platform Team

    Version:
        2.1.0
    """

    user_id: Mapped[UUID] = mapped_column(primary_key=True)
    username: Mapped[str] = mapped_column(String(50), unique=True)

C# (XML Documentation)

/// <summary>
/// Represents a registered user in the system.
///
/// Users are created via the registration API and must have
/// a verified email before they can access premium features.
/// </summary>
/// <remarks>
/// <para><strong>Database Mapping:</strong></para>
/// <list type="bullet">
///   <item>Table: users</item>
///   <item>Primary Key: UserId (Guid)</item>
///   <item>Indexes: Username (unique), Email (unique)</item>
/// </list>
/// <para><strong>Access Patterns:</strong></para>
/// <list type="bullet">
///   <item>Find by ID: O(1)</item>
///   <item>Find by username: O(1)</item>
///   <item>Find by email: O(1)</item>
/// </list>
/// </remarks>
/// <author>Platform Team</author>
/// <version>2.1.0</version>
[Table("users")]
public class User
{
    /// <summary>
    /// System-generated unique identifier. Immutable after creation.
    /// </summary>
    /// <value>A unique identifier that cannot be changed after creation.</value>
    [Key]
    [Column("user_id")]
    public Guid UserId { get; set; }
}

HTML Documentation Site

Generates a static documentation website with navigation, search, and diagrams.

Features:

  • Entity catalog with full details
  • Repository method documentation
  • DTO schemas with examples
  • Interactive ER diagrams
  • Access pattern analysis
  • Full-text search
  • Responsive design
  • Dark/light theme toggle

Example structure:

docs/html/
├── index.html
├── entities/
│   ├── User.html
│   ├── Order.html
│   └── Product.html
├── repositories/
│   ├── UserRepository.html
│   └── OrderRepository.html
├── dtos/
│   ├── CreateUserInput.html
│   └── UserSummary.html
├── diagrams/
│   ├── er-diagram.html
│   └── access-patterns.html
├── assets/
│   ├── styles.css
│   └── search.js
└── api/
    └── search-index.json

Themes: modern, minimal, corporate, github


Markdown Documentation

Generates markdown files suitable for GitHub/GitLab wikis or static site generators.

Example output (User.md):

# User

Represents a registered user in the system.

Users are created via the registration API and must have a verified email before they can access premium features.

## Database Schema

**Table**: `users`

| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| user_id | UUID | PRIMARY KEY | System-generated unique identifier |
| username | VARCHAR(50) | UNIQUE, NOT NULL | Unique username for the user |
| email | VARCHAR(255) | UNIQUE, NOT NULL | User's email address |
| status | user_status | NOT NULL | Current status of the user account |
| created_at | TIMESTAMP | NOT NULL | Record creation timestamp |
| updated_at | TIMESTAMP | NOT NULL | Last update timestamp |

## Indexes

- **Primary Key**: `user_id`
- **Unique Index**: `username`
- **Unique Index**: `email`

## Access Patterns

- **Find by ID**: O(1) via primary key
- **Find by username**: O(1) via unique index
- **Find by email**: O(1) via unique index
- **List active users**: O(n) with status filter

## Relationships

- **One-to-Many**: `User``Order` (via `user_id`)
- **One-to-Many**: `User``Session` (via `user_id`)

## Repository Methods

### CRUD Operations

- `User findById(UUID userId)`
- `List<User> findAll()`
- `User save(User user)`
- `void deleteById(UUID userId)`

### Custom Queries

- `Optional<User> findByUsername(String username)`
- `Optional<User> findByEmail(String email)`
- `List<User> findByStatus(UserStatus status)`

### Mutations

- `User deactivate(UUID userId)` - Set user status to INACTIVE

### Aggregations

- `Map<UserStatus, Long> countByStatus()` - Count users by status

## DTOs

### CreateUserInput

Input DTO for creating a new user.

```json
{
  "username": "alice",
  "email": "alice@example.com",
  "phone": "+14155551234"
}
```

### UserSummary

Output DTO for user list views.

```json
{
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "username": "alice",
  "email": "alice@example.com",
  "status": "ACTIVE",
  "createdAt": "2024-01-15T10:00:00Z"
}
```

## Version History

- **v2.1.0** - Added phone field and email verification
- **v2.0.0** - Migrated to UUID primary keys
- **v1.0.0** - Initial release

Entity-Relationship Diagrams

Generates ER diagrams in multiple formats.

Mermaid

erDiagram
    USER ||--o{ ORDER : places
    USER ||--o{ SESSION : has
    ORDER ||--|{ ORDER_ITEM : contains
    PRODUCT ||--o{ ORDER_ITEM : "included in"

    USER {
        uuid user_id PK
        string username UK
        string email UK
        enum status
        timestamp created_at
        timestamp updated_at
    }

    ORDER {
        uuid order_id PK
        uuid user_id FK
        enum status
        decimal total
        timestamp created_at
    }

    ORDER_ITEM {
        uuid item_id PK
        uuid order_id FK
        string product_sku FK
        int quantity
        decimal price
    }

    PRODUCT {
        string sku PK
        string name
        decimal price
        int inventory
    }

    SESSION {
        uuid session_id PK
        uuid user_id FK
        timestamp expires_at
    }

PlantUML

@startuml
entity "User" as user {
  * user_id : UUID <<PK>>
  --
  * username : String <<UK>>
  * email : String <<UK>>
  * status : UserStatus
  * created_at : Timestamp
  * updated_at : Timestamp
}

entity "Order" as order {
  * order_id : UUID <<PK>>
  --
  * user_id : UUID <<FK>>
  * status : OrderStatus
  * total : Decimal
  * created_at : Timestamp
}

entity "OrderItem" as order_item {
  * item_id : UUID <<PK>>
  --
  * order_id : UUID <<FK>>
  * product_sku : String <<FK>>
  * quantity : Integer
  * price : Decimal
}

entity "Product" as product {
  * sku : String <<PK>>
  --
  * name : String
  * price : Decimal
  * inventory : Integer
}

user ||--o{ order : "places"
order ||--|{ order_item : "contains"
product ||--o{ order_item : "included in"
@enduml

Configuration:

{
  "documentation": {
    "diagrams": {
      "formats": ["mermaid", "plantuml", "svg", "png"],
      "layout": "top-to-bottom",
      "includeAttributes": true,
      "includeCardinality": true,
      "groupByService": true
    }
  }
}

Access Pattern Documentation

Generates documentation analyzing how data is accessed across different query patterns.

Example (access-patterns.md):

# Access Patterns Analysis

## User Entity

### Primary Access Patterns

| Pattern | Frequency | Complexity | Index | Notes |
|---------|-----------|------------|-------|-------|
| Find by ID | High | O(1) | Primary Key | Most common lookup |
| Find by username | High | O(1) | username_idx | Login flow |
| Find by email | Medium | O(1) | email_idx | Password reset |
| List by status | Low | O(n) | Sequential Scan | Consider materialized view |

### Performance Recommendations

-**Optimal**: Primary key and unique index queries
- ⚠️ **Review**: Status filtering causes full table scan on large datasets
- 💡 **Suggestion**: Add composite index on (status, created_at) for common queries

## Order Entity

### Primary Access Patterns

| Pattern | Frequency | Complexity | Index | Notes |
|---------|-----------|------------|-------|-------|
| Find by ID | High | O(1) | Primary Key | Order detail page |
| Find by user | High | O(log n) | user_id_idx | User order history |
| Find by date range | Medium | O(n) | created_at_idx | Reports and analytics |
| Count by status | Low | O(n) | Aggregate query | Dashboard |

### Query Optimization

```sql
-- Optimized query using covering index
CREATE INDEX order_user_status_idx ON orders(user_id, status, created_at)
    INCLUDE (order_id, total);

-- Enables efficient user order history without table lookup
SELECT order_id, status, total, created_at
FROM orders
WHERE user_id = ? AND status = 'ACTIVE'
ORDER BY created_at DESC;

---

### Compatibility Matrices

Documents feature support across different database targets.

**Example** (`compatibility.md`):

```markdown
# Feature Compatibility Matrix

## User Entity - PostgreSQL vs MongoDB

| Feature | PostgreSQL | MongoDB | Migration Notes |
|---------|-----------|---------|-----------------|
| Enums | ✅ Native `user_status` type | 🔧 Validation + SDK | Add runtime validation |
| Unique constraints | ✅ Native UNIQUE | ✅ Unique index | Direct mapping |
| Check constraints | ✅ Native CHECK | 🔧 Schema validation | Some constraints SDK-only |
| Foreign keys | ✅ Native FK | 🔧 Reference + SDK | Add cascade delete logic |
| Transactions | ✅ ACID | ⚠️ Limited (replica sets only) | Review transaction boundaries |

## Cost Implications - DynamoDB

| Feature | Implementation | Cost Impact |
|---------|----------------|-------------|
| Unique username | Separate index table | +100% write cost |
| Find by email | GSI | +100% write cost, +storage |
| Count by status | Full table scan | Very high RCU usage |
| Aggregations | Scan or Stream+Lambda | High ongoing cost |

**Recommendation**: Consider PostgreSQL for complex query patterns. DynamoDB better suited for simple key-value access at massive scale.

Build Commands

Generate documentation using CLI:

# Generate all configured documentation
capacitor build --docs

# Generate specific format only
capacitor build --docs --format html
capacitor build --docs --format markdown

# Generate docs without building code
capacitor docs

# Serve HTML docs locally
capacitor docs --serve --port 8080

Output:

Generating documentation...

✅ Inline documentation (Javadoc, TSDoc, Docstrings)
   - 47 entities
   - 23 repositories
   - 65 DTOs

✅ HTML documentation site
   - Output: ./docs/html
   - Theme: modern
   - 3 ER diagrams generated

✅ Markdown documentation
   - Output: ./docs/markdown
   - 47 entity pages
   - 23 repository pages

✅ Access pattern analysis
   - 12 entities analyzed
   - 3 optimization recommendations

Documentation complete. View at: file://./docs/html/index.html

PostgreSQL Generator

Overview

PostgreSQL is Capacitor's reference implementation. The generator produces pure SQL DDL and leverages PostgreSQL's rich feature set to implement nearly all Capacitor features natively.

Core Features

Entities

Implementation: Standard CREATE TABLE

-- From: entity User { @identity userId: UUID, username: String }
CREATE TABLE users (
    user_id UUID PRIMARY KEY,
    username VARCHAR(255) NOT NULL
);

Shapes (Composite Types)

Implementation: PostgreSQL composite types or JSONB

-- From: shape Address { street: String, city: String }
CREATE TYPE address AS (
    street VARCHAR(255),
    city VARCHAR(255),
    state VARCHAR(2),
    zip_code VARCHAR(10)
);

CREATE TABLE warehouses (
    id UUID PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    location address NOT NULL
);

Alternative (JSONB for flexibility):

CREATE TABLE warehouses (
    id UUID PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    location JSONB NOT NULL
);

Enums

Implementation: Native CREATE TYPE ... AS ENUM

-- From: enum OrderStatus { PENDING, CONFIRMED, SHIPPED }
CREATE TYPE order_status AS ENUM ('PENDING', 'CONFIRMED', 'SHIPPED', 'DELIVERED');

CREATE TABLE orders (
    order_id UUID PRIMARY KEY,
    status order_status NOT NULL DEFAULT 'PENDING'
);

Linter: ✅ No warnings


Constraints

Unique Constraints

CREATE TABLE users (
    user_id UUID PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE
);

Check Constraints

CREATE TABLE products (
    sku VARCHAR(50) PRIMARY KEY,
    price NUMERIC(10,2) NOT NULL CHECK (price > 0),
    inventory INTEGER NOT NULL CHECK (inventory >= 0)
);

Default Values

CREATE TABLE products (
    sku VARCHAR(50) PRIMARY KEY,
    inventory INTEGER NOT NULL DEFAULT 0,
    is_active BOOLEAN NOT NULL DEFAULT true,
    created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

Linter: ✅ No warnings


Relationships

One-to-Many with Cascade

CREATE TABLE authors (
    author_id UUID PRIMARY KEY,
    name VARCHAR(255) NOT NULL
);

CREATE TABLE books (
    isbn VARCHAR(13) PRIMARY KEY,
    title VARCHAR(500) NOT NULL,
    author_id UUID NOT NULL,
    FOREIGN KEY (author_id)
        REFERENCES authors(author_id)
        ON DELETE CASCADE
);

Many-to-Many

-- Generated junction table
CREATE TABLE student_course_enrollments (
    student_id UUID NOT NULL,
    course_id VARCHAR(50) NOT NULL,
    enrolled_at TIMESTAMP NOT NULL DEFAULT NOW(),
    PRIMARY KEY (student_id, course_id),
    FOREIGN KEY (student_id) REFERENCES students(student_id) ON DELETE CASCADE,
    FOREIGN KEY (course_id) REFERENCES courses(course_id) ON DELETE CASCADE
);

Linter: ✅ No warnings


Views and Materialized Views

Views

-- From: view ActiveUsers
CREATE VIEW active_users AS
SELECT user_id, username, email, last_login
FROM users
WHERE status = 'active'
  AND last_login > NOW() - INTERVAL '30 days';

Materialized Views

-- From: @materialized(refresh: "incremental")
CREATE MATERIALIZED VIEW order_summary AS
SELECT
    customer_id,
    COUNT(order_id) as total_orders,
    SUM(amount) as revenue,
    AVG(amount) as avg_order_value
FROM orders
GROUP BY customer_id;

-- Create unique index for concurrent refresh
CREATE UNIQUE INDEX order_summary_customer_idx
    ON order_summary(customer_id);

-- Generated refresh function
CREATE OR REPLACE FUNCTION refresh_order_summary()
RETURNS void AS $$
BEGIN
    REFRESH MATERIALIZED VIEW CONCURRENTLY order_summary;
END;
$$ LANGUAGE plpgsql;

-- Scheduled via pg_cron or external scheduler
SELECT cron.schedule('refresh-order-summary', '0 * * * *',
    'CALL refresh_order_summary()');

Linter: ✅ No warnings


Computed Fields

CREATE TABLE orders (
    order_id UUID PRIMARY KEY,
    subtotal NUMERIC(10,2) NOT NULL,
    tax_rate NUMERIC(4,3) NOT NULL,

    -- Generated column (computed on read)
    total_with_tax NUMERIC(10,2) GENERATED ALWAYS AS
        (subtotal * (1 + tax_rate)) STORED
);

Linter: ✅ No warnings


Sequences

CREATE SEQUENCE invoice_number_seq
    START WITH 1000
    INCREMENT BY 1
    CACHE 20;

CREATE TABLE invoices (
    invoice_number INTEGER PRIMARY KEY
        DEFAULT nextval('invoice_number_seq'),
    customer_id UUID NOT NULL,
    amount NUMERIC(10,2) NOT NULL
);

Linter: ✅ No warnings


Advanced Features

CREATE TABLE articles (
    id UUID PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    search_vector tsvector GENERATED ALWAYS AS
        (to_tsvector('english', title || ' ' || content)) STORED
);

CREATE INDEX articles_search_idx ON articles USING GIN(search_vector);

Soft Delete

CREATE TABLE documents (
    doc_id UUID PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    deleted_at TIMESTAMP DEFAULT NULL
);

-- Generated view excludes soft-deleted
CREATE VIEW active_documents AS
SELECT * FROM documents WHERE deleted_at IS NULL;

Audit Trail via Trigger

CREATE TABLE audit_log (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    table_name VARCHAR(50) NOT NULL,
    record_id UUID NOT NULL,
    action VARCHAR(10) NOT NULL,
    old_data JSONB,
    new_data JSONB,
    changed_at TIMESTAMP NOT NULL DEFAULT NOW(),
    changed_by VARCHAR(255)
);

CREATE OR REPLACE FUNCTION audit_trigger_func()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO audit_log (table_name, record_id, action, old_data, new_data)
    VALUES (TG_TABLE_NAME, NEW.id, TG_OP, row_to_json(OLD), row_to_json(NEW));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER users_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION audit_trigger_func();

Linter: ✅ No warnings


Namespace Support

Implementation: Maps to PostgreSQL schema names

-- From: namespace com.acme.data.users
CREATE SCHEMA IF NOT EXISTS com_acme_data_users;

-- Entities created within the schema
CREATE TABLE com_acme_data_users.users (
    user_id UUID PRIMARY KEY,
    username VARCHAR(255) NOT NULL
);

-- Types created within the schema
CREATE TYPE com_acme_data_users.user_status AS ENUM ('ACTIVE', 'INACTIVE', 'SUSPENDED');

Alternative mapping (when schema isolation not needed):

-- Use namespace as table prefix
CREATE TABLE com_acme_data_users__users (
    user_id UUID PRIMARY KEY,
    username VARCHAR(255) NOT NULL
);

Generator Note: Configure via namespaceStrategy option:

  • schema (default): Each namespace becomes a PostgreSQL schema
  • prefix: Namespace becomes table name prefix (useful for shared hosting)

Repository Pattern

Implementation: Generates Spring Data JPA interfaces or similar ORM repository classes

// From: repository UserRepository for User { ... }
package com.acme.data.users;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.Modifying;
import java.util.UUID;
import java.util.Optional;
import java.util.Map;

@Repository
public interface UserRepository extends JpaRepository<User, UUID> {

    // Auto-generated CRUD methods from JpaRepository:
    // - save(User entity)
    // - findById(UUID id)
    // - findAll()
    // - deleteById(UUID id)
    // - count()

    // From: @query findByUsername(username: String): User?
    Optional<User> findByUsername(String username);

    // From: @mutation deactivate(userId: UUID): User
    @Modifying
    @Query("UPDATE User u SET u.status = 'INACTIVE' WHERE u.userId = :userId")
    User deactivate(UUID userId);

    // From: @aggregate countByStatus(): Map<UserStatus, Long>
    @Query("SELECT u.status as status, COUNT(u) as count FROM User u GROUP BY u.status")
    Map<UserStatus, Long> countByStatus();
}

Traits Support:

// From: repository UserRepository for User with @cached, @audited { ... }

@Repository
@Cacheable  // Generated from @cached trait
public interface UserRepository extends JpaRepository<User, UUID> {

    @Cacheable("users")
    Optional<User> findByUsername(String username);

    @CacheEvict(value = "users", allEntries = true)
    @Audited  // Generated from @audited trait - logs all mutations
    User save(User user);
}

Linter: ✅ No warnings


Input/Output DTOs

Implementation: Generates Java record classes or POJOs with MapStruct mappers

// From: input CreateUserInput for User { include: [username, email, phone] }
package com.acme.data.users.dto;

import jakarta.validation.constraints.*;

public record CreateUserInput(
    @NotBlank @Size(min = 3, max = 50) String username,
    @NotBlank @Email String email,
    @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$") String phone
) {}

// From: output UserSummary from User { include: [userId, username, email, status, createdAt] }
public record UserSummary(
    UUID userId,
    String username,
    String email,
    UserStatus status,
    Instant createdAt
) {}

// MapStruct mapper for conversion
@Mapper(componentModel = "spring")
public interface UserMapper {

    User toEntity(CreateUserInput input);

    UserSummary toSummary(User entity);

    List<UserSummary> toSummaryList(List<User> entities);
}

With allOptional: true:

// From: input UpdateUserInput for User { include: [email, phone], allOptional: true }
public record UpdateUserInput(
    @Email Optional<String> email,
    @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$") Optional<String> phone
) {}

// Mapper handles optional fields
@Mapper(componentModel = "spring")
public interface UserMapper {

    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    void updateEntityFromInput(UpdateUserInput input, @MappingTarget User entity);
}

Linter: ✅ No warnings


Mixins

Implementation: JPA @MappedSuperclass or entity listeners for behavioral composition

// From: mixin Auditable { createdAt: Timestamp, createdBy: UUID?, ... }
package com.acme.data.common;

import jakarta.persistence.*;
import java.time.Instant;
import java.util.UUID;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Auditable {

    @Column(name = "created_at", nullable = false, updatable = false)
    @CreatedDate
    private Instant createdAt;

    @Column(name = "created_by", updatable = false)
    private UUID createdBy;

    @Column(name = "updated_at", nullable = false)
    @LastModifiedDate
    private Instant updatedAt;

    @Column(name = "updated_by")
    private UUID updatedBy;

    // Getters and setters...
}

// From: entity User with Auditable, SoftDeletable { ... }
@Entity
@Table(name = "users")
@SQLDelete(sql = "UPDATE users SET deleted_at = NOW() WHERE user_id = ?")
@Where(clause = "deleted_at IS NULL")
public class User extends Auditable implements SoftDeletable {

    @Id
    @Column(name = "user_id")
    private UUID userId;

    @Column(name = "username", nullable = false, unique = true)
    private String username;

    // Inherited from Auditable:
    // - createdAt, createdBy, updatedAt, updatedBy

    @Column(name = "deleted_at")
    private Instant deletedAt;  // From SoftDeletable mixin

    // Getters and setters...
}

Common generated mixins:

  • Auditable@MappedSuperclass with @CreatedDate, @LastModifiedDate
  • SoftDeletable@SQLDelete annotation + deletedAt field
  • Versioned@Version field for optimistic locking
  • TenantScoped@TenantId field with @Filter

Linter: ✅ No warnings


Invariants (Cross-Field Validation)

Implementation: Bean Validation custom validators + database CHECK constraints

// From: entity DateRange with @invariant(condition: "endDate > startDate", ...)
package com.acme.data.scheduling;

import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

// Custom validation annotation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateRangeValidator.class)
public @interface ValidDateRange {
    String message() default "End date must be after start date";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// Validator implementation
public class DateRangeValidator implements ConstraintValidator<ValidDateRange, DateRange> {

    @Override
    public boolean isValid(DateRange range, ConstraintValidatorContext context) {
        if (range.getStartDate() == null || range.getEndDate() == null) {
            return true;  // Null checks handled by @NotNull
        }
        return range.getEndDate().isAfter(range.getStartDate());
    }
}

// Entity with invariant
@Entity
@Table(name = "date_ranges")
@ValidDateRange
public class DateRange {

    @Id
    private UUID id;

    @NotNull
    @Column(name = "start_date", nullable = false)
    private LocalDate startDate;

    @NotNull
    @Column(name = "end_date", nullable = false)
    private LocalDate endDate;

    // Getters and setters...
}

Database-level enforcement:

-- Generated migration includes CHECK constraint
CREATE TABLE date_ranges (
    id UUID PRIMARY KEY,
    start_date DATE NOT NULL,
    end_date DATE NOT NULL,

    CONSTRAINT valid_date_range CHECK (end_date > start_date)
);

Linter: ✅ No warnings


Fixtures (Test Data)

Implementation: @Sql scripts or test helper classes

// From: fixture UserFixtures for User { activeUser: { ... }, inactiveUser: { ... } }
package com.acme.data.users;

import org.springframework.test.context.jdbc.Sql;

// SQL seed script approach
@Sql(scripts = "/test-data/user-fixtures.sql")
public class UserRepositoryTest {

    @Autowired
    private UserRepository repository;

    @Test
    void testFindActiveUser() {
        // activeUser fixture is loaded from SQL
        User user = repository.findByUsername("alice").orElseThrow();
        assertEquals(UserStatus.ACTIVE, user.getStatus());
    }
}

Generated SQL fixture (test-data/user-fixtures.sql):

-- From: fixture UserFixtures for User
INSERT INTO users (user_id, username, email, status, created_at, updated_at)
VALUES
    ('550e8400-e29b-41d4-a716-446655440000', 'alice', 'alice@example.com', 'ACTIVE', NOW(), NOW()),
    ('550e8400-e29b-41d4-a716-446655440001', 'bob', 'bob@example.com', 'INACTIVE', NOW(), NOW());

Alternative: Test data builder approach:

// Generated test helper class
public class UserFixtures {

    public static User activeUser() {
        return User.builder()
            .userId(UUID.fromString("550e8400-e29b-41d4-a716-446655440000"))
            .username("alice")
            .email("alice@example.com")
            .status(UserStatus.ACTIVE)
            .createdAt(Instant.now())
            .updatedAt(Instant.now())
            .build();
    }

    public static User inactiveUser() {
        return User.builder()
            .userId(UUID.fromString("550e8400-e29b-41d4-a716-446655440001"))
            .username("bob")
            .email("bob@example.com")
            .status(UserStatus.INACTIVE)
            .createdAt(Instant.now())
            .updatedAt(Instant.now())
            .build();
    }
}

Linter: ✅ No warnings


Documentation Generation

Implementation: Javadoc comments on all generated classes

// From: /// Represents a registered user in the system.
//       /// Users are created via the registration API...
//       entity User { ... }

/**
 * Represents a registered user in the system.
 *
 * <p>Users are created via the registration API and must have
 * a verified email before they can access premium features.</p>
 *
 * <p><strong>Access Patterns:</strong></p>
 * <ul>
 *   <li>Primary key: userId (UUID)</li>
 *   <li>Unique index: username</li>
 *   <li>Unique index: email</li>
 * </ul>
 *
 * @see UserRepository
 * @since 1.0.0
 */
@Entity
@Table(name = "users")
public class User extends Auditable {

    /**
     * System-generated unique identifier. Immutable after creation.
     *
     * <p>This field serves as the primary key and cannot be changed
     * after the user is created.</p>
     */
    @Id
    @Column(name = "user_id", nullable = false, updatable = false)
    private UUID userId;

    /**
     * Unique username for the user. Must be 3-50 characters.
     *
     * <p>Usernames are case-sensitive and must be unique across
     * the entire system.</p>
     */
    @Column(name = "username", nullable = false, unique = true, length = 50)
    @Size(min = 3, max = 50)
    private String username;

    // Additional fields with documentation...
}

Repository documentation:

/**
 * Repository interface for {@link User} entity operations.
 *
 * <p>Provides CRUD operations and custom queries for user management.
 * All methods are automatically audited and cached where appropriate.</p>
 *
 * @see User
 * @see UserMapper
 */
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {

    /**
     * Find a user by their username.
     *
     * @param username the username to search for (case-sensitive)
     * @return an Optional containing the user if found, or empty if not found
     */
    Optional<User> findByUsername(String username);
}

Linter: ✅ No warnings


MongoDB Generator

Overview

MongoDB generator produces schema validation rules and generates application-layer shims for features not natively supported.

Core Features

Entities

Implementation: Collection with JSON schema validation

// From: entity User
db.createCollection("users", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["userId", "username", "email"],
      properties: {
        userId: { bsonType: "string" },
        username: { bsonType: "string" },
        email: { bsonType: "string" }
      }
    }
  }
});

// Unique indexes
db.users.createIndex({ userId: 1 }, { unique: true });
db.users.createIndex({ username: 1 }, { unique: true });
db.users.createIndex({ email: 1 }, { unique: true });

Enums

Implementation: Schema validation + SDK types

// MongoDB schema validation
db.createCollection("orders", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["orderId", "status"],
      properties: {
        orderId: { bsonType: "string" },
        status: {
          enum: ["PENDING", "CONFIRMED", "SHIPPED", "DELIVERED"],
          description: "Must be a valid order status"
        }
      }
    }
  }
});

// Generated TypeScript SDK
export enum OrderStatus {
  PENDING = "PENDING",
  CONFIRMED = "CONFIRMED",
  SHIPPED = "SHIPPED",
  DELIVERED = "DELIVERED"
}

// Runtime validation in SDK
class OrderRepository {
  async create(order: CreateOrderInput) {
    // Validate enum
    if (!Object.values(OrderStatus).includes(order.status)) {
      throw new ValidationError(`Invalid status: ${order.status}`);
    }
    return await db.collection("orders").insertOne(order);
  }
}

Linter: ℹ️ Info

[INFO] Enums enforced via schema validation and SDK types.
       Direct database writes bypass SDK validation.

Constraints

Check Constraints (Shimmed)

// Schema validation
db.createCollection("products", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      properties: {
        price: {
          bsonType: "double",
          minimum: 0,
          description: "Price must be positive"
        },
        inventory: {
          bsonType: "int",
          minimum: 0
        }
      }
    }
  }
});

// SDK enforces checks
class ProductRepository {
  async create(product: CreateProductInput) {
    if (product.price <= 0) {
      throw new ValidationError("Price must be positive");
    }
    if (product.inventory < 0) {
      throw new ValidationError("Inventory cannot be negative");
    }
    return await db.collection("products").insertOne(product);
  }
}

Linter: ⚠️ Warning

[WARN] Check constraints enforced via schema validation and SDK.
       Direct database access may bypass these checks.
       Recommendation: Use generated SDK exclusively.

Relationships

One-to-Many (Reference Pattern)

// Authors collection
db.authors.insertOne({
  _id: new ObjectId("..."),
  authorId: "uuid-123",
  name: "Author Name"
});

// Books collection with reference
db.books.insertOne({
  _id: new ObjectId("..."),
  isbn: "123-456",
  title: "Book Title",
  authorId: "uuid-123"  // Reference to author
});

// SDK provides join helper
class BookRepository {
  async findWithAuthor(isbn: string) {
    const book = await db.collection("books").findOne({ isbn });
    if (!book) return null;

    const author = await db.collection("authors")
      .findOne({ authorId: book.authorId });

    return { ...book, author };
  }
}

Cascade Delete (Shimmed)

// Generated cascade delete logic
class UserRepository {
  async delete(userId: string) {
    const session = client.startSession();

    try {
      await session.withTransaction(async () => {
        // Delete related orders
        await db.collection("orders").deleteMany(
          { userId },
          { session }
        );

        // Delete user
        await db.collection("users").deleteOne(
          { userId },
          { session }
        );
      });
    } finally {
      await session.endSession();
    }
  }
}

Linter: ⚠️ Warning

[WARN] Referential integrity enforced in application code.
       Direct database access bypasses these checks.
       Use transactions to ensure consistency.

Views

Regular Views

Implementation: Aggregation pipeline views

// From: view ActiveUsers
db.createView(
  "activeUsers",
  "users",
  [
    {
      $match: {
        status: "active",
        lastLogin: { $gt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }
      }
    },
    {
      $project: {
        userId: 1,
        username: 1,
        email: 1,
        lastLogin: 1
      }
    }
  ]
);

Linter: ⚠️ Warning

[WARN] MongoDB views are read-only aggregation pipelines.
       Writes must go to the source collection.
       Views are computed on query (not cached).

Materialized Views (Shimmed)

Implementation: Separate collection + scheduled aggregation

// Create collection for materialized data
db.createCollection("orderSummary");
db.orderSummary.createIndex({ customerId: 1 }, { unique: true });

// Generated refresh function
async function refreshOrderSummary() {
  const pipeline = [
    {
      $group: {
        _id: "$customerId",
        totalOrders: { $sum: 1 },
        revenue: { $sum: "$amount" },
        avgOrderValue: { $avg: "$amount" }
      }
    },
    {
      $project: {
        customerId: "$_id",
        totalOrders: 1,
        revenue: 1,
        avgOrderValue: 1
      }
    },
    { $out: "orderSummary" }
  ];

  await db.collection("orders").aggregate(pipeline).toArray();
  console.log("Order summary refreshed");
}

// Scheduler (using node-cron or similar)
import cron from 'node-cron';
cron.schedule('0 * * * *', refreshOrderSummary);  // Hourly

// For on_change refresh, use Change Streams
const changeStream = db.collection("orders").watch();
changeStream.on("change", (change) => {
  if (["insert", "update", "delete"].includes(change.operationType)) {
    refreshOrderSummary();  // Or use debouncing
  }
});

Linter: ⚠️ Warning

[WARN] Materialized views require manual refresh orchestration.
       Generated code includes:
       - Separate collection for materialized data
       - Aggregation pipeline for refresh
       - Scheduler integration (requires Node.js runtime)

       Performance: Full refresh can be expensive for large datasets.
       Consider: Incremental updates using Change Streams for real-time needs.

Advanced Features

Full-Text Search

// Create text index
db.articles.createIndex(
  { title: "text", content: "text" },
  {
    weights: { title: 10, content: 1 },
    name: "article_text_search"
  }
);

// Generated search method
class ArticleRepository {
  async search(query: string, options?: SearchOptions) {
    return await db.collection("articles").find(
      { $text: { $search: query } },
      {
        score: { $meta: "textScore" },
        ...options
      }
    ).sort({ score: { $meta: "textScore" } }).toArray();
  }
}

Linter: ✅ No warnings

Change Data Capture

// From: stream UserChanges
const changeStream = db.collection("users").watch([
  {
    $match: {
      operationType: { $in: ["insert", "update", "delete"] }
    }
  }
]);

changeStream.on("change", async (change) => {
  // Send to external system (Kafka, EventBridge, etc.)
  await kafka.send({
    topic: "user-events",
    messages: [{
      key: change.documentKey._id.toString(),
      value: JSON.stringify(change)
    }]
  });
});

Linter: ✅ No warnings


Namespace Support

Implementation: Maps to database name or collection prefix

// From: namespace com.acme.data.users

// Option 1: Separate database per namespace
use com_acme_data_users;

db.createCollection("users", {
  validator: { /* ... */ }
});

// Option 2: Collection prefix (single database)
use shared_database;

db.createCollection("com_acme_data_users__users", {
  validator: { /* ... */ }
});

Generator Note: Configure via namespaceStrategy option:

  • database (default): Each namespace becomes a separate MongoDB database
  • prefix: Namespace becomes collection name prefix (useful for shared hosting/MongoDB Atlas free tier)

Linter: ℹ️ Info

[INFO] Namespace strategy affects connection strings and access control.
       Separate databases provide better isolation but require more connection management.

Repository Pattern

Implementation: Generates TypeScript/JavaScript classes with Mongoose or native driver

// From: repository UserRepository for User { ... }
import { Collection, Db, Filter, UpdateFilter } from 'mongodb';
import { User, UserStatus, CreateUserInput, UserSummary } from './types';

export class UserRepository {
  private collection: Collection<User>;

  constructor(db: Db) {
    this.collection = db.collection<User>('users');
  }

  // Auto-generated CRUD methods
  async create(input: CreateUserInput): Promise<User> {
    const user: User = {
      userId: crypto.randomUUID(),
      ...input,
      createdAt: new Date(),
      updatedAt: new Date()
    };

    await this.collection.insertOne(user);
    return user;
  }

  async findById(userId: string): Promise<User | null> {
    return await this.collection.findOne({ userId });
  }

  async findAll(): Promise<User[]> {
    return await this.collection.find({}).toArray();
  }

  async deleteById(userId: string): Promise<boolean> {
    const result = await this.collection.deleteOne({ userId });
    return result.deletedCount > 0;
  }

  // From: @query findByUsername(username: String): User?
  async findByUsername(username: string): Promise<User | null> {
    return await this.collection.findOne({ username });
  }

  // From: @mutation deactivate(userId: UUID): User
  async deactivate(userId: string): Promise<User> {
    const result = await this.collection.findOneAndUpdate(
      { userId },
      { $set: { status: UserStatus.INACTIVE, updatedAt: new Date() } },
      { returnDocument: 'after' }
    );

    if (!result.value) {
      throw new Error(`User not found: ${userId}`);
    }

    return result.value;
  }

  // From: @aggregate countByStatus(): Map<UserStatus, Long>
  async countByStatus(): Promise<Map<UserStatus, number>> {
    const pipeline = [
      {
        $group: {
          _id: '$status',
          count: { $sum: 1 }
        }
      }
    ];

    const results = await this.collection.aggregate(pipeline).toArray();
    return new Map(results.map(r => [r._id as UserStatus, r.count]));
  }
}

With Mongoose:

// Alternative: Mongoose-based repository
import mongoose, { Schema, Model } from 'mongoose';

const userSchema = new Schema<User>({
  userId: { type: String, required: true, unique: true },
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  status: { type: String, enum: Object.values(UserStatus), required: true }
}, { timestamps: true });

const UserModel: Model<User> = mongoose.model('User', userSchema);

export class UserRepository {
  async findByUsername(username: string): Promise<User | null> {
    return await UserModel.findOne({ username }).exec();
  }

  async deactivate(userId: string): Promise<User> {
    const user = await UserModel.findOneAndUpdate(
      { userId },
      { status: UserStatus.INACTIVE },
      { new: true }
    ).exec();

    if (!user) {
      throw new Error(`User not found: ${userId}`);
    }

    return user;
  }
}

Linter: ℹ️ Info

[INFO] Repository pattern generated as application-layer SDK.
       Choose native driver for lightweight apps, Mongoose for complex schemas.

Input/Output DTOs

Implementation: TypeScript interfaces or Zod schemas

// From: input CreateUserInput for User { include: [username, email, phone] }
import { z } from 'zod';

export const CreateUserInputSchema = z.object({
  username: z.string().min(3).max(50),
  email: z.string().email(),
  phone: z.string().regex(/^\+?[1-9]\d{1,14}$/)
});

export type CreateUserInput = z.infer<typeof CreateUserInputSchema>;

// From: output UserSummary from User { include: [userId, username, email, status, createdAt] }
export interface UserSummary {
  userId: string;
  username: string;
  email: string;
  status: UserStatus;
  createdAt: Date;
}

// Mapper functions
export class UserMapper {
  static toSummary(user: User): UserSummary {
    return {
      userId: user.userId,
      username: user.username,
      email: user.email,
      status: user.status,
      createdAt: user.createdAt
    };
  }

  static toSummaryList(users: User[]): UserSummary[] {
    return users.map(this.toSummary);
  }
}

With allOptional: true:

// From: input UpdateUserInput for User { include: [email, phone], allOptional: true }
export const UpdateUserInputSchema = z.object({
  email: z.string().email().optional(),
  phone: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional()
});

export type UpdateUserInput = z.infer<typeof UpdateUserInputSchema>;

// Repository method uses partial update
async update(userId: string, input: UpdateUserInput): Promise<User> {
  const update: any = {};
  if (input.email !== undefined) update.email = input.email;
  if (input.phone !== undefined) update.phone = input.phone;

  const result = await this.collection.findOneAndUpdate(
    { userId },
    { $set: { ...update, updatedAt: new Date() } },
    { returnDocument: 'after' }
  );

  if (!result.value) {
    throw new Error(`User not found: ${userId}`);
  }

  return result.value;
}

Linter: ✅ No warnings


Mixins

Implementation: TypeScript mixins or Mongoose plugins for behavioral composition

// From: mixin Auditable { createdAt: Timestamp, createdBy: UUID?, ... }

// TypeScript mixin interface
export interface Auditable {
  createdAt: Date;
  createdBy?: string;
  updatedAt: Date;
  updatedBy?: string;
}

// Mongoose plugin approach
import { Schema, Document } from 'mongoose';

export function auditablePlugin(schema: Schema) {
  schema.add({
    createdAt: { type: Date, required: true, default: Date.now },
    createdBy: { type: String },
    updatedAt: { type: Date, required: true, default: Date.now },
    updatedBy: { type: String }
  });

  schema.pre('save', function(next) {
    this.updatedAt = new Date();
    next();
  });
}

// From: entity User with Auditable, SoftDeletable { ... }
export interface User extends Auditable, SoftDeletable {
  userId: string;
  username: string;
  email: string;
  status: UserStatus;
}

const userSchema = new Schema<User>({
  userId: { type: String, required: true, unique: true },
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  status: { type: String, enum: Object.values(UserStatus), required: true }
});

// Apply mixin plugins
userSchema.plugin(auditablePlugin);
userSchema.plugin(softDeletablePlugin);

SoftDeletable mixin:

export interface SoftDeletable {
  deletedAt?: Date;
}

export function softDeletablePlugin(schema: Schema) {
  schema.add({
    deletedAt: { type: Date }
  });

  // Override find queries to exclude soft-deleted
  schema.pre(/^find/, function(next) {
    if (this.getOptions().includeSoftDeleted !== true) {
      this.where({ deletedAt: null });
    }
    next();
  });

  // Soft delete method
  schema.methods.softDelete = async function() {
    this.deletedAt = new Date();
    return await this.save();
  };
}

Linter: ✅ No warnings


Invariants (Cross-Field Validation)

Implementation: Schema validation + Zod runtime validation

// From: entity DateRange with @invariant(condition: "endDate > startDate", ...)

// Zod schema with custom refinement
export const DateRangeSchema = z.object({
  id: z.string().uuid(),
  startDate: z.date(),
  endDate: z.date()
}).refine(
  (data) => data.endDate > data.startDate,
  {
    message: "End date must be after start date",
    path: ["endDate"]
  }
);

export type DateRange = z.infer<typeof DateRangeSchema>;

// Repository validates before insertion
export class DateRangeRepository {
  async create(input: Omit<DateRange, 'id'>): Promise<DateRange> {
    const dateRange: DateRange = {
      id: crypto.randomUUID(),
      ...input
    };

    // Validate invariant
    const validated = DateRangeSchema.parse(dateRange);

    await this.collection.insertOne(validated);
    return validated;
  }
}

MongoDB schema validation:

// Also enforced at database level
db.createCollection("date_ranges", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["id", "startDate", "endDate"],
      properties: {
        id: { bsonType: "string" },
        startDate: { bsonType: "date" },
        endDate: { bsonType: "date" }
      }
    },
    $expr: {
      $gt: ["$endDate", "$startDate"]
    }
  }
});

Linter: ⚠️ Warning

[WARN] Invariants enforced via schema validation and SDK.
       Direct database writes may bypass validation.
       Use generated SDK for all write operations.

Fixtures (Test Data)

Implementation: JSON seed data files or test helper functions

// From: fixture UserFixtures for User { activeUser: { ... }, inactiveUser: { ... } }

// Generated seed data (fixtures/users.json)
export const UserFixtures = {
  activeUser: {
    userId: "550e8400-e29b-41d4-a716-446655440000",
    username: "alice",
    email: "alice@example.com",
    status: "ACTIVE",
    createdAt: new Date("2024-01-15T10:00:00Z"),
    updatedAt: new Date("2024-01-15T10:00:00Z")
  },
  inactiveUser: {
    userId: "550e8400-e29b-41d4-a716-446655440001",
    username: "bob",
    email: "bob@example.com",
    status: "INACTIVE",
    createdAt: new Date("2024-01-10T08:00:00Z"),
    updatedAt: new Date("2024-01-10T08:00:00Z")
  }
};

// Test helper to load fixtures
export async function loadUserFixtures(db: Db) {
  const collection = db.collection<User>('users');

  // Clear existing test data
  await collection.deleteMany({ userId: { $in: [
    UserFixtures.activeUser.userId,
    UserFixtures.inactiveUser.userId
  ]}});

  // Insert fixtures
  await collection.insertMany([
    UserFixtures.activeUser,
    UserFixtures.inactiveUser
  ]);
}

Usage in tests:

import { MongoClient } from 'mongodb';
import { loadUserFixtures, UserFixtures } from './fixtures';

describe('UserRepository', () => {
  let client: MongoClient;
  let repository: UserRepository;

  beforeEach(async () => {
    client = await MongoClient.connect(process.env.MONGO_URL!);
    await loadUserFixtures(client.db('test'));
    repository = new UserRepository(client.db('test'));
  });

  test('should find active user', async () => {
    const user = await repository.findByUsername('alice');
    expect(user).toBeDefined();
    expect(user!.status).toBe(UserStatus.ACTIVE);
  });

  afterEach(async () => {
    await client.close();
  });
});

Linter: ✅ No warnings


Documentation Generation

Implementation: JSDoc comments on all generated classes

// From: /// Represents a registered user in the system.
//       /// Users are created via the registration API...
//       entity User { ... }

/**
 * Represents a registered user in the system.
 *
 * Users are created via the registration API and must have
 * a verified email before they can access premium features.
 *
 * **Collection**: `users`
 * **Indexes**:
 * - Primary key: userId (unique)
 * - Unique index: username
 * - Unique index: email
 *
 * @since 1.0.0
 */
export interface User extends Auditable, SoftDeletable {
  /**
   * System-generated unique identifier. Immutable after creation.
   *
   * This field serves as the primary key and cannot be changed
   * after the user is created.
   */
  userId: string;

  /**
   * Unique username for the user. Must be 3-50 characters.
   *
   * Usernames are case-sensitive and must be unique across
   * the entire system.
   */
  username: string;

  /**
   * User's email address. Must be valid and verified.
   */
  email: string;

  /**
   * Current status of the user account.
   */
  status: UserStatus;
}

/**
 * Repository interface for User entity operations.
 *
 * Provides CRUD operations and custom queries for user management.
 * All write operations validate data against the schema and business rules.
 *
 * @see User
 * @see CreateUserInput
 * @see UserSummary
 */
export class UserRepository {
  /**
   * Find a user by their username.
   *
   * @param username - The username to search for (case-sensitive)
   * @returns The user if found, or null if not found
   */
  async findByUsername(username: string): Promise<User | null> {
    return await this.collection.findOne({ username });
  }
}

Linter: ✅ No warnings


DynamoDB Generator

Overview

DynamoDB generator produces CloudFormation/Terraform for tables and extensive application shims. Many relational concepts require complex workarounds.

Core Features

Entities

Implementation: DynamoDB table

# CloudFormation
Resources:
  UsersTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: users
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: userId
          AttributeType: S
      KeySchema:
        - AttributeName: userId
          KeyType: HASH

Enums (Shimmed)

Implementation: SDK validation only

// No database-level enum support
// Generated TypeScript SDK with validation
export enum OrderStatus {
  PENDING = "PENDING",
  CONFIRMED = "CONFIRMED",
  SHIPPED = "SHIPPED"
}

class OrderRepository {
  async create(order: CreateOrderInput) {
    // Validate enum before write
    if (!Object.values(OrderStatus).includes(order.status)) {
      throw new ValidationError(`Invalid status: ${order.status}`);
    }

    return await dynamodb.put({
      TableName: "orders",
      Item: marshall(order)
    });
  }
}

Linter: ⚠️ Warning

[WARN] Enums enforced in SDK only. No database-level validation.
       Direct AWS SDK/Console access bypasses validation.
       Recommendation: Use generated SDK exclusively.

Constraints

Unique Constraints (Complex)

Implementation: Conditional writes + separate index table

# Main table
UsersTable:
  Type: AWS::DynamoDB::Table
  Properties:
    TableName: users
    AttributeDefinitions:
      - AttributeName: userId
        AttributeType: S
    KeySchema:
      - AttributeName: userId
        KeyType: HASH

# Separate table for username uniqueness
UsernameIndexTable:
  Type: AWS::DynamoDB::Table
  Properties:
    TableName: users_username_index
    AttributeDefinitions:
      - AttributeName: username
        AttributeType: S
    KeySchema:
      - AttributeName: username
        KeyType: HASH
// Generated SDK enforces uniqueness
class UserRepository {
  async create(user: CreateUserInput) {
    const client = new DynamoDBClient({});

    try {
      // Atomic: Insert into uniqueness table first
      await client.send(new PutItemCommand({
        TableName: "users_username_index",
        Item: marshall({ username: user.username, userId: user.userId }),
        ConditionExpression: "attribute_not_exists(username)"
      }));

      // Insert main record
      await client.send(new PutItemCommand({
        TableName: "users",
        Item: marshall(user)
      }));

      return user;
    } catch (error) {
      if (error.name === "ConditionalCheckFailedException") {
        throw new UniqueConstraintViolation("Username already exists");
      }
      throw error;
    }
  }
}

Linter: ⚠️ Warning

[WARN] Unique constraints require separate index tables.
       Cost: Additional table + write capacity.
       Performance: Requires two writes per operation.
       Generated: Conditional write logic in SDK.

Relationships

One-to-Many (Reference with GSI)

OrdersTable:
  Type: AWS::DynamoDB::Table
  Properties:
    TableName: orders
    BillingMode: PAY_PER_REQUEST
    AttributeDefinitions:
      - AttributeName: orderId
        AttributeType: S
      - AttributeName: userId
        AttributeType: S
    KeySchema:
      - AttributeName: orderId
        KeyType: HASH
    GlobalSecondaryIndexes:
      - IndexName: UserOrdersIndex
        KeySchema:
          - AttributeName: userId
            KeyType: HASH
          - AttributeName: orderId
            KeyType: RANGE
        Projection:
          ProjectionType: ALL
// SDK provides query helper
class OrderRepository {
  async findByUser(userId: string) {
    return await dynamodb.query({
      TableName: "orders",
      IndexName: "UserOrdersIndex",
      KeyConditionExpression: "userId = :userId",
      ExpressionAttributeValues: {
        ":userId": { S: userId }
      }
    });
  }
}

Cascade Delete (Lambda Shim)

# DynamoDB Stream
UsersTable:
  Properties:
    StreamSpecification:
      StreamViewType: NEW_AND_OLD_IMAGES

# Lambda function for cascade
CascadeDeleteFunction:
  Type: AWS::Lambda::Function
  Properties:
    Handler: index.handler
    Runtime: nodejs18.x
    Code:
      ZipFile: |
        exports.handler = async (event) => {
          for (const record of event.Records) {
            if (record.eventName === 'REMOVE') {
              const userId = record.dynamodb.Keys.userId.S;

              // Delete related orders
              await deleteOrdersByUser(userId);
            }
          }
        };

EventSourceMapping:
  Type: AWS::Lambda::EventSourceMapping
  Properties:
    EventSourceArn: !GetAtt UsersTable.StreamArn
    FunctionName: !Ref CascadeDeleteFunction

Linter: ⚠️ Warning

[WARN] Cascade deletes require DynamoDB Streams + Lambda.
       Generated infrastructure:
       - DynamoDB Stream on parent table
       - Lambda function for delete logic
       - IAM roles and permissions

       Cost: Lambda invocations + Stream reads.
       Eventual consistency: Deletes are asynchronous.

Views (Not Supported)

Implementation: ❌ Cannot implement

Linter: ❌ Error

[ERROR] Views are not supported for DynamoDB targets.

        Alternatives:
        1. Create denormalized entity and use Streams to keep updated
        2. Query and filter in application code
        3. Use separate analytics database (e.g., Athena, Redshift)

        Example denormalization pattern:

        entity ActiveUserSnapshot {
            @identity userId: UUID,
            username: String,
            email: Email,
            @system snapshotTimestamp: Timestamp
        }

        stream SyncActiveUsers {
            source: User,
            destination: "dynamodb/table/active_user_snapshot",
            filter: "status == 'active'"
        }

Materialized Views (Complex Shim)

Implementation: Separate table + DynamoDB Streams + Lambda

# Materialized view table
OrderSummaryTable:
  Type: AWS::DynamoDB::Table
  Properties:
    TableName: order_summary
    AttributeDefinitions:
      - AttributeName: customerId
        AttributeType: S
    KeySchema:
      - AttributeName: customerId
        KeyType: HASH

# Lambda to update summary
UpdateSummaryFunction:
  Type: AWS::Lambda::Function
  Properties:
    Handler: index.handler
    Runtime: nodejs18.x
    Code:
      ZipFile: |
        exports.handler = async (event) => {
          for (const record of event.Records) {
            if (record.eventName === 'INSERT' || record.eventName === 'MODIFY') {
              const order = unmarshall(record.dynamodb.NewImage);

              // Atomic counter update
              await dynamodb.update({
                TableName: 'order_summary',
                Key: { customerId: order.customerId },
                UpdateExpression:
                  'ADD totalOrders :one, revenue :amount SET lastUpdated = :now',
                ExpressionAttributeValues: {
                  ':one': 1,
                  ':amount': order.amount,
                  ':now': Date.now()
                }
              });
            }
          }
        };

Linter: ⚠️ Warning

[WARN] Materialized views on DynamoDB require extensive infrastructure:

       Generated resources:
       - Separate DynamoDB table for aggregated data
       - DynamoDB Streams on source table(s)
       - Lambda function for aggregation logic
       - IAM roles and permissions
       - CloudWatch alarms for monitoring

       Cost implications:
       - Additional table storage
       - Stream read capacity
       - Lambda invocations (can be high for active tables)

       Performance:
       - Eventually consistent (stream processing delay)
       - Atomic counters prevent race conditions

       Alternative: Use DynamoDB + S3 + Athena for complex analytics.

Namespace Support

Implementation: Maps to table name prefix or CloudFormation stack names

# From: namespace com.acme.data.users

# Option 1: Table prefix
Resources:
  UsersTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: com-acme-data-users--users
      # ... rest of configuration

# Option 2: Separate CloudFormation stack per namespace
# Stack name: com-acme-data-users
Resources:
  UsersTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: users

Generator Note: Configure via namespaceStrategy option:

  • prefix (default): Namespace becomes table name prefix
  • stack: Each namespace deployed as separate CloudFormation stack (better isolation but more complex management)

Linter: ℹ️ Info

[INFO] Namespace strategy affects resource naming and IAM policies.
       Table prefixes work for most cases; separate stacks useful for team isolation.

Repository Pattern

Implementation: Generates TypeScript SDK with DynamoDB DocumentClient wrappers

// From: repository UserRepository for User { ... }
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
  DynamoDBDocumentClient,
  GetCommand,
  PutCommand,
  UpdateCommand,
  DeleteCommand,
  QueryCommand,
  ScanCommand
} from '@aws-sdk/lib-dynamodb';
import { User, UserStatus, CreateUserInput, UserSummary } from './types';

export class UserRepository {
  private readonly docClient: DynamoDBDocumentClient;
  private readonly tableName = 'users';

  constructor(client: DynamoDBClient) {
    this.docClient = DynamoDBDocumentClient.from(client);
  }

  // Auto-generated CRUD methods
  async create(input: CreateUserInput): Promise<User> {
    const user: User = {
      userId: crypto.randomUUID(),
      ...input,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };

    await this.docClient.send(new PutCommand({
      TableName: this.tableName,
      Item: user,
      ConditionExpression: 'attribute_not_exists(userId)'
    }));

    return user;
  }

  async findById(userId: string): Promise<User | null> {
    const result = await this.docClient.send(new GetCommand({
      TableName: this.tableName,
      Key: { userId }
    }));

    return result.Item as User || null;
  }

  async deleteById(userId: string): Promise<boolean> {
    await this.docClient.send(new DeleteCommand({
      TableName: this.tableName,
      Key: { userId }
    }));

    return true;
  }

  // From: @query findByUsername(username: String): User?
  // Requires GSI on username
  async findByUsername(username: string): Promise<User | null> {
    const result = await this.docClient.send(new QueryCommand({
      TableName: this.tableName,
      IndexName: 'UsernameIndex',
      KeyConditionExpression: 'username = :username',
      ExpressionAttributeValues: {
        ':username': username
      },
      Limit: 1
    }));

    return result.Items?.[0] as User || null;
  }

  // From: @mutation deactivate(userId: UUID): User
  async deactivate(userId: string): Promise<User> {
    const result = await this.docClient.send(new UpdateCommand({
      TableName: this.tableName,
      Key: { userId },
      UpdateExpression: 'SET #status = :inactive, updatedAt = :now',
      ExpressionAttributeNames: {
        '#status': 'status'
      },
      ExpressionAttributeValues: {
        ':inactive': UserStatus.INACTIVE,
        ':now': new Date().toISOString()
      },
      ReturnValues: 'ALL_NEW'
    }));

    if (!result.Attributes) {
      throw new Error(`User not found: ${userId}`);
    }

    return result.Attributes as User;
  }

  // From: @aggregate countByStatus(): Map<UserStatus, Long>
  // Requires full table scan - expensive!
  async countByStatus(): Promise<Map<UserStatus, number>> {
    const counts = new Map<UserStatus, number>();
    let lastKey: any = undefined;

    do {
      const result = await this.docClient.send(new ScanCommand({
        TableName: this.tableName,
        ProjectionExpression: '#status',
        ExpressionAttributeNames: {
          '#status': 'status'
        },
        ExclusiveStartKey: lastKey
      }));

      for (const item of result.Items || []) {
        const status = item.status as UserStatus;
        counts.set(status, (counts.get(status) || 0) + 1);
      }

      lastKey = result.LastEvaluatedKey;
    } while (lastKey);

    return counts;
  }
}

Required GSI for username query:

GlobalSecondaryIndexes:
  - IndexName: UsernameIndex
    KeySchema:
      - AttributeName: username
        KeyType: HASH
    Projection:
      ProjectionType: ALL

Linter: ⚠️ Warning

[WARN] Custom queries require GSIs or full table scans.
       Generated: GSI configurations for all @query methods with non-PK fields.
       Cost: Each GSI doubles write costs and adds storage costs.

       Aggregations use Scan operations - extremely expensive for large tables!
       Recommendation: Use DynamoDB Streams + Lambda + aggregate table.

Input/Output DTOs

Implementation: TypeScript interfaces with Zod validation

// From: input CreateUserInput for User { include: [username, email, phone] }
import { z } from 'zod';

export const CreateUserInputSchema = z.object({
  username: z.string().min(3).max(50),
  email: z.string().email(),
  phone: z.string().regex(/^\+?[1-9]\d{1,14}$/)
});

export type CreateUserInput = z.infer<typeof CreateUserInputSchema>;

// From: output UserSummary from User { include: [userId, username, email, status, createdAt] }
export interface UserSummary {
  userId: string;
  username: string;
  email: string;
  status: UserStatus;
  createdAt: string;  // ISO string
}

// Projection expression helper
export class UserMapper {
  static readonly SUMMARY_PROJECTION = 'userId, username, email, #status, createdAt';
  static readonly SUMMARY_ATTRIBUTE_NAMES = {
    '#status': 'status'  // 'status' is a reserved word in DynamoDB
  };

  static toSummary(user: User): UserSummary {
    return {
      userId: user.userId,
      username: user.username,
      email: user.email,
      status: user.status,
      createdAt: user.createdAt
    };
  }
}

// Repository uses projection for efficient queries
async findAllSummaries(): Promise<UserSummary[]> {
  const result = await this.docClient.send(new ScanCommand({
    TableName: this.tableName,
    ProjectionExpression: UserMapper.SUMMARY_PROJECTION,
    ExpressionAttributeNames: UserMapper.SUMMARY_ATTRIBUTE_NAMES
  }));

  return (result.Items || []).map(item => item as UserSummary);
}

Linter: ℹ️ Info

[INFO] DTOs generate ProjectionExpression helpers to minimize read costs.
       DynamoDB charges by data transferred, so projections reduce costs significantly.

Mixins

Implementation: TypeScript interfaces composed in entity types

// From: mixin Auditable { createdAt: Timestamp, createdBy: UUID?, ... }
export interface Auditable {
  createdAt: string;  // ISO 8601 timestamp
  createdBy?: string;
  updatedAt: string;
  updatedBy?: string;
}

// From: mixin SoftDeletable { deletedAt: Timestamp? }
export interface SoftDeletable {
  deletedAt?: string;
}

// From: entity User with Auditable, SoftDeletable { ... }
export interface User extends Auditable, SoftDeletable {
  userId: string;
  username: string;
  email: string;
  status: UserStatus;
}

// Repository helper for soft delete
export class UserRepository {
  async softDelete(userId: string): Promise<User> {
    const result = await this.docClient.send(new UpdateCommand({
      TableName: this.tableName,
      Key: { userId },
      UpdateExpression: 'SET deletedAt = :now',
      ExpressionAttributeValues: {
        ':now': new Date().toISOString()
      },
      ReturnValues: 'ALL_NEW'
    }));

    if (!result.Attributes) {
      throw new Error(`User not found: ${userId}`);
    }

    return result.Attributes as User;
  }

  // Exclude soft-deleted items from queries
  async findAllActive(): Promise<User[]> {
    const result = await this.docClient.send(new ScanCommand({
      TableName: this.tableName,
      FilterExpression: 'attribute_not_exists(deletedAt)'
    }));

    return result.Items as User[] || [];
  }
}

With Auditable auto-update:

// Intercept all updates to set updatedAt
async update(userId: string, updates: Partial<User>): Promise<User> {
  const updateExpression: string[] = [];
  const attributeValues: any = {};

  Object.entries(updates).forEach(([key, value], index) => {
    updateExpression.push(`#attr${index} = :val${index}`);
    attributeValues[`:val${index}`] = value;
  });

  // Always update timestamp
  updateExpression.push('updatedAt = :now');
  attributeValues[':now'] = new Date().toISOString();

  const result = await this.docClient.send(new UpdateCommand({
    TableName: this.tableName,
    Key: { userId },
    UpdateExpression: 'SET ' + updateExpression.join(', '),
    ExpressionAttributeValues: attributeValues,
    ReturnValues: 'ALL_NEW'
  }));

  return result.Attributes as User;
}

Linter: ✅ No warnings


Invariants (Cross-Field Validation)

Implementation: SDK-level validation only (no database enforcement)

// From: entity DateRange with @invariant(condition: "endDate > startDate", ...)

// Zod schema with custom refinement
export const DateRangeSchema = z.object({
  id: z.string().uuid(),
  startDate: z.string().datetime(),  // ISO 8601
  endDate: z.string().datetime()
}).refine(
  (data) => new Date(data.endDate) > new Date(data.startDate),
  {
    message: "End date must be after start date",
    path: ["endDate"]
  }
);

export type DateRange = z.infer<typeof DateRangeSchema>;

// Repository validates before write
export class DateRangeRepository {
  async create(input: Omit<DateRange, 'id'>): Promise<DateRange> {
    const dateRange: DateRange = {
      id: crypto.randomUUID(),
      ...input
    };

    // Validate invariant
    const validated = DateRangeSchema.parse(dateRange);

    await this.docClient.send(new PutCommand({
      TableName: this.tableName,
      Item: validated
    }));

    return validated;
  }
}

Linter: ⚠️ Warning

[WARN] DynamoDB has no database-level constraint support.
       Invariants enforced in SDK only.

       Direct AWS Console/SDK access bypasses ALL validation!
       Mitigation strategies:
       - IAM policies to restrict direct table access
       - DynamoDB Streams + Lambda for validation monitoring
       - Regular data validation jobs

       Recommendation: Use generated SDK exclusively.

Fixtures (Test Data)

Implementation: JSON seed data with batch write helpers

// From: fixture UserFixtures for User { activeUser: { ... }, inactiveUser: { ... } }

// Generated seed data
export const UserFixtures = {
  activeUser: {
    userId: "550e8400-e29b-41d4-a716-446655440000",
    username: "alice",
    email: "alice@example.com",
    status: UserStatus.ACTIVE,
    createdAt: "2024-01-15T10:00:00.000Z",
    updatedAt: "2024-01-15T10:00:00.000Z"
  },
  inactiveUser: {
    userId: "550e8400-e29b-41d4-a716-446655440001",
    username: "bob",
    email: "bob@example.com",
    status: UserStatus.INACTIVE,
    createdAt: "2024-01-10T08:00:00.000Z",
    updatedAt: "2024-01-10T08:00:00.000Z"
  }
};

// Test helper to load fixtures
export async function loadUserFixtures(docClient: DynamoDBDocumentClient) {
  const items = Object.values(UserFixtures);

  // Batch write (handles chunking for >25 items)
  for (let i = 0; i < items.length; i += 25) {
    const batch = items.slice(i, i + 25);

    await docClient.send(new BatchWriteCommand({
      RequestItems: {
        'users': batch.map(item => ({
          PutRequest: { Item: item }
        }))
      }
    }));
  }
}

// Cleanup helper
export async function clearUserFixtures(docClient: DynamoDBDocumentClient) {
  const keys = Object.values(UserFixtures).map(user => user.userId);

  for (let i = 0; i < keys.length; i += 25) {
    const batch = keys.slice(i, i + 25);

    await docClient.send(new BatchWriteCommand({
      RequestItems: {
        'users': batch.map(userId => ({
          DeleteRequest: { Key: { userId } }
        }))
      }
    }));
  }
}

Usage in tests:

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { loadUserFixtures, clearUserFixtures, UserFixtures } from './fixtures';

describe('UserRepository', () => {
  let docClient: DynamoDBDocumentClient;
  let repository: UserRepository;

  beforeEach(async () => {
    const client = new DynamoDBClient({
      endpoint: 'http://localhost:8000',  // DynamoDB Local
      region: 'us-east-1'
    });
    docClient = DynamoDBDocumentClient.from(client);

    await loadUserFixtures(docClient);
    repository = new UserRepository(client);
  });

  test('should find active user', async () => {
    const user = await repository.findById(UserFixtures.activeUser.userId);
    expect(user).toBeDefined();
    expect(user!.status).toBe(UserStatus.ACTIVE);
  });

  afterEach(async () => {
    await clearUserFixtures(docClient);
  });
});

Linter: ℹ️ Info

[INFO] Fixtures generate BatchWrite helpers for efficient test data loading.
       DynamoDB Local recommended for local testing (https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html).

Documentation Generation

Implementation: TSDoc comments on all generated classes

// From: /// Represents a registered user in the system.
//       /// Users are created via the registration API...
//       entity User { ... }

/**
 * Represents a registered user in the system.
 *
 * Users are created via the registration API and must have
 * a verified email before they can access premium features.
 *
 * **DynamoDB Table**: `users`
 * **Primary Key**: `userId` (HASH)
 * **Global Secondary Indexes**:
 * - `UsernameIndex`: username (HASH) - for username lookups
 * - `EmailIndex`: email (HASH) - for email lookups
 *
 * **Access Patterns**:
 * - Get user by ID: O(1) via primary key
 * - Find by username: O(1) via UsernameIndex GSI
 * - Find by email: O(1) via EmailIndex GSI
 * - List all users: O(n) Scan operation (expensive!)
 *
 * @since 1.0.0
 */
export interface User extends Auditable, SoftDeletable {
  /**
   * System-generated unique identifier. Immutable after creation.
   *
   * **Partition Key**: This field is the hash key for the DynamoDB table.
   */
  userId: string;

  /**
   * Unique username for the user. Must be 3-50 characters.
   *
   * **GSI**: Indexed via `UsernameIndex` for efficient lookups.
   */
  username: string;

  /**
   * User's email address. Must be valid and verified.
   *
   * **GSI**: Indexed via `EmailIndex` for efficient lookups.
   */
  email: string;

  /**
   * Current status of the user account.
   *
   * **Note**: DynamoDB has no enum enforcement - validation in SDK only.
   */
  status: UserStatus;
}

/**
 * Repository interface for User entity operations.
 *
 * Provides CRUD operations and custom queries for user management.
 * All write operations validate data against business rules.
 *
 * **Cost Considerations**:
 * - Primary key access: 1 RCU per 4KB
 * - GSI queries: Same as primary key access
 * - Scans: Very expensive, avoid for production queries
 *
 * **Best Practices**:
 * - Use primary key or GSI queries whenever possible
 * - Avoid Scan operations on large tables
 * - Use ProjectionExpression to minimize data transfer
 * - Enable DynamoDB Auto Scaling for production
 *
 * @see User
 * @see CreateUserInput
 * @see UserSummary
 */
export class UserRepository {
  /**
   * Find a user by their username.
   *
   * Uses the `UsernameIndex` GSI for efficient O(1) lookup.
   *
   * @param username - The username to search for (case-sensitive)
   * @returns The user if found, or null if not found
   * @throws {Error} If the query fails
   */
  async findByUsername(username: string): Promise<User | null> {
    // Implementation...
  }

  /**
   * Count users by status.
   *
   * **WARNING**: This operation performs a full table Scan and is
   * extremely expensive for large tables. Use sparingly or consider
   * maintaining counts in a separate aggregate table.
   *
   * @returns A map of status to count
   */
  async countByStatus(): Promise<Map<UserStatus, number>> {
    // Implementation...
  }
}

Linter: ✅ No warnings


Cassandra Generator

Overview

Cassandra generator targets Apache Cassandra and ScyllaDB. It focuses on partition-based data modeling and has limited support for relational features.

Core Features

Entities with Partition Keys

-- From: entity TimeSeriesData with partition and sort
CREATE TABLE sensor_data (
    device_id UUID,
    timestamp TIMESTAMP,
    value DOUBLE,
    PRIMARY KEY ((device_id), timestamp)
) WITH CLUSTERING ORDER BY (timestamp DESC);

Materialized Views (Native but Limited)

-- Cassandra has native materialized views
CREATE MATERIALIZED VIEW tasks_by_priority AS
    SELECT * FROM tasks
    WHERE project_id IS NOT NULL
      AND priority IS NOT NULL
      AND task_id IS NOT NULL
    PRIMARY KEY ((project_id), priority, task_id)
    WITH CLUSTERING ORDER BY (priority DESC);

Linter: ⚠️ Warning

[WARN] Cassandra materialized views have restrictions:
       - Can only add one non-PK column to the materialized view PK
       - All base table PK columns must be in materialized view PK
       - Automatic maintenance (cannot control refresh)
       - Performance impact on writes

       Recommendation: Carefully plan materialized view strategy.
       Alternative: Manual denormalization for complex cases.

Enums (Not Supported)

Implementation: Use strings with application validation

CREATE TABLE orders (
    order_id UUID PRIMARY KEY,
    status TEXT  -- Would be enum in Capacitor
);
// SDK enforces enum
export enum OrderStatus {
  PENDING = "PENDING",
  CONFIRMED = "CONFIRMED"
}

class OrderRepository {
  async create(order: CreateOrderInput) {
    if (!Object.values(OrderStatus).includes(order.status)) {
      throw new ValidationError(`Invalid status`);
    }
    // Insert with validated string
  }
}

Linter: ℹ️ Info

[INFO] Cassandra does not support enums.
       Implementation: TEXT column + SDK validation.

Prisma Generator

Overview

Prisma generator produces .prisma schema files that work with Prisma's own code generators. Support varies based on the underlying database.

Example Output

// Generated from Capacitor
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

enum OrderStatus {
  PENDING
  CONFIRMED
  SHIPPED
  DELIVERED
}

model User {
  id        String   @id @default(uuid())
  username  String   @unique
  email     String   @unique
  orders    Order[]
  createdAt DateTime @default(now())
}

model Order {
  id         String      @id @default(uuid())
  userId     String
  user       User        @relation(fields: [userId], references: [id], onDelete: Cascade)
  status     OrderStatus @default(PENDING)
  amount     Decimal
  createdAt  DateTime    @default(now())

  @@index([userId])
}

Limitations

Views (Not Supported)

Linter: ⚠️ Warning

[WARN] Prisma does not support views in schema.

       Workarounds:
       1. Use raw SQL queries: prisma.$queryRaw
       2. Create separate model and manually sync
       3. Use PostgreSQL generator if views are critical

       Example raw query:
       const activeUsers = await prisma.$queryRaw`
         SELECT * FROM active_users_view
       `;

Materialized Views (Not Supported)

Linter: ❌ Error

[ERROR] Materialized views not supported in Prisma.
        Use PostgreSQL generator for native materialized view support.

TypeORM Generator

Overview

TypeORM generator produces TypeScript entity decorators and works with multiple databases.

Example Output

// Generated from Capacitor
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  OneToMany,
  ManyToOne,
  Index,
  CreateDateColumn
} from 'typeorm';

export enum OrderStatus {
  PENDING = "PENDING",
  CONFIRMED = "CONFIRMED",
  SHIPPED = "SHIPPED",
  DELIVERED = "DELIVERED"
}

@Entity('users')
@Index(['username'], { unique: true })
@Index(['email'], { unique: true })
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  username: string;

  @Column({ unique: true })
  email: string;

  @OneToMany(() => Order, order => order.user)
  orders: Order[];

  @CreateDateColumn()
  createdAt: Date;
}

@Entity('orders')
@Index(['userId'])
export class Order {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  userId: string;

  @ManyToOne(() => User, user => user.orders, { onDelete: 'CASCADE' })
  user: User;

  @Column({
    type: 'enum',
    enum: OrderStatus,
    default: OrderStatus.PENDING
  })
  status: OrderStatus;

  @Column('decimal', { precision: 10, scale: 2 })
  amount: number;

  @CreateDateColumn()
  createdAt: Date;
}

Computed Fields

@Entity('orders')
export class Order {
  @Column('decimal')
  subtotal: number;

  @Column('decimal')
  taxRate: number;

  // Virtual property (not stored)
  @Column({ select: false, insert: false, update: false })
  get totalWithTax(): number {
    return this.subtotal * (1 + this.taxRate);
  }
}

Artifact Packaging and Publishing

Capacitor generates language-specific artifacts ready for publishing to package repositories. Each generator produces a complete, distributable package with proper dependency management, versioning, and metadata.

Semantic Versioning

All generated artifacts follow semantic versioning (SemVer):

service UserDataService {
    version: "2.1.0",
    entities: [User, UserPreferences, Session],
    repositories: [UserRepository, SessionRepository]
}

Version format: MAJOR.MINOR.PATCH

  • MAJOR: Breaking changes (incompatible API changes)
  • MINOR: New features (backward-compatible additions)
  • PATCH: Bug fixes (backward-compatible fixes)

Java (Maven/Gradle)

Directory Structure

target/
└── generated/
    └── java/
        ├── pom.xml                          # Maven build configuration
        ├── build.gradle                     # Gradle build configuration (alternative)
        └── src/
            └── main/
                ├── java/
                │   └── com/
                │       └── acme/
                │           └── data/
                │               └── users/
                │                   ├── entities/
                │                   │   ├── User.java
                │                   │   └── UserPreferences.java
                │                   ├── repositories/
                │                   │   ├── UserRepository.java
                │                   │   └── SessionRepository.java
                │                   ├── dto/
                │                   │   ├── CreateUserInput.java
                │                   │   ├── UpdateUserInput.java
                │                   │   └── UserSummary.java
                │                   ├── mappers/
                │                   │   └── UserMapper.java
                │                   └── config/
                │                       └── UserDataConfiguration.java
                └── resources/
                    ├── META-INF/
                    │   └── spring.factories         # Spring Boot auto-configuration
                    └── db/
                        └── migration/
                            ├── V1__initial_schema.sql
                            └── V2__add_user_preferences.sql

Maven Configuration (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.acme.data</groupId>
    <artifactId>user-data-service</artifactId>
    <version>2.1.0</version>
    <packaging>jar</packaging>

    <name>User Data Service</name>
    <description>Generated data access layer for user management</description>

    <properties>
        <java.version>17</java.version>
        <spring.boot.version>3.2.0</spring.boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.5.5.Final</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.30</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>1.5.5.Final</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Publishing to Maven Central

# Build artifact
capacitor build --generator java-jpa --package

# Generate POM with signing
mvn clean deploy -P release

# Or using Capacitor CLI
capacitor build --publish --repository maven-central

Generated artifact: user-data-service-2.1.0.jar


TypeScript/JavaScript (npm)

Directory Structure

target/
└── generated/
    └── typescript/
        ├── package.json                     # npm package configuration
        ├── tsconfig.json                    # TypeScript configuration
        ├── README.md                        # Generated documentation
        ├── src/
        │   ├── entities/
        │   │   ├── User.ts
        │   │   └── UserPreferences.ts
        │   ├── repositories/
        │   │   ├── UserRepository.ts
        │   │   └── SessionRepository.ts
        │   ├── dto/
        │   │   ├── CreateUserInput.ts
        │   │   ├── UpdateUserInput.ts
        │   │   └── UserSummary.ts
        │   ├── mappers/
        │   │   └── UserMapper.ts
        │   ├── types/
        │   │   └── index.ts                 # Re-exports all types
        │   └── index.ts                     # Main entry point
        ├── dist/                            # Compiled JavaScript (after build)
        │   ├── index.js
        │   ├── index.d.ts
        │   └── ...
        └── tests/
            └── fixtures/
                └── users.fixtures.ts

Package Configuration (package.json)

{
  "name": "@acme/user-data-service",
  "version": "2.1.0",
  "description": "Generated data access layer for user management",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist",
    "README.md"
  ],
  "scripts": {
    "build": "tsc",
    "test": "jest",
    "prepublishOnly": "npm run build"
  },
  "keywords": [
    "capacitor",
    "data-layer",
    "orm",
    "user-management"
  ],
  "author": "Platform Team",
  "license": "MIT",
  "dependencies": {
    "mongodb": "^6.3.0",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/node": "^20.10.0",
    "typescript": "^5.3.3",
    "jest": "^29.7.0"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

Publishing to npm

# Build artifact
capacitor build --generator typescript-sdk --package

# Publish to npm
npm publish --access public

# Or using Capacitor CLI
capacitor build --publish --repository npm

Generated artifact: @acme/user-data-service-2.1.0.tgz


Python (PyPI)

Directory Structure

target/
└── generated/
    └── python/
        ├── setup.py                         # Package setup
        ├── pyproject.toml                   # Modern Python packaging
        ├── README.md
        ├── MANIFEST.in
        ├── acme_user_data/
        │   ├── __init__.py
        │   ├── entities/
        │   │   ├── __init__.py
        │   │   ├── user.py
        │   │   └── user_preferences.py
        │   ├── repositories/
        │   │   ├── __init__.py
        │   │   ├── user_repository.py
        │   │   └── session_repository.py
        │   ├── dto/
        │   │   ├── __init__.py
        │   │   ├── create_user_input.py
        │   │   └── user_summary.py
        │   └── mappers/
        │       ├── __init__.py
        │       └── user_mapper.py
        └── tests/
            └── fixtures/
                └── users.py

Package Configuration (pyproject.toml)

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "acme-user-data-service"
version = "2.1.0"
description = "Generated data access layer for user management"
readme = "README.md"
authors = [
    { name = "Platform Team", email = "platform@acme.com" }
]
keywords = ["capacitor", "data-layer", "orm", "user-management"]
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Intended Audience :: Developers",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
]
requires-python = ">=3.10"
dependencies = [
    "sqlalchemy>=2.0.0",
    "pydantic>=2.5.0",
    "alembic>=1.13.0"
]

[project.optional-dependencies]
dev = [
    "pytest>=7.4.0",
    "black>=23.12.0",
    "mypy>=1.7.0"
]

[project.urls]
Homepage = "https://github.com/acme/user-data-service"
Documentation = "https://acme.github.io/user-data-service"

Publishing to PyPI

# Build artifact
capacitor build --generator python-sqlalchemy --package

# Build distribution
python -m build

# Publish to PyPI
twine upload dist/*

# Or using Capacitor CLI
capacitor build --publish --repository pypi

Generated artifacts:

  • acme_user_data_service-2.1.0-py3-none-any.whl
  • acme_user_data_service-2.1.0.tar.gz

Go (Go Modules)

Directory Structure

target/
└── generated/
    └── go/
        ├── go.mod                           # Go module definition
        ├── go.sum                           # Dependency checksums
        ├── README.md
        ├── entities/
        │   ├── user.go
        │   └── user_preferences.go
        ├── repositories/
        │   ├── user_repository.go
        │   └── session_repository.go
        ├── dto/
        │   ├── create_user_input.go
        │   └── user_summary.go
        └── fixtures/
            └── users.go

Module Configuration (go.mod)

module github.com/acme/user-data-service

go 1.21

require (
    github.com/google/uuid v1.5.0
    gorm.io/gorm v1.25.5
    gorm.io/driver/postgres v1.5.4
)

require (
    github.com/jackc/pgx/v5 v5.5.0 // indirect
    golang.org/x/crypto v0.17.0 // indirect
)

Publishing as Go Module

# Build artifact
capacitor build --generator go-gorm --package

# Tag version
git tag v2.1.0
git push origin v2.1.0

# Module is now available via:
# go get github.com/acme/user-data-service@v2.1.0

Module import: import "github.com/acme/user-data-service/repositories"


C# (NuGet)

Directory Structure

target/
└── generated/
    └── csharp/
        ├── Acme.UserDataService.csproj      # Project file
        ├── Acme.UserDataService.nuspec      # NuGet package spec
        ├── README.md
        ├── Entities/
        │   ├── User.cs
        │   └── UserPreferences.cs
        ├── Repositories/
        │   ├── IUserRepository.cs
        │   ├── UserRepository.cs
        │   └── SessionRepository.cs
        ├── Dto/
        │   ├── CreateUserInput.cs
        │   └── UserSummary.cs
        └── Mappers/
            └── UserMapper.cs

Project File (.csproj)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Version>2.1.0</Version>
    <PackageId>Acme.UserDataService</PackageId>
    <Authors>Platform Team</Authors>
    <Description>Generated data access layer for user management</Description>
    <PackageTags>capacitor;data-layer;orm;user-management</PackageTags>
    <RepositoryUrl>https://github.com/acme/user-data-service</RepositoryUrl>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
    <PackageReference Include="AutoMapper" Version="12.0.1" />
  </ItemGroup>
</Project>

Publishing to NuGet

# Build artifact
capacitor build --generator csharp-ef --package

# Pack NuGet package
dotnet pack --configuration Release

# Publish to NuGet.org
dotnet nuget push bin/Release/Acme.UserDataService.2.1.0.nupkg \
    --api-key YOUR_API_KEY \
    --source https://api.nuget.org/v3/index.json

# Or using Capacitor CLI
capacitor build --publish --repository nuget

Generated artifact: Acme.UserDataService.2.1.0.nupkg


Dependency Management

Generated artifacts declare their dependencies based on the generator and target database.

Example: Java JPA + PostgreSQL

Dependencies automatically included:

<dependencies>
    <!-- ORM Framework -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- Database Driver -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Validation -->
    <dependency>
        <groupId>jakarta.validation</groupId>
        <artifactId>jakarta.validation-api</artifactId>
    </dependency>

    <!-- Utilities -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

Example: TypeScript + MongoDB

Dependencies automatically included:

{
  "dependencies": {
    "mongodb": "^6.3.0",          // Database driver
    "zod": "^3.22.4"              // Runtime validation
  },
  "devDependencies": {
    "@types/node": "^20.10.0",
    "typescript": "^5.3.3"
  }
}

Build Configuration

Configure artifact generation in capacitor-build.json:

{
  "metadata": {
    "name": "UserDataService",
    "version": "2.1.0",
    "organization": "com.acme",
    "author": "Platform Team",
    "license": "MIT"
  },
  "generators": [
    {
      "id": "java-jpa",
      "enabled": true,
      "output": "./target/generated/java",
      "options": {
        "package": "com.acme.data.users",
        "springBoot": {
          "enabled": true,
          "version": "3.2"
        }
      }
    }
  ],
  "publishing": {
    "enabled": true,
    "repositories": [
      {
        "type": "maven-central",
        "credentials": "${env:MAVEN_CENTRAL_TOKEN}"
      }
    ],
    "releaseNotes": "auto"  // Extract from git commits
  }
}

Publishing Commands

# Package artifacts (no publish)
capacitor build --package

# Package and publish to all configured repositories
capacitor build --publish

# Publish to specific repository
capacitor build --publish --repository maven-central
capacitor build --publish --repository npm

# Dry run (verify without publishing)
capacitor build --publish --dry-run

# Publish with custom version (overrides capacitor-build.json)
capacitor build --publish --version 2.2.0-beta.1

Output:

Building artifacts...
✅ Java (Maven)
   - Artifact: user-data-service-2.1.0.jar
   - Location: target/generated/java/target/

✅ TypeScript (npm)
   - Artifact: @acme/user-data-service-2.1.0.tgz
   - Location: target/generated/typescript/

Publishing artifacts...
✅ Published to Maven Central
   - Group: com.acme.data
   - Artifact: user-data-service
   - Version: 2.1.0
   - URL: https://central.sonatype.com/artifact/com.acme.data/user-data-service/2.1.0

✅ Published to npm
   - Package: @acme/user-data-service
   - Version: 2.1.0
   - URL: https://www.npmjs.com/package/@acme/user-data-service/v/2.1.0

Complete! Artifacts published successfully.

Migration Between Targets

Capacitor can analyze migration paths between databases.

Example: PostgreSQL → MongoDB

capacitor migrate-analysis --from postgres --to mongodb

# Output:
Migration Analysis: PostgreSQL → MongoDB

✓ Compatible (no changes needed):
  - Entities: User, Order, Product (3)
  - Basic relationships (3)
  - Enums (2) - will use validation

⚠ Requires Shims:
  - Materialized view: OrderSummary
    Implementation: Collection + scheduled aggregation
    Performance: Full refresh every hour

  - Foreign key cascades: User → Orders
    Implementation: Application-level transaction
    Requires: MongoDB sessions

  - Check constraints: Product.price > 0
    Implementation: Schema validation + SDK

❌ Manual Changes Required:
  - View: ActiveUsers
    Action: Convert to denormalized collection or query in app

  - Full-text search on Article.content
    Action: Create text index (supported)

Estimated migration complexity: Medium
Recommended approach: Blue-green deployment with dual-write period

Example: DynamoDB → PostgreSQL

capacitor migrate-analysis --from dynamodb --to postgres

# Output:
Migration Analysis: DynamoDB → PostgreSQL

✓ Improvements (simpler in target):
  - Unique constraints: username, email
    Current: Separate index tables
    Target: Native UNIQUE constraint
    Benefit: Reduced complexity and cost

  - Referential integrity: User → Orders
    Current: Application-level with Streams + Lambda
    Target: Native FOREIGN KEY
    Benefit: Database-enforced, no Lambda costs

  - Materialized view: OrderSummary
    Current: Separate table + Stream + Lambda
    Target: Native MATERIALIZED VIEW
    Benefit: Simpler, no Lambda infrastructure

⚠ Data modeling changes:
  - Denormalized data in DynamoDB may need normalization
  - Review GSI usage - may become standard indexes

Estimated migration complexity: Low-Medium
Recommended approach: Export to S3, transform, import via COPY

Performance Considerations

PostgreSQL

  • Best for: ACID transactions, complex queries, referential integrity
  • Watch out for: Index bloat, vacuum performance on large tables

MongoDB

  • Best for: Flexible schemas, horizontal scaling, document-oriented data
  • Watch out for: Join performance (use denormalization), lack of transactions across collections in older versions

DynamoDB

  • Best for: Massive scale, predictable performance, serverless workloads
  • Watch out for: Cost at scale, complex query patterns, eventual consistency

Cassandra

  • Best for: Time-series data, write-heavy workloads, multi-datacenter
  • Watch out for: Limited query flexibility, no joins, eventual consistency

Further Reading