- Renamed Lossless class to Hyperview for clarity - Renamed Lossy class to View for simplicity - Updated all imports and references throughout the codebase - Updated documentation to reflect new terminology - Fixed test files to use new class names - Added proper exports in index files
3.9 KiB
3.9 KiB
Views and Resolvers
Core Concepts
Views
A View
(previously known as Lossy
) is a derived, computed representation of your data that provides efficient access to resolved values. It's built on top of the Hyperview
(previously Hyperview
) storage layer, which maintains the complete history of all deltas.
// Basic View implementation
export abstract class View<Accumulator, Result = Accumulator> {
// Core methods
abstract reducer(acc: Accumulator, cur: HyperviewOne): Accumulator;
resolver?(acc: Accumulator, entityIds: DomainEntityID[]): Result;
// Built-in functionality
resolve(entityIds?: DomainEntityID[]): Result | undefined;
}
Hyperview
Hyperview
(previously Hyperview
) maintains the complete, immutable history of all deltas and provides the foundation for building derived views.
// Basic Hyperview usage
const hyperview = new Hyperview(rhizomeNode);
const view = new MyCustomView(hyperview);
const result = view.resolve(['entity1', 'entity2']);
Creating Custom Resolvers
Basic Resolver Pattern
- Define your accumulator type: This holds the state of your view
- Implement the reducer: Pure function that updates the accumulator
- Optionally implement resolver: Transforms the accumulator into the final result
class CountResolver extends View<number> {
reducer(acc: number, view: HyperviewOne): number {
return acc + Object.keys(view.propertyDeltas).length;
}
initializer() { return 0; }
}
Custom Resolver with Dependencies
Resolvers can depend on other resolvers using the CustomResolver
system:
// Define resolver plugins
const resolver = new CustomResolver(hyperview, {
totalItems: new CountPlugin(),
average: new AveragePlugin()
});
// Get resolved values
const result = resolver.resolve(['entity1', 'entity2']);
Built-in Resolvers
Last Write Wins
Keeps the most recent value based on timestamp:
const resolver = new CustomResolver(hyperview, {
status: new LastWriteWinsPlugin()
});
First Write Wins
Keeps the first value that was written:
const resolver = new CustomResolver(hyperview, {
initialStatus: new FirstWriteWinsPlugin()
});
Aggregation Resolvers
Built-in aggregators for common operations:
SumPlugin
: Sum of numeric valuesAveragePlugin
: Average of numeric valuesCountPlugin
: Count of valuesMinPlugin
: Minimum valueMaxPlugin
: Maximum value
Advanced Topics
Performance Considerations
- Efficient Reducers: Keep reducers pure and fast
- Selective Updates: Only process changed entities
- Memoization: Cache expensive computations
Common Patterns
- Derived Properties: Calculate values based on other properties
- State Machines: Track state transitions over time
- Validation: Enforce data consistency rules
Best Practices
- Keep Reducers Pure: Avoid side effects in reducers
- Use Strong Typing: Leverage TypeScript for type safety
- Handle Edge Cases: Consider empty states and error conditions
- Profile Performance: Monitor and optimize hot paths
Examples
Simple Counter
class CounterView extends View<number> {
reducer(acc: number, view: HyperviewOne): number {
return acc + view.propertyDeltas['increment']?.length || 0;
}
initializer() { return 0; }
}
Running Average
class RunningAverageView extends View<{sum: number, count: number}, number> {
reducer(acc: {sum: number, count: number}, view: HyperviewOne): {sum: number, count: number} {
const value = // extract value from view
return {
sum: acc.sum + value,
count: acc.count + 1
};
}
resolver(acc: {sum: number, count: number}): number {
return acc.count > 0 ? acc.sum / acc.count : 0;
}
initializer() { return {sum: 0, count: 0}; }
}