Skip to content

Commit

Permalink
feat(utils): 添加批量更新相关的一个数据转换工具函数 normBulkUpdate
Browse files Browse the repository at this point in the history
在批量接口使用 PUT 请求时,后端 response 上往往不会把所有更新的对象一
一罗列,而是以 `{ xxIds: Id[], ...updatedFields }` 的压缩形式呈现。这
样的数据是没办法直接更新到缓存层(RDB)的。

这里添加的 normBulkUpdate 函数可以用来将后端返回的这种压缩形式转换为缓
存层能接收的形式。如:
```
normBulkUpdate({
  taskIds: ['123', '456'],
  isArchived: true,
  updated: '2018-08-21T05:43:10.000Z'
}, 'taskIds', '_id')
```
会得到
```
[
  {_id: '123', isArchived: true, updated: '2018-08-21T05:43:10.000Z'},
  {_id: '456', isArchived: true, updated: '2018-08-21T05:43:10.000Z'}
]
```
更多边界条件的行为定义,请见相应测试。
  • Loading branch information
chuan6 committed Aug 22, 2018
1 parent 4c23de3 commit ea6652f
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 2 deletions.
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/// <reference path="./teambition.ts" />
import 'tslib'

import { forEach, clone, uuid, concat, dropEle, hasMorePages, pagination, eventToRE } from './utils'
import { forEach, clone, uuid, concat, dropEle, hasMorePages, pagination, eventToRE, normBulkUpdate } from './utils'

export { hasMorePages, pagination }
export { hasMorePages, pagination, normBulkUpdate }
export const Utils = { forEach, clone, uuid, concat, dropEle }
export { eventParser } from './sockets/EventParser'

Expand Down
56 changes: 56 additions & 0 deletions src/utils/httpclient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Omit } from './internalTypes'

/**
* 从类型 T(如 `{ _taskId: TaskId[] }`)上获得给定字段 K
* (如 `_taskId`)对应的数组的元素类型(如 `TaskId`)。如果
* `T[K]` 不是数组类型,则放弃类型推断,返回 any。
*/
export type ArrayPropertyElement<T, K extends keyof T> = T[K] extends (infer U)[]
? U
: any

/**
* 生成的结果类型,替换了类型 T(如 `{ taskIds: TaskId[], isArchived: boolean }`)
* 上的字段 K(如 `taskIds`)为字段 S(如 `_id`),而字段 S
* 对应的值类型则是字段 K 对应的数组值的元素类型(如 `TaskId`)。
* 如果 `T[K]` 不是数组类型,则字段 S(如 `_id`)的类型将是 any。
*/
export type NormBulkUpdateResult<T, K extends keyof T, S extends string> = Array<
Record<S, ArrayPropertyElement<T, K>> & Omit<T, K>
>

/**
* 将批量 PUT 的返回结果转变为可以直接被缓存层消费的数据。
* 用法如:有 response$ 的元素形状为
* `{ taskIds: TaskId[], isArchived: boolean, updated: string }`
* 则调用 `normBulkUpdate(response$, 'taskIds', '_id')` 将推出元素形状为
* `{ _id: TaskId, isArchived: boolean, updated: string }[]`
* 的数据。
*/
export function normBulkUpdate<
T extends {},
K extends keyof T,
U extends string
>(
response: T,
responseIdsField: K,
entityIdField: U
): NormBulkUpdateResult<T, K, U> {
const { [responseIdsField]: ids, ...rest } = response as any

return ids
? (ids as Array<ArrayPropertyElement<T, K>>).map((id) => {
const existingValue = rest[entityIdField]
if (existingValue != null && existingValue !== id) {
throw new Error(
`normBulkUpdate: specified key-value pair(${entityIdField}-${id})` +
` is conflicting to an existing one(${entityIdField}-${existingValue}).)`
)
}
return {
...rest,
[entityIdField]: id
}
})
: []
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './helper'
export * from './internalTypes'
export { createProxy } from './proxy'
export { eventToRegexp as eventToRE } from './eventToRegexp'
export * from './httpclient'
2 changes: 2 additions & 0 deletions src/utils/internalTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export type Dict<T> = {
[key: string]: T
}

export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

export type TableInfo = {
tabName: string,
pkName: string
Expand Down
1 change: 1 addition & 0 deletions test/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ import './mock'
import './apis'
import './sockets'
import './net'
import './utils/index'
74 changes: 74 additions & 0 deletions test/utils/httpclient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { expect } from 'chai'
import { it, describe } from 'tman'
import { TaskId } from 'teambition-types'
import { normBulkUpdate } from '../../src/utils'

describe('httpclient utils spec', () => {
it(`${normBulkUpdate.name} should produce [] when responseIdsField is undefined`, () => {
expect(normBulkUpdate(
{
taskIds: ['123', '456'],
isArchived: true,
updated: '2018-08-21T05:43:10.000Z'
} as any,
'_taskIds',
'_id'
)).to.deep.equal([])
})

it(`${normBulkUpdate.name} should produce [] when responseIdsField is empty`, () => {
expect(normBulkUpdate(
{
taskIds: [],
isArchived: true,
updated: '2018-08-21T05:43:10.000Z'
},
'taskIds',
'_id'
)).to.deep.equal([])
})

it(`${normBulkUpdate.name} should work when responseIdsField and entityIdField are the same`, () => {
expect(normBulkUpdate(
{
_id: ['123' as TaskId, '456' as TaskId],
isArchived: true,
updated: '2018-08-21T05:43:10.000Z'
},
'_id',
'_id'
)).to.deep.equal([
{ _id: '123', isArchived: true, updated: '2018-08-21T05:43:10.000Z' },
{ _id: '456', isArchived: true, updated: '2018-08-21T05:43:10.000Z' }
])
})

it(`${normBulkUpdate.name} should work when responseIdsField and entityIdField are different`, () => {
expect(normBulkUpdate(
{
taskIds: ['123' as TaskId, '456' as TaskId],
isArchived: true,
updated: '2018-08-21T05:43:10.000Z'
},
'taskIds',
'_id'
)).to.deep.equal([
{ _id: '123', isArchived: true, updated: '2018-08-21T05:43:10.000Z' },
{ _id: '456', isArchived: true, updated: '2018-08-21T05:43:10.000Z' }
])
})

it(`${normBulkUpdate.name} should throw when property conflict happends`, () => {
expect(() => normBulkUpdate(
{
taskIds: ['123', '456'],
cid: '789', // 语义为 child id
isArchived: true,
updated: '2018-08-21T05:43:10.000Z'
},
'taskIds',
'cid' // 语义为 backbone 的 client id
)).to.throw()
})

})
1 change: 1 addition & 0 deletions test/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './httpclient'

0 comments on commit ea6652f

Please sign in to comment.