-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(utils): 添加批量更新相关的一个数据转换工具函数 normBulkUpdate
在批量接口使用 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
Showing
7 changed files
with
152 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 extends { [key: string]: any }, | ||
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 | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,4 @@ import './mock' | |
import './apis' | ||
import './sockets' | ||
import './net' | ||
import './utils/index' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' } | ||
]) | ||
}) | ||
|
||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import './httpclient' |