Skip to content

Commit

Permalink
update HttpApiClient
Browse files Browse the repository at this point in the history
  • Loading branch information
lideming committed Jan 16, 2024
1 parent 02d63b6 commit 804d2c1
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 28 deletions.
99 changes: 77 additions & 22 deletions src/database/HttpApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,49 @@ export class ClientDatabase {
return this.getSet(name, type as any) as any;
}

async createKvSet(name: string): Promise<ClientKvSet> {
return await this.createSet(name, "kv");
}

async createDocSet(name: string): Promise<ClientDocSet> {
return await this.createSet(name, "doc");
}

getSet(name: string, type?: "kv"): ClientKvSet;
getSet(name: string, type: "doc"): ClientDocSet;
getSet(name: string, type: "kv" | "doc" = "kv") {
if (type === "kv" || type === undefined) {
return new ClientKvSet(this.httpClient, name);
return this.getKvSet(name);
} else if (type === "doc") {
return new ClientDocSet(this.httpClient, name);
return this.getDocSet(name);
} else {
throw new Error("wrong type " + type);
}
}

getKvSet(name: string) {
return new ClientKvSet(this.httpClient, name);
}

getDocSet(name: string) {
return new ClientDocSet(this.httpClient, name);
}

async deleteSet(name: string, type: "kv" | "doc"): Promise<boolean> {
return await this.httpClient.request(
"DELETE",
url`/sets/${type}:${name}`,
);
}

async deleteKvSet(name: string): Promise<boolean> {
return await this.deleteSet(name, "kv");
}

async deleteDocSet(name: string): Promise<boolean> {
return await this.deleteSet(name, "doc");
}

async getObjects(): Promise<
{
name: string;
Expand All @@ -51,27 +76,32 @@ export class ClientDatabase {
export class ClientKvSet {
constructor(readonly httpClient: HttpClient, readonly name: string) {}

exists(): Promise<boolean> {
throw new Error("Method not implemented.");
async exists(): Promise<boolean> {
const { status } = await this.httpClient.requestReturnStatus(
"HEAD",
url`/sets/kv:${this.name}`,
);
return status == 200;
}
async get(key: SetKeyType): Promise<SetValueType | null> {
return await this.httpClient.request(
"GET",
url`/sets/kv:${this.name}/${JSON.stringify(key)}`,
);
}
async set(key: SetKeyType, value: SetValueType): Promise<void> {
await this.httpClient.request(
async set(key: SetKeyType, value: SetValueType): Promise<boolean> {
return await this.httpClient.request(
"PUT",
url`/sets/kv:${this.name}/${JSON.stringify(key)}`,
value,
);
}
async delete(key: SetKeyType): Promise<void> {
await this.httpClient.request(
async delete(key: SetKeyType): Promise<boolean> {
const { status } = await this.httpClient.requestReturnStatus(
"DELETE",
url`/sets/kv:${this.name}/${JSON.stringify(key)}`,
);
return status == 200;
}
async getAll(): Promise<
{
Expand Down Expand Up @@ -105,10 +135,13 @@ export class ClientKvSet {
export class ClientDocSet {
constructor(readonly httpClient: HttpClient, readonly name: string) {}

get setId() {
return `doc:${this.name}`;
async exists(): Promise<boolean> {
const { status } = await this.httpClient.requestReturnStatus(
"HEAD",
url`/sets/doc:${this.name}`,
);
return status == 200;
}

async getCount(): Promise<number> {
return await this.httpClient.request(
"GET",
Expand All @@ -130,16 +163,17 @@ export class ClientDocSet {
}
async upsert(doc: any): Promise<void> {
return await this.httpClient.request(
"POST",
url`/sets/doc:${this.name}/${JSON.stringify(doc.id)}?insert`,
"PUT",
url`/sets/doc:${this.name}/${JSON.stringify(doc.id)}`,
doc,
);
}
async delete(id: unknown): Promise<boolean> {
return await this.httpClient.request(
const { status } = await this.httpClient.requestReturnStatus(
"DELETE",
url`/sets/doc:${this.name}/${JSON.stringify(id)}`,
);
return status == 200;
}
async getIds(): Promise<any[]> {
return await this.httpClient.request(
Expand Down Expand Up @@ -175,6 +209,22 @@ export class HttpClient {
method: "GET" | "POST" | "PUT" | "DELETE",
url: string,
body?: any,
) {
const { status, body: resBody } = await this.requestReturnStatus(
method,
url,
body,
);
if (status < 200 || status >= 300) {
throw new HTTPError(status, resBody);
}
return resBody;
}

async requestReturnStatus(
method: "GET" | "POST" | "PUT" | "DELETE" | "HEAD",
url: string,
body?: any,
) {
const { fetch = defaultFetch, baseUrl = "", token } = this.options;
const headers: any = {};
Expand All @@ -190,14 +240,19 @@ export class HttpClient {
if (resp.headers.get("content-type")?.startsWith("application/json")) {
json = await resp.json();
}
if (!resp.ok) {
if (json) {
throw new Error(`API: ${json.error}`);
} else {
throw new Error(`HTTP status (${resp.status}) ${await resp.text()}`);
}
}
return json;
return {
status: resp.status,
body: json !== undefined ? json : await resp.text(),
};
}
}

class HTTPError extends Error {
constructor(readonly httpStatus: number, readonly httpBody: any) {
const bodyString = typeof httpBody == "object"
? JSON.stringify(httpBody)
: httpBody;
super(`HTTP status (${httpStatus}): ${bodyString}`);
}
}

Expand Down
11 changes: 8 additions & 3 deletions src/database/HttpApiServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ export class HttpApiHanlder {
} else if (req.method == "DELETE") {
// Delete a set
return await this.db.deleteSet(setname, settype);
} else if (req.method == "HEAD") {
// Check if the set exists
// this.getSet() throws 404 if the set not found
//@ts-ignore
await this.getSet(setname, settype);
return;
}
}
if (settype == "kv") {
Expand All @@ -104,8 +110,7 @@ export class HttpApiHanlder {
} else if (req.method == "PUT") {
// Set a key-value pair
const set = await this.getSet(setname, settype);
await set.set(key, await req.json());
return;
return await set.set(key, await req.json());
} else if (req.method == "DELETE") {
// Delete a key
const set = await this.getSet(setname, settype);
Expand Down Expand Up @@ -215,7 +220,7 @@ export class HttpApiHanlder {

private async getSet(name: string, type: "kv"): Promise<IDbSet>;
private async getSet(name: string, type: "doc"): Promise<IDbDocSet>;
private async getSet(name: string, type: string) {
private async getSet(name: string, type: "kv" | "doc") {
const set = this.db.getSet(name, type as any) as any;
if (!await set.exists()) throw new ApiError(404, `set not found`);
return set;
Expand Down
11 changes: 8 additions & 3 deletions tests/http_api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,18 @@ for (const RUN_IN_WORKER of [false, true]) {
testOptions,
async () => {
await testApi("POST", "/sets/kv:test");
await testApi("PUT", `/sets/kv:test/${jsonUri("key1")}`, "value1");
await testApi("PUT", `/sets/kv:test/${jsonUri("key1")}`, "value1", true);
await testApi("GET", `/sets/kv:test/${jsonUri("key1")}`, "value1");
await testApi("GET", `/sets/kv:test/?count`, 1);
await testApi("PUT", `/sets/kv:test/${jsonUri(123)}`, 456);
await testApi("PUT", `/sets/kv:test/${jsonUri(123)}`, 456, true);
await testApi("GET", `/sets/kv:test/${jsonUri(123)}`, 456);
await testApi("GET", `/sets/kv:test/?count`, 2);
await testApi("PUT", `/sets/kv:test/${jsonUri("with/ & %")}`, "okay?");
await testApi(
"PUT",
`/sets/kv:test/${jsonUri("with/ & %")}`,
"okay?",
true,
);
await testApi("GET", `/sets/kv:test/${jsonUri("with/ & %")}`, "okay?");
await testApi("GET", `/sets/kv:test/?count`, 3);
await testApi("DELETE", `/sets/kv:test/${jsonUri("with/ & %")}`);
Expand Down

0 comments on commit 804d2c1

Please sign in to comment.