Search Results for

    Show / Hide Table of Contents

    Designer Scripting

    Designer scripting allows you to automate modeling tasks in Intent Architect's designers through JavaScript-based scripts. Whether you need to create dozens of entities at once, enforce modeling conventions, or generate boilerplate structures, designer scripting can save significant time and ensure consistency across your models.

    There are two primary ways to use designer scripting:

    • Execute Script Dialog: Run ad-hoc scripts to perform one-time bulk operations or automate repetitive modeling tasks
    • Event Handlers: Create automated responses to modeling events (e.g., automatically configure new entities or associations as they're created)

    Both approaches share the same powerful API that gives you full programmatic access to create, modify, and configure elements in your designers. IntelliSense and inline documentation are available throughout the scripting environment to help you discover available functions and their usage.

    Getting Started with the Execute Script Dialog

    Intent Architect includes a scripting editor which can be launched by clicking the Execute Script Dialog button (</>) located in the designer toolbar. This editor allows you to write and execute JavaScript scripts directly within your designer environment.

    Code Docs in Code Completion Screenshot

    You can access detailed documentation by clicking on the small arrow beside each code construct in the IntelliSense dropdown to expand the respective documentation and learn more about the available functions.

    Your First Script

    Here's a simple example that creates a class with five attributes:

    let mainPackage = getPackages()[0];
    let newClass = createElement("Class", "Customer", mainPackage.id);
    
    const stringTypeId = "d384db9c-a279-45e1-801e-e4e8099625f2";
    
    for (let i = 1; i <= 5; i++) {
        let attr = createElement("Attribute", `Attribute${i}`, newClass.id);
        attr.typeReference.setType(stringTypeId);
    }
    

    This script demonstrates the core concepts:

    • getPackages() retrieves available packages in your designer
    • createElement(type, name, parentId) creates new elements
    • Type IDs (like the string type ID) are used to set data types
    • Elements are created hierarchically (attributes belong to a class)

    Understanding the Basics

    Before diving into more complex examples, it's helpful to understand the key concepts and building blocks available in designer scripting.

    Element Types by Designer

    Different designers support different element types. Here are the most common ones:

    Domain Designer:

    • Class
    • Attribute
    • Association
    • Constructor
    • Operation

    Services Designer:

    • Service
    • DTO
    • DTO-Field
    • Command
    • Query
    • Operation

    Common (available in multiple designers):

    • Operation
    • Parameter
    • Package (folder)

    Common Type IDs

    When setting type references for attributes, parameters, or return types, you'll need to use type IDs. Here are the most commonly used ones:

    Type ID
    string d384db9c-a279-45e1-801e-e4e8099625f2
    int fb0a362d-e9e2-40de-b6ff-5ce8167cbe74
    long 33013006-E404-48C2-AC46-24EF5A5774FD
    bool e6f92b09-b2c5-4536-8270-a4d9e5bbd930
    guid 6b649125-18ea-48fd-a6ba-0bfff0d8f488
    datetime a4107c29-7851-4121-9416-cf1236908f1e
    decimal 675c7b84-997a-44e0-82b9-cd724c07c9e6
    double 24A77F70-5B97-40DD-8F9A-4208AD5F9219

    Best practice: Define these as constants at the top of your script rather than using magic strings:

    // Define type constants for better maintainability
    const stringType = "d384db9c-a279-45e1-801e-e4e8099625f2";
    const intType = "fb0a362d-e9e2-40de-b6ff-5ce8167cbe74";
    const guidType = "6b649125-18ea-48fd-a6ba-0bfff0d8f488";
    

    Working with Packages

    Packages are the containers (folders) that organize elements in your designer. You can find existing packages or create new ones:

    // Get all packages
    let allPackages = getPackages();
    
    // Find a specific package by name
    let domainPackage = getPackages().find(p => p.name === "Domain");
    
    // Use the first package as default if target not found
    let targetPackage = getPackages().find(p => p.name === "MyPackage") || getPackages()[0];
    

    Printing to the Console

    When running scripts in the Execute Script dialog you can write messages and inspect objects using console.log. The output is shown in the Task Output Console in Intent Architect. For complex objects use JSON.stringify to get a readable JSON representation.

    // Get all packages and print them
    let allPackages = getPackages();
    console.log(`allPackages = ${JSON.stringify(allPackages)}`);
    
    // Find a named package and print it (may be undefined if not present)
    let domainPackage = getPackages().find(p => p.name === "Domain");
    console.log(`domainPackage = ${JSON.stringify(domainPackage)}`);
    
    // Use the first package as default if target not found
    let targetPackage = getPackages().find(p => p.name === "MyPackage") || getPackages()[0];
    console.log(`targetPackage = ${JSON.stringify(targetPackage)}`);
    
    Tip

    JSON.stringify provides compact output; if you prefer pretty output use JSON.stringify(obj, null, 2).

    Example of what the results pane might show (trimmed/simplified):

    [12:24:56] Executed Script: Executing script (0ms)
    allPackages = [{"specializationId":"1a824508-4623-45d9-accc-f572091ade5a","specialization":"Domain Package","id":"fad9fe56-bb78-4b1d-8945-0e40ca9d77d3","name":"NewApplication.Domain",...}]
    domainPackage = undefined
    targetPackage = {"specializationId":"1a824508-4623-45d9-accc-f572091ade5a","specialization":"Domain Package","id":"fad9fe56-bb78-4b1d-8945-0e40ca9d77d3","name":"NewApplication.Domain",...}
    

    This mirrors the example screenshots in the editor: objects printed as JSON, undefined printed when a find returns nothing, and the selected fallback package printed when the named package is not found.

    Creating Elements and Setting Types

    The basic pattern for creating elements is:

    // Get arbitrary package ID
    let packageId = getPackages()[0].id;
    
    // Create an element: createElement(elementType, name, parentId)
    let myClass = createElement("Class", "Customer", packageId);
    
    // Create a child element
    let myAttribute = createElement("Attribute", "Name", myClass.id);
    
    // Set the attribute's type
    const stringTypeId = "d384db9c-a279-45e1-801e-e4e8099625f2";
    myAttribute.typeReference.setType(stringTypeId);
    
    

    Practical Examples: Ad-hoc Scripts

    The following examples demonstrate common scenarios where ad-hoc scripts can save significant time. You can copy these examples and adapt them to your needs.

    Bulk Domain Model Creation

    This example creates a complete e-commerce domain model with multiple entities and different relationship types. This is useful when you need to quickly scaffold a domain model based on existing documentation or requirements.

    // Define type constants
    const stringType = "d384db9c-a279-45e1-801e-e4e8099625f2";
    const guidType = "6b649125-18ea-48fd-a6ba-0bfff0d8f488";
    const intType = "fb0a362d-e9e2-40de-b6ff-5ce8167cbe74";
    const datetimeType = "a4107c29-7851-4121-9416-cf1236908f1e";
    const decimalType = "675c7b84-997a-44e0-82b9-cd724c07c9e6";
    
    // Helper function to add attributes to entities
    function addAttributes(entity, attributes) {
        // Note: Id attributes are auto-generated when Intent.Metadata.RDBMS module is installed
        attributes.forEach(attr => {
            let attribute = createElement("Attribute", attr.name, entity.id);
            attribute.typeReference.setType(attr.type);
        });
    }
    
    // Find target package
    let domainPackage = getPackages().find(p => p.name === "Domain") || getPackages()[0];
    
    // Create entities
    let customer = createElement("Class", "Customer", domainPackage.id);
    let order = createElement("Class", "Order", domainPackage.id);
    let orderItem = createElement("Class", "OrderItem", domainPackage.id);
    let product = createElement("Class", "Product", domainPackage.id);
    
    // Add attributes to each entity
    // Use (or create) a DTO package to hold generated DTOs
    // (fall back to the first package if a dedicated DTOs package doesn't exist)
    let dtosPackage = getPackages().find(p => p.name === "DTOs") || getPackages()[0];
    addAttributes(customer, [
        { name: "Name", type: stringType },
        { name: "Email", type: stringType }
    ]);
    
    addAttributes(order, [
        { name: "OrderDate", type: datetimeType },
        { name: "TotalAmount", type: decimalType }
    ]);
    
    addAttributes(orderItem, [
        { name: "Quantity", type: intType },
        { name: "UnitPrice", type: decimalType }
    ]);
    
    addAttributes(product, [
        { name: "Name", type: stringType }
    ]);
    
    // Create relationships with different patterns:
    
    // 1. Aggregate: Customer -> Orders (defaults to 1-to-many aggregate relationship)
    createAssociation("Association", customer.id, order.id);
    
    // 2. Composition: Order -> OrderItems (1-to-many composite - OrderItem can't exist without Order)
    // Key: Use getOtherEnd() to configure both sides for composite relationships
    let orderItemAssoc = createAssociation("Association", order.id, orderItem.id);
    orderItemAssoc.getOtherEnd().typeReference.setIsCollection(false); // Order side (one)
    orderItemAssoc.typeReference.setIsCollection(true); // OrderItem side (many)
    
    // 3. Reference: OrderItem -> Product (many-to-1 reference - Product exists independently)
    createAssociation("Association", orderItem.id, product.id);
    
    await dialogService.info("Created e-commerce domain model with proper relationship types!");
    

    Once created, you can drag them onto the diagram to resemble this:

    Bulk domain element creation

    Gathering User Input with Dynamic Forms

    When you need to make your scripts more flexible and reusable, you can prompt users for input using dynamic forms. This example shows how to create a configurable entity generator that asks users what entities to create and what common fields to add.

    // Define type constants
    const guidType = "6b649125-18ea-48fd-a6ba-0bfff0d8f488";
    const intType = "fb0a362d-e9e2-40de-b6ff-5ce8167cbe74";
    const longType = "33013006-E404-48C2-AC46-24EF5A5774FD";
    const datetimeType = "a4107c29-7851-4121-9416-cf1236908f1e";
    
    // Configure a form to collect entity generation parameters
    let formConfig = {
        title: "Bulk Entity Generator",
        submitButtonText: "Generate Entities",
        minWidth: "500px",
        fields: [
            {
                id: "entityNames",
                fieldType: "textarea",
                label: "Entity Names (one per line)",
                isRequired: true,
                placeholder: "Customer\nOrder\nProduct\nCategory",
                hint: "Enter each entity name on a separate line"
            },
            {
                id: "addIdAttribute",
                fieldType: "checkbox",
                label: "Add Id attribute to each entity",
                value: true
            },
            {
                id: "idType",
                fieldType: "select",
                label: "Id Type",
                selectOptions: [
                    { id: guidType, description: "Guid" },
                    { id: intType, description: "Int" },
                    { id: longType, description: "Long" }
                ],
                value: guidType
            },
            {
                id: "addAuditFields",
                fieldType: "checkbox",
                label: "Add audit fields (CreatedDate, UpdatedDate)",
                value: true
            }
        ]
    };
    
    // Show the form and get user input
    let result = await dialogService.openForm(formConfig);
    let entityNames = result.entityNames.split('\n').filter(name => name.trim());
    
    // Create entities based on form input
    let targetPackage = getPackages()[0];
    entityNames.forEach(name => {
        let entity = createElement("Class", name.trim(), targetPackage.id);
        
        // Add Id attribute if requested
        if (result.addIdAttribute) {
            let idAttr = createElement("Attribute", "Id", entity.id);
            idAttr.typeReference.setType(result.idType);
        }
        
        // Add audit fields if requested
        if (result.addAuditFields) {
            let createdDate = createElement("Attribute", "CreatedDate", entity.id);
            createdDate.typeReference.setType(datetimeType);
            
            let updatedDate = createElement("Attribute", "UpdatedDate", entity.id);
            updatedDate.typeReference.setType(datetimeType);
        }
    });
    
    await dialogService.info(`Successfully created ${entityNames.length} entities!`);
    

    Service Layer Generation

    This example generates CRUD (Create, Read, Update, Delete) operations for existing domain entities. This is particularly useful when you've modeled your domain and need to quickly create a corresponding service layer.

    Note

    You need to have domain entities modeled before running this script.

    // Define type constants
    const guidType = "6b649125-18ea-48fd-a6ba-0bfff0d8f488";
    
    // Generate CRUD operations for all domain classes
    let domainClasses = lookupTypesOf("Class");
    let servicesPackage = getPackages().find(p => p.name === "Services") || getPackages()[0];
    
    // Use (or create) a DTO package to hold generated DTOs
    let dtosPackage = getPackages().find(p => p.name === "DTOs") || servicesPackage;
    
    domainClasses.forEach(domainClass => {
        // Create or reuse a DTO that represents the domain entity in service layer APIs
        const dtoName = `${domainClass.getName()}Dto`;
        let dto = lookupTypesOf("DTO").find(d => d.getName() === dtoName);
        if (!dto) {
            dto = createElement("DTO", dtoName, dtosPackage.id);
        }
    
        // Copy attributes from the domain class into DTO fields where available
        // (This keeps the service layer decoupled from domain element types)
        domainClass.getChildren("Attribute").forEach(attr => {
            let field = createElement("DTO-Field", attr.getName(), dto.id);
            if (attr.typeReference) {
                // Use the attribute's type for the DTO field when possible
                field.typeReference.setType(attr.typeReference.getTypeId());
                field.typeReference.setIsNullable(attr.typeReference.getIsNullable());
                field.typeReference.setIsCollection(attr.typeReference.getIsCollection());
            }
        });
    
        let service = createElement("Service", `${domainClass.getName()}Service`, servicesPackage.id);
        
        // Create CRUD operations
        let createOp = createElement("Operation", `Create${domainClass.getName()}`, service.id);
        let getOp = createElement("Operation", `Get${domainClass.getName()}`, service.id);
        let updateOp = createElement("Operation", `Update${domainClass.getName()}`, service.id);
        let deleteOp = createElement("Operation", `Delete${domainClass.getName()}`, service.id);
        
        // Set return/parameter types to the DTO (not the domain element)
        // Get should return the DTO representation
        getOp.typeReference.setType(dto.id);
        // Create returns an id (guid)
        createOp.typeReference.setType(guidType);
        
        // Add parameters to operations - use DTO for payloads
        let createParam = createElement("Parameter", `create${domainClass.getName()}Request`, createOp.id);
        createParam.typeReference.setType(dto.id);
        
        let idParam = createElement("Parameter", "id", getOp.id);
        idParam.typeReference.setType(guidType);
    });
    

    Command/Query Pattern Generator

    If you're following the CQRS (Command Query Responsibility Segregation) pattern, this script can automatically generate Commands and Queries from existing Service Operations. It analyzes operation names to determine whether to create a Command or Query and copies parameters as DTO fields.

    Tip

    Run the script in Service Layer Generation before executing this one.

    // Generate Commands and Queries for selected service operations
    let services = lookupTypesOf("Service");
    let commandsPackage = getPackages()[0];
    let queriesPackage = getPackages()[0];
    
    services.forEach(service => {
        service.getChildren("Operation").forEach(operation => {
            let operationName = operation.getName();
            
            if (operationName.startsWith("Get") || operationName.startsWith("Find") || operationName.startsWith("Search")) {
                // Create Query
                let query = createElement("Query", `${operationName}Query`, queriesPackage.id);
                
                // Copy parameters as DTO fields
                operation.getChildren("Parameter").forEach(param => {
                    let field = createElement("DTO-Field", param.getName(), query.id);
                    if (param.typeReference) {
                        field.typeReference.setType(param.typeReference.getTypeId());
                        field.typeReference.setIsNullable(param.typeReference.getIsNullable());
                        field.typeReference.setIsCollection(param.typeReference.getIsCollection());
                    }
                });
                
                // Set return type to match operation
                if (operation.typeReference && operation.typeReference.getTypeId()) {
                    query.typeReference.setType(operation.typeReference.getTypeId());
                    query.typeReference.setIsCollection(operation.typeReference.getIsCollection());
                }
                
            } else {
                // Create Command
                let command = createElement("Command", `${operationName}Command`, commandsPackage.id);
                
                // Copy parameters as DTO fields
                operation.getChildren("Parameter").forEach(param => {
                    let field = createElement("DTO-Field", param.getName(), command.id);
                    if (param.typeReference) {
                        field.typeReference.setType(param.typeReference.getTypeId());
                        field.typeReference.setIsNullable(param.typeReference.getIsNullable());
                        field.typeReference.setIsCollection(param.typeReference.getIsCollection());
                    }
                });
                
                // Commands typically return void or an ID
                if (operation.typeReference && operation.typeReference.getTypeId()) {
                    command.typeReference.setType(operation.typeReference.getTypeId());
                }
            }
        });
    });
    
    await dialogService.info("Commands and Queries generated successfully!");
    

    Event-Driven Automation

    While ad-hoc scripts are great for one-time operations, event handlers allow you to automate responses to modeling actions. Event handlers run automatically when specific events occur, such as when an element is created, modified, or when an association is drawn.

    Event handlers are particularly useful for:

    • Enforcing modeling conventions automatically
    • Auto-configuring new elements with common attributes
    • Maintaining relationships between elements
    • Applying stereotypes and metadata consistently
    Note

    Event handlers are created in the Module Builder designer and become part of a module. This section assumes you're familiar with basic module building concepts. See Module Builder overview for conceptual background.

    Element Event Handlers

    Element event handlers execute when an element is created or modified. This example shows how to automatically manage a "soft delete" pattern by adding or removing an IsDeleted attribute based on whether a stereotype is applied.

    Event Triggered Script for Elements Screenshot

    Inside the Module Builder designer, you can add Element Event Handlers for an Element you've created or extend an existing Element from a Designer.

    Installed as: Element Event Handler on the Class element's On Changed event. Requires a Stereotype Soft Delete Entity.

    const softDeleteStereotype = "Soft Delete Entity";
    const boolTypeId = "e6f92b09-b2c5-4536-8270-a4d9e5bbd930";
    
    if (element.hasStereotype(softDeleteStereotype)) {
        let isDeleteAttr = element.getChildren("Attribute").filter(x => x.hasMetadata("soft-delete"))[0] ||
            createElement("Attribute", "IsDeleted", element.id);
        isDeleteAttr.typeReference.setType(boolTypeId);
        isDeleteAttr.setMetadata("soft-delete", true);
        return;
    }
    
    let isDeleteAttr = element.getChildren("Attribute").filter(x => x.hasMetadata("soft-delete"))[0];
    if (isDeleteAttr) {
        isDeleteAttr.delete();
    }
    

    In this JavaScript example, the script activates when a Class element is modified. It performs the following actions:

    • When a Class has a Soft Delete stereotype applied, it adds an IsDeleted attribute of boolean type, marked with soft-delete metadata
    • When the Soft Delete stereotype is removed, it searches for any attribute with soft-delete metadata and deletes it from the Class

    Auto-Configuring New Entities

    This example shows how to automatically add common attributes (like Id, CreatedDate, UpdatedDate) whenever a new entity is created. This ensures consistency across your domain model without manual repetition.

    Installed as: Element Event Handler on the Class element's On Created event.

    // Define type constants
    const guidType = "6b649125-18ea-48fd-a6ba-0bfff0d8f488";
    const datetimeType = "a4107c29-7851-4121-9416-cf1236908f1e";
    
    // When a new Class is created, auto-add common attributes
    if (element.specialization === "Class") {
        // Add Id attribute if it doesn't exist
        let hasId = element.getChildren("Attribute").some(attr => attr.getName().toLowerCase() === "id");
        if (!hasId) {
            let idAttr = createElement("Attribute", "Id", element.id);
            idAttr.typeReference.setType(guidType);
        }
        
        // Add CreatedDate and UpdatedDate for audit trail
        let createdDate = createElement("Attribute", "CreatedDate", element.id);
        createdDate.typeReference.setType(datetimeType);
        
        let updatedDate = createElement("Attribute", "UpdatedDate", element.id);
        updatedDate.typeReference.setType(datetimeType);
    }
    

    Association Event Handlers

    Association event handlers execute when associations (relationships) are created between elements. This is useful for automatically configuring relationship properties based on conventions or element types.

    Event Triggered Script for Associations Screenshot

    Inside the Module Builder designer, you can add Association Event Handlers for an Association you've created or extend an existing Association from a Designer.

    Installed as: Association Event Handler on the Association element's On Created event.

    Here's a simple example that automatically configures new associations so that the source is the composite owner of the relationship (no matter which association type you use):

    if (!association) {
        return;
    }
    let sourceEnd = association.getOtherEnd().typeReference;
    sourceEnd.setIsCollection(false);
    sourceEnd.setIsNullable(false);
    

    This script gets executed when an association is created and turns it into a 1-to-1 composite relationship by disabling Is Collection and Is Nullable on the source end of the association.

    Auto-configuring association properties by aggregate semantics

    Rather than relying on literal type names, this example shows a more robust approach: determine whether an element is an aggregate root (no other entity composes it) or a value object (specialization Value Object), and apply relationship semantics accordingly.

    Installed as: Association Event Handler on the Association element's On Created event.

    // Auto-configure association properties using aggregate-root inference
    if (!association) { return; }
    
    function isAggregateRoot(element) {
        return !element.getAssociations("Association")
            .some(x => x.isSourceEnd() && !x.typeReference.isCollection && !x.typeReference.isNullable);
    }
    
    const sourceElement = association.getOtherEnd().typeReference.getType();
    const targetElement = association.typeReference.getType();
    
    // 1) If target is explicitly a Value Object -> make it a composition (parent one -> child many)
    if (targetElement && targetElement.specialization === 'Value Object') {
        association.getOtherEnd().typeReference.setIsCollection(false); // Parent: only one composite owner
        association.typeReference.setIsCollection(true); // Child: collection of value objects
        association.typeReference.setIsNullable(false); // Child instances cannot be null
        return;
    }
    
    // 2) If target is an aggregate root (no composite owners) -> make relationship a required reference (many-to-one)
    if (isAggregateRoot(targetElement)) {
        association.getOtherEnd().typeReference.setIsCollection(false); // Source end: single reference
        association.typeReference.setIsCollection(false); // Target end: single reference
        association.typeReference.setIsNullable(false); // Target must be present (required)
        return;
    }
    
    // 3) Fallback: optional reference (one-to-one optional)
    association.getOtherEnd().typeReference.setIsCollection(false);
    association.typeReference.setIsCollection(false);
    association.typeReference.setIsNullable(true);
    

    Full API Documentation

    Complete API documentation with IntelliSense is available in the built-in script editor. For the full TypeScript definitions, see the GitHub repository.

    • Edit this page
    ☀
    ☾
    In this article
    Back to top Copyright © 2017-, Intent Software Pte Ltd.