Engine API
Core engine classes and helper functions. No DOM dependency — safe for Node.js.
Entry points
alap (main entry)
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.
import {
AlapEngine,
ExpressionParser,
mergeConfigs,
validateConfig,
sanitizeUrl,
validateRegex,
} from 'alap/core';AlapEngine
Stateful wrapper around the expression parser.
const engine = new AlapEngine(config);| Method | Signature | Description |
|---|---|---|
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) => void | Replace configuration |
clearCache() | () => void | Clear cached generate protocol results |
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.
const parser = new ExpressionParser(config);
const ids = parser.query('.coffee + .nyc');Utility functions
mergeConfigs(...configs)
Compose multiple configs. Later configs win on key collision.
const merged = mergeConfigs(baseConfig, overrides, moreOverrides);Merges settings, macros, allLinks, searchPatterns independently. Returns a new object. Filters prototype-pollution keys (__proto__, constructor, prototype).
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: 5000validateConfig(raw)
Sanitize an untrusted config (from a remote API or JSON file).
const config = validateConfig(JSON.parse(jsonString));- Validates structure (
allLinksmust be an object, links must haveurlstrings) - 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.
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.
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).
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.
buildMenuList(links, options)
Pure DOM builder. Returns a <ul> or <ol> element.
| Option | Type | Description |
|---|---|---|
listType | string | 'ul' or 'ol' |
maxVisibleItems | number | Scroll after N items |
globalHooks | string[] | Default hooks from settings.hooks |
liAttributes | Record<string, string> | Extra attributes on <li> |
aAttributes | Record<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.
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.
injectExistingUrl(links, trigger, mode)
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.