setup boilerplate for data and rendering
This includes zod types creation for API response validation, and exploration of path based resolution of rendering particles.
This commit is contained in:
+90
-91
@@ -1,17 +1,19 @@
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import type { z } from "zod";
|
||||
import {
|
||||
DepotObjectSchema,
|
||||
HumanSchema,
|
||||
ListNetworksResponseSchema,
|
||||
NetworkSchema,
|
||||
PrepareUploadResponseSchema,
|
||||
SignInResponseSchema,
|
||||
} from "./types";
|
||||
import type {
|
||||
CreateStreamParticleRequest,
|
||||
CreateStreamRequest,
|
||||
Human,
|
||||
MarkSeenBatchRequest,
|
||||
AddMembersRequest,
|
||||
CreateNetworkRequest,
|
||||
PrepareUploadRequest,
|
||||
PrepareUploadResponse,
|
||||
RequestCodeRequest,
|
||||
SignInRequest,
|
||||
SignInResponse,
|
||||
StartupResponse,
|
||||
Stream,
|
||||
StreamParticle,
|
||||
} from "./types";
|
||||
|
||||
export class ApiError extends Error {
|
||||
@@ -37,11 +39,11 @@ class ApiClient {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private async request<T>(
|
||||
private async fetch(
|
||||
method: string,
|
||||
path: string,
|
||||
body?: unknown,
|
||||
): Promise<T> {
|
||||
): Promise<Response> {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
@@ -67,109 +69,106 @@ class ApiClient {
|
||||
throw new ApiError(response.status, text);
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
private async request<T>(
|
||||
schema: z.ZodType<T>,
|
||||
method: string,
|
||||
path: string,
|
||||
body?: unknown,
|
||||
): Promise<T> {
|
||||
const response = await this.fetch(method, path, body);
|
||||
const json = await response.json();
|
||||
return schema.parse(json);
|
||||
}
|
||||
|
||||
private async requestVoid(
|
||||
method: string,
|
||||
path: string,
|
||||
body?: unknown,
|
||||
): Promise<void> {
|
||||
await this.fetch(method, path, body);
|
||||
}
|
||||
|
||||
// --- Auth ---
|
||||
|
||||
async requestCode(data: RequestCodeRequest): Promise<void> {
|
||||
await this.request<void>("POST", "/auth/request-code", data);
|
||||
await this.requestVoid("POST", "/auth/request-code", data);
|
||||
}
|
||||
|
||||
async signIn(data: SignInRequest): Promise<SignInResponse> {
|
||||
return this.request<SignInResponse>("POST", "/auth/sign-in", data);
|
||||
async signIn(data: SignInRequest) {
|
||||
return this.request(SignInResponseSchema, "POST", "/auth/sign-in", data);
|
||||
}
|
||||
|
||||
async me(): Promise<Human> {
|
||||
return this.request<Human>("GET", "/auth/me");
|
||||
async me() {
|
||||
return this.request(HumanSchema, "GET", "/auth/me");
|
||||
}
|
||||
|
||||
async signOut(): Promise<void> {
|
||||
await this.request<void>("POST", "/auth/sign-out");
|
||||
await this.requestVoid("POST", "/auth/sign-out");
|
||||
}
|
||||
|
||||
// --- Startup ---
|
||||
|
||||
async startup(): Promise<StartupResponse> {
|
||||
return this.request<StartupResponse>("GET", "/startup");
|
||||
}
|
||||
|
||||
// --- Streams ---
|
||||
|
||||
async createStream(
|
||||
networkId: string,
|
||||
data: CreateStreamRequest,
|
||||
): Promise<Stream> {
|
||||
return this.request<Stream>(
|
||||
"POST",
|
||||
`/networks/${networkId}/streams`,
|
||||
data,
|
||||
// TODO: security: require passing in the particle id once api deprecates this
|
||||
async getParticleDownloadUrl(objectId: string): Promise<string> {
|
||||
const response = await this.fetch(
|
||||
"GET",
|
||||
`/particles/${objectId}/download`,
|
||||
);
|
||||
}
|
||||
|
||||
async createStreamParticle(
|
||||
streamId: string,
|
||||
data: CreateStreamParticleRequest,
|
||||
): Promise<StreamParticle> {
|
||||
return this.request<StreamParticle>(
|
||||
"POST",
|
||||
`/streams/${streamId}/particles`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
// --- Particles ---
|
||||
|
||||
async markSeen(particleId: string): Promise<void> {
|
||||
await this.request<void>("POST", `/particles/${particleId}/seen`);
|
||||
}
|
||||
|
||||
async ackParticle(particleId: string): Promise<void> {
|
||||
await this.request<void>("POST", `/particles/${particleId}/ack`);
|
||||
}
|
||||
|
||||
async markSeenBatch(data: MarkSeenBatchRequest): Promise<void> {
|
||||
await this.request<void>("POST", "/particles/seen", data);
|
||||
}
|
||||
|
||||
async getParticleDownloadUrl(particleId: string): Promise<string> {
|
||||
const token = this.config.getToken();
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) {
|
||||
headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${this.config.baseUrl}/particles/${particleId}/download`,
|
||||
{ headers, redirect: "follow" },
|
||||
);
|
||||
|
||||
if (response.status === 401) {
|
||||
this.config.onUnauthorized();
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(response.status, "Failed to get download URL");
|
||||
}
|
||||
|
||||
return response.url;
|
||||
}
|
||||
|
||||
// --- Depot ---
|
||||
|
||||
async prepareUpload(
|
||||
data: PrepareUploadRequest,
|
||||
): Promise<PrepareUploadResponse> {
|
||||
return this.request<PrepareUploadResponse>("POST", "/depot/upload", data);
|
||||
async prepareUpload(data: PrepareUploadRequest) {
|
||||
return this.request(
|
||||
PrepareUploadResponseSchema,
|
||||
"POST",
|
||||
"/depot/upload",
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
async confirmUpload(objectId: string): Promise<void> {
|
||||
await this.request<void>("POST", `/depot/objects/${objectId}/confirm`);
|
||||
async confirmUpload(objectId: string) {
|
||||
return this.request(
|
||||
DepotObjectSchema,
|
||||
"POST",
|
||||
`/depot/objects/${objectId}/confirm`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// --- Networks ---
|
||||
|
||||
async listNetworks() {
|
||||
return this.request(
|
||||
ListNetworksResponseSchema,
|
||||
"GET",
|
||||
"/networks",
|
||||
);
|
||||
}
|
||||
|
||||
async createNetwork(data: CreateNetworkRequest) {
|
||||
return this.request(NetworkSchema, "POST", "/networks", data);
|
||||
}
|
||||
|
||||
async getNetwork(id: string) {
|
||||
return this.request(NetworkSchema, "GET", `/networks/${id}`);
|
||||
}
|
||||
|
||||
async addMembers(networkId: string, data: AddMembersRequest): Promise<void> {
|
||||
await this.requestVoid(
|
||||
"POST",
|
||||
`/networks/${networkId}/members`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
async removeMember(networkId: string, email: string): Promise<void> {
|
||||
await this.requestVoid(
|
||||
"DELETE",
|
||||
`/networks/${networkId}/members/${email}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user