@auldrant/api 0.1.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,10 +8,12 @@ We recommend using [Bun](https://bun.sh) to work with @auldrant/api.
8
8
  bun install
9
9
  ```
10
10
 
11
- ## Usage
11
+ ## Quick start
12
12
 
13
13
  ```ts
14
- import { api } from '@auldrant/api';
14
+ import { createApi } from '@auldrant/api';
15
+
16
+ const api = createApi();
15
17
 
16
18
  const result = await api.get<User[]>('/api/users');
17
19
  if (result.ok) {
@@ -21,56 +23,145 @@ if (result.ok) {
21
23
  }
22
24
  ```
23
25
 
24
- ## API
26
+ ## Configured instance
27
+
28
+ Pass options to `createApi` to set defaults that apply to every request:
29
+
30
+ ```ts
31
+ const api = createApi({
32
+ baseUrl: 'https://api.example.com',
33
+ headers: { Authorization: `Bearer ${token}` },
34
+ timeout: 5000,
35
+ });
36
+
37
+ // Relative paths are resolved against baseUrl
38
+ const users = await api.get<User[]>('/users');
39
+ ```
25
40
 
26
- ### Method helpers
41
+ Per-request options always override instance defaults.
27
42
 
28
- All helpers return `Promise<ApiResponse<T>>`.
43
+ ## Method helpers
44
+
45
+ All helpers are methods on the instance returned by `createApi` and return `Promise<ApiResponse<T>>`.
29
46
 
30
47
  | Method | Signature |
31
48
  |--------|-----------|
32
- | `api.get` | `(url, options?)` |
33
- | `api.post` | `(url, body?, options?)` |
34
- | `api.put` | `(url, body?, options?)` |
35
- | `api.patch` | `(url, body?, options?)` |
36
- | `api.delete` | `(url, options?)` |
37
- | `api.head` | `(url, options?)` |
38
- | `api.options` | `(url, options?)` |
49
+ | `.get` | `(url, options?)` |
50
+ | `.post` | `(url, body?, options?)` |
51
+ | `.put` | `(url, body?, options?)` |
52
+ | `.patch` | `(url, body?, options?)` |
53
+ | `.delete` | `(url, options?)` |
54
+ | `.head` | `(url, options?)` |
55
+ | `.options` | `(url, options?)` |
39
56
 
40
57
  Plain objects passed as `body` are automatically serialized to JSON.
41
58
 
42
- ### ApiResponse
59
+ ## ApiResponse
43
60
 
44
61
  ```ts
45
62
  type ApiResponse<T> =
46
- | { ok: true; data: T; status: number }
47
- | { ok: false; data: null; status: number };
63
+ | { ok: true; data: T | null; status: number }
64
+ | { ok: false; data: null; status: number };
48
65
  ```
49
66
 
50
- Use `ok` to narrow the type. Status `0` means a network error or aborted request.
67
+ Use `ok` to narrow the type. Status `0` means a network error, timeout, or aborted request.
68
+
69
+ ## Options reference
70
+
71
+ ### `RequestOptions` (GET, DELETE, HEAD, OPTIONS)
72
+
73
+ | Field | Type | Default | Description |
74
+ |-------|------|---------|-------------|
75
+ | `accept` | `MimeType` | `MimeType.JSON` | Expected response MIME type |
76
+ | `headers` | `HeadersInit` | — | Additional headers |
77
+ | `signal` | `AbortSignal` | — | Cancel the request |
78
+ | `timeout` | `number` | — | Abort after N milliseconds |
79
+ | `retry` | `number` | `0` | Max additional attempts on network failure |
80
+ | `retryDelay` | `number` | `0` | Milliseconds to wait between retries |
51
81
 
52
- ### Options
82
+ ### `RequestBodyOptions` (POST, PUT, PATCH)
83
+
84
+ Extends `RequestOptions` with:
85
+
86
+ | Field | Type | Default | Description |
87
+ |-------|------|---------|-------------|
88
+ | `contentType` | `MimeType` | `MimeType.JSON` | Request body content type |
89
+ | `compression` | `CompressionMethod` | — | Compress the request body |
90
+
91
+ ### `ApiConfig` (createApi)
92
+
93
+ | Field | Type | Default | Description |
94
+ |-------|------|---------|-------------|
95
+ | `baseUrl` | `string \| URL` | — | Prepended to all relative request paths |
96
+ | `headers` | `HeadersInit` | — | Default headers for every request |
97
+ | `accept` | `MimeType` | `MimeType.JSON` | Default Accept type for every request |
98
+ | `timeout` | `number` | — | Default timeout for every request |
99
+
100
+ ## Timeout
53
101
 
54
102
  ```ts
55
- interface RequestOptions {
56
- accept?: MimeType; // Default: MimeType.JSON
57
- headers?: HeadersInit;
58
- signal?: AbortSignal;
59
- }
103
+ // Per-request
104
+ const result = await api.get('/data', { timeout: 3000 });
60
105
 
61
- interface RequestBodyOptions extends RequestOptions {
62
- contentType?: MimeType; // Default: MimeType.JSON
63
- compression?: CompressionMethod; // gzip or deflate
64
- }
106
+ // Or set a default for all requests
107
+ const api = createApi({ timeout: 5000 });
108
+ const result = await api.get('/data'); // uses 5s timeout
65
109
  ```
66
110
 
67
- ## Static Content
111
+ Timed-out requests return `{ ok: false, status: 0 }`.
112
+
113
+ ## Retry
68
114
 
69
- All enums and types are re-exported from the package root:
115
+ Retries apply only to network failures (status 0). Server responses (4xx, 5xx) are never
116
+ retried. The wait between attempts doubles on each retry (exponential backoff).
117
+
118
+ ```ts
119
+ const result = await api.get('/data', {
120
+ retry: 3, // up to 3 additional attempts
121
+ retryDelay: 500, // 500ms → 1000ms → 2000ms
122
+ });
123
+ ```
124
+
125
+ ## Abort
126
+
127
+ ```ts
128
+ const controller = new AbortController();
129
+
130
+ const result = await api.get('/slow', { signal: controller.signal });
131
+
132
+ // Cancel from elsewhere
133
+ controller.abort();
134
+ ```
135
+
136
+ Aborted requests return `{ ok: false, status: 0 }`.
137
+
138
+ ## Compression
139
+
140
+ Compress request bodies before sending (useful for large payloads):
141
+
142
+ ```ts
143
+ const data = largeJsonPayload;
144
+
145
+ await api.post('/ingest', data, {
146
+ compression: CompressionMethod.GZIP,
147
+ });
148
+ ```
70
149
 
71
- - `HttpMethod` — GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
72
- - `HttpStatus` — common HTTP status codes
73
- - `MimeType` — common MIME type strings
74
- - `CompressionMethod` — gzip, deflate
150
+ `FormData` and `URLSearchParams` bodies are always passed through unchanged the browser manages their encoding. Small payloads (under 1 KB) are skipped automatically.
151
+
152
+ ## Exports
153
+
154
+ | Export | Kind | Description |
155
+ |--------|------|-------------|
156
+ | `createApi` | function | Creates a configured API instance |
157
+ | `ApiConfig` | type | Config object for `createApi` |
158
+ | `ApiInstance` | type | Return type of `createApi` |
159
+ | `ApiResponse` | type | Discriminated union response type |
160
+ | `RequestOptions` | type | Options for GET/DELETE/HEAD/OPTIONS |
161
+ | `RequestBodyOptions` | type | Options for POST/PUT/PATCH |
162
+ | `HttpMethod` | enum | GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS |
163
+ | `HttpStatus` | enum | Common HTTP status codes |
164
+ | `MimeType` | enum | Common MIME type strings |
165
+ | `CompressionMethod` | enum | gzip, deflate |
75
166
 
76
167
  [fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
package/dist/api.d.ts CHANGED
@@ -1,59 +1,18 @@
1
- import { ApiResponse, RequestBodyOptions, RequestOptions } from './static.ts';
1
+ import { ApiConfig, ApiInstance } from './static.ts';
2
2
  /**
3
- * RESTful API client with method helpers.
3
+ * Creates a configured API client.
4
4
  *
5
5
  * @example
6
6
  * ```ts
7
- * const result = await api.get<User[]>('/api/users');
7
+ * const api = createApi({
8
+ * baseUrl: 'https://api.example.com',
9
+ * headers: { Authorization: `Bearer ${token}` },
10
+ * timeout: 5000,
11
+ * });
12
+ * const result = await api.get<User[]>('/users');
8
13
  * if (result.ok) {
9
14
  * console.log(result.data); // User[]
10
15
  * }
11
16
  * ```
12
17
  */
13
- export declare const api: {
14
- /**
15
- * Sends a GET request.
16
- * @param url - Request URL
17
- * @param options - Request options (accept, headers, signal)
18
- */
19
- get<T>(url: string | URL, options?: RequestOptions): Promise<ApiResponse<T>>;
20
- /**
21
- * Sends a POST request.
22
- * @param url - Request URL
23
- * @param body - Request body (auto-serialized to JSON if plain object)
24
- * @param options - Request options (contentType, accept, headers, signal, compression)
25
- */
26
- post<T>(url: string | URL, body?: BodyInit | object, options?: RequestBodyOptions): Promise<ApiResponse<T>>;
27
- /**
28
- * Sends a PUT request.
29
- * @param url - Request URL
30
- * @param body - Request body (auto-serialized to JSON if plain object)
31
- * @param options - Request options (contentType, accept, headers, signal, compression)
32
- */
33
- put<T>(url: string | URL, body?: BodyInit | object, options?: RequestBodyOptions): Promise<ApiResponse<T>>;
34
- /**
35
- * Sends a PATCH request.
36
- * @param url - Request URL
37
- * @param body - Request body (auto-serialized to JSON if plain object)
38
- * @param options - Request options (contentType, accept, headers, signal, compression)
39
- */
40
- patch<T>(url: string | URL, body?: BodyInit | object, options?: RequestBodyOptions): Promise<ApiResponse<T>>;
41
- /**
42
- * Sends a DELETE request.
43
- * @param url - Request URL
44
- * @param options - Request options (accept, headers, signal)
45
- */
46
- delete<T>(url: string | URL, options?: RequestOptions): Promise<ApiResponse<T>>;
47
- /**
48
- * Sends a HEAD request. Returns status and headers only (no body).
49
- * @param url - Request URL
50
- * @param options - Request options (headers, signal)
51
- */
52
- head(url: string | URL, options?: RequestOptions): Promise<ApiResponse<null>>;
53
- /**
54
- * Sends an OPTIONS request. Returns allowed methods and CORS info.
55
- * @param url - Request URL
56
- * @param options - Request options (accept, headers, signal)
57
- */
58
- options<T>(url: string | URL, options?: RequestOptions): Promise<ApiResponse<T>>;
59
- };
18
+ export declare function createApi(config?: ApiConfig): ApiInstance;
@@ -1,134 +1,151 @@
1
- var l = /* @__PURE__ */ ((E) => (E[E.OK = 200] = "OK", E[E.CREATED = 201] = "CREATED", E[E.ACCEPTED = 202] = "ACCEPTED", E[E.NO_CONTENT = 204] = "NO_CONTENT", E[E.PARTIAL_CONTENT = 206] = "PARTIAL_CONTENT", E[E.MOVED_PERMANENTLY = 301] = "MOVED_PERMANENTLY", E[E.FOUND = 302] = "FOUND", E[E.SEE_OTHER = 303] = "SEE_OTHER", E[E.NOT_MODIFIED = 304] = "NOT_MODIFIED", E[E.TEMPORARY_REDIRECT = 307] = "TEMPORARY_REDIRECT", E[E.PERMANENT_REDIRECT = 308] = "PERMANENT_REDIRECT", E[E.BAD_REQUEST = 400] = "BAD_REQUEST", E[E.UNAUTHORIZED = 401] = "UNAUTHORIZED", E[E.PAYMENT_REQUIRED = 402] = "PAYMENT_REQUIRED", E[E.FORBIDDEN = 403] = "FORBIDDEN", E[E.NOT_FOUND = 404] = "NOT_FOUND", E[E.METHOD_NOT_ALLOWED = 405] = "METHOD_NOT_ALLOWED", E[E.NOT_ACCEPTABLE = 406] = "NOT_ACCEPTABLE", E[E.PROXY_AUTHENTICATION_REQUIRED = 407] = "PROXY_AUTHENTICATION_REQUIRED", E[E.REQUEST_TIMEOUT = 408] = "REQUEST_TIMEOUT", E[E.CONFLICT = 409] = "CONFLICT", E[E.GONE = 410] = "GONE", E[E.LENGTH_REQUIRED = 411] = "LENGTH_REQUIRED", E[E.PRECONDITION_FAILED = 412] = "PRECONDITION_FAILED", E[E.PAYLOAD_TOO_LARGE = 413] = "PAYLOAD_TOO_LARGE", E[E.URI_TOO_LONG = 414] = "URI_TOO_LONG", E[E.UNSUPPORTED_MEDIA_TYPE = 415] = "UNSUPPORTED_MEDIA_TYPE", E[E.RANGE_NOT_SATISFIABLE = 416] = "RANGE_NOT_SATISFIABLE", E[E.EXPECTATION_FAILED = 417] = "EXPECTATION_FAILED", E[E.IM_A_TEAPOT = 418] = "IM_A_TEAPOT", E[E.MISDIRECTED_REQUEST = 421] = "MISDIRECTED_REQUEST", E[E.UNPROCESSABLE_ENTITY = 422] = "UNPROCESSABLE_ENTITY", E[E.LOCKED = 423] = "LOCKED", E[E.FAILED_DEPENDENCY = 424] = "FAILED_DEPENDENCY", E[E.TOO_EARLY = 425] = "TOO_EARLY", E[E.UPGRADE_REQUIRED = 426] = "UPGRADE_REQUIRED", E[E.PRECONDITION_REQUIRED = 428] = "PRECONDITION_REQUIRED", E[E.TOO_MANY_REQUESTS = 429] = "TOO_MANY_REQUESTS", E[E.REQUEST_HEADER_FIELDS_TOO_LARGE = 431] = "REQUEST_HEADER_FIELDS_TOO_LARGE", E[E.UNAVAILABLE_FOR_LEGAL_REASONS = 451] = "UNAVAILABLE_FOR_LEGAL_REASONS", E[E.INTERNAL_SERVER_ERROR = 500] = "INTERNAL_SERVER_ERROR", E[E.NOT_IMPLEMENTED = 501] = "NOT_IMPLEMENTED", E[E.BAD_GATEWAY = 502] = "BAD_GATEWAY", E[E.SERVICE_UNAVAILABLE = 503] = "SERVICE_UNAVAILABLE", E[E.GATEWAY_TIMEOUT = 504] = "GATEWAY_TIMEOUT", E[E.HTTP_VERSION_NOT_SUPPORTED = 505] = "HTTP_VERSION_NOT_SUPPORTED", E[E.VARIANT_ALSO_NEGOTIATES = 506] = "VARIANT_ALSO_NEGOTIATES", E[E.INSUFFICIENT_STORAGE = 507] = "INSUFFICIENT_STORAGE", E[E.LOOP_DETECTED = 508] = "LOOP_DETECTED", E[E.NOT_EXTENDED = 510] = "NOT_EXTENDED", E[E.NETWORK_AUTHENTICATION_REQUIRED = 511] = "NETWORK_AUTHENTICATION_REQUIRED", E))(l || {}), R = /* @__PURE__ */ ((E) => (E.HTML = "text/html", E.PLAIN = "text/plain", E.CSV = "text/csv", E.CSS = "text/css", E.JAVASCRIPT = "text/javascript", E.JSON = "application/json", E.XML = "application/xml", E.PDF = "application/pdf", E.ZIP = "application/zip", E.GZIP = "application/gzip", E.OCTET_STREAM = "application/octet-stream", E.FORM_DATA = "multipart/form-data", E.URL_ENCODED = "application/x-www-form-urlencoded", E.JPEG = "image/jpeg", E.PNG = "image/png", E.GIF = "image/gif", E.SVG = "image/svg+xml", E.WEBP = "image/webp", E.ICO = "image/x-icon", E.MP4 = "video/mp4", E.WEBM = "video/webm", E.MP3 = "audio/mpeg", E.OGG = "audio/ogg", E.WAV = "audio/wav", E.TAR = "application/x-tar", E))(R || {}), A = /* @__PURE__ */ ((E) => (E.GET = "GET", E.POST = "POST", E.PUT = "PUT", E.DELETE = "DELETE", E.PATCH = "PATCH", E.OPTIONS = "OPTIONS", E.HEAD = "HEAD", E))(A || {}), c = /* @__PURE__ */ ((E) => (E.GZIP = "gzip", E.DEFLATE = "deflate", E))(c || {});
2
- const o = 1024;
3
- function f(E, T) {
4
- return T == null || E instanceof FormData || E instanceof URLSearchParams ? E : E instanceof ReadableStream ? E.pipeThrough(new CompressionStream(T)) : (typeof E == "string" ? E.length : E instanceof Blob ? E.size : E.byteLength) < o ? E : new Blob([E]).stream().pipeThrough(new CompressionStream(T));
1
+ var g = /* @__PURE__ */ ((E) => (E[E.OK = 200] = "OK", E[E.CREATED = 201] = "CREATED", E[E.ACCEPTED = 202] = "ACCEPTED", E[E.NO_CONTENT = 204] = "NO_CONTENT", E[E.PARTIAL_CONTENT = 206] = "PARTIAL_CONTENT", E[E.MOVED_PERMANENTLY = 301] = "MOVED_PERMANENTLY", E[E.FOUND = 302] = "FOUND", E[E.SEE_OTHER = 303] = "SEE_OTHER", E[E.NOT_MODIFIED = 304] = "NOT_MODIFIED", E[E.TEMPORARY_REDIRECT = 307] = "TEMPORARY_REDIRECT", E[E.PERMANENT_REDIRECT = 308] = "PERMANENT_REDIRECT", E[E.BAD_REQUEST = 400] = "BAD_REQUEST", E[E.UNAUTHORIZED = 401] = "UNAUTHORIZED", E[E.PAYMENT_REQUIRED = 402] = "PAYMENT_REQUIRED", E[E.FORBIDDEN = 403] = "FORBIDDEN", E[E.NOT_FOUND = 404] = "NOT_FOUND", E[E.METHOD_NOT_ALLOWED = 405] = "METHOD_NOT_ALLOWED", E[E.NOT_ACCEPTABLE = 406] = "NOT_ACCEPTABLE", E[E.PROXY_AUTHENTICATION_REQUIRED = 407] = "PROXY_AUTHENTICATION_REQUIRED", E[E.REQUEST_TIMEOUT = 408] = "REQUEST_TIMEOUT", E[E.CONFLICT = 409] = "CONFLICT", E[E.GONE = 410] = "GONE", E[E.LENGTH_REQUIRED = 411] = "LENGTH_REQUIRED", E[E.PRECONDITION_FAILED = 412] = "PRECONDITION_FAILED", E[E.PAYLOAD_TOO_LARGE = 413] = "PAYLOAD_TOO_LARGE", E[E.URI_TOO_LONG = 414] = "URI_TOO_LONG", E[E.UNSUPPORTED_MEDIA_TYPE = 415] = "UNSUPPORTED_MEDIA_TYPE", E[E.RANGE_NOT_SATISFIABLE = 416] = "RANGE_NOT_SATISFIABLE", E[E.EXPECTATION_FAILED = 417] = "EXPECTATION_FAILED", E[E.IM_A_TEAPOT = 418] = "IM_A_TEAPOT", E[E.MISDIRECTED_REQUEST = 421] = "MISDIRECTED_REQUEST", E[E.UNPROCESSABLE_ENTITY = 422] = "UNPROCESSABLE_ENTITY", E[E.LOCKED = 423] = "LOCKED", E[E.FAILED_DEPENDENCY = 424] = "FAILED_DEPENDENCY", E[E.TOO_EARLY = 425] = "TOO_EARLY", E[E.UPGRADE_REQUIRED = 426] = "UPGRADE_REQUIRED", E[E.PRECONDITION_REQUIRED = 428] = "PRECONDITION_REQUIRED", E[E.TOO_MANY_REQUESTS = 429] = "TOO_MANY_REQUESTS", E[E.REQUEST_HEADER_FIELDS_TOO_LARGE = 431] = "REQUEST_HEADER_FIELDS_TOO_LARGE", E[E.UNAVAILABLE_FOR_LEGAL_REASONS = 451] = "UNAVAILABLE_FOR_LEGAL_REASONS", E[E.INTERNAL_SERVER_ERROR = 500] = "INTERNAL_SERVER_ERROR", E[E.NOT_IMPLEMENTED = 501] = "NOT_IMPLEMENTED", E[E.BAD_GATEWAY = 502] = "BAD_GATEWAY", E[E.SERVICE_UNAVAILABLE = 503] = "SERVICE_UNAVAILABLE", E[E.GATEWAY_TIMEOUT = 504] = "GATEWAY_TIMEOUT", E[E.HTTP_VERSION_NOT_SUPPORTED = 505] = "HTTP_VERSION_NOT_SUPPORTED", E[E.VARIANT_ALSO_NEGOTIATES = 506] = "VARIANT_ALSO_NEGOTIATES", E[E.INSUFFICIENT_STORAGE = 507] = "INSUFFICIENT_STORAGE", E[E.LOOP_DETECTED = 508] = "LOOP_DETECTED", E[E.NOT_EXTENDED = 510] = "NOT_EXTENDED", E[E.NETWORK_AUTHENTICATION_REQUIRED = 511] = "NETWORK_AUTHENTICATION_REQUIRED", E))(g || {}), r = /* @__PURE__ */ ((E) => (E.HTML = "text/html", E.PLAIN = "text/plain", E.CSV = "text/csv", E.CSS = "text/css", E.JAVASCRIPT = "text/javascript", E.JSON = "application/json", E.XML = "application/xml", E.PDF = "application/pdf", E.ZIP = "application/zip", E.GZIP = "application/gzip", E.OCTET_STREAM = "application/octet-stream", E.FORM_DATA = "multipart/form-data", E.URL_ENCODED = "application/x-www-form-urlencoded", E.JPEG = "image/jpeg", E.PNG = "image/png", E.GIF = "image/gif", E.SVG = "image/svg+xml", E.WEBP = "image/webp", E.ICO = "image/x-icon", E.MP4 = "video/mp4", E.WEBM = "video/webm", E.MP3 = "audio/mpeg", E.OGG = "audio/ogg", E.WAV = "audio/wav", E.TAR = "application/x-tar", E))(r || {}), N = /* @__PURE__ */ ((E) => (E.GET = "GET", E.POST = "POST", E.PUT = "PUT", E.DELETE = "DELETE", E.PATCH = "PATCH", E.OPTIONS = "OPTIONS", E.HEAD = "HEAD", E))(N || {}), B = /* @__PURE__ */ ((E) => (E.GZIP = "gzip", E.DEFLATE = "deflate", E))(B || {});
2
+ const Y = 1024;
3
+ function v(E, A) {
4
+ return A == null || E instanceof FormData || E instanceof URLSearchParams ? E : E instanceof ReadableStream ? E.pipeThrough(new CompressionStream(A)) : (typeof E == "string" ? E.length : E instanceof Blob ? E.size : E.byteLength) < Y ? E : new Blob([E]).stream().pipeThrough(new CompressionStream(A));
5
5
  }
6
- function G(E, T, O, n, r) {
7
- const _ = new Headers(n);
8
- _.has("Accept") || _.set("Accept", T);
9
- const D = r instanceof FormData || r instanceof URLSearchParams;
10
- return E != null && !D && !_.has("Content-Type") && _.set("Content-Type", E), O != null && !_.has("Content-Encoding") && _.set("Content-Encoding", O), _;
6
+ function w(E, A, O, T, R, I) {
7
+ const _ = new Headers(T);
8
+ R && new Headers(R).forEach((e, P) => {
9
+ _.set(P, e);
10
+ }), _.has("Accept") || _.set("Accept", A);
11
+ const i = I instanceof FormData || I instanceof URLSearchParams;
12
+ return E != null && !i && !_.has("Content-Type") && _.set("Content-Type", E), O != null && !_.has("Content-Encoding") && _.set("Content-Encoding", O), _;
11
13
  }
12
- async function a(E, T) {
13
- switch (T) {
14
- case R.JSON:
14
+ async function Q(E, A) {
15
+ switch (A) {
16
+ case r.JSON:
15
17
  return await E.json();
16
- case R.PLAIN:
17
- case R.HTML:
18
- case R.CSV:
19
- case R.XML:
20
- case R.CSS:
21
- case R.JAVASCRIPT:
18
+ case r.PLAIN:
19
+ case r.HTML:
20
+ case r.CSV:
21
+ case r.XML:
22
+ case r.CSS:
23
+ case r.JAVASCRIPT:
22
24
  return await E.text();
23
25
  default:
24
26
  return await E.blob();
25
27
  }
26
28
  }
27
- async function N(E, T, O, n = {}) {
28
- const {
29
- accept: r = R.JSON,
30
- contentType: _ = R.JSON,
31
- compression: D,
32
- headers: C,
33
- signal: i
34
- } = n;
35
- try {
36
- const L = O != null && T !== A.GET && T !== A.HEAD ? f(O, D) : null, U = G(
37
- O != null ? _ : void 0,
38
- r,
39
- O != null ? D : void 0,
40
- C,
41
- O
42
- ), e = {
43
- method: T,
44
- body: L,
45
- headers: U
46
- };
47
- i != null && (e.signal = i);
48
- const I = await fetch(E, e);
49
- return I.ok ? T === A.HEAD || I.status === 204 ? { ok: !0, data: null, status: I.status } : { ok: !0, data: await a(I, r), status: I.status } : { ok: !1, data: null, status: I.status };
50
- } catch {
51
- return { ok: !1, data: null, status: 0 };
52
- }
53
- }
54
- function P(E) {
29
+ function U(E) {
55
30
  return E instanceof FormData || E instanceof URLSearchParams || E instanceof Blob || E instanceof ArrayBuffer || E instanceof ReadableStream || typeof E == "string" ? E : JSON.stringify(E);
56
31
  }
57
- const g = {
58
- /**
59
- * Sends a GET request.
60
- * @param url - Request URL
61
- * @param options - Request options (accept, headers, signal)
62
- */
63
- get(E, T) {
64
- return N(E, A.GET, void 0, T);
65
- },
66
- /**
67
- * Sends a POST request.
68
- * @param url - Request URL
69
- * @param body - Request body (auto-serialized to JSON if plain object)
70
- * @param options - Request options (contentType, accept, headers, signal, compression)
71
- */
72
- post(E, T, O) {
73
- return N(
74
- E,
75
- A.POST,
76
- T != null ? P(T) : void 0,
77
- O
78
- );
79
- },
80
- /**
81
- * Sends a PUT request.
82
- * @param url - Request URL
83
- * @param body - Request body (auto-serialized to JSON if plain object)
84
- * @param options - Request options (contentType, accept, headers, signal, compression)
85
- */
86
- put(E, T, O) {
87
- return N(E, A.PUT, T != null ? P(T) : void 0, O);
88
- },
89
- /**
90
- * Sends a PATCH request.
91
- * @param url - Request URL
92
- * @param body - Request body (auto-serialized to JSON if plain object)
93
- * @param options - Request options (contentType, accept, headers, signal, compression)
94
- */
95
- patch(E, T, O) {
96
- return N(
97
- E,
98
- A.PATCH,
99
- T != null ? P(T) : void 0,
100
- O
101
- );
102
- },
103
- /**
104
- * Sends a DELETE request.
105
- * @param url - Request URL
106
- * @param options - Request options (accept, headers, signal)
107
- */
108
- delete(E, T) {
109
- return N(E, A.DELETE, void 0, T);
110
- },
111
- /**
112
- * Sends a HEAD request. Returns status and headers only (no body).
113
- * @param url - Request URL
114
- * @param options - Request options (headers, signal)
115
- */
116
- head(E, T) {
117
- return N(E, A.HEAD, void 0, T);
118
- },
119
- /**
120
- * Sends an OPTIONS request. Returns allowed methods and CORS info.
121
- * @param url - Request URL
122
- * @param options - Request options (accept, headers, signal)
123
- */
124
- options(E, T) {
125
- return N(E, A.OPTIONS, void 0, T);
32
+ const V = (E) => new Promise((A) => setTimeout(A, E));
33
+ function x(E = {}) {
34
+ async function A(O, T, R, I = {}) {
35
+ const {
36
+ accept: _ = E.accept ?? r.JSON,
37
+ contentType: i = r.JSON,
38
+ compression: e,
39
+ headers: P,
40
+ signal: l,
41
+ retry: f = 0,
42
+ retryDelay: C = 0
43
+ } = I, G = E.baseUrl != null ? new URL(O, E.baseUrl) : O, D = I.timeout ?? E.timeout, o = l != null && D != null ? AbortSignal.any([l, AbortSignal.timeout(D)]) : D != null ? AbortSignal.timeout(D) : l;
44
+ let L = 0;
45
+ for (; ; )
46
+ try {
47
+ const c = R != null && T !== N.GET && T !== N.HEAD ? v(R, e) : null, F = w(
48
+ R != null ? i : void 0,
49
+ _,
50
+ R != null ? e : void 0,
51
+ E.headers,
52
+ P,
53
+ R
54
+ ), a = {
55
+ method: T,
56
+ body: c,
57
+ headers: F
58
+ };
59
+ o != null && (a.signal = o);
60
+ const n = await fetch(G, a);
61
+ return n.ok ? T === N.HEAD || n.status === 204 ? { ok: !0, data: null, status: n.status } : { ok: !0, data: await Q(n, _), status: n.status } : { ok: !1, data: null, status: n.status };
62
+ } catch {
63
+ if (L >= f)
64
+ return { ok: !1, data: null, status: 0 };
65
+ L++, C > 0 && await V(C * 2 ** (L - 1));
66
+ }
126
67
  }
127
- };
68
+ return {
69
+ /**
70
+ * Sends a GET request.
71
+ * @param url - Request URL
72
+ * @param options - Request options (accept, headers, signal, timeout, retry, retryDelay)
73
+ */
74
+ get(O, T) {
75
+ return A(O, N.GET, void 0, T);
76
+ },
77
+ /**
78
+ * Sends a POST request.
79
+ * @param url - Request URL
80
+ * @param body - Request body (auto-serialized to JSON if plain object)
81
+ * @param options - Request options (contentType, accept, headers, signal, compression, timeout, retry, retryDelay)
82
+ */
83
+ post(O, T, R) {
84
+ return A(
85
+ O,
86
+ N.POST,
87
+ T != null ? U(T) : void 0,
88
+ R
89
+ );
90
+ },
91
+ /**
92
+ * Sends a PUT request.
93
+ * @param url - Request URL
94
+ * @param body - Request body (auto-serialized to JSON if plain object)
95
+ * @param options - Request options (contentType, accept, headers, signal, compression, timeout, retry, retryDelay)
96
+ */
97
+ put(O, T, R) {
98
+ return A(
99
+ O,
100
+ N.PUT,
101
+ T != null ? U(T) : void 0,
102
+ R
103
+ );
104
+ },
105
+ /**
106
+ * Sends a PATCH request.
107
+ * @param url - Request URL
108
+ * @param body - Request body (auto-serialized to JSON if plain object)
109
+ * @param options - Request options (contentType, accept, headers, signal, compression, timeout, retry, retryDelay)
110
+ */
111
+ patch(O, T, R) {
112
+ return A(
113
+ O,
114
+ N.PATCH,
115
+ T != null ? U(T) : void 0,
116
+ R
117
+ );
118
+ },
119
+ /**
120
+ * Sends a DELETE request.
121
+ * @param url - Request URL
122
+ * @param options - Request options (accept, headers, signal, timeout, retry, retryDelay)
123
+ */
124
+ delete(O, T) {
125
+ return A(O, N.DELETE, void 0, T);
126
+ },
127
+ /**
128
+ * Sends a HEAD request. Returns status and headers only (no body).
129
+ * @param url - Request URL
130
+ * @param options - Request options (headers, signal, timeout)
131
+ */
132
+ head(O, T) {
133
+ return A(O, N.HEAD, void 0, T);
134
+ },
135
+ /**
136
+ * Sends an OPTIONS request. Returns allowed methods and CORS info.
137
+ * @param url - Request URL
138
+ * @param options - Request options (accept, headers, signal, timeout)
139
+ */
140
+ options(O, T) {
141
+ return A(O, N.OPTIONS, void 0, T);
142
+ }
143
+ };
144
+ }
128
145
  export {
129
- c as CompressionMethod,
130
- A as HttpMethod,
131
- l as HttpStatus,
132
- R as MimeType,
133
- g as api
146
+ B as CompressionMethod,
147
+ N as HttpMethod,
148
+ g as HttpStatus,
149
+ r as MimeType,
150
+ x as createApi
134
151
  };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { api } from './api.ts';
2
- export { type ApiResponse, CompressionMethod, HttpMethod, HttpStatus, MimeType, type RequestBodyOptions, type RequestOptions, } from './static.ts';
1
+ export { createApi } from './api.ts';
2
+ export { type ApiConfig, type ApiInstance, type ApiResponse, CompressionMethod, HttpMethod, HttpStatus, MimeType, type RequestBodyOptions, type RequestOptions, } from './static.ts';
package/dist/static.d.ts CHANGED
@@ -99,6 +99,20 @@ export interface RequestOptions {
99
99
  headers?: HeadersInit;
100
100
  /** AbortSignal to cancel the request. */
101
101
  signal?: AbortSignal;
102
+ /** Abort the request after this many milliseconds (raises a TimeoutError). */
103
+ timeout?: number;
104
+ /**
105
+ * Maximum number of additional attempts on network failure (status 0).
106
+ * Server responses (4xx, 5xx) are never retried — they are intentional responses.
107
+ * Defaults to 0 (no retry).
108
+ */
109
+ retry?: number;
110
+ /**
111
+ * Base delay in milliseconds for exponential backoff between retry attempts.
112
+ * Attempt 1 waits `retryDelay` ms, attempt 2 waits `retryDelay * 2` ms, etc.
113
+ * Defaults to 0 (no delay).
114
+ */
115
+ retryDelay?: number;
102
116
  }
103
117
  /** Options for requests that carry a body (POST, PUT, PATCH). */
104
118
  export interface RequestBodyOptions extends RequestOptions {
@@ -120,3 +134,28 @@ export type ApiResponse<T> = {
120
134
  data: null;
121
135
  status: number;
122
136
  };
137
+ /**
138
+ * Configuration for a {@link createApi} instance.
139
+ * All fields are optional. Per-request options always take precedence over these defaults.
140
+ */
141
+ export interface ApiConfig {
142
+ /** Base URL prepended to all relative request paths. Absolute URLs and URL
143
+ * instances bypass this automatically. */
144
+ baseUrl?: string | URL;
145
+ /** Default headers included in every request. Per-request headers take precedence. */
146
+ headers?: HeadersInit;
147
+ /** Default Accept MIME type for all requests. Defaults to {@link MimeType.JSON}. */
148
+ accept?: MimeType;
149
+ /** Default timeout in milliseconds for all requests. */
150
+ timeout?: number;
151
+ }
152
+ /** A configured API client returned by {@link createApi}. */
153
+ export interface ApiInstance {
154
+ get<T>(url: string | URL, options?: RequestOptions): Promise<ApiResponse<T>>;
155
+ post<T>(url: string | URL, body?: BodyInit | object, options?: RequestBodyOptions): Promise<ApiResponse<T>>;
156
+ put<T>(url: string | URL, body?: BodyInit | object, options?: RequestBodyOptions): Promise<ApiResponse<T>>;
157
+ patch<T>(url: string | URL, body?: BodyInit | object, options?: RequestBodyOptions): Promise<ApiResponse<T>>;
158
+ delete<T>(url: string | URL, options?: RequestOptions): Promise<ApiResponse<T>>;
159
+ head(url: string | URL, options?: RequestOptions): Promise<ApiResponse<null>>;
160
+ options<T>(url: string | URL, options?: RequestOptions): Promise<ApiResponse<T>>;
161
+ }
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@auldrant/api",
3
- "version": "0.1.1",
3
+ "version": "1.0.1",
4
4
  "description": "Simple library for working with APIs",
5
5
  "author": "Colonel Jade <colonel.jade@proton.me> (https://github.com/coloneljade/)",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/coloneljade/auldrant-api#readme",
8
- "repository": "github:coloneljade/auldrant-api",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/coloneljade/auldrant-api.git"
11
+ },
9
12
  "bugs": {
10
13
  "url": "https://github.com/coloneljade/auldrant-api/issues",
11
14
  "email": "colonel.jade@proton.me"