Skip to content

Utility Functions

Helper classes and APIs for managing security, custom markup insertion, and programmatic reactivity.

Creates a SafeHtml wrapper around a template literal, allowing you to build raw HTML content safely. Parameters inserted are automatically escaped unless they are instances of SafeHtml.

import { html } from 'avenx-core/runtime';
const userContent = "<script>alert('xss')</script>";
const element = html`<div class="content">${userContent}</div>`;
// Output escapes userContent safely!

A wrapper class designating that a string is verified and safe for raw output. Evaluated directly without escaping inside {{{ ... }}} expressions.

Internal utility class providing character replacement mappings to prevent code injections:

const escaper = new HtmlEscaper();
escaper.escape('<h1>Text</h1>');
// Returns: &lt;h1&gt;Text&lt;/h1&gt;

A utility class used to escape and clean up templates and dynamic HTML tags by stripping dangerous elements/attributes while preserving safe markup.

import { Sanitizer } from 'avenx-core/runtime';
const sanitizer = new Sanitizer(config);
  • config (optional): An object to customize the allowed HTML tags and attributes.
    • allowedTags (string[]): Custom array of allowed tag names. Defaults to a standard safe set of elements (e.g., div, span, p, a, img, etc.).
    • allowedAttributes (Record<string, string[]>): Custom mapping of tag names to allowed attribute arrays. Use * to specify attributes allowed globally on all elements.

Sanitizes an input string containing HTML by filtering it against the allowed tags and attributes configuration. Dangerous elements (like <script>, <style>, <iframe>, etc.) and unsafe URL protocols (like javascript:, data: except for safe image data) are stripped.

Parameters:

  • html (any): The raw content to sanitize (coerced to a string).

Returns:

  • string: The sanitized, safe HTML string.

Example

import { Sanitizer } from 'avenx-core/runtime';
const sanitizer = new Sanitizer();
const dirtyHtml = '<div>Hello <script>alert("xss")</script> <a href="javascript:alert(1)">World</a></div>';
const cleanHtml = sanitizer.sanitize(dirtyHtml);
console.log(cleanHtml);
// Output: <div>Hello <a>World</a></div>

Formats an Avenx error/warning template into a console-ready string without throwing. Use this when you want to log or report a framework message yourself.

import { AvenxErrorCodes, formatMessage } from 'avenx-core/runtime';
logger.warn(formatMessage(AvenxErrorCodes.COMPONENT_INJECT_KEY_NOT_FOUND, 'theme'));
// => [AVX_W15] Inject key "theme" was not found in the component provide/inject tree.
Param Type Description
code string An AvenxErrorCodes value (e.g. AVX_W15)
...args any[] Values substituted for {0}, {1}, … placeholders in AvenxErrorMessages

Returns: string in the form [`code`] formatted message.

Unlike constructing new AvenxError(code, ...args), formatMessage never throws—it only builds the text for logger.warn, telemetry, or custom UI.

Avenx-JS exposes APIs for programmatically creating reactive state objects and observing reactive values.

The core reactivity APIs include StateFactory, AvenxWatcher, and the AvenxComponent.watch() instance method.

StateFactory creates reactive proxy objects from regular JavaScript objects.

import { StateFactory } from 'avenx-core/runtime';
const stateFactory = new StateFactory();

The constructor optionally accepts a proxy handler factory class.

new StateFactory(handlerFactoryClass);
  • handlerFactoryClass (optional): The factory class used to create proxy handlers. Defaults to ProxyHandlerFactory.

Creates and returns a reactive proxy for the provided state object.

const state = stateFactory.create(initialState, options);

Parameters

  • initialState (object, optional): The initial state object to make reactive. Defaults to an empty object.
  • options (object, optional): Configuration options passed to the proxy handler factory. Defaults to an empty object.

Returns

  • Proxy: A reactive proxy around the provided state object.

