All files / json-pack/src/rm RmRecordEncoder.ts

73.17% Statements 30/41
62.5% Branches 5/8
100% Functions 8/8
73.17% Lines 30/41

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 8532x     32x 32x   32x 606x     5x 5x       25x 25x       1564x       27x 27x 27x 27x 27x 27x                       2x 2x 2x                 1529x 1529x 1529x 1529x                         1529x 1529x 1529x 1529x 1529x 1529x 1529x                    
import {Writer} from '@jsonjoy.com/buffers/lib/Writer';
import type {IWriter, IWriterGrowable} from '@jsonjoy.com/buffers';
 
const RM_HEADER_SIZE = 4;
const MAX_SINGLE_FRAME_SIZE = 0x7fffffff;
 
export class RmRecordEncoder<W extends IWriter & IWriterGrowable = IWriter & IWriterGrowable> {
  constructor(public readonly writer: W = new Writer() as any) {}
 
  public encodeHdr(fin: 0 | 1, length: number): Uint8Array {
    this.writeHdr(fin, length);
    return this.writer.flush();
  }
 
  public encodeRecord(record: Uint8Array): Uint8Array {
    this.writeRecord(record);
    return this.writer.flush();
  }
 
  public writeHdr(fin: 0 | 1, length: number): void {
    this.writer.u32((fin ? 0b10000000_00000000_00000000_00000000 : 0) + length);
  }
 
  public writeRecord(record: Uint8Array): void {
    const length = record.length;
    if (length <= 2147483647) {
      const writer = this.writer;
      writer.u32(0b10000000_00000000_00000000_00000000 + length);
      writer.buf(record, length);
      return;
    }
    let offset = 0;
    while (offset < length) {
      const fragmentLength = Math.min(length - offset, 0x7fffffff);
      const fin = fragmentLength + offset >= length ? 1 : 0;
      this.writeFragment(record, offset, fragmentLength, fin);
      offset += fragmentLength;
    }
  }
 
  public writeFragment(record: Uint8Array, offset: number, length: number, fin: 0 | 1): void {
    this.writeHdr(fin, length);
    const fragment = record.subarray(offset, offset + length);
    this.writer.buf(fragment, length);
  }
 
  /**
   * To write an RM record in one pass this method reserves space for the RM
   * header, and returns the state, which needs to passed to `endRmRecord` to
   * finalize the RM header.
   */
  public startRecord(): number {
    const writer = this.writer;
    const rmHeaderPosition = writer.x;
    writer.x += RM_HEADER_SIZE;
    return rmHeaderPosition;
  }
 
  /**
   * Finalize the RM header started by `startRmRecord`.
   *
   * @param rmHeaderPosition The position returned by `startRmRecord`
   * @remarks This method will check if the data written after `startRmRecord`
   * fits into a single RM frame. If it does, it will write the RM header in
   * place. If it doesn't, it will move the data to a new location and write
   * it as multiple RM frames.
   */
  public endRecord(rmHeaderPosition: number): void {
    const writer = this.writer;
    const totalSize = writer.x - rmHeaderPosition - RM_HEADER_SIZE;
    if (totalSize <= MAX_SINGLE_FRAME_SIZE) {
      const currentX = writer.x;
      writer.x = rmHeaderPosition;
      this.writeHdr(1, totalSize);
      writer.x = currentX;
    } else E{
      const currentX = writer.x;
      writer.x = rmHeaderPosition;
      const data = writer.uint8.subarray(rmHeaderPosition + RM_HEADER_SIZE, currentX);
      writer.reset();
      this.writeRecord(data);
    }
  }
}