UNPKG

@volverjs/data

Version:

Repository pattern implementation with a tiny HttpClient based on Fetch API.

220 lines (219 loc) 7.99 kB
import { Hash as _ } from "./Hash.js"; import { HTTPError as c } from "ky"; import "qs"; class q { _client; _template; _responseAdapter = (t) => Array.isArray(t) ? t : [t]; _requestAdapter = (t) => t; _metadataAdapter = (t) => { let s; return t.headers.has("Content-Language") && (s = { contentLanguage: t.headers.get("Content-Language") }), t.headers.has("Accept-Language") && (s = { acceptLanguage: t.headers.get("Accept-Language") }), t.headers.has("X-Total-Count") && (s = { ...s, total: t.headers.get("X-Total-Count") }), s; }; _hashFunction = _.cyrb53; _readPendingRequests = /* @__PURE__ */ new Map(); _httpClientOptions; /** * @param client The HTTP client to use. * @param template The URL template to use for requests. * @param options The options to use. */ constructor(t, s, e) { if (this._client = t, this._template = s, e?.httpClientOptions && (this._httpClientOptions = e.httpClientOptions), e?.class && !e?.responseAdapter) { const o = e.class; this._responseAdapter = (r) => Array.isArray(r) ? r.map((a) => new o(a)) : [new o(r)]; } e?.responseAdapter && (this._responseAdapter = e.responseAdapter), e?.requestAdapter && (this._requestAdapter = e.requestAdapter), e?.metadataAdapter && (this._metadataAdapter = e.metadataAdapter), e?.hashFunction && (this._hashFunction = e.hashFunction); } /** * @params params - The parameters to use in the request template URL or query. * @params options - The HTTP Client request options. * @returns A an object with the response promise and a function to abort the request. * @example * ```typescript * const repository = new RepositoryHttp(client, 'users/:type') * const { response, abort } = repository.read({ type: 'admin', page: 1 }) * const { data, item, metadata, ok } = await response * //=> GET /users/admin?page=1 * ``` */ read = (t = {}, s = {}) => { const { key: e, ...o } = s; let r = e; if (r !== !1 && ((!r || typeof r == "boolean") && (r = this._hashFunction(JSON.stringify(t))), this._hasRepositoryHttpReadPendingRequest(r))) return this._cloneRepositoryHttpReadPendingRequest(r); const { responsePromise: a, abort: p, signal: n } = this._client.request( "get", this._requestUrl(t), this._requestOptions(o) ), h = (async () => { try { const i = await a, u = await i.json(), d = this._responseAdapter(u), l = this._metadataAdapter(i); return r !== !1 && this._deleteRepositoryHttpReadPendingRequest(r), { data: d, item: d?.[0], metadata: l, ok: i.ok }; } catch (i) { if (r !== !1 && this._deleteRepositoryHttpReadPendingRequest(r), !n.aborted && i instanceof c) throw i; return { ok: !1, aborted: !0, abortReason: n.reason }; } })(); return r === !1 ? { abort: p, responsePromise: h, signal: n } : this._setRepositoryHttpReadPendingRequest(r, { abort: p, responsePromise: h }); }; /** * @params payload - The payload to use in the request body. * @params params - The parameters to use in the request template URL or query. * @params options - The HTTP Client request options. * @returns A an object with the response promise and a function to abort the request. * @example * ```typescript * const repository = new RepositoryHttp(client, 'users/:type') * const payload = { name: 'John' } * const { response, abort } = repository.create(payload, { type: 'admin' }) * const { data, item, metadata, ok } = await response * //=> POST /users/admin * ``` */ create = (t, s, e) => { const { responsePromise: o, abort: r, signal: a } = this._client.request( "post", this._requestUrl(s), this._requestOptions(e, t) ), p = (async () => { try { const n = await o, h = await n.json(), i = this._responseAdapter(h), u = this._metadataAdapter(n); return { data: i, item: i?.[0], metadata: u, ok: n.ok }; } catch (n) { if (!a.aborted) throw n; return { ok: !1, aborted: !0, abortReason: a.reason }; } })(); return { abort: r, responsePromise: p, signal: a }; }; /** * @params payload - The payload to use in the request body. * @params params - The parameters to use in the request template URL or query. * @params options - The HTTP Client request options. * @returns A an object with the response promise and a function to abort the request. * @example * ```typescript * const repository = new RepositoryHttp(client, 'users/:type/?:id') * const payload = { id: 1, name: 'John' } * const { response, abort } = repository.update(payload, { type: 'admin', id: 1 }) * const { data, item, metadata, ok } = await response * //=> PUT /users/admin/1 * ``` */ update = (t, s, e) => { const { responsePromise: o, abort: r, signal: a } = this._client.request( "put", this._requestUrl(s), this._requestOptions(e, t) ), p = (async () => { try { const n = await o, h = await n.json(), i = this._responseAdapter(h), u = this._metadataAdapter(n); return { data: i, item: i?.[0], metadata: u, ok: n.ok }; } catch (n) { if (!a.aborted) throw n; return { ok: !1, aborted: !0, abortReason: a.reason }; } })(); return { abort: r, responsePromise: p, signal: a }; }; /** * @params params - The parameters to use in the request template URL or query. * @params options - The HTTP Client request options. * @returns A an object with the response promise and a function to abort the request. * @example * ```typescript * const repository = new RepositoryHttp(client, 'users/:type/?:id') * const { response, abort } = repository.delete({ type: 'admin', id: 1 }) * const { ok } = await response * //=> DELETE /users/admin/1 * ``` */ remove = (t, s) => { const e = this._requestOptions(s), { responsePromise: o, abort: r, signal: a } = this._client.request( "delete", this._requestUrl(t), e ), p = (async () => { try { return { ok: (await o).ok }; } catch (n) { if (!a.aborted) throw n; return { ok: !1, aborted: !0, abortReason: a.reason }; } })(); return { abort: r, responsePromise: p, signal: a }; }; _hasRepositoryHttpReadPendingRequest = (t) => t && this._readPendingRequests.has(t); _deleteRepositoryHttpReadPendingRequest = (t) => this._readPendingRequests.delete(t); _cloneRepositoryHttpReadPendingRequest = (t) => { const s = new AbortController(), e = this._readPendingRequests.get( t ); return e.count++, { responsePromise: new Promise((o, r) => { s.signal.addEventListener("abort", () => { e.count--, e.count === 0 && e.abort(s.signal.reason), o({ ok: !1, aborted: !0, abortReason: s.signal.reason }); }), e.responsePromise.then((...a) => { o(...a); }).catch((a) => { a instanceof c && r(a); }); }), abort: (o) => s.abort(o), signal: s.signal }; }; _setRepositoryHttpReadPendingRequest = (t, { abort: s, responsePromise: e }) => (this._readPendingRequests.set(t, { abort: s, responsePromise: e, count: 0 }), this._cloneRepositoryHttpReadPendingRequest(t)); _requestUrl = (t = {}) => typeof this._template == "string" ? { template: this._template, params: t } : { ...this._template, params: { ...this._template.params, ...t } }; _requestOptions = (t, s) => { const e = { ...this._httpClientOptions, ...t }; return s ? Array.isArray(s) ? (e.json = s.map((o) => this._requestAdapter(o)), e) : (e.json = this._requestAdapter(s), e) : e; }; } export { q as RepositoryHttp };