If initialState is already an Avenx reactive proxy, create() returns the existing proxy instead of wrapping it in another proxy.

Example

import { StateFactory } from 'avenx-core/runtime';
const stateFactory = new StateFactory();
const state = stateFactory.create({
count: 0,
user: {
name: 'Avenx User',
},
});
state.count++;
state.user.name = 'Updated User';

The options object passed to create(initialState, options) configures the behavior of the created reactive proxy and its underlying ProxyHandlerFactory:

Option Type Default Description
onChange Function () => {} A change notification callback executed whenever any reactive property on the target (or nested reactive child objects, arrays, Sets, or Maps) is modified, set, or deleted.
computedKeys Array<String> [] An array of property names to treat as dynamic computed properties on the target object.
instance Object null Optional component or context instance reference passed for scope resolution and method binding.
bypassSymbol Boolean false When true, prevents StateFactory from defining the internal non-enumerable __avenx_proxy_ref__ symbol property on the target object.

The onChange option allows developers to attach a change listener to a standalone reactive state object created outside of a component lifecycle.

Whenever a property on the reactive state proxy (or any nested reactive object/array) is modified, set, or deleted, onChange is invoked automatically. This enables building custom state management stores, state persistence sync (e.g. with localStorage), or external event logs.

Example: Standalone Reactive Store with onChange Persistence

Section titled “Example: Standalone Reactive Store with onChange Persistence”
import { StateFactory } from 'avenx-core/runtime';
const stateFactory = new StateFactory();
// Load initial state from localStorage or fallback defaults
const savedState = JSON.parse(localStorage.getItem('app_settings') || '{}');
const settingsState = stateFactory.create(
{
theme: savedState.theme || 'dark',
notifications: savedState.notifications ?? true,
user: {
name: 'Alice',
},
},
{
onChange() {
// Sync state updates to localStorage whenever any property changes
localStorage.setItem('app_settings', JSON.stringify({
theme: settingsState.theme,
notifications: settingsState.notifications,
user: settingsState.user,
}));
console.log('Settings persisted to localStorage:', settingsState);
},
}
);
// Mutating top-level or nested properties automatically triggers onChange
settingsState.theme = 'light'; // Logs and saves to localStorage
settingsState.user.name = 'Bob'; // Triggers onChange for nested mutations

AvenxWatcher observes values returned by reactive getter functions. During getter evaluation, the watcher tracks accessed reactive properties and responds when those dependencies change.

import { AvenxWatcher } from 'avenx-core/runtime';
const watcher = new AvenxWatcher(getter, callback, options);

Parameters

  • getter (function): A function that returns the reactive value or expression to observe.
  • callback (function | null, optional): Called when the watched value changes. The callback receives the new value and previous value.
  • options (object, optional): Configuration options controlling watcher behavior.
{
immediate: true;
}

When true, the callback runs immediately after the initial value is evaluated.

The initial callback receives the current value as the first argument and undefined as the previous value.

{
lazy: true;
}

When true, the initial getter evaluation is postponed until the watcher is evaluated.

  • getter — The reactive evaluation function supplied to the constructor.
  • callback — The callback function invoked when the watched value changes.
  • options — The watcher configuration object.
  • deps — A Set containing the reactive dependencies tracked by the watcher.
  • dirty — A boolean indicating whether a lazy watcher needs to be re-evaluated.
  • value — The currently stored value returned by the getter.

Evaluates the getter inside the active watcher context and tracks reactive dependencies.

const value = watcher.get();

Evaluates a lazy watcher when it is dirty and returns the stored value.

const value = watcher.evaluate();

Re-evaluates the watcher when one of its tracked dependencies changes.

For non-lazy watchers, the callback runs when the value changes or when the evaluated value is an object.

For lazy watchers, the watcher is marked as dirty.

Removes the watcher from all tracked dependencies and clears its dependency collection.

watcher.teardown();

Use teardown() when manually managing an AvenxWatcher instance that is no longer needed.

