@volverjs/data
Version:
Repository pattern implementation with a tiny HttpClient based on Fetch API.
220 lines (219 loc) • 7.99 kB
JavaScript
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
};