@molecule/app-http 1.0.0 → 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.
Files changed (2) hide show
  1. package/README.md +536 -0
  2. package/package.json +9 -8
package/README.md ADDED
@@ -0,0 +1,536 @@
1
+ <!--
2
+ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
+ Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
+ Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
+ To change this document, edit the module-level JSDoc in src/index.ts.
6
+ Generated: 2026-08-04T01:50:58.464Z
7
+ -->
8
+
9
+ # @molecule/app-http
10
+
11
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
+ > It is written to be read by coding agents as much as by people, and is generated from this
13
+ > package's source — edit `src/index.ts` JSDoc, not this file.
14
+
15
+ Client HTTP interface for molecule.dev.
16
+
17
+ Provides a unified HTTP client API that works across different
18
+ HTTP libraries (fetch, axios, ky, etc.).
19
+
20
+ ## Quick Start
21
+
22
+ ```tsx
23
+ // In a React component, get the configured client from context and call it.
24
+ // The hook is exported by the framework binding (@molecule/app-react), not
25
+ // this core package — never construct your own fetch/axios client.
26
+ import { useHttpClient } from '@molecule/app-react'
27
+
28
+ function Plants() {
29
+ const http = useHttpClient()
30
+ const load = async () => {
31
+ const res = await http.get<Plant[]>('/plants') // baseURL ('/api') is prepended
32
+ setPlants(res.data)
33
+ }
34
+ // http.post(url, body), http.put, http.delete are also available.
35
+ }
36
+ ```
37
+
38
+ ## Type
39
+
40
+ `core`
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ npm install @molecule/app-http @molecule/app-bond @molecule/app-i18n @molecule/app-logger
46
+ ```
47
+
48
+ ## API
49
+
50
+ ### Interfaces
51
+
52
+ #### `FullRequestConfig`
53
+
54
+ Full request configuration including method and URL.
55
+
56
+ ```typescript
57
+ interface FullRequestConfig extends RequestConfig {
58
+ /**
59
+ * HTTP method.
60
+ */
61
+ method: HttpMethod
62
+
63
+ /**
64
+ * Request URL (can be relative or absolute).
65
+ */
66
+ url: string
67
+
68
+ /**
69
+ * Request body data.
70
+ */
71
+ data?: unknown
72
+ }
73
+ ```
74
+
75
+ #### `HttpClient`
76
+
77
+ HTTP client interface.
78
+
79
+ All HTTP providers must implement this interface.
80
+
81
+ ```typescript
82
+ interface HttpClient {
83
+ /**
84
+ * Base URL for all requests.
85
+ */
86
+ baseURL: string
87
+
88
+ /**
89
+ * Default headers for all requests.
90
+ */
91
+ defaultHeaders: Record<string, string>
92
+
93
+ /**
94
+ * Makes a generic HTTP request.
95
+ */
96
+ request<T = unknown>(config: FullRequestConfig): Promise<HttpResponse<T>>
97
+
98
+ /**
99
+ * Makes a GET request.
100
+ */
101
+ get<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>
102
+
103
+ /**
104
+ * Makes a POST request.
105
+ */
106
+ post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
107
+
108
+ /**
109
+ * Makes a PUT request.
110
+ */
111
+ put<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
112
+
113
+ /**
114
+ * Makes a PATCH request.
115
+ */
116
+ patch<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
117
+
118
+ /**
119
+ * Makes a DELETE request.
120
+ */
121
+ delete<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>
122
+
123
+ /**
124
+ * Adds a request interceptor.
125
+ * Returns a function to remove the interceptor.
126
+ */
127
+ addRequestInterceptor(interceptor: RequestInterceptor): () => void
128
+
129
+ /**
130
+ * Adds a response interceptor.
131
+ * Returns a function to remove the interceptor.
132
+ */
133
+ addResponseInterceptor(interceptor: ResponseInterceptor): () => void
134
+
135
+ /**
136
+ * Adds an error interceptor.
137
+ * Returns a function to remove the interceptor.
138
+ */
139
+ addErrorInterceptor(interceptor: ErrorInterceptor): () => void
140
+
141
+ /**
142
+ * Sets the authorization token.
143
+ */
144
+ setAuthToken(token: string | null): void
145
+
146
+ /**
147
+ * Returns the current authorization token, or `null` if not set.
148
+ */
149
+ getAuthToken(): string | null
150
+
151
+ /**
152
+ * Registers a handler for authentication errors (401).
153
+ *
154
+ * @returns An unsubscribe function.
155
+ */
156
+ onAuthError(handler: () => void): () => void
157
+ }
158
+ ```
159
+
160
+ #### `HttpClientConfig`
161
+
162
+ HTTP client configuration.
163
+
164
+ ```typescript
165
+ interface HttpClientConfig {
166
+ /**
167
+ * Base URL for all requests.
168
+ */
169
+ baseURL?: string
170
+
171
+ /**
172
+ * Default headers for all requests.
173
+ */
174
+ defaultHeaders?: Record<string, string>
175
+
176
+ /**
177
+ * Default timeout in milliseconds.
178
+ */
179
+ timeout?: number
180
+
181
+ /**
182
+ * Whether to include credentials by default.
183
+ */
184
+ withCredentials?: boolean
185
+ }
186
+ ```
187
+
188
+ #### `HttpResponse`
189
+
190
+ Parsed HTTP response with status code, headers, and typed body data.
191
+
192
+ ```typescript
193
+ interface HttpResponse<T = unknown> {
194
+ /**
195
+ * Response data.
196
+ */
197
+ data: T
198
+
199
+ /**
200
+ * HTTP status code.
201
+ */
202
+ status: number
203
+
204
+ /**
205
+ * HTTP status text.
206
+ */
207
+ statusText: string
208
+
209
+ /**
210
+ * Response headers.
211
+ */
212
+ headers: Record<string, string>
213
+
214
+ /**
215
+ * Original request config.
216
+ */
217
+ config: FullRequestConfig
218
+ }
219
+ ```
220
+
221
+ #### `RequestConfig`
222
+
223
+ HTTP request options (headers, query params, timeout, credentials, response type, abort signal).
224
+
225
+ ```typescript
226
+ interface RequestConfig {
227
+ /**
228
+ * Request headers.
229
+ */
230
+ headers?: Record<string, string>
231
+
232
+ /**
233
+ * Query parameters.
234
+ */
235
+ params?: Record<string, string | number | boolean | undefined>
236
+
237
+ /**
238
+ * Request timeout in milliseconds.
239
+ */
240
+ timeout?: number
241
+
242
+ /**
243
+ * Whether to include credentials (cookies).
244
+ */
245
+ withCredentials?: boolean
246
+
247
+ /**
248
+ * Response type.
249
+ */
250
+ responseType?: 'json' | 'text' | 'blob' | 'arraybuffer'
251
+
252
+ /**
253
+ * Abort signal for cancellation.
254
+ */
255
+ signal?: AbortSignal
256
+
257
+ /**
258
+ * Request body data.
259
+ */
260
+ data?: unknown
261
+
262
+ /**
263
+ * Custom request options (implementation-specific).
264
+ */
265
+ options?: Record<string, unknown>
266
+ }
267
+ ```
268
+
269
+ ### Types
270
+
271
+ #### `ErrorInterceptor`
272
+
273
+ Intercepts HTTP errors to transform, retry, or rethrow them.
274
+ Throwing from this interceptor propagates the error to the caller.
275
+
276
+ ```typescript
277
+ type ErrorInterceptor = (error: HttpError) => HttpError | Promise<HttpError> | never
278
+ ```
279
+
280
+ #### `HttpMethod`
281
+
282
+ HTTP request method.
283
+
284
+ ```typescript
285
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'
286
+ ```
287
+
288
+ #### `RequestInterceptor`
289
+
290
+ Intercepts outgoing requests to modify headers, URL, or body
291
+ before the request is sent.
292
+
293
+ ```typescript
294
+ type RequestInterceptor = (
295
+ config: FullRequestConfig,
296
+ ) => FullRequestConfig | Promise<FullRequestConfig>
297
+ ```
298
+
299
+ #### `ResponseInterceptor`
300
+
301
+ Intercepts incoming responses to transform data, check status,
302
+ or perform side effects before the response reaches the caller.
303
+
304
+ ```typescript
305
+ type ResponseInterceptor<T = unknown> = (
306
+ response: HttpResponse<T>,
307
+ ) => HttpResponse<T> | Promise<HttpResponse<T>>
308
+ ```
309
+
310
+ ### Classes
311
+
312
+ #### `HttpError`
313
+
314
+ HTTP error with response details.
315
+
316
+ ### Functions
317
+
318
+ #### `createFetchClient(config)`
319
+
320
+ Creates a fetch-based HTTP client using the native Fetch API.
321
+
322
+ Supports request/response/error interceptors, automatic JSON
323
+ serialization, auth token injection, timeout via AbortController,
324
+ and 401 error handler hooks.
325
+
326
+ ```typescript
327
+ function createFetchClient(config?: HttpClientConfig): HttpClient
328
+ ```
329
+
330
+ - `config` — Client configuration including baseURL, default headers, timeout, and credentials.
331
+
332
+ **Returns:** A fully configured `HttpClient` instance.
333
+
334
+ #### `del(url, config)`
335
+
336
+ Makes a DELETE request using the bonded HTTP client.
337
+
338
+ ```typescript
339
+ function del(url: string, config?: RequestConfig): Promise<HttpResponse<T>>
340
+ ```
341
+
342
+ - `url` — The request URL.
343
+ - `config` — Optional request configuration.
344
+
345
+ **Returns:** The HTTP response with typed data.
346
+
347
+ #### `get(url, config)`
348
+
349
+ Makes a GET request using the bonded HTTP client.
350
+
351
+ ```typescript
352
+ function get(url: string, config?: RequestConfig): Promise<HttpResponse<T>>
353
+ ```
354
+
355
+ - `url` — The request URL (relative to baseURL if configured).
356
+ - `config` — Optional request configuration (headers, params, timeout).
357
+
358
+ **Returns:** The HTTP response with typed data.
359
+
360
+ #### `getClient()`
361
+
362
+ Retrieves the bonded HTTP client. If none is bonded, automatically
363
+ creates a default fetch-based client.
364
+
365
+ ```typescript
366
+ function getClient(): HttpClient
367
+ ```
368
+
369
+ **Returns:** The active HTTP client instance.
370
+
371
+ #### `patch(url, data, config)`
372
+
373
+ Makes a PATCH request using the bonded HTTP client.
374
+
375
+ ```typescript
376
+ function patch(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
377
+ ```
378
+
379
+ - `url` — The request URL.
380
+ - `data` — The request body (partial update).
381
+ - `config` — Optional request configuration.
382
+
383
+ **Returns:** The HTTP response with typed data.
384
+
385
+ #### `post(url, data, config)`
386
+
387
+ Makes a POST request using the bonded HTTP client.
388
+
389
+ ```typescript
390
+ function post(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
391
+ ```
392
+
393
+ - `url` — The request URL.
394
+ - `data` — The request body.
395
+ - `config` — Optional request configuration.
396
+
397
+ **Returns:** The HTTP response with typed data.
398
+
399
+ #### `put(url, data, config)`
400
+
401
+ Makes a PUT request using the bonded HTTP client.
402
+
403
+ ```typescript
404
+ function put(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
405
+ ```
406
+
407
+ - `url` — The request URL.
408
+ - `data` — The request body.
409
+ - `config` — Optional request configuration.
410
+
411
+ **Returns:** The HTTP response with typed data.
412
+
413
+ #### `setClient(client)`
414
+
415
+ Registers an HTTP client as the active singleton.
416
+
417
+ ```typescript
418
+ function setClient(client: HttpClient): void
419
+ ```
420
+
421
+ - `client` — The HTTP client implementation to bond.
422
+
423
+ #### `unwrapList(res)`
424
+
425
+ Normalize an unknown response body into a typed array.
426
+
427
+ Accepts:
428
+
429
+ - a bare array → returned as-is (cast)
430
+ - `{ data: T[] }` envelope → the inner array
431
+ - `HttpResponse<T[]>` (i.e. `{ data: T[], status, ... }`) → the inner array
432
+ - `HttpResponse<{ data: T[] }>` (the response of an envelope-returning
433
+ endpoint as it arrives from `@molecule/app-http`'s `HttpClient`) → the
434
+ doubly-nested inner array
435
+ - `HttpResponse<{ data: T[], total, limit, offset }>` (a RICH pagination
436
+ envelope from a pre-built `@molecule/api-resource-*` list endpoint) → the
437
+ inner `data` array (the numeric `total`/`limit`/`offset` make the shape
438
+ unambiguous, so callers can pass the whole HttpResponse and still get the rows)
439
+ - anything else → `[]`
440
+
441
+ Callers commonly pass either the raw JSON body (e.g. from `fetch().then(r =>
442
+ r.json())`) or the `HttpResponse` returned by `useHttpClient().get(...)`.
443
+ Both shapes are handled here so pages don't have to remember to call
444
+ `unwrapList(res.data)` vs `unwrapList(res)`.
445
+
446
+ ```typescript
447
+ function unwrapList(res: unknown): T[]
448
+ ```
449
+
450
+ - `res` — Raw response body OR an `HttpResponse` envelope from `@molecule/app-http`.
451
+
452
+ **Returns:** A typed array `T[]`; never `null`/`undefined`.
453
+
454
+ #### `unwrapSingle(res)`
455
+
456
+ Normalize an unknown response body into a single typed resource.
457
+
458
+ Accepts:
459
+
460
+ - a non-empty plain object → returned as-is (cast)
461
+ - `{ data: T }` envelope → the inner value
462
+ - `{ data: null }`, `{ data: undefined }`, `{ data: [] }`, or
463
+ `{ data: {} }` (mock-server's no-match shape) → `null`
464
+ - an empty object `{}` → `null`
465
+ - arrays, primitives, `null`, `undefined` → `null`
466
+
467
+ The "envelope contains an array → null" branch handles the case
468
+ where the mock server returns `[]` for unmatched endpoints but the
469
+ caller expects a single resource.
470
+
471
+ ```typescript
472
+ function unwrapSingle(res: unknown): T | null
473
+ ```
474
+
475
+ - `res` — Raw response body (e.g. `HttpResponse.data` from `@molecule/app-http`).
476
+
477
+ **Returns:** The typed resource `T`, or `null` when the response shape indicates "no resource" (including the various empty envelopes above).
478
+
479
+ ### Constants
480
+
481
+ #### `fetchClient`
482
+
483
+ Pre-created fetch client for environments where `fetch` is available.
484
+ `null` in environments without a global `fetch`.
485
+
486
+ ```typescript
487
+ const fetchClient: HttpClient | null
488
+ ```
489
+
490
+ ## Available Providers
491
+
492
+ | Provider | Package |
493
+ | -------- | -------------------------- |
494
+ | Axios | `@molecule/app-http-axios` |
495
+
496
+ ## Injection Notes
497
+
498
+ ### Requirements
499
+
500
+ Peer dependencies:
501
+
502
+ - `@molecule/app-bond` ^1.0.1
503
+ - `@molecule/app-i18n` ^1.0.1
504
+ - `@molecule/app-logger` ^1.0.1
505
+
506
+ ### Runtime Dependencies
507
+
508
+ - `@molecule/app-bond`
509
+ - `@molecule/app-i18n`
510
+ - `@molecule/app-logger`
511
+
512
+ Make ALL API calls through this client (via the framework hook `useHttpClient()` in
513
+ React / the Vue composable) — it carries the configured `baseURL`, auth headers, and
514
+ interceptors. Do NOT call `fetch()` / `axios` directly in components — that bypasses auth
515
+
516
+ - base-URL config and breaks when the transport is swapped.
517
+
518
+ Two mistakes that break in preview/production (seen in real imported apps):
519
+
520
+ - **Pass RELATIVE paths; never a hardcoded host.** Use `'/plants'` (the `baseURL` `'/api'`
521
+ is prepended), NOT `'/api/plants'`, and NEVER an absolute dev URL like
522
+ `'http://localhost:4000/api/…'`. A hardcoded `localhost`/host works on the author's
523
+ machine, then fails cross-origin (CORS) in the preview and points at the wrong server in
524
+ production. The base URL is configured ONCE (via `setClient`), not per call.
525
+ - **The client is PUBLIC — never put a secret in it.** Anything the browser sends (an API
526
+ key, a service-role / `sk_…` key, a signing secret) is visible to every user. Secrets
527
+ stay in YOUR API; the browser calls your API and the API uses the secret server-side.
528
+ Only a publishable/public key may ever be client-side.
529
+
530
+ Auth (the bearer token / session cookie) is attached by the client's interceptors — do not
531
+ read a token from `localStorage` or hand-attach it (the token is memory-only; see the user
532
+ resource).
533
+
534
+ ## Translations
535
+
536
+ Translation strings are provided by `@molecule/app-locales-http`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/app-http",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Client HTTP interface for molecule.dev",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,8 @@
17
17
  }
18
18
  },
19
19
  "files": [
20
- "dist"
20
+ "dist",
21
+ "README.md"
21
22
  ],
22
23
  "keywords": [
23
24
  "molecule",
@@ -27,14 +28,14 @@
27
28
  ],
28
29
  "license": "Apache-2.0",
29
30
  "peerDependencies": {
30
- "@molecule/app-bond": "^1.0.0",
31
- "@molecule/app-i18n": "^1.0.0",
32
- "@molecule/app-logger": "^1.0.0"
31
+ "@molecule/app-bond": "^1.0.1",
32
+ "@molecule/app-i18n": "^1.0.1",
33
+ "@molecule/app-logger": "^1.0.1"
33
34
  },
34
35
  "devDependencies": {
35
- "@molecule/app-bond": "1.0.0",
36
- "@molecule/app-i18n": "1.0.0",
37
- "@molecule/app-logger": "1.0.0",
36
+ "@molecule/app-bond": "1.0.1",
37
+ "@molecule/app-i18n": "1.0.1",
38
+ "@molecule/app-logger": "1.0.1",
38
39
  "@types/node": "26.1.2",
39
40
  "typescript": "6.0.3",
40
41
  "vitest": "4.1.10"