JavaScript API Reference
Fabrik recipes have access to Node.js-compatible APIs (via LLRT) and Fabrik-specific APIs for caching and file operations.
Module Overview
Standard Modules (LLRT)
These modules provide Node.js-compatible APIs:
fs- File system operations (synchronous)fs/promises- File system operations (promise-based)child_process- Process spawningpath- Path manipulation utilitiesbuffer- Buffer and binary data handlingconsole- Console logging
Fabrik Modules
These modules provide Fabrik-specific functionality:
fabrik:cache- Content-addressed cachingfabrik:fs- File utilities (glob, hashing)fabrik:kv- Key-value storage for cache metadata
Fabrik Global Object
The Fabrik global object is automatically available in all recipes and provides core functionality for file operations, process execution, and caching.
Fabrik.readFile(path)
Read a file as a byte array.
Parameters:
path(string): Path to the file
Returns:
Promise<Uint8Array>: File contents as bytes
Example:
const data = await Fabrik.readFile("config.json");
const text = new TextDecoder().decode(data);Fabrik.writeFile(path, data)
Write data to a file.
Parameters:
path(string): Path to the filedata(Uint8Array): Data to write
Returns:
Promise<void>
Example:
const data = new TextEncoder().encode("Hello World");
await Fabrik.writeFile("output.txt", data);Fabrik.exists(path)
Check if a file or directory exists.
Parameters:
path(string): Path to check
Returns:
Promise<boolean>:trueif exists,falseotherwise
Example:
if (await Fabrik.exists("dist/")) {
console.log("Build output exists");
}Fabrik.glob(pattern)
Find files matching a glob pattern.
Parameters:
pattern(string): Glob pattern
Returns:
Promise<string[]>: Array of matching file paths
Example:
const tsFiles = await Fabrik.glob("src/**/*.ts");
console.log(`Found ${tsFiles.length} TypeScript files`);Fabrik.exec(command, args)
Execute a command and return the exit code.
Parameters:
command(string): Command to executeargs(string[], optional): Command arguments
Returns:
Promise<number>: Exit code (0 = success)
Example:
const exitCode = await Fabrik.exec("npm", ["run", "build"]);
if (exitCode !== 0) {
throw new Error(`Build failed with exit code ${exitCode}`);
}TIP
For commands that need stdout/stderr capture, use LLRT's child_process module.
Fabrik.hashFile(path)
Compute SHA256 hash of a file.
Parameters:
path(string): Path to the file
Returns:
Promise<string>: SHA256 hash as hex string (64 characters)
Example:
const hash = await Fabrik.hashFile("package-lock.json");
console.log(`Package lock hash: ${hash.slice(0, 8)}`);Fabrik.cache
Low-level cache operations object. Contains:
Fabrik.cache.get(hash)- Get artifact from cacheFabrik.cache.put(hash, data)- Store artifact in cacheFabrik.cache.has(hash)- Check if artifact exists
NOTE
These are low-level APIs. Most recipes should use runCached() from fabrik:cache instead.
fabrik:cache
Content-addressed caching APIs for recipe optimization.
runCached(action, options)
Run an action only if cache miss. Automatically handles cache checking, action execution, and output archiving.
Parameters:
action(Function): Async function to execute on cache missoptions(Object): Cache configurationinputs(string[]): Input file patterns (globs) that affect cache keyoutputs(string[]): Output paths (files or directories) to cacheenv(string[]): Environment variable names to include in cache keycacheDir(string, optional): Override cache directory (default:.fabrik/cache)upstream(string[], optional): Override upstream cache serversttl(string, optional): Cache expiration (e.g.,"7d","2h")hashMethod(string, optional): How to hash input files ("content","mtime","size")
Returns:
Promise<Object>:cacheKey(string): Computed cache key (SHA256 hash)cached(boolean):trueif cache hit,falseif cache missrestoredFiles(string[], optional): Files restored on cache hitdurationMs(number, optional): Execution time in milliseconds on cache miss
Example:
import { runCached } from 'fabrik:cache';
const result = await runCached(
async () => {
// Build logic (only runs on cache miss)
const exitCode = await Fabrik.exec("npm", ["run", "build"]);
if (exitCode !== 0) {
throw new Error("Build failed");
}
},
{
inputs: ["src/**/*.ts", "tsconfig.json"],
outputs: ["dist/"],
env: ["NODE_ENV"],
ttl: "7d"
}
);
if (result.cached) {
console.log(`Cache HIT: ${result.cacheKey.slice(0, 8)}`);
console.log(`Restored ${result.restoredFiles.length} files`);
} else {
console.log(`Cache MISS: ${result.cacheKey.slice(0, 8)}`);
console.log(`Built in ${result.durationMs}ms`);
}needsRun(options)
Check if action needs to run based on cache state. Useful when you want to control the execution logic yourself.
Parameters:
options(Object): Same asrunCached(except nooutputsrequired)inputs(string[]): Input file patterns to checkenv(string[]): Environment variables to checkcacheDir(string, optional): Override cache directoryhashMethod(string, optional): Hash method
Returns:
Promise<boolean>:trueif action needs to run (cache miss),falseif cached
Example:
import { needsRun } from 'fabrik:cache';
const shouldRun = await needsRun({
inputs: ["src/**/*.rs", "Cargo.toml"],
env: ["RUSTFLAGS"]
});
if (shouldRun) {
console.log("Source changed, rebuilding...");
const exitCode = await Fabrik.exec("cargo", ["build", "--release"]);
if (exitCode !== 0) {
throw new Error("Build failed");
}
} else {
console.log("Nothing changed, skipping build");
}fabrik:fs
File system utilities for recipes.
glob(pattern)
Find files matching a glob pattern.
Parameters:
pattern(string): Glob pattern (e.g.,"src/**/*.ts","*.json")
Returns:
Promise<string[]>: Array of matching file paths
Example:
import { glob } from 'fabrik:fs';
// Find all TypeScript files
const tsFiles = await glob("src/**/*.ts");
console.log(`Found ${tsFiles.length} TypeScript files`);
// Find all test files
const testFiles = await glob("tests/**/*.test.js");
// Multiple patterns require multiple calls
const configFiles = [
...await glob("*.json"),
...await glob("*.toml"),
...await glob("*.yml")
];Supported glob syntax:
*- Match any characters except/**- Match any characters including/(recursive)?- Match single character[abc]- Match any character in set{a,b}- Match eitheraorb
hashFile(path)
Compute SHA256 hash of a file.
Parameters:
path(string): File path to hash
Returns:
Promise<string>: SHA256 hash as hex string (64 characters)
Example:
import { hashFile } from 'fabrik:fs';
// Check if file changed
const currentHash = await hashFile("package-lock.json");
const previousHash = "abc123..."; // Stored somewhere
if (currentHash !== previousHash) {
console.log("Dependencies changed, reinstalling...");
await Fabrik.exec("npm", ["install"]);
}
// Use hash as cache key component
const buildHash = await hashFile("dist/bundle.js");
console.log(`Build hash: ${buildHash}`);fabrik:kv
Low-level key-value storage for cache metadata.
NOTE
Most recipes should use runCached() or needsRun() instead of accessing KV directly. The KV API is for advanced use cases where you need custom cache key logic.
has(key)
Check if key exists in KV store.
Parameters:
key(string): Key to check
Returns:
Promise<boolean>:trueif key exists,falseotherwise
Example:
import { has } from 'fabrik:kv';
const cacheKey = "build-v1-prod-abc123";
if (await has(cacheKey)) {
console.log("Already built this version");
} else {
console.log("Need to build");
}get(key)
Get value from KV store.
Parameters:
key(string): Key to retrieve
Returns:
Promise<Object | null>: Stored value as object, ornullif key doesn't exist
Example:
import { get } from 'fabrik:kv';
const metadata = await get("build-metadata");
if (metadata) {
console.log("Last build:", metadata);
} else {
console.log("No previous build metadata");
}set(key, value)
Store value in KV store.
Parameters:
key(string): Key to storevalue(any): Value to store (will be JSON serialized)
Returns:
Promise<void>
Example:
import { set } from 'fabrik:kv';
// Store build metadata
await set("build-metadata", {
timestamp: Date.now(),
version: "1.0.0",
commit: "abc123"
});Standard Node.js APIs (via LLRT)
fs (File System)
import { existsSync, readFileSync, writeFileSync } from 'fs';
// Check if file exists
if (existsSync("package.json")) {
console.log("Found package.json");
}
// Read file (synchronous)
const content = readFileSync("config.json", "utf-8");
// Write file (synchronous)
writeFileSync("output.txt", "Hello world");Available functions:
existsSync(path)- Check if file/directory existsreadFileSync(path, encoding)- Read file synchronouslywriteFileSync(path, data)- Write file synchronouslymkdirSync(path, options)- Create directorystatSync(path)- Get file stats- And more...
See LLRT fs documentation for full API.
fs/promises (Async File System)
import { readFile, writeFile, mkdir } from 'fs/promises';
// Read file (async)
const data = await readFile("config.json", "utf-8");
// Write file (async)
await writeFile("output.txt", "Hello world");
// Create directory
await mkdir("build", { recursive: true });Available functions:
readFile(path, encoding)- Read filewriteFile(path, data)- Write filemkdir(path, options)- Create directorystat(path)- Get file stats- And more...
child_process (Process Spawning)
NOTE
LLRT's child_process module is provided for Node.js compatibility but may have different behavior than Node.js. For simpler process execution, consider using the global Fabrik.exec() function.
import { spawn } from 'child_process';
// Spawn process using LLRT's child_process module
// See LLRT documentation for exact API behavior
const child = spawn("npm", ["install"]);Alternative: Fabrik.exec() (recommended)
The global Fabrik.exec() function provides simpler process execution:
// Simpler process execution via Fabrik global
const exitCode = await Fabrik.exec("npm", ["install"]);
if (exitCode === 0) {
console.log("Command succeeded");
} else {
throw new Error(`Command failed with exit code: ${exitCode}`);
}TIP
Fabrik.exec() currently returns only the exit code. Support for capturing stdout/stderr may be added in a future release.
path (Path Utilities)
import { join, basename, dirname, extname } from 'path';
// Join paths
const fullPath = join("src", "components", "Button.tsx");
// => "src/components/Button.tsx"
// Extract filename
const filename = basename("/path/to/file.txt");
// => "file.txt"
// Extract directory
const dir = dirname("/path/to/file.txt");
// => "/path/to"
// Extract extension
const ext = extname("file.txt");
// => ".txt"buffer (Binary Data)
import { Buffer } from 'buffer';
// Create buffer from string
const buf = Buffer.from("Hello", "utf-8");
// Convert buffer to string
const str = buf.toString("utf-8");
// Create buffer from array
const buf2 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);Configuration Discovery
When recipes call runCached() or needsRun(), Fabrik automatically discovers configuration:
- Start from recipe location: Look for
fabrik.tomlin the directory containing the recipe - Traverse up: Walk up parent directories until
fabrik.tomlis found - Global fallback: Use
~/.config/fabrik/config.tomlif no project config found - Runtime override: Parameters passed to API calls take precedence
Configuration precedence (highest to lowest):
- Runtime parameters (passed to
runCached()/needsRun()) - Project
fabrik.toml(discovered from recipe directory) - Global
~/.config/fabrik/config.toml
Example:
// Uses discovered fabrik.toml automatically
await runCached(
async () => { /* action */ },
{ inputs: ["src/**/*.ts"], outputs: ["dist/"] }
);
// Override cache directory at runtime
await runCached(
async () => { /* action */ },
{
inputs: ["src/**/*.ts"],
outputs: ["dist/"],
cacheDir: ".custom-cache" // Override
}
);Complete Example
Here's a comprehensive recipe using multiple APIs:
import { existsSync } from 'fs';
import { join } from 'path';
import { runCached, needsRun } from 'fabrik:cache';
import { glob, hashFile } from 'fabrik:fs';
console.log("Building TypeScript project...");
// Check prerequisites
if (!existsSync("package.json")) {
throw new Error("No package.json found");
}
// Install dependencies (with caching)
const depsChanged = await needsRun({
inputs: ["package.json", "package-lock.json"],
hashMethod: "content"
});
if (depsChanged) {
console.log("Installing dependencies...");
const exitCode = await Fabrik.exec("npm", ["install"]);
if (exitCode !== 0) {
throw new Error("npm install failed");
}
}
// Build TypeScript (with caching)
const buildResult = await runCached(
async () => {
console.log("Compiling TypeScript...");
const exitCode = await Fabrik.exec("npm", ["run", "build"]);
if (exitCode !== 0) {
throw new Error("TypeScript compilation failed");
}
},
{
inputs: ["src/**/*.ts", "tsconfig.json"],
outputs: ["dist/"],
env: ["NODE_ENV"],
ttl: "7d"
}
);
if (buildResult.cached) {
console.log(`✓ Restored from cache (${buildResult.restoredFiles.length} files)`);
} else {
console.log(`✓ Built in ${buildResult.durationMs}ms`);
}
// Verify outputs
const distFiles = await glob("dist/**/*.js");
console.log(`Generated ${distFiles.length} JavaScript files`);
// Compute bundle hash
const bundleHash = await hashFile("dist/bundle.js");
console.log(`Bundle hash: ${bundleHash.slice(0, 8)}`);See Also
- Portable Recipes - Execute recipes from Git repositories
- Standard Recipes - Script recipes with FABRIK annotations
- Configuration Reference - Fabrik configuration options