chore: strengthen backend eslint profile

This commit is contained in:
2026-09-28 11:04:18 +03:00
parent ca5dbcb382
commit 9b3558f96c
179 changed files with 4485 additions and 2716 deletions
@@ -1,24 +1,25 @@
import { Injectable } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import { createHash } from 'node:crypto';
import { createHash, randomUUID } from 'node:crypto';
import type { Prisma } from '../../../../../generated/prisma/client';
import { PrismaService } from '../../../../prisma/prisma.service';
import type { TbankSyncAttempt } from '../../../../integrations/tbank/tbank.api';
import { PrismaService } from '../../../../prisma/prisma.service';
import {
confirmedCandleCoverage,
type CandleRange,
confirmedCandleCoverage,
type SavedCandle,
} from '../domain/candle-coverage';
const provider = 'tbank';
const interval = 'day';
type CandleIntent = {
instrumentType?: 'share' | 'bond';
uid: string;
ranges: CandleRange[];
period?: string;
now?: string;
force?: boolean;
instrumentType?: 'bond' | 'share';
now?: string;
period?: string;
ranges: CandleRange[];
uid: string;
};
@Injectable()
export class CandleRepository {
@@ -57,7 +58,7 @@ export class CandleRepository {
}
reserve(
owner: { userId: string; revision: string },
owner: { revision: string; userId: string },
scopeKey: string,
intent: CandleIntent,
runId: string,
@@ -88,29 +89,31 @@ export class CandleRepository {
(!intent.force ||
(previousIntent.force === true &&
(!intent.now || intent.now <= previousNow)))
)
) {
return { work: previous, launch: false };
}
const work = await tx.tbankSyncWork.update({
where: { id: previous.id },
data: {
priority: nextPriority,
intent: {
...previousIntent,
...(intent.force
? {
...(intent.force ?
{
force: true,
...(intent.now && intent.now > previousNow
? { now: intent.now }
: {}),
}
: {}),
...(intent.now && intent.now > previousNow ?
{ now: intent.now } :
{}),
} :
{}),
},
},
});
return { work, launch: false };
}
if (previous)
if (previous) {
await tx.tbankSyncStage.deleteMany({ where: { workId: previous.id } });
}
const data = {
attemptId: randomUUID(),
processRunId: runId,
@@ -142,12 +145,15 @@ export class CandleRepository {
fetchedAt: Date,
) {
// Reject malformed financial input instead of publishing a false empty range.
for (const candle of candles)
for (const candle of candles) {
if (
!Number.isFinite(Date.parse(candle.time)) ||
// eslint-disable-next-line security/detect-unsafe-regex -- Anchored decimal grammar: the literal decimal point separates disjoint digit runs; backtracking is linear.
!/^-?\d+(?:\.\d+)?$/.test(candle.close)
)
) {
throw new Error('Invalid candle');
}
}
const key = `${range.from.toISOString()}/${range.to.toISOString()}`;
const rows = [
...new Map(
@@ -155,7 +161,7 @@ export class CandleRepository {
.filter(
(candle) =>
new Date(candle.time) >= range.from &&
new Date(candle.time) < range.to,
new Date(candle.time) < range.to,
)
.map((candle) => [candle.time, candle]),
).values(),
@@ -222,8 +228,9 @@ export class CandleRepository {
`${range.from.toISOString()}/${range.to.toISOString()}`,
),
)
)
) {
throw new Error('Incomplete candle reconciliation');
}
const identity = { provider, instrumentUid: intent.uid, interval };
for (const range of intent.ranges) {
const row = staged.find(
@@ -254,21 +261,22 @@ export class CandleRepository {
// Preserve coverage outside the requested reconciliation boundaries.
for (const previous of old) {
for (const part of [
...(previous.from < range.from
? [{ from: previous.from, to: range.from }]
: []),
...(previous.to > range.to
? [{ from: range.to, to: previous.to }]
: []),
])
...(previous.from < range.from ?
[{ from: previous.from, to: range.from }] :
[]),
...(previous.to > range.to ?
[{ from: range.to, to: previous.to }] :
[]),
]) {
await tx.tbankCandleCoverage.create({
data: { ...previous, ...part },
});
}
}
await tx.tbankCandle.deleteMany({
where: { ...identity, time: { gte: range.from, lt: range.to } },
});
for (const candle of payload.candles)
for (const candle of payload.candles) {
await tx.tbankCandle.create({
data: {
...identity,
@@ -279,14 +287,16 @@ export class CandleRepository {
fetchedAt,
},
});
}
for (const covered of confirmedCandleCoverage(
range,
payload.candles,
fetchedAt,
))
)) {
await tx.tbankCandleCoverage.create({
data: { ...identity, ...covered },
});
}
}
await tx.tbankSyncStage.deleteMany({
where: { workId: attempt.id, attemptId: attempt.attemptId },
@@ -1,40 +1,42 @@
import { Injectable } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import { isDeepStrictEqual } from 'node:util';
import type { Prisma } from '../../../../../generated/prisma/client';
import { PrismaService } from '../../../../prisma/prisma.service';
import type {
TbankBond,
TbankShare,
TbankSyncAttempt,
} from '../../../../integrations/tbank/tbank.api';
import { PrismaService } from '../../../../prisma/prisma.service';
export type CatalogQuery = {
instrumentType: 'share' | 'bond';
query: string;
exactTicker?: boolean;
instrumentType: 'bond' | 'share';
query: string;
};
export type CatalogSource = {
instrumentType: 'share' | 'bond';
uid: string;
instrumentType: 'bond' | 'share';
kind:
| 'detail'
| 'last-price'
| 'bond-current'
| 'description'
| 'fundamentals'
| 'trading-status'
| 'comparison'
| 'coupons'
| 'yield'
| 'rating'
| 'dividends'
| 'bond-events';
| 'bond-current' |
'bond-events' |
'comparison' |
'coupons' |
'description' |
'detail' |
'dividends' |
'fundamentals' |
'last-price' |
'rating' |
'trading-status' |
'yield';
rangeKey?: string;
ticker?: string;
uid: string;
};
export type CatalogWork = CatalogQuery | CatalogSource;
export type CatalogInstrument = (TbankShare | TbankBond) & { uid: string };
export type CatalogInstrument = (TbankBond | TbankShare) & { uid: string };
const provider = 'tbank';
export function catalogQuery(input: CatalogQuery): CatalogQuery {
return {
@@ -44,13 +46,14 @@ export function catalogQuery(input: CatalogQuery): CatalogQuery {
};
}
export function catalogScope(input: CatalogWork) {
if ('uid' in input)
if ('uid' in input) {
return `catalog-source:${JSON.stringify([
input.kind,
input.kind === 'detail' ? input.instrumentType : null,
input.rangeKey ?? '',
input.uid,
])}`;
}
return `${input.exactTicker ? 'ticker' : 'catalog'}:${JSON.stringify([input.instrumentType, input.query])}`;
}
@@ -60,8 +63,8 @@ export class CatalogDiscoveryRepository {
read(input: CatalogQuery) {
return this.prisma.$transaction(async (tx) => {
const alias = input.exactTicker
? await tx.tbankInstrumentLookup.findUnique({
const alias = input.exactTicker ?
await tx.tbankInstrumentLookup.findUnique({
where: {
provider_identifierType_identifier: {
provider,
@@ -69,17 +72,17 @@ export class CatalogDiscoveryRepository {
identifier: input.query,
},
},
})
: null;
const discovery = input.exactTicker
? alias
? {
}) :
null;
const discovery = input.exactTicker ?
alias ?
{
instrumentUids: alias.matchedUids,
fetchedAt: alias.fetchedAt,
dataRevision: alias.dataRevision,
}
: null
: await tx.tbankCatalogDiscovery.findUnique({
} :
null :
await tx.tbankCatalogDiscovery.findUnique({
where: {
provider_instrumentType_query: {
provider,
@@ -101,12 +104,13 @@ export class CatalogDiscoveryRepository {
});
const byUid = new Map(rows.map((row) => [row.instrumentUid, row]));
// A damaged/missing dependency is unknown, never a confirmed empty search.
if (uids.some((uid) => !byUid.has(uid)))
if (uids.some((uid) => !byUid.has(uid))) {
return {
data: null,
fetchedAt: null,
dataRevision: String(discovery.dataRevision),
};
}
return {
data: uids.map((uid) => byUid.get(uid)?.payload as CatalogInstrument),
fetchedAt: discovery.fetchedAt,
@@ -127,9 +131,9 @@ export class CatalogDiscoveryRepository {
kind: input.kind,
source: sourceProvider(input),
rangeKey:
input.kind === 'detail'
? input.instrumentType
: (input.rangeKey ?? ''),
input.kind === 'detail' ?
input.instrumentType :
(input.rangeKey ?? ''),
})),
},
});
@@ -176,7 +180,7 @@ export class CatalogDiscoveryRepository {
},
});
const instrument = (
value as { instrument?: TbankShare | TbankBond | null }
value as { instrument?: null | TbankBond | TbankShare }
).instrument;
if (
(input.kind === 'detail' || input.kind === 'bond-current') &&
@@ -225,9 +229,9 @@ export class CatalogDiscoveryRepository {
increment: isDeepStrictEqual(
previousAlias?.matchedUids,
matchedUids,
)
? 0
: 1,
) ?
0 :
1,
},
},
});
@@ -239,9 +243,9 @@ export class CatalogDiscoveryRepository {
source: sourceProvider(input),
kind: input.kind,
rangeKey:
input.kind === 'detail'
? input.instrumentType
: (input.rangeKey ?? ''),
input.kind === 'detail' ?
input.instrumentType :
(input.rangeKey ?? ''),
};
const previous = await tx.tbankInstrumentSource.findUnique({
where: { provider_instrumentUid_source_kind_rangeKey: key },
@@ -262,7 +266,7 @@ export class CatalogDiscoveryRepository {
}
reserve(
owner: { userId: string; revision: string },
owner: { revision: string; userId: string },
input: CatalogWork,
runId: string,
active: ReadonlySet<string>,
@@ -281,8 +285,9 @@ export class CatalogDiscoveryRepository {
previous?.state === 'running' &&
previous.processRunId === runId &&
active.has(previous.attemptId)
)
) {
return { work: previous, launch: false };
}
const data = {
attemptId: randomUUID(),
processRunId: runId,
@@ -351,9 +356,9 @@ export class CatalogDiscoveryRepository {
instrumentKind: instrument.instrumentKind,
apiTradeAvailableFlag: instrument.apiTradeAvailableFlag,
currency: instrument.currency,
brand: instrument.brand
? { logoName: instrument.brand.logoName }
: undefined,
brand: instrument.brand ?
{ logoName: instrument.brand.logoName } :
undefined,
}),
) as Prisma.InputJsonObject;
await tx.tbankInstrument.upsert({
@@ -410,9 +415,9 @@ export class CatalogDiscoveryRepository {
increment: isDeepStrictEqual(
previous?.matchedUids,
instrumentUids,
)
? 0
: 1,
) ?
0 :
1,
},
},
});
@@ -436,9 +441,9 @@ export class CatalogDiscoveryRepository {
increment: isDeepStrictEqual(
previous?.instrumentUids,
instrumentUids,
)
? 0
: 1,
) ?
0 :
1,
},
},
});
@@ -472,9 +477,9 @@ export class CatalogDiscoveryRepository {
}
function sourceProvider(input: CatalogSource) {
return input.kind === 'yield' || input.kind === 'bond-events'
? 'moex'
: input.kind === 'rating'
? 'ncr'
: 'tbank';
return input.kind === 'yield' || input.kind === 'bond-events' ?
'moex' :
input.kind === 'rating' ?
'ncr' :
'tbank';
}