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', '_id')({
  taskIds: ['123', '456'],
  isArchived: true,
  updated: '2018-08-21T05:43:10.000Z'
})
```
会得到
```
[
  {_id: '123', isArchived: true, updated: '2018-08-21T05:43:10.000Z'},
  {_id: '456', isArchived: true, updated: '2018-08-21T05:43:10.000Z'}
]
```
而在常见的 Observable response$ 上,则可以
```
response$.map(normBulkUpdate('taskIds', '_id'))
```

更多边界条件的行为定义,请见相应测试。
  • Loading branch information
chuan6 committed Sep 13, 2018
1 parent b919cde commit 1bfe236
Show file tree
Hide file tree
Showing 7 changed files with 152 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
66 changes: 66 additions & 0 deletions src/utils/httpclient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Omit } from './internalTypes'
import { SDKLogger } from './Logger'

/**
* 从类型 T(如 `{ taskIds: TaskId[] }`)上获得给定字段 K
* (如 `taskIds`)对应的数组的元素类型(如 `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,
U extends string
> = Array<Record<U, ArrayPropertyElement<T, K>> & Omit<T, K>>

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

if (currentValue == null || currentValue === id /* not likely */) {
return { ...rest, [entityIdField]: id }
}

const incoming = `${entityIdField}-${id}`
const current = `${entityIdField}-${currentValue}`
SDKLogger.warn('normBulkUpdate:' +
` specified key-value pair(${incoming})` +
` conflicts with an existing one(${current}).)`
)
return rest
})
}
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'
79 changes: 79 additions & 0 deletions test/utils/httpclient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { expect } from 'chai'
import { it, describe } from 'tman'
import { TaskId } from 'teambition-types'
import { normBulkUpdate } from '../../src/utils'

describe('httpclient utils spec', () => {

type ResponsePayload = {
taskIds: TaskId[]
isArchived: boolean
updated: string
}

it('normBoldUpdate should produce [] for undefined response', () => {
const norm = normBulkUpdate<any>('taskIds', '_id')
expect(norm(undefined)).to.deep.equal([])
expect(norm(null)).to.deep.equal([])
expect(norm('success')).to.deep.equal([])
})

it('normBulkUpdate should produce [] when responseIdsField is undefined', () => {
const norm = normBulkUpdate<any>('_taskIds', '_id')
expect(norm({
taskIds: ['123', '456'],
isArchived: true,
updated: '2018-08-21T05:43:10.000Z'
})).to.deep.equal([])
})

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

it('normBulkUpdate should work when responseIdsField and entityIdField are the same', () => {
const norm = normBulkUpdate<any>('_id', '_id')
expect(norm({
_id: ['123' as TaskId, '456' as TaskId],
isArchived: true,
updated: '2018-08-21T05:43:10.000Z'
})).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 should work when responseIdsField and entityIdField are different', () => {
const norm = normBulkUpdate<ResponsePayload>('taskIds', '_id')
expect(norm({
taskIds: ['123' as TaskId, '456' as TaskId],
isArchived: true,
updated: '2018-08-21T05:43:10.000Z'
})).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 should not overwrite when property conflict happends', () => {
const norm = normBulkUpdate<any>(
'taskIds',
'cid' // 语义为 backbone 的 client id
)
expect(norm({
taskIds: ['123', '456'],
cid: '789', // 语义为 child id
isArchived: true,
updated: '2018-08-21T05:43:10.000Z'
})).to.deep.equal([
{ cid: '789', isArchived: true, updated: '2018-08-21T05:43:10.000Z' },
{ cid: '789', isArchived: true, updated: '2018-08-21T05:43:10.000Z' }
])
})

})
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 1bfe236

Please sign in to comment.