storage/src/remote.ts

71 lines
2.0 KiB
TypeScript

import { KeyValueStorage } from "./basic.ts";
function protectKey(key: string): string {
return encodeURIComponent(key);
}
type RestClient = (
method: "GET" | "POST" | "PUT" | "DELETE",
uri: string,
body?: string,
) => Promise<string | null>;
export const HEADER_REQUESTER = "X-TK-Storage-App";
export const HEADER_REPLYIER = "X-TK-Storage-Control";
/**
* Storage on a remote server, directly usable using HTTP REST verbs
*/
export class RestRemoteStorage implements KeyValueStorage {
readonly appname: string;
readonly base_url: string;
readonly client: RestClient;
constructor(appname: string, url: string) {
this.appname = appname;
this.base_url = (url[url.length - 1] != "/") ? url + "/" : url;
this.client = this.createClient();
}
async get(key: string): Promise<string | null> {
key = protectKey(key);
return await this.client("GET", key);
}
async set(key: string, value: string | null): Promise<void> {
key = protectKey(key);
if (value === null) {
await this.client("DELETE", key);
} else {
await this.client("PUT", key, value);
}
}
private createClient(): RestClient {
const client: RestClient = async (method, path, body?) => {
const response = await fetch(`${this.base_url}${path}`, {
method,
body,
headers: {
[HEADER_REQUESTER]: this.appname,
},
});
// the body is consumed here to not leak resource, but it would
// be better to consume it only when needed, once
// https://github.com/denoland/deno/issues/4735 is fixed
const text = await response.text();
if (response.headers.get(HEADER_REPLYIER) != "ok") {
throw new Error("storage not compatible with tk-storage");
} else if (response.status == 200) {
return text;
} else if (response.status == 404) {
return null;
} else {
throw new Error(
`http error ${response.status}: ${response.statusText}`,
);
}
};
return client;
}
}