我目前正在对 CQL 进行包装,并在重命名属性键(即别名)方面遇到麻烦。这是包含所有所需代码和 3 个案例的 Playground:Playground
如果可能,仅更改部分代码的类型。 “1”是数据库连接客户端,因此代码中无论如何都会出现“asknown as ***”。 这是代码:
export type SelectFields<T> = {
field: Select<T>;
alias?: string;
};
export type SelectResult<
T,
S extends SelectFields<T>[],
> = S[number]['alias'] extends string
? { [K in S[number]['field'] as S[number]['alias']]: T[K] }
: { [K in S[number]['field']]: T[K] };
@Injectable()
export class BaseRepository<T> {
public readonly mapper: ModelMapper<T>;
private readonly queryFactory: QueryFactory<T>;
public async findByField<const S extends SelectFields<T>[]>(params?: {
select?: SelectFields<T>[];
where?: WhereFields<T>[];
limit?: number;
}) {
const { select, where } = params || {};
const query = this.queryFactory.createQuery(select, where);
const result = await this.client.execute(query);
return result.rows as unknown as SelectResult<T, S>[];
}
constructor(
private readonly client: Client,
private readonly entity: new (...args) => any,
) {
const tableName = getTableName(entity);
const primaryKeys = getPrimaryKeyFields(entity);
if (!tableName) throw new Error('No entity name provided!');
const newMapper = new Mapper(client, {
models: { [tableName + 'Model']: { tables: [tableName] } },
});
this.mapper = newMapper.forModel(tableName);
this.queryFactory = new QueryFactory<T>(tableName, primaryKeys);
}
}
//Usage:
@Injectable()
export class MyService {
constructor(
@Inject('SCYLLA_CLIENT') private readonly scylla: Client,
@Inject('test_MAPPER') private readonly repository: BaseRepository<Test>,
) {}
async fetchData() {
const result = await this.repository.findByField({
select: [
{ field: 'numero', alias: 'number_alias' },
{ field: 'test_id', alias: 'id_alias' },
],
});
return result;
}
}
但被结果困住了。
代码最重要的问题是编译器不希望您关心 field
的
literal types和
alias
参数的 select
子属性。它往往会推断出 string
,这会阻止一切工作。您可以使用 a const
类型参数: 来更改它
declare const findByField:
<const S extends SelectFields<Test>[]>(params?: {
select?: S;
where?: any[];
limit?: number;
}) => Promise<SelectResult<Test, S>>;
现在 TypeScript 将尝试将
S
推断为它所能实现的最窄类型,包括为 field
和 alias
传入的确切值,甚至数组元素的顺序(尽管我认为你不关心这一点) ).
在此之后,您只需要实现
SelectResult
,以便它实际上将键与值正确匹配。您已经在映射类型中使用了键重新映射,但仍在迭代键类型,事实证明,只要as
之后的内容是键类型,您就可以迭代任意联合类型。 (所以在
{[A in B as C]: D}
中,你不需要 B
成为按键,只要 C
成为按键即可。)所以你可以这样做:
type SelectResult<
T,
S extends SelectFields<T>[],
> = { [U in S[number]
as U['alias'] extends string ? U['alias'] : U['field']
]: T[U['field']] }
请注意,由于这现在只是一个映射类型,因此 IntelliSense 在显示结果时可能会保留
SelectResult
名称。为了避免这种情况,您可以对空对象类型执行无操作intersection,以提示类型显示进一步简化:
type SelectResult<
T,
S extends SelectFields<T>[],
> = { [U in S[number]
as U['alias'] extends string ? U['alias'] : U['field']
]: T[U['field']] } & {};
好的,我们来测试一下:
const result1 = findByField({
select: [
{ field: 'numero', alias: 'number_alias' },
{ field: 'test_id', alias: 'id_alias' }],
})
// const result1: Promise<{ number_alias: number; id_alias: string; }>
const result2 = findByField({
select: [
{ field: 'numero' },
{ field: 'test_id', alias: 'id_alias' }
]
})
// const result2: Promise<{ numero: number; id_alias: string; }>
const result3 = findByField({
select: [
{ field: 'numero' },
{ field: 'test_id' }],
})
// const result3: Promise<{ numero: number; test_id: string; }>
看起来不错!