Skip to content

Engine API

Core engine classes and helper functions. No DOM dependency — safe for Node.js.

Entry points

alap (main entry)

typescript
import {
  AlapEngine,
  ExpressionParser,
  mergeConfigs,
  AlapUI,
  AlapLinkElement,
  registerConfig,
  updateRegisteredConfig,
  defineAlapLink,
} from 'alap';

alap/core

Engine and utilities only. No DOM. Safe for Node.js, tests, and build tools.

typescript
import {
  AlapEngine,
  ExpressionParser,
  mergeConfigs,
  validateConfig,
  sanitizeUrl,
  validateRegex,
} from 'alap/core';

AlapEngine

Stateful wrapper around the expression parser.

typescript
const engine = new AlapEngine(config);
MethodSignatureDescription
query()(expression: string, anchorId?: string) => string[]Expression to deduplicated array of item IDs
resolve()(expression: string, anchorId?: string) => ResolvedLink[]Expression to full link objects
resolveAsync()(expression: string, anchorId?: string) => Promise<ResolvedLink[]>Pre-resolves generate protocols, then evaluates
getLinks()(ids: string[]) => ResolvedLink[]IDs to full link objects
updateConfig()(config: AlapConfig) => voidReplace configuration
clearCache()() => voidClear cached generate protocol results
typescript
import { AlapEngine } from 'alap/core';

const engine = new AlapEngine(config);

// Get just the IDs
const ids = engine.query('.coffee + .sf');
// → ['bluebottle']

// Get full link objects
const links = engine.resolve('.coffee');
// → [{ id: 'bluebottle', url: '...', label: 'Blue Bottle', tags: ['coffee', 'sf'] }, ...]

// Use anchorId for bare @ macros
const fromMacro = engine.query('@', 'nycbridges');

// Async: expressions with generate protocols (e.g. :web:)
const books = await engine.resolveAsync(':web:books:architecture:limit=5:');

// Update config at runtime
engine.updateConfig(newConfig);

ExpressionParser

Low-level stateless parser. Use AlapEngine unless you need direct parser access.

typescript
const parser = new ExpressionParser(config);
const ids = parser.query('.coffee + .nyc');

Utility functions

mergeConfigs(...configs)

Compose multiple configs. Later configs win on key collision.

typescript
const merged = mergeConfigs(baseConfig, overrides, moreOverrides);

Merges settings, macros, allLinks, searchPatterns independently. Returns a new object. Filters prototype-pollution keys (__proto__, constructor, prototype).

typescript
import { mergeConfigs } from 'alap/core';

const baseConfig = {
  settings: { menuTimeout: 5000 },
  allLinks: {
    home: { url: '/', label: 'Home', tags: ['nav'] },
    about: { url: '/about', label: 'About', tags: ['nav'] },
  },
};

const blogOverrides = {
  allLinks: {
    latest: { url: '/blog/latest', label: 'Latest Post', tags: ['blog'] },
  },
  macros: {
    navigation: { linkItems: '.nav' },
  },
};

const blogConfig = mergeConfigs(baseConfig, blogOverrides);
// blogConfig.allLinks has: home, about, latest
// blogConfig.macros has: navigation
// blogConfig.settings has: menuTimeout: 5000

validateConfig(raw)

Sanitize an untrusted config (from a remote API or JSON file).

typescript
const config = validateConfig(JSON.parse(jsonString));
  • Validates structure (allLinks must be an object, links must have url strings)
  • Sanitizes URLs via sanitizeUrl()
  • Removes dangerous regex patterns via validateRegex()
  • Filters prototype-pollution keys
  • Strips unknown fields from link entries
  • Returns a sanitized copy. Throws if structurally invalid.
typescript
import { validateConfig } from 'alap/core';

const response = await fetch('https://api.example.com/configs/main');
const raw = await response.json();

try {
  const config = validateConfig(raw);
  registerConfig(config);
} catch (e) {
  console.error('Invalid config:', e.message);
}

sanitizeUrl(url)

Block dangerous URI schemes.

typescript
sanitizeUrl('https://example.com')     // → 'https://example.com'
sanitizeUrl('javascript:alert(1)')      // → 'about:blank'
sanitizeUrl('data:text/html,...')       // → 'about:blank'
sanitizeUrl('/relative/path')           // → '/relative/path'
sanitizeUrl('mailto:user@example.com')  // → 'mailto:user@example.com'

Allows: http, https, mailto, tel, relative URLs. Blocks: javascript, data, vbscript, blob.

validateRegex(pattern)

Check a regex pattern for catastrophic backtracking (ReDoS).

typescript
validateRegex('(a+)+$')   // → { safe: false, reason: 'Nested quantifier detected...' }
validateRegex('bridge')    // → { safe: true }
validateRegex('\\w+')      // → { safe: true }

Shared utilities

Internal building blocks used by all adapters. Exported for custom integrations.

Pure DOM builder. Returns a <ul> or <ol> element.

OptionTypeDescription
listTypestring'ul' or 'ol'
maxVisibleItemsnumberScroll after N items
globalHooksstring[]Default hooks from settings.hooks
liAttributesRecord<string, string>Extra attributes on <li>
aAttributesRecord<string, string>Extra attributes on <a>

handleMenuKeyboard(event, items, activeElement, closeMenu, options?)

Stateless keyboard nav handler. Returns true if the event was consumed.

DismissTimer

Auto-dismiss timer. Call start() when mouse leaves, stop() when it re-enters.

typescript
const timer = new DismissTimer(5000, () => { menu.hidden = true; });

menu.addEventListener('mouseenter', () => timer.stop());
menu.addEventListener('mouseleave', () => timer.start());

resolveExistingUrlMode(trigger, globalSetting)

Resolves the existingUrl mode for a trigger element.

Extracts href from a trigger and injects a synthetic link item. Returns a new array.

Building a custom adapter

These utilities compose into a custom adapter in ~30 lines. See Events for the full event type definitions.