@rivinia/http 0.5.0 → 0.5.2
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 +337 -132
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,198 +3,403 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@rivinia/http)
|
|
4
4
|
[](https://www.npmjs.com/package/@rivinia/http)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
`@rivinia/http` is a thin, typed Angular HTTP client for plain `Observable`
|
|
7
|
+
requests and reactive `httpResource` requests. It leaves URLs, backend
|
|
8
|
+
contracts, and interceptors under app control. Response bodies pass through
|
|
9
|
+
unchanged unless a request explicitly asks to unwrap one.
|
|
9
10
|
|
|
10
|
-
-
|
|
11
|
-
- Requires
|
|
12
|
-
|
|
13
|
-
Call `provideHttpClient()` / pass `httpFeatures` only when you configure
|
|
14
|
-
interceptors or other backend features.
|
|
11
|
+
- Package version: **0.5.2**
|
|
12
|
+
- Requires Angular `^22.0.0` and RxJS `^6.5.3 || ^7.4.0`; `tslib` is installed
|
|
13
|
+
as a dependency.
|
|
15
14
|
|
|
16
|
-
## Install
|
|
15
|
+
## Install and configure
|
|
17
16
|
|
|
18
17
|
```bash
|
|
19
18
|
npm install @rivinia/http
|
|
20
|
-
ng add @rivinia/http
|
|
19
|
+
ng add @rivinia/http
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
For a standalone app, `ng add` adds `provideHttpClient()` and
|
|
23
|
+
`provideRiviniaHttp()` to the selected project's `src/app/app.config.ts`.
|
|
24
|
+
It has no prompts and does not set a base URL or an envelope policy. It is
|
|
25
|
+
idempotent and only edits an existing app config containing a `providers: []`
|
|
26
|
+
array; if no config is found it warns and skips, so add the providers
|
|
27
|
+
manually. In a multi-project workspace it targets `defaultProject` or the
|
|
28
|
+
first project; use `--project <name>` to select a project.
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
// app.config.ts — style A: nothing to configure (Angular ≥21 default)
|
|
27
|
-
providers: [provideRiviniaHttp()];
|
|
28
|
-
|
|
29
|
-
// style B: single call with interceptors (do NOT also call provideHttpClient)
|
|
30
|
-
providers: [provideRiviniaHttp({ httpFeatures: [withInterceptors([authInterceptor])] })];
|
|
30
|
+
The equivalent minimal standalone setup is:
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
```ts
|
|
33
|
+
// src/app/app.config.ts
|
|
34
|
+
import { provideHttpClient } from '@angular/common/http';
|
|
35
|
+
import { ApplicationConfig } from '@angular/core';
|
|
36
|
+
import { provideRiviniaHttp } from '@rivinia/http';
|
|
37
|
+
|
|
38
|
+
export const appConfig: ApplicationConfig = {
|
|
39
|
+
providers: [provideHttpClient(), provideRiviniaHttp()],
|
|
40
|
+
};
|
|
34
41
|
```
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
The library never joins, prefixes, or rewrites URLs. Compose them at the
|
|
39
|
-
call site from wherever you want — typically your environment files
|
|
40
|
-
(the BRT pattern):
|
|
43
|
+
Use the same `provideHttpClient` setup to add app-owned interceptors:
|
|
41
44
|
|
|
42
45
|
```ts
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
import { ApplicationConfig } from '@angular/core';
|
|
47
|
+
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
48
|
+
import { provideRiviniaHttp } from '@rivinia/http';
|
|
49
|
+
import { authInterceptor } from './auth.interceptor';
|
|
50
|
+
|
|
51
|
+
export const appConfig: ApplicationConfig = {
|
|
52
|
+
providers: [provideHttpClient(withInterceptors([authInterceptor])), provideRiviniaHttp()],
|
|
53
|
+
};
|
|
54
|
+
```
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
create = (dto: CreateTrip) => this.api.post<Trip>(this.url('trips'), dto);
|
|
55
|
-
}
|
|
56
|
+
Alternatively, pass features through `provideRiviniaHttp`:
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
58
|
+
```ts
|
|
59
|
+
import { ApplicationConfig } from '@angular/core';
|
|
60
|
+
import { withInterceptors } from '@angular/common/http';
|
|
61
|
+
import { provideRiviniaHttp } from '@rivinia/http';
|
|
62
|
+
import { authInterceptor } from './auth.interceptor';
|
|
63
|
+
|
|
64
|
+
export const appConfig: ApplicationConfig = {
|
|
65
|
+
providers: [provideRiviniaHttp({ httpFeatures: [withInterceptors([authInterceptor])] })],
|
|
66
|
+
};
|
|
62
67
|
```
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
```
|
|
69
|
+
Choose one backend setup: when using `httpFeatures`, do not also call
|
|
70
|
+
`provideHttpClient()`; otherwise the HTTP backend and its default XSRF
|
|
71
|
+
interceptor can be registered twice. If the app already configures
|
|
72
|
+
`provideHttpClient(...)`, keep that and call `provideRiviniaHttp()` without
|
|
73
|
+
`httpFeatures`.
|
|
71
74
|
|
|
72
|
-
##
|
|
75
|
+
## URLs and typed service calls
|
|
73
76
|
|
|
74
|
-
The
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
The client sends the endpoint string exactly as given. It never prepends a
|
|
78
|
+
base URL or rewrites a path. Compose URLs yourself, for example with
|
|
79
|
+
`joinUrl` and the app's environment:
|
|
77
80
|
|
|
78
81
|
```ts
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
// src/environments/environment.ts
|
|
83
|
+
export const environment = {
|
|
84
|
+
apiUrl: 'https://api.example.com',
|
|
85
|
+
} as const;
|
|
81
86
|
```
|
|
82
87
|
|
|
83
|
-
The type system enforces the contract: `{ unwrap: true }` without a key,
|
|
84
|
-
a key alone, and `{ unwrap: false, unwrapKey }` all fail compilation.
|
|
85
|
-
Different shapes per API in the same app:
|
|
86
|
-
|
|
87
88
|
```ts
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
// src/app/trip.service.ts
|
|
90
|
+
import { Service } from '@angular/core';
|
|
91
|
+
import type { HttpResponse } from '@angular/common/http';
|
|
92
|
+
import { Observable } from 'rxjs';
|
|
93
|
+
import { injectRiviniaHttp, joinUrl } from '@rivinia/http';
|
|
94
|
+
import { environment } from '../environments/environment';
|
|
95
|
+
|
|
96
|
+
export interface Trip {
|
|
97
|
+
id: string;
|
|
98
|
+
title: string;
|
|
99
|
+
departureDate: string;
|
|
100
|
+
status: 'planned' | 'booked' | 'cancelled';
|
|
101
|
+
}
|
|
90
102
|
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
export interface TripDraft {
|
|
104
|
+
title: string;
|
|
105
|
+
departureDate: string;
|
|
106
|
+
}
|
|
93
107
|
|
|
94
|
-
|
|
95
|
-
|
|
108
|
+
interface ApiEnvelope<T> {
|
|
109
|
+
data: T;
|
|
110
|
+
}
|
|
96
111
|
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
@Service()
|
|
113
|
+
export class TripApi {
|
|
114
|
+
private readonly http = injectRiviniaHttp();
|
|
115
|
+
private readonly tripsUrl = joinUrl(environment.apiUrl, 'trips');
|
|
116
|
+
|
|
117
|
+
list(): Observable<Trip[]> {
|
|
118
|
+
return this.http.get<Trip[]>(this.tripsUrl, {
|
|
119
|
+
params: { page: 1, search: 'coast' },
|
|
120
|
+
unwrap: true,
|
|
121
|
+
unwrapKey: 'data',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get(id: string): Observable<Trip> {
|
|
126
|
+
return this.http.get<Trip>(joinUrl(this.tripsUrl, id), {
|
|
127
|
+
unwrap: true,
|
|
128
|
+
unwrapKey: 'data',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
create(input: TripDraft): Observable<Trip> {
|
|
133
|
+
return this.http.post<Trip>(this.tripsUrl, input, {
|
|
134
|
+
unwrap: true,
|
|
135
|
+
unwrapKey: 'data',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
replace(id: string, input: TripDraft): Observable<Trip> {
|
|
140
|
+
return this.http.put<Trip>(joinUrl(this.tripsUrl, id), input, {
|
|
141
|
+
unwrap: true,
|
|
142
|
+
unwrapKey: 'data',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
updateStatus(id: string, status: Trip['status']): Observable<Trip> {
|
|
147
|
+
return this.http.patch<Trip>(
|
|
148
|
+
joinUrl(this.tripsUrl, id),
|
|
149
|
+
{ status },
|
|
150
|
+
{ unwrap: true, unwrapKey: 'data' },
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
cancel(id: string, reason: string): Observable<void> {
|
|
155
|
+
return this.http.delete<void>(joinUrl(this.tripsUrl, id), {
|
|
156
|
+
body: { reason },
|
|
157
|
+
unwrap: true,
|
|
158
|
+
unwrapKey: 'data',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
downloadItinerary(id: string): Observable<Blob> {
|
|
163
|
+
return this.http.getBlob(joinUrl(this.tripsUrl, `${id}/itinerary`));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getOperatorNotes(id: string): Observable<string> {
|
|
167
|
+
return this.http.getText(joinUrl(this.tripsUrl, `${id}/operator-notes`));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
listWithHeaders(): Observable<HttpResponse<ApiEnvelope<Trip[]>>> {
|
|
171
|
+
return this.http.getResponse<ApiEnvelope<Trip[]>>(this.tripsUrl, {
|
|
172
|
+
params: { page: 1 },
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
createAndReadLocation(input: TripDraft): Observable<HttpResponse<ApiEnvelope<Trip>>> {
|
|
177
|
+
return this.http.postResponse<ApiEnvelope<Trip>>(this.tripsUrl, input);
|
|
178
|
+
}
|
|
99
179
|
}
|
|
100
180
|
```
|
|
101
181
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
182
|
+
The JSON endpoints in this example return an object with a `data` field; the first five
|
|
183
|
+
methods opt in to extracting that field. `getBlob()` downloads a binary file,
|
|
184
|
+
and `getText()` reads plain text; neither method unwraps or checks JSON
|
|
185
|
+
envelopes. `getResponse()` and `postResponse()` return the complete
|
|
186
|
+
`HttpResponse` so the caller can inspect the status and headers; their body
|
|
187
|
+
stays raw and is not unwrapped.
|
|
188
|
+
|
|
189
|
+
| `RiviniaHttp` method | Used above for |
|
|
190
|
+
| -------------------- | ------------------------------------------------------------------ |
|
|
191
|
+
| `get` | `list()` and `get(id)` |
|
|
192
|
+
| `post` | `create(input)` |
|
|
193
|
+
| `put` | `replace(id, input)` |
|
|
194
|
+
| `patch` | `updateStatus(id, status)` |
|
|
195
|
+
| `delete` | `cancel(id, reason)`; its optional request body is passed through. |
|
|
196
|
+
| `getBlob` | `downloadItinerary(id)` |
|
|
197
|
+
| `getText` | `getOperatorNotes(id)` |
|
|
198
|
+
| `getResponse` | `listWithHeaders()` |
|
|
199
|
+
| `postResponse` | `createAndReadLocation(input)` |
|
|
200
|
+
| `getResource` | Reactive paginated list in `TripListComponent`. |
|
|
106
201
|
|
|
107
202
|
```ts
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
203
|
+
// Example use where the API returns pagination metadata in a header.
|
|
204
|
+
this.api.listWithHeaders().subscribe((response) => {
|
|
205
|
+
const total = Number(response.headers.get('X-Total-Count') ?? 0);
|
|
206
|
+
console.log(response.status, total, response.body?.data);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// A create endpoint may return 201 plus a Location header.
|
|
210
|
+
this.api.createAndReadLocation(input).subscribe((response) => {
|
|
211
|
+
const location = response.headers.get('Location');
|
|
212
|
+
console.log(response.status, location, response.body?.data);
|
|
111
213
|
});
|
|
112
214
|
```
|
|
113
215
|
|
|
114
|
-
`
|
|
115
|
-
|
|
216
|
+
`Observable<T>` is the compile-time type of the result; it does not validate
|
|
217
|
+
the server payload. `joinUrl` removes one boundary slash and passes absolute
|
|
218
|
+
`http://` or `https://` endpoints through unchanged. Use `params` for query
|
|
219
|
+
values: `null` and `undefined` entries are omitted, while strings, numbers,
|
|
220
|
+
and booleans are stringified. The standard request options also pass
|
|
221
|
+
`headers`, `HttpContext`, and `withCredentials` through to Angular's
|
|
222
|
+
`HttpClient`. Use `HttpContext` with your own interceptors for app-specific
|
|
223
|
+
controls such as opting out of auth.
|
|
116
224
|
|
|
117
|
-
##
|
|
225
|
+
## Reactive resources
|
|
118
226
|
|
|
119
|
-
|
|
120
|
-
|
|
227
|
+
`getResource` uses Angular's `httpResource`. The endpoint can be a string, a
|
|
228
|
+
signal, or a reactive function; `undefined` disables the request. URL,
|
|
229
|
+
`params`, `headers`, and `context` can be reactive inputs and trigger a new
|
|
230
|
+
request when their signal values change. Other options are read once when
|
|
231
|
+
the resource is created.
|
|
121
232
|
|
|
122
233
|
```ts
|
|
123
|
-
|
|
124
|
-
|
|
234
|
+
import { Component, signal } from '@angular/core';
|
|
235
|
+
import { injectRiviniaHttp, joinUrl } from '@rivinia/http';
|
|
236
|
+
import { environment } from '../environments/environment';
|
|
237
|
+
|
|
238
|
+
import type { Trip } from './trip.service';
|
|
239
|
+
|
|
240
|
+
@Component({
|
|
241
|
+
selector: 'app-trip-list',
|
|
242
|
+
standalone: true,
|
|
243
|
+
templateUrl: './trip-list.component.html',
|
|
244
|
+
})
|
|
245
|
+
export class TripListComponent {
|
|
246
|
+
private readonly http = injectRiviniaHttp();
|
|
247
|
+
readonly page = signal(1);
|
|
248
|
+
readonly trips = this.http.getResource<Trip[]>(joinUrl(environment.apiUrl, 'trips'), {
|
|
249
|
+
params: () => ({ page: this.page() }),
|
|
250
|
+
unwrap: true,
|
|
251
|
+
unwrapKey: 'data',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
nextPage(): void {
|
|
255
|
+
this.page.update((value) => value + 1);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
125
258
|
```
|
|
126
259
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
| types | types | `RiviniaHttpConfig`, `RiviniaRequestOptions`, `RiviniaRawOptions`, `RiviniaDeleteOptions`, `RiviniaResourceOptions`, `RiviniaEnvelopePolicy`, `ReactiveInput`, `QueryParamsInput` |
|
|
260
|
+
```html
|
|
261
|
+
<!-- src/app/trip-list.component.html -->
|
|
262
|
+
@if (trips.isLoading()) {
|
|
263
|
+
<p>Loading trips…</p>
|
|
264
|
+
} @else if (trips.hasValue()) {
|
|
265
|
+
<ul>
|
|
266
|
+
@for (trip of trips.value(); track trip.id) {
|
|
267
|
+
<li>{{ trip.title }} — {{ trip.departureDate }} ({{ trip.status }})</li>
|
|
268
|
+
}
|
|
269
|
+
</ul>
|
|
270
|
+
} @else if (trips.error()) {
|
|
271
|
+
<p role="alert">Could not load trips. Try again.</p>
|
|
272
|
+
}
|
|
273
|
+
<button type="button" (click)="nextPage()">Next page</button>
|
|
274
|
+
```
|
|
143
275
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
accept values, signals or reactive functions (everything else is static).
|
|
276
|
+
In a real app, share the Trip model through a model module. `parse` is optional;
|
|
277
|
+
when provided it runs after envelope validation and unwrapping, and can perform
|
|
278
|
+
runtime validation (for example with Zod or Valibot). A parse error, HTTP
|
|
279
|
+
error, or library envelope error is exposed through `trips.error()`.
|
|
280
|
+
`defaultValue` supplies a value while idle/loading. `reportProgress` is
|
|
281
|
+
forwarded to the resource request; for progress events on an `Observable`,
|
|
282
|
+
use Angular `HttpClient` directly.
|
|
152
283
|
|
|
153
|
-
|
|
154
|
-
app interceptors (auth opt-out, error toasts) keep working. For caching,
|
|
155
|
-
polling, retry and TanStack Query interop see [RECIPES.md](./RECIPES.md).
|
|
284
|
+
## Response envelopes and errors
|
|
156
285
|
|
|
157
|
-
|
|
286
|
+
By default, all JSON response bodies are returned raw, even when they contain
|
|
287
|
+
a field named `data`. To extract a payload, opt in on that call and provide a
|
|
288
|
+
key:
|
|
158
289
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
(Zod/Valibot — see [RECIPES.md](./RECIPES.md)).
|
|
290
|
+
```ts
|
|
291
|
+
this.http.get<Trip[]>(url); // raw response body
|
|
292
|
+
this.http.get<Trip[]>(url, { unwrap: true, unwrapKey: 'data' });
|
|
293
|
+
```
|
|
164
294
|
|
|
165
|
-
|
|
295
|
+
The type contract requires `unwrapKey` when `unwrap: true`, and rejects a key
|
|
296
|
+
when unwrapping is off. Extraction is strict and single-level: the response
|
|
297
|
+
must be a non-null, non-array object with that own property. A missing key,
|
|
298
|
+
primitive/array/null body, or empty key throws `RiviniaHttpUnwrapError`;
|
|
299
|
+
it does not silently return the original body. Present values such as `null`,
|
|
300
|
+
`false`, `0`, and `''` are returned as-is. Nested data and sibling fields are
|
|
301
|
+
not modified. `unwrapAs<T>` performs the same one-level extraction outside a
|
|
302
|
+
request; its generic type is an unchecked cast, not runtime validation.
|
|
166
303
|
|
|
167
|
-
|
|
304
|
+
An optional envelope policy checks backend-declared failures before
|
|
305
|
+
unwrapping. The default keys are `isSuccess` and `error`; only an object whose
|
|
306
|
+
`isSuccess` value is exactly `false` is considered failed:
|
|
168
307
|
|
|
169
308
|
```ts
|
|
170
|
-
|
|
171
|
-
|
|
309
|
+
// Apply to JSON request paths made by this configured client.
|
|
310
|
+
provideRiviniaHttp({ envelope: {} });
|
|
311
|
+
|
|
312
|
+
// Or specify a per-request policy; it replaces the global policy.
|
|
313
|
+
this.http.get<Trip[]>(url, {
|
|
314
|
+
envelope: { successKey: 'ok', errorKey: 'message' },
|
|
315
|
+
});
|
|
172
316
|
```
|
|
173
317
|
|
|
174
|
-
|
|
318
|
+
When a failure is detected, `RiviniaHttpEnvelopeError` is sent through the
|
|
319
|
+
Observable error channel or appears as the resource error. It carries the raw
|
|
320
|
+
`body` and failure `code`. Set `throwOnFailure: false` for a passive policy.
|
|
321
|
+
Policy validation runs before unwrapping, including on `getResponse` and
|
|
322
|
+
`postResponse`. Those full-response methods return raw `HttpResponse<T>`
|
|
323
|
+
(status, headers, and unwrapped body is not applied). `getBlob` and `getText`
|
|
324
|
+
also return raw bodies and do not run envelope validation.
|
|
325
|
+
|
|
326
|
+
The generic type does not validate the server response. For untrusted or
|
|
327
|
+
loosely typed APIs, validate the extracted value with `getResource`'s `parse`
|
|
328
|
+
hook, or validate in the Observable pipeline in app code. Do not log
|
|
329
|
+
`RiviniaHttpUnwrapError.body` or `RiviniaHttpEnvelopeError.body` without
|
|
330
|
+
considering that they may contain sensitive data.
|
|
331
|
+
|
|
332
|
+
## Public API
|
|
333
|
+
|
|
334
|
+
| Export | Kind | Purpose |
|
|
335
|
+
| ----------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
336
|
+
| `provideRiviniaHttp(config?)` | provider | Sets `envelope` and optional `httpFeatures`; no global unwrap option. |
|
|
337
|
+
| `RiviniaHttp` | class | `get`, `post`, `put`, `patch`, `delete`, `getBlob`, `getText`, `getResponse`, `postResponse`, and `getResource`. |
|
|
338
|
+
| `injectRiviniaHttp()` | function | Injection-context shorthand for `inject(RiviniaHttp)`. |
|
|
339
|
+
| `joinUrl(base, endpoint)` | utility | Removes one boundary slash when joining; absolute HTTP(S) endpoints pass through. |
|
|
340
|
+
| `unwrapAs(body, key)` | utility | Strict, single-level typed extraction. |
|
|
341
|
+
| `extractKey(body, key)` | utility | Strict extraction without a generic cast. |
|
|
342
|
+
| `RiviniaHttpUnwrapError` | class | Error for invalid or missing unwrap properties. |
|
|
343
|
+
| `RiviniaHttpEnvelopeError` | class | Error for a failed configured response envelope. |
|
|
344
|
+
| `RiviniaHttpConfig` | type | `envelope` and `httpFeatures` provider configuration. |
|
|
345
|
+
| `RiviniaEnvelopePolicy` | type | Success key, error key, and failure policy settings. |
|
|
346
|
+
| `RiviniaRequestOptions` | type | Options for requests that may unwrap. |
|
|
347
|
+
| `RiviniaRawOptions` | type | Options for methods that never unwrap. |
|
|
348
|
+
| `RiviniaDeleteOptions` | type | Delete options including an optional request body. |
|
|
349
|
+
| `RiviniaDeleteRequestOptions` | type | Delete options including the unwrap contract. |
|
|
350
|
+
| `RiviniaRequestBase` | type | Shared transport and envelope options. |
|
|
351
|
+
| `RiviniaResourceOptions` | type | Reactive resource options and unwrap contract. |
|
|
352
|
+
| `RiviniaResourceBase` | type | Shared resource-specific options. |
|
|
353
|
+
| `ReactiveInput` | type | Static value, signal, or reactive function input. |
|
|
354
|
+
| `QueryParamsInput` | type | `HttpParams` or a primitive-valued query object. |
|
|
355
|
+
| `RiviniaUnwrapOn` | type | Required key form for `{ unwrap: true }`. |
|
|
356
|
+
| `RiviniaUnwrapOff` | type | Form where an unwrap key is disallowed. |
|
|
357
|
+
|
|
358
|
+
## Testing
|
|
359
|
+
|
|
360
|
+
Use Angular's HTTP testing providers. The testing provider follows
|
|
361
|
+
`provideHttpClient()` so the test backend replaces the real backend:
|
|
175
362
|
|
|
176
363
|
```ts
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
364
|
+
import { provideHttpClient } from '@angular/common/http';
|
|
365
|
+
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
|
366
|
+
import { TestBed } from '@angular/core/testing';
|
|
367
|
+
import { provideRiviniaHttp, RiviniaHttp } from '@rivinia/http';
|
|
368
|
+
|
|
369
|
+
TestBed.configureTestingModule({
|
|
370
|
+
providers: [provideHttpClient(), provideHttpClientTesting(), provideRiviniaHttp()],
|
|
371
|
+
});
|
|
180
372
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
373
|
+
const api = TestBed.inject(RiviniaHttp);
|
|
374
|
+
const requests = TestBed.inject(HttpTestingController);
|
|
375
|
+
api
|
|
376
|
+
.get<unknown[]>('https://api.test/trips', {
|
|
377
|
+
unwrap: true,
|
|
378
|
+
unwrapKey: 'data',
|
|
379
|
+
})
|
|
380
|
+
.subscribe();
|
|
381
|
+
requests.expectOne('https://api.test/trips').flush({ data: [] });
|
|
382
|
+
requests.verify();
|
|
383
|
+
```
|
|
185
384
|
|
|
186
|
-
##
|
|
385
|
+
## Migration from 0.4.x
|
|
187
386
|
|
|
188
|
-
|
|
387
|
+
Global `unwrap` and `unwrapKey` settings were removed in 0.5.0. Move the
|
|
388
|
+
unwrap decision and key to each applicable request:
|
|
189
389
|
|
|
190
390
|
```ts
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
391
|
+
// Before
|
|
392
|
+
provideRiviniaHttp({ unwrap: true, unwrapKey: 'data' });
|
|
393
|
+
this.http.get<Trip[]>(url);
|
|
394
|
+
|
|
395
|
+
// After
|
|
396
|
+
provideRiviniaHttp();
|
|
397
|
+
this.http.get<Trip[]>(url, { unwrap: true, unwrapKey: 'data' });
|
|
196
398
|
```
|
|
197
399
|
|
|
400
|
+
The `ng add` schematic now emits bare providers and does not migrate request
|
|
401
|
+
call sites. Without the per-request options, the response remains raw.
|
|
402
|
+
|
|
198
403
|
## License
|
|
199
404
|
|
|
200
405
|
MIT.
|