@travetto/runtime
v8.0.1
Published
Runtime for travetto applications.
Maintainers
Readme
Runtime
Runtime for travetto applications.
Install: @travetto/runtime
npm install @travetto/runtime
# or
yarn add @travetto/runtimeRuntime is the foundation of all Travetto applications. It is intended to be a minimal application set, as well as support for commonly shared functionality. It has support for the following key areas:
- Runtime Context
- Environment Support
- Standard Error Support
- Console Management
- Resource Access
- Encoding and Decoding Utilities
- Binary Utilities
- JSON Utilities
- Common Utilities
- Time Utilities
- Process Execution
- Shutdown Management
- Path behavior
Runtime Context
While running any code within the framework, there are common patterns/goals for interacting with the underlying code repository. These include:
- Determining attributes of the running environment (e.g., name, debug information, production flags)
- Resolving paths within the workspace (e.g. standard, tooling, resourcing, modules)
Code: Runtime Shape
class $Runtime {
constructor(idx: ManifestIndex, resourceOverrides?: Record<string, string>);
/** The role we are running as */
get role(): Role;
/** Are we in production mode */
get production(): boolean;
/** Are we in development mode */
get localDevelopment(): boolean;
/** Get debug value */
get debug(): false | string;
/** Manifest main */
get main(): ManifestContext['main'];
/** Manifest workspace */
get workspace(): ManifestContext['workspace'];
/** Are we running from a mono-root? */
get monoRoot(): boolean;
/** Main source path */
get mainSourcePath(): string;
/** Produce a workspace relative path */
workspaceRelative(...parts: string[]): string;
/** Strip off the workspace path from a file */
stripWorkspacePath(full: string): string;
/** Produce a workspace path for tooling, with '@' being replaced by node_module/name folder */
toolPath(...parts: string[]): string;
/** Resolve single module path */
modulePath(modulePath: string, overrides?: Record<string, string>): string;
/** Resolve resource paths */
resourcePaths(paths: string[] = []): string[];
/** Get source for function */
getSourceFile(handle: Function): string;
/** Get import for function */
getImport(handle: Function): string;
/** Import from a given path */
async importFrom<T = unknown>(location?: string): Promise<T>;
/** Get an install command for a given npm module */
getInstallCommand(pkg: string, production?: boolean): string;
}Class and Function Metadata
For the framework to work properly, metadata needs to be collected about files, classes and functions to uniquely identify them, with support for detecting changes during live reloads. To achieve this, every class is decorated with metadata, including methods, line numbers, and ultimately a unique id stored at Ⲑid.
Environment Support
The functionality we support for testing and retrieving environment information for known environment variables. They can be accessed directly on the Env object, and will return a scoped EnvProp, that is compatible with the property definition. E.g. only showing boolean related fields when the underlying flag supports true or false
Code: Base Known Environment Flags
interface EnvData {
/**
* The node environment we are running in
* @default development
*/
NODE_ENV: 'development' | 'production';
/**
* Outputs all console.debug messages, defaults to off
*/
DEBUG: boolean | string;
/**
* The role we are running as, allows access to additional files from the manifest during runtime.
*/
TRV_ROLE: Role;
/**
* The folders to use for resource lookup
*/
TRV_RESOURCES: string[];
/**
* Resource path overrides
* @private
*/
TRV_RESOURCE_OVERRIDES: Record<string, string>;
/**
* The max time to wait for shutdown to finish after initial SIGINT,
* @default 2s
*/
TRV_SHUTDOWN_WAIT: TimeSpan | number;
/**
* The desired runtime module
*/
TRV_MODULE: string;
/**
* The location of the manifest file
* @default undefined
*/
TRV_MANIFEST: string;
/**
* trvc log level
*/
TRV_BUILD: 'none' | 'info' | 'debug' | 'error' | 'warn';
/**
* Should break on first line of a method when using the @DebugBreak decorator
* @default false
*/
TRV_DEBUG_BREAK: boolean;
}Environment Property
For a given EnvProp, we support the ability to access different properties as a means to better facilitate environment variable usage.
Code: EnvProp Shape
export class EnvProp<T> {
readonly key: string;
constructor(key: string);
/** Set value according to type */
set(value: T | undefined | null): void;
/** Remove value */
clear(): void;
/** Export value */
export(value?: T | undefined | null): Record<string, string>;
/** Read value as string */
get value(): string | undefined;
/** Read value as list */
get list(): string[] | undefined;
/** Read value as object */
get object(): Record<string, string> | undefined;
/** Add values to list */
add(...items: string[]): void;
/** Read value as int */
get int(): number | undefined;
/** Read value as boolean */
get bool(): boolean | undefined;
/** Determine if the underlying value is truthy */
get isTrue(): boolean;
/** Determine if the underlying value is falsy */
get isFalse(): boolean;
/** Determine if the underlying value is set */
get isSet(): boolean;
}Standard Error Support
While the framework is 100 % compatible with standard Error instances, there are cases in which additional functionality is desired. Within the framework we use RuntimeError (or its derivatives) to represent framework errors. This class is available for use in your own projects. Some of the additional benefits of using this class is enhanced error reporting, as well as better integration with other modules (e.g. the Web API module and HTTP status codes).
The RuntimeError takes in a message, and an optional payload and / or error classification. The currently supported error classifications are:
general- General purpose errorssystem- Synonym forgeneraldata- Data format, content, etc are incorrect. Generally correlated to bad input.permission- Operation failed due to lack of permissionsauth- Operation failed due to lack of authenticationmissing- Resource was not found when requestedtimeout- Operation did not finish in a timely mannerunavailable- Resource was unresponsive
Console Management
This module provides logging functionality, built upon console operations.
The supported operations are:
console.errorwhich logs at theERRORlevelconsole.warnwhich logs at theWARNlevelconsole.infowhich logs at theINFOlevelconsole.debugwhich logs at theDEBUGlevelconsole.logwhich logs at theINFOlevel
Note: All other console methods are excluded, specifically trace, inspect, dir, time/timeEnd
How Logging is Instrumented
All of the logging instrumentation occurs at transpilation time. All console.* methods are replaced with a call to a globally defined variable that delegates to the ConsoleManager. This module, hooks into the ConsoleManager and receives all logging events from all files compiled by the Travetto.
A sample of the instrumentation would be:
Code: Sample logging at various levels
export function work() {
console.debug('Start Work');
try {
1 / 0;
} catch (error) {
console.error('Divide by zero', { error });
}
console.debug('End Work');
}Code: Sample After Transpilation
import * as Δfunction from "@travetto/runtime/src/function.js";
import * as Δconsole from "@travetto/runtime/src/console.js";
const Δm_1 = ["@travetto/runtime", "doc/transpile.ts"];
export function work() {
Δconsole.log({ level: "debug", import: Δm_1, line: 2, scope: "work", args: ['Start Work'] });
try {
1 / 0;
}
catch (error) {
Δconsole.log({ level: "error", import: Δm_1, line: 7, scope: "work", args: ['Divide by zero', { error }] });
}
Δconsole.log({ level: "debug", import: Δm_1, line: 9, scope: "work", args: ['End Work'] });
}
Δfunction.registerFunction(work, Δm_1, { hash: 159357293, lines: [1, 10, 2] });Filtering Debug
The debug messages can be filtered using the patterns from the debug. You can specify wild cards to only DEBUG specific modules, folders or files. You can specify multiple, and you can also add negations to exclude specific packages.
Terminal: Sample environment flags
# Debug
$ DEBUG=-@travetto/model npx trv run app
$ DEBUG=-@travetto/registry npx trv run app
$ DEBUG=@travetto/web npx trv run app
$ DEBUG=@travetto/*,-@travetto/model npx trv run appAdditionally, the logging framework will merge debug into the output stream, and supports the standard usage
Terminal: Sample environment flags for standard usage
# Debug
$ DEBUG=express:*,@travetto/web npx trv run webResource Access
The primary access patterns for resources, is to directly request a file, and to resolve that file either via file-system look up or leveraging the Manifest's data for what resources were found at manifesting time.
The FileLoader allows for accessing information about the resources, and subsequently reading the file as text/binary or to access the resource as a Readable stream. If a file is not found, it will throw an RuntimeError with a category of 'notfound'.
The FileLoader also supports tying itself to Env's TRV_RESOURCES information on where to attempt to find a requested resource.
Encoding and Decoding Utilities
The CodecUtil class provides a variety of static methods for encoding and decoding data. When working with JSON data, it also provide security checks to prevent prototype pollution. The utility supports the following formats:
- Hex
- Base64
- UTF8
- UTT8 Encoded JSON
- Base64 Encoded JSON
- New Line Delimited UTF8
Common Utilities
Common utilities used throughout the framework. Currently Util includes:
uuid(len: number)generates a simple uuid for use within the application.allowDenyMatcher(rules[])builds a matching function that leverages the rules as an allow/deny list, where order of the rules matters. Negative rules are prefixed by '!'.hash(text: string, size?: number)produces a full sha512 hash.resolvablePromise()produces aPromiseinstance with theresolveandrejectmethods attached to the instance. This is extremely useful for integrating promises into async iterations, or any other situation in which the promise creation and the execution flow don't always match up.bufferedFileWrite(file:string, content: string)will write the file, using a temporary buffer file to ensure that the entire file is written before being moved to the final location. This helps minimize file watch noise when writing files.
Code: Sample makeTemplate Usage
const tpl = makeTemplate((name: 'age'|'name', value) => `**${name}: ${value}**`);
tpl`{{age:20}} {{name: 'bob'}}</>;
// produces
'**age: 20** **name: bob**'Binary Utilities
The BinaryUtil class provides a unified interface for working with binary data across different formats, especially bridging the gap between Node.js specific types (Buffer, Stream) and Web Standard types (Blob, ArrayBuffer). The framework leverages this to allow for seamless handling of binary data, regardless of the source.
JSON Utilities
The JSONUtil class provides a comprehensive set of utilities for working with JSON data, including serialization, deserialization, encoding, and deep cloning capabilities. The utility handles special types like Date, BigInt, and Error objects seamlessly. Key features include:
fromUTF8(input, config?)- Parse JSON from a UTF-8 stringtoUTF8(value, config?)- Serialize a value to JSON stringtoUTF8Pretty(value)- Serialize with pretty formatting (2-space indent)fromBinaryArray(input)- Parse JSON from binary arraytoBinaryArray(value, config?)- Serialize to binary array (UTF-8 encoded)toBase64(value)- Encode JSON as base64 stringfromBase64(input)- Decode JSON from base64 stringclone(input, config?)- Deep clone objects with optional transformationscloneForTransmit(input)- Clone for transmission with error serializationcloneFromTransmit(input)- Clone from transmission with type restoration
The TRANSMIT_REVIVER automatically restores Date objects and BigInt values during deserialization, making it ideal for transmitting complex data structures across network boundaries.
Time Utilities
TimeUtil contains general helper methods, created to assist with time-based inputs via environment variables, command line interfaces, and other string-heavy based input.
Code: Time Utilities
export class TimeUtil {
/**
* Test to see if a string is valid for relative time
*/
static isTimeSpan(value: string): value is TimeSpan;
/**
* Exposes the ability to create a duration succinctly
*/
static duration(input: TimeSpan | number | string, outputUnit: TimeUnit): number;
/**
* Returns a new date with `amount` units into the future
*/
static fromNow(input: TimeSpan | number | string): Date;
/**
* Returns a pretty timestamp
*/
static asClock(input: TimeSpan | number | string): string;
}Process Execution
ExecUtil exposes getResult as a means to wrap child_process's process object. This wrapper allows for a promise-based resolution of the subprocess with the ability to capture the stderr/stdout.
A simple example would be:
Code: Running a directory listing via ls
import { spawn } from 'node:child_process';
import { ExecUtil } from '@travetto/runtime';
export async function executeListing() {
const final = await ExecUtil.getResult(spawn('ls'));
console.log('Listing', { lines: final.stdout.split('\n') });
}Shutdown Management
Another key lifecycle is the process of shutting down. The framework provides centralized functionality for running operations on graceful shutdown. Primarily used by the framework for cleanup operations, this provides a clean interface for registering shutdown handlers. The code intercepts SIGTERM and SIGUSR2, with a default threshold of 2 seconds. These events will start the shutdown process, but also clear out the pending queue. If a kill signal is sent again, it will complete immediately.
As a registered shutdown handler, you can do.
Code: Registering a shutdown handler
import { ShutdownManager } from '@travetto/runtime';
export function registerShutdownHandler() {
ShutdownManager.signal.addEventListener('abort', () => {
// Do important work, the framework will wait until all async
// operations are completed before finishing shutdown
});
}Path Behavior
To ensure consistency in path usage throughout the framework, imports pointing at node:path and path are rewritten at compile time. These imports are pointing towards Manifest's path implementation. This allows for seamless import/usage patterns with the reliability needed for cross platform support.
