@typescript-eda/domain
Version:
Core domain primitives for event-driven architecture
457 lines (404 loc) • 10.9 kB
text/typescript
// Copyright 2025-today Semantest Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview SemanTestContract entity representing automation contracts
* @author Semantest Team
* @module domain/semantic-automation/semantest-contract
*/
import { Entity } from '../entity';
import { SemanTestCapability } from './semantest-capability.entity';
/**
* Properties for SemanTestContract entity
*/
export interface SemanTestContractProps {
readonly id: string;
readonly version: string;
readonly domain: string;
readonly title: string;
readonly description?: string;
readonly capabilities: Record<string, SemanTestCapability>;
readonly workflows?: Record<string, WorkflowDefinition>;
readonly metadata?: ContractMetadata;
readonly createdAt: Date;
readonly updatedAt: Date;
}
/**
* Workflow definition for multi-step automation
*/
export interface WorkflowDefinition {
readonly description: string;
readonly parameters?: ParameterDefinition[];
readonly steps: WorkflowStep[];
readonly errorHandling?: ErrorHandling;
}
/**
* Individual workflow step
*/
export interface WorkflowStep {
readonly capability: string;
readonly parameters?: Record<string, any>;
readonly condition?: ExecutionCondition;
readonly onSuccess?: string;
readonly onFailure?: string;
readonly retry?: RetryConfiguration;
}
/**
* Parameter definition for workflows and capabilities
*/
export interface ParameterDefinition {
readonly name: string;
readonly type: ParameterType;
readonly description?: string;
readonly required?: boolean;
readonly default?: any;
readonly validation?: ParameterValidation;
readonly examples?: any[];
}
/**
* Parameter data types
*/
export type ParameterType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'file';
/**
* Parameter validation rules
*/
export interface ParameterValidation {
readonly minLength?: number;
readonly maxLength?: number;
readonly pattern?: string;
readonly minimum?: number;
readonly maximum?: number;
readonly enum?: any[];
}
/**
* Execution conditions
*/
export interface ExecutionCondition {
readonly type: ConditionType;
readonly selector?: string;
readonly urlPattern?: string;
readonly text?: string;
readonly customCondition?: string;
readonly negate?: boolean;
}
/**
* Condition types
*/
export type ConditionType = 'element' | 'url' | 'text' | 'custom';
/**
* Retry configuration
*/
export interface RetryConfiguration {
readonly attempts: number;
readonly delay: number;
}
/**
* Error handling strategy
*/
export interface ErrorHandling {
readonly strategy: ErrorStrategy;
readonly maxRetries?: number;
readonly fallbackCapability?: string;
}
/**
* Error handling strategies
*/
export type ErrorStrategy = 'abort' | 'continue' | 'retry' | 'fallback';
/**
* Contract metadata
*/
export interface ContractMetadata {
readonly author?: string;
readonly tags?: string[];
readonly compatibilityScore?: number;
readonly validationResults?: ValidationResult[];
}
/**
* Validation result
*/
export interface ValidationResult {
readonly valid: boolean;
readonly errors: string[];
readonly warnings: string[];
readonly timestamp: Date;
}
/**
* SemanTestContract entity representing automation contracts
* Extends Entity from typescript-eda-domain for consistent domain modeling
*/
export class SemanTestContract extends Entity<SemanTestContractProps> {
constructor(props: SemanTestContractProps) {
super(props);
}
/**
* Factory method to create a new contract
*/
static create(
id: string,
version: string,
domain: string,
title: string,
capabilities: Record<string, SemanTestCapability>,
options?: {
description?: string;
workflows?: Record<string, WorkflowDefinition>;
metadata?: ContractMetadata;
}
): SemanTestContract {
const now = new Date();
return new SemanTestContract({
id,
version,
domain,
title,
description: options?.description,
capabilities,
workflows: options?.workflows,
metadata: options?.metadata,
createdAt: now,
updatedAt: now
});
}
/**
* Get contract ID
*/
getId(): string {
return this.get('id');
}
/**
* Get contract version
*/
getVersion(): string {
return this.get('version');
}
/**
* Get target domain
*/
getDomain(): string {
return this.get('domain');
}
/**
* Get contract title
*/
getTitle(): string {
return this.get('title');
}
/**
* Get contract description
*/
getDescription(): string | undefined {
return this.get('description');
}
/**
* Get all capabilities
*/
getCapabilities(): Record<string, SemanTestCapability> {
return this.get('capabilities');
}
/**
* Get specific capability by name
*/
getCapability(name: string): SemanTestCapability | undefined {
return this.get('capabilities')[name];
}
/**
* Get capability names
*/
getCapabilityNames(): string[] {
return Object.keys(this.get('capabilities'));
}
/**
* Check if contract has capability
*/
hasCapability(name: string): boolean {
return name in this.get('capabilities');
}
/**
* Get workflows
*/
getWorkflows(): Record<string, WorkflowDefinition> | undefined {
return this.get('workflows');
}
/**
* Get specific workflow by name
*/
getWorkflow(name: string): WorkflowDefinition | undefined {
const workflows = this.get('workflows');
return workflows ? workflows[name] : undefined;
}
/**
* Get contract metadata
*/
getMetadata(): ContractMetadata | undefined {
return this.get('metadata');
}
/**
* Get creation timestamp
*/
getCreatedAt(): Date {
return this.get('createdAt');
}
/**
* Get last update timestamp
*/
getUpdatedAt(): Date {
return this.get('updatedAt');
}
/**
* Add or update capability
*/
withCapability(name: string, capability: SemanTestCapability): SemanTestContract {
const capabilities = { ...this.get('capabilities') };
capabilities[name] = capability;
return new SemanTestContract({
...this.props,
capabilities,
updatedAt: new Date()
});
}
/**
* Remove capability
*/
withoutCapability(name: string): SemanTestContract {
const capabilities = { ...this.get('capabilities') };
delete capabilities[name];
return new SemanTestContract({
...this.props,
capabilities,
updatedAt: new Date()
});
}
/**
* Update contract metadata
*/
withMetadata(metadata: ContractMetadata): SemanTestContract {
return new SemanTestContract({
...this.props,
metadata,
updatedAt: new Date()
});
}
/**
* Add workflow
*/
withWorkflow(name: string, workflow: WorkflowDefinition): SemanTestContract {
const workflows = { ...this.get('workflows') };
workflows[name] = workflow;
return new SemanTestContract({
...this.props,
workflows,
updatedAt: new Date()
});
}
/**
* Validate contract structure and capabilities
*/
validate(): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Validate required fields
if (!this.getId()) {
errors.push('Contract ID is required');
}
if (!this.getVersion()) {
errors.push('Contract version is required');
}
if (!this.getDomain()) {
errors.push('Contract domain is required');
}
if (!this.getTitle()) {
errors.push('Contract title is required');
}
// Validate capabilities
const capabilities = this.getCapabilities();
if (Object.keys(capabilities).length === 0) {
warnings.push('Contract has no capabilities defined');
}
// Validate each capability
for (const [name, capability] of Object.entries(capabilities)) {
try {
const capabilityValidation = capability.validate();
if (!capabilityValidation.valid) {
errors.push(`Capability '${name}': ${capabilityValidation.errors.join(', ')}`);
}
if (capabilityValidation.warnings.length > 0) {
warnings.push(`Capability '${name}': ${capabilityValidation.warnings.join(', ')}`);
}
} catch (error) {
errors.push(`Capability '${name}': ${error instanceof Error ? error.message : String(error)}`);
}
}
// Validate workflows
const workflows = this.getWorkflows();
if (workflows) {
for (const [name, workflow] of Object.entries(workflows)) {
for (const step of workflow.steps) {
if (!this.hasCapability(step.capability)) {
errors.push(`Workflow '${name}' references unknown capability '${step.capability}'`);
}
}
}
}
return {
valid: errors.length === 0,
errors,
warnings,
timestamp: new Date()
};
}
/**
* Convert contract to JSON for serialization
*/
toJSON(): Record<string, any> {
const capabilities: Record<string, any> = {};
for (const [name, capability] of Object.entries(this.get('capabilities'))) {
capabilities[name] = capability.toJSON();
}
return {
id: this.getId(),
version: this.getVersion(),
domain: this.getDomain(),
title: this.getTitle(),
description: this.getDescription(),
capabilities,
workflows: this.getWorkflows(),
metadata: this.getMetadata(),
createdAt: this.getCreatedAt().toISOString(),
updatedAt: this.getUpdatedAt().toISOString()
};
}
/**
* Create contract from JSON
*/
static fromJSON(json: any): SemanTestContract {
// Convert capabilities from JSON
const capabilities: Record<string, SemanTestCapability> = {};
if (json.capabilities) {
for (const [name, capabilityJson] of Object.entries(json.capabilities)) {
capabilities[name] = SemanTestCapability.fromJSON(capabilityJson);
}
}
return new SemanTestContract({
id: json.id,
version: json.version,
domain: json.domain,
title: json.title,
description: json.description,
capabilities,
workflows: json.workflows,
metadata: json.metadata,
createdAt: new Date(json.createdAt),
updatedAt: new Date(json.updatedAt)
});
}
}