UNPKG

@data-loom/postgrest-js

Version:

Isomorphic PostgREST client

286 lines 12.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); // @ts-ignore const node_fetch_1 = __importDefault(require("@data-loom/node-fetch")); const PostgrestError_1 = __importDefault(require("./PostgrestError")); const trace_id_1 = require("./util/trace-id"); class PostgrestBuilder { constructor(builder) { this.shouldThrowOnError = false; 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.isMaybeSingle = builder.isMaybeSingle; this.action = builder.action; this.relation = builder.relation; this.logger = builder.logger; this.onRequestError = builder.onRequestError; if (builder.fetch) { this.fetch = builder.fetch; } else if (typeof fetch === 'undefined') { this.fetch = node_fetch_1.default; } else { this.fetch = fetch; } } /** * 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.shouldThrowOnError = true; return this; } /** * Set an HTTP header for the request. */ setHeader(name, value) { this.headers = Object.assign({}, this.headers); this.headers[name] = value; return this; } then(onfulfilled, onrejected) { // 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 traceId; if (this.logger) { traceId = trace_id_1.traceIdGenerator.next(); this.logRequest(traceId); } let res = _fetch(this.url.toString(), { method: this.method, mode: 'cors', redirect: 'manual', credentials: 'include', headers: this.headers, body: JSON.stringify(this.body), signal: this.signal, }).then(async (res) => { var _a, _b, _c, _d; let error = null; let data = null; let count = null; let status = res.status; let statusText = res.statusText; if (res.ok) { const body = await res.text(); const parsedBody = JSON.parse(body); if (parsedBody.status_code && parsedBody.error_msg) { data = null; error = { code: parsedBody.status_code, details: '', hint: '', message: parsedBody.error_msg, }; } else { if (this.method !== 'HEAD') { if (body === '') { // Prefer: return=minimal } else if (this.headers['Accept'] === 'text/csv') { data = body; } else if (this.headers['Accept'] && this.headers['Accept'].includes('application/vnd.pgrst.plan+text')) { data = body; } else { // body 结构: {data: {}, status_code: 0} try { data = JSON.parse(body).data; } catch (e) { data = null; error = JSON.parse(body); } } } const countHeader = (_a = this.headers['Prefer']) === null || _a === void 0 ? void 0 : _a.match(/count=(exact|planned|estimated)/); const contentRange = (_b = res.headers.get('content-range')) === null || _b === void 0 ? void 0 : _b.split('/'); if (countHeader && contentRange && contentRange.length > 1) { // 这种情况 method 是 GET,但是取值需要通过 header 取 count = parseInt(contentRange[1]); } // Temporary partial fix for https://github.com/supabase/postgrest-js/issues/361 // Issue persists e.g. for `.insert([...]).select().maybeSingle()` if (this.isMaybeSingle && this.method === 'GET' && Array.isArray(data)) { if (data.length > 1) { error = { // https://github.com/PostgREST/postgrest/blob/a867d79c42419af16c18c3fb019eba8df992626f/src/PostgREST/Error.hs#L553 code: 'PGRST116', details: `Results contain ${data.length} rows, application/vnd.pgrst.object+json requires 1 row`, hint: '', message: 'JSON object requested, multiple (or no) rows returned', }; data = null; count = null; status = 406; statusText = 'Not Acceptable'; } else if (data.length === 1) { data = data[0]; } else { data = null; } } } } else { const body = await res.text(); try { const parsedBody = JSON.parse(body); error = { code: parsedBody.status_code || res.status, details: '', hint: '', message: parsedBody.error_msg || 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'; } if (res.status === 429) { error = { code: 429, message: 'Too many requests received within 5 seconds.', details: '', hint: 'Retry after 5 seconds.', }; } } catch (_e) { // 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.isMaybeSingle && ((_c = error === null || error === void 0 ? void 0 : error.details) === null || _c === void 0 ? void 0 : _c.includes('0 rows'))) { error = null; status = 200; statusText = 'OK'; } if (error && this.shouldThrowOnError) { throw new PostgrestError_1.default(error); } } const postgrestResponse = { error, data, count, status, statusText, }; if (error) { (_d = this.onRequestError) === null || _d === void 0 ? void 0 : _d.call(this, Object.assign(Object.assign({}, error), { status, statusText })); } if (this.logger) { this.logResponse(traceId, postgrestResponse); } return postgrestResponse; }); if (!this.shouldThrowOnError) { res = res.catch((fetchError) => { var _a, _b, _c; return ({ error: { message: `${(_a = fetchError === null || fetchError === void 0 ? void 0 : fetchError.name) !== null && _a !== void 0 ? _a : 'FetchError'}: ${fetchError === null || fetchError === void 0 ? void 0 : fetchError.message}`, details: `${(_b = fetchError === null || fetchError === void 0 ? void 0 : fetchError.stack) !== null && _b !== void 0 ? _b : ''}`, hint: '', code: `${(_c = fetchError === null || fetchError === void 0 ? void 0 : fetchError.code) !== null && _c !== void 0 ? _c : ''}`, }, data: null, count: null, status: 0, statusText: '', }); }); } return res.then(onfulfilled, onrejected); } logResponse(traceId, postgrestResponse) { const { status, data, error, count } = postgrestResponse; console[error ? 'error' : 'log'](`[Dataloom] [Response ${error ? 'Failed' : 'Success'}]`, 'trace id:', traceId, 'table name:', this.relation, 'statusCode:', status, 'data:', data, 'error:', error, 'count:', count); } logRequest(traceId) { // count=exact,tx=rollback,missing=default,resolution=ignore-duplicates|merge-duplicates const decodedParams = Array.from(this.url.searchParams.entries()) .filter(([key]) => !['count', 'tx', 'missing', 'resolution', 'column'].includes(key)) .map(([key, value]) => `${key}=${value}`); console.log('[Dataloom] [Request]', 'trace id:', traceId, 'table name:', this.relation, 'method:', this.method, 'action:', this.action, 'params:', decodedParams.join(','), 'preferHeaders:', this.headers['Prefer'], 'body:', this.body, 'schema:', this.schema); } /** * Override the type of the returned `data`. * * @typeParam NewResult - The new result type to override with * @deprecated Use overrideTypes<yourType, { merge: false }>() method at the end of your call chain instead */ returns() { /* istanbul ignore next */ return this; } /** * Override the type of the returned `data` field in the response. * * @typeParam NewResult - The new type to cast the response data to * @typeParam Options - Optional type configuration (defaults to { merge: true }) * @typeParam Options.merge - When true, merges the new type with existing return type. When false, replaces the existing types entirely (defaults to true) * @example * ```typescript * // Merge with existing types (default behavior) * const query = supabase * .from('users') * .select() * .overrideTypes<{ custom_field: string }>() * * // Replace existing types completely * const replaceQuery = supabase * .from('users') * .select() * .overrideTypes<{ id: number; name: string }, { merge: false }>() * ``` * @returns A PostgrestBuilder instance with the new type */ overrideTypes() { return this; } } exports.default = PostgrestBuilder; //# sourceMappingURL=PostgrestBuilder.js.map