From 0e3685a0f94e4400c657e973f74ab5b4e9a7097d Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 3 Jul 2026 16:30:54 +0100 Subject: [PATCH] Split the Projection interface into distinct reader / writer sides. https://github.com/the-draupnir-project/planning/issues/150 --- .../src/Projection/Projection.ts | 18 +++++++++++++----- .../src/Protection/ProtectionHandles.ts | 9 +++++++-- .../MemberBanIntentProjection.ts | 5 +++++ .../ServerBanIntentProjection.ts | 5 +++++ 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/matrix-protection-suite/src/Projection/Projection.ts b/packages/matrix-protection-suite/src/Projection/Projection.ts index b5647f5f..b0112cb7 100644 --- a/packages/matrix-protection-suite/src/Projection/Projection.ts +++ b/packages/matrix-protection-suite/src/Projection/Projection.ts @@ -17,6 +17,8 @@ import { ProjectionNode, } from "./ProjectionNode"; +export const ProjectionOrchestrationKey = Symbol("ProjectionOrchestration"); + export type ProjectionNodeListener< TProjectionNode extends ProjectionNode = ProjectionNode, > = ( @@ -25,10 +27,9 @@ export type ProjectionNodeListener< previousNode: TProjectionNode ) => void; -export interface Projection< +export interface ProjectionOrchestration< TProjectionNode extends AnyProjectionNode = AnyProjectionNode, > { - readonly currentNode: TProjectionNode; addOutput(projection: Projection): this; removeOutput(projection: Projection): this; applyInput(input: ExtractProjectionInputs): void; @@ -36,6 +37,13 @@ export interface Projection< removeNodeListener(listener: ProjectionNodeListener): this; } +export interface Projection< + TProjectionNode extends AnyProjectionNode = AnyProjectionNode, +> { + readonly currentNode: TProjectionNode; + readonly [ProjectionOrchestrationKey]: ProjectionOrchestration; +} + export type ExtractProjectionNode = TProjection extends Projection ? TProjectionNode @@ -57,7 +65,7 @@ export type ExtractProjectionNode = export class ProjectionOutputHelper< TProjectionNode extends ProjectionNode = ProjectionNode, > { - private readonly outputs = new Set>(); + private readonly outputs = new Set>(); private readonly emitter = new EventEmitter(); public constructor(public currentNode: TProjectionNode) { // nothing to do. @@ -81,12 +89,12 @@ export class ProjectionOutputHelper< } addOutput(projection: Projection): this { - this.outputs.add(projection); + this.outputs.add(projection[ProjectionOrchestrationKey]); return this; } removeOutput(projection: Projection): this { - this.outputs.delete(projection); + this.outputs.delete(projection[ProjectionOrchestrationKey]); return this; } diff --git a/packages/matrix-protection-suite/src/Protection/ProtectionHandles.ts b/packages/matrix-protection-suite/src/Protection/ProtectionHandles.ts index d7453bd5..2936d83f 100644 --- a/packages/matrix-protection-suite/src/Protection/ProtectionHandles.ts +++ b/packages/matrix-protection-suite/src/Protection/ProtectionHandles.ts @@ -12,6 +12,7 @@ import { ProtectedRoomsSet } from "./ProtectedRoomsSet"; import { Protection, ProtectionDescription } from "./Protection"; import { AnyProjectionNode } from "../Projection/ProjectionNode"; import { StandardHandleRegistryDescription } from "./HandleRegistry/StandardHandleRegistryDescription"; +import { ProjectionOrchestrationKey } from "../Projection/Projection"; export const ProtectionIntentProjectionNodeHandle: HandleDescription< "handleIntentProjectionNode", @@ -38,11 +39,15 @@ export const ProtectionIntentProjectionNodeHandle: HandleDescription< protection.handleIntentProjectionNode?.(node, delta); return lifetime.allocateResource( () => { - protection.intentProjection?.addNodeListener(listener); + protection.intentProjection?.[ + ProjectionOrchestrationKey + ].addNodeListener(listener); return Ok(listener); }, () => () => { - protection.intentProjection?.removeNodeListener(listener); + protection.intentProjection?.[ + ProjectionOrchestrationKey + ].removeNodeListener(listener); } ) as Result; }, diff --git a/packages/matrix-protection-suite/src/Protection/StandardProtections/MemberBanSynchronisation/MemberBanIntentProjection.ts b/packages/matrix-protection-suite/src/Protection/StandardProtections/MemberBanSynchronisation/MemberBanIntentProjection.ts index 3de2249e..817a8973 100644 --- a/packages/matrix-protection-suite/src/Protection/StandardProtections/MemberBanSynchronisation/MemberBanIntentProjection.ts +++ b/packages/matrix-protection-suite/src/Protection/StandardProtections/MemberBanSynchronisation/MemberBanIntentProjection.ts @@ -14,6 +14,7 @@ import { } from "../../../MembershipPolicies/SetMembershipPolicyRevisionIssuer"; import { Projection, + ProjectionOrchestrationKey, ProjectionOutputHelper, } from "../../../Projection/Projection"; import { @@ -44,6 +45,10 @@ export class StandardMemberBanIntentProjection ); } + public get [ProjectionOrchestrationKey]() { + return this; + } + private handleUpstreamRevision = ((_revision, delta) => { this.applyInput(delta); }) satisfies SetMembershipPolicyRevisionListener; diff --git a/packages/matrix-protection-suite/src/Protection/StandardProtections/ServerBanSynchronisation/ServerBanIntentProjection.ts b/packages/matrix-protection-suite/src/Protection/StandardProtections/ServerBanSynchronisation/ServerBanIntentProjection.ts index ad95cf92..adae2ff1 100644 --- a/packages/matrix-protection-suite/src/Protection/StandardProtections/ServerBanSynchronisation/ServerBanIntentProjection.ts +++ b/packages/matrix-protection-suite/src/Protection/StandardProtections/ServerBanSynchronisation/ServerBanIntentProjection.ts @@ -14,6 +14,7 @@ import { } from "../../../PolicyList/PolicyListRevisionIssuer"; import { Projection, + ProjectionOrchestrationKey, ProjectionOutputHelper, } from "../../../Projection/Projection"; import { @@ -49,4 +50,8 @@ export class StandardServerBanIntentProjection this.policyListRevisionIssuer.off("revision", this.handleUpstreamRevision); super[Symbol.dispose](); } + + public get [ProjectionOrchestrationKey]() { + return this; + } }