@rivinia/http 0.1.0 → 0.2.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/README.md +55 -27
- package/fesm2022/rivinia-http.mjs +2 -2
- package/fesm2022/rivinia-http.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/ng-add/index.cjs +14 -13
- package/schematics/ng-add/schema.json +1 -9
- package/types/rivinia-http.d.ts +16 -12
package/README.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@rivinia/http)
|
|
4
4
|
[](https://www.npmjs.com/package/@rivinia/http)
|
|
5
5
|
|
|
6
|
-
Headless typed HTTP client for Angular 22+.
|
|
7
|
-
|
|
8
|
-
styling, zero backend opinions,
|
|
6
|
+
Headless typed HTTP client for Angular 22+. Configurable envelope
|
|
7
|
+
unwrapping, plain Observables **and** `httpResource` — URLs always used as
|
|
8
|
+
given, zero styling, zero backend opinions, interceptors left to your app.
|
|
9
9
|
|
|
10
|
-
- Version: **0.
|
|
10
|
+
- Version: **0.2.0**
|
|
11
11
|
- Requires: **Angular `^22.0.0`**, `rxjs` (same range Angular uses), `tslib`
|
|
12
12
|
- Since Angular 21 `HttpClient` (fetch backend) is injectable with zero setup.
|
|
13
13
|
Call `provideHttpClient()` / pass `httpFeatures` only when you configure
|
|
@@ -17,39 +17,47 @@ styling, zero backend opinions, and interceptors left to your app.
|
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
npm install @rivinia/http
|
|
20
|
-
ng add @rivinia/http # interactive: asks
|
|
20
|
+
ng add @rivinia/http # interactive: asks about unwrapping + key, then wires app.config.ts
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Quick start — pick ONE backend style
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
26
|
// app.config.ts — style A: nothing to configure (Angular ≥21 default)
|
|
27
|
-
providers: [provideRiviniaHttp(
|
|
27
|
+
providers: [provideRiviniaHttp()];
|
|
28
28
|
|
|
29
29
|
// style B: single call with interceptors (do NOT also call provideHttpClient)
|
|
30
|
-
providers: [
|
|
31
|
-
provideRiviniaHttp({
|
|
32
|
-
baseUrl: 'https://api.example.com',
|
|
33
|
-
httpFeatures: [withInterceptors([authInterceptor])],
|
|
34
|
-
}),
|
|
35
|
-
];
|
|
30
|
+
providers: [provideRiviniaHttp({ httpFeatures: [withInterceptors([authInterceptor])] })];
|
|
36
31
|
|
|
37
32
|
// style C: explicit backend config, like the official setup guide
|
|
38
|
-
providers: [provideHttpClient(withInterceptors([authInterceptor])), provideRiviniaHttp(
|
|
33
|
+
providers: [provideHttpClient(withInterceptors([authInterceptor])), provideRiviniaHttp()];
|
|
39
34
|
```
|
|
40
35
|
|
|
36
|
+
## URLs: yours, always
|
|
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):
|
|
41
|
+
|
|
41
42
|
```ts
|
|
42
|
-
//
|
|
43
|
+
// src/environments/environment.ts / environment.prod.ts
|
|
44
|
+
export const environment = { apiUrl: 'https://api.example.com' };
|
|
45
|
+
|
|
46
|
+
// feature service
|
|
47
|
+
import { joinUrl } from '@rivinia/http';
|
|
48
|
+
|
|
43
49
|
export class TripService {
|
|
44
50
|
private api = injectRiviniaHttp();
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
private url = (path: string) => joinUrl(environment.apiUrl, path);
|
|
52
|
+
|
|
53
|
+
list = () => this.api.get<Trip[]>(this.url('trips'));
|
|
54
|
+
create = (dto: CreateTrip) => this.api.post<Trip>(this.url('trips'), dto);
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
// Reactive resource (auto refetch, loading/error signals)
|
|
50
58
|
export class TripPage {
|
|
51
59
|
page = signal(1);
|
|
52
|
-
trips = this.api.getResource<Trip[]>(() => `trips?page=${this.page()}`);
|
|
60
|
+
trips = this.api.getResource<Trip[]>(() => this.url(`trips?page=${this.page()}`));
|
|
53
61
|
}
|
|
54
62
|
```
|
|
55
63
|
|
|
@@ -61,15 +69,35 @@ export class TripPage {
|
|
|
61
69
|
}
|
|
62
70
|
```
|
|
63
71
|
|
|
64
|
-
## Envelope unwrapping
|
|
72
|
+
## Envelope unwrapping (opt-in, default off)
|
|
73
|
+
|
|
74
|
+
The library assumes nothing about your backend: responses pass through raw
|
|
75
|
+
unless unwrapping is enabled. Backends that wrap payloads
|
|
76
|
+
(`{ data, error, isSuccess }`) opt in globally or per call:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
provideRiviniaHttp({ unwrap: true }); // whole app, 'data' key
|
|
80
|
+
provideRiviniaHttp({ unwrap: true, unwrapKey: 'result' }); // whole app, custom key
|
|
81
|
+
this.api.get<Legacy>(url, { unwrap: true, unwrapKey: 'result' }); // this call only
|
|
82
|
+
this.api.get<Raw>(url); // raw body (default)
|
|
83
|
+
```
|
|
65
84
|
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
NOTE: a key alone never enables unwrapping — only `unwrap: true` does,
|
|
86
|
+
at either level. Different shapes per API in the same app:
|
|
68
87
|
|
|
69
88
|
```ts
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
export class MixedService {
|
|
90
|
+
private api = injectRiviniaHttp();
|
|
91
|
+
|
|
92
|
+
// main backend: { data: … } (global unwrap: true applies, nothing to say)
|
|
93
|
+
trips = () => this.api.get<Trip[]>(fullUrl('trips'));
|
|
94
|
+
|
|
95
|
+
// legacy backend: { result: … } (per-call key, unwrapping already on globally)
|
|
96
|
+
legacy = () => this.api.get<Old[]>(fullUrl('old'), { unwrapKey: 'result' });
|
|
97
|
+
|
|
98
|
+
// webhook endpoint: raw body despite the enabled global (per-call kill-switch)
|
|
99
|
+
ping = () => this.api.get<string>(fullUrl('ping'), { unwrap: false });
|
|
100
|
+
}
|
|
73
101
|
```
|
|
74
102
|
|
|
75
103
|
`getResource` applies the same rules through `parse` (your own `parse` runs
|
|
@@ -79,7 +107,7 @@ after unwrapping — ideal for Zod/Valibot validation).
|
|
|
79
107
|
|
|
80
108
|
| Export | Kind | Purpose |
|
|
81
109
|
|---|---|---|
|
|
82
|
-
| `provideRiviniaHttp(config?)` | provider | `{
|
|
110
|
+
| `provideRiviniaHttp(config?)` | provider | `{ unwrap?=false, unwrapKey?='data', httpFeatures? }` |
|
|
83
111
|
| `RiviniaHttp` | `@Service()` | `get/post/put/patch/delete`, `getBlob`, `getResource` |
|
|
84
112
|
| `injectRiviniaHttp()` | function | shorthand for `inject(RiviniaHttp)` |
|
|
85
113
|
| `joinUrl(base, endpoint)` | util | slash-safe join; absolute URLs pass through |
|
|
@@ -96,10 +124,10 @@ Standard Angular tooling — nothing custom to learn:
|
|
|
96
124
|
|
|
97
125
|
```ts
|
|
98
126
|
TestBed.configureTestingModule({
|
|
99
|
-
providers: [provideHttpClient(), provideHttpClientTesting(), provideRiviniaHttp(
|
|
127
|
+
providers: [provideHttpClient(), provideHttpClientTesting(), provideRiviniaHttp()],
|
|
100
128
|
});
|
|
101
|
-
api.get('users').subscribe();
|
|
102
|
-
TestBed.inject(HttpTestingController).expectOne('
|
|
129
|
+
api.get('https://api.test/users').subscribe();
|
|
130
|
+
TestBed.inject(HttpTestingController).expectOne('https://api.test/users').flush({ data: [] });
|
|
103
131
|
```
|
|
104
132
|
|
|
105
133
|
## License
|
|
@@ -84,10 +84,10 @@ var RiviniaHttp = class RiviniaHttp {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
url(endpoint) {
|
|
87
|
-
return
|
|
87
|
+
return endpoint;
|
|
88
88
|
}
|
|
89
89
|
resolveUnwrap(options) {
|
|
90
|
-
return options?.unwrap ?? this.config?.unwrap ??
|
|
90
|
+
return options?.unwrap ?? this.config?.unwrap ?? false;
|
|
91
91
|
}
|
|
92
92
|
resolveKey(options) {
|
|
93
93
|
return options?.unwrapKey ?? this.config?.unwrapKey ?? "data";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rivinia-http.mjs","names":[],"sources":["../../../projects/http/src/lib/http-utils.ts","../../../projects/http/src/lib/http-config.ts","../../../projects/http/src/lib/rivinia-http.ts","../../../projects/http/src/lib/provide-http.ts"],"sourcesContent":["/**\n * Joins a base URL and an endpoint without producing double slashes.\n * Absolute `http(s)` endpoints pass through untouched.\n */\nexport function joinUrl(baseUrl: string, endpoint: string): string {\n if (/^https?:\\/\\//i.test(endpoint)) {\n return endpoint;\n }\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n const path = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint;\n return path ? `${base}/${path}` : base;\n}\n\n/**\n * Extracts `body[key]` when the backend wraps payloads in an envelope\n * (e.g. `{ data, error, isSuccess }`); passes anything else through\n * unchanged. Recursive, so nested envelopes collapse.\n */\nexport function unwrapAs<T>(body: unknown, key: string): T {\n if (isObjectRecord(body) && hasOwn(body, key)) {\n return unwrapAs<T>(body[key], key);\n }\n return body as T;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction hasOwn(obj: Record<string, unknown>, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key);\n}\n","import type { HttpFeature, HttpFeatureKind } from '@angular/common/http';\nimport { InjectionToken } from '@angular/core';\n\n/**\n * Global configuration for `@rivinia/http`.\n *\n * Every field is optional and can be overridden per request, except\n * `baseUrl` which only exists globally (absolute URLs always pass through).\n */\nexport interface RiviniaHttpConfig {\n /** Base URL joined with every relative endpoint. No trailing slash needed. */\n baseUrl?: string;\n /**\n * Unwrap `{ [unwrapKey]: payload }` envelopes by default.\n * @default true (BRT convention: backends wrap payloads in `{ data }`).\n */\n unwrap?: boolean;\n /**\n * Envelope key unwrapped when `unwrap` is enabled.\n * @default 'data'\n */\n unwrapKey?: string;\n /**\n * Optional `HttpClient` features (`withInterceptors(...)`, `withXhr()`, …),\n * applied via a single internal `provideHttpClient(...httpFeatures)` call.\n *\n * Use this INSTEAD of calling `provideHttpClient()` yourself — calling both\n * registers the built-in XSRF interceptor twice (verified against Angular\n * 22.2.2 source: `xsrfInterceptorFn` is a multi-provider with no cross-call\n * dedup). Omit it entirely on Angular ≥21 when you need no features:\n * `HttpClient` (fetch backend) is injectable by default.\n */\n httpFeatures?: HttpFeature<HttpFeatureKind>[];\n}\n\n/**\n * Internal configuration token. Consumers must use `provideRiviniaHttp()`.\n * @internal\n */\nexport const RIVINIA_HTTP_CONFIG = new InjectionToken<RiviniaHttpConfig>(\n 'RIVINIA_HTTP_CONFIG',\n);\n\nexport const DEFAULT_UNWRAP = true;\nexport const DEFAULT_UNWRAP_KEY = 'data';\n","import {\n HttpClient,\n HttpContext,\n HttpParams,\n httpResource,\n type HttpResourceRef,\n} from '@angular/common/http';\nimport { Service, inject, type Signal } from '@angular/core';\nimport { map, type Observable } from 'rxjs';\nimport {\n DEFAULT_UNWRAP,\n DEFAULT_UNWRAP_KEY,\n RIVINIA_HTTP_CONFIG,\n type RiviniaHttpConfig,\n} from './http-config';\nimport {\n type QueryParamsInput,\n type RiviniaDeleteOptions,\n type RiviniaRequestOptions,\n} from './http-options';\nimport { joinUrl, unwrapAs } from './http-utils';\n\n/** Options for `getResource` in addition to per-request options. */\nexport interface RiviniaResourceOptions<T> extends RiviniaRequestOptions {\n /** Value while idle/loading. Defaults to `undefined`. */\n defaultValue?: T;\n /**\n * Runs AFTER envelope unwrapping: `raw -> unwrap -> parse`.\n * Use for runtime validation (e.g. Zod) or shaping.\n */\n parse?: (value: unknown) => T;\n /**\n * Forwarded to the `httpResource` request object only (enables the\n * resource `progress` signal for downloads). Has no effect on the\n * plain Observable methods — use `HttpClient` events there directly.\n */\n reportProgress?: boolean;\n}\n\n/**\n * Headless typed HTTP client.\n *\n * Thin over `HttpClient` (so app interceptors, `HttpContext` escape hatches\n * and `provideHttpClient()` testing all keep working). Owns no backend:\n * the app provides `HttpClient`, this library only adds base-URL joining\n * plus configurable envelope unwrapping.\n *\n * Unwrap resolution per call: `options.unwrap ?? config.unwrap ?? true`,\n * key: `options.unwrapKey ?? config.unwrapKey ?? 'data'`.\n */\n@Service()\nexport class RiviniaHttp {\n private readonly http = inject(HttpClient);\n private readonly config = inject<RiviniaHttpConfig>(RIVINIA_HTTP_CONFIG, {\n optional: true,\n });\n\n get<T>(endpoint: string, options?: RiviniaRequestOptions): Observable<T> {\n return this.http\n .get<unknown>(this.url(endpoint), this.httpOptions(options))\n .pipe(map((body) => this.applyUnwrap<T>(body, options)));\n }\n\n post<T>(endpoint: string, body?: unknown, options?: RiviniaRequestOptions): Observable<T> {\n return this.http\n .post<unknown>(this.url(endpoint), body, this.httpOptions(options))\n .pipe(map((res) => this.applyUnwrap<T>(res, options)));\n }\n\n put<T>(endpoint: string, body?: unknown, options?: RiviniaRequestOptions): Observable<T> {\n return this.http\n .put<unknown>(this.url(endpoint), body, this.httpOptions(options))\n .pipe(map((res) => this.applyUnwrap<T>(res, options)));\n }\n\n patch<T>(endpoint: string, body?: unknown, options?: RiviniaRequestOptions): Observable<T> {\n return this.http\n .patch<unknown>(this.url(endpoint), body, this.httpOptions(options))\n .pipe(map((res) => this.applyUnwrap<T>(res, options)));\n }\n\n delete<T>(endpoint: string, options?: RiviniaDeleteOptions): Observable<T> {\n return this.http\n .delete<unknown>(this.url(endpoint), {\n ...this.httpOptions(options),\n body: options?.body,\n })\n .pipe(map((res) => this.applyUnwrap<T>(res, options)));\n }\n\n /** Binary download. Never unwrapped — envelopes don't apply to blobs. */\n getBlob(endpoint: string, options?: RiviniaRequestOptions): Observable<Blob> {\n return this.http.get(this.url(endpoint), {\n ...this.httpOptions(options),\n responseType: 'blob',\n });\n }\n\n /**\n * Reactive GET as an `httpResource` (stable since Angular v22).\n * Re-fetches when the endpoint signal changes; `undefined` disables.\n * Unwrap applies through `parse`, composed before a user `parse` if given.\n */\n getResource<T>(\n endpoint: string | Signal<string | undefined>,\n options?: RiviniaResourceOptions<T>,\n ): HttpResourceRef<T | undefined> {\n const { defaultValue, parse, reportProgress, ...requestOptions } = options ?? {};\n const request = () => {\n const ep = typeof endpoint === 'string' ? endpoint : endpoint();\n if (ep === undefined) return undefined;\n return {\n url: this.url(ep),\n params: this.toParams(requestOptions.params),\n headers: requestOptions.headers,\n context: requestOptions.context,\n withCredentials: requestOptions.withCredentials,\n ...(reportProgress !== undefined ? { reportProgress } : {}),\n };\n };\n const unwrapKey = this.resolveKey(requestOptions);\n const unwrapEnabled = this.resolveUnwrap(requestOptions);\n const base = { defaultValue: defaultValue as T | undefined };\n if (unwrapEnabled) {\n const applyParse = (raw: unknown): T =>\n parse ? parse(unwrapAs<T>(raw, unwrapKey)) : unwrapAs<T>(raw, unwrapKey);\n return defaultValue === undefined\n ? httpResource<T>(request, { ...base, parse: applyParse })\n : httpResource<T>(request, {\n ...base,\n defaultValue: defaultValue as T,\n parse: applyParse,\n });\n }\n return defaultValue === undefined\n ? httpResource<T>(request, { ...base, ...(parse ? { parse } : {}) })\n : httpResource<T>(request, {\n ...base,\n defaultValue: defaultValue as T,\n ...(parse ? { parse } : {}),\n });\n }\n\n private url(endpoint: string): string {\n return joinUrl(this.config?.baseUrl ?? '', endpoint);\n }\n\n private resolveUnwrap(options?: RiviniaRequestOptions): boolean {\n return options?.unwrap ?? this.config?.unwrap ?? DEFAULT_UNWRAP;\n }\n\n private resolveKey(options?: RiviniaRequestOptions): string {\n return options?.unwrapKey ?? this.config?.unwrapKey ?? DEFAULT_UNWRAP_KEY;\n }\n\n private applyUnwrap<T>(body: unknown, options?: RiviniaRequestOptions): T {\n if (!this.resolveUnwrap(options)) return body as T;\n return unwrapAs<T>(body, this.resolveKey(options));\n }\n\n private httpOptions(options?: RiviniaRequestOptions) {\n return {\n params: this.toParams(options?.params),\n headers: options?.headers,\n context: options?.context ?? new HttpContext(),\n withCredentials: options?.withCredentials,\n };\n }\n\n private toParams(input?: QueryParamsInput): HttpParams | undefined {\n if (!input) return undefined;\n if (input instanceof HttpParams) return input;\n let params = new HttpParams();\n for (const [key, value] of Object.entries(input)) {\n if (value !== undefined && value !== null) {\n params = params.set(key, String(value));\n }\n }\n return params;\n }\n}\n","import { EnvironmentProviders, inject, makeEnvironmentProviders } from '@angular/core';\nimport { provideHttpClient } from '@angular/common/http';\nimport { RIVINIA_HTTP_CONFIG, type RiviniaHttpConfig } from './http-config';\nimport { RiviniaHttp } from './rivinia-http';\n\n/**\n * Configures `@rivinia/http`.\n *\n * Three styles (pick ONE backend path):\n * - `provideRiviniaHttp()` alone — Angular ≥21 already injects `HttpClient` (fetch).\n * - `provideRiviniaHttp({ httpFeatures: [withInterceptors([...])] })` — single call\n * that also configures the backend. Do NOT additionally call\n * `provideHttpClient()` (double registration duplicates the XSRF interceptor).\n * - `provideHttpClient(...)` + `provideRiviniaHttp()` separately — equivalent, keeps\n * backend config visibly in app code (this is what `ng add` generates).\n *\n * @example\n * ```ts\n * provideRiviniaHttp({ baseUrl: 'https://api.example.com', unwrapKey: 'data' })\n * ```\n */\nexport function provideRiviniaHttp(config: RiviniaHttpConfig = {}): EnvironmentProviders {\n const { httpFeatures, ...rest } = config;\n return makeEnvironmentProviders([\n ...(httpFeatures ? [provideHttpClient(...httpFeatures)] : []),\n { provide: RIVINIA_HTTP_CONFIG, useValue: rest },\n ]);\n}\n\n/** Shorthand for `inject(RiviniaHttp)`. */\nexport function injectRiviniaHttp(): RiviniaHttp {\n return inject(RiviniaHttp);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rivinia-http.mjs","names":[],"sources":["../../../projects/http/src/lib/http-utils.ts","../../../projects/http/src/lib/http-config.ts","../../../projects/http/src/lib/rivinia-http.ts","../../../projects/http/src/lib/provide-http.ts"],"sourcesContent":["/**\n * Joins a base URL and an endpoint without producing double slashes.\n * Absolute `http(s)` endpoints pass through untouched.\n */\nexport function joinUrl(baseUrl: string, endpoint: string): string {\n if (/^https?:\\/\\//i.test(endpoint)) {\n return endpoint;\n }\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n const path = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint;\n return path ? `${base}/${path}` : base;\n}\n\n/**\n * Extracts `body[key]` when the backend wraps payloads in an envelope\n * (e.g. `{ data, error, isSuccess }`); passes anything else through\n * unchanged. Recursive, so nested envelopes collapse.\n */\nexport function unwrapAs<T>(body: unknown, key: string): T {\n if (isObjectRecord(body) && hasOwn(body, key)) {\n return unwrapAs<T>(body[key], key);\n }\n return body as T;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction hasOwn(obj: Record<string, unknown>, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key);\n}\n","import type { HttpFeature, HttpFeatureKind } from '@angular/common/http';\nimport { InjectionToken } from '@angular/core';\n\n/**\n * Global configuration for `@rivinia/http`.\n *\n * Every field is optional and can be overridden per request.\n * URLs are ALWAYS used as given — the library never joins, prefixes, or\n * rewrites them. Build them at the call site from wherever you want\n * (`environment.apiUrl`, a constant, a signal …) with the exported `joinUrl`.\n */\nexport interface RiviniaHttpConfig {\n /**\n * Unwrap `{ [unwrapKey]: payload }` envelopes by default.\n * @default false — the library assumes nothing about your backend;\n * opt in globally here or per call with `{ unwrap: true }`.\n */\n unwrap?: boolean;\n /**\n * Envelope key unwrapped when `unwrap` is enabled.\n * @default 'data'\n */\n unwrapKey?: string;\n /**\n * Optional `HttpClient` features (`withInterceptors(...)`, `withXhr()`, …),\n * applied via a single internal `provideHttpClient(...httpFeatures)` call.\n *\n * Use this INSTEAD of calling `provideHttpClient()` yourself — calling both\n * registers the built-in XSRF interceptor twice (verified against Angular\n * 22.2.2 source: `xsrfInterceptorFn` is a multi-provider with no cross-call\n * dedup). Omit it entirely on Angular ≥21 when you need no features:\n * `HttpClient` (fetch backend) is injectable by default.\n */\n httpFeatures?: HttpFeature<HttpFeatureKind>[];\n}\n\n/**\n * Internal configuration token. Consumers must use `provideRiviniaHttp()`.\n * @internal\n */\nexport const RIVINIA_HTTP_CONFIG = new InjectionToken<RiviniaHttpConfig>(\n 'RIVINIA_HTTP_CONFIG',\n);\n\nexport const DEFAULT_UNWRAP = false;\nexport const DEFAULT_UNWRAP_KEY = 'data';\n","import {\n HttpClient,\n HttpContext,\n HttpParams,\n httpResource,\n type HttpResourceRef,\n} from '@angular/common/http';\nimport { Service, inject, type Signal } from '@angular/core';\nimport { map, type Observable } from 'rxjs';\nimport {\n DEFAULT_UNWRAP,\n DEFAULT_UNWRAP_KEY,\n RIVINIA_HTTP_CONFIG,\n type RiviniaHttpConfig,\n} from './http-config';\nimport {\n type QueryParamsInput,\n type RiviniaDeleteOptions,\n type RiviniaRequestOptions,\n} from './http-options';\nimport { unwrapAs } from './http-utils';\n\n/** Options for `getResource` in addition to per-request options. */\nexport interface RiviniaResourceOptions<T> extends RiviniaRequestOptions {\n /** Value while idle/loading. Defaults to `undefined`. */\n defaultValue?: T;\n /**\n * Runs AFTER envelope unwrapping: `raw -> unwrap -> parse`.\n * Use for runtime validation (e.g. Zod) or shaping.\n */\n parse?: (value: unknown) => T;\n /**\n * Forwarded to the `httpResource` request object only (enables the\n * resource `progress` signal for downloads). Has no effect on the\n * plain Observable methods — use `HttpClient` events there directly.\n */\n reportProgress?: boolean;\n}\n\n/**\n * Headless typed HTTP client.\n *\n * Thin over `HttpClient` (so app interceptors, `HttpContext` escape hatches\n * and `provideHttpClient()` testing all keep working). Owns no backend:\n * the app provides `HttpClient`, this library only adds base-URL joining\n * plus configurable envelope unwrapping.\n *\n * Unwrap resolution per call: `options.unwrap ?? config.unwrap ?? true`,\n * key: `options.unwrapKey ?? config.unwrapKey ?? 'data'`.\n */\n@Service()\nexport class RiviniaHttp {\n private readonly http = inject(HttpClient);\n private readonly config = inject<RiviniaHttpConfig>(RIVINIA_HTTP_CONFIG, {\n optional: true,\n });\n\n get<T>(endpoint: string, options?: RiviniaRequestOptions): Observable<T> {\n return this.http\n .get<unknown>(this.url(endpoint), this.httpOptions(options))\n .pipe(map((body) => this.applyUnwrap<T>(body, options)));\n }\n\n post<T>(endpoint: string, body?: unknown, options?: RiviniaRequestOptions): Observable<T> {\n return this.http\n .post<unknown>(this.url(endpoint), body, this.httpOptions(options))\n .pipe(map((res) => this.applyUnwrap<T>(res, options)));\n }\n\n put<T>(endpoint: string, body?: unknown, options?: RiviniaRequestOptions): Observable<T> {\n return this.http\n .put<unknown>(this.url(endpoint), body, this.httpOptions(options))\n .pipe(map((res) => this.applyUnwrap<T>(res, options)));\n }\n\n patch<T>(endpoint: string, body?: unknown, options?: RiviniaRequestOptions): Observable<T> {\n return this.http\n .patch<unknown>(this.url(endpoint), body, this.httpOptions(options))\n .pipe(map((res) => this.applyUnwrap<T>(res, options)));\n }\n\n delete<T>(endpoint: string, options?: RiviniaDeleteOptions): Observable<T> {\n return this.http\n .delete<unknown>(this.url(endpoint), {\n ...this.httpOptions(options),\n body: options?.body,\n })\n .pipe(map((res) => this.applyUnwrap<T>(res, options)));\n }\n\n /** Binary download. Never unwrapped — envelopes don't apply to blobs. */\n getBlob(endpoint: string, options?: RiviniaRequestOptions): Observable<Blob> {\n return this.http.get(this.url(endpoint), {\n ...this.httpOptions(options),\n responseType: 'blob',\n });\n }\n\n /**\n * Reactive GET as an `httpResource` (stable since Angular v22).\n * Re-fetches when the endpoint signal changes; `undefined` disables.\n * Unwrap applies through `parse`, composed before a user `parse` if given.\n */\n getResource<T>(\n endpoint: string | Signal<string | undefined>,\n options?: RiviniaResourceOptions<T>,\n ): HttpResourceRef<T | undefined> {\n const { defaultValue, parse, reportProgress, ...requestOptions } = options ?? {};\n const request = () => {\n const ep = typeof endpoint === 'string' ? endpoint : endpoint();\n if (ep === undefined) return undefined;\n return {\n url: this.url(ep),\n params: this.toParams(requestOptions.params),\n headers: requestOptions.headers,\n context: requestOptions.context,\n withCredentials: requestOptions.withCredentials,\n ...(reportProgress !== undefined ? { reportProgress } : {}),\n };\n };\n const unwrapKey = this.resolveKey(requestOptions);\n const unwrapEnabled = this.resolveUnwrap(requestOptions);\n const base = { defaultValue: defaultValue as T | undefined };\n if (unwrapEnabled) {\n const applyParse = (raw: unknown): T =>\n parse ? parse(unwrapAs<T>(raw, unwrapKey)) : unwrapAs<T>(raw, unwrapKey);\n return defaultValue === undefined\n ? httpResource<T>(request, { ...base, parse: applyParse })\n : httpResource<T>(request, {\n ...base,\n defaultValue: defaultValue as T,\n parse: applyParse,\n });\n }\n return defaultValue === undefined\n ? httpResource<T>(request, { ...base, ...(parse ? { parse } : {}) })\n : httpResource<T>(request, {\n ...base,\n defaultValue: defaultValue as T,\n ...(parse ? { parse } : {}),\n });\n }\n\n private url(endpoint: string): string {\n // Deliberate passthrough: the caller owns the URL (environment, constant,\n // signal …). Use the exported `joinUrl` at the call site when composing.\n return endpoint;\n }\n\n private resolveUnwrap(options?: RiviniaRequestOptions): boolean {\n return options?.unwrap ?? this.config?.unwrap ?? DEFAULT_UNWRAP;\n }\n\n private resolveKey(options?: RiviniaRequestOptions): string {\n return options?.unwrapKey ?? this.config?.unwrapKey ?? DEFAULT_UNWRAP_KEY;\n }\n\n private applyUnwrap<T>(body: unknown, options?: RiviniaRequestOptions): T {\n if (!this.resolveUnwrap(options)) return body as T;\n return unwrapAs<T>(body, this.resolveKey(options));\n }\n\n private httpOptions(options?: RiviniaRequestOptions) {\n return {\n params: this.toParams(options?.params),\n headers: options?.headers,\n context: options?.context ?? new HttpContext(),\n withCredentials: options?.withCredentials,\n };\n }\n\n private toParams(input?: QueryParamsInput): HttpParams | undefined {\n if (!input) return undefined;\n if (input instanceof HttpParams) return input;\n let params = new HttpParams();\n for (const [key, value] of Object.entries(input)) {\n if (value !== undefined && value !== null) {\n params = params.set(key, String(value));\n }\n }\n return params;\n }\n}\n","import { EnvironmentProviders, inject, makeEnvironmentProviders } from '@angular/core';\nimport { provideHttpClient } from '@angular/common/http';\nimport { RIVINIA_HTTP_CONFIG, type RiviniaHttpConfig } from './http-config';\nimport { RiviniaHttp } from './rivinia-http';\n\n/**\n * Configures `@rivinia/http`.\n *\n * URLs are always used as given — build them at the call site from wherever\n * you want (see the exported `joinUrl` + your `environment.apiUrl`).\n *\n * Three backend styles (pick ONE):\n * - `provideRiviniaHttp()` alone — Angular ≥21 already injects `HttpClient` (fetch).\n * - `provideRiviniaHttp({ httpFeatures: [withInterceptors([...])] })` — single\n * call that also configures the backend. Do NOT additionally call\n * `provideHttpClient()` (double registration duplicates the XSRF interceptor).\n * - `provideHttpClient(...)` + `provideRiviniaHttp()` separately — equivalent,\n * keeps backend config visibly in app code (this is what `ng add` generates).\n *\n * @example\n * ```ts\n * provideRiviniaHttp({ unwrapKey: 'data' })\n * ```\n */\nexport function provideRiviniaHttp(config: RiviniaHttpConfig = {}): EnvironmentProviders {\n const { httpFeatures, ...rest } = config;\n return makeEnvironmentProviders([\n ...(httpFeatures ? [provideHttpClient(...httpFeatures)] : []),\n { provide: RIVINIA_HTTP_CONFIG, useValue: rest },\n ]);\n}\n\n/** Shorthand for `inject(RiviniaHttp)`. */\nexport function injectRiviniaHttp(): RiviniaHttp {\n return inject(RiviniaHttp);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@ const PROVIDE_HTTP_IMPORT = `import { provideRiviniaHttp } from '@rivinia/http';
|
|
|
10
10
|
* - Never overwrites user files; only inserts two imports + two providers.
|
|
11
11
|
* - Idempotent: safe to run twice (existing entries are detected and skipped).
|
|
12
12
|
* - Never provides the backend beyond `provideHttpClient()`; interceptors,
|
|
13
|
-
*
|
|
13
|
+
* URLs and unwrap options stay app-owned (see schema options).
|
|
14
14
|
*/
|
|
15
15
|
function ngAdd(options) {
|
|
16
16
|
return (tree, context) => {
|
|
@@ -32,23 +32,24 @@ function ngAdd(options) {
|
|
|
32
32
|
if (!content) {
|
|
33
33
|
throw new schematics_1.SchematicsException(`[rivinia/http] ${configPath} is empty.`);
|
|
34
34
|
}
|
|
35
|
-
// Deterministic mapping, no ambiguity:
|
|
36
|
-
// - unwrap
|
|
37
|
-
//
|
|
38
|
-
//
|
|
35
|
+
// Deterministic mapping, no ambiguity (library default: disabled):
|
|
36
|
+
// - unwrap !== true → emit nothing about unwrapping (raw passthrough).
|
|
37
|
+
// A custom key given alongside is ignored, said plainly in logs.
|
|
38
|
+
// - unwrap === true → emit `unwrap: true`, plus the key only when custom
|
|
39
|
+
// (anything but ''/'data', which both mean the library default key).
|
|
40
|
+
// URLs are never touched: the caller owns them (environment, joinUrl, …).
|
|
39
41
|
const parts = [];
|
|
40
|
-
|
|
41
|
-
parts.push(`baseUrl: '${options.baseUrl}'`);
|
|
42
|
-
}
|
|
43
|
-
const unwrapEnabled = options.unwrap !== false;
|
|
42
|
+
const unwrapEnabled = options.unwrap === true;
|
|
44
43
|
if (!unwrapEnabled) {
|
|
45
|
-
parts.push('unwrap: false');
|
|
46
44
|
if (options.unwrapKey && options.unwrapKey !== 'data') {
|
|
47
|
-
context.logger.info(`[rivinia/http] unwrapping
|
|
45
|
+
context.logger.info(`[rivinia/http] unwrapping not enabled — custom unwrapKey '${options.unwrapKey}' ignored.`);
|
|
48
46
|
}
|
|
49
47
|
}
|
|
50
|
-
else
|
|
51
|
-
parts.push(
|
|
48
|
+
else {
|
|
49
|
+
parts.push('unwrap: true');
|
|
50
|
+
if (options.unwrapKey && options.unwrapKey.length > 0 && options.unwrapKey !== 'data') {
|
|
51
|
+
parts.push(`unwrapKey: '${options.unwrapKey}'`);
|
|
52
|
+
}
|
|
52
53
|
}
|
|
53
54
|
const provideArg = parts.length > 0 ? `{ ${parts.join(', ')} }` : '';
|
|
54
55
|
let changed = false;
|
|
@@ -4,17 +4,9 @@
|
|
|
4
4
|
"title": "Rivinia HTTP ng-add options",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
|
-
"baseUrl": {
|
|
8
|
-
"type": "string",
|
|
9
|
-
"description": "Base URL passed as provideRiviniaHttp({ baseUrl }). Omit to configure later.",
|
|
10
|
-
"x-prompt": {
|
|
11
|
-
"message": "API base URL for this project (Enter to skip)?",
|
|
12
|
-
"type": "input"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
7
|
"project": {
|
|
16
8
|
"type": "string",
|
|
17
|
-
"description": "Angular project
|
|
9
|
+
"description": "Angular CLI project name — only for multi-app workspaces. Defaults to the workspace default project. Unrelated to unwrap scope.",
|
|
18
10
|
"default": ""
|
|
19
11
|
},
|
|
20
12
|
"unwrapKey": {
|
package/types/rivinia-http.d.ts
CHANGED
|
@@ -5,15 +5,16 @@ import { Observable } from "rxjs";
|
|
|
5
5
|
/**
|
|
6
6
|
* Global configuration for `@rivinia/http`.
|
|
7
7
|
*
|
|
8
|
-
* Every field is optional and can be overridden per request
|
|
9
|
-
*
|
|
8
|
+
* Every field is optional and can be overridden per request.
|
|
9
|
+
* URLs are ALWAYS used as given — the library never joins, prefixes, or
|
|
10
|
+
* rewrites them. Build them at the call site from wherever you want
|
|
11
|
+
* (`environment.apiUrl`, a constant, a signal …) with the exported `joinUrl`.
|
|
10
12
|
*/
|
|
11
13
|
interface RiviniaHttpConfig {
|
|
12
|
-
/** Base URL joined with every relative endpoint. No trailing slash needed. */
|
|
13
|
-
baseUrl?: string;
|
|
14
14
|
/**
|
|
15
15
|
* Unwrap `{ [unwrapKey]: payload }` envelopes by default.
|
|
16
|
-
* @default
|
|
16
|
+
* @default false — the library assumes nothing about your backend;
|
|
17
|
+
* opt in globally here or per call with `{ unwrap: true }`.
|
|
17
18
|
*/
|
|
18
19
|
unwrap?: boolean;
|
|
19
20
|
/**
|
|
@@ -38,7 +39,7 @@ interface RiviniaHttpConfig {
|
|
|
38
39
|
* @internal
|
|
39
40
|
*/
|
|
40
41
|
declare const RIVINIA_HTTP_CONFIG: InjectionToken<RiviniaHttpConfig>;
|
|
41
|
-
declare const DEFAULT_UNWRAP =
|
|
42
|
+
declare const DEFAULT_UNWRAP = false;
|
|
42
43
|
declare const DEFAULT_UNWRAP_KEY = "data";
|
|
43
44
|
/** Shorthand for building query strings; `null`/`undefined` values are dropped. */
|
|
44
45
|
type QueryParamsInput = HttpParams | Record<string, string | number | boolean | undefined | null>;
|
|
@@ -126,17 +127,20 @@ export declare function unwrapAs<T>(body: unknown, key: string): T;
|
|
|
126
127
|
/**
|
|
127
128
|
* Configures `@rivinia/http`.
|
|
128
129
|
*
|
|
129
|
-
*
|
|
130
|
+
* URLs are always used as given — build them at the call site from wherever
|
|
131
|
+
* you want (see the exported `joinUrl` + your `environment.apiUrl`).
|
|
132
|
+
*
|
|
133
|
+
* Three backend styles (pick ONE):
|
|
130
134
|
* - `provideRiviniaHttp()` alone — Angular ≥21 already injects `HttpClient` (fetch).
|
|
131
|
-
* - `provideRiviniaHttp({ httpFeatures: [withInterceptors([...])] })` — single
|
|
132
|
-
* that also configures the backend. Do NOT additionally call
|
|
135
|
+
* - `provideRiviniaHttp({ httpFeatures: [withInterceptors([...])] })` — single
|
|
136
|
+
* call that also configures the backend. Do NOT additionally call
|
|
133
137
|
* `provideHttpClient()` (double registration duplicates the XSRF interceptor).
|
|
134
|
-
* - `provideHttpClient(...)` + `provideRiviniaHttp()` separately — equivalent,
|
|
135
|
-
* backend config visibly in app code (this is what `ng add` generates).
|
|
138
|
+
* - `provideHttpClient(...)` + `provideRiviniaHttp()` separately — equivalent,
|
|
139
|
+
* keeps backend config visibly in app code (this is what `ng add` generates).
|
|
136
140
|
*
|
|
137
141
|
* @example
|
|
138
142
|
* ```ts
|
|
139
|
-
* provideRiviniaHttp({
|
|
143
|
+
* provideRiviniaHttp({ unwrapKey: 'data' })
|
|
140
144
|
* ```
|
|
141
145
|
*/
|
|
142
146
|
export declare function provideRiviniaHttp(config?: RiviniaHttpConfig): EnvironmentProviders;
|