forattini-dev/s3db.js

View on GitHub
src/cache/s3-cache.class.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import zlib from "zlib";
import * as path from "path";
import { isString } from "lodash";
import sha256 from "crypto-js/sha256";

import S3Client from "../s3-client.class";
import Serializers from "./serializers.type";
import { JsonSerializer } from "./json.serializer";
import { AvroSerializer } from "./avro.serializer";

export class S3Cache {
  serializers: any;
  s3Client: S3Client;
  compressData: boolean;
  serializer: Serializers;

  constructor({
    s3Client,
    compressData = true,
    serializer = Serializers.json,
  }: {
    s3Client: S3Client;
    compressData?: boolean;
    serializer?: Serializers;
  }) {
    this.s3Client = s3Client;
    this.serializer = serializer;
    this.compressData = compressData;

    this.serializers = {
      [Serializers.json]: JsonSerializer,
      [Serializers.avro]: AvroSerializer,
    };
  }

  getKey({
    params,
    hashed = true,
    additionalPrefix = "",
  }: {
    params?: any;
    hashed?: boolean;
    additionalPrefix?: string;
  }) {
    let filename: any =
      Object.keys(params || {})
        .sort()
        .map((x) => `${x}:${params[x]}`)
        .join("|") || "";

    if (filename.length === 0) filename = `empty`;

    if (hashed) {
      filename = sha256(filename);
      // filename = Buffer.from(filename).toString("base64").split("").reverse().join("");
    }

    if (additionalPrefix.length > 0) {
      filename = additionalPrefix + filename;
    }

    filename = filename + "." + this.serializer;

    if (this.compressData) filename += ".gz";

    return path.join("cache", filename);
  }

  async _put({ key, data }: { key: string; data: any }) {
    const lengthRaw = isString(data)
      ? data.length
      : JSON.stringify(data).length;

    let body: string | Uint8Array = this.serialize({ data });
    const lengthSerialized = body.length;

    if (this.compressData) {
      body = zlib.gzipSync(body);
    }

    const metadata = {
      compressor: "zlib",
      "client-id": this.s3Client.id,
      serializer: String(this.serializer),
      compressed: String(this.compressData),
      "length-raw": String(lengthRaw),
      "length-serialized": String(lengthSerialized),
      "length-compressed": String(body.length),
    };

    return this.s3Client.putObject({
      key,
      body,
      metadata,
      contentEncoding: this.compressData ? "gzip" : null,
      contentType: this.compressData
        ? "application/gzip"
        : `application/${this.serializer}`,
    });
  }

  async _get({ key }: { key: string }) {
    try {
      const res = await this.s3Client.getObject(key);

      if (!res.Body) return "";
      let content = res.Body;

      if (res.Metadata) {
        const { serializer, compressor, compressed } = res.Metadata;

        if (["true", true].includes(compressed)) {
          if (compressor === `zlib`) {
            content = zlib.unzipSync(content as Buffer);
          }
        }

        const { data } = this.serializers[serializer].unserialize(content);
        return data;
      }

      return this.unserialize(content);
    } catch (error) {
      if (error instanceof Error) {
        if (error.name !== "ClientNoSuchKey") {
          return Promise.reject(error);
        }
      }
    }

    return null;
  }

  async _delete({ key }: { key: string }) {
    try {
      await this.s3Client.deleteObject(key);
    } catch (error) {
      if (error instanceof Error) {
        if (error.name !== "ClientNoSuchKey") {
          return Promise.reject(error);
        }
      }
    }

    return true;
  }

  serialize(data: any) {
    return this.serializers[this.serializer].serialize(data);
  }

  unserialize(data: any) {
    return this.serializers[this.serializer].unserialize(data);
  }
}

export default S3Cache;