Files
atlas-seller-kit/apps/api/src/common/revision.ts
T
2026-09-09 15:11:58 +08:00

51 lines
1.8 KiB
TypeScript

import type { DatabaseTransaction } from '@atlas/database';
import { ApiError } from './api-error.js';
/**
* Shared optimistic-lock conflict response. Versioned writes still keep their
* domain-specific SET clauses, but they must use a conditional `revision =
* expected` update and this helper when no row was affected.
*/
const resources = {
collection_item: {
table: 'atlas.collection_items',
label: '采集记录',
active: ' AND deleted_at IS NULL',
},
product: { table: 'atlas.products', label: '商品', active: '' },
product_platform_profile: {
table: 'atlas.product_platform_profiles',
label: '商品发布配置',
active: '',
},
} as const;
export async function throwRevisionConflict(
transaction: DatabaseTransaction,
resource: keyof typeof resources,
id: string,
expected: number,
): Promise<never> {
const target = resources[resource];
const current = await transaction.query<{ revision: number }>(
`SELECT revision FROM ${target.table} WHERE id = $1${target.active}`,
[id],
);
if (!current.rows[0])
throw new ApiError(404, 'RESOURCE_NOT_FOUND', `${target.label}不存在`);
if (current.rows[0].revision !== expected) {
throw new ApiError(409, 'VERSION_CONFLICT', `${target.label}已被更新`, [
{
field: 'If-Match',
code: 'VERSION_CONFLICT',
message: `当前版本为 ${current.rows[0].revision}`,
current_revision: current.rows[0].revision,
},
]);
}
// A conditional write with a matching version affecting no rows is a
// resource-state error (for example, a concurrent soft delete), not a
// successful retry.
throw new ApiError(409, 'VERSION_CONFLICT', `${target.label}状态已变化`);
}