CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-dustjs-linkedin

Asynchronous templates for the browser and server (LinkedIn fork)

Overview
Eval results
Files

filters.mddocs/

Filter System

Built-in and extensible filtering system for data transformation with automatic escaping support and custom filter registration.

Capabilities

Built-in Filters

Dust provides several built-in filters for common data transformations and security escaping.

/**
 * Built-in filter functions
 */
const filters: {
  /** HTML escape filter - escapes HTML entities for safe output */
  h: (value: any) => string;
  /** JavaScript string escape filter - escapes for safe JS string literals */
  j: (value: any) => string;
  /** URL encode filter - encodes using encodeURI */
  u: (value: any) => string;
  /** URL component encode filter - encodes using encodeURIComponent */
  uc: (value: any) => string;
  /** JSON stringify filter - safe JSON conversion for JavaScript */
  js: (value: any) => string;
  /** JSON parse filter - parses JSON strings to objects */
  jp: (value: any) => any;
};

Usage Examples:

const dust = require('dustjs-linkedin');

// Template usage with built-in filters:
// {name|h} - HTML escape
// {script|j} - JavaScript string escape
// {url|u} - URL encode
// {param|uc} - URL component encode
// {data|js} - JSON stringify
// {jsonString|jp} - JSON parse

// Test built-in filters directly
console.log(dust.filters.h('<script>alert("xss")</script>'));
// Output: "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"

console.log(dust.filters.j('Hello "world"\nNew line'));
// Output: "Hello \\\"world\\\"\\nNew line"

console.log(dust.filters.u('hello world'));
// Output: "hello%20world"

console.log(dust.filters.uc('hello@world.com'));
// Output: "hello%40world.com"

console.log(dust.filters.js({name: 'test', value: 123}));
// Output: '{"name":"test","value":123}'

console.log(dust.filters.jp('{"name":"test","value":123}'));
// Output: {name: 'test', value: 123}

Filter Application

Core function for applying filters to values with automatic escaping.

/**
 * Applies filter chain to string value
 * @param string - Input value to filter
 * @param auto - Automatic filter (usually 'h' for HTML escape)
 * @param filters - Array of filter names to apply in sequence
 * @param context - Template context for filter execution
 * @returns Filtered string value
 */
function filter(
  string: any,
  auto: string | null,
  filters: string[],
  context: Context
): string;

Usage Examples:

const context = dust.context({});

// Apply single filter
const htmlEscaped = dust.filter('<script>alert("xss")</script>', null, ['h'], context);
console.log(htmlEscaped); // "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"

// Apply multiple filters in sequence
const multiFiltered = dust.filter('Hello World', null, ['j', 'u'], context);
// First applies 'j' (JavaScript escape), then 'u' (URL encode)

// Apply with automatic filter
const autoFiltered = dust.filter('<div>Content</div>', 'h', [], context);
console.log(autoFiltered); // "&lt;div&gt;Content&lt;/div&gt;" (auto HTML escaped)

// Chain filters
const chained = dust.filter({name: 'test'}, null, ['js', 'u'], context);
// First converts to JSON, then URL encodes the result

Escape Functions

Individual escape functions used by filters and available for direct use.

/**
 * HTML entity escape function
 * @param string - String to escape
 * @returns String with HTML entities escaped
 */
function escapeHtml(string: any): string;

/**
 * JavaScript string escape function
 * @param string - String to escape for JS
 * @returns String safe for JavaScript string literals
 */
function escapeJs(string: any): string;

/**
 * Safe JSON stringify function
 * @param obj - Object to stringify
 * @returns JSON string safe for JavaScript execution
 */
function escapeJSON(obj: any): string;

Usage Examples:

// Direct use of escape functions
const htmlSafe = dust.escapeHtml('<script>alert("xss")</script>');
console.log(htmlSafe); // "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"

const jsSafe = dust.escapeJs('Line 1\nLine 2\t"Quote"');
console.log(jsSafe); // "Line 1\\nLine 2\\t\\\"Quote\\\""

const jsonSafe = dust.escapeJSON({
  name: 'Test',
  code: '<script>alert("xss")</script>',
  special: '\u2028\u2029' // Line/paragraph separators
});
console.log(jsonSafe); // Safely escaped JSON string

