feat: send dashboard read-model deltas

This commit is contained in:
2026-08-11 12:36:29 +00:00
parent c737ee2cd1
commit a2e94c3092
17 changed files with 1285 additions and 395 deletions
+1
View File
@@ -16,6 +16,7 @@ export * from './tournament/autoStart.js';
export * from './turnDaemon/types.js';
export * from './realtime/keys.js';
export * from './realtime/types.js';
export * from './realtime/delta.js';
export * from './ranking/types.js';
export * from './ranking/legacyColor.js';
export * from './auth/accountIconProjection.js';
+88
View File
@@ -0,0 +1,88 @@
import { applyPatch, createPatch, type Operation } from 'rfc6902';
export interface JsonPatchOperation {
op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test';
path: string;
from?: string;
value?: unknown;
}
export type ReadModelDelta<T> =
| {
kind: 'snapshot';
revision: string;
data: T;
}
| {
kind: 'unchanged';
revision: string;
}
| {
kind: 'patch';
baseRevision: string;
revision: string;
operations: JsonPatchOperation[];
};
export class ReadModelDeltaMismatchError extends Error {
constructor(message: string) {
super(message);
this.name = 'ReadModelDeltaMismatchError';
}
}
export interface AppliedReadModelDelta<T> {
data: T;
revision: string;
}
const cloneJsonValue = <T>(value: T): T => structuredClone(value);
export const createJsonPatch = (current: unknown, next: unknown): JsonPatchOperation[] => createPatch(current, next);
export const applyReadModelDelta = <T>(
current: T | undefined,
currentRevision: string | null,
delta: ReadModelDelta<T>
): AppliedReadModelDelta<T> => {
if (delta.kind === 'snapshot') {
return {
data: delta.data,
revision: delta.revision,
};
}
if (current === undefined || currentRevision === null) {
throw new ReadModelDeltaMismatchError('A delta cannot be applied before the initial snapshot.');
}
if (delta.kind === 'unchanged') {
if (currentRevision !== delta.revision) {
throw new ReadModelDeltaMismatchError(
`Unchanged revision mismatch: have ${currentRevision}, received ${delta.revision}.`
);
}
return {
data: current,
revision: currentRevision,
};
}
if (currentRevision !== delta.baseRevision) {
throw new ReadModelDeltaMismatchError(
`Patch base revision mismatch: have ${currentRevision}, expected ${delta.baseRevision}.`
);
}
const next = cloneJsonValue(current);
const errors = applyPatch(next, delta.operations as Operation[]);
const failure = errors.find((error) => error !== null);
if (failure) {
throw new ReadModelDeltaMismatchError(`JSON Patch application failed: ${failure.message}`);
}
return {
data: next,
revision: delta.revision,
};
};