more progress
This commit is contained in:
parent
880affad1c
commit
bdc6958b49
@ -16,7 +16,7 @@ describe('Basic Dependency Resolution', () => {
|
|||||||
|
|
||||||
test('should resolve dependencies in correct order', () => {
|
test('should resolve dependencies in correct order', () => {
|
||||||
// Define a simple plugin that depends on another
|
// Define a simple plugin that depends on another
|
||||||
class FirstPlugin implements ResolverPlugin<{ value: string }, string> {
|
class FirstPlugin extends ResolverPlugin<{ value: string }, string> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -34,7 +34,7 @@ describe('Basic Dependency Resolution', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SecondPlugin implements ResolverPlugin<{ value: string }, string> {
|
class SecondPlugin extends ResolverPlugin<{ value: string }, string> {
|
||||||
readonly dependencies = ['first'] as const;
|
readonly dependencies = ['first'] as const;
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
|
@ -16,7 +16,7 @@ describe('Circular Dependency Detection', () => {
|
|||||||
|
|
||||||
test('should detect circular dependencies', () => {
|
test('should detect circular dependencies', () => {
|
||||||
// PluginA depends on PluginB
|
// PluginA depends on PluginB
|
||||||
class PluginA implements ResolverPlugin<{ value: string }, string> {
|
class PluginA extends ResolverPlugin<{ value: string }, string> {
|
||||||
readonly dependencies = ['b'] as const;
|
readonly dependencies = ['b'] as const;
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -34,7 +34,7 @@ describe('Circular Dependency Detection', () => {
|
|||||||
|
|
||||||
|
|
||||||
// PluginB depends on PluginA (circular dependency)
|
// PluginB depends on PluginA (circular dependency)
|
||||||
class PluginB implements ResolverPlugin<{ value: string }, string> {
|
class PluginB extends ResolverPlugin<{ value: string }, string> {
|
||||||
readonly dependencies = ['a'] as const;
|
readonly dependencies = ['a'] as const;
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -61,21 +61,21 @@ describe('Circular Dependency Detection', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should detect longer circular dependency chains', () => {
|
test('should detect longer circular dependency chains', () => {
|
||||||
class PluginA implements ResolverPlugin<{ value: string }, string> {
|
class PluginA extends ResolverPlugin<{ value: string }, string> {
|
||||||
readonly dependencies = ['c'] as const;
|
readonly dependencies = ['c'] as const;
|
||||||
initialize() { return { value: '' }; }
|
initialize() { return { value: '' }; }
|
||||||
update() { return { value: '' }; }
|
update() { return { value: '' }; }
|
||||||
resolve() { return 'a'; }
|
resolve() { return 'a'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class PluginB implements ResolverPlugin<{ value: string }, string> {
|
class PluginB extends ResolverPlugin<{ value: string }, string> {
|
||||||
readonly dependencies = ['a'] as const;
|
readonly dependencies = ['a'] as const;
|
||||||
initialize() { return { value: '' }; }
|
initialize() { return { value: '' }; }
|
||||||
update() { return { value: '' }; }
|
update() { return { value: '' }; }
|
||||||
resolve() { return 'b'; }
|
resolve() { return 'b'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class PluginC implements ResolverPlugin<{ value: string }, string> {
|
class PluginC extends ResolverPlugin<{ value: string }, string> {
|
||||||
readonly dependencies = ['b'] as const;
|
readonly dependencies = ['b'] as const;
|
||||||
initialize() { return { value: '' }; }
|
initialize() { return { value: '' }; }
|
||||||
update() { return { value: '' }; }
|
update() { return { value: '' }; }
|
||||||
|
@ -18,7 +18,7 @@ describe('Edge Cases', () => {
|
|||||||
|
|
||||||
test('should handle null values', () => {
|
test('should handle null values', () => {
|
||||||
// Create a type-safe plugin that handles null/undefined values
|
// Create a type-safe plugin that handles null/undefined values
|
||||||
class NullSafeLastWriteWinsPlugin implements ResolverPlugin<{ value: PropertyTypes | null, timestamp: number }, never> {
|
class NullSafeLastWriteWinsPlugin extends ResolverPlugin<{ value: PropertyTypes | null, timestamp: number }, never> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -64,7 +64,7 @@ describe('Edge Cases', () => {
|
|||||||
|
|
||||||
test('should handle concurrent updates with same timestamp', () => {
|
test('should handle concurrent updates with same timestamp', () => {
|
||||||
// Custom plugin that handles concurrent updates with the same timestamp
|
// Custom plugin that handles concurrent updates with the same timestamp
|
||||||
class ConcurrentUpdatePlugin implements ResolverPlugin<{ value: PropertyTypes, timestamp: number }, never> {
|
class ConcurrentUpdatePlugin extends ResolverPlugin<{ value: PropertyTypes, timestamp: number }, never> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -125,7 +125,7 @@ describe('Edge Cases', () => {
|
|||||||
|
|
||||||
test('should handle very large numbers of updates', () => {
|
test('should handle very large numbers of updates', () => {
|
||||||
// Plugin that handles large numbers of updates efficiently
|
// Plugin that handles large numbers of updates efficiently
|
||||||
class CounterPlugin implements ResolverPlugin<{ count: number }, never> {
|
class CounterPlugin extends ResolverPlugin<{ count: number }, never> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -173,7 +173,7 @@ describe('Edge Cases', () => {
|
|||||||
test('should handle missing properties gracefully', () => {
|
test('should handle missing properties gracefully', () => {
|
||||||
// No deltas added - should handle empty state
|
// No deltas added - should handle empty state
|
||||||
// Plugin that handles missing properties gracefully
|
// Plugin that handles missing properties gracefully
|
||||||
class MissingPropertyPlugin implements ResolverPlugin<{ initialized: boolean }, never> {
|
class MissingPropertyPlugin extends ResolverPlugin<{ initialized: boolean }, never> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
|
@ -11,10 +11,11 @@ import {
|
|||||||
} from '@src/views/resolvers/custom-resolvers';
|
} from '@src/views/resolvers/custom-resolvers';
|
||||||
|
|
||||||
// A simple plugin that depends on other plugins
|
// A simple plugin that depends on other plugins
|
||||||
class AveragePlugin<Targets extends PropertyID> implements ResolverPlugin<{ initialized: boolean }, Targets> {
|
class AveragePlugin<Targets extends PropertyID> extends ResolverPlugin<{ initialized: boolean }, Targets> {
|
||||||
readonly dependencies: Targets[] = [];
|
readonly dependencies: Targets[] = [];
|
||||||
|
|
||||||
constructor(...targets: Targets[]) {
|
constructor(...targets: Targets[]) {
|
||||||
|
super();
|
||||||
if (targets.length !== 2) {
|
if (targets.length !== 2) {
|
||||||
throw new Error('This AveragePlugin requires exactly two targets');
|
throw new Error('This AveragePlugin requires exactly two targets');
|
||||||
}
|
}
|
||||||
@ -96,8 +97,8 @@ describe('Multiple Plugins Integration', () => {
|
|||||||
lossless.ingestDelta(
|
lossless.ingestDelta(
|
||||||
createDelta('user1', 'host1')
|
createDelta('user1', 'host1')
|
||||||
.withTimestamp(1000)
|
.withTimestamp(1000)
|
||||||
.setProperty('entity1', 'name', 'Test Entity', 'test')
|
.setProperty('entity1', 'name', 'Test Entity', 'test-name')
|
||||||
.setProperty('entity1', 'tags', 'tag1', 'test')
|
.setProperty('entity1', 'tags', 'tag1', 'test-tags')
|
||||||
.buildV1()
|
.buildV1()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
import { PropertyTypes } from '@src/core/types';
|
import { PropertyTypes } from '@src/core/types';
|
||||||
|
|
||||||
// A simple plugin for testing lifecycle methods
|
// A simple plugin for testing lifecycle methods
|
||||||
class LifecycleTestPlugin implements ResolverPlugin<LifecycleTestState> {
|
class LifecycleTestPlugin extends ResolverPlugin<LifecycleTestState> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
private initialState: LifecycleTestState = {
|
private initialState: LifecycleTestState = {
|
||||||
|
@ -4,7 +4,7 @@ import { PropertyTypes } from '@src/core/types';
|
|||||||
import type { CollapsedDelta } from '@src/views/lossless';
|
import type { CollapsedDelta } from '@src/views/lossless';
|
||||||
import { testResolverWithPlugins, createTestDelta } from '@test-helpers/resolver-test-helper';
|
import { testResolverWithPlugins, createTestDelta } from '@test-helpers/resolver-test-helper';
|
||||||
|
|
||||||
class CountPlugin implements ResolverPlugin<{ count: number }, never> {
|
class CountPlugin extends ResolverPlugin<{ count: number }, never> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
|
@ -5,7 +5,7 @@ import { testResolverWithPlugins, createTestDelta } from '@test-helpers/resolver
|
|||||||
import Debug from 'debug';
|
import Debug from 'debug';
|
||||||
const debug = Debug('rz:test:discount-plugins');
|
const debug = Debug('rz:test:discount-plugins');
|
||||||
// Mock plugins for testing
|
// Mock plugins for testing
|
||||||
class DiscountPlugin implements ResolverPlugin<number, never> {
|
class DiscountPlugin extends ResolverPlugin<number, never> {
|
||||||
readonly name = 'discount' as const;
|
readonly name = 'discount' as const;
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ class DiscountPlugin implements ResolverPlugin<number, never> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiscountedPricePlugin implements ResolverPlugin<number | null, 'discount'> {
|
class DiscountedPricePlugin extends ResolverPlugin<number | null, 'discount'> {
|
||||||
readonly name = 'price' as const;
|
readonly name = 'price' as const;
|
||||||
readonly dependencies = ['discount'] as const;
|
readonly dependencies = ['discount'] as const;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import { ResolverPlugin } from '@src/views/resolvers/custom-resolvers/plugin';
|
|||||||
// const debug = Debug('rz:test:resolver');
|
// const debug = Debug('rz:test:resolver');
|
||||||
|
|
||||||
// Mock plugins for testing
|
// Mock plugins for testing
|
||||||
class TestPlugin implements ResolverPlugin<unknown, string> {
|
class TestPlugin extends ResolverPlugin<unknown, string> {
|
||||||
name: string;
|
name: string;
|
||||||
dependencies: readonly string[];
|
dependencies: readonly string[];
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ describe('State Visibility', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// A test plugin that records which states it sees
|
// A test plugin that records which states it sees
|
||||||
class StateSpyPlugin implements ResolverPlugin<{ values: string[] }, 'dependsOn'> {
|
class StateSpyPlugin extends ResolverPlugin<{ values: string[] }, 'dependsOn'> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
seenStates: Record<string, unknown>[] = [];
|
seenStates: Record<string, unknown>[] = [];
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ describe('State Visibility', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A simple plugin that depends on another property
|
// A simple plugin that depends on another property
|
||||||
class DependentPlugin implements ResolverPlugin<{ value: string }, 'dependsOn'> {
|
class DependentPlugin extends ResolverPlugin<{ value: string }, 'dependsOn'> {
|
||||||
readonly dependencies = ['dependsOn'] as const;
|
readonly dependencies = ['dependsOn'] as const;
|
||||||
seenStates: Record<string, unknown>[] = [];
|
seenStates: Record<string, unknown>[] = [];
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ describe('State Visibility', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should throw error for unknown dependencies', () => {
|
test('should throw error for unknown dependencies', () => {
|
||||||
class PluginWithBadDeps implements ResolverPlugin<{ value: string }, 'nonexistent'> {
|
class PluginWithBadDeps extends ResolverPlugin<{ value: string }, 'nonexistent'> {
|
||||||
readonly dependencies = ['nonexistent'] as const;
|
readonly dependencies = ['nonexistent'] as const;
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
|
@ -164,7 +164,7 @@ const resolver = new CustomResolver(view, {
|
|||||||
taxRate: new LastWriteWinsPlugin(),
|
taxRate: new LastWriteWinsPlugin(),
|
||||||
|
|
||||||
// Complex plugin with multiple dependencies
|
// Complex plugin with multiple dependencies
|
||||||
subtotal: new class implements ResolverPlugin<SubtotalState, 'unitPrice' | 'quantity'> {
|
subtotal: new class extends ResolverPlugin<SubtotalState, 'unitPrice' | 'quantity'> {
|
||||||
readonly dependencies = ['unitPrice', 'quantity'] as const;
|
readonly dependencies = ['unitPrice', 'quantity'] as const;
|
||||||
|
|
||||||
initialize() { return { value: 0 }; }
|
initialize() { return { value: 0 }; }
|
||||||
|
@ -93,7 +93,7 @@ Resolves the final value from the current state.
|
|||||||
## Example Implementation
|
## Example Implementation
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class CounterPlugin implements ResolverPlugin<CounterState> {
|
class CounterPlugin extends ResolverPlugin<CounterState> {
|
||||||
|
|
||||||
initialize(): CounterState {
|
initialize(): CounterState {
|
||||||
return { count: 0 };
|
return { count: 0 };
|
||||||
@ -126,7 +126,7 @@ class CounterPlugin implements ResolverPlugin<CounterState> {
|
|||||||
### Accessing Dependencies
|
### Accessing Dependencies
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class PriceCalculator implements ResolverPlugin<PriceState, 'basePrice' | 'taxRate'> {
|
class PriceCalculator extends ResolverPlugin<PriceState, 'basePrice' | 'taxRate'> {
|
||||||
readonly dependencies = ['basePrice', 'taxRate'] as const;
|
readonly dependencies = ['basePrice', 'taxRate'] as const;
|
||||||
|
|
||||||
update(
|
update(
|
||||||
@ -147,7 +147,7 @@ class PriceCalculator implements ResolverPlugin<PriceState, 'basePrice' | 'taxRa
|
|||||||
### Optional Dependencies
|
### Optional Dependencies
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class OptionalDepPlugin implements ResolverPlugin<State, 'required' | 'optional?'> {
|
class OptionalDepPlugin extends ResolverPlugin<State, 'required' | 'optional?'> {
|
||||||
readonly dependencies = ['required', 'optional?'] as const;
|
readonly dependencies = ['required', 'optional?'] as const;
|
||||||
|
|
||||||
update(
|
update(
|
||||||
|
@ -117,7 +117,7 @@ Configuration object mapping property IDs to their resolver plugins.
|
|||||||
### `LastWriteWinsPlugin`
|
### `LastWriteWinsPlugin`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class LastWriteWinsPlugin implements ResolverPlugin<LastWriteWinsState> {
|
class LastWriteWinsPlugin extends ResolverPlugin<LastWriteWinsState> {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ interface LastWriteWinsState {
|
|||||||
### `FirstWriteWinsPlugin`
|
### `FirstWriteWinsPlugin`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class FirstWriteWinsPlugin implements ResolverPlugin<FirstWriteWinsState> {
|
class FirstWriteWinsPlugin extends ResolverPlugin<FirstWriteWinsState> {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ interface ConcatenationOptions {
|
|||||||
sort?: boolean;
|
sort?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConcatenationPlugin implements ResolverPlugin<ConcatenationState> {
|
class ConcatenationPlugin extends ResolverPlugin<ConcatenationState> {
|
||||||
|
|
||||||
constructor(private options: ConcatenationOptions = {}) {
|
constructor(private options: ConcatenationOptions = {}) {
|
||||||
this.options = {
|
this.options = {
|
||||||
@ -173,7 +173,7 @@ interface MajorityVoteOptions {
|
|||||||
minVotes?: number;
|
minVotes?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MajorityVotePlugin implements ResolverPlugin<MajorityVoteState> {
|
class MajorityVotePlugin extends ResolverPlugin<MajorityVoteState> {
|
||||||
|
|
||||||
constructor(private options: MajorityVoteOptions = {}) {
|
constructor(private options: MajorityVoteOptions = {}) {
|
||||||
this.options = {
|
this.options = {
|
||||||
@ -222,7 +222,7 @@ interface CounterState {
|
|||||||
type CounterDeps = 'incrementBy' | 'resetThreshold';
|
type CounterDeps = 'incrementBy' | 'resetThreshold';
|
||||||
|
|
||||||
// Implement plugin with type safety
|
// Implement plugin with type safety
|
||||||
class CounterPlugin implements ResolverPlugin<CounterState, CounterDeps> {
|
class CounterPlugin extends ResolverPlugin<CounterState, CounterDeps> {
|
||||||
readonly dependencies = ['incrementBy', 'resetThreshold'] as const;
|
readonly dependencies = ['incrementBy', 'resetThreshold'] as const;
|
||||||
|
|
||||||
initialize(): CounterState {
|
initialize(): CounterState {
|
||||||
|
@ -50,7 +50,7 @@ const resolver = new CustomResolver(view, {
|
|||||||
To make a dependency optional, mark it with a `?` suffix:
|
To make a dependency optional, mark it with a `?` suffix:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class MyPlugin implements ResolverPlugin<MyState, 'required' | 'optional?'> {
|
class MyPlugin extends ResolverPlugin<MyState, 'required' | 'optional?'> {
|
||||||
readonly dependencies = ['required', 'optional?'] as const;
|
readonly dependencies = ['required', 'optional?'] as const;
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
@ -62,7 +62,7 @@ class MyPlugin implements ResolverPlugin<MyState, 'required' | 'optional?'> {
|
|||||||
For plugins that need to determine dependencies at runtime, you can implement a custom resolver:
|
For plugins that need to determine dependencies at runtime, you can implement a custom resolver:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class DynamicDepsPlugin implements ResolverPlugin<DynamicState> {
|
class DynamicDepsPlugin extends ResolverPlugin<DynamicState> {
|
||||||
|
|
||||||
getDependencies(config: any): string[] {
|
getDependencies(config: any): string[] {
|
||||||
// Determine dependencies based on config
|
// Determine dependencies based on config
|
||||||
|
@ -21,7 +21,7 @@ The Custom Resolver system provides a powerful dependency management system that
|
|||||||
## Example
|
## Example
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class TotalPricePlugin implements ResolverPlugin<TotalState, 'price' | 'tax'> {
|
class TotalPricePlugin extends ResolverPlugin<TotalState, 'price' | 'tax'> {
|
||||||
readonly dependencies = ['price', 'tax'] as const;
|
readonly dependencies = ['price', 'tax'] as const;
|
||||||
|
|
||||||
initialize(): TotalState {
|
initialize(): TotalState {
|
||||||
|
@ -37,7 +37,7 @@ type DependencyStates = {
|
|||||||
Dependencies are declared as a readonly array of string literals:
|
Dependencies are declared as a readonly array of string literals:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class MyPlugin implements ResolverPlugin<MyState, 'dep1' | 'dep2'> {
|
class MyPlugin extends ResolverPlugin<MyState, 'dep1' | 'dep2'> {
|
||||||
readonly dependencies = ['dep1', 'dep2'] as const;
|
readonly dependencies = ['dep1', 'dep2'] as const;
|
||||||
|
|
||||||
// ... implementation
|
// ... implementation
|
||||||
@ -101,7 +101,7 @@ if (typeof deps.price === 'number') {
|
|||||||
### Optional Dependencies
|
### Optional Dependencies
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class MyPlugin implements ResolverPlugin<MyState, 'required' | 'optional?'> {
|
class MyPlugin extends ResolverPlugin<MyState, 'required' | 'optional?'> {
|
||||||
readonly dependencies = ['required', 'optional?'] as const;
|
readonly dependencies = ['required', 'optional?'] as const;
|
||||||
|
|
||||||
update(_state: MyState, _value: unknown, _delta: CollapsedDelta, deps: any) {
|
update(_state: MyState, _value: unknown, _delta: CollapsedDelta, deps: any) {
|
||||||
@ -118,7 +118,7 @@ class MyPlugin implements ResolverPlugin<MyState, 'required' | 'optional?'> {
|
|||||||
```typescript
|
```typescript
|
||||||
type PriceDependencies = 'price1' | 'price2' | 'price3';
|
type PriceDependencies = 'price1' | 'price2' | 'price3';
|
||||||
|
|
||||||
class PriceAggregator implements ResolverPlugin<PriceState, PriceDependencies> {
|
class PriceAggregator extends ResolverPlugin<PriceState, PriceDependencies> {
|
||||||
readonly dependencies: readonly PriceDependencies[] = ['price1', 'price2', 'price3'] as const;
|
readonly dependencies: readonly PriceDependencies[] = ['price1', 'price2', 'price3'] as const;
|
||||||
|
|
||||||
update(_state: PriceState, _value: unknown, _delta: CollapsedDelta, deps: any) {
|
update(_state: PriceState, _value: unknown, _delta: CollapsedDelta, deps: any) {
|
||||||
|
@ -11,7 +11,7 @@ A minimal plugin must implement the `ResolverPlugin` interface:
|
|||||||
```typescript
|
```typescript
|
||||||
import { ResolverPlugin } from '../resolver';
|
import { ResolverPlugin } from '../resolver';
|
||||||
|
|
||||||
class MyPlugin implements ResolverPlugin<MyState> {
|
class MyPlugin extends ResolverPlugin<MyState> {
|
||||||
|
|
||||||
initialize(): MyState {
|
initialize(): MyState {
|
||||||
// Return initial state
|
// Return initial state
|
||||||
@ -40,7 +40,7 @@ class MyPlugin implements ResolverPlugin<MyState> {
|
|||||||
To depend on other properties, specify the dependency types:
|
To depend on other properties, specify the dependency types:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class DiscountedPricePlugin implements ResolverPlugin<DiscountState, 'basePrice' | 'discount'> {
|
class DiscountedPricePlugin extends ResolverPlugin<DiscountState, 'basePrice' | 'discount'> {
|
||||||
readonly dependencies = ['basePrice', 'discount'] as const;
|
readonly dependencies = ['basePrice', 'discount'] as const;
|
||||||
|
|
||||||
initialize(): DiscountState {
|
initialize(): DiscountState {
|
||||||
|
@ -28,7 +28,7 @@ export abstract class BaseOrchestrator implements NodeOrchestrator {
|
|||||||
* Default implementation does nothing - should be overridden by subclasses
|
* Default implementation does nothing - should be overridden by subclasses
|
||||||
* that support direct node connections
|
* that support direct node connections
|
||||||
*/
|
*/
|
||||||
async connectNodes(node1: NodeHandle, node2: NodeHandle): Promise<void> {
|
async connectNodes(_node1: NodeHandle, _node2: NodeHandle): Promise<void> {
|
||||||
// Default implementation does nothing
|
// Default implementation does nothing
|
||||||
console.warn('connectNodes not implemented for this orchestrator');
|
console.warn('connectNodes not implemented for this orchestrator');
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ export abstract class BaseOrchestrator implements NodeOrchestrator {
|
|||||||
* Default implementation does nothing - should be overridden by subclasses
|
* Default implementation does nothing - should be overridden by subclasses
|
||||||
* that support network partitioning
|
* that support network partitioning
|
||||||
*/
|
*/
|
||||||
async partitionNetwork(partitions: { groups: string[][] }): Promise<void> {
|
async partitionNetwork(_partitions: { groups: string[][] }): Promise<void> {
|
||||||
// Default implementation does nothing
|
// Default implementation does nothing
|
||||||
console.warn('partitionNetwork not implemented for this orchestrator');
|
console.warn('partitionNetwork not implemented for this orchestrator');
|
||||||
}
|
}
|
||||||
@ -49,8 +49,8 @@ export abstract class BaseOrchestrator implements NodeOrchestrator {
|
|||||||
* that support resource management
|
* that support resource management
|
||||||
*/
|
*/
|
||||||
async setResourceLimits(
|
async setResourceLimits(
|
||||||
handle: NodeHandle,
|
_handle: NodeHandle,
|
||||||
limits: Partial<NodeConfig['resources']>
|
_limits: Partial<NodeConfig['resources']>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Default implementation does nothing
|
// Default implementation does nothing
|
||||||
console.warn('setResourceLimits not implemented for this orchestrator');
|
console.warn('setResourceLimits not implemented for this orchestrator');
|
||||||
|
@ -28,6 +28,40 @@ export abstract class ResolverPlugin<
|
|||||||
*/
|
*/
|
||||||
dependencies?: readonly D[];
|
dependencies?: readonly D[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience wrapper to avoid calling update() when there is no new value
|
||||||
|
* @param currentState The current state of the plugin
|
||||||
|
* @param newValue The new value to apply
|
||||||
|
* @param delta The delta that triggered the update
|
||||||
|
* @param dependencies The dependencies of the plugin
|
||||||
|
* @returns The updated state
|
||||||
|
*/
|
||||||
|
applyUpdate(
|
||||||
|
currentState: T,
|
||||||
|
newValue?: PropertyTypes,
|
||||||
|
delta?: CollapsedDelta,
|
||||||
|
dependencies?: DependencyStates
|
||||||
|
): T {
|
||||||
|
if (newValue === undefined) {
|
||||||
|
switch(this.dependencies?.length) {
|
||||||
|
case 0: {
|
||||||
|
// No dependencies, no new value -- nothing to do.
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
// Only one dependency, use it as the new value.
|
||||||
|
newValue = dependencies![this.dependencies[0]] as PropertyTypes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// Pass dependencies as is, and leave newValue undefined.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.update(currentState, newValue, delta, dependencies);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the state for a property
|
* Initialize the state for a property
|
||||||
*/
|
*/
|
||||||
@ -38,7 +72,7 @@ export abstract class ResolverPlugin<
|
|||||||
/**
|
/**
|
||||||
* Process a new value for the property
|
* Process a new value for the property
|
||||||
*/
|
*/
|
||||||
abstract update(
|
protected abstract update(
|
||||||
currentState: T,
|
currentState: T,
|
||||||
newValue?: PropertyTypes,
|
newValue?: PropertyTypes,
|
||||||
delta?: CollapsedDelta,
|
delta?: CollapsedDelta,
|
||||||
|
@ -13,10 +13,12 @@ type ConcatenationState = {
|
|||||||
*
|
*
|
||||||
* Concatenates all string values with a separator
|
* Concatenates all string values with a separator
|
||||||
*/
|
*/
|
||||||
export class ConcatenationPlugin implements ResolverPlugin<ConcatenationState, never> {
|
export class ConcatenationPlugin extends ResolverPlugin<ConcatenationState, never> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
constructor(private separator: string = ' ') {}
|
constructor(private separator: string = ' ') {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
initialize(): ConcatenationState {
|
initialize(): ConcatenationState {
|
||||||
return { values: [] };
|
return { values: [] };
|
||||||
|
@ -12,7 +12,7 @@ type FirstWriteWinsState = {
|
|||||||
*
|
*
|
||||||
* Keeps the first value that was written, ignoring subsequent writes
|
* Keeps the first value that was written, ignoring subsequent writes
|
||||||
*/
|
*/
|
||||||
export class FirstWriteWinsPlugin implements ResolverPlugin<FirstWriteWinsState, never> {
|
export class FirstWriteWinsPlugin extends ResolverPlugin<FirstWriteWinsState, never> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
initialize(): FirstWriteWinsState {
|
initialize(): FirstWriteWinsState {
|
||||||
|
@ -12,7 +12,7 @@ type LastWriteWinsState = {
|
|||||||
*
|
*
|
||||||
* Keeps the most recent value based on the delta's timestamp
|
* Keeps the most recent value based on the delta's timestamp
|
||||||
*/
|
*/
|
||||||
export class LastWriteWinsPlugin implements ResolverPlugin<LastWriteWinsState, never> {
|
export class LastWriteWinsPlugin extends ResolverPlugin<LastWriteWinsState, never> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
initialize(): LastWriteWinsState {
|
initialize(): LastWriteWinsState {
|
||||||
|
@ -10,7 +10,7 @@ type MajorityVoteState = {
|
|||||||
*
|
*
|
||||||
* Returns the value that appears most frequently
|
* Returns the value that appears most frequently
|
||||||
*/
|
*/
|
||||||
export class MajorityVotePlugin implements ResolverPlugin<MajorityVoteState, never> {
|
export class MajorityVotePlugin extends ResolverPlugin<MajorityVoteState, never> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
initialize(): MajorityVoteState {
|
initialize(): MajorityVoteState {
|
||||||
@ -21,6 +21,7 @@ export class MajorityVotePlugin implements ResolverPlugin<MajorityVoteState, nev
|
|||||||
currentState: MajorityVoteState,
|
currentState: MajorityVoteState,
|
||||||
newValue: PropertyTypes,
|
newValue: PropertyTypes,
|
||||||
): MajorityVoteState {
|
): MajorityVoteState {
|
||||||
|
if (newValue === undefined) return currentState;
|
||||||
const currentCount = currentState.votes.get(newValue) || 0;
|
const currentCount = currentState.votes.get(newValue) || 0;
|
||||||
// Create a new Map to ensure immutability
|
// Create a new Map to ensure immutability
|
||||||
const newVotes = new Map(currentState.votes);
|
const newVotes = new Map(currentState.votes);
|
||||||
|
@ -11,11 +11,12 @@ type MaxPluginState = {
|
|||||||
*
|
*
|
||||||
* Tracks the maximum numeric value
|
* Tracks the maximum numeric value
|
||||||
*/
|
*/
|
||||||
export class MaxPlugin<Target extends PropertyID> implements ResolverPlugin<MaxPluginState, Target> {
|
export class MaxPlugin<Target extends PropertyID> extends ResolverPlugin<MaxPluginState, Target> {
|
||||||
name = 'max';
|
name = 'max';
|
||||||
readonly dependencies: Target[] = [];
|
readonly dependencies: Target[] = [];
|
||||||
|
|
||||||
constructor(private readonly target?: Target) {
|
constructor(private readonly target?: Target) {
|
||||||
|
super();
|
||||||
if (target) {
|
if (target) {
|
||||||
this.dependencies = [target];
|
this.dependencies = [target];
|
||||||
}
|
}
|
||||||
@ -29,11 +30,8 @@ export class MaxPlugin<Target extends PropertyID> implements ResolverPlugin<MaxP
|
|||||||
currentState: MaxPluginState,
|
currentState: MaxPluginState,
|
||||||
newValue?: PropertyTypes,
|
newValue?: PropertyTypes,
|
||||||
_delta?: CollapsedDelta,
|
_delta?: CollapsedDelta,
|
||||||
dependencies?: DependencyStates
|
|
||||||
): MaxPluginState {
|
): MaxPluginState {
|
||||||
// const numValue = typeof newValue === 'number' ? newValue : parseFloat(String(newValue));
|
const numValue = newValue as number;
|
||||||
const numValue = (this.target ? dependencies?.[this.target] : newValue) as number;
|
|
||||||
|
|
||||||
if (currentState.max === undefined || numValue > currentState.max) {
|
if (currentState.max === undefined || numValue > currentState.max) {
|
||||||
return { max: numValue };
|
return { max: numValue };
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { PropertyTypes, PropertyID } from "../../../../core/types";
|
import { PropertyTypes, PropertyID } from "../../../../core/types";
|
||||||
import { CollapsedDelta } from "../../../lossless";
|
import { DependencyStates, ResolverPlugin } from "../plugin";
|
||||||
import { ResolverPlugin, DependencyStates } from "../plugin";
|
|
||||||
|
|
||||||
type MinPluginState = {
|
type MinPluginState = {
|
||||||
min?: number;
|
min?: number;
|
||||||
@ -11,11 +10,12 @@ type MinPluginState = {
|
|||||||
*
|
*
|
||||||
* Tracks the minimum numeric value
|
* Tracks the minimum numeric value
|
||||||
*/
|
*/
|
||||||
export class MinPlugin<Target extends PropertyID> implements ResolverPlugin<MinPluginState, Target> {
|
export class MinPlugin<Target extends PropertyID> extends ResolverPlugin<MinPluginState, Target> {
|
||||||
name = 'min';
|
name = 'min';
|
||||||
readonly dependencies: Target[] = [];
|
readonly dependencies: Target[] = [];
|
||||||
|
|
||||||
constructor(private readonly target?: Target) {
|
constructor(private readonly target?: Target) {
|
||||||
|
super();
|
||||||
if (target) {
|
if (target) {
|
||||||
this.dependencies = [target];
|
this.dependencies = [target];
|
||||||
}
|
}
|
||||||
@ -27,12 +27,9 @@ export class MinPlugin<Target extends PropertyID> implements ResolverPlugin<MinP
|
|||||||
|
|
||||||
update(
|
update(
|
||||||
currentState: MinPluginState,
|
currentState: MinPluginState,
|
||||||
newValue?: PropertyTypes,
|
newValue: PropertyTypes,
|
||||||
_delta?: CollapsedDelta,
|
|
||||||
dependencies?: DependencyStates
|
|
||||||
): MinPluginState {
|
): MinPluginState {
|
||||||
const numValue = (this.target ? dependencies?.[this.target] : newValue) as number;
|
const numValue = newValue as number;
|
||||||
|
|
||||||
if (currentState.min === undefined || numValue < currentState.min) {
|
if (currentState.min === undefined || numValue < currentState.min) {
|
||||||
return { min: numValue };
|
return { min: numValue };
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ type RunningAverageState = {
|
|||||||
*
|
*
|
||||||
* Tracks the running average of numeric values
|
* Tracks the running average of numeric values
|
||||||
*/
|
*/
|
||||||
export class RunningAveragePlugin implements ResolverPlugin<RunningAverageState, never> {
|
export class RunningAveragePlugin extends ResolverPlugin<RunningAverageState, never> {
|
||||||
readonly dependencies = [] as const;
|
readonly dependencies = [] as const;
|
||||||
|
|
||||||
initialize(): RunningAverageState {
|
initialize(): RunningAverageState {
|
||||||
|
@ -291,7 +291,7 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
|||||||
|
|
||||||
// Update the plugin state with the new delta
|
// Update the plugin state with the new delta
|
||||||
const dependencies = this.getDependencyStates(entityState, plugin);
|
const dependencies = this.getDependencyStates(entityState, plugin);
|
||||||
entityState[pluginKey] = plugin.update(pluginState, propertyValue, updateDelta, dependencies);
|
entityState[pluginKey] = plugin.applyUpdate(pluginState, propertyValue, updateDelta, dependencies);
|
||||||
debugState(`Updated entity state for ${entityId}:`, JSON.stringify(entityState[pluginKey]));
|
debugState(`Updated entity state for ${entityId}:`, JSON.stringify(entityState[pluginKey]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user