@fimbul-works/fimbul
v1.3.1
Published
Manage complex computation dependencies with automatic topological sorting and built-in caching
Maintainers
Readme

Fimbul manages complex computation dependencies using directed acyclic graphs (DAGs). Define your computations, declare their dependencies, and let Fimbul handle the rest.
It automatically prevents circular references, sorts computations in the correct execution order, and caches results to eliminate redundant calculations.
Whether you're generating procedural worlds, handling data transformations, or managing complex state calculations, Fimbul helps you build clear, maintainable, and efficient computation chains.
Table of Contents
Features
- 🔍 Type-safe dependency graph management with full type inference and validation
- ⚡ Automatic dependency resolution and caching
- 🔄 Both synchronous and asynchronous computation support
- 🎯 Zero external dependencies
- 🧮 Optimal performance with O(n) worst-case complexity
- 📦 Ultra-lightweight at just 518 bytes minified
Installation
npm install @fimbul-works/fimbul
# or
yarn add @fimbul-works/fimbul
# or
pnpm install @fimbul-works/fimbulQuick Start
import Fimbul from '@fimbul-works/fimbul';
type Params = {
greeting: string;
recipient: string;
};
type Results = {
greeting: string;
punctuation: string;
output: string;
};
const compute = Fimbul<Params, Results>();
// Define computation nodes
compute.define('greeting',
({ greeting, recipient }) => `${greeting} ${recipient}`
);
compute.define('punctuation',
() => '!'
);
compute.define('output',
(params, { greeting, punctuation }) => `${greeting}${punctuation}`,
['greeting', 'punctuation'] // Declare dependencies
);
// Check if nodes exist
console.log(compute.has('output')); // true
console.log(compute.has('missing')); // false
// Get a single result
const output = compute.get('output', {
greeting: 'Hello',
recipient: 'Fimbul'
}); // "Hello Fimbul!"
// Get multiple results at once
const results = compute.getMany(['greeting', 'output'], {
greeting: 'Hello',
recipient: 'Fimbul'
});
// {
// greeting: "Hello Fimbul",
// output: "Hello Fimbul!"
// }Why Fimbul?
Unlike manual dependency management or reactive frameworks, Fimbul gives you:
- Explicit control over computation flow
- Zero overhead - no observers, no subscriptions
- Predictable performance - O(n) complexity with perfect caching
- Type safety - catch errors at compile time, not runtime
Core Concepts
Computation Nodes
Each node in your computation graph represents a discrete calculation unit that:
- Takes input parameters
- Optionally depends on other nodes
- Produces a typed output
- Is computed exactly once per set of parameters
- Can be synchronous or asynchronous
Dependencies
The dependency system is designed for maximum efficiency and safety:
- Explicit dependency declaration prevents hidden dependencies
- Automatic topological sorting ensures correct execution order
- Built-in cycle detection prevents infinite loops
- Smart caching with result reuse
- Type-safe dependency chains
Memory Management
Fimbul is designed for optimal memory usage:
- Only stores function definitions and computed results
- No memory leaks from circular references
- Efficient garbage collection of unused results
- Minimal memory footprint
Documentation
For detailed API documentation, see the API Reference.
Example: World Generator
A complete example of a simple world generator using Fimbul and simplex-noise. This example showcases how Fimbul's dependency graph can transform simple inputs into complex, interconnected world features.
import { createNoise2D } from 'simplex-noise';
type WorldGenParams = {
x: number;
y: number;
noise2D: (x: number, y: number) => number;
noiseScale: number;
};
type WorldGenResults = {
continentShape: number;
heightNoise: number;
height: number;
temperature: number;
precipitation: number;
biome: string;
};
const worldgen = Fimbul<WorldGenParams, WorldGenResults>();1. Continent Shapes
First define the basic continent shapes by multiplying sine waves:
worldgen.define('continentShape',
({ x, y }) =>
Math.abs(
Math.sin(x * Math.PI * 2) * Math.sin(y * Math.PI)
)
);
The base continent shapes create two large-scale landmasses.
2. Height Variation
Add variation to the height using noise:
worldgen.define('heightNoise',
({ x, y, noiseScale, noise2D }) =>
noise2D(x * noiseScale, y * noiseScale) * 0.5 + 0.5
);
Noise makes the terrain more natural-looking.
3. Combined Height
Combine the continent shapes with height noise by multiplying:
worldgen.define('height',
(params, { continentShape, heightNoise }) =>
continentShape * heightNoise,
['continentShape', 'heightNoise']
);
The result is the final elevation.
4. Temperature
Temperature varies with latitude and elevation:
worldgen.define('temperature',
({ y }, { height }) =>
height > 0.4 ? y - (height - 0.4) * 2 : y,
['height']
);
Temperature varies from poles to equator, and higher elevations are colder.
5. Precipitation
Rainfall patterns emerge from temperature:
worldgen.define('precipitation',
(params, { temperature }) => 1 - temperature,
['temperature']
);
Precipitation patterns create diverse climate zones.
6. Biomes
Finally, determine biomes based on all previous factors:
worldgen.define('biome',
(params, { height, temperature, precipitation }) => {
if (height < 0.2023) return 'ocean';
if (temperature >= 0.666) return 'desert';
if (temperature > 0.42 && precipitation > 0.42) return 'rainforest';
if (temperature > 0.3 && precipitation > 0.3) return 'forest';
if (temperature <= 0.21) return 'tundra';
return 'meadows';
},
['height', 'temperature', 'precipitation']
);
The final biome map shows the rich variety of environments.
Generate World Data
const noise2D = createNoise2D();
const biome = worldgen.get(
'biome',
{
x: Math.random(),
y: Math.random(),
noiseScale: 8,
noise2D
}
);This example demonstrates Fimbul's power in managing complex, interdependent calculations. Each step builds upon previous results, creating a simple world from simple mathematical functions - all while maintaining clean, maintainable code structure.
Async Support
Fimbul provides first-class support for async computations:
import FimbulAsync from '@fimbul-works/fimbul/async';
type Params = { base: number };
type Results = { double: number, triple: number };
const compute = FimbulAsync<Params, Results>();
// Define async computation nodes
compute.define('double',
async ({ base }) => {
await someAsyncOperation();
return base * 2;
}
);
compute.define('triple',
async ({ base }) => base * 3
);
// Get results
const result = await compute.get('double', { base: 21 }); // 42Advanced Usage
Error Handling
Fimbul provides clear error messages for common issues:
// Attempting to define duplicate nodes
compute.define('output', fn); // OK
compute.define('output', fn); // Error: "output" already defined
// Missing dependencies
compute.define('derived', fn, ['missing']); // Error: "missing" not foundType Safety
Fimbul leverages TypeScript's type system to catch errors at compile time:
type Params = { base: number };
type Results = { doubled: number };
const compute = Fimbul<Params, Results>();
// Type error: string is not assignable to number
compute.define('doubled', ({base}) => `${base * 2}`);
// Type error: missing dependency
compute.define('tripled', (_, {missing}) => missing * 3);License
MIT License - See LICENSE file for details.
Built with ⚡ by FimbulWorks
