You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

82 lines
2.9 KiB

/**
* 识别各驱动常见的「唯一 / 重复键」错误,供任意 service 复用。
* 具体业务(如用户名冲突 vs 主键重试)用 {@link uniqueConstraintTouchesField} 再区分即可。
*/
/** 遍历包装错误(如 Drizzle)与底层驱动的整条 cause 链 */
export function* eachErrorInChain(err: unknown): Generator<Error> {
let current: unknown = err;
const seen = new Set<unknown>();
while (current instanceof Error && !seen.has(current)) {
seen.add(current);
yield current;
current = "cause" in current ? (current as { cause?: unknown }).cause : undefined;
}
}
function escapeRegExp(s: string) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
/**
* 报错文案或 constraint 名是否涉及该字段。
* message 用词边界,避免 `guid` 误匹配 `id`;constraint 多为 `users_username_key` 式拼接,用子串匹配。
*/
export function uniqueConstraintTouchesField(err: unknown, field: string): boolean {
const fl = field.toLowerCase();
const re = new RegExp(`\\b${escapeRegExp(field)}\\b`, "i");
for (const e of eachErrorInChain(err)) {
if (re.test(e.message)) {
return true;
}
if ("constraint" in e) {
const c = String((e as { constraint?: unknown }).constraint ?? "").toLowerCase();
if (c.includes(fl)) {
return true;
}
}
}
return false;
}
export function isUniqueConstraintViolation(err: unknown): boolean {
for (const e of eachErrorInChain(err)) {
const code = "code" in e ? String((e as { code?: unknown }).code ?? "") : "";
const errno = "errno" in e ? (e as { errno?: unknown }).errno : undefined;
if (code === "23505") {
return true;
}
if (code === "ER_DUP_ENTRY") {
return true;
}
if (typeof errno === "number" && errno === 1062) {
return true;
}
if (code.startsWith("SQLITE_CONSTRAINT")) {
return true;
}
const m = e.message.toLowerCase();
if (m.includes("unique constraint failed")) {
return true;
}
if (m.includes("duplicate entry")) {
return true;
}
if (m.includes("duplicate key") && m.includes("unique")) {
return true;
}
}
return false;
}
/** 唯一冲突且与指定字段相关(例如用户名占用) */
export function isUniqueConflictOnField(err: unknown, field: string): boolean {
return isUniqueConstraintViolation(err) && uniqueConstraintTouchesField(err, field);
}
/** 唯一冲突且与指定字段无关(例如除 username 外仅有主键时的 id 撞车重试) */
export function isUniqueConflictExceptField(err: unknown, field: string): boolean {
return isUniqueConstraintViolation(err) && !uniqueConstraintTouchesField(err, field);
}