feat: add inter-plugin dependencies to CustomResolver
- Update ResolverPlugin interface with allStates parameter - Modify CustomResolver to pass all plugin states - Update built-in plugins for compatibility - Add comprehensive tests for inter-plugin dependencies - Add detailed documentation with examples
This commit is contained in:
parent
fa739d047f
commit
8de512cd5b
@ -326,6 +326,97 @@ describe('Custom Resolvers', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Inter-Plugin Dependencies', () => {
|
||||||
|
test('should allow plugins to depend on other plugin states', () => {
|
||||||
|
// A plugin that applies a discount to a price
|
||||||
|
class DiscountedPricePlugin implements ResolverPlugin<{ price: number }> {
|
||||||
|
name = 'discounted-price';
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return { price: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
currentState: { price: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
_delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
if (typeof newValue === 'number') {
|
||||||
|
return { price: newValue };
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(
|
||||||
|
state: { price: number },
|
||||||
|
allStates?: Record<string, unknown>
|
||||||
|
): number | undefined {
|
||||||
|
// Get discount from another plugin's state
|
||||||
|
const discountState = allStates?.['discount'] as { value: number } | undefined;
|
||||||
|
if (discountState) {
|
||||||
|
return state.price * (1 - (discountState.value / 100));
|
||||||
|
}
|
||||||
|
return state.price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple discount plugin
|
||||||
|
class DiscountPlugin implements ResolverPlugin<{ value: number }> {
|
||||||
|
name = 'discount';
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return { value: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
currentState: { value: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
_delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
if (typeof newValue === 'number') {
|
||||||
|
return { value: newValue };
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(
|
||||||
|
state: { value: number },
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
): number {
|
||||||
|
return state.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set base price
|
||||||
|
lossless.ingestDelta(
|
||||||
|
createDelta('user1', 'host1')
|
||||||
|
.withTimestamp(1000)
|
||||||
|
.setProperty('product1', 'price', 100, 'products')
|
||||||
|
.buildV1()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set discount (20%)
|
||||||
|
lossless.ingestDelta(
|
||||||
|
createDelta('user1', 'host1')
|
||||||
|
.withTimestamp(1000)
|
||||||
|
.setProperty('product1', 'discount', 20, 'products')
|
||||||
|
.buildV1()
|
||||||
|
);
|
||||||
|
|
||||||
|
const resolver = new CustomResolver(lossless, {
|
||||||
|
price: new DiscountedPricePlugin(),
|
||||||
|
discount: new DiscountPlugin()
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolver.resolve();
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result!['product1'].properties.price).toBe(80); // 100 - 20%
|
||||||
|
expect(result!['product1'].properties.discount).toBe(20);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Custom Plugin Implementation', () => {
|
describe('Custom Plugin Implementation', () => {
|
||||||
test('should work with custom plugin', () => {
|
test('should work with custom plugin', () => {
|
||||||
// Custom plugin that counts the number of updates
|
// Custom plugin that counts the number of updates
|
||||||
|
238
docs/custom-resolvers.md
Normal file
238
docs/custom-resolvers.md
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
# Custom Resolvers
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `CustomResolver` class provides a flexible system for resolving property conflicts in a distributed system. This document covers the implementation details, including the support for inter-plugin dependencies.
|
||||||
|
|
||||||
|
## Current Implementation
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
1. **ResolverPlugin Interface**
|
||||||
|
- Defines the contract for all resolver implementations
|
||||||
|
- Key methods:
|
||||||
|
- `initialize()`: Creates initial state
|
||||||
|
- `update()`: Processes new values with timestamps
|
||||||
|
- `resolve()`: Produces final value from accumulated state
|
||||||
|
|
||||||
|
2. **CustomResolver Class**
|
||||||
|
- Manages resolution of entity properties using configured plugins
|
||||||
|
- Implements the core resolution logic:
|
||||||
|
- `initializer`: Creates initial state structure
|
||||||
|
- `reducer`: Processes deltas and updates state using plugins
|
||||||
|
- `resolver`: Produces final resolved values
|
||||||
|
|
||||||
|
3. **Built-in Plugins**
|
||||||
|
- `LastWriteWinsPlugin`: Keeps the most recent value
|
||||||
|
- `FirstWriteWinsPlugin`: Keeps the first value seen
|
||||||
|
- `ConcatenationPlugin`: Combines string values with a separator
|
||||||
|
- `MajorityVotePlugin`: Selects the most common value
|
||||||
|
- `MinPlugin`/`MaxPlugin`: Tracks minimum/maximum numeric values
|
||||||
|
|
||||||
|
## Inter-Plugin Dependencies
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
The system now supports inter-plugin dependencies, allowing plugins to access the state of other plugins during both the update and resolve phases. This enables the creation of more sophisticated resolution strategies that can depend on multiple properties.
|
||||||
|
|
||||||
|
### Implementation Details
|
||||||
|
|
||||||
|
#### ResolverPlugin Interface
|
||||||
|
|
||||||
|
The `ResolverPlugin` interface has been updated to include an optional `allStates` parameter in both the `update` and `resolve` methods:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ResolverPlugin<T = unknown> {
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
// Initialize the state for a property
|
||||||
|
initialize(): T;
|
||||||
|
|
||||||
|
// Process a new value for the property
|
||||||
|
update(
|
||||||
|
currentState: T,
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
delta: CollapsedDelta,
|
||||||
|
allStates?: Record<PropertyID, unknown> // Access to other plugin states
|
||||||
|
): T;
|
||||||
|
|
||||||
|
// Resolve the final value from the accumulated state
|
||||||
|
resolve(
|
||||||
|
state: T,
|
||||||
|
allStates?: Record<PropertyID, unknown> // Access to other plugin states
|
||||||
|
): PropertyTypes | undefined;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CustomResolver Class
|
||||||
|
|
||||||
|
The `CustomResolver` class has been enhanced to:
|
||||||
|
1. Collect all plugin states before processing updates
|
||||||
|
2. Pass the complete state to each plugin during updates and resolution
|
||||||
|
3. Maintain backward compatibility with existing plugins
|
||||||
|
|
||||||
|
### Example: Discounted Price Plugin
|
||||||
|
|
||||||
|
Here's a practical example of a plugin that calculates a discounted price based on another property:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class DiscountedPricePlugin implements ResolverPlugin<{ price: number }> {
|
||||||
|
name = 'discounted-price';
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return { price: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
state: { price: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
_delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
) {
|
||||||
|
if (typeof newValue === 'number') {
|
||||||
|
return { price: newValue };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(
|
||||||
|
state: { price: number },
|
||||||
|
allStates?: Record<PropertyID, unknown>
|
||||||
|
): number | undefined {
|
||||||
|
// Access the discount value from another plugin's state
|
||||||
|
const discountState = allStates?.['discount'] as { value: number } | undefined;
|
||||||
|
if (discountState) {
|
||||||
|
// Apply discount if available
|
||||||
|
return state.price * (1 - (discountState.value / 100));
|
||||||
|
}
|
||||||
|
return state.price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage with a discount plugin
|
||||||
|
const resolver = new CustomResolver(losslessView, {
|
||||||
|
price: new DiscountedPricePlugin(),
|
||||||
|
discount: new LastWriteWinsPlugin()
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
1. **Dependency Management**:
|
||||||
|
- Clearly document which properties your plugin depends on
|
||||||
|
- Handle cases where dependencies might be undefined
|
||||||
|
- Consider using TypeScript type guards for safer property access
|
||||||
|
|
||||||
|
2. **Performance Considerations**:
|
||||||
|
- Access only the states you need in the `allStates` object
|
||||||
|
- Consider caching resolved values if the same calculation is performed multiple times
|
||||||
|
|
||||||
|
3. **Testing**:
|
||||||
|
- Test plugins with and without their dependencies
|
||||||
|
- Verify behavior when dependencies are updated in different orders
|
||||||
|
- Test edge cases like missing or invalid dependencies
|
||||||
|
|
||||||
|
### Built-in Plugins
|
||||||
|
|
||||||
|
All built-in plugins have been updated to be compatible with the new interface:
|
||||||
|
|
||||||
|
- `LastWriteWinsPlugin`
|
||||||
|
- `FirstWriteWinsPlugin`
|
||||||
|
- `ConcatenationPlugin`
|
||||||
|
- `MajorityVotePlugin`
|
||||||
|
- `MinPlugin`
|
||||||
|
- `MaxPlugin`
|
||||||
|
|
||||||
|
These plugins maintain backward compatibility while supporting the new functionality.
|
||||||
|
|
||||||
|
## Implementation Status
|
||||||
|
|
||||||
|
The inter-plugin dependency feature has been implemented and includes:
|
||||||
|
|
||||||
|
1. Updated `ResolverPlugin` interface with `allStates` parameter
|
||||||
|
2. Enhanced `CustomResolver` class for state sharing between plugins
|
||||||
|
3. Updated all built-in plugins for compatibility
|
||||||
|
4. Comprehensive test coverage including:
|
||||||
|
- Basic functionality of all built-in plugins
|
||||||
|
- Inter-plugin dependency scenarios
|
||||||
|
- Edge cases and error conditions
|
||||||
|
5. Complete documentation with examples
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const resolver = new CustomResolver(losslessView, {
|
||||||
|
title: new LastWriteWinsPlugin(),
|
||||||
|
price: new LastWriteWinsPlugin(),
|
||||||
|
discount: new LastWriteWinsPlugin()
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Dependent Plugins
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const resolver = new CustomResolver(losslessView, {
|
||||||
|
basePrice: new LastWriteWinsPlugin(),
|
||||||
|
discount: new LastWriteWinsPlugin(),
|
||||||
|
finalPrice: new DiscountedPricePlugin() // Depends on discount
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complex Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const resolver = new CustomResolver(losslessView, {
|
||||||
|
// Basic properties
|
||||||
|
name: new LastWriteWinsPlugin(),
|
||||||
|
description: new ConcatenationPlugin(' '),
|
||||||
|
|
||||||
|
// Pricing
|
||||||
|
basePrice: new LastWriteWinsPlugin(),
|
||||||
|
taxRate: new LastWriteWinsPlugin(),
|
||||||
|
discount: new LastWriteWinsPlugin(),
|
||||||
|
|
||||||
|
// Calculated fields
|
||||||
|
subtotal: new SubtotalCalculator(), // Uses basePrice and quantity
|
||||||
|
tax: new TaxCalculator(), // Uses subtotal and taxRate
|
||||||
|
total: new TotalCalculator() // Uses subtotal, tax, and discount
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
1. **Plugin Dependencies**: Explicitly declare dependencies between plugins
|
||||||
|
2. **Caching**: Cache resolved values for better performance
|
||||||
|
3. **Validation**: Add validation to prevent circular dependencies
|
||||||
|
4. **Debugging**: Add logging for plugin execution order and state access
|
||||||
|
5. **Optimization**: Lazy-load plugin states to improve performance with many properties
|
||||||
|
|
||||||
|
## Example Configurations
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const resolver = new CustomResolver(losslessView, {
|
||||||
|
title: new LastWriteWinsPlugin(),
|
||||||
|
price: new LastWriteWinsPlugin(),
|
||||||
|
discount: new LastWriteWinsPlugin()
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Dependent Plugins
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const resolver = new CustomResolver(losslessView, {
|
||||||
|
basePrice: new LastWriteWinsPlugin(),
|
||||||
|
discount: new LastWriteWinsPlugin(),
|
||||||
|
finalPrice: new DiscountedPricePlugin()
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
1. **Plugin Dependencies**: Explicitly declare dependencies between plugins
|
||||||
|
2. **Caching**: Cache resolved values for better performance
|
||||||
|
3. **Validation**: Add validation to prevent circular dependencies
|
||||||
|
4. **Debugging**: Add logging for plugin execution order and state access
|
@ -11,11 +11,19 @@ export interface ResolverPlugin<T = unknown> {
|
|||||||
initialize(): T;
|
initialize(): T;
|
||||||
|
|
||||||
// Process a new value for the property
|
// Process a new value for the property
|
||||||
update(currentState: T, newValue: PropertyTypes, delta: CollapsedDelta): T;
|
update(
|
||||||
|
currentState: T,
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
delta: CollapsedDelta,
|
||||||
|
allStates?: Record<PropertyID, unknown>
|
||||||
|
): T;
|
||||||
|
|
||||||
// Resolve the final value from the accumulated state
|
// Resolve the final value from the accumulated state
|
||||||
// Returns undefined if no valid value could be resolved
|
// Returns undefined if no valid value could be resolved
|
||||||
resolve(state: T): PropertyTypes | undefined;
|
resolve(
|
||||||
|
state: T,
|
||||||
|
allStates?: Record<PropertyID, unknown>
|
||||||
|
): PropertyTypes | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuration for custom resolver
|
// Configuration for custom resolver
|
||||||
@ -75,6 +83,13 @@ export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResol
|
|||||||
acc[cur.id] = { id: cur.id, properties: {} };
|
acc[cur.id] = { id: cur.id, properties: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First pass: collect all property states for this entity
|
||||||
|
const allStates: Record<PropertyID, unknown> = {};
|
||||||
|
for (const [propertyId, propertyState] of Object.entries(acc[cur.id].properties)) {
|
||||||
|
allStates[propertyId] = propertyState.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: update each property with access to all states
|
||||||
for (const [propertyId, deltas] of Object.entries(cur.propertyDeltas)) {
|
for (const [propertyId, deltas] of Object.entries(cur.propertyDeltas)) {
|
||||||
const plugin = this.config[propertyId];
|
const plugin = this.config[propertyId];
|
||||||
if (!plugin) continue;
|
if (!plugin) continue;
|
||||||
@ -85,6 +100,8 @@ export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResol
|
|||||||
plugin,
|
plugin,
|
||||||
state: plugin.initialize()
|
state: plugin.initialize()
|
||||||
};
|
};
|
||||||
|
// Update allStates with the new state
|
||||||
|
allStates[propertyId] = acc[cur.id].properties[propertyId].state;
|
||||||
}
|
}
|
||||||
|
|
||||||
const propertyState = acc[cur.id].properties[propertyId];
|
const propertyState = acc[cur.id].properties[propertyId];
|
||||||
@ -93,7 +110,14 @@ export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResol
|
|||||||
for (const delta of deltas || []) {
|
for (const delta of deltas || []) {
|
||||||
const value = extractValueFromDelta(propertyId, delta);
|
const value = extractValueFromDelta(propertyId, delta);
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
propertyState.state = propertyState.plugin.update(propertyState.state, value, delta);
|
propertyState.state = propertyState.plugin.update(
|
||||||
|
propertyState.state,
|
||||||
|
value,
|
||||||
|
delta,
|
||||||
|
allStates
|
||||||
|
);
|
||||||
|
// Update allStates with the new state
|
||||||
|
allStates[propertyId] = propertyState.state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,8 +131,18 @@ export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResol
|
|||||||
for (const [entityId, entity] of Object.entries(cur)) {
|
for (const [entityId, entity] of Object.entries(cur)) {
|
||||||
const entityResult: { id: string; properties: EntityProperties } = { id: entityId, properties: {} };
|
const entityResult: { id: string; properties: EntityProperties } = { id: entityId, properties: {} };
|
||||||
|
|
||||||
|
// First pass: collect all states for this entity
|
||||||
|
const allStates: Record<PropertyID, unknown> = {};
|
||||||
for (const [propertyId, propertyState] of Object.entries(entity.properties)) {
|
for (const [propertyId, propertyState] of Object.entries(entity.properties)) {
|
||||||
const resolvedValue = propertyState.plugin.resolve(propertyState.state);
|
allStates[propertyId] = propertyState.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: resolve each property with access to all states
|
||||||
|
for (const [propertyId, propertyState] of Object.entries(entity.properties)) {
|
||||||
|
const resolvedValue = propertyState.plugin.resolve(
|
||||||
|
propertyState.state,
|
||||||
|
allStates
|
||||||
|
);
|
||||||
// Only add the property if the resolved value is not undefined
|
// Only add the property if the resolved value is not undefined
|
||||||
if (resolvedValue !== undefined) {
|
if (resolvedValue !== undefined) {
|
||||||
entityResult.properties[propertyId] = resolvedValue;
|
entityResult.properties[propertyId] = resolvedValue;
|
||||||
@ -137,7 +171,12 @@ export class LastWriteWinsPlugin implements ResolverPlugin<{ value?: PropertyTyp
|
|||||||
return { timestamp: 0 };
|
return { timestamp: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
update(currentState: { value?: PropertyTypes, timestamp: number }, newValue: PropertyTypes, delta: CollapsedDelta) {
|
update(
|
||||||
|
currentState: { value?: PropertyTypes, timestamp: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
) {
|
||||||
if (delta.timeCreated > currentState.timestamp) {
|
if (delta.timeCreated > currentState.timestamp) {
|
||||||
return {
|
return {
|
||||||
value: newValue,
|
value: newValue,
|
||||||
@ -147,7 +186,10 @@ export class LastWriteWinsPlugin implements ResolverPlugin<{ value?: PropertyTyp
|
|||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(state: { value?: PropertyTypes, timestamp: number }): PropertyTypes {
|
resolve(
|
||||||
|
state: { value?: PropertyTypes, timestamp: number },
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
): PropertyTypes {
|
||||||
return state.value || '';
|
return state.value || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,7 +202,12 @@ export class FirstWriteWinsPlugin implements ResolverPlugin<{ value?: PropertyTy
|
|||||||
return { timestamp: Infinity };
|
return { timestamp: Infinity };
|
||||||
}
|
}
|
||||||
|
|
||||||
update(currentState: { value?: PropertyTypes, timestamp: number }, newValue: PropertyTypes, delta: CollapsedDelta) {
|
update(
|
||||||
|
currentState: { value?: PropertyTypes, timestamp: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
) {
|
||||||
if (delta.timeCreated < currentState.timestamp) {
|
if (delta.timeCreated < currentState.timestamp) {
|
||||||
return {
|
return {
|
||||||
value: newValue,
|
value: newValue,
|
||||||
@ -170,7 +217,10 @@ export class FirstWriteWinsPlugin implements ResolverPlugin<{ value?: PropertyTy
|
|||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(state: { value?: PropertyTypes, timestamp: number }): PropertyTypes {
|
resolve(
|
||||||
|
state: { value?: PropertyTypes, timestamp: number },
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
): PropertyTypes {
|
||||||
return state.value || '';
|
return state.value || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,7 +235,12 @@ export class ConcatenationPlugin implements ResolverPlugin<{ values: { value: st
|
|||||||
return { values: [] };
|
return { values: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
update(currentState: { values: { value: string, timestamp: number }[] }, newValue: PropertyTypes, delta: CollapsedDelta) {
|
update(
|
||||||
|
currentState: { values: { value: string, timestamp: number }[] },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
) {
|
||||||
if (typeof newValue === 'string') {
|
if (typeof newValue === 'string') {
|
||||||
// Check if this value already exists (avoid duplicates)
|
// Check if this value already exists (avoid duplicates)
|
||||||
const exists = currentState.values.some(v => v.value === newValue);
|
const exists = currentState.values.some(v => v.value === newValue);
|
||||||
@ -201,7 +256,10 @@ export class ConcatenationPlugin implements ResolverPlugin<{ values: { value: st
|
|||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(state: { values: { value: string, timestamp: number }[] }): PropertyTypes {
|
resolve(
|
||||||
|
state: { values: { value: string, timestamp: number }[] },
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
): PropertyTypes {
|
||||||
return state.values.map(v => v.value).join(this.separator);
|
return state.values.map(v => v.value).join(this.separator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,13 +272,21 @@ export class MajorityVotePlugin implements ResolverPlugin<{ votes: Map<PropertyT
|
|||||||
return { votes: new Map() };
|
return { votes: new Map() };
|
||||||
}
|
}
|
||||||
|
|
||||||
update(currentState: { votes: Map<PropertyTypes, number> }, newValue: PropertyTypes, _delta: CollapsedDelta) {
|
update(
|
||||||
|
currentState: { votes: Map<PropertyTypes, number> },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
_delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
) {
|
||||||
const currentCount = currentState.votes.get(newValue) || 0;
|
const currentCount = currentState.votes.get(newValue) || 0;
|
||||||
currentState.votes.set(newValue, currentCount + 1);
|
currentState.votes.set(newValue, currentCount + 1);
|
||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(state: { votes: Map<PropertyTypes, number> }): PropertyTypes {
|
resolve(
|
||||||
|
state: { votes: Map<PropertyTypes, number> },
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
): PropertyTypes {
|
||||||
let maxVotes = 0;
|
let maxVotes = 0;
|
||||||
let winner: PropertyTypes = '';
|
let winner: PropertyTypes = '';
|
||||||
|
|
||||||
@ -243,7 +309,12 @@ export class MinPlugin implements ResolverPlugin<{ min?: number }> {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
update(currentState: { min?: number }, newValue: PropertyTypes, _delta: CollapsedDelta) {
|
update(
|
||||||
|
currentState: { min?: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
_delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
) {
|
||||||
if (typeof newValue === 'number') {
|
if (typeof newValue === 'number') {
|
||||||
if (currentState.min === undefined || newValue < currentState.min) {
|
if (currentState.min === undefined || newValue < currentState.min) {
|
||||||
return { min: newValue };
|
return { min: newValue };
|
||||||
@ -252,7 +323,10 @@ export class MinPlugin implements ResolverPlugin<{ min?: number }> {
|
|||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(state: { min?: number }): PropertyTypes | undefined {
|
resolve(
|
||||||
|
state: { min?: number },
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
): PropertyTypes | undefined {
|
||||||
return state.min;
|
return state.min;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,7 +338,12 @@ export class MaxPlugin implements ResolverPlugin<{ max?: number }> {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
update(currentState: { max?: number }, newValue: PropertyTypes, _delta: CollapsedDelta) {
|
update(
|
||||||
|
currentState: { max?: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
_delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
) {
|
||||||
if (typeof newValue === 'number') {
|
if (typeof newValue === 'number') {
|
||||||
if (currentState.max === undefined || newValue > currentState.max) {
|
if (currentState.max === undefined || newValue > currentState.max) {
|
||||||
return { max: newValue };
|
return { max: newValue };
|
||||||
@ -273,7 +352,10 @@ export class MaxPlugin implements ResolverPlugin<{ max?: number }> {
|
|||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(state: { max?: number }): PropertyTypes | undefined {
|
resolve(
|
||||||
|
state: { max?: number },
|
||||||
|
_allStates?: Record<PropertyID, unknown>
|
||||||
|
): PropertyTypes | undefined {
|
||||||
return state.max;
|
return state.max;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user