Skip to content

[Feature]: Support for S3 with AccessKey and SecretKey #123

Open
@jhnnsrs

Description

Hi!

First thanks for the great work, especially for the effort in types!
As a lot of the community uses S3 for storage, I was wondering if you would be
interested in supporting a dedicated S3 Store (building on the HTTP Store but with
support for accessKeys and secretKeys? I could spin up a PR, but here is already an
implementation using "aws4fetch".

import {
  HTTPStore,
  openGroup,
  openArray,
  ZarrArray,
  Group as ZarrGroup,
} from "zarr";
import { AwsClient } from "aws4fetch";

enum HTTPMethod {
  Get = "GET",
  Head = "HEAD",
  Put = "PUT",
}


export class S3HttpError extends Error {
  __zarr__: string;
  constructor(code: any) {
    super(code);
    this.__zarr__ = "HTTPError";
    Object.setPrototypeOf(this, HTTPError.prototype);
  }
}

export class S3KeyError extends Error {
  __zarr__: string;

  constructor(key: any) {
    super(`key ${key} not present`);
    this.__zarr__ = "KeyError";
    Object.setPrototypeOf(this, KeyError.prototype);
  }
}

export function joinUrlParts(...args: string[]) {
  return args
    .map((part, i) => {
      if (i === 0) {
        return part.trim().replace(/[\/]*$/g, "");
      } else {
        return part.trim().replace(/(^[\/]*|[\/]*$)/g, "");
      }
    })
    .filter((x) => x.length)
    .join("/");
}

class S3Store extends HTTPStore {
  aws: AwsClient;

  constructor(url: string, aws: AwsClient, options: any = {}) {
    super(url, options);
    this.aws = aws;
  }

  async getItem(item: any, opts: any) {
    const url = joinUrlParts(this.url, item);
    let value: any;
    try {
      value = await this.aws.fetch(url, { ...this.fetchOptions, ...opts });
    } catch (e) {
      throw new S3HTTPError("present");
    }
    if (value.status === 404) {
      // Item is not found
      throw new S3KeyError(item);
    } else if (value.status !== 200) {
      throw new S3HttpError(String(value.status));
    }

    return value.arrayBuffer(); // Browser
    // only decode if 200
  }
  async setItem(item: any, value: any) {
    const url = joinUrlParts(this.url, item);
    if (typeof value === "string") {
      value = new TextEncoder().encode(value).buffer;
    }
    const set = await this.aws.fetch(url, {
      ...this.fetchOptions,
      method: HTTPMethod.Put,
      body: value,
    });
    return set.status.toString()[0] === "2";
  }

  async containsItem(item: any) {
    const url = joinUrlParts(this.url, item);
    try {
      const value = await this.aws.fetch(url, {
        ...this.fetchOptions,
      });

      return value.status === 200;
    } catch (e) {
      return false;
    }
  }
}

Hope that might help some!

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions