Examples of implementing Monk Entities using the new TypeScript-based approach with MonkEC compiler.
This repository contains examples of Monk entities implemented using TypeScript source code that gets compiled into YAML and JavaScript files. This approach provides:
- Type Safety: Full TypeScript support with interfaces and type checking
 - Better Developer Experience: IDE support, autocomplete, and error detection
 - Modular Architecture: Reusable base classes and shared utilities
 - Testing Framework: Built-in testing capabilities with functional tests
 - Compilation Pipeline: Automatic conversion from TypeScript to Monk-compatible YAML/JS
 - Module System: Reusable JavaScript modules with TypeScript definitions
 - HTTP Client: Built-in HTTP client for API interactions
 
# Build all default entity packages (monkec, mongodb-atlas, neon, ...)
./build.sh
# Build specific entity packages
./build.sh mongodb-atlas neon
# Load compiled entity package MANIFEST
cd dist/mongodb-atlas/
monk load MANIFEST# Test with automatic environment loading
sudo INPUT_DIR=./src/mongodb-atlas/ ./monkec.sh test
# Verbose output
sudo INPUT_DIR=./src/mongodb-atlas/ ./monkec.sh test --verbose
# Specific test file
sudo INPUT_DIR=./src/mongodb-atlas/ ./monkec.sh test --test-file test/stack-integration.test.yaml
# Watch mode
sudo INPUT_DIR=./src/mongodb-atlas/ ./monkec.sh test --watchsrc/
├── your-entity/
│   ├── base.ts              # Base class and common interfaces
│   ├── entity.ts            # Main entity implementation
│   ├── common.ts            # Shared utilities and constants
│   ├── README.md            # Entity documentation
│   └── test/
│       ├── README.md        # Testing instructions
│       ├── env.example      # Environment variables template
│       ├── stack-template.yaml      # Test stack configuration
│       └── stack-integration.test.yaml  # Functional test configuration
├── lib/
│   ├── modules/
│   │   ├── base.d.ts        # MonkEC base types
│   │   └── http-client.d.ts # HTTP client types
│   └── builtins/            # Built-in module types
└── monkec/                  # MonkEC compiler implementation
See also:
doc/new-entity-guide.md— end-to-end authoring/testing guidedoc/monk-cli.md— Monk CLI quick referencedoc/templates.md— Templates, stacks, and secretsdoc/testing.md— Test framework and patternsdoc/entity-conventions.md— Standard patterns for consistent entitiesdoc/scaffold.md— Canonical scaffold for new entitiesdoc/common-issues.md— Troubleshooting common problems
After creating src/<package>/:
- Update the build script defaults in 
build.shto include<package>in themodules=(...)list so./build.shcompiles it by default. - Update the root 
MANIFESTto includedist/<package>in theDIRSline somonk load MANIFESTpicks it up after compilation. - Then run:
INPUT_DIR=./src/<package>/ OUTPUT_DIR=./dist/<package>/ ./monkec.sh compilemonk load MANIFEST(from repo root) orcd dist/<package>/ && monk load MANIFEST
 
Create a base class that extends MonkEntity:
// src/your-entity/base.ts
import { MonkEntity } from "monkec/base";
import { HttpClient } from "monkec/http-client";
import cli from "cli";
export interface YourEntityDefinition {
    secret_ref: string;
    // Add your entity-specific properties
}
export interface YourEntityState {
    existing?: boolean;
    // Add your entity-specific state
}
export abstract class YourEntity<
    D extends YourEntityDefinition,
    S extends YourEntityState
> extends MonkEntity<D, S> {
    
    protected apiKey!: string;
    protected httpClient!: HttpClient;
    protected override before(): void {
        // Initialize authentication and HTTP client
        this.apiKey = secret.get(this.definition.secret_ref);
        if (!this.apiKey) {
            throw new Error(`Failed to retrieve API key from secret: ${this.definition.secret_ref}`);
        }
        this.httpClient = new HttpClient({
            baseUrl: "https://api.yourservice.com",
            headers: {
                "Authorization": `Bearer ${this.apiKey}`,
                "Accept": "application/json",
                "Content-Type": "application/json",
            },
            parseJson: true,
            stringifyJson: true,
            timeout: 10000,
        });
    }
    protected abstract getEntityName(): string;
    protected makeRequest(method: string, path: string, body?: any): any {
        try {
            const response = this.httpClient.request(method as any, path, { body });
            
            if (!response.ok) {
                throw new Error(`API error: ${response.statusCode} ${response.status} - ${response.data}`);
            }
            
            return response.data;
        } catch (error) {
            throw new Error(`${method} request to ${path} failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
    }
    protected checkResourceExists(path: string): any | null {
        try {
            return this.makeRequest("GET", path);
        } catch (error) {
            return null;
        }
    }
    protected deleteResource(path: string, resourceName: string): void {
        if (this.state.existing) {
            cli.output(`${resourceName} wasn't created by this entity, skipping delete`);
            return;
        }
        try {
            this.makeRequest("DELETE", path);
            cli.output(`Successfully deleted ${resourceName}`);
        } catch (error) {
            throw new Error(`Failed to delete ${resourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
    }
}Create your specific entity class:
// src/your-entity/entity.ts
import { YourEntity, YourEntityDefinition, YourEntityState } from "./base.ts";
import { action, Args } from "monkec/base";
import cli from "cli";
export interface SpecificEntityDefinition extends YourEntityDefinition {
    name: string;
    // Add specific properties
}
export interface SpecificEntityState extends YourEntityState {
    id?: string;
    name?: string;
    // Add specific state properties
}
export class SpecificEntity extends YourEntity<SpecificEntityDefinition, SpecificEntityState> {
    
    // Customize readiness check parameters
    static readonly readiness = { period: 10, initialDelay: 2, attempts: 20 };
    
    protected getEntityName(): string {
        return this.definition.name;
    }
    override create(): void {
        // Check if resource already exists
        const existing = this.checkResourceExists(`/resources/${this.definition.name}`);
        
        if (existing) {
            this.state = {
                id: existing.id,
                name: existing.name,
                existing: true
            };
            return;
        }
        // Create new resource
        const body = {
            name: this.definition.name,
            // Add other properties
        };
        
        const created = this.makeRequest("POST", "/resources", body);
        
        this.state = {
            id: created.id,
            name: created.name,
            existing: false
        };
    }
    override update(): void {
        if (!this.state.id) {
            this.create();
            return;
        }
        // Update logic here
        const body = {
            name: this.definition.name,
            // Add update properties
        };
        
        this.makeRequest("PUT", `/resources/${this.state.id}`, body);
    }
    override delete(): void {
        if (!this.state.id) {
            cli.output("Resource does not exist, nothing to delete");
            return;
        }
        
        this.deleteResource(`/resources/${this.state.id}`, "Resource");
    }
    override checkReadiness(): boolean {
        if (!this.state.id) {
            return false;
        }
        try {
            const resource = this.makeRequest("GET", `/resources/${this.state.id}`);
            return resource.status === "ready";
        } catch (error) {
            return false;
        }
    }
    // Custom actions using @action decorator
    @action("backup")
    backup(args?: Args): void {
        cli.output(`Backing up resource: ${this.definition.name}`);
        
        const backupResponse = this.makeRequest("POST", `/resources/${this.state.id}/backup`);
        cli.output(`Backup created: ${backupResponse.backupId}`);
    }
    @action("restore")
    restore(args?: Args): void {
        const backupId = args?.backupId;
        if (!backupId) {
            throw new Error("backupId argument is required");
        }
        cli.output(`Restoring resource from backup: ${backupId}`);
        
        this.makeRequest("POST", `/resources/${this.state.id}/restore`, {
            backupId: backupId
        });
    }
}Create comprehensive tests using the MonkEC testing framework:
# src/your-entity/test/stack-template.yaml
namespace: your-entity-test
test-resource:
  defines: your-entity/specific-entity
  secret_ref: your-service-token
  name: test-resource-123
  permitted-secrets:
    your-service-token: true
  services:
    data:
      protocol: custom# src/your-entity/test/stack-integration.test.yaml
name: Your Entity Integration Test
description: Complete integration test for Your Entity
timeout: 300000
secrets:
  global:
    your-service-token: "$YOUR_SERVICE_TOKEN"
    your-dev-password: "dev-secure-password-123"
setup:
  - name: Load compiled entity
    action: load
    target: dist/your-entity/MANIFEST
    expect:
      exitCode: 0
  - name: Load entity template
    action: load
    target: test/stack-template.yaml
    expect:
      exitCode: 0
tests:
  - name: Create and start entity
    action: run
    target: your-entity-test/test-resource
    expect:
      exitCode: 0
      output:
        - "Started your-entity-test/test-resource"
  - name: Wait for entity to be ready
    action: wait
    target: your-entity-test/test-resource
    waitFor:
      condition: ready
      timeout: 60000
  - name: Test custom action
    action: action
    target: your-entity-test/test-resource
    actionName: backup
    expect:
      exitCode: 0
      output:
        - "Backing up resource"
  - name: Test action with arguments
    action: action
    target: your-entity-test/test-resource
    actionName: restore
    args:
      backupId: "backup-123"
    expect:
      exitCode: 0
      output:
        - "Restoring resource from backup"
cleanup:
  - name: Delete entity
    action: delete
    target: your-entity-test/test-resource
    expect:
      exitCode: 0Create environment template with automatic loading:
# src/your-entity/test/env.example
# Required: Your Service API Token
YOUR_SERVICE_TOKEN=your-actual-api-token-here
# Optional: Test configuration
MONKEC_VERBOSE=true
TEST_TIMEOUT=300000The testing framework automatically loads .env files from the test directory.
# 1. Make changes to TypeScript source
vim src/your-entity/entity.ts
# 2. Compile the entity
./build.sh your-entity
# 3. Load the compiled entity
monk load dist/your-entity/MANIFEST
# 4. Test the entity
sudo INPUT_DIR=./src/your-entity/ ./monkec.sh test
# 5. Iterate and repeat- Type Safety: Use TypeScript interfaces for all definitions and state
 - Error Handling: Implement comprehensive error handling with try-catch blocks
 - Logging: Use 
cli.output()for user-friendly messages - Testing: Write functional tests for all entity operations
 - Environment Isolation: Use separate test environments with 
.envfiles - Resource Cleanup: Always clean up test resources
 - Watch Mode: Use 
--watchflag for rapid development iteration 
The MonkEC framework provides a powerful HTTP client for API interactions:
import { HttpClient } from "monkec/http-client";
import cli from "cli";
// Create client with configuration
const client = new HttpClient({
  baseUrl: "https://api.example.com",
  headers: {
    Authorization: "Bearer your-token",
    "Content-Type": "application/json",
  },
  timeout: 10000,
  parseJson: true,
  stringifyJson: true,
});
// Make requests
const response = client.get("/users/1");
if (response.ok) {
  cli.output("User: " + JSON.stringify(response.data));
}
// Error handling
if (!response.ok) {
  throw new Error(`Request failed: ${response.status}`);
}Create reusable modules for shared functionality:
namespace: my-app
http-client:
  defines: module
  source: |
    function get(url, options = {}) {
      const http = require('http');
      return http.get(url, options);
    }
    
    function post(url, data, options = {}) {
      const http = require('http');
      return http.post(url, { 
        body: JSON.stringify(data),
        ...options 
      });
    }
    
    module.exports = { get, post };
  types: |
    export interface HttpOptions {
      headers?: Record<string, string>;
      timeout?: number;
    }
    
    export interface HttpResponse {
      status: number;
      data: any;
      headers: Record<string, string>;
    }
    
    export function get(url: string, options?: HttpOptions): HttpResponse;
    export function post(url: string, data: any, options?: HttpOptions): HttpResponse;- Location: 
src/mongodb-atlas/ - Features: Project, cluster, and user management
 - Documentation: See 
src/mongodb-atlas/README.md - Testing: Comprehensive integration tests with stack and multi-instance scenarios
 
- Location: 
src/neon/ - Features: Project, branch, compute, and role management
 - Documentation: See 
src/neon/README.md - Testing: Full lifecycle testing with operation waiting
 
- Location: 
src/netlify/ - Features: Site, deployment, and form management
 - Documentation: See 
src/netlify/README.md - Testing: Complete integration tests with site, deploy, and form scenarios
 - API: Based on Netlify API documentation
 
- Location: 
src/monkec/ - Features: Base classes, HTTP client, and compilation tools
 - Documentation: See 
src/monkec/base.tsandsrc/monkec/http-client.ts 
- This repo uses TypeScript entities compiled by MonkEC; see 
doc/arrowscript.mdfor how ArrowScript relates and when to use it. 
- 
Compilation Errors
- Check TypeScript syntax and imports
 - Verify all required interfaces are defined
 - Ensure proper module paths in 
tsconfig.json 
 - 
Runtime Errors
- Verify API credentials and permissions
 - Check network connectivity to external APIs
 - Review entity state and definition validation
 
 - 
Test Failures
- Ensure environment variables are set correctly in 
.envfile - Check API rate limits and quotas
 - Verify test resources are properly cleaned up
 - Use 
--verboseflag for detailed debugging 
 - Ensure environment variables are set correctly in 
 - 
HTTP Client Issues
- Check response.ok before using data
 - Verify base URL and headers configuration
 - Set appropriate timeouts for your use case
 
 
# Enable verbose compilation
./build.sh your-entity
# Enable verbose testing
sudo MONKEC_VERBOSE=true INPUT_DIR=./src/your-entity/ ./monkec.sh test --verbose
# Check entity state
monk describe your-namespace/your-entity
# Watch mode for development
sudo INPUT_DIR=./src/your-entity/ ./monkec.sh test --watchWhen contributing new entities:
- Follow the established project structure
 - Implement comprehensive TypeScript interfaces
 - Add functional tests with proper cleanup
 - Document all features and usage examples
 - Update this README with new entity information
 - Use the module system for reusable functionality
 - Implement proper error handling and logging
 - Add readiness checks with appropriate timeouts
 
Use this minimal prompt in Cursor when adding a new entity package from external docs:
Build a new MonkEC entity package using the linked API docs and this repo’s conventions.
Inputs:
- Documentation URL(s)
- Entity package name
- Credential env var(s) (names and values)
References: doc/entity-conventions.md, doc/scaffold.md, doc/templates.md, doc/testing.md, doc/monkec.md, doc/monk-cli.md
Deliverables:
- Code in src/<package>/ following conventions (snake_case Definition/State, kebab-case actions, optional secret_ref with provider default, reserved-name avoidance)
- Tests in src/<package>/test (stack-template.yaml with depends/connections and a data service for providers, stack-integration.test.yaml, env.example; create .env if credentials provided)
- example.yaml and package README
Process:
- Compile: `INPUT_DIR=./src/<package>/ OUTPUT_DIR=./dist/<package>/ ./monkec.sh compile`
- Test: `sudo INPUT_DIR=./src/<package>/ ./monkec.sh test --verbose`
- In tests, MANIFEST path inside container is `dist/input/<package>/MANIFEST`
- Use `existing` state flag to mark resources discovered vs created; readiness should reflect existing resources appropriately
- Use `depends` + `connections` with `connection-target("service") entity-state get-member("field")`
- Map .env vars to provider default secret name(s) via test `secrets`
- Minimal follow-ups only if docs are ambiguous; otherwise iterate via tests
Output only the changed/added files with complete contents.
Example for Cloudflare:
Build a new MonkEC entity package using the linked API docs and this repo’s conventions.
Inputs:
- Documentation URL(s): https://developers.cloudflare.com/api/ (DNS management)
- Entity package name: cloudflare
- Credential env var(s) (names and values): api token <TOKEN>
...
See source code in subfolders and README.md for usage.
Use monk load MANIFEST to load all entity types at once.
You can see example.yaml in subfolders for example definitions.