Portal Community

Prefix Strategies and Naming Conventions

Single-Prefix Strategy

All shorthands use a consistent primary prefix style:

// Strategy: Use only "$" prefix family
$output      // Node outputs
$input       // Node inputs
$var         // Variables
$memory      // Execution memory
$config      // Custom config
$user        // Custom user data

// Pros:
// - Consistent, predictable
// - Easy to remember
// - Simple pattern matching

// Cons:
// - Limited to one prefix style
// - May conflict with framework conventions
// - Hard to distinguish categories visually

Mixed-Prefix Strategy

Different prefixes for different categories:

// Strategy: Multiple semantic prefixes
$output      // Node outputs ($ = built-in)
$$parsed     // Parsed data (double $)
@selector    // DOM selectors (@)
@api         // API references (@)
#feature     // Feature flags (#)

// Pros:
// - Visual distinction between categories
// - Can encode type information in prefix
// - Semantic clarity

// Cons:
// - More complex to manage
// - Harder to document
// - Users need to learn multiple conventions

Hierarchical Strategy

Nested prefixes for complex data hierarchies:

// Strategy: Namespace-like prefixes
$output.rest        // REST API outputs
$output.database    // Database outputs
$output.webhook     // Webhook outputs
$memory.cache       // Cache memory
$memory.persistent  // Persistent memory

// Pros:
// - Scales to large numbers of shorthands
// - Groups related shorthands
// - Reflects data source hierarchy

// Cons:
// - More verbose syntax
// - Implementation complexity
// - Steeper learning curve

Shorthand Resolution Process

Understanding how shorthands are resolved helps with debugging and optimization:

Step 1: Parse Expression

// Input from workflow designer
@{$output:form-1.email}

// Parser extracts:
// - Sigil: @{...}
// - Prefix: $output
// - Path: form-1.email

Step 2: Prefix Matching

// Registry lookup
ShorthandRegistry.GetDefinition("$output") → ShorthandDefinition found

// If not found:
ShorthandRegistry.GetDefinition("$output") → null (treated as directive)

Step 3: Expansion

// Shorthand expands the path
$output.Expand("form-1.email")
→ "@{output:form-1.email}"

Step 4: Evaluation

// Expanded directive is evaluated normally
@{output:form-1.email} → alice@example.com

Performance Considerations

Shorthand Resolution Overhead

Shorthands add minimal overhead but should be considered at scale:

OperationCostOptimization
Prefix matching O(n) where n = registered shorthands Use HashMap for O(1) lookup
Expansion O(1) string concatenation Cache expansion results if repeated
Directive evaluation O(m) where m = resolution steps Depends on directive type, not shorthand

Optimization Patterns

// Pattern 1: Cache frequently expanded shorthands
public class CachingShorthandDefinition : IShorthandDefinition
{
    private readonly Dictionary<string, string> _cache = new();
    private readonly IShorthandDefinition _inner;

    public string Prefix => _inner.Prefix;

    public string Expand(string shorthandPath)
    {
        if (_cache.TryGetValue(shorthandPath, out var cached))
            return cached;

        var result = _inner.Expand(shorthandPath);
        _cache[shorthandPath] = result;
        return result;
    }
}

// Pattern 2: Lazy-load shorthands on demand
public class LazyShorthandRegistry
{
    private readonly Lazy<Dictionary<string, IShorthandDefinition>> _definitions;

    public IShorthandDefinition GetDefinition(string prefix)
    {
        var definitions = _definitions.Value; // Only loaded on first access
        return definitions.TryGetValue(prefix, out var def) ? def : null;
    }
}

Debugging Shorthands

Enable Expression Logging

// Log shorthand expansions during evaluation
public class DebuggingExpressionResolver : IExpressionResolver
{
    private readonly ILogger<DebuggingExpressionResolver> _logger;

    public async Task<object> ResolveAsync(string expression, EvaluationContext context)
    {
        _logger.LogInformation("Original expression: {Expression}", expression);

        var expanded = ExpandShorthands(expression);
        if (expanded != expression)
            _logger.LogInformation("Expanded expression: {Expanded}", expanded);

        var result = await _evaluator.EvaluateAsync(expanded, context);
        _logger.LogInformation("Evaluation result: {Result}", result);

        return result;
    }

