@nexdk/http
REST helpers shared by the defineDriver-based drivers: a JSON request over the native fetch, HttpError, and small query-string and id helpers. It has no runtime dependency besides its nexdk peer.
The package is optional. A driver can use fetch directly, as nhost does (supabase goes through supabase-js).
Install
Section titled “Install”Not published to npm yet. Build it from the repository (packages/http).
Once published, install the package with everything it needs:
pnpm add nexdk @nexdk/httpimport { HttpError, createRequest, encodeId, encodeResource, request, resolveHeaders, splitId, toQuery } from '@nexdk/http'request<T>(url, init?): JSON request.initis{ method?, headers?, body?, fetch?, timeout?, signal? }.fetchis aFetchLikeand replacesglobalThis.fetchfor that call.timeout(milliseconds) andsignal(anAbortSignal) abort the request: it then throws anHttpErrorwithstatus0. Neither is set by default. ThrowsHttpErroron a non-2xx response or a network failure. There are no retries, so one call is one request.FetchLike:(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>, defined innexdkand re-exported here. It is the type of thefetchoption of every driver and ofrequest. It is narrower thantypeof fetch, somock.fetchfrom shamock and thefetchof bun-types (which carries an extrapreconnectmember) are accepted without a cast.createRequest(fetch?, { timeout?, signal? }?): returns arequestbound to the givenfetchand defaults. Without an argument it returnsrequest. The REST drivers call it with theirfetchconfig option.HttpError: a subclass ofSdkErrorfromnexdk:{ status, data, errors, message }.datais the parsed response body.errorsis normalized toRecord<string, string[]>when the body allows it: itserrorskey (Laravel; string values are wrapped in arrays), the API Platformviolationsgrouped bypropertyPath(_when empty), the Strapierror.details.errorskeyed by dottedpath, a PocketBase body{ data: { field: { code, message } } }, or a NestValidationPipebody{ statusCode, message: string[], error }(see below). Otherwise it isundefined: the thrown error and theerrorevent always carry the samestatusanderrors.messageis the first usable backend message:message(a string, or an array joined by,),detail,error.message,error.details, a stringerror,title, elseHTTP {status}. Network failures havestatus0 and the network error message.toQuery(payload): object to?a=1&b=2. A space is encoded as%20.undefinedandnullvalues are skipped, arrays repeat the key, other objects are JSON-encoded. Returns''when empty.splitId(payload):{ id, ...rest }to{ id, body: rest }.encodeId(id): URL-encodes an id. Throws on an empty id,.or...encodeResource(name): same guard for a resource name (Invalid resource name).resolveHeaders,HeadersResolver: re-exported fromnexdk.
import { HttpError, encodeId, request, splitId, toQuery } from '@nexdk/http'
const { id, body } = splitId({ id: 1, name: 'Alice' })
await request(`https://api.example.com/users/${encodeId(id)}${toQuery({ notify: true })}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body,})
try { await request('https://api.example.com/missing')} catch (error) { if (error instanceof HttpError) console.log(error.status, error.data)}Who uses it
Section titled “Who uses it”nest, symfony, pocketbase, strapi and firebase use createRequest, encodeId, resolveHeaders and, depending on the driver, toQuery and splitId. lomkit uses createRequest, HttpError and resolveHeaders. nhost and supabase do not use it.
Nest validation messages
Section titled “Nest validation messages”A Nest ValidationPipe answers { statusCode, message: string[], error: 'Bad Request' }. When statusCode is a number, error a string and message an array, each string becomes an entry of errors:
<property> must|should|has|is|contains|cannot|does ...is keyed by the first word, if it starts with a lowercase letter and is a plain or dotted name (email must be an email,address.city must be a string).each value in <property> must ...andproperty <name> should not existare keyed by that property.- Anything else (a custom message such as
Email already used) goes under_.
The full message is kept as the value, so { email: ['email must be an email'], _: ['Email already used'] } is a possible result. The rule only reads the text: a custom message that happens to start with a lowercase word and is or has is keyed by that word. error.data keeps the raw body.