TypeScript与ElasticSearch Node Client批量操作类型定义问题
批量Elasticsearch操作的TypeScript类型约束问题
使用环境
- NodeJS
v16.16.0 - "@elastic/elasticsearch": "8.7.0"
需求
要实现一个支持多种批量写入的函数,确保输入的操作只能是以下两种情况之一:
- 全部为Op1类型:包含索引操作行
Op1IndexRow和带a字段的文档Op1BulkDocument - 全部为Op2类型:包含索引操作行
Op2IndexRow和带b字段的文档Op2BulkDocument
禁止同时混合Op1和Op2类型的操作。
问题代码
import { Client } from "@elastic/elasticsearch"; // Operation 1 affect 1 index with "index" action interface Op1IndexRow { index: { _index: string; _id: string; retry_on_conflict: number; } } interface Op1BulkDocument { a: string, } type Op1BulkBody = Array<Op1BulkDocument | Op1IndexRow>; interface Op2IndexRow { index: { _index: string; _id: string; retry_on_conflict: number; } } interface Op2BulkDocument { b: string, } type Op2BulkBody = Array<Op2BulkDocument | Op2IndexRow>; const client = new Client({ node: 'localhost' }) const bulkSave = async (operations: Op1BulkBody | Op2BulkBody) => { await client.bulk({ refresh: true, operations, }); }
遇到的问题
直接定义operations: Op1BulkBody | Op2BulkBody无法通过TypeScript检查;如果改用Array<Op1BulkDocument | Op1IndexRow | Op2BulkDocument | Op2IndexRow>,虽然能通过检查,但会允许混合Op1和Op2类型的操作,违反需求。
解决方案
方案1:泛型约束+类型映射
先提取重复的索引行类型,再通过泛型强制操作属于同一组:
import { Client } from "@elastic/elasticsearch"; // 提取公共索引行类型,避免重复定义 interface BaseIndexRow { index: { _index: string; _id: string; retry_on_conflict: number; } } // 定义操作组的类型映射,明确每组包含的元素类型 type BulkOperationGroups = { op1: BaseIndexRow | { a: string }; op2: BaseIndexRow | { b: string }; }; const client = new Client({ node: 'localhost' }) // 用泛型约束操作必须属于同一组 const bulkSave = async <T extends keyof BulkOperationGroups>( operations: BulkOperationGroups[T][] ) => { await client.bulk({ refresh: true, operations, }); }; // 合法调用示例 bulkSave([ { index: { _index: "test1", _id: "1", retry_on_conflict: 3 } }, { a: "value1" } ]); bulkSave([ { index: { _index: "test2", _id: "2", retry_on_conflict: 3 } }, { b: "value2" } ]); // 非法调用(混合op1和op2类型)会触发TS报错 bulkSave([ { index: { _index: "test", _id: "1", retry_on_conflict: 3 } }, { a: "value1" }, { b: "value2" } // 此处TS会提示类型不匹配 ]);
方案2:明确的联合数组类型
如果不需要泛型的灵活性,也可以直接定义严格的联合数组类型,强制TS识别数组的同质性:
import { Client } from "@elastic/elasticsearch"; interface BaseIndexRow { index: { _index: string; _id: string; retry_on_conflict: number; } } // 定义单组操作的元素类型 type Op1Item = BaseIndexRow | { a: string }; type Op2Item = BaseIndexRow | { b: string }; // 明确数组只能是纯Op1或纯Op2集合 type StrictBulkBody = Op1Item[] | Op2Item[]; const client = new Client({ node: 'localhost' }) const bulkSave = async (operations: StrictBulkBody) => { await client.bulk({ refresh: true, operations, }); };
问题根源
TypeScript对数组类型的协变处理会将Array<A> | Array<B>自动降级为Array<A | B>,导致无法区分纯数组和混合数组。通过泛型约束或明确的联合数组类型,可以绕过这一行为,强制TS检查数组元素的一致性。
内容的提问来源于stack exchange,提问作者BastienSander
相关产品推荐
相关产品推荐

