Separated custom-resolver.ts into separate files
This commit is contained in:
parent
cf29338c9b
commit
f6790bf90d
@ -1,6 +1,6 @@
|
|||||||
|
import { RhizomeNode, Lossless, createDelta } from "../../../../src";
|
||||||
|
import { CollapsedDelta } from "../../../../src/views/lossless";
|
||||||
import {
|
import {
|
||||||
RhizomeNode,
|
|
||||||
Lossless,
|
|
||||||
CustomResolver,
|
CustomResolver,
|
||||||
ResolverPlugin,
|
ResolverPlugin,
|
||||||
LastWriteWinsPlugin,
|
LastWriteWinsPlugin,
|
||||||
@ -8,11 +8,10 @@ import {
|
|||||||
ConcatenationPlugin,
|
ConcatenationPlugin,
|
||||||
MajorityVotePlugin,
|
MajorityVotePlugin,
|
||||||
MinPlugin,
|
MinPlugin,
|
||||||
MaxPlugin,
|
MaxPlugin
|
||||||
PropertyTypes,
|
} from "../../../../src/views/resolvers/custom-resolvers";
|
||||||
CollapsedDelta,
|
|
||||||
createDelta
|
type PropertyTypes = string | number | boolean | null;
|
||||||
} from "../../../../src";
|
|
||||||
|
|
||||||
describe('Custom Resolvers', () => {
|
describe('Custom Resolvers', () => {
|
||||||
let node: RhizomeNode;
|
let node: RhizomeNode;
|
||||||
|
@ -5,3 +5,4 @@
|
|||||||
- [ ] Rename/consolidate, lossless view() and compose() --> composeView()
|
- [ ] Rename/consolidate, lossless view() and compose() --> composeView()
|
||||||
- [ ] Rename Lossless to HyperView
|
- [ ] Rename Lossless to HyperView
|
||||||
- [ ] Rename Lossy to View
|
- [ ] Rename Lossy to View
|
||||||
|
- [ ] Consider whether we should use collapsed deltas
|
||||||
|
3
src/views/resolvers/custom-resolvers/index.ts
Normal file
3
src/views/resolvers/custom-resolvers/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './plugin';
|
||||||
|
export * from './resolver';
|
||||||
|
export * from './plugins';
|
45
src/views/resolvers/custom-resolvers/plugin.ts
Normal file
45
src/views/resolvers/custom-resolvers/plugin.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { PropertyID, PropertyTypes } from "../../../core/types";
|
||||||
|
import { CollapsedDelta } from "../../lossless";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin interface for custom resolvers
|
||||||
|
*/
|
||||||
|
export interface ResolverPlugin<T = unknown> {
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of property IDs that this plugin depends on.
|
||||||
|
* These properties will be processed before this plugin.
|
||||||
|
*/
|
||||||
|
dependencies?: PropertyID[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the final value from the accumulated state
|
||||||
|
*/
|
||||||
|
resolve(
|
||||||
|
state: T,
|
||||||
|
allStates?: Record<PropertyID, unknown>
|
||||||
|
): PropertyTypes | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for custom resolver
|
||||||
|
*/
|
||||||
|
export type CustomResolverConfig = {
|
||||||
|
[propertyId: PropertyID]: ResolverPlugin;
|
||||||
|
};
|
@ -0,0 +1,57 @@
|
|||||||
|
import { PropertyTypes } from "../../../../core/types";
|
||||||
|
import { CollapsedDelta } from "../../../lossless";
|
||||||
|
import { ResolverPlugin } from "../plugin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenation plugin (for string values)
|
||||||
|
*
|
||||||
|
* Concatenates all string values with a separator
|
||||||
|
*/
|
||||||
|
export class ConcatenationPlugin implements ResolverPlugin<{ values: { value: string, timestamp: number }[] }> {
|
||||||
|
name = 'concatenation';
|
||||||
|
dependencies: string[] = [];
|
||||||
|
|
||||||
|
constructor(private separator: string = ' ') {}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return { values: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
currentState: { values: { value: string, timestamp: number }[] },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
if (typeof newValue === 'string') {
|
||||||
|
return {
|
||||||
|
values: [
|
||||||
|
...currentState.values,
|
||||||
|
{ value: newValue, timestamp: delta.timeCreated }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(
|
||||||
|
state: { values: { value: string, timestamp: number }[] },
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
): PropertyTypes {
|
||||||
|
// Sort by timestamp to ensure consistent ordering
|
||||||
|
const sortedValues = [...state.values].sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
|
||||||
|
// Use a Set to track seen values and keep only the first occurrence of each value
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const uniqueValues: string[] = [];
|
||||||
|
|
||||||
|
for (const { value } of sortedValues) {
|
||||||
|
if (!seen.has(value)) {
|
||||||
|
seen.add(value);
|
||||||
|
uniqueValues.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniqueValues.join(this.separator);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
import { PropertyTypes } from "../../../../core/types";
|
||||||
|
import { CollapsedDelta } from "../../../lossless";
|
||||||
|
import { ResolverPlugin } from "../plugin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First Write Wins plugin
|
||||||
|
*
|
||||||
|
* Keeps the first value that was written, ignoring subsequent writes
|
||||||
|
*/
|
||||||
|
export class FirstWriteWinsPlugin implements ResolverPlugin<{ value?: PropertyTypes, timestamp: number }> {
|
||||||
|
name = 'first-write-wins';
|
||||||
|
dependencies: string[] = [];
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return { timestamp: Infinity };
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
currentState: { value?: PropertyTypes, timestamp: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
// Only update if this delta is earlier than our current earliest
|
||||||
|
if (delta.timeCreated < currentState.timestamp) {
|
||||||
|
return {
|
||||||
|
value: newValue,
|
||||||
|
timestamp: delta.timeCreated
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(
|
||||||
|
state: { value?: PropertyTypes, timestamp: number },
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
): PropertyTypes | undefined {
|
||||||
|
return state.value;
|
||||||
|
}
|
||||||
|
}
|
6
src/views/resolvers/custom-resolvers/plugins/index.ts
Normal file
6
src/views/resolvers/custom-resolvers/plugins/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from './last-write-wins.plugin';
|
||||||
|
export * from './first-write-wins.plugin';
|
||||||
|
export * from './concatenation.plugin';
|
||||||
|
export * from './majority-vote.plugin';
|
||||||
|
export * from './min.plugin';
|
||||||
|
export * from './max.plugin';
|
@ -0,0 +1,39 @@
|
|||||||
|
import { PropertyID, PropertyTypes } from "../../../../core/types";
|
||||||
|
import { CollapsedDelta } from "../../../lossless";
|
||||||
|
import { ResolverPlugin } from "../plugin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last Write Wins plugin
|
||||||
|
*
|
||||||
|
* Keeps the most recent value based on the delta's timestamp
|
||||||
|
*/
|
||||||
|
export class LastWriteWinsPlugin implements ResolverPlugin<{ value?: PropertyTypes, timestamp: number }> {
|
||||||
|
name = 'last-write-wins';
|
||||||
|
dependencies: PropertyID[] = [];
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return { timestamp: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
currentState: { value?: PropertyTypes, timestamp: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
if (delta.timeCreated > currentState.timestamp) {
|
||||||
|
return {
|
||||||
|
value: newValue,
|
||||||
|
timestamp: delta.timeCreated
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(
|
||||||
|
state: { value?: PropertyTypes, timestamp: number },
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
): PropertyTypes {
|
||||||
|
return state.value || '';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
import { PropertyTypes } from "../../../../core/types";
|
||||||
|
import { CollapsedDelta } from "../../../lossless";
|
||||||
|
import { ResolverPlugin } from "../plugin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Majority vote plugin
|
||||||
|
*
|
||||||
|
* Returns the value that appears most frequently
|
||||||
|
*/
|
||||||
|
export class MajorityVotePlugin implements ResolverPlugin<{ votes: Map<PropertyTypes, number> }> {
|
||||||
|
name = 'majority-vote';
|
||||||
|
dependencies: string[] = [];
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return { votes: new Map<PropertyTypes, number>() };
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
currentState: { votes: Map<PropertyTypes, number> },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
_delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
const currentCount = currentState.votes.get(newValue) || 0;
|
||||||
|
currentState.votes.set(newValue, currentCount + 1);
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(
|
||||||
|
state: { votes: Map<PropertyTypes, number> },
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
): PropertyTypes | undefined {
|
||||||
|
let maxCount = 0;
|
||||||
|
let result: PropertyTypes | undefined;
|
||||||
|
|
||||||
|
state.votes.forEach((count, value) => {
|
||||||
|
if (count > maxCount) {
|
||||||
|
maxCount = count;
|
||||||
|
result = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
38
src/views/resolvers/custom-resolvers/plugins/max.plugin.ts
Normal file
38
src/views/resolvers/custom-resolvers/plugins/max.plugin.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { PropertyTypes } from "../../../../core/types";
|
||||||
|
import { CollapsedDelta } from "../../../lossless";
|
||||||
|
import { ResolverPlugin } from "../plugin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Numeric max plugin
|
||||||
|
*
|
||||||
|
* Tracks the maximum numeric value
|
||||||
|
*/
|
||||||
|
export class MaxPlugin implements ResolverPlugin<{ max?: number }> {
|
||||||
|
name = 'max';
|
||||||
|
dependencies: string[] = [];
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return { max: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
currentState: { max?: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
_delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
const numValue = typeof newValue === 'number' ? newValue : parseFloat(String(newValue));
|
||||||
|
|
||||||
|
if (!isNaN(numValue) && (currentState.max === undefined || numValue > currentState.max)) {
|
||||||
|
return { max: numValue };
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(
|
||||||
|
state: { max?: number },
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
): PropertyTypes | undefined {
|
||||||
|
return state.max;
|
||||||
|
}
|
||||||
|
}
|
38
src/views/resolvers/custom-resolvers/plugins/min.plugin.ts
Normal file
38
src/views/resolvers/custom-resolvers/plugins/min.plugin.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { PropertyTypes } from "../../../../core/types";
|
||||||
|
import { CollapsedDelta } from "../../../lossless";
|
||||||
|
import { ResolverPlugin } from "../plugin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Numeric min plugin
|
||||||
|
*
|
||||||
|
* Tracks the minimum numeric value
|
||||||
|
*/
|
||||||
|
export class MinPlugin implements ResolverPlugin<{ min?: number }> {
|
||||||
|
name = 'min';
|
||||||
|
dependencies: string[] = [];
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return { min: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
currentState: { min?: number },
|
||||||
|
newValue: PropertyTypes,
|
||||||
|
_delta: CollapsedDelta,
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
const numValue = typeof newValue === 'number' ? newValue : parseFloat(String(newValue));
|
||||||
|
|
||||||
|
if (!isNaN(numValue) && (currentState.min === undefined || numValue < currentState.min)) {
|
||||||
|
return { min: numValue };
|
||||||
|
}
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(
|
||||||
|
state: { min?: number },
|
||||||
|
_allStates?: Record<string, unknown>
|
||||||
|
): PropertyTypes | undefined {
|
||||||
|
return state.min;
|
||||||
|
}
|
||||||
|
}
|
@ -1,40 +1,8 @@
|
|||||||
import { EntityProperties } from "../../core/entity";
|
import { EntityProperties } from "../../../core/entity";
|
||||||
import { CollapsedDelta, Lossless, LosslessViewOne } from "../lossless";
|
import { CollapsedDelta, Lossless, LosslessViewOne } from "../../lossless";
|
||||||
import { Lossy } from '../lossy';
|
import { Lossy } from '../../lossy';
|
||||||
import { DomainEntityID, PropertyID, PropertyTypes, ViewMany } from "../../core/types";
|
import { DomainEntityID, PropertyID, PropertyTypes, ViewMany } from "../../../core/types";
|
||||||
|
import { ResolverPlugin } from "./plugin";
|
||||||
// Plugin interface for custom resolvers
|
|
||||||
export interface ResolverPlugin<T = unknown> {
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array of property IDs that this plugin depends on.
|
|
||||||
* These properties will be processed before this plugin.
|
|
||||||
*/
|
|
||||||
dependencies?: PropertyID[];
|
|
||||||
|
|
||||||
// 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>
|
|
||||||
): T;
|
|
||||||
|
|
||||||
// Resolve the final value from the accumulated state
|
|
||||||
resolve(
|
|
||||||
state: T,
|
|
||||||
allStates?: Record<PropertyID, unknown>
|
|
||||||
): PropertyTypes | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuration for custom resolver
|
|
||||||
export type CustomResolverConfig = {
|
|
||||||
[propertyId: PropertyID]: ResolverPlugin;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PropertyState = {
|
type PropertyState = {
|
||||||
plugin: ResolverPlugin;
|
plugin: ResolverPlugin;
|
||||||
@ -57,7 +25,9 @@ type CustomResolverResult = ViewMany<{
|
|||||||
properties: EntityProperties;
|
properties: EntityProperties;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Extract value from delta for a specific property
|
/**
|
||||||
|
* Extract value from delta for a specific property
|
||||||
|
*/
|
||||||
function extractValueFromDelta(propertyId: PropertyID, delta: CollapsedDelta): PropertyTypes | undefined {
|
function extractValueFromDelta(propertyId: PropertyID, delta: CollapsedDelta): PropertyTypes | undefined {
|
||||||
for (const pointer of delta.pointers) {
|
for (const pointer of delta.pointers) {
|
||||||
for (const [key, value] of Object.entries(pointer)) {
|
for (const [key, value] of Object.entries(pointer)) {
|
||||||
@ -71,11 +41,11 @@ function extractValueFromDelta(propertyId: PropertyID, delta: CollapsedDelta): P
|
|||||||
|
|
||||||
export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResolverResult> {
|
export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResolverResult> {
|
||||||
private executionOrder: PropertyID[];
|
private executionOrder: PropertyID[];
|
||||||
private readonly config: CustomResolverConfig;
|
private readonly config: Record<PropertyID, ResolverPlugin>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
lossless: Lossless,
|
lossless: Lossless,
|
||||||
config: CustomResolverConfig
|
config: Record<PropertyID, ResolverPlugin>
|
||||||
) {
|
) {
|
||||||
super(lossless);
|
super(lossless);
|
||||||
this.config = config;
|
this.config = config;
|
||||||
@ -258,212 +228,4 @@ export class CustomResolver extends Lossy<CustomResolverAccumulator, CustomResol
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Built-in plugin implementations
|
|
||||||
|
|
||||||
// Last Write Wins plugin
|
|
||||||
export class LastWriteWinsPlugin implements ResolverPlugin<{ value?: PropertyTypes, timestamp: number }> {
|
|
||||||
name = 'last-write-wins';
|
|
||||||
dependencies: PropertyID[] = [];
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
return { timestamp: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
update(
|
|
||||||
currentState: { value?: PropertyTypes, timestamp: number },
|
|
||||||
newValue: PropertyTypes,
|
|
||||||
delta: CollapsedDelta,
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
) {
|
|
||||||
if (delta.timeCreated > currentState.timestamp) {
|
|
||||||
return {
|
|
||||||
value: newValue,
|
|
||||||
timestamp: delta.timeCreated
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(
|
|
||||||
state: { value?: PropertyTypes, timestamp: number },
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
): PropertyTypes {
|
|
||||||
return state.value || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// First Write Wins plugin
|
|
||||||
export class FirstWriteWinsPlugin implements ResolverPlugin<{ value?: PropertyTypes, timestamp: number }> {
|
|
||||||
name = 'first-write-wins';
|
|
||||||
dependencies: PropertyID[] = [];
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
return { timestamp: Infinity };
|
|
||||||
}
|
|
||||||
|
|
||||||
update(
|
|
||||||
currentState: { value?: PropertyTypes, timestamp: number },
|
|
||||||
newValue: PropertyTypes,
|
|
||||||
delta: CollapsedDelta,
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
) {
|
|
||||||
if (delta.timeCreated < currentState.timestamp) {
|
|
||||||
return {
|
|
||||||
value: newValue,
|
|
||||||
timestamp: delta.timeCreated
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(
|
|
||||||
state: { value?: PropertyTypes, timestamp: number },
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
): PropertyTypes {
|
|
||||||
return state.value || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concatenation plugin (for string values)
|
|
||||||
export class ConcatenationPlugin implements ResolverPlugin<{ values: { value: string, timestamp: number }[] }> {
|
|
||||||
name = 'concatenation';
|
|
||||||
dependencies: PropertyID[] = [];
|
|
||||||
|
|
||||||
constructor(private separator: string = ' ') { }
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
return { values: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
update(
|
|
||||||
currentState: { values: { value: string, timestamp: number }[] },
|
|
||||||
newValue: PropertyTypes,
|
|
||||||
delta: CollapsedDelta,
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
) {
|
|
||||||
if (typeof newValue === 'string') {
|
|
||||||
// Check if this value already exists (avoid duplicates)
|
|
||||||
const exists = currentState.values.some(v => v.value === newValue);
|
|
||||||
if (!exists) {
|
|
||||||
currentState.values.push({
|
|
||||||
value: newValue,
|
|
||||||
timestamp: delta.timeCreated
|
|
||||||
});
|
|
||||||
// Sort by timestamp to maintain chronological order
|
|
||||||
currentState.values.sort((a, b) => a.timestamp - b.timestamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(
|
|
||||||
state: { values: { value: string, timestamp: number }[] },
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
): PropertyTypes {
|
|
||||||
return state.values.map(v => v.value).join(this.separator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Majority vote plugin
|
|
||||||
export class MajorityVotePlugin implements ResolverPlugin<{ votes: Map<PropertyTypes, number> }> {
|
|
||||||
name = 'majority-vote';
|
|
||||||
dependencies: PropertyID[] = [];
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
return { votes: new Map() };
|
|
||||||
}
|
|
||||||
|
|
||||||
update(
|
|
||||||
currentState: { votes: Map<PropertyTypes, number> },
|
|
||||||
newValue: PropertyTypes,
|
|
||||||
_delta: CollapsedDelta,
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
) {
|
|
||||||
const currentCount = currentState.votes.get(newValue) || 0;
|
|
||||||
currentState.votes.set(newValue, currentCount + 1);
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(
|
|
||||||
state: { votes: Map<PropertyTypes, number> },
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
): PropertyTypes {
|
|
||||||
let maxVotes = 0;
|
|
||||||
let winner: PropertyTypes = '';
|
|
||||||
|
|
||||||
for (const [value, votes] of state.votes.entries()) {
|
|
||||||
if (votes > maxVotes) {
|
|
||||||
maxVotes = votes;
|
|
||||||
winner = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return winner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Numeric min plugin
|
|
||||||
export class MinPlugin implements ResolverPlugin<{ min?: number }> {
|
|
||||||
name = 'min';
|
|
||||||
dependencies: PropertyID[] = [];
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
update(
|
|
||||||
currentState: { min?: number },
|
|
||||||
newValue: PropertyTypes,
|
|
||||||
_delta: CollapsedDelta,
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
) {
|
|
||||||
if (typeof newValue === 'number') {
|
|
||||||
if (currentState.min === undefined || newValue < currentState.min) {
|
|
||||||
return { min: newValue };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(
|
|
||||||
state: { min?: number },
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
): PropertyTypes | undefined {
|
|
||||||
return state.min;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Numeric max plugin
|
|
||||||
export class MaxPlugin implements ResolverPlugin<{ max?: number }> {
|
|
||||||
name = 'max';
|
|
||||||
dependencies: PropertyID[] = [];
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
update(
|
|
||||||
currentState: { max?: number },
|
|
||||||
newValue: PropertyTypes,
|
|
||||||
_delta: CollapsedDelta,
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
) {
|
|
||||||
if (typeof newValue === 'number') {
|
|
||||||
if (currentState.max === undefined || newValue > currentState.max) {
|
|
||||||
return { max: newValue };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(
|
|
||||||
state: { max?: number },
|
|
||||||
_allStates?: Record<PropertyID, unknown>
|
|
||||||
): PropertyTypes | undefined {
|
|
||||||
return state.max;
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user