learning_ai_invt_trdg/backend/src/services/distributedLockRepository.ts

187 lines
5.6 KiB
TypeScript

import { getContainer } from '@bytelyst/cosmos';
import { config } from '../config/index.js';
import logger from '../utils/logger.js';
const CONTAINER_NAME = 'runtime_locks';
type LockScope = 'entry' | 'reconciliation';
interface RuntimeLockDocument {
id: string;
productId: string;
scope: LockScope;
profileId: string;
symbol?: string;
owner: string;
expiresAt: number;
updatedAt: string;
ttl: number;
}
function isCosmosConfigured(): boolean {
return Boolean(config.COSMOS_ENDPOINT && config.COSMOS_KEY);
}
function buildEntryLockId(profileId: string, symbol?: string): string {
return `entry:${profileId}:${String(symbol || '').trim()}`;
}
function buildReconciliationLockId(profileId: string): string {
return `reconciliation:${profileId}`;
}
async function readLock(id: string): Promise<RuntimeLockDocument | null> {
const container = getContainer(CONTAINER_NAME);
try {
const { resource } = await container.item(id, config.PRODUCT_ID).read<RuntimeLockDocument>();
return resource || null;
} catch (error) {
const code = (error as { code?: number })?.code;
if (code === 404) return null;
throw error;
}
}
async function writeLock(doc: RuntimeLockDocument): Promise<boolean> {
const container = getContainer(CONTAINER_NAME);
await container.items.upsert(doc);
return true;
}
async function deleteLock(id: string): Promise<boolean> {
const container = getContainer(CONTAINER_NAME);
try {
await container.item(id, config.PRODUCT_ID).delete();
return true;
} catch (error) {
const code = (error as { code?: number })?.code;
if (code === 404) return false;
throw error;
}
}
function isActive(doc: RuntimeLockDocument | null | undefined): boolean {
return Boolean(doc && Number(doc.expiresAt) > Date.now());
}
export async function tryAcquireEntryLock(
profileId: string,
symbol: string | undefined,
owner: string,
ttlSeconds: number
): Promise<boolean> {
if (!profileId || !owner) return false;
if (!isCosmosConfigured()) return false;
try {
const id = buildEntryLockId(profileId, symbol);
const existing = await readLock(id);
if (isActive(existing) && existing?.owner !== owner) {
return false;
}
await writeLock({
id,
productId: config.PRODUCT_ID,
scope: 'entry',
profileId,
symbol: String(symbol || '').trim() || undefined,
owner,
expiresAt: Date.now() + Math.max(1, ttlSeconds) * 1000,
updatedAt: new Date().toISOString(),
ttl: Math.max(1, ttlSeconds)
});
return true;
} catch (error) {
logger.warn(`[DistributedLockRepo] Cosmos entry-lock acquire failed: ${error instanceof Error ? error.message : 'unknown error'}`);
return false;
}
}
export async function releaseEntryLock(
profileId: string,
symbol: string | undefined,
owner: string
): Promise<boolean> {
if (!profileId || !owner) return false;
if (!isCosmosConfigured()) return false;
try {
const id = buildEntryLockId(profileId, symbol);
const existing = await readLock(id);
if (!existing) return false;
if (existing.owner !== owner) return false;
return await deleteLock(id);
} catch (error) {
logger.warn(`[DistributedLockRepo] Cosmos entry-lock release failed: ${error instanceof Error ? error.message : 'unknown error'}`);
return false;
}
}
export async function tryAcquireReconciliationLock(
profileId: string,
owner: string,
ttlSeconds: number
): Promise<boolean> {
if (!profileId || !owner) return false;
if (!isCosmosConfigured()) return false;
try {
const id = buildReconciliationLockId(profileId);
const existing = await readLock(id);
if (isActive(existing) && existing?.owner !== owner) {
return false;
}
await writeLock({
id,
productId: config.PRODUCT_ID,
scope: 'reconciliation',
profileId,
owner,
expiresAt: Date.now() + Math.max(1, ttlSeconds) * 1000,
updatedAt: new Date().toISOString(),
ttl: Math.max(1, ttlSeconds)
});
return true;
} catch (error) {
logger.warn(`[DistributedLockRepo] Cosmos reconciliation-lock acquire failed: ${error instanceof Error ? error.message : 'unknown error'}`);
return false;
}
}
export async function releaseReconciliationLock(
profileId: string,
owner: string
): Promise<boolean> {
if (!profileId || !owner) return false;
if (!isCosmosConfigured()) return false;
try {
const id = buildReconciliationLockId(profileId);
const existing = await readLock(id);
if (!existing) return false;
if (existing.owner !== owner) return false;
return await deleteLock(id);
} catch (error) {
logger.warn(`[DistributedLockRepo] Cosmos reconciliation-lock release failed: ${error instanceof Error ? error.message : 'unknown error'}`);
return false;
}
}
export async function isEntryLockActive(
profileId: string,
symbol: string | undefined
): Promise<boolean> {
if (!profileId) return false;
if (!isCosmosConfigured()) return false;
try {
const existing = await readLock(buildEntryLockId(profileId, symbol));
return isActive(existing);
} catch (error) {
logger.warn(`[DistributedLockRepo] Cosmos entry-lock status failed: ${error instanceof Error ? error.message : 'unknown error'}`);
return true;
}
}