    private string ExpandShorthands(string expression)
    {
        // Expansion logic with logging
        return expanded;
    }
}

// Configuration
builder.Services.AddScoped<IExpressionResolver>, DebuggingExpressionResolver>();

Test Shorthand Expansions

[TestClass]
public class ShorthandExpansionTests
{
    private IShorthandDefinition _shorthand;

    [TestInitialize]
    public void Setup()
    {
        _shorthand = new ConfigShorthandDefinition();
    }

    [TestMethod]
    public void Expand_SimpleKey_ReturnsValidDirective()
    {
        // Arrange
        string path = "maxRetries";

        // Act
        string expanded = _shorthand.Expand(path);

        // Assert
        Assert.IsTrue(expanded.StartsWith("@{"));
        Assert.IsTrue(expanded.EndsWith("}"));
        Assert.AreEqual("@{memory:config.maxRetries}", expanded);
    }

    [TestMethod]
    public void Expand_NestedPath_HandlesCorrectly()
    {
        // Arrange
        string path = "database.connectionTimeout";

        // Act
        string expanded = _shorthand.Expand(path);

        // Assert
        Assert.AreEqual("@{memory:config.database.connectionTimeout}", expanded);
    }

    [TestMethod]
    [ExpectedException(typeof(ArgumentException))]
    public void Expand_InvalidPath_ThrowsException()
    {
        // Arrange
        string path = "../../etc/passwd"; // Path traversal attempt

        // Act
        _shorthand.Expand(path);

        // Assert: Exception expected
    }
}

Visual Debugging in Designer

If your designer supports expression preview, show expanded forms:

// In workflow designer UI
Expression Input:   @{$config:maxRetries}
Expands to:         @{memory:config.maxRetries}
Evaluates to:       3
Type:               Integer

Integration with Directives

Shorthands with Modifiers

Some frameworks support modifiers on directives. Combine with shorthands carefully:

// Standard modifier usage
@{output:api-1.responseBody | parseJson}

// Using shorthand with modifier
// This requires the modifier to apply to the expanded form
@{$output:api-1.responseBody | parseJson}

// Implementation: Modifiers are applied AFTER expansion
1. Expand: @{$output:api-1.responseBody | parseJson}
   → @{output:api-1.responseBody | parseJson}
2. Evaluate: Apply parseJson modifier to result

Chaining Multiple Shorthands

Avoid nested shorthands — they don't compose:

// Bad: Nested shorthands (don't work)
@{$config:@{$memory:configKey}}

// Good: Single shorthand with complete path
@{$config:settingName}

// Good: Use JavaScript to combine
@{js: $config.setting1 + $config.setting2}

Common Patterns and Anti-Patterns

Good: Domain-Specific Shorthand

// Clear, focused purpose
$user       // User data
$config     // Configuration
$api        // API responses

// Pros: Simple to understand and use

Bad: Too-Generic Shorthand

// Unclear what it references
$data
$info
$temp

// Cons: Leads to confusion and misuse

Good: Consistent Nesting

// All follow same pattern
$output.rest
$output.database
$output.webhook
$memory.cache
$memory.persistent

// Pros: Easy to predict and discover

Bad: Inconsistent Nesting

// Mixed patterns
$output
$$output.parsed
@api
#cache

// Cons: Users must remember each rule

Good: Expansion Transparency

// Expansion is predictable
$config.maxRetries → @{memory:config.maxRetries}
$user.email → @{memory:user.email}

// Pros: Users can understand what's happening

Bad: Hidden Complexity

// Expansion is magic
$data → @{memory:contextData.user.profile.email.value}

// Cons: Users can't predict or debug easily

Security Implications

Input Validation

Always validate shorthand paths to prevent injection:

public class SecureShorthandDefinition : IShorthandDefinition
{
    public string Prefix => "$config";

    public string Expand(string shorthandPath)
    {
        // Validate against whitelist
        var validPaths = new[] { "maxRetries", "timeout", "retryPolicy" };
        if (!validPaths.Contains(shorthandPath))
            throw new ArgumentException($"Invalid path: {shorthandPath}");

        return $"@{{memory:config.{shorthandPath}}}";
    }
}

// Or use regex validation
private static readonly Regex ValidPathPattern = new(@"^[a-zA-Z0-9_.]+$");

