diff --git a/src/api.ts b/src/api.ts deleted file mode 100644 index 1c909ca..0000000 --- a/src/api.ts +++ /dev/null @@ -1,74 +0,0 @@ -type Something = { some: T; none: false } -type Nothing = { none: true } -export type Option = Something | Nothing - -export function Some(value: T): Option { - return Object.freeze({ some: value, none: false }) -} - -export const None: Nothing = Object.freeze({ none: true }) - -export function unwrap(option: Option): T { - const value = 'some' in option ? option.some : undefined - if (value == null) { - throw new Error('cannot unwrap a None value') - } - return value -} - -export type Result = { - ok?: T - err?: E -} - -export type ApiError = { - code: string - message: string -} - -class ServerError extends Error { - status: number - constructor(message: string, status: number) { - super(message) - this.status = status - this.name = 'ServerError' - } -} - -export class ClientError extends Error { - body: T - status: number - constructor(message: string, status: number, body: T) { - super(message) - this.status = status - this.name = 'ClientError' - this.body = body - } -} - -export async function apiResult(whenResponse: Promise): Promise>> { - try { - const response = await whenResponse - if (response.status === 204) { - return { ok: None } - } else if (response.ok) { - const body: T = await response.json() - return { ok: Some(body) } - } else if (response.status >= 400 && response.status < 500) { - const body: ApiError = await response.json() - const error = new ClientError(response.statusText, response.status, body) - return { err: error } - } else { - const error = new ServerError(response.statusText, response.status) - return { err: error } - } - } catch (e: any) { - return { err: e } - } -} - -export function apiMessage(error: Error): string { - return error instanceof ClientError - ? error.body.message - : 'There was an error contacting the server. Please try again.' -} diff --git a/src/index.ts b/src/index.ts index cf1ef4b..458f328 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -import { Result, apiResult, unwrap, apiMessage, ClientError } from './api.js' import { utf8Encode } from './text.js' export interface Credentials { @@ -79,95 +78,72 @@ export default class Client { } export class Connection { - client: Client - session: QuerySession | null + private client: Client + private session: QuerySession | null constructor(client: Client) { this.client = client this.session = null } - async createSession(): Promise { - const saved = await this.postJSON( - `${this.client.credentials.mysqlAddress}/psdb.v1alpha1.Database/CreateSession`, - {} - ) - if (saved.ok && !saved.ok.error) { - if (saved.ok.session) { - this.session = saved.ok.session - } - return saved.ok.session - } else { - throw saved.err + async refresh(): Promise { + try { + const session = await this.createSession() + return !!session + } catch { + return false } } - async postJSON(url: string, body?: unknown): Promise> { - try { - const result = await apiResult( - fetch(url, { - method: 'POST', - body: body ? JSON.stringify(body) : undefined, - headers: { - 'Content-Type': 'application/json', - ...this.client.authorizationHeader() - }, - credentials: 'include' - }) - ) - - return result.err ? { err: result.err } : { ok: unwrap(result.ok) } - } catch (e: any) { - return { err: e } + private async createSession(): Promise { + const url = `${this.client.credentials.mysqlAddress}/psdb.v1alpha1.Database/CreateSession` + const { session } = await this.postJSON(url) + this.session = session + return session + } + + private async postJSON(url: string, body = {}): Promise { + const response = await fetch(url, { + method: 'POST', + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + ...this.client.authorizationHeader() + }, + credentials: 'include' + }) + + if (response.ok) { + const result = await response.json() + return result + } else { + throw new Error(`${response.status} ${response.statusText}`) } } async execute(query: string): Promise { - const startTime = new Date().getTime() - const saved = await this.postJSON( - `${this.client.credentials.mysqlAddress}/psdb.v1alpha1.Database/Execute`, - { - query: query, - session: this.session - } - ) - const endTime = new Date().getTime() - const elapsedTime = endTime - startTime - if (saved.ok && !saved.ok.error) { - const body = saved.ok - const result = body.result - const rows = result ? parse(result) : null - const headers = result?.fields?.map((f) => f.name) - - this.session = body.session - - // Transform response into something we understand, this matches our - // console's `QueryConsole` response format. - return { - headers, - rows, - size: rows.length, - statement: query, - time: elapsedTime - } - } else if (saved.ok && saved.ok.error) { - return { - statement: query, - errorMessage: saved.ok.error.message, - time: elapsedTime - } - } else { - let errorCode: string | null = null - if (saved.err instanceof ClientError) { - errorCode = saved.err.body.code - } - - return { - statement: query, - errorCode: errorCode, - errorMessage: apiMessage(saved.err), - time: elapsedTime - } + const startTime = Date.now() + const url = `${this.client.credentials.mysqlAddress}/psdb.v1alpha1.Database/Execute` + const saved = await this.postJSON(url, { + query: query, + session: this.session + }) + const time = Date.now() - startTime + + const { result, session, error } = saved + if (error) throw new Error(error.message) + + this.session = session + + const rows = result ? parse(result) : [] + const headers = result ? result.fields?.map((f) => f.name) : [] + + return { + headers, + rows, + size: rows.length, + statement: query, + time } } }