chore: strengthen backend eslint profile
This commit is contained in:
@@ -1,24 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { isDeepStrictEqual } from 'node:util';
|
||||
|
||||
import type {
|
||||
Prisma,
|
||||
TbankInstrumentLookup as MetadataLookupRow,
|
||||
TbankInstrument as MetadataRow,
|
||||
Prisma,
|
||||
} from '../../../generated/prisma/client';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import type { TbankSyncAttempt } from './tbank-access.service';
|
||||
import type { TbankInstrument } from './tbank.client';
|
||||
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import { tbankSourceTtlMs } from './tbank-source.policy';
|
||||
|
||||
export type MetadataIdentity = {
|
||||
instrumentUid?: string | null;
|
||||
figi?: string | null;
|
||||
figi?: null | string;
|
||||
instrumentUid?: null | string;
|
||||
};
|
||||
export type MetadataLookup = {
|
||||
type: 'uid' | 'figi';
|
||||
data: null | TbankInstrument;
|
||||
id: string;
|
||||
data: TbankInstrument | null;
|
||||
type: 'figi' | 'uid';
|
||||
};
|
||||
const provider = 'tbank';
|
||||
// Keep IN lists below the SQLite/Prisma parameter limit, including fixed predicates.
|
||||
@@ -36,15 +38,16 @@ export class InstrumentMetadataRepository {
|
||||
|
||||
async readMany(identities: MetadataIdentity[]) {
|
||||
const identifiers = identities.flatMap((identity) => [
|
||||
...(identity.instrumentUid?.trim()
|
||||
? [{ identifierType: 'uid', identifier: identity.instrumentUid.trim() }]
|
||||
: []),
|
||||
...(identity.figi?.trim()
|
||||
? [{ identifierType: 'figi', identifier: identity.figi.trim() }]
|
||||
: []),
|
||||
...(identity.instrumentUid?.trim() ?
|
||||
[{ identifierType: 'uid', identifier: identity.instrumentUid.trim() }] :
|
||||
[]),
|
||||
...(identity.figi?.trim() ?
|
||||
[{ identifierType: 'figi', identifier: identity.figi.trim() }] :
|
||||
[]),
|
||||
]);
|
||||
if (!identifiers.length)
|
||||
if (!identifiers.length) {
|
||||
return new Map<string, ReturnType<typeof selectMetadata>>();
|
||||
}
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
const lookups: MetadataLookupRow[] = [];
|
||||
for (const identifierType of ['uid', 'figi']) {
|
||||
@@ -59,7 +62,7 @@ export class InstrumentMetadataRepository {
|
||||
let offset = 0;
|
||||
offset < values.length;
|
||||
offset += metadataReadBatchSize
|
||||
)
|
||||
) {
|
||||
lookups.push(
|
||||
...(await tx.tbankInstrumentLookup.findMany({
|
||||
where: {
|
||||
@@ -71,6 +74,7 @@ export class InstrumentMetadataRepository {
|
||||
},
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
const uids = [
|
||||
...new Set(lookups.flatMap((row) => row.matchedUids as string[])),
|
||||
@@ -80,7 +84,7 @@ export class InstrumentMetadataRepository {
|
||||
let offset = 0;
|
||||
offset < uids.length;
|
||||
offset += metadataReadBatchSize
|
||||
)
|
||||
) {
|
||||
instruments.push(
|
||||
...(await tx.tbankInstrument.findMany({
|
||||
where: {
|
||||
@@ -89,6 +93,7 @@ export class InstrumentMetadataRepository {
|
||||
},
|
||||
})),
|
||||
);
|
||||
}
|
||||
const byIdentifier = new Map(
|
||||
lookups.map((row) => [`${row.identifierType}:${row.identifier}`, row]),
|
||||
);
|
||||
@@ -103,7 +108,7 @@ export class InstrumentMetadataRepository {
|
||||
}
|
||||
|
||||
async reserve(
|
||||
attemptOwner: { userId: string; revision: string },
|
||||
attemptOwner: { revision: string; userId: string },
|
||||
identity: MetadataIdentity,
|
||||
runId: string,
|
||||
active: ReadonlySet<string>,
|
||||
@@ -125,8 +130,9 @@ export class InstrumentMetadataRepository {
|
||||
previous?.state === 'running' &&
|
||||
previous.processRunId === runId &&
|
||||
active.has(previous.attemptId)
|
||||
)
|
||||
) {
|
||||
return { work: previous, launch: false };
|
||||
}
|
||||
const data = {
|
||||
attemptId: randomUUID(),
|
||||
processRunId: runId,
|
||||
@@ -183,12 +189,13 @@ export class InstrumentMetadataRepository {
|
||||
['uid', uid],
|
||||
['figi', figi],
|
||||
] as const) {
|
||||
if (id && uid && !aliases.has(`${type}:${id}`))
|
||||
if (id && uid && !aliases.has(`${type}:${id}`)) {
|
||||
aliases.set(`${type}:${id}`, {
|
||||
type,
|
||||
id,
|
||||
data: { ...result.data, uid },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const result of aliases.values()) {
|
||||
@@ -196,8 +203,9 @@ export class InstrumentMetadataRepository {
|
||||
const uid =
|
||||
instrument?.uid?.trim() || (result.type === 'uid' ? result.id : null);
|
||||
// FIGI is an alias, never a fabricated provider UID.
|
||||
if (instrument && !uid)
|
||||
if (instrument && !uid) {
|
||||
throw new Error('Metadata response has no instrument UID');
|
||||
}
|
||||
if (instrument && uid) {
|
||||
const key = { provider, uid };
|
||||
const previous = await tx.tbankInstrument.findUnique({
|
||||
@@ -220,9 +228,9 @@ export class InstrumentMetadataRepository {
|
||||
update: {
|
||||
...data,
|
||||
dataRevision: {
|
||||
increment: isDeepStrictEqual(previous?.metadata, payload)
|
||||
? 0
|
||||
: 1,
|
||||
increment: isDeepStrictEqual(previous?.metadata, payload) ?
|
||||
0 :
|
||||
1,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -243,9 +251,9 @@ export class InstrumentMetadataRepository {
|
||||
matchedUids,
|
||||
fetchedAt: now,
|
||||
dataRevision: {
|
||||
increment: isDeepStrictEqual(previous?.matchedUids, matchedUids)
|
||||
? 0
|
||||
: 1,
|
||||
increment: isDeepStrictEqual(previous?.matchedUids, matchedUids) ?
|
||||
0 :
|
||||
1,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -266,7 +274,7 @@ export class InstrumentMetadataRepository {
|
||||
errorCode: success ? null : 'TBANK_UNAVAILABLE',
|
||||
},
|
||||
});
|
||||
if (success)
|
||||
if (success) {
|
||||
await this.prisma.tbankSyncWork.updateMany({
|
||||
where: {
|
||||
...attempt,
|
||||
@@ -275,6 +283,7 @@ export class InstrumentMetadataRepository {
|
||||
},
|
||||
data: { state: 'failed', errorCode: 'INVALID_TBANK_TOKEN' },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +292,7 @@ function selectMetadata(
|
||||
lookups: ReadonlyMap<string, MetadataLookupRow>,
|
||||
instruments: ReadonlyMap<string, MetadataRow>,
|
||||
) {
|
||||
const resolve = (type: string, id: string | null | undefined) => {
|
||||
const resolve = (type: string, id: null | string | undefined) => {
|
||||
const lookup = lookups.get(`${type}:${id?.trim()}`);
|
||||
const uid = (lookup?.matchedUids as string[] | undefined)?.[0];
|
||||
const instrument = uid ? instruments.get(uid) : undefined;
|
||||
@@ -307,6 +316,7 @@ function selectMetadata(
|
||||
complete,
|
||||
fresh:
|
||||
complete &&
|
||||
// eslint-disable-next-line sonarjs/different-types-comparison -- The conditional aggregation is nullable at runtime; keep the freshness guard.
|
||||
fetchedAt !== null &&
|
||||
Date.now() - fetchedAt.valueOf() < tbankSourceTtlMs.metadata,
|
||||
fetchedAt,
|
||||
|
||||
Reference in New Issue
Block a user