// Use in custom helpers
dust.helpers.safeOutput = (chunk, context, bodies, params) => {
  const rawData = params.data;
  const escaped = dust.escapeHtml(rawData);
  return chunk.write(escaped);
};

Custom Filters

Filter Registration

How to register custom filters for template use.

/**
 * Register custom filter by adding to filters object
 */
dust.filters.customFilter = (value: any) => string;

Usage Examples:

// Register a simple text transformation filter
dust.filters.capitalize = (value) => {
  if (typeof value !== 'string') return value;
  return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
};

// Register a number formatting filter
dust.filters.currency = (value) => {
  const number = parseFloat(value);
  if (isNaN(number)) return value;
  return '$' + number.toFixed(2);
};

// Register a date formatting filter
dust.filters.dateFormat = (value) => {
  const date = new Date(value);
  if (isNaN(date.getTime())) return value;
  return date.toLocaleDateString('en-US');
};

// Register a text truncation filter
dust.filters.truncate = (value) => {
  if (typeof value !== 'string') return value;
  return value.length > 50 ? value.substring(0, 50) + '...' : value;
};

// Template usage:
// {name|capitalize}
// {price|currency}
// {date|dateFormat}
// {description|truncate}

Advanced Custom Filters

More complex filter implementations with error handling and configuration.

// Filter with configuration
dust.filters.truncateBy = (value, length = 50) => {
  if (typeof value !== 'string') return value;
  const maxLength = parseInt(length) || 50;
  return value.length > maxLength ? value.substring(0, maxLength) + '...' : value;
};

// Filter with multiple transformations
dust.filters.slug = (value) => {
  if (typeof value !== 'string') return value;
  return value
    .toLowerCase()
    .replace(/[^\w\s-]/g, '') // Remove special characters
    .replace(/\s+/g, '-')     // Replace spaces with hyphens
    .replace(/-+/g, '-')      // Collapse multiple hyphens
    .trim('-');               // Remove leading/trailing hyphens
};

// Filter with error handling
dust.filters.safeNumber = (value) => {
  try {
    const number = parseFloat(value);
    return isNaN(number) ? '0' : number.toString();
  } catch (err) {
    dust.log('Filter error in safeNumber: ' + err.message, 'ERROR');
    return '0';
  }
};

// Filter that uses context (advanced - requires custom implementation)
dust.filters.translate = function(value, context) {
  const translations = context.get('translations') || {};
  return translations[value] || value;
};

Filter Chaining

Understanding how multiple filters work together in sequence.

// Filters are applied left to right
// Template: {name|capitalize|truncate}
// 1. First applies capitalize filter
// 2. Then applies truncate filter to the result

// Example filter chain execution:
const input = 'hello world this is a long string';

// Step 1: Apply capitalize
const step1 = dust.filters.capitalize(input); // "Hello world this is a long string"

// Step 2: Apply truncate
const final = dust.filters.truncate(step1); // "Hello world this is a long string..."

// Register filters that work well together
dust.filters.clean = (value) => {
  if (typeof value !== 'string') return value;
  return value.trim().replace(/\s+/g, ' '); // Clean whitespace
};

dust.filters.title = (value) => {
  if (typeof value !== 'string') return value;
  return value.replace(/\w\S*/g, (txt) =>
    txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
  );
};

// Template usage for chaining:
// {text|clean|title} - First clean whitespace, then title case

Automatic Escaping

Default Auto-Escape Behavior

Dust automatically applies HTML escaping by default for security.

// By default, all variable references are HTML escaped
// Template: {userInput}
// If userInput = '<script>alert("xss")</script>'
// Output: &lt;script&gt;alert("xss")&lt;/script&gt;

// Disable auto-escaping with 's' filter (suppress)
// Template: {userInput|s}
// Output: <script>alert("xss")</script> (DANGEROUS!)

// Override auto-escape with specific filter
// Template: {userInput|j} - JavaScript escape instead of HTML

Configuring Auto-Escape

Control automatic escaping behavior in templates.

// The default auto-escape filter is 'h' (HTML escape)
// This is applied automatically to all references unless:
// 1. Explicitly suppressed with |s
// 2. Overridden with another filter

// Example configurations in templates:
// {value}        - Auto HTML escaped
// {value|s}      - No escaping (suppress)
// {value|j}      - JavaScript escaped (overrides auto HTML)
// {value|h|j}    - HTML escaped, then JavaScript escaped
// {value|s|h}    - Suppress auto, then explicitly HTML escape

Security Considerations

Best practices for safe filter usage.

// SAFE: Always escape user content for HTML output
// Template: <div>{userContent}</div>
// Automatically HTML escaped by default

// DANGEROUS: Suppressing escaping
// Template: <div>{userContent|s}</div>
// Only use |s for trusted content!

// SAFE: Appropriate escaping for context
// Template: <script>var data = "{jsonData|js}";</script>
// Uses JavaScript escaping for script context

// SAFE: URL context escaping
// Template: <a href="?q={query|uc}">Link</a>
// Uses URL component encoding for query parameters

// Register security-focused filters
dust.filters.stripTags = (value) => {
  if (typeof value !== 'string') return value;
  return value.replace(/<[^>]*>/g, ''); // Remove HTML tags
};

dust.filters.allowedTags = (value) => {
  if (typeof value !== 'string') return value;
  // Allow only specific safe tags
  return value.replace(/<(?!\/?(?:b|i|em|strong|p|br)\b)[^>]*>/gi, '');
};

Filter Performance and Best Practices

Performance Optimization

Best practices for efficient filter usage.

// EFFICIENT: Simple, focused filters
dust.filters.upper = (value) => {
  return typeof value === 'string' ? value.toUpperCase() : value;
};

// LESS EFFICIENT: Complex processing in filters
dust.filters.complexProcess = (value) => {
  // Avoid heavy computations in filters
  // Consider doing this in helpers or before rendering
  return expensiveTransformation(value);
};

// GOOD: Cache expensive computations
const computationCache = new Map();
dust.filters.cachedTransform = (value) => {
  if (computationCache.has(value)) {
    return computationCache.get(value);
  }

  const result = expensiveComputation(value);
  computationCache.set(value, result);
  return result;
};

Error Handling in Filters

Robust error handling patterns for custom filters.

// Robust filter with error handling
dust.filters.robustFilter = (value) => {
  try {
    // Type checking
    if (value === null || value === undefined) {
      return '';
    }

    // Safe conversion
    const stringValue = String(value);

    // Process with error handling
    return processValue(stringValue);

  } catch (err) {
    // Log error for debugging
    dust.log(`Filter error: ${err.message}`, 'ERROR');

    // Return safe fallback
    return String(value || '');
  }
};

// Filter with validation
dust.filters.emailMask = (value) => {
  if (typeof value !== 'string') return value;

  // Validate email format
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(value)) {
    return '[Invalid Email]';
  }

  // Mask email
  const [local, domain] = value.split('@');
  const maskedLocal = local.charAt(0) + '*'.repeat(local.length - 2) + local.charAt(local.length - 1);
  return `${maskedLocal}@${domain}`;
};

Filter Testing

Patterns for testing custom filters.

// Test custom filters
function testFilters() {
  const testCases = [
    { input: 'hello world', expected: 'Hello World', filter: 'title' },
    { input: 123.456, expected: '$123.46', filter: 'currency' },
    { input: '<script>alert()</script>', expected: '[sanitized]', filter: 'sanitize' }
  ];

  testCases.forEach(testCase => {
    const result = dust.filters[testCase.filter](testCase.input);
    console.log(`Filter ${testCase.filter}: ${result === testCase.expected ? 'PASS' : 'FAIL'}`);
  });
}

// Unit test helper for filters
function testFilter(filterName, testCases) {
  const filter = dust.filters[filterName];
  if (!filter) {
    console.error(`Filter ${filterName} not found`);
    return;
  }

  testCases.forEach((testCase, index) => {
    const result = filter(testCase.input);
    const passed = result === testCase.expected;
    console.log(`Test ${index + 1}: ${passed ? 'PASS' : 'FAIL'}`);
    if (!passed) {
      console.log(`  Expected: ${testCase.expected}`);
      console.log(`  Actual: ${result}`);
    }
  });
}

Install with Tessl CLI

npx tessl i tessl/npm-dustjs-linkedin

docs

cli.md

compilation.md

context.md

filters.md

helpers.md

index.md

parsing.md

rendering.md

tile.json