@rivinia/http 0.0.0-stage → 0.1.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 CHANGED
@@ -1,3 +1,107 @@
1
- # Temporary Holding Version
1
+ # @rivinia/http
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/@rivinia/http.svg)](https://www.npmjs.com/package/@rivinia/http)
4
+ [![license](https://img.shields.io/npm/l/@rivinia/http.svg)](https://www.npmjs.com/package/@rivinia/http)
5
+
6
+ Headless typed HTTP client for Angular 22+. Base-URL joining, configurable
7
+ envelope unwrapping, plain Observables **and** `httpResource` — with zero
8
+ styling, zero backend opinions, and interceptors left to your app.
9
+
10
+ - Version: **0.1.0**
11
+ - Requires: **Angular `^22.0.0`**, `rxjs` (same range Angular uses), `tslib`
12
+ - Since Angular 21 `HttpClient` (fetch backend) is injectable with zero setup.
13
+ Call `provideHttpClient()` / pass `httpFeatures` only when you configure
14
+ interceptors or other backend features.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install @rivinia/http
20
+ ng add @rivinia/http # interactive: asks baseUrl + envelope key, then wires app.config.ts
21
+ ```
22
+
23
+ ## Quick start — pick ONE backend style
24
+
25
+ ```ts
26
+ // app.config.ts — style A: nothing to configure (Angular ≥21 default)
27
+ providers: [provideRiviniaHttp({ baseUrl: 'https://api.example.com' })];
28
+
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
+ ];
36
+
37
+ // style C: explicit backend config, like the official setup guide
38
+ providers: [provideHttpClient(withInterceptors([authInterceptor])), provideRiviniaHttp({ baseUrl })];
39
+ ```
40
+
41
+ ```ts
42
+ // Plain Observable (mutations, one-shot reads)
43
+ export class TripService {
44
+ private api = injectRiviniaHttp();
45
+ list = () => this.api.get<Trip[]>('trips');
46
+ create = (dto: CreateTrip) => this.api.post<Trip>('trips', dto);
47
+ }
48
+
49
+ // Reactive resource (auto refetch, loading/error signals)
50
+ export class TripPage {
51
+ page = signal(1);
52
+ trips = this.api.getResource<Trip[]>(() => `trips?page=${this.page()}`);
53
+ }
54
+ ```
55
+
56
+ ```html
57
+ @if (trips.hasValue()) {
58
+ @for (t of trips.value(); track t.id) { <p>{{ t.title }}</p> }
59
+ } @else if (trips.isLoading()) {
60
+ <p>Loading…</p>
61
+ }
62
+ ```
63
+
64
+ ## Envelope unwrapping
65
+
66
+ Backends that wrap payloads (`{ data, error, isSuccess }`) are unwrapped
67
+ automatically. Control it globally, per call, or turn it off:
68
+
69
+ ```ts
70
+ provideRiviniaHttp({ unwrapKey: 'data' }); // global default
71
+ this.api.get<Legacy>('legacy', { unwrapKey: 'result' }); // this call only
72
+ this.api.get<Raw>('raw', { unwrap: false }); // raw body
73
+ ```
74
+
75
+ `getResource` applies the same rules through `parse` (your own `parse` runs
76
+ after unwrapping — ideal for Zod/Valibot validation).
77
+
78
+ ## API
79
+
80
+ | Export | Kind | Purpose |
81
+ |---|---|---|
82
+ | `provideRiviniaHttp(config?)` | provider | `{ baseUrl?, unwrap?=true, unwrapKey?='data', httpFeatures? }` |
83
+ | `RiviniaHttp` | `@Service()` | `get/post/put/patch/delete`, `getBlob`, `getResource` |
84
+ | `injectRiviniaHttp()` | function | shorthand for `inject(RiviniaHttp)` |
85
+ | `joinUrl(base, endpoint)` | util | slash-safe join; absolute URLs pass through |
86
+ | `unwrapAs(body, key)` | util | recursive envelope extraction |
87
+ | types | types | `RiviniaHttpConfig`, `RiviniaRequestOptions`, `RiviniaDeleteOptions`, `RiviniaResourceOptions`, `QueryParamsInput` |
88
+
89
+ `getBlob` is never unwrapped. `HttpContext` passes through untouched, so
90
+ app interceptors (auth opt-out, error toasts) keep working. For caching,
91
+ polling, retry and TanStack Query interop see [RECIPES.md](./RECIPES.md).
92
+
93
+ ## Testing your app code
94
+
95
+ Standard Angular tooling — nothing custom to learn:
96
+
97
+ ```ts
98
+ TestBed.configureTestingModule({
99
+ providers: [provideHttpClient(), provideHttpClientTesting(), provideRiviniaHttp({ baseUrl: '/api' })],
100
+ });
101
+ api.get('users').subscribe();
102
+ TestBed.inject(HttpTestingController).expectOne('/api/users').flush({ data: [] });
103
+ ```
104
+
105
+ ## License
106
+
107
+ MIT.
@@ -0,0 +1,148 @@
1
+ import * as i0 from "@angular/core";
2
+ import { InjectionToken, Service, inject, makeEnvironmentProviders } from "@angular/core";
3
+ import { HttpClient, HttpContext, HttpParams, httpResource, provideHttpClient } from "@angular/common/http";
4
+ import { map } from "rxjs";
5
+ function joinUrl(baseUrl, endpoint) {
6
+ if (/^https?:\/\//i.test(endpoint)) return endpoint;
7
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
8
+ const path = endpoint.startsWith("/") ? endpoint.slice(1) : endpoint;
9
+ return path ? `${base}/${path}` : base;
10
+ }
11
+ function unwrapAs(body, key) {
12
+ if (isObjectRecord(body) && hasOwn(body, key)) return unwrapAs(body[key], key);
13
+ return body;
14
+ }
15
+ function isObjectRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+ function hasOwn(obj, key) {
19
+ return Object.prototype.hasOwnProperty.call(obj, key);
20
+ }
21
+ const RIVINIA_HTTP_CONFIG = new InjectionToken("RIVINIA_HTTP_CONFIG");
22
+ var RiviniaHttp = class RiviniaHttp {
23
+ http = inject(HttpClient);
24
+ config = inject(RIVINIA_HTTP_CONFIG, { optional: true });
25
+ get(endpoint, options) {
26
+ return this.http.get(this.url(endpoint), this.httpOptions(options)).pipe(map((body) => this.applyUnwrap(body, options)));
27
+ }
28
+ post(endpoint, body, options) {
29
+ return this.http.post(this.url(endpoint), body, this.httpOptions(options)).pipe(map((res) => this.applyUnwrap(res, options)));
30
+ }
31
+ put(endpoint, body, options) {
32
+ return this.http.put(this.url(endpoint), body, this.httpOptions(options)).pipe(map((res) => this.applyUnwrap(res, options)));
33
+ }
34
+ patch(endpoint, body, options) {
35
+ return this.http.patch(this.url(endpoint), body, this.httpOptions(options)).pipe(map((res) => this.applyUnwrap(res, options)));
36
+ }
37
+ delete(endpoint, options) {
38
+ return this.http.delete(this.url(endpoint), {
39
+ ...this.httpOptions(options),
40
+ body: options?.body
41
+ }).pipe(map((res) => this.applyUnwrap(res, options)));
42
+ }
43
+ getBlob(endpoint, options) {
44
+ return this.http.get(this.url(endpoint), {
45
+ ...this.httpOptions(options),
46
+ responseType: "blob"
47
+ });
48
+ }
49
+ getResource(endpoint, options) {
50
+ const { defaultValue, parse, reportProgress, ...requestOptions } = options ?? {};
51
+ const request = () => {
52
+ const ep = typeof endpoint === "string" ? endpoint : endpoint();
53
+ if (ep === void 0) return void 0;
54
+ return {
55
+ url: this.url(ep),
56
+ params: this.toParams(requestOptions.params),
57
+ headers: requestOptions.headers,
58
+ context: requestOptions.context,
59
+ withCredentials: requestOptions.withCredentials,
60
+ ...reportProgress !== void 0 ? { reportProgress } : {}
61
+ };
62
+ };
63
+ const unwrapKey = this.resolveKey(requestOptions);
64
+ const unwrapEnabled = this.resolveUnwrap(requestOptions);
65
+ const base = { defaultValue };
66
+ if (unwrapEnabled) {
67
+ const applyParse = (raw) => parse ? parse(unwrapAs(raw, unwrapKey)) : unwrapAs(raw, unwrapKey);
68
+ return defaultValue === void 0 ? httpResource(request, {
69
+ ...base,
70
+ parse: applyParse
71
+ }) : httpResource(request, {
72
+ ...base,
73
+ defaultValue,
74
+ parse: applyParse
75
+ });
76
+ }
77
+ return defaultValue === void 0 ? httpResource(request, {
78
+ ...base,
79
+ ...parse ? { parse } : {}
80
+ }) : httpResource(request, {
81
+ ...base,
82
+ defaultValue,
83
+ ...parse ? { parse } : {}
84
+ });
85
+ }
86
+ url(endpoint) {
87
+ return joinUrl(this.config?.baseUrl ?? "", endpoint);
88
+ }
89
+ resolveUnwrap(options) {
90
+ return options?.unwrap ?? this.config?.unwrap ?? true;
91
+ }
92
+ resolveKey(options) {
93
+ return options?.unwrapKey ?? this.config?.unwrapKey ?? "data";
94
+ }
95
+ applyUnwrap(body, options) {
96
+ if (!this.resolveUnwrap(options)) return body;
97
+ return unwrapAs(body, this.resolveKey(options));
98
+ }
99
+ httpOptions(options) {
100
+ return {
101
+ params: this.toParams(options?.params),
102
+ headers: options?.headers,
103
+ context: options?.context ?? new HttpContext(),
104
+ withCredentials: options?.withCredentials
105
+ };
106
+ }
107
+ toParams(input) {
108
+ if (!input) return void 0;
109
+ if (input instanceof HttpParams) return input;
110
+ let params = new HttpParams();
111
+ for (const [key, value] of Object.entries(input)) if (value !== void 0 && value !== null) params = params.set(key, String(value));
112
+ return params;
113
+ }
114
+ static ɵfac = i0.ɵɵngDeclareFactory({
115
+ minVersion: "12.0.0",
116
+ version: "22.2.2",
117
+ ngImport: i0,
118
+ type: RiviniaHttp,
119
+ deps: [],
120
+ target: i0.ɵɵFactoryTarget.Service
121
+ });
122
+ static ɵprov = i0.ɵɵngDeclareService({
123
+ minVersion: "22.0.0",
124
+ version: "22.2.2",
125
+ ngImport: i0,
126
+ type: RiviniaHttp
127
+ });
128
+ };
129
+ i0.ɵɵngDeclareClassMetadata({
130
+ minVersion: "12.0.0",
131
+ version: "22.2.2",
132
+ ngImport: i0,
133
+ type: RiviniaHttp,
134
+ decorators: [{ type: Service }]
135
+ });
136
+ function provideRiviniaHttp(config = {}) {
137
+ const { httpFeatures, ...rest } = config;
138
+ return makeEnvironmentProviders([...httpFeatures ? [provideHttpClient(...httpFeatures)] : [], {
139
+ provide: RIVINIA_HTTP_CONFIG,
140
+ useValue: rest
141
+ }]);
142
+ }
143
+ function injectRiviniaHttp() {
144
+ return inject(RiviniaHttp);
145
+ }
146
+ export { RiviniaHttp, injectRiviniaHttp, joinUrl, provideRiviniaHttp, unwrapAs };
147
+
148
+ //# sourceMappingURL=rivinia-http.mjs.map
@@ -0,0 +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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"}
package/package.json CHANGED
@@ -1,6 +1,36 @@
1
1
  {
2
2
  "name": "@rivinia/http",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Headless typed HTTP client for Angular: envelope unwrapping, plain Observables and httpResource.",
5
+ "keywords": [
6
+ "angular",
7
+ "http",
8
+ "http-client",
9
+ "signals",
10
+ "http-resource",
11
+ "typescript"
12
+ ],
13
+ "license": "MIT",
14
+ "peerDependencies": {
15
+ "@angular/common": "^22.0.0",
16
+ "@angular/core": "^22.0.0",
17
+ "rxjs": "^6.5.3 || ^7.4.0"
18
+ },
19
+ "dependencies": {
20
+ "tslib": "^2.3.0"
21
+ },
22
+ "sideEffects": false,
23
+ "schematics": "./schematics/collection.json",
24
+ "module": "fesm2022/rivinia-http.mjs",
25
+ "typings": "types/rivinia-http.d.ts",
26
+ "exports": {
27
+ "./package.json": {
28
+ "default": "./package.json"
29
+ },
30
+ ".": {
31
+ "types": "./types/rivinia-http.d.ts",
32
+ "default": "./fesm2022/rivinia-http.mjs"
33
+ }
34
+ },
35
+ "type": "module"
6
36
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
3
+ "schematics": {
4
+ "ng-add": {
5
+ "description": "Add provideHttpClient() and provideRiviniaHttp() to the application config.",
6
+ "factory": "./ng-add/index.cjs#ngAdd",
7
+ "schema": "./ng-add/schema.json"
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ngAdd = ngAdd;
4
+ const schematics_1 = require("@angular-devkit/schematics");
5
+ const HTTP_CLIENT_IMPORT = `import { provideHttpClient } from '@angular/common/http';`;
6
+ const PROVIDE_HTTP_IMPORT = `import { provideRiviniaHttp } from '@rivinia/http';`;
7
+ /**
8
+ * `ng add @rivinia/http` — wires the library into a standalone app config.
9
+ *
10
+ * - Never overwrites user files; only inserts two imports + two providers.
11
+ * - Idempotent: safe to run twice (existing entries are detected and skipped).
12
+ * - Never provides the backend beyond `provideHttpClient()`; interceptors,
13
+ * base URL policy and unwrap options stay app-owned (see `baseUrl` option).
14
+ */
15
+ function ngAdd(options) {
16
+ return (tree, context) => {
17
+ let configPath;
18
+ try {
19
+ configPath = findAppConfig(tree, options.project);
20
+ }
21
+ catch (error) {
22
+ context.logger.warn(`[rivinia/http] workspace lookup failed (${error instanceof Error ? error.message : error}) — skipping. ` +
23
+ `Add provideHttpClient() and provideRiviniaHttp() manually (see README).`);
24
+ return tree;
25
+ }
26
+ if (!configPath || !tree.exists(configPath)) {
27
+ context.logger.warn(`[rivinia/http] app.config.ts not found — skipping. ` +
28
+ `Add provideHttpClient() and provideRiviniaHttp() manually (see README).`);
29
+ return tree;
30
+ }
31
+ let content = tree.read(configPath)?.toString('utf-8') ?? '';
32
+ if (!content) {
33
+ throw new schematics_1.SchematicsException(`[rivinia/http] ${configPath} is empty.`);
34
+ }
35
+ // Deterministic mapping, no ambiguity:
36
+ // - unwrap === false → { ..., unwrap: false } (key ignored, said plainly in logs)
37
+ // - unwrap !== false → key emitted only when custom (anything but ''/'data',
38
+ // which both mean the library default — emitting them would be noise).
39
+ const parts = [];
40
+ if (options.baseUrl && options.baseUrl.length > 0) {
41
+ parts.push(`baseUrl: '${options.baseUrl}'`);
42
+ }
43
+ const unwrapEnabled = options.unwrap !== false;
44
+ if (!unwrapEnabled) {
45
+ parts.push('unwrap: false');
46
+ if (options.unwrapKey && options.unwrapKey !== 'data') {
47
+ context.logger.info(`[rivinia/http] unwrapping disabled — custom unwrapKey '${options.unwrapKey}' ignored.`);
48
+ }
49
+ }
50
+ else if (options.unwrapKey && options.unwrapKey.length > 0 && options.unwrapKey !== 'data') {
51
+ parts.push(`unwrapKey: '${options.unwrapKey}'`);
52
+ }
53
+ const provideArg = parts.length > 0 ? `{ ${parts.join(', ')} }` : '';
54
+ let changed = false;
55
+ if (!content.includes(HTTP_CLIENT_IMPORT)) {
56
+ content = insertAfterLastImport(content, HTTP_CLIENT_IMPORT);
57
+ changed = true;
58
+ }
59
+ if (!content.includes(PROVIDE_HTTP_IMPORT)) {
60
+ content = insertAfterLastImport(content, PROVIDE_HTTP_IMPORT);
61
+ changed = true;
62
+ }
63
+ if (!content.includes('provideHttpClient(')) {
64
+ content = insertProvider(content, 'provideHttpClient()');
65
+ changed = true;
66
+ }
67
+ else {
68
+ context.logger.info('[rivinia/http] provideHttpClient() already present — skipped.');
69
+ }
70
+ const provideCall = `provideRiviniaHttp(${provideArg})`;
71
+ if (!content.includes('provideRiviniaHttp(')) {
72
+ content = insertProvider(content, provideCall);
73
+ changed = true;
74
+ }
75
+ else {
76
+ context.logger.info('[rivinia/http] provideRiviniaHttp() already present — skipped.');
77
+ }
78
+ if (changed) {
79
+ tree.overwrite(configPath, content);
80
+ context.logger.info(`[rivinia/http] wired into ${configPath}.`);
81
+ }
82
+ return tree;
83
+ };
84
+ }
85
+ function defaultProjectName(tree, requested) {
86
+ if (requested)
87
+ return requested;
88
+ const angularJson = tree.read('/angular.json')?.toString('utf-8');
89
+ if (!angularJson) {
90
+ throw new schematics_1.SchematicsException('[rivinia/http] angular.json not found.');
91
+ }
92
+ const workspace = JSON.parse(angularJson);
93
+ if (workspace.defaultProject && typeof workspace.defaultProject === 'string') {
94
+ return workspace.defaultProject;
95
+ }
96
+ const names = Object.keys(workspace.projects ?? {});
97
+ if (names.length === 0) {
98
+ throw new schematics_1.SchematicsException('[rivinia/http] no projects in angular.json.');
99
+ }
100
+ return names[0];
101
+ }
102
+ function findAppConfig(tree, requestedProject) {
103
+ const angularJson = tree.read('/angular.json')?.toString('utf-8');
104
+ if (!angularJson)
105
+ return null;
106
+ const workspace = JSON.parse(angularJson);
107
+ const name = defaultProjectName(tree, requestedProject);
108
+ const project = workspace.projects?.[name];
109
+ if (!project)
110
+ return null;
111
+ const sourceRoot = project.sourceRoot ?? (project.root ? `${project.root}/src` : 'src');
112
+ return `/${sourceRoot}/app/app.config.ts`;
113
+ }
114
+ function insertAfterLastImport(content, line) {
115
+ const lines = content.split('\n');
116
+ let idx = -1;
117
+ for (let i = 0; i < lines.length; i++) {
118
+ if (/^\s*import\s/.test(lines[i]))
119
+ idx = i;
120
+ }
121
+ lines.splice(idx + 1, 0, line);
122
+ return lines.join('\n');
123
+ }
124
+ function insertProvider(content, call) {
125
+ const match = /providers\s*:\s*\[/.exec(content);
126
+ if (!match) {
127
+ throw new schematics_1.SchematicsException('[rivinia/http] no `providers: [` array found in app.config.ts — add providers manually.');
128
+ }
129
+ const at = match.index + match[0].length;
130
+ return `${content.slice(0, at)}\n ${call},${content.slice(at)}`;
131
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "rivinia-http-ng-add",
4
+ "title": "Rivinia HTTP ng-add options",
5
+ "type": "object",
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
+ "project": {
16
+ "type": "string",
17
+ "description": "Angular project to modify. Defaults to the workspace default project.",
18
+ "default": ""
19
+ },
20
+ "unwrapKey": {
21
+ "type": "string",
22
+ "description": "Envelope key passed as provideRiviniaHttp({ unwrapKey }). Asked only in effect when unwrapping is enabled; omit to keep the 'data' default.",
23
+ "x-prompt": {
24
+ "message": "Backend envelope key (Enter for the 'data' default)?",
25
+ "type": "input"
26
+ }
27
+ },
28
+ "unwrap": {
29
+ "type": "boolean",
30
+ "description": "Enable envelope unwrapping project-wide. When false, provideRiviniaHttp({ unwrap: false }) is emitted and unwrapKey is ignored.",
31
+ "x-prompt": {
32
+ "message": "Enable backend envelope unwrapping for this project?",
33
+ "type": "confirmation"
34
+ }
35
+ }
36
+ },
37
+ "required": []
38
+ }
@@ -0,0 +1,145 @@
1
+ import { HttpContext, HttpFeature, HttpFeatureKind, HttpHeaders, HttpParams, HttpResourceRef } from "@angular/common/http";
2
+ import * as i0 from "@angular/core";
3
+ import { EnvironmentProviders, InjectionToken, Signal } from "@angular/core";
4
+ import { Observable } from "rxjs";
5
+ /**
6
+ * Global configuration for `@rivinia/http`.
7
+ *
8
+ * Every field is optional and can be overridden per request, except
9
+ * `baseUrl` which only exists globally (absolute URLs always pass through).
10
+ */
11
+ interface RiviniaHttpConfig {
12
+ /** Base URL joined with every relative endpoint. No trailing slash needed. */
13
+ baseUrl?: string;
14
+ /**
15
+ * Unwrap `{ [unwrapKey]: payload }` envelopes by default.
16
+ * @default true (BRT convention: backends wrap payloads in `{ data }`).
17
+ */
18
+ unwrap?: boolean;
19
+ /**
20
+ * Envelope key unwrapped when `unwrap` is enabled.
21
+ * @default 'data'
22
+ */
23
+ unwrapKey?: string;
24
+ /**
25
+ * Optional `HttpClient` features (`withInterceptors(...)`, `withXhr()`, …),
26
+ * applied via a single internal `provideHttpClient(...httpFeatures)` call.
27
+ *
28
+ * Use this INSTEAD of calling `provideHttpClient()` yourself — calling both
29
+ * registers the built-in XSRF interceptor twice (verified against Angular
30
+ * 22.2.2 source: `xsrfInterceptorFn` is a multi-provider with no cross-call
31
+ * dedup). Omit it entirely on Angular ≥21 when you need no features:
32
+ * `HttpClient` (fetch backend) is injectable by default.
33
+ */
34
+ httpFeatures?: HttpFeature<HttpFeatureKind>[];
35
+ }
36
+ /**
37
+ * Internal configuration token. Consumers must use `provideRiviniaHttp()`.
38
+ * @internal
39
+ */
40
+ declare const RIVINIA_HTTP_CONFIG: InjectionToken<RiviniaHttpConfig>;
41
+ declare const DEFAULT_UNWRAP = true;
42
+ declare const DEFAULT_UNWRAP_KEY = "data";
43
+ /** Shorthand for building query strings; `null`/`undefined` values are dropped. */
44
+ type QueryParamsInput = HttpParams | Record<string, string | number | boolean | undefined | null>;
45
+ /**
46
+ * Per-request options. `unwrap` / `unwrapKey` override the global
47
+ * `provideRiviniaHttp()` configuration for this call only.
48
+ */
49
+ interface RiviniaRequestOptions {
50
+ params?: QueryParamsInput;
51
+ headers?: HttpHeaders | Record<string, string | string[]>;
52
+ /** Pass-through for app-owned interceptors (auth opt-out, error policy, ...). */
53
+ context?: HttpContext;
54
+ withCredentials?: boolean;
55
+ /** Enable envelope unwrapping for this call (overrides global `unwrap`). */
56
+ unwrap?: boolean;
57
+ /** Envelope key for this call (overrides global `unwrapKey`). */
58
+ unwrapKey?: string;
59
+ }
60
+ interface RiviniaDeleteOptions extends RiviniaRequestOptions {
61
+ body?: unknown;
62
+ }
63
+ /** Options for `getResource` in addition to per-request options. */
64
+ interface RiviniaResourceOptions<T> extends RiviniaRequestOptions {
65
+ /** Value while idle/loading. Defaults to `undefined`. */
66
+ defaultValue?: T;
67
+ /**
68
+ * Runs AFTER envelope unwrapping: `raw -> unwrap -> parse`.
69
+ * Use for runtime validation (e.g. Zod) or shaping.
70
+ */
71
+ parse?: (value: unknown) => T;
72
+ /**
73
+ * Forwarded to the `httpResource` request object only (enables the
74
+ * resource `progress` signal for downloads). Has no effect on the
75
+ * plain Observable methods — use `HttpClient` events there directly.
76
+ */
77
+ reportProgress?: boolean;
78
+ }
79
+ /**
80
+ * Headless typed HTTP client.
81
+ *
82
+ * Thin over `HttpClient` (so app interceptors, `HttpContext` escape hatches
83
+ * and `provideHttpClient()` testing all keep working). Owns no backend:
84
+ * the app provides `HttpClient`, this library only adds base-URL joining
85
+ * plus configurable envelope unwrapping.
86
+ *
87
+ * Unwrap resolution per call: `options.unwrap ?? config.unwrap ?? true`,
88
+ * key: `options.unwrapKey ?? config.unwrapKey ?? 'data'`.
89
+ */
90
+ export declare class RiviniaHttp {
91
+ private readonly http;
92
+ private readonly config;
93
+ get<T>(endpoint: string, options?: RiviniaRequestOptions): Observable<T>;
94
+ post<T>(endpoint: string, body?: unknown, options?: RiviniaRequestOptions): Observable<T>;
95
+ put<T>(endpoint: string, body?: unknown, options?: RiviniaRequestOptions): Observable<T>;
96
+ patch<T>(endpoint: string, body?: unknown, options?: RiviniaRequestOptions): Observable<T>;
97
+ delete<T>(endpoint: string, options?: RiviniaDeleteOptions): Observable<T>;
98
+ /** Binary download. Never unwrapped — envelopes don't apply to blobs. */
99
+ getBlob(endpoint: string, options?: RiviniaRequestOptions): Observable<Blob>;
100
+ /**
101
+ * Reactive GET as an `httpResource` (stable since Angular v22).
102
+ * Re-fetches when the endpoint signal changes; `undefined` disables.
103
+ * Unwrap applies through `parse`, composed before a user `parse` if given.
104
+ */
105
+ getResource<T>(endpoint: string | Signal<string | undefined>, options?: RiviniaResourceOptions<T>): HttpResourceRef<T | undefined>;
106
+ private url;
107
+ private resolveUnwrap;
108
+ private resolveKey;
109
+ private applyUnwrap;
110
+ private httpOptions;
111
+ private toParams;
112
+ static ɵfac: i0.ɵɵFactoryDeclaration<RiviniaHttp, never>;
113
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
114
+ }
115
+ /**
116
+ * Joins a base URL and an endpoint without producing double slashes.
117
+ * Absolute `http(s)` endpoints pass through untouched.
118
+ */
119
+ export declare function joinUrl(baseUrl: string, endpoint: string): string;
120
+ /**
121
+ * Extracts `body[key]` when the backend wraps payloads in an envelope
122
+ * (e.g. `{ data, error, isSuccess }`); passes anything else through
123
+ * unchanged. Recursive, so nested envelopes collapse.
124
+ */
125
+ export declare function unwrapAs<T>(body: unknown, key: string): T;
126
+ /**
127
+ * Configures `@rivinia/http`.
128
+ *
129
+ * Three styles (pick ONE backend path):
130
+ * - `provideRiviniaHttp()` alone — Angular ≥21 already injects `HttpClient` (fetch).
131
+ * - `provideRiviniaHttp({ httpFeatures: [withInterceptors([...])] })` — single call
132
+ * that also configures the backend. Do NOT additionally call
133
+ * `provideHttpClient()` (double registration duplicates the XSRF interceptor).
134
+ * - `provideHttpClient(...)` + `provideRiviniaHttp()` separately — equivalent, keeps
135
+ * backend config visibly in app code (this is what `ng add` generates).
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * provideRiviniaHttp({ baseUrl: 'https://api.example.com', unwrapKey: 'data' })
140
+ * ```
141
+ */
142
+ export declare function provideRiviniaHttp(config?: RiviniaHttpConfig): EnvironmentProviders;
143
+ /** Shorthand for `inject(RiviniaHttp)`. */
144
+ export declare function injectRiviniaHttp(): RiviniaHttp;
145
+ export type { QueryParamsInput, RiviniaDeleteOptions, RiviniaHttpConfig, RiviniaRequestOptions, RiviniaResourceOptions };