0.1.4Updated 8 days ago
import { AssetStoreBase, type UploadableDataTypes, type FileStats } from "./asset_store_base.ts";
import { S3Client } from "jsr:@hk/s3";

export interface S3StoreOptions {
  /** S3 Region */
  region: string | undefined
  /** S3 Bucket */
  bucket: string | undefined
  /** S3 AccessKeyID */
  access_key_id: string | undefined
  /** S3 SecretAccessKey */
  secret_access_key: string | undefined
}

export class S3Store extends AssetStoreBase {
  private S3: S3Client;
  private promises: Record<string, Promise<ArrayBufferLike | null>> = {}

  constructor(namespace: string, s3_options: S3StoreOptions) {
    super(namespace);

    this.S3 = new S3Client({
      region: s3_options.region,
      bucket: s3_options.bucket,
      accessKeyId: s3_options.access_key_id,
      secretAccessKey: s3_options.secret_access_key,
    })
  }

  private to_file = (key: string) => this.S3.file(this.namespaced_key(key));

  override async Upload(key: string, data: UploadableDataTypes) {
    key = this.stripped_key(key);

    await this.ToArrayBuffer(key, data);

    await this.to_file(key).write(data);

    return this.namespaced_key(key);
  }

  override async Get(key: string) {
    key = this.stripped_key(key);

    if(this.cache.has(key)) return this.cache.get(key);
    if(this.promises[key] !== undefined) return this.promises[key];

    const file = this.to_file(key);

    if(!(await file.exists())) return null;

    this.promises[key] = this.to_file(key).arrayBuffer();

    const response = await this.promises[key];
    if(response) this.cache.set(key, response);

    delete this.promises[key];

    return response;
  }

  override async Info(key: string): Promise<FileStats> {
    key = this.stripped_key(key);

    return await this.to_file(key).stat();
  }

  override async Delete(key: string) {
    key = this.stripped_key(key);

    this.cache.delete(key);

    await this.to_file(key).delete();
    return;
  }
}