UNPKG

@miuiu/postgrest

Version:
168 lines (153 loc) 4.34 kB
import type { Fetch, PostgrestSingleResponse } from "./types"; export default abstract class PostgrestBuilder<Result> implements PromiseLike<PostgrestSingleResponse<Result>> { protected method: "GET" | "HEAD" | "POST" | "PATCH" | "DELETE"; protected url: URL; protected headers: Record<string, string>; protected schema?: string; protected body?: unknown; protected shouldThrowOnError = false; protected signal?: AbortSignal; protected fetch: Fetch = fetch; protected allowEmpty: boolean; constructor(builder: PostgrestBuilder<Result>) { this.method = builder.method; this.url = builder.url; this.headers = builder.headers; this.schema = builder.schema; this.body = builder.body; this.shouldThrowOnError = builder.shouldThrowOnError; this.signal = builder.signal; this.allowEmpty = builder.allowEmpty; } /** * If there's an error with the query, throwOnError will reject the promise by * throwing the error instead of returning it as part of a successful response. * * {@link https://github.com/supabase/supabase-js/issues/92} */ throwOnError(): this { this.shouldThrowOnError = true; return this; } then<TResult1 = PostgrestSingleResponse<Result>, TResult2 = never>( onfulfilled?: | (( value: PostgrestSingleResponse<Result>, ) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: | ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null, ): PromiseLike<TResult1 | TResult2> { // https://postgrest.org/en/stable/api.html#switching-schemas if (this.schema === undefined) { // skip } else if (["GET", "HEAD"].includes(this.method)) { this.headers["Accept-Profile"] = this.schema; } else { this.headers["Content-Profile"] = this.schema; } if (this.method !== "GET" && this.method !== "HEAD") { this.headers["Content-Type"] = "application/json"; } // NOTE: Invoke w/o `this` to avoid illegal invocation error. // https://github.com/supabase/postgrest-js/pull/247 const _fetch = this.fetch; let res = _fetch(this.url.toString(), { method: this.method, headers: this.headers, body: JSON.stringify(this.body), signal: this.signal, }).then(async (res) => { let error = null; let data = null; let count: number | null = null; let status = res.status; let statusText = res.statusText; if (res.ok) { if (this.method !== "HEAD") { const body = await res.text(); if (body === "") { // Prefer: return=minimal } else if (this.headers["Accept"] === "text/csv") { data = body; } else if ( this.headers["Accept"]?.includes("application/vnd.pgrst.plan+text") ) { data = body; } else { data = JSON.parse(body); } } const countHeader = this.headers["Prefer"]?.match( /count=(exact|planned|estimated)/, ); const contentRange = res.headers.get("content-range")?.split("/"); if (countHeader && contentRange && contentRange.length > 1) { count = parseInt(contentRange[1]); } } else { const body = await res.text(); try { error = JSON.parse(body); // Workaround for https://github.com/supabase/postgrest-js/issues/295 if (Array.isArray(error) && res.status === 404) { data = []; error = null; status = 200; statusText = "OK"; } } catch { // Workaround for https://github.com/supabase/postgrest-js/issues/295 if (res.status === 404 && body === "") { status = 204; statusText = "No Content"; } else { error = { message: body, }; } } if ( error && this.allowEmpty && error?.details?.includes("Results contain 0 rows") ) { error = null; status = 200; statusText = "OK"; } if (error && this.shouldThrowOnError) { throw error; } } const postgrestResponse = { error, data, count, status, statusText, }; return postgrestResponse; }); if (!this.shouldThrowOnError) { res = res.catch((fetchError) => ({ error: { message: `FetchError: ${fetchError.message}`, details: "", hint: "", code: fetchError.code || "", }, data: null, count: null, status: 0, statusText: "", })); } return res.then(onfulfilled, onrejected); } }