All files / utils DataPacker.ts

97.62% Statements 41/42
95.83% Branches 23/24
85.71% Functions 6/7
97.3% Lines 36/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125                                                        3x               8x                 3x 3x 3x     3x 3x 3x 5x 5x 10x   5x   3x                                       8x 8x 8x 8x 1x   8x 23x 23x 46x   23x   8x                     17x     14x 1x 1x 1x 1x   14x 14x 5x   9x 8x       4x      
import { isPlainObject, mapValues, range, shuffle } from 'lodash-uni'
import { base64UrlDecode, base64UrlEncode } from './base64'
import { isType } from './isType'
import { rot13 } from './rot13'
 
export type RawObjectData = Record<any, any>
export type RawListData<TRawObjectData extends RawObjectData = RawObjectData> =
  TRawObjectData[]
export type RawData<TRawObjectData extends RawObjectData = RawObjectData> =
  | TRawObjectData
  | RawListData<TRawObjectData>
export type ElementOfRawData<TRawData extends RawData> =
  TRawData extends RawData<infer X> ? X : never
export type KeyOfRawData<TRawData extends RawData> =
  keyof ElementOfRawData<TRawData>
export type ValueOfRawData<TRawData extends RawData> =
  ElementOfRawData<TRawData>[KeyOfRawData<TRawData>]
export type PackedData<TRawData extends RawData> = {
  readonly _k: Array<KeyOfRawData<TRawData>>
  readonly _v: Array<Array<ValueOfRawData<TRawData>>>
  readonly _s: string
}
 
/**
 * 数据打包器。
 */
export class DataPacker {
  private static encodeIndexes(indexes: number[]): string {
    return rot13(
      base64UrlEncode(
        `${Math.random().toString(36).slice(2)}.${indexes.join('.')}`,
      ),
    )
  }
 
  private static decodeIndexes(value: string): number[] {
    return base64UrlDecode(rot13(value)).split('.').slice(1).map(Number)
  }
 
  /**
   * 打包数据。
   */
  static pack<TRawObjectData extends RawData>(
    rawData: TRawObjectData,
  ): PackedData<TRawObjectData> {
    const notArray = !Array.isArray(rawData)
    const rawList: RawListData = notArray ? [rawData] : (rawData as any)
    const keys: Array<KeyOfRawData<TRawObjectData>> = rawList.length
      ? shuffle(Object.keys(rawList[0]) as any)
      : []
    const indexes: number[] = shuffle(range(0, keys.length))
    const values = []
    for (const rawItem of rawList) {
      const item = []
      for (let i = 0, len = indexes.length; i < len; i++) {
        item[indexes[i]] = rawItem[keys[i]]
      }
      values.push(item)
    }
    return {
      _k: keys,
      _v: values,
      _s: DataPacker.encodeIndexes(notArray ? [-1, ...indexes] : indexes),
    }
  }
 
  /**
   * 返回结果同 `pack()`,不过类型是原数据的类型。
   */
  static packAsRawType<T>(rawData: T): T {
    return DataPacker.pack(rawData as any) as any
  }
 
  /**
   * 解包数据。
   */
  static unpack<TRawObjectData extends RawData>(
    packedData: PackedData<TRawObjectData>,
  ): RawData<TRawObjectData> {
    const rawList: Array<RawListData<TRawObjectData>> = []
    const indexes = DataPacker.decodeIndexes(packedData._s)
    const notArray = indexes[0] === -1
    if (notArray) {
      indexes.shift()
    }
    for (const values of packedData._v) {
      const item: ElementOfRawData<TRawObjectData> = {} as any
      for (let i = 0, len = indexes.length; i < len; i++) {
        item[packedData._k[i]] = values[indexes[i]]
      }
      rawList.push(item)
    }
    return (notArray ? rawList[0] : rawList) as any
  }
 
  /**
   * 如果是打包后的数据,则解包后返回,否则直接返回。如果是对象,则递归尝试解包。
   *
   * @param value 数据
   * @param depth 递归层级,默认:2
   * @returns 返回结果数据
   */
  static unpackIfNeeded(value: any, depth = 2): any {
    if (isPlainObject(value)) {
      // 兼容 v2 的 StructuredListTransformer 产生的数据
      // https://github.com/fjc0k/vtils/blob/v2/packages/vtils/src/StructuredListTransformer.ts
      if (value.__IS_PACKED_STRUCTURED_LIST__) {
        value._k = value.keys
        value._v = value.values
        value._s = value.signature
        delete value.__IS_PACKED_STRUCTURED_LIST__
      }
      if (isType<PackedData<RawObjectData>>(value)) {
        if (value._k && value._v && value._s) {
          return DataPacker.unpack(value)
        }
        if (depth > 0) {
          return mapValues(value, v => DataPacker.unpackIfNeeded(v, depth - 1))
        }
      }
    }
    return value
  }
}