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; 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 { key = protectKey(key); return await this.client("GET", key); } async set(key: string, value: string | null): Promise { 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; } }