67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
export const runtime = "nodejs";
|
|
|
|
export function toCamelCaseKey(key: string): string {
|
|
return key.replace(/_([a-zA-Z])/g, (_, c: string) => c.toUpperCase());
|
|
}
|
|
|
|
export function toSnakeCaseKey(key: string): string {
|
|
return key.replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`);
|
|
}
|
|
|
|
/**
|
|
* 创建一个代理对象,使得读取属性时同时兼容 snake_case 与 camelCase 键名。
|
|
* 访问顺序:原名 -> camelCase -> snake_case
|
|
*/
|
|
export function createCamelCompatibleProxy<T extends object>(root: T): T {
|
|
const seen = new WeakMap<object, any>();
|
|
|
|
const wrap = (value: any): any => {
|
|
if (value === null || typeof value !== "object") return value;
|
|
if (seen.has(value)) return seen.get(value);
|
|
const proxied = new Proxy(value, handler);
|
|
seen.set(value, proxied);
|
|
return proxied;
|
|
};
|
|
|
|
const handler: ProxyHandler<any> = {
|
|
get(target, prop, receiver) {
|
|
// 非字符串属性(如 Symbol、数字索引)直接透传
|
|
if (typeof prop !== "string") {
|
|
return wrap(Reflect.get(target, prop, receiver));
|
|
}
|
|
|
|
const primary = prop;
|
|
|
|
if (primary in target)
|
|
return wrap(Reflect.get(target, primary, receiver));
|
|
|
|
const camel = toCamelCaseKey(primary);
|
|
if (camel in target) return wrap(Reflect.get(target, camel, receiver));
|
|
|
|
const snake = toSnakeCaseKey(primary);
|
|
if (snake in target) return wrap(Reflect.get(target, snake, receiver));
|
|
|
|
return wrap(Reflect.get(target, prop, receiver));
|
|
},
|
|
has(target, prop) {
|
|
if (typeof prop !== "string") return prop in target;
|
|
const primary = prop === "auther" ? "autherInfo" : prop;
|
|
return (
|
|
primary in target ||
|
|
toCamelCaseKey(primary) in target ||
|
|
toSnakeCaseKey(primary) in target
|
|
);
|
|
},
|
|
};
|
|
|
|
return wrap(root);
|
|
}
|
|
|
|
/** 选择首个可用 URL */
|
|
export function pickFirstUrl(list?: string[]) {
|
|
return Array.isArray(list) && list.length ? list[0] : undefined;
|
|
}
|
|
|
|
// 别名,兼容旧命名
|
|
export const firstUrl = pickFirstUrl;
|