public string Expand(string shorthandPath)
{
    if (!ValidPathPattern.IsMatch(shorthandPath))
        throw new ArgumentException($"Invalid path characters: {shorthandPath}");

    return $"@{{memory:config.{shorthandPath}}}";
}

Access Control

Control who can use which shorthands:

public class AccessControlledShorthandDefinition : IShorthandDefinition
{
    private readonly IAccessControl _access;

    public string Prefix => "$sensitive";

    public string Expand(string shorthandPath)
    {
        // Check caller permissions
        if (!_access.HasRole("admin"))
            throw new UnauthorizedAccessException(
                "Only administrators can access $sensitive shorthand"
            );

        return $"@{{memory:sensitive.{shorthandPath}}}";
    }
}

Future Extensibility

Plugin Architecture

// Design for extensibility via plugin system
public interface IShorthandPlugin
{
    IEnumerable<IShorthandDefinition> GetShorthands();
    string Name { get; }
    string Version { get; }
}

public class WalmartPlugin : IShorthandPlugin
{
    public string Name => "Walmart Enterprise Shorthands";
    public string Version => "1.0.0";

    public IEnumerable<IShorthandDefinition> GetShorthands()
    {
        yield return new WalmartStoreShorthand();
        yield return new WalmartCustomerShorthand();
        yield return new WalmartInventoryShorthand();
    }
}

// Load plugins at startup
foreach (var plugin in LoadPlugins())
{
    foreach (var shorthand in plugin.GetShorthands())
    {
        services.AddSingleton<IShorthandDefinition>(shorthand);
    }
}

Configuration-Driven Shorthands

// Define shorthands in configuration
{
  "expressions": {
    "shorthands": [
      {
        "prefix": "$config",
        "expansion": "@{memory:appConfig.{path}}",
        "description": "Application configuration"
      },
      {
        "prefix": "$user",
        "expansion": "@{memory:currentUser.{path}}",
        "description": "Current user data"
      }
    ]
  }
}

// Load at startup
public void LoadShorthandsFromConfig(IConfiguration config)
{
    var shorthandConfigs = config.GetSection("expressions:shorthands")
        .GetChildren();

    foreach (var shConfig in shorthandConfigs)
    {
        var shorthand = new ConfigurableShorthand(
            shConfig["prefix"],
            shConfig["expansion"],
            shConfig["description"]
        );
        services.AddSingleton<IShorthandDefinition>(shorthand);
    }
}

Monitoring and Observability

Shorthand Usage Metrics

public class MetricsShorthandDefinition : IShorthandDefinition
{
    private readonly IMetricsCollector _metrics;

    public string Prefix => "$config";

    public string Expand(string shorthandPath)
    {
        _metrics.IncrementCounter("shorthand.expansion", new[]
        {
            ("prefix", Prefix),
            ("path", shorthandPath)
        });

        return $"@{{memory:config.{shorthandPath}}}";
    }
}

// Query metrics
// Most used shorthands?
// Failing expansions?
// Performance trends?
Warning: Shorthand Overload Adding too many shorthands defeats their purpose. Shorthands should reduce cognitive load, not increase it. Keep the total number below 20 for any single application.

Migration and Refactoring

Renaming a Shorthand

// Phase 1: Support both old and new names
services.AddSingleton<IShorthandDefinition>(new ConfigShorthandDefinition("$config"));
services.AddSingleton<IShorthandDefinition>(new ConfigShorthandDefinition("$cfg")); // Legacy

// Phase 2: Log deprecation warnings
public string Expand(string shorthandPath)
{
    if (Prefix == "$cfg")
        _logger.LogWarning("Deprecated shorthand $cfg used. Use $config instead.");

    return $"@{{memory:config.{shorthandPath}}}";
}

// Phase 3: After migration period, remove old shorthand

Changing Expansion Target

// Before: $config points to memory
// After: $config points to persistent store

// Safe migration:
1. Add new prefix $config-v2 pointing to persistent store
2. In designer, allow both $config and $config-v2
3. Log warning when $config is used
4. Migrate workflows from $config to $config-v2
5. Remove $config after all migrations complete
Shorthand Versioning When you need to change a shorthand's expansion, create a new versioned shorthand rather than breaking existing ones. This prevents workflow failures during migration.