Every AvenxComponent instance provides a watch() method for observing reactive values programmatically.

this.watch(getter, callback, options);

Parameters

  • getter (function): A function returning the reactive value to observe.
  • callback (function): Called when the watched value changes. Receives newValue and oldValue.
  • options (object, optional): Watcher configuration options such as immediate and lazy.

Returns

  • AvenxWatcher: The watcher instance created for the component.

Watchers registered with this.watch() are stored by the component and automatically cleaned up when the component is unmounted.

The getter function determines which reactive state properties should be tracked.

import { AvenxComponent } from 'avenx-core/runtime';
class CounterComponent extends AvenxComponent {
constructor() {
super({
count: 0,
});
this.watch(
() => this.state.count,
(newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
},
);
}
}

Whenever state.count changes, the getter is re-evaluated and the callback receives the new and previous values.

Set immediate to true to execute the callback immediately with the initial value.

this.watch(
() => this.state.count,
(newValue, oldValue) => {
console.log('Current count:', newValue);
},
{
immediate: true,
},
);

During the initial callback, oldValue is undefined.

Watchers track reactive properties that are accessed while the getter executes. This allows the watched dependency to change dynamically.

this.watch(
() => {
return this.state.usePrimary ? this.state.primaryValue : this.state.secondaryValue;
},
(newValue, oldValue) => {
console.log('Selected value changed:', newValue, oldValue);
},
);

The getter observes usePrimary and accesses either primaryValue or secondaryValue based on the current state.

Watchers created with this.watch() are automatically cleaned up when the component is unmounted.

When creating an AvenxWatcher manually, call teardown() when the watcher is no longer required:

const watcher = new AvenxWatcher(
() => state.count,
(newValue, oldValue) => {
console.log(newValue, oldValue);
},
);
watcher.teardown();

The AvenxLogger class provides Avenx-JS’s built-in logging system. It supports configurable log levels, custom formatting, and custom transports, making it suitable for both development and production environments.

A shared logger instance is also exported from the runtime for convenient use throughout your application.

import { AvenxLogger, logger } from "avenx-core/runtime";
  • AvenxLogger creates a new logger instance with custom configuration.
  • logger is the default global logger instance provided by Avenx-JS.

const logger = new AvenxLogger(config);
Option Type Default Description
level string "info" Minimum log level to output.
silent boolean false Disables all logging when enabled.
formatter Function defaultFormatter Formats log messages before they are passed to transports.
transports Array [consoleTransport] Collection of custom transport targets.

Supported log levels are listed below in ascending order of severity.

Level Description
trace Detailed diagnostic information.
debug Development and debugging messages.
info General application information.
warn Warning messages.
error Error messages.
fatal Critical failures.
off Disables logging.
silent Alias for off.

The logger only outputs messages whose severity is greater than or equal to the configured log level.


Every logger instance provides the following methods.

Method Description
trace(...args) Logs a trace message.
debug(...args) Logs a debug message.
info(...args) Logs an informational message.
log(...args) Alias for info().
warn(...args) Logs a warning.
error(...args) Logs an error.
fatal(...args) Logs a fatal error.

The runtime exports a shared logger instance that can be used anywhere in your application.

import { logger } from "avenx-core/runtime";
logger.info("Application started.");
logger.warn("Cache miss.");
logger.error("Failed to load configuration.");

This is the same instance that AvenxApp configures when you pass a logging option to its constructor, so logger.configure() calls and the logging option affect the same shared state:

const app = new AvenxApp({
target: "#app",
logging: { level: "debug" },
});

Note that this is unrelated to the logging option in avenx.config.json, which only controls the CLI’s own build-time output, not this runtime logger.


Create a separate logger instance with its own configuration.

import { AvenxLogger } from "avenx-core/runtime";
const logger = new AvenxLogger({
level: "debug"
});
logger.debug("Debug logging enabled.");

Logger instances can be reconfigured at runtime.

import { logger } from "avenx-core/runtime";
logger.configure({
level: "trace"
});
logger.trace("Verbose logging is now enabled.");

You can update one or more configuration options at any time using configure().

If level is set to a value that isn’t one of the supported log levels, configure() logs a warning and falls back to "info".


A formatter receives the log level and the original arguments, then returns the formatted arguments passed to each transport.

const formatter = (level, args) => [
`[MyApp] [${level.toUpperCase()}]`,
...args
];
const logger = new AvenxLogger({
formatter
});

By default, AvenxLogger uses consoleTransport, which dispatches each level to a console method: fatal logs via console.error, trace logs via console.debug, and every other level logs via the matching console method (e.g. info → console.info), falling back to console.log if no matching method exists.

Custom transports allow log messages to be forwarded to destinations other than the browser console.

A transport may be either:

  • an object exposing a log() method
  • a function
const transport = {
log(level, formattedArgs, rawArgs) {
console.log("Sending log:", formattedArgs);
}
};
const logger = new AvenxLogger({
transports: [transport]
});
const transport = (level, formattedArgs, rawArgs) => {
console.log(level, formattedArgs);
};
const logger = new AvenxLogger({
transports: [transport]
});

import { logger } from "avenx-core/runtime";
logger.info("Application initialized.");
logger.debug("Loaded configuration.");
logger.warn("Using default settings.");
logger.error("Unable to connect to the server.");
logger.fatal("Unexpected unrecoverable error.");

LruCache is a Least Recently Used (LRU) cache implementation built using JavaScript Map’s key insertion order preservation. It is used internally for features like page keep-alive caching and is exported from avenx-core/runtime for application-level data caching.

import { LruCache } from 'avenx-core/runtime';
const cache = new LruCache(limit, onEvict);
Parameter Type Default Description
limit number Required Maximum number of items allowed in the cache. Must be a positive number (> 0).
onEvict (key: string, value: any) => void null Optional callback function invoked whenever an item is evicted due to exceeding capacity.

Property Type Description
limit number Capacity limit of the cache instance.
size number Getter returning the current count of items stored in the cache.

Retrieves an item from the cache and updates its recency to make it the most recently used item.

  • Parameters: key: string
  • Returns: any — The cached item, or undefined if the key does not exist.

Inserts or updates a key-value pair in the cache. If the cache size reaches the specified limit, the least recently used (LRU) item is evicted and the optional onEvict callback is triggered.

  • Parameters:
    • key: string — Item identifier.
    • value: any — Data payload to cache.
  • Returns: void

Checks whether a key exists in the cache without altering its recency ordering.

  • Parameters: key: string
  • Returns: boolean — true if the key exists, otherwise false.

Removes a specific item from the cache.

  • Parameters: key: string
  • Returns: boolean — true if the item existed and was removed, otherwise false.

Removes all items from the cache.

  • Returns: void

import { LruCache } from 'avenx-core/runtime';
// Create a cache holding up to 3 items with an eviction listener
const userCache = new LruCache(3, (evictedKey, evictedValue) => {
console.log(`Cache full. Evicted key "${evictedKey}":`, evictedValue);
});
// Store items
userCache.set('user:101', { name: 'Alice', role: 'admin' });
userCache.set('user:102', { name: 'Bob', role: 'editor' });
userCache.set('user:103', { name: 'Charlie', role: 'viewer' });
console.log(userCache.size); // 3
// Accessing 'user:101' refreshes its recency
const user = userCache.get('user:101');
console.log(user.name); // 'Alice'
// Inserting a 4th item triggers LRU eviction of 'user:102' (since 'user:101' was recently accessed)
userCache.set('user:104', { name: 'Diana', role: 'manager' });
// Output: Cache full. Evicted key "user:102": { name: 'Bob', role: 'editor' }
console.log(userCache.has('user:102')); // false
console.log(userCache.has('user:101')); // true