or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

api

features

charts

charts.mdconditional-formatting.mdvisualizations.md
authorization.mdchangesets.mdcharts-as-code.mdcompiler.mddashboards.mddbt.mdee-features.mdformatting.mdparameters.mdpivot.mdprojects-spaces.mdsql-runner.mdtemplating.mdwarehouse.md
index.md
tile.json

constants.mddocs/api/utilities/specialized/

Constants

Application-wide constants used throughout the Lightdash platform.

Capabilities

This module provides the following functionality:

Session Storage Keys

enum SessionStorageKeys {
  SEND_NOW_SCHEDULER_FILTERS = 'sendNowSchedulerFilters',
  SEND_NOW_SCHEDULER_PARAMETERS = 'sendNowSchedulerParameters',
}

Enumeration of session storage keys used by the application for storing temporary state.

Example:

import { SessionStorageKeys } from '@lightdash/common';

// Store scheduler filters in session storage
sessionStorage.setItem(
  SessionStorageKeys.SEND_NOW_SCHEDULER_FILTERS,
  JSON.stringify(filters)
);

// Retrieve scheduler parameters
const params = sessionStorage.getItem(
  SessionStorageKeys.SEND_NOW_SCHEDULER_PARAMETERS
);

SQL Runner Constants

const MAX_SAFE_INTEGER: number;  // Value: 2_147_483_647

Maximum safe 32-bit signed integer used for SQL query result limits to avoid issues with certain warehouses like Redshift.

Example:

import { MAX_SAFE_INTEGER } from '@lightdash/common';

// Use for "all results" queries
const query = {
  ...metricQuery,
  limit: MAX_SAFE_INTEGER  // Fetch all results
};

Metrics Tree Constants

const MAX_METRICS_TREE_NODE_COUNT: number;

Maximum number of nodes allowed in a metrics tree catalog.

Example:

import { MAX_METRICS_TREE_NODE_COUNT } from '@lightdash/common';

if (nodeCount > MAX_METRICS_TREE_NODE_COUNT) {
  throw new Error(`Metrics tree exceeds maximum node count of ${MAX_METRICS_TREE_NODE_COUNT}`);
}

Examples

Session Storage Management

import { SessionStorageKeys } from '@lightdash/common';

// Scheduler Filters Storage
interface SchedulerFilters {
  dateRange: { start: Date; end: Date };
  dashboards: string[];
  users: string[];
}

function saveSchedulerFilters(filters: SchedulerFilters) {
  sessionStorage.setItem(
    SessionStorageKeys.SEND_NOW_SCHEDULER_FILTERS,
    JSON.stringify(filters)
  );
}

function loadSchedulerFilters(): SchedulerFilters | null {
  const stored = sessionStorage.getItem(
    SessionStorageKeys.SEND_NOW_SCHEDULER_FILTERS
  );

  if (!stored) {
    return null;
  }

  try {
    return JSON.parse(stored);
  } catch (error) {
    console.error('Failed to parse scheduler filters:', error);
    return null;
  }
}

function clearSchedulerFilters() {
  sessionStorage.removeItem(SessionStorageKeys.SEND_NOW_SCHEDULER_FILTERS);
}

Scheduler Parameters

import { SessionStorageKeys } from '@lightdash/common';

interface SchedulerParameters {
  format: 'pdf' | 'png' | 'csv';
  recipients: string[];
  message?: string;
}

function saveSchedulerParameters(params: SchedulerParameters) {
  sessionStorage.setItem(
    SessionStorageKeys.SEND_NOW_SCHEDULER_PARAMETERS,
    JSON.stringify(params)
  );
}

function getSchedulerParameters(): SchedulerParameters | null {
  const stored = sessionStorage.getItem(
    SessionStorageKeys.SEND_NOW_SCHEDULER_PARAMETERS
  );

  return stored ? JSON.parse(stored) : null;
}

// Usage in scheduler UI
function SchedulerForm() {
  const [params, setParams] = useState<SchedulerParameters>(() => {
    // Load from session storage on mount
    return getSchedulerParameters() || {
      format: 'pdf',
      recipients: [],
    };
  });

  useEffect(() => {
    // Save to session storage on change
    saveSchedulerParameters(params);
  }, [params]);

  // ... form implementation
}

SQL Query Limits

import { MAX_SAFE_INTEGER } from '@lightdash/common';

// Fetch all results without limit
function fetchAllResults(metricQuery: MetricQuery) {
  return executeQuery({
    ...metricQuery,
    limit: MAX_SAFE_INTEGER,
  });
}

// Apply safe limit
function applySafeLimit(requestedLimit: number): number {
  if (requestedLimit === -1 || requestedLimit > MAX_SAFE_INTEGER) {
    // Use max safe integer for "unlimited" requests
    return MAX_SAFE_INTEGER;
  }

  return Math.min(requestedLimit, MAX_SAFE_INTEGER);
}

// SQL Runner query
async function runSqlQuery(sql: string, limit?: number) {
  const safeLimit = limit ? applySafeLimit(limit) : 500;

  const query = `
    ${sql}
    LIMIT ${safeLimit}
  `;

  return await warehouse.execute(query);
}

Metrics Tree Validation

import { MAX_METRICS_TREE_NODE_COUNT } from '@lightdash/common';

interface MetricsTreeNode {
  id: string;
  name: string;
  children: MetricsTreeNode[];
}

function countNodes(node: MetricsTreeNode): number {
  let count = 1; // Count this node

  for (const child of node.children) {
    count += countNodes(child);
  }

  return count;
}

function validateMetricsTree(root: MetricsTreeNode): void {
  const totalNodes = countNodes(root);

  if (totalNodes > MAX_METRICS_TREE_NODE_COUNT) {
    throw new Error(
      `Metrics tree has ${totalNodes} nodes, exceeding the maximum of ${MAX_METRICS_TREE_NODE_COUNT}`
    );
  }
}

// Usage
try {
  validateMetricsTree(metricsTreeRoot);
  await saveMetricsTree(metricsTreeRoot);
} catch (error) {
  console.error('Invalid metrics tree:', error.message);
}

React Hook for Session Storage

import { SessionStorageKeys } from '@lightdash/common';
// External dependency - Install separately: npm install react
import { useState, useEffect } from 'react';

function useSessionStorage<T>(
  key: SessionStorageKeys,
  initialValue: T
): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = sessionStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error('Error loading from session storage:', error);
      return initialValue;
    }
  });

  const setValue = (value: T) => {
    try {
      setStoredValue(value);
      sessionStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error('Error saving to session storage:', error);
    }
  };

  return [storedValue, setValue];
}

// Usage
function SchedulerComponent() {
  const [filters, setFilters] = useSessionStorage(
    SessionStorageKeys.SEND_NOW_SCHEDULER_FILTERS,
    { dashboards: [], users: [] }
  );

  return (
    <div>
      {/* Component implementation */}
    </div>
  );
}

API Endpoint with Safe Limits

import { MAX_SAFE_INTEGER } from '@lightdash/common';

app.get('/api/sql-runner/query', async (req, res) => {
  const { sql, limit } = req.body;

  // Apply safe limit
  const safeLimit = limit === undefined
    ? 500
    : Math.min(limit, MAX_SAFE_INTEGER);

  try {
    const results = await warehouse.query(sql, { limit: safeLimit });

    res.json({
      results,
      limit: safeLimit,
      isLimitReached: results.rows.length >= safeLimit,
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Session Storage Cleanup

import { SessionStorageKeys } from '@lightdash/common';

// Clear all scheduler-related session storage
function clearSchedulerSession() {
  sessionStorage.removeItem(SessionStorageKeys.SEND_NOW_SCHEDULER_FILTERS);
  sessionStorage.removeItem(SessionStorageKeys.SEND_NOW_SCHEDULER_PARAMETERS);
}

// Clear on logout
function handleLogout() {
  clearSchedulerSession();
  // ... other logout logic
}

// Clear on navigation away from scheduler
function useSchedulerCleanup() {
  useEffect(() => {
    return () => {
      // Cleanup on unmount
      clearSchedulerSession();
    };
  }, []);
}

Testing

import {
  SessionStorageKeys,
  MAX_SAFE_INTEGER,
  MAX_METRICS_TREE_NODE_COUNT,
} from '@lightdash/common';

describe('Constants', () => {
  describe('SessionStorageKeys', () => {
    it('should have correct filter key', () => {
      expect(SessionStorageKeys.SEND_NOW_SCHEDULER_FILTERS).toBe(
        'sendNowSchedulerFilters'
      );
    });

    it('should have correct parameters key', () => {
      expect(SessionStorageKeys.SEND_NOW_SCHEDULER_PARAMETERS).toBe(
        'sendNowSchedulerParameters'
      );
    });
  });

  describe('MAX_SAFE_INTEGER', () => {
    it('should be 32-bit signed integer max', () => {
      expect(MAX_SAFE_INTEGER).toBe(2_147_483_647);
    });

    it('should handle query limits', () => {
      const limit = applySafeLimit(Number.MAX_VALUE);
      expect(limit).toBe(MAX_SAFE_INTEGER);
    });
  });

  describe('MAX_METRICS_TREE_NODE_COUNT', () => {
    it('should be defined', () => {
      expect(MAX_METRICS_TREE_NODE_COUNT).toBeDefined();
      expect(typeof MAX_METRICS_TREE_NODE_COUNT).toBe('number');
    });
  });
});

Use Cases

Session Storage Keys

  • Scheduler State: Persist scheduler filters and parameters across page reloads
  • Form State: Maintain form state during navigation
  • User Preferences: Store temporary user preferences

MAX_SAFE_INTEGER

  • SQL Queries: Prevent overflow errors in warehouses
  • Result Limits: Safe upper bound for result set sizes
  • Pagination: Maximum safe page size

MAX_METRICS_TREE_NODE_COUNT

  • Tree Validation: Prevent excessively large metrics trees
  • Performance: Ensure UI can handle tree size
  • Resource Limits: Prevent resource exhaustion

Related Constants

  • Test Fixtures: See test-fixtures.md for seed data constants
  • Role Constants: See types documentation for role enums
  • Type Constants: See types documentation for dimension and metric types

Notes

  • Session Storage: Use for temporary state that shouldn't persist after session ends
  • Local Storage: Consider for persistent user preferences
  • Safe Integers: Important for warehouse compatibility (especially Redshift)
  • Tree Limits: Balance between functionality and performance