forked from boos/BoosAPI
refactor: consolidate single-source routing and protocol helpers
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import { refreshBalance } from './balanceService.js';
|
||||
import {
|
||||
ensureDefaultTokenForAccount,
|
||||
syncTokensFromUpstream,
|
||||
} from './accountTokenService.js';
|
||||
import {
|
||||
rebuildTokenRoutesFromAvailability,
|
||||
refreshModelsForAccount,
|
||||
type ModelRefreshResult,
|
||||
} from './modelService.js';
|
||||
|
||||
type UpstreamTokenLike = {
|
||||
name?: string | null;
|
||||
key?: string | null;
|
||||
enabled?: boolean | null;
|
||||
tokenGroup?: string | null;
|
||||
};
|
||||
|
||||
export type CoverageBatchRebuildResult =
|
||||
| { success: true; result: Awaited<ReturnType<typeof rebuildTokenRoutesFromAvailability>> }
|
||||
| { success: false; error: string };
|
||||
|
||||
export async function convergeAccountMutation(input: {
|
||||
accountId: number;
|
||||
preferredApiToken?: string | null;
|
||||
defaultTokenSource?: string;
|
||||
ensurePreferredTokenBeforeSync?: boolean;
|
||||
upstreamTokens?: UpstreamTokenLike[];
|
||||
refreshBalance?: boolean;
|
||||
refreshModels?: boolean;
|
||||
rebuildRoutes?: boolean;
|
||||
continueOnError?: boolean;
|
||||
}): Promise<{
|
||||
defaultTokenId: number | null;
|
||||
tokenSync: Awaited<ReturnType<typeof syncTokensFromUpstream>> | null;
|
||||
refreshedBalance: boolean;
|
||||
refreshedModels: boolean;
|
||||
rebuiltRoutes: boolean;
|
||||
balanceResult: Awaited<ReturnType<typeof refreshBalance>> | null;
|
||||
modelRefreshResult: ModelRefreshResult | null;
|
||||
rebuildResult: Awaited<ReturnType<typeof rebuildTokenRoutesFromAvailability>> | null;
|
||||
}> {
|
||||
const result = {
|
||||
defaultTokenId: null as number | null,
|
||||
tokenSync: null as Awaited<ReturnType<typeof syncTokensFromUpstream>> | null,
|
||||
refreshedBalance: false,
|
||||
refreshedModels: false,
|
||||
rebuiltRoutes: false,
|
||||
balanceResult: null as Awaited<ReturnType<typeof refreshBalance>> | null,
|
||||
modelRefreshResult: null as ModelRefreshResult | null,
|
||||
rebuildResult: null as Awaited<ReturnType<typeof rebuildTokenRoutesFromAvailability>> | null,
|
||||
};
|
||||
|
||||
const runStep = async <T>(fn: () => Promise<T>): Promise<T | null> => {
|
||||
if (!input.continueOnError) return fn();
|
||||
try {
|
||||
return await fn();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
if (input.ensurePreferredTokenBeforeSync && input.preferredApiToken?.trim()) {
|
||||
const defaultTokenId = await runStep(() => ensureDefaultTokenForAccount(
|
||||
input.accountId,
|
||||
input.preferredApiToken!,
|
||||
{ name: 'default', source: input.defaultTokenSource || 'manual' },
|
||||
));
|
||||
if (defaultTokenId != null) {
|
||||
result.defaultTokenId = defaultTokenId;
|
||||
}
|
||||
}
|
||||
|
||||
if ((input.upstreamTokens?.length || 0) > 0) {
|
||||
const tokenSync = await runStep(() => syncTokensFromUpstream(input.accountId, input.upstreamTokens!));
|
||||
if (tokenSync) {
|
||||
result.tokenSync = tokenSync;
|
||||
result.defaultTokenId = tokenSync.defaultTokenId ?? result.defaultTokenId;
|
||||
}
|
||||
} else if (!input.ensurePreferredTokenBeforeSync && input.preferredApiToken?.trim()) {
|
||||
const defaultTokenId = await runStep(() => ensureDefaultTokenForAccount(
|
||||
input.accountId,
|
||||
input.preferredApiToken!,
|
||||
{ name: 'default', source: input.defaultTokenSource || 'manual' },
|
||||
));
|
||||
if (defaultTokenId != null) {
|
||||
result.defaultTokenId = defaultTokenId;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.refreshBalance) {
|
||||
const balanceResult = await runStep(() => refreshBalance(input.accountId));
|
||||
if (balanceResult) {
|
||||
result.balanceResult = balanceResult;
|
||||
result.refreshedBalance = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.refreshModels) {
|
||||
const modelRefreshResult = await runStep(() => refreshModelsForAccount(input.accountId));
|
||||
if (modelRefreshResult) {
|
||||
result.modelRefreshResult = modelRefreshResult;
|
||||
result.refreshedModels = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.rebuildRoutes) {
|
||||
const rebuildResult = await runStep(() => rebuildTokenRoutesFromAvailability());
|
||||
if (rebuildResult) {
|
||||
result.rebuildResult = rebuildResult;
|
||||
result.rebuiltRoutes = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function refreshAccountCoverageBatch<TFailure>(input: {
|
||||
accountIds: number[];
|
||||
batchSize: number;
|
||||
mapFailure: (accountId: number, errorMessage: string) => TFailure;
|
||||
}): Promise<{
|
||||
refresh: Array<ModelRefreshResult | TFailure>;
|
||||
rebuild: CoverageBatchRebuildResult | null;
|
||||
}> {
|
||||
const uniqueAccountIds = Array.from(new Set(
|
||||
input.accountIds.filter((id) => Number.isFinite(id) && id > 0),
|
||||
));
|
||||
|
||||
if (uniqueAccountIds.length === 0) {
|
||||
return { refresh: [], rebuild: null };
|
||||
}
|
||||
|
||||
const refresh: Array<ModelRefreshResult | TFailure> = [];
|
||||
for (let offset = 0; offset < uniqueAccountIds.length; offset += input.batchSize) {
|
||||
const batch = uniqueAccountIds.slice(offset, offset + input.batchSize);
|
||||
const settled = await Promise.allSettled(
|
||||
batch.map(async (accountId) => refreshModelsForAccount(accountId)),
|
||||
);
|
||||
settled.forEach((entry, index) => {
|
||||
if (entry.status === 'fulfilled') {
|
||||
refresh.push(entry.value);
|
||||
return;
|
||||
}
|
||||
|
||||
const accountId = batch[index] || 0;
|
||||
const errorMessage = entry.reason instanceof Error
|
||||
? entry.reason.message
|
||||
: String(entry.reason || 'coverage refresh failed');
|
||||
refresh.push(input.mapFailure(accountId, errorMessage));
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
refresh,
|
||||
rebuild: {
|
||||
success: true,
|
||||
result: await rebuildTokenRoutesFromAvailability(),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error || 'route rebuild failed');
|
||||
return {
|
||||
refresh,
|
||||
rebuild: {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user