0.1.4Updated 9 days ago
import { MemCache } from "@utils/memcache";

export interface FileStats {
  size: number;
  type: string;
  lastModified: Date;
  etag?: string
}

export abstract class AssetStoreBase {
  private static __Stores: Record<string, AssetStoreBase> = {}

  static Infer(key: string) {
    if(!key.match(/^\/?\w+\//)) return null;
    const namespace = key.replace(/^\/?(\w+)\/.+?$/, '$1');
    return this.__Stores[namespace] || null;
  }

  constructor(readonly namespace: string) {
    AssetStoreBase.__Stores[this.namespace] = this;
  }

  cache = new MemCache<ArrayBufferLike>(
    86_400, // 1 Day
    300,    // 5 minutes
  );

  protected async ToArrayBuffer(key: string, data: UploadableDataTypes) {
    let array_buffer: ArrayBufferLike | undefined;

    if(typeof data === 'string') {
      array_buffer = this.textEncoder.encode(data).buffer;
    } else if(data instanceof ArrayBuffer || data instanceof SharedArrayBuffer) {
      array_buffer = data;
    } else if(data instanceof Uint8Array) {
      array_buffer = data.buffer;
    } else if(data instanceof Blob || data instanceof File) {
      array_buffer = await data.arrayBuffer();
    }

    if(array_buffer) this.cache.set(key, array_buffer);

    return array_buffer;
  }

  protected stripped_key = (key: string) => key.startsWith(this.namespace) ? key.replace(this.namespace + '/', '') : key;
  protected namespaced_key = (key: string) => `${this.namespace}/${key}`;

  protected textEncoder = new TextEncoder();

  abstract Upload(key: string, data: UploadableDataTypes): string | Promise<string>
  abstract Get(key: string): ArrayBufferLike | null | Promise<ArrayBufferLike | null>
  abstract Info(key: string): FileStats | Promise<FileStats>
  abstract Delete(key: string): void | Promise<void>

  async MoveToStore(key: string, store: AssetStoreBase) {
    key = this.stripped_key(key);

    const data = await this.Get(key);
    if(!data) return false;

    await store.Upload(key, data);

    await this.Delete(key);

    return store.namespaced_key(key);
  }

  URL(key: string) {
    return `/serve/${this.namespace}/${this.stripped_key(key)}`.replace(/\/{2,}/g, '/');
  }
}

export type UploadableDataTypes = 
  | string
  | ArrayBuffer
  | Uint8Array
  | Blob
  | File
  | SharedArrayBuffer
  | ArrayBufferView;