@randajan/api-kit 1.2.0 → 3.0.0

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Jan Blaha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md CHANGED
@@ -1,66 +1,351 @@
1
- # @randajan/api-kit
2
-
3
- [![NPM](https://img.shields.io/npm/v/@randajan/api-kit.svg)](https://www.npmjs.com/package/@randajan/api-kit) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
4
-
5
- JavaScript library for rendering structured data in HTML format. It supports automatic detection of tabular structures and visualization of deeply nested objects. It is ideal for debugging, admin interfaces, or displaying API responses.
6
-
7
- ## Installation
8
-
9
-
10
- ## Usage
11
-
12
- ### Server
13
- You can use the library as an ES module:
14
-
15
- ```javascript
16
- import createApi from "@randajan/api-kit/server";
17
-
18
- const apiReponder = ({
19
- code:0,
20
- isAsync:false,
21
- timestamp:false,
22
- trait:(opt)=>opt,
23
- onOk:(resp, opt)=>{},
24
- onError:(resp, opt)=>{ },
25
- throwError:false
26
- });
27
-
28
-
29
- ```
30
-
31
- ### Client
32
- You can use the library as an ES module:
33
-
34
- ```javascript
35
- import createFetch from "@randajan/api-kit/client";
36
-
37
- const apiFetch = createFetch({
38
- code:0,
39
- url:"",
40
- fetch:globalThis.fetch,
41
- query:{},
42
- parseHeaders:false, //if true it will parse response headers
43
- resultOnly:false, //if true it will return only result
44
- timestamp:false, //if true it will calculate timestamps
45
- parseBody:(body)=>body, //custom parser for non api-kit server fetches only
46
- trait:(opt)=>opt,
47
- onOk:(resp, opt)=>{},
48
- onError:(resp, opt)=>{ },
49
- requestType:"json", // json or form, the 'type' property is fallback
50
- responseType:"json", // json or form, the 'type' property is fallback
51
- throwError:false
52
- });
53
-
54
-
55
- ```
56
-
57
-
58
-
59
- ## Support
60
-
61
- If you have any questions or suggestions for improvements, feel free to open an issue in the repository.
62
-
63
-
64
- ## License
65
-
66
- MIT © [randajan](https://github.com/randajan)
1
+ # @randajan/api-kit
2
+
3
+ [![NPM](https://img.shields.io/npm/v/@randajan/api-kit.svg)](https://www.npmjs.com/package/@randajan/api-kit) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
4
+
5
+ `@randajan/api-kit` wraps server handlers and client `fetch` calls into one predictable response and error contract. It keeps successful results, known HTTP errors, foreign API responses, decode failures, timeouts, and aborts distinguishable without forcing every call site to use `try/catch`.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ npm install @randajan/api-kit
11
+ ```
12
+
13
+ ## Imports
14
+
15
+ ```js
16
+ import createApi, { HttpError } from "@randajan/api-kit/server";
17
+ import createFetch, { FetchError } from "@randajan/api-kit/client";
18
+ ```
19
+
20
+ For shared packages or full-stack code:
21
+
22
+ ```js
23
+ import { createApi, createFetch, HttpError, FetchError } from "@randajan/api-kit";
24
+ ```
25
+
26
+ `HttpError` is exported by the server subpath and the root package. The client subpath exports `FetchError`.
27
+
28
+ ## Quick Start
29
+
30
+ ### Server
31
+
32
+ Do: wrap handler logic with `api(...)`, then send the returned body with `body.statusCode`.
33
+
34
+ ```js
35
+ import express from "express";
36
+ import createApi, { HttpError } from "@randajan/api-kit/server";
37
+
38
+ const app = express();
39
+ const api = createApi({ timetrack:true });
40
+
41
+ app.get("/users/:id", (req, res) => {
42
+ const body = api.code(10, () => {
43
+ if (!req.params.id) {
44
+ throw HttpError.code(1, "Missing user id", 400);
45
+ }
46
+
47
+ return { id:req.params.id, name:"Ada" };
48
+ });
49
+
50
+ res.status(body.statusCode).json(body);
51
+ });
52
+ ```
53
+
54
+ Returns an api-kit response body containing a package marker, `statusCode`, and either `result` or `error`.
55
+
56
+ ### Client
57
+
58
+ Do: create a configured fetch wrapper and read `isOk`, `result`, and `error`.
59
+
60
+ ```js
61
+ import createFetch from "@randajan/api-kit/client";
62
+
63
+ const api = createFetch({
64
+ url:"https://api.example.com/",
65
+ timeout:10000
66
+ });
67
+
68
+ const body = await api.get("users/42");
69
+
70
+ if (body.isOk) {
71
+ console.log(body.result);
72
+ } else {
73
+ console.error(body.statusCode, body.error);
74
+ }
75
+ ```
76
+
77
+ Returns a frozen response object unless `resultOnly:true` or `throwError:true` changes the flow.
78
+
79
+ ## Response Contract
80
+
81
+ Client calls return this normalized shape:
82
+
83
+ ```js
84
+ {
85
+ "@randajan/api-kit":"3.0.0", // present only for api-kit server responses
86
+ isOk:true, // true when there is no error
87
+ isRemote:true, // true when fetch received an HTTP Response
88
+ isApiKit:true, // true when the response had the api-kit marker
89
+ apiKitDiff:undefined, // "major", "minor", or "patch" when versions differ
90
+ statusCode:200,
91
+ result:{},
92
+ error:undefined,
93
+ headers:undefined, // present when parseHeaders:true
94
+ time:undefined // present when timetrack:true
95
+ }
96
+ ```
97
+
98
+ Common flag combinations:
99
+
100
+ | `isOk` | `isRemote` | `isApiKit` | Meaning |
101
+ | --- | --- | --- | --- |
102
+ | `true` | `true` | `true` | Successful api-kit response |
103
+ | `false` | `true` | `true` | Api-kit error response |
104
+ | `true` | `true` | `false` | Successful foreign API response |
105
+ | `false` | `true` | `false` | Foreign HTTP/API error or remote decode/read error |
106
+ | `false` | `false` | `false` | Local transport error, timeout, abort, or fetch failure |
107
+
108
+ Notes:
109
+
110
+ - `statusCode` is taken from `error.httpStatusCode` when the error has one, otherwise from the HTTP response.
111
+ - Empty successful responses such as `204`, `HEAD 200`, and `304` return `isOk:true` with `result:undefined`.
112
+ - Non-api-kit successful responses are returned as `result` after response decoding.
113
+
114
+ ## Errors
115
+
116
+ ### Known Server Errors
117
+
118
+ Use `HttpError.code(code, message, httpStatusCode, options)` for errors that should cross the API boundary with a known HTTP status.
119
+
120
+ ```js
121
+ throw HttpError.code(20, "Email already exists", 409, {
122
+ detail:{ field:"email" },
123
+ cause:error
124
+ });
125
+ ```
126
+
127
+ `detail` is public structured data. `cause` is hidden by default unless the server option `exposeCause:true` is enabled.
128
+
129
+ ### Local Client Errors
130
+
131
+ Client transport failures are represented as `FetchError` instances:
132
+
133
+ ```js
134
+ import createFetch, { FetchError } from "@randajan/api-kit/client";
135
+
136
+ const api = createFetch({
137
+ normalizeError(error) {
138
+ if (error.name === "QuotaExceededError") {
139
+ return FetchError.code(90, "Client quota exceeded", { cause:error });
140
+ }
141
+ }
142
+ });
143
+ ```
144
+
145
+ Return a `FetchError` from client `normalizeError`. Returning anything else falls back to an `Unknown` `FetchError`.
146
+
147
+ ### Throwing Instead Of Returning
148
+
149
+ By default, api-kit returns response objects. Use `throwError:true` when you want errors to be thrown after normalization.
150
+
151
+ ```js
152
+ const api = createFetch({ url:"https://api.example.com/", throwError:true });
153
+
154
+ try {
155
+ const body = await api.get("users/42");
156
+ console.log(body.result);
157
+ } catch (error) {
158
+ console.error(error.code, error.message);
159
+ }
160
+ ```
161
+
162
+ The same option exists on the server wrapper.
163
+
164
+ ### Normalizing Known Errors
165
+
166
+ Use `normalizeError(error, opt)` to convert framework or application errors into api-kit errors before the default fallback runs.
167
+
168
+ ```js
169
+ const api = createApi({
170
+ normalizeError(error) {
171
+ if (error.name === "ValidationError") {
172
+ return HttpError.code(30, "Validation failed", 422, {
173
+ detail:error.fields,
174
+ cause:error
175
+ });
176
+ }
177
+ }
178
+ });
179
+ ```
180
+
181
+ Server `normalizeError` should return `HttpError`. Client `normalizeError` should return `FetchError`. If the hook throws or returns another value, api-kit falls back to `Unknown`.
182
+
183
+ ### Exposing Cause
184
+
185
+ Use `exposeCause:true` only for trusted environments. It can expose server-side error messages and stacks in the serialized API response.
186
+
187
+ ```js
188
+ const api = createApi({ exposeCause:process.env.NODE_ENV !== "production" });
189
+ ```
190
+
191
+ ## Options
192
+
193
+ Options can be passed to the constructor and overridden per call. Client options are shallow-merged, with `body`, `params`, and `headers` merged separately.
194
+
195
+ ### Shared Options
196
+
197
+ | Option | Type | Description |
198
+ | --- | --- | --- |
199
+ | `timetrack` | `boolean` | Adds timing metadata. Server records handler time; client can calculate network timing from server time. |
200
+ | `trait` | `(opt) => opt` | Last chance to inspect or mutate options before execution. |
201
+ | `onOk` | `(body, opt) => void` | Called when normalized response has no `error`. |
202
+ | `onError` | `(body, opt) => void` | Called when normalized response has `error`. |
203
+ | `throwError` | `boolean` | Throws `body.error` instead of returning an error response. |
204
+ | `normalizeError` | `(error, opt) => Error` | Converts known errors before api-kit creates its default unknown error. |
205
+
206
+ ### Server Options
207
+
208
+ | Option | Type | Description |
209
+ | --- | --- | --- |
210
+ | `code` | `string` or `number` | Prefix for server error codes. Also set by `api.code(code, exe, opt)`. |
211
+ | `isAsync` | `boolean` | Forces async handler execution. Promise results are also detected without this option. |
212
+ | `exposeCause` | `boolean` | Includes serialized `cause` in `HttpError.toJSON()`. |
213
+
214
+ ### Client Options
215
+
216
+ | Option | Type | Description |
217
+ | --- | --- | --- |
218
+ | `fetch` | `Function` | Custom fetch implementation. Constructor-only; removed from stored config. |
219
+ | `url` | `string` | Base or prefix URL joined with the call URL. |
220
+ | `params` | `object` | Query params appended to the final URL. `null` and `undefined` values are skipped. |
221
+ | `method` | `string` | HTTP method. Set automatically by `.get()`, `.post()`, `.put()`, `.delete()`, `.patch()`, `.head()`, and `.options()`. |
222
+ | `body` | `object` or any | Request body before encoding. |
223
+ | `headers` | `object` | Request headers. api-kit also sets `Content-Type` and `Accept` from the selected type. |
224
+ | `requestType` | `string` | Encoder for request body. Native values: `json`, `form`. |
225
+ | `responseType` | `string` | Decoder for response body. Native values: `json`, `form`. |
226
+ | `type` | `string` | Fallback for both `requestType` and `responseType`. |
227
+ | `types` | `object` | Custom type definitions: `{ mime, encode, decode }`. |
228
+ | `timeout` | `number` | Timeout in milliseconds. Creates an abort signal when greater than `0`. |
229
+ | `isAbortable` | `boolean` | Adds `.abort()` to the returned promise. |
230
+ | `parseHeaders` | `boolean` | Adds frozen parsed response headers to `body.headers`. |
231
+ | `resultOnly` | `boolean` | Returns only `body.result` after normalization. |
232
+
233
+ ### Runtime Fields
234
+
235
+ These fields are created or changed by api-kit during execution. Do not set them manually unless you are intentionally extending internals.
236
+
237
+ | Field | Created by | Description |
238
+ | --- | --- | --- |
239
+ | `startAt` | `start(opt)` | Timestamp used by `timetrack`. |
240
+ | `decodeBody` | `attachBodyDecoder()` | Response body decoder selected from `responseType` or `type`. |
241
+ | `abortController` | client prepare step | Controller used by timeout and abortable requests. |
242
+ | `signal` | client prepare step | Fetch abort signal. |
243
+ | `timeoutId` | client prepare step | Timer cleared after the request settles. |
244
+
245
+ The client also mutates prepared `url`, `method`, `headers`, and encoded `body`.
246
+
247
+ ## Recipes
248
+
249
+ ### POST JSON
250
+
251
+ Do:
252
+
253
+ ```js
254
+ const body = await api.post("users", {
255
+ body:{ name:"Ada" }
256
+ });
257
+ ```
258
+
259
+ Returns an api-kit response object. JSON is the default request and response type.
260
+
261
+ ### POST Form Data
262
+
263
+ Do:
264
+
265
+ ```js
266
+ const body = await api.post("login", {
267
+ requestType:"form",
268
+ body:{ username:"ada", password:"secret" }
269
+ });
270
+ ```
271
+
272
+ Notes: `requestType:"form"` uses `application/x-www-form-urlencoded`.
273
+
274
+ ### Timeout And Abort
275
+
276
+ Do:
277
+
278
+ ```js
279
+ const request = api.get("slow", {
280
+ timeout:1000,
281
+ isAbortable:true
282
+ });
283
+
284
+ request.abort();
285
+
286
+ const body = await request;
287
+ ```
288
+
289
+ Returns a local transport error response with `isRemote:false` when the request is aborted before an HTTP response is received.
290
+
291
+ ### Parse Response Headers
292
+
293
+ Do:
294
+
295
+ ```js
296
+ const body = await api.get("users/42", {
297
+ parseHeaders:true
298
+ });
299
+
300
+ console.log(body.headers);
301
+ ```
302
+
303
+ Returns `body.headers` as a frozen plain object.
304
+
305
+ ### Custom Content Type
306
+
307
+ Do:
308
+
309
+ ```js
310
+ const api = createFetch({
311
+ url:"https://api.example.com/",
312
+ responseType:"text",
313
+ types:{
314
+ text:{
315
+ mime:"text/plain",
316
+ encode:value=>String(value),
317
+ decode:text=>text
318
+ }
319
+ }
320
+ });
321
+
322
+ const body = await api.get("status");
323
+ ```
324
+
325
+ Notes: Custom request types require `mime` and `encode`. Custom response types require `mime` and `decode`.
326
+
327
+ ### Map A Known App Error
328
+
329
+ Do:
330
+
331
+ ```js
332
+ class NotFoundError extends Error {}
333
+
334
+ const api = createApi({
335
+ normalizeError(error) {
336
+ if (error instanceof NotFoundError) {
337
+ return HttpError.code(40, "Not found", 404, { cause:error });
338
+ }
339
+ }
340
+ });
341
+ ```
342
+
343
+ Returns a regular api-kit error response with HTTP status `404`.
344
+
345
+ ## Support
346
+
347
+ Open an issue in the repository for questions, bugs, or improvement ideas.
348
+
349
+ ## License
350
+
351
+ MIT (c) [randajan](https://github.com/randajan)