mirror of
https://github.com/the-draupnir-project/Draupnir.git
synced 2026-10-16 04:16:43 +02:00
Seperate Projection into distinct reader / writer sides and add description for Projection partitions (#1183)
Tests / Unit tests (push) Has been cancelled
Ci in a Box / Build & Lint / Node 24 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Build & Lint / Node 24 / slim / amd64 (push) Has been cancelled
Ci in a Box / Build & Lint / Node 26 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Build & Lint / Node 24 / alpine / arm64 (push) Has been cancelled
Ci in a Box / Build & Lint / Node 24 / slim / arm64 (push) Has been cancelled
Ci in a Box / Unit tests / Node 24 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Unit tests / Node 24 / slim / amd64 (push) Has been cancelled
Ci in a Box / Unit tests / Node 26 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Unit tests / Node 24 / alpine / arm64 (push) Has been cancelled
Ci in a Box / Unit tests / Node 24 / slim / arm64 (push) Has been cancelled
Ci in a Box / Integration tests / Node 24 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Integration tests / Node 24 / slim / amd64 (push) Has been cancelled
Ci in a Box / Integration tests / Node 26 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Integration tests / Node 24 / alpine / arm64 (push) Has been cancelled
Ci in a Box / Integration tests / Node 24 / slim / arm64 (push) Has been cancelled
Ci in a Box / Application Service Integration tests / Node 24 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Application Service Integration tests / Node 24 / slim / amd64 (push) Has been cancelled
Ci in a Box / Application Service Integration tests / Node 26 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Application Service Integration tests / Node 24 / alpine / arm64 (push) Has been cancelled
Ci in a Box / Application Service Integration tests / Node 24 / slim / arm64 (push) Has been cancelled
Docker Hub - Develop / docker-latest (push) Has been cancelled
GHCR - Development Branches / ghcr-publish (push) Has been cancelled
Tests / Build & Lint (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Application Service Integration tests (push) Has been cancelled
Validate Docker Build / validate-docker-build (push) Has been cancelled
Tests / Unit tests (push) Has been cancelled
Ci in a Box / Build & Lint / Node 24 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Build & Lint / Node 24 / slim / amd64 (push) Has been cancelled
Ci in a Box / Build & Lint / Node 26 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Build & Lint / Node 24 / alpine / arm64 (push) Has been cancelled
Ci in a Box / Build & Lint / Node 24 / slim / arm64 (push) Has been cancelled
Ci in a Box / Unit tests / Node 24 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Unit tests / Node 24 / slim / amd64 (push) Has been cancelled
Ci in a Box / Unit tests / Node 26 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Unit tests / Node 24 / alpine / arm64 (push) Has been cancelled
Ci in a Box / Unit tests / Node 24 / slim / arm64 (push) Has been cancelled
Ci in a Box / Integration tests / Node 24 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Integration tests / Node 24 / slim / amd64 (push) Has been cancelled
Ci in a Box / Integration tests / Node 26 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Integration tests / Node 24 / alpine / arm64 (push) Has been cancelled
Ci in a Box / Integration tests / Node 24 / slim / arm64 (push) Has been cancelled
Ci in a Box / Application Service Integration tests / Node 24 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Application Service Integration tests / Node 24 / slim / amd64 (push) Has been cancelled
Ci in a Box / Application Service Integration tests / Node 26 / alpine / amd64 (push) Has been cancelled
Ci in a Box / Application Service Integration tests / Node 24 / alpine / arm64 (push) Has been cancelled
Ci in a Box / Application Service Integration tests / Node 24 / slim / arm64 (push) Has been cancelled
Docker Hub - Develop / docker-latest (push) Has been cancelled
GHCR - Development Branches / ghcr-publish (push) Has been cancelled
Tests / Build & Lint (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Application Service Integration tests (push) Has been cancelled
Validate Docker Build / validate-docker-build (push) Has been cancelled
* Split the Projection interface into distinct reader / writer sides. https://github.com/the-draupnir-project/planning/issues/150 * Add ProjectionDescription and make it hold the shape. https://github.com/the-draupnir-project/planning/issues/152 * Expand ignore pattern for unused vars Type paraemters weren't included mew * Add a builder for protection descriptions. https://github.com/the-draupnir-project/planning/issues/152 * WIP: begin using projection locator * Rename ProjectionLocator to ProjectionAllocator and wire in. The reason why we can't have an orchestrator own the projects yet is simply because currently the projections are created from _protected rooms set_ specific revision issuers that are disposed alongside it. And of course if we were naive and implemented the global orchestrator, we'd be tied to a stale protected rooms set if a Draupnir was restarted via safe mode or whatever. * Remove the projection allocator. The problem with it is that we would be tunneling through the dataflow from source to sink, not from sink to source. But the allocator is designed for sink to source. Which is an issue because the ownership semantics from source to sink are the desired ones (the orchestrator owns the projections and is a global thing that caches them). But from sink to source, ownership is adhoc and they can't be cached at all because context is needed from the non-ported parts of the upstream data pipeline. Which tbh are effectively non deterministic sources as far as the orchestrator engine is concerned. Additionally, using the projections or projection nodes from code outside of the reducer pipeline will always require allocation of a live handle. This is because of a few reaons: 1. GCing old projection nodes that are lazy loaded requires there are no live handles. 2. Managing cache of projection nodes (including via a Projection) on workers requires knowing if they are being accessed. I don't think there are other reasons. So this really is the API to get right. And i don't think it's easy while projections are muddled with context.
This commit is contained in:
1 parent
7822eda0c6
commit
a1cc936995
12 files changed
+332
-60
No files matched your search
+6
-1
@@ -64,7 +64,12 @@ export default tseslint.config(
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{ argsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_" },
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
destructuredArrayIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
// we implement a lot of interfaces that return promises with synchronous functions.
|
||||
"require-await": "off",
|
||||
|
||||
@@ -14,21 +14,21 @@ import {
|
||||
ExtractInputDeltaShapes,
|
||||
ExtractInputProjectionNodes,
|
||||
ExtractProjectionInputs,
|
||||
ProjectionNode,
|
||||
} from "./ProjectionNode";
|
||||
|
||||
export const ProjectionOrchestrationKey = Symbol("ProjectionOrchestration");
|
||||
|
||||
export type ProjectionNodeListener<
|
||||
TProjectionNode extends ProjectionNode = ProjectionNode,
|
||||
TProjectionNode extends AnyProjectionNode = AnyProjectionNode,
|
||||
> = (
|
||||
currentNode: TProjectionNode,
|
||||
delta: ExtractDeltaShape<TProjectionNode>,
|
||||
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<TProjectionNode>): void;
|
||||
@@ -36,6 +36,13 @@ export interface Projection<
|
||||
removeNodeListener(listener: ProjectionNodeListener<TProjectionNode>): this;
|
||||
}
|
||||
|
||||
export interface Projection<
|
||||
TProjectionNode extends AnyProjectionNode = AnyProjectionNode,
|
||||
> {
|
||||
readonly currentNode: TProjectionNode;
|
||||
readonly [ProjectionOrchestrationKey]: ProjectionOrchestration<TProjectionNode>;
|
||||
}
|
||||
|
||||
export type ExtractProjectionNode<TProjection> =
|
||||
TProjection extends Projection<infer TProjectionNode>
|
||||
? TProjectionNode
|
||||
@@ -55,9 +62,9 @@ export type ExtractProjectionNode<TProjection> =
|
||||
// something unsafe about forks in the first place? there is.
|
||||
// Dependencies like this can only be viewed through other projections.
|
||||
export class ProjectionOutputHelper<
|
||||
TProjectionNode extends ProjectionNode = ProjectionNode,
|
||||
TProjectionNode extends AnyProjectionNode = AnyProjectionNode,
|
||||
> {
|
||||
private readonly outputs = new Set<Projection<ProjectionNode>>();
|
||||
private readonly outputs = new Set<ProjectionOrchestration>();
|
||||
private readonly emitter = new EventEmitter();
|
||||
public constructor(public currentNode: TProjectionNode) {
|
||||
// nothing to do.
|
||||
@@ -81,12 +88,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// SPDX-FileCopyrightText: 2026 Gnuxie <Gnuxie@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// SPDX-FileAttributionText: <text>
|
||||
// This modified file incorporates work from Draupnir
|
||||
// https://github.com/the-draupnir-project/Draupnir
|
||||
// </text>
|
||||
|
||||
declare const ProjectionDescriptionShape: unique symbol;
|
||||
|
||||
export type ProjectionPartitionKeys = readonly string[];
|
||||
|
||||
export interface ProjectionDescription<
|
||||
TInputs extends readonly unknown[] = readonly unknown[],
|
||||
TDownstreamDeltaShape = unknown,
|
||||
TAccessMixin = Record<never, never>,
|
||||
TPartitionKeys extends ProjectionPartitionKeys = ProjectionPartitionKeys,
|
||||
> {
|
||||
readonly name: string;
|
||||
readonly partitionKeys: TPartitionKeys;
|
||||
readonly [ProjectionDescriptionShape]?: {
|
||||
readonly inputs: TInputs;
|
||||
readonly downstreamDeltaShape: TDownstreamDeltaShape;
|
||||
readonly accessMixin: TAccessMixin;
|
||||
};
|
||||
}
|
||||
|
||||
export type AnyProjectionDescription = ProjectionDescription;
|
||||
|
||||
type ProjectionDescriptionRuntime<
|
||||
TName extends string,
|
||||
TPartitionKeys extends ProjectionPartitionKeys,
|
||||
> = {
|
||||
readonly name: TName;
|
||||
readonly partitionKeys: TPartitionKeys;
|
||||
};
|
||||
|
||||
export type ProjectionDescriptionBuilder<
|
||||
TName extends string,
|
||||
TPartitionKeys extends ProjectionPartitionKeys,
|
||||
TInputs extends readonly unknown[] = readonly unknown[],
|
||||
TDownstreamDeltaShape = unknown,
|
||||
TAccessMixin = Record<never, never>,
|
||||
> = {
|
||||
withInputs<
|
||||
TNextInputs extends readonly unknown[],
|
||||
>(): ProjectionDescriptionBuilder<
|
||||
TName,
|
||||
TPartitionKeys,
|
||||
TNextInputs,
|
||||
TDownstreamDeltaShape,
|
||||
TAccessMixin
|
||||
>;
|
||||
withDownstreamDeltaShape<
|
||||
TNextDownstreamDeltaShape,
|
||||
>(): ProjectionDescriptionBuilder<
|
||||
TName,
|
||||
TPartitionKeys,
|
||||
TInputs,
|
||||
TNextDownstreamDeltaShape,
|
||||
TAccessMixin
|
||||
>;
|
||||
withAccessMixin<TNextAccessMixin>(): ProjectionDescriptionBuilder<
|
||||
TName,
|
||||
TPartitionKeys,
|
||||
TInputs,
|
||||
TDownstreamDeltaShape,
|
||||
TNextAccessMixin
|
||||
>;
|
||||
build(): ProjectionDescription<
|
||||
TInputs,
|
||||
TDownstreamDeltaShape,
|
||||
TAccessMixin,
|
||||
TPartitionKeys
|
||||
> & {
|
||||
readonly name: TName;
|
||||
};
|
||||
};
|
||||
|
||||
function makeProjectionDescriptionBuilder<
|
||||
TName extends string,
|
||||
TPartitionKeys extends ProjectionPartitionKeys,
|
||||
TInputs extends readonly unknown[] = readonly unknown[],
|
||||
TDownstreamDeltaShape = unknown,
|
||||
TAccessMixin = Record<never, never>,
|
||||
>(
|
||||
description: ProjectionDescriptionRuntime<TName, TPartitionKeys>
|
||||
): ProjectionDescriptionBuilder<
|
||||
TName,
|
||||
TPartitionKeys,
|
||||
TInputs,
|
||||
TDownstreamDeltaShape,
|
||||
TAccessMixin
|
||||
> {
|
||||
return {
|
||||
withInputs<TNextInputs extends readonly unknown[]>() {
|
||||
return makeProjectionDescriptionBuilder<
|
||||
TName,
|
||||
TPartitionKeys,
|
||||
TNextInputs,
|
||||
TDownstreamDeltaShape,
|
||||
TAccessMixin
|
||||
>(description);
|
||||
},
|
||||
withDownstreamDeltaShape<TNextDownstreamDeltaShape>() {
|
||||
return makeProjectionDescriptionBuilder<
|
||||
TName,
|
||||
TPartitionKeys,
|
||||
TInputs,
|
||||
TNextDownstreamDeltaShape,
|
||||
TAccessMixin
|
||||
>(description);
|
||||
},
|
||||
withAccessMixin<TNextAccessMixin>() {
|
||||
return makeProjectionDescriptionBuilder<
|
||||
TName,
|
||||
TPartitionKeys,
|
||||
TInputs,
|
||||
TDownstreamDeltaShape,
|
||||
TNextAccessMixin
|
||||
>(description);
|
||||
},
|
||||
build() {
|
||||
return description;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function describeProjection<
|
||||
const TName extends string,
|
||||
const TPartitionKeys extends ProjectionPartitionKeys,
|
||||
>({
|
||||
name,
|
||||
partitionKeys,
|
||||
}: {
|
||||
readonly name: TName;
|
||||
readonly partitionKeys: TPartitionKeys;
|
||||
}): ProjectionDescriptionBuilder<TName, TPartitionKeys> {
|
||||
return makeProjectionDescriptionBuilder<TName, TPartitionKeys>({
|
||||
name,
|
||||
partitionKeys,
|
||||
});
|
||||
}
|
||||
|
||||
export type ExtractProjectionDescriptionInputs<
|
||||
TDescription extends AnyProjectionDescription,
|
||||
> =
|
||||
TDescription extends ProjectionDescription<
|
||||
infer TInputs,
|
||||
infer _TDownstreamDeltaShape,
|
||||
infer _TAccessMixin,
|
||||
infer _TPartitionKeys
|
||||
>
|
||||
? TInputs
|
||||
: never;
|
||||
|
||||
export type ExtractProjectionDescriptionDeltaShape<
|
||||
TDescription extends AnyProjectionDescription,
|
||||
> =
|
||||
TDescription extends ProjectionDescription<
|
||||
infer _TInputs,
|
||||
infer TDownstreamDeltaShape,
|
||||
infer _TAccessMixin,
|
||||
infer _TPartitionKeys
|
||||
>
|
||||
? TDownstreamDeltaShape
|
||||
: never;
|
||||
|
||||
export type ExtractProjectionDescriptionAccessMixin<
|
||||
TDescription extends AnyProjectionDescription,
|
||||
> =
|
||||
TDescription extends ProjectionDescription<
|
||||
infer _TInputs,
|
||||
infer _TDownstreamDeltaShape,
|
||||
infer TAccessMixin,
|
||||
infer _TPartitionKeys
|
||||
>
|
||||
? TAccessMixin
|
||||
: never;
|
||||
|
||||
export type ExtractProjectionDescriptionPartitionKeys<
|
||||
TDescription extends AnyProjectionDescription,
|
||||
> =
|
||||
TDescription extends ProjectionDescription<
|
||||
infer _TInputs,
|
||||
infer _TDownstreamDeltaShape,
|
||||
infer _TAccessMixin,
|
||||
infer TPartitionKeys
|
||||
>
|
||||
? TPartitionKeys
|
||||
: never;
|
||||
|
||||
export type ProjectionPartition<
|
||||
TDescription extends AnyProjectionDescription = AnyProjectionDescription,
|
||||
> = {
|
||||
readonly [Key in ExtractProjectionDescriptionPartitionKeys<TDescription>[number]]: string;
|
||||
};
|
||||
@@ -8,27 +8,32 @@
|
||||
// </text>
|
||||
|
||||
import { ULID } from "ulidx";
|
||||
import type {
|
||||
AnyProjectionDescription,
|
||||
ExtractProjectionDescriptionAccessMixin,
|
||||
ExtractProjectionDescriptionDeltaShape,
|
||||
ExtractProjectionDescriptionInputs,
|
||||
} from "./ProjectionDescription";
|
||||
|
||||
export type ExtractDeltaShape<TProjectionNode extends ProjectionNode> =
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
TProjectionNode extends ProjectionNode<infer _, infer TDeltaShape>
|
||||
? TDeltaShape
|
||||
TProjectionNode extends ProjectionNode<infer TDescription>
|
||||
? ExtractProjectionDescriptionDeltaShape<TDescription>
|
||||
: never;
|
||||
|
||||
export type ExtractInputDeltaShapes<
|
||||
TInputs extends ProjectionNode[] | unknown[],
|
||||
> = TInputs extends ProjectionNode[]
|
||||
? ExtractDeltaShape<TInputs[number]>
|
||||
: unknown;
|
||||
export type ExtractInputDeltaShapes<TInputs extends readonly unknown[]> =
|
||||
TInputs extends readonly ProjectionNode[]
|
||||
? ExtractDeltaShape<TInputs[number]>
|
||||
: unknown;
|
||||
|
||||
export type ExtractInputProjectionNodes<
|
||||
TProjectionNode extends ProjectionNode,
|
||||
> = TProjectionNode extends ProjectionNode<infer TInputs> ? TInputs : never;
|
||||
> =
|
||||
TProjectionNode extends ProjectionNode<infer TDescription>
|
||||
? ExtractProjectionDescriptionInputs<TDescription>
|
||||
: never;
|
||||
|
||||
export type ProjectionNode<
|
||||
TInputs extends ProjectionNode[] | unknown[] = unknown[],
|
||||
TDownstreamDeltaShape = unknown,
|
||||
TAccessMixin = Record<never, never>,
|
||||
TDescription extends AnyProjectionDescription = AnyProjectionDescription,
|
||||
> = {
|
||||
readonly ulid: ULID;
|
||||
// Whether the projection has no state at all.
|
||||
@@ -40,21 +45,23 @@ export type ProjectionNode<
|
||||
* from deltas you have to use the input deltas.
|
||||
*/
|
||||
diff(
|
||||
nextNode: ProjectionNode<TInputs, TDownstreamDeltaShape, TAccessMixin>
|
||||
): TDownstreamDeltaShape;
|
||||
nextNode: ProjectionNode<TDescription>
|
||||
): ExtractProjectionDescriptionDeltaShape<TDescription>;
|
||||
/**
|
||||
* Reduces an input delta into the next projection node.
|
||||
*/
|
||||
reduceInput(
|
||||
input: ExtractInputDeltaShapes<TInputs>
|
||||
): ProjectionNode<TInputs, TDownstreamDeltaShape, TAccessMixin>;
|
||||
input: ExtractInputDeltaShapes<
|
||||
ExtractProjectionDescriptionInputs<TDescription>
|
||||
>
|
||||
): ProjectionNode<TDescription>;
|
||||
/**
|
||||
* Produces the initial node from the current input projection nodes. This can
|
||||
* only be used when the node is empty. Otherwise use reduceRebuild.
|
||||
*/
|
||||
reduceInitialInputs(
|
||||
input: TInputs
|
||||
): ProjectionNode<TInputs, TDownstreamDeltaShape, TAccessMixin>;
|
||||
input: ExtractProjectionDescriptionInputs<TDescription>
|
||||
): ProjectionNode<TDescription>;
|
||||
/**
|
||||
* Reconciles this node against the current input projection nodes. This is
|
||||
* intended for persistence/rebuild flows after reducer bugs are fixed.
|
||||
@@ -63,15 +70,15 @@ export type ProjectionNode<
|
||||
* corrective downstream delta by diffing this node against the corrected node.
|
||||
*/
|
||||
reduceRebuild?(
|
||||
inputs: TInputs
|
||||
): ProjectionNode<TInputs, TDownstreamDeltaShape, TAccessMixin>;
|
||||
} & TAccessMixin;
|
||||
inputs: ExtractProjectionDescriptionInputs<TDescription>
|
||||
): ProjectionNode<TDescription>;
|
||||
} & ExtractProjectionDescriptionAccessMixin<TDescription>;
|
||||
|
||||
export type AnyProjectionNode = ProjectionNode<never>;
|
||||
export type AnyProjectionNode = ProjectionNode;
|
||||
|
||||
export type ExtractProjectionInputs<
|
||||
TProjectionNode extends AnyProjectionNode = AnyProjectionNode,
|
||||
> =
|
||||
TProjectionNode extends ProjectionNode<infer TInputs>
|
||||
? ExtractInputDeltaShapes<TInputs>
|
||||
TProjectionNode extends ProjectionNode<infer TDescription>
|
||||
? ExtractInputDeltaShapes<ExtractProjectionDescriptionInputs<TDescription>>
|
||||
: never;
|
||||
@@ -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<void>;
|
||||
},
|
||||
|
||||
+5
@@ -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;
|
||||
|
||||
+33
-15
@@ -12,6 +12,10 @@ import {
|
||||
ExtractInputDeltaShapes,
|
||||
ProjectionNode,
|
||||
} from "../../../Projection/ProjectionNode";
|
||||
import {
|
||||
describeProjection,
|
||||
ProjectionDescription,
|
||||
} from "../../../Projection/ProjectionDescription";
|
||||
import {
|
||||
MemberPolicyMatches,
|
||||
MembershipPolicyRevision,
|
||||
@@ -30,11 +34,14 @@ import { ListMultiMap } from "../../../Projection/ListMultiMap";
|
||||
* This is just a stand in while we wait to convert the upstream MembershipPolicyRevision
|
||||
* to a projection.
|
||||
*/
|
||||
export type MemberBanInputProjectionNode = ProjectionNode<
|
||||
export type MemberBanInputProjectionDescription = ProjectionDescription<
|
||||
never[],
|
||||
MembershipPolicyRevisionDelta
|
||||
> &
|
||||
MembershipPolicyRevision;
|
||||
MembershipPolicyRevisionDelta,
|
||||
MembershipPolicyRevision
|
||||
>;
|
||||
|
||||
export type MemberBanInputProjectionNode =
|
||||
ProjectionNode<MemberBanInputProjectionDescription>;
|
||||
|
||||
export interface MemberBanIntentProjectionDelta {
|
||||
ban: StringUserID[];
|
||||
@@ -46,6 +53,14 @@ type MemberBanIntentMap = PersistentMap<
|
||||
List<LiteralPolicyRule | GlobPolicyRule>
|
||||
>;
|
||||
|
||||
export type MemberBanIntentProjectionAccessMixin = {
|
||||
allMembersWithRules(): MemberPolicyMatches[];
|
||||
isMemberBanned(member: StringUserID): boolean;
|
||||
allRulesMatchingMember(
|
||||
member: StringUserID
|
||||
): (LiteralPolicyRule | GlobPolicyRule)[];
|
||||
};
|
||||
|
||||
function isPolicyRelevant(policy: LiteralPolicyRule | GlobPolicyRule): boolean {
|
||||
return (
|
||||
policy.recommendation === Recommendation.Ban ||
|
||||
@@ -53,17 +68,20 @@ function isPolicyRelevant(policy: LiteralPolicyRule | GlobPolicyRule): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export type MemberBanIntentProjectionNode = ProjectionNode<
|
||||
[MemberBanInputProjectionNode],
|
||||
MemberBanIntentProjectionDelta,
|
||||
{
|
||||
allMembersWithRules(): MemberPolicyMatches[];
|
||||
isMemberBanned(member: StringUserID): boolean;
|
||||
allRulesMatchingMember(
|
||||
member: StringUserID
|
||||
): (LiteralPolicyRule | GlobPolicyRule)[];
|
||||
}
|
||||
>;
|
||||
export const MemberBanIntentProjectionDescription = describeProjection({
|
||||
name: "MemberBanIntentProjection",
|
||||
partitionKeys: ["draupnirID"],
|
||||
})
|
||||
.withInputs<[MemberBanInputProjectionNode]>()
|
||||
.withDownstreamDeltaShape<MemberBanIntentProjectionDelta>()
|
||||
.withAccessMixin<MemberBanIntentProjectionAccessMixin>()
|
||||
.build();
|
||||
|
||||
export type MemberBanIntentProjectionDescription =
|
||||
typeof MemberBanIntentProjectionDescription;
|
||||
|
||||
export type MemberBanIntentProjectionNode =
|
||||
ProjectionNode<MemberBanIntentProjectionDescription>;
|
||||
|
||||
// Upstream inputs are not yet converted to projections, so have to be never[]
|
||||
// for now.
|
||||
|
||||
+5
-1
@@ -9,10 +9,14 @@
|
||||
|
||||
import { PolicyListRevision } from "../../../PolicyList/PolicyListRevision";
|
||||
import { PolicyRuleChange } from "../../../PolicyList/PolicyRuleChange";
|
||||
import { ProjectionDescription } from "../../../Projection/ProjectionDescription";
|
||||
import { ProjectionNode } from "../../../Projection/ProjectionNode";
|
||||
|
||||
export type PolicyListBridgeProjectionNode = ProjectionNode<
|
||||
export type PolicyListBridgeProjectionDescription = ProjectionDescription<
|
||||
[],
|
||||
PolicyRuleChange[],
|
||||
PolicyListRevision
|
||||
>;
|
||||
|
||||
export type PolicyListBridgeProjectionNode =
|
||||
ProjectionNode<PolicyListBridgeProjectionDescription>;
|
||||
+5
@@ -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;
|
||||
}
|
||||
}
|
||||
+20
-8
@@ -8,6 +8,7 @@
|
||||
// </text>
|
||||
|
||||
import { StringServerName } from "@the-draupnir-project/matrix-basic-types";
|
||||
import { describeProjection } from "../../../Projection/ProjectionDescription";
|
||||
import { ProjectionNode } from "../../../Projection/ProjectionNode";
|
||||
import { PolicyListBridgeProjectionNode } from "./PolicyListBridgeProjection";
|
||||
import {
|
||||
@@ -35,17 +36,28 @@ type ServerBanIntentMap = PersistentMap<
|
||||
List<LiteralPolicyRule | GlobPolicyRule>
|
||||
>;
|
||||
|
||||
export type ServerBanIntentProjectionAccessMixin = {
|
||||
deny: StringServerName[];
|
||||
isServerDenied(serverName: StringServerName): boolean;
|
||||
};
|
||||
|
||||
// is there a way that we can adapt this so that it can possibly be swapped
|
||||
// to a lazy ban style protection if acls become exhausted.
|
||||
// not withiout addressing the issues in the member protection tbh.
|
||||
export type ServerBanIntentProjectionNode = ProjectionNode<
|
||||
[PolicyListBridgeProjectionNode],
|
||||
ServerBanIntentProjectionDelta,
|
||||
{
|
||||
deny: StringServerName[];
|
||||
isServerDenied(serverName: StringServerName): boolean;
|
||||
}
|
||||
>;
|
||||
export const ServerBanIntentProjectionDescription = describeProjection({
|
||||
name: "ServerBanIntentProjection",
|
||||
partitionKeys: ["draupnirID"],
|
||||
})
|
||||
.withInputs<[PolicyListBridgeProjectionNode]>()
|
||||
.withDownstreamDeltaShape<ServerBanIntentProjectionDelta>()
|
||||
.withAccessMixin<ServerBanIntentProjectionAccessMixin>()
|
||||
.build();
|
||||
|
||||
export type ServerBanIntentProjectionDescription =
|
||||
typeof ServerBanIntentProjectionDescription;
|
||||
|
||||
export type ServerBanIntentProjectionNode =
|
||||
ProjectionNode<ServerBanIntentProjectionDescription>;
|
||||
|
||||
export class StandardServerBanIntentProjectionNode implements ServerBanIntentProjectionNode {
|
||||
public readonly ulid: ULID;
|
||||
|
||||
@@ -118,6 +118,7 @@ export * from "./PolicyList/StandardPolicyRoomRevision";
|
||||
export * from "./PolicyList/StandardPolicyRoomRevisionIssuer";
|
||||
|
||||
export * from "./Projection/Projection";
|
||||
export * from "./Projection/ProjectionDescription";
|
||||
export * from "./Projection/ProjectionNode";
|
||||
|
||||
export * from "./Protection/Capability/StandardCapability/CapabilityMethodSchema";
|
||||
|
||||
@@ -52,7 +52,12 @@ export default tseslint.config({
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{ argsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_" },
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
destructuredArrayIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
// we implement a lot of interfaces that return promises with synchronous functions.
|
||||
"require-await": "off",
|
||||
|
||||
Reference in new issue
Block a user