refactor: split brokerage operations repository and cursor codec (WealthLedger/backend#167) (#190)
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 1m11s
Backend verify / verify (push) Failing after 7m17s

This commit was merged in pull request #190.
This commit is contained in:
2026-11-04 22:01:02 +03:00
parent deda7ffc2c
commit cd78b43783
9 changed files with 570 additions and 415 deletions
@@ -0,0 +1,165 @@
import { accountHistory } from '../../investment-sync-plan';
import { confirmedOperationRanges, type OperationCoverage,
type OperationInterval,
operationIntervalsOverlap,
operationsSyncPriority,
planOperationRead,
uncoveredOperationIntervals } from './operations-sync-plan';
const UNKNOWN_AS_OF_MS = 0;
type WorkState = {
errorCode: null | string;
id: string;
intervals: OperationInterval[];
priority: number;
state: string;
};
type CoverageAccount = { accountId: string; openedOn: null | string };
// Pure derivation of the ledger read view: what was requested, what is
// confirmed, what is missing, and which sync work is currently relevant.
export function deriveReadCoverage(input: {
accounts: CoverageAccount[];
connectionMembershipAt: Date | null;
coverage: OperationCoverage[];
from: Date;
instrumentUid?: string;
movingEnd: boolean;
now: Date;
to: Date;
work: WorkState[];
}) {
const targets = input.accounts.flatMap((account) =>
input.from < input.to
? [
{
accountId: account.accountId,
from: input.from,
to: input.to,
instrumentUid: input.instrumentUid,
},
]
: [],
);
const plan = planOperationRead(targets, input.coverage, input.now, {
movingEnd: input.movingEnd,
});
const confirmed = confirmedOperationRanges(plan.intervals, input.coverage);
const missing = uncoveredOperationIntervals(
plan.intervals,
confirmed.map((range) => ({
...range,
instrumentUid: input.instrumentUid,
})),
);
const available = Boolean(input.connectionMembershipAt);
const complete = available && !missing.length;
const relevant = input.work.filter((work) =>
work.intervals.some((interval) =>
targets.some((target) => operationIntervalsOverlap(interval, target)),
),
);
const work =
relevant.find((work) => work.state === 'running') ??
relevant.find((work) => work.state === 'failed') ??
null;
const sources = input.coverage.filter((row) =>
targets.some(
(target) =>
operationIntervalsOverlap(row, target) &&
(!row.instrumentUid || row.instrumentUid === target.instrumentUid),
),
);
const lastSuccessfulAt =
sources
.map((row) => row.fetchedAt)
.sort((a, b) => a.valueOf() - b.valueOf())[0] ??
(complete ? input.connectionMembershipAt : null) ??
null;
const history = planOperationRead(
accountHistory(input.accounts, input.now),
input.coverage,
input.now,
{ movingEnd: true },
);
const knownBoundary = input.accounts.every(
(account) => account.openedOn !== null,
);
return {
targets,
plan,
confirmed,
missing,
available,
complete,
work,
lastSuccessfulAt,
history,
knownBoundary,
};
}
export function coverageAsOf(
plan: ReturnType<typeof planOperationRead>,
accountIds: string[],
): null | string {
if (
accountIds.length &&
accountIds.every((accountId) =>
plan.intervals.some(
(interval) =>
interval.accountId === accountId && interval.asOf,
),
)
) {
return new Date(
Math.min(
...accountIds.map((accountId) =>
Math.max(
...plan.intervals
.filter((interval) => interval.accountId === accountId)
.map((interval) => interval.asOf?.valueOf() ?? UNKNOWN_AS_OF_MS),
),
),
),
).toISOString();
}
return null;
}
export function historyState(
knownBoundary: boolean,
history: ReturnType<typeof planOperationRead>,
work: WorkState[],
coverageCount: number,
): 'complete' | 'failed' | 'loading' | 'missing' | 'partial' {
if (
work.some(
(work) => work.state === 'running' && work.priority > operationsSyncPriority.screen,
)
) {
return 'loading';
}
if (
work.some(
(work) => work.state === 'failed' && work.priority > operationsSyncPriority.screen,
)
) {
return 'failed';
}
if (
knownBoundary &&
history.intervals.every(
(interval) => interval.availability === 'complete',
)
) {
return 'complete';
}
return coverageCount ? 'partial' : 'missing';
}
export function serializeRange(range: { accountId: string; from: Date; to: Date }) {
return { accountId: range.accountId, from: range.from.toISOString(), to: range.to.toISOString() };
}