首页 > 解决方案 > (打字稿)您能否验证部分返回类型中的所有可能属性在编译时是否可以选择性地映射到新对象上?

问题描述

大家好——我有一个由数据库读取器组成的服务,中间有一些转换,以及一个将请求发布到第三方 API 的“同步”服务。

类型如下所示:

export interface RowType {
  userId: string,
  userSeq: number,
  info: InfoType
}

export type InfoType = Partial<{
  propertyA: number,
  propertyB: boolean,
  propertyC: string,
  propertyD: boolean,
}>;

阅读器看起来像:

export const usePostgresReader = (
  pool: Pool,
  tenant: string,
  limit = 1000,
): PersistedReader<RowType> => ({
  head: async () =>
    pool
      .query(
        `SELECT COALESCE(MAX(position), 0) as head 
        FROM table_name`,
      )
      .then(({ rows }) => Number(rows[0].head)),
  read: async from =>
    pool
      .query(
        `SELECT position, user_id, user_seq, payload
        FROM table_name
        WHERE tenant = $1 AND position > $2
        ORDER BY position ASC
        LIMIT $3`,
        [tenant, from, limit],
      )
      .then(({ rows }) =>
        rows.map<Persisted<RowType>>(
          ({ position, user_id: userId, user_seq: userSeq, payload }) => ({
            position: Number(position),
            payload: {
              customerId,
              customerSequence: Number(customerSequence),
              info: {
                propertyA: info.propertyA,
                propertyB: info.propertyB,
                propertyC: info.propertyC
                // should be propertyD: info.propertyD, but the dev forgets to add it
              },
            },
          }),
        ),
      ),
});

“同步”服务如下所示:

export const useSyncService = (
  getToken: AuthService,
): SyncService => async rows =>
  fetch(`https://some-other-site.com/api`, {
    method: `POST`,
    headers: {
      Authorization: `Bearer ${await getToken()}`,
      'Content-Type': `application/json`,
    },
    timeout: 60000,
    body: JSON.stringify({
      input: rows.map(({ userId, info }) => ({
        apiThingA: info.propertyA,
        apiThingB: info.propertyB,
        apiThingC: info.propertyC,
        apiThingD: info.propertyD // compiler is happy with this because of partial type
      })),
    }),
  })

处理此代码的工程师不断向同步负载添加新字段,我们现在偶然发现了几次 - 我们将新字段添加到请求的正文中,而不是添加到读取器返回类型。

但是,因为行类型是部分的(并且必须是,因为许多服务使用不同的有效负载填充它正在读取的所有表),编译器不会抱怨从允许的类型映射的值是未定义,即使您忘记将其添加到阅读器,它也将始终未定义。

有没有办法在编译时捕捉到这个?我试图弄清楚如何使用像 Zod ( https://github.com/colinhacks/zod ) 这样的库来验证这些东西,但我目前的解决方案只会在运行时捕获它。

提前干杯,传奇!

标签: typescriptvalidationtypesmapping

解决方案


我想出的编译时解决方案是将部分类型重新声明为联合类型。

IE。代替:

export type InfoType = Partial<{...}>

...我明确声明了所有可能的有效载荷形状,并将其声明InfoType为所有这些形状的联合。

如:

type InfoTypeA = {...}
type InfoTypeB = {...}
type InfoTypeC = {...}
type InfoTypeD = {...}

export type InfoType = InfoTypeA | InfoTypeB | InfoTypeC | InfoTypeD

这样,请求正文 ( {}) 将不再有效,因此如果编译器发现无法将可用值映射到联合的有效子类型的情况,则会引发类型错误。

希望这可以帮助遇到类似问题的其他人!


推荐阅读