ngx-openapi-typescript 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# ngx-openapi-typescript
|
|
2
|
+
|
|
3
|
+
Type-safe Angular resource signals and HTTP client backed by [openapi-typescript](https://openapi-ts.dev/).
|
|
4
|
+
|
|
5
|
+
This library provides two main utilities:
|
|
6
|
+
|
|
7
|
+
- **`createOpenApiResource`** — a reactive factory built on Angular's `httpResource` that gives you compile-time type safety for every HTTP request and response shape defined in your OpenAPI schema.
|
|
8
|
+
- **`OpenApiHttpClient`** — a typed wrapper around Angular's `HttpClient` for imperative (non-reactive) HTTP calls.
|
|
9
|
+
|
|
10
|
+
Both are driven by the types generated by `openapi-typescript`, so typos in paths, wrong parameter names, or incorrect body shapes are caught at **compile time**, not at runtime.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install ngx-openapi-typescript openapi-typescript-helpers
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This package is built for Angular `^21.2.0` and `^22.0.0` and expects these peer dependencies in your app:
|
|
19
|
+
|
|
20
|
+
- `@angular/common ^21.2.0 || ^22.0.0`
|
|
21
|
+
- `@angular/core ^21.2.0 || ^22.0.0`
|
|
22
|
+
- `openapi-typescript-helpers ^0.1.0`
|
|
23
|
+
|
|
24
|
+
If you want to generate types locally, also install the generator as a dev dependency:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm install -D openapi-typescript
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Generating types
|
|
31
|
+
|
|
32
|
+
Generate type definitions by pointing `openapi-typescript` at your OpenAPI spec:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npx openapi-typescript https://api.example.com/openapi.yaml -o src/api.types.ts
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This creates a `paths` interface that both utilities consume.
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
### `createOpenApiResource`
|
|
43
|
+
|
|
44
|
+
A factory for creating reactive Angular resource signals. Must be called inside an Angular injection context (component/service field initializer or constructor).
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import type { paths } from './api.types';
|
|
48
|
+
import { createOpenApiResource } from 'ngx-openapi-typescript';
|
|
49
|
+
|
|
50
|
+
@Component({ ... })
|
|
51
|
+
export class PetListComponent {
|
|
52
|
+
// Set up once per component/service
|
|
53
|
+
private readonly api = createOpenApiResource<paths>({
|
|
54
|
+
baseUrl: 'https://api.example.com',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Simple GET — no params required
|
|
58
|
+
protected readonly pets = this.api('/pets');
|
|
59
|
+
|
|
60
|
+
// Reactive GET with params — re-fetches when the signals read inside the lambda change
|
|
61
|
+
protected readonly petsLimited = this.api('/pets', () => ({
|
|
62
|
+
params: { query: { limit: 10 } },
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
// Pass signal-driven path params — re-fetches when petId() changes
|
|
66
|
+
private readonly petId = signal(1);
|
|
67
|
+
protected readonly pet = this.api('/pets/{petId}', () => ({
|
|
68
|
+
params: { path: { petId: this.petId() } },
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
// Return undefined to put the resource in Idle state (no request made)
|
|
72
|
+
private readonly enabled = signal(false);
|
|
73
|
+
protected readonly lazyPets = this.api('/pets', () =>
|
|
74
|
+
this.enabled() ? {} : undefined,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Method Override
|
|
80
|
+
|
|
81
|
+
The factory defaults to GET. You can pass a `method` option to use a different HTTP method,
|
|
82
|
+
but Angular recommends using `httpResource` only for reads. Prefer `OpenApiHttpClient` for mutations.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// Explicit method override (not recommended for production use)
|
|
86
|
+
protected readonly createResult = this.api('/pets', () => ({
|
|
87
|
+
body: { name: 'Buddy' },
|
|
88
|
+
}), { method: 'post' });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `OpenApiHttpClient`
|
|
92
|
+
|
|
93
|
+
A base class that wraps Angular's `HttpClient` with OpenAPI types. Ideal for imperative calls, mutations, or service-layer code.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import type { paths } from './api.types';
|
|
97
|
+
import { OpenApiHttpClient } from 'ngx-openapi-typescript';
|
|
98
|
+
import { Injectable } from '@angular/core';
|
|
99
|
+
|
|
100
|
+
@Injectable({ providedIn: 'root' })
|
|
101
|
+
export class PetApiClient extends OpenApiHttpClient<paths> {
|
|
102
|
+
constructor() {
|
|
103
|
+
super({ baseUrl: 'https://api.example.com' });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// In a component/service
|
|
110
|
+
@Component({ ... })
|
|
111
|
+
export class PetDetailComponent {
|
|
112
|
+
private readonly api = inject(PetApiClient);
|
|
113
|
+
|
|
114
|
+
load(id: number) {
|
|
115
|
+
this.api
|
|
116
|
+
.get('/pets/{petId}', { params: { path: { petId: id } } })
|
|
117
|
+
.subscribe(pet => console.log(pet)); // pet is typed as Pet
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
create(name: string) {
|
|
121
|
+
this.api
|
|
122
|
+
.post('/pets', { body: { name } })
|
|
123
|
+
.subscribe(newPet => console.log(newPet));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
# Build the library
|
|
132
|
+
ng build ngx-openapi-typescript
|
|
133
|
+
|
|
134
|
+
# Run tests
|
|
135
|
+
ng test ngx-openapi-typescript
|
|
136
|
+
|
|
137
|
+
# Start the demo app
|
|
138
|
+
ng serve demo
|
|
139
|
+
```
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { HttpParams, httpResource, HttpClient } from '@angular/common/http';
|
|
2
|
+
import { inject } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/** Builds a full URL by substituting path parameters into the path template */
|
|
5
|
+
function buildUrl(baseUrl, path, pathParams) {
|
|
6
|
+
let url = baseUrl + path;
|
|
7
|
+
if (pathParams) {
|
|
8
|
+
for (const [key, value] of Object.entries(pathParams)) {
|
|
9
|
+
url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return url;
|
|
13
|
+
}
|
|
14
|
+
/** Converts a query parameter map to an `HttpParams` instance */
|
|
15
|
+
function toHttpParams(query) {
|
|
16
|
+
if (!query)
|
|
17
|
+
return undefined;
|
|
18
|
+
let params = new HttpParams();
|
|
19
|
+
for (const [key, value] of Object.entries(query)) {
|
|
20
|
+
if (value === undefined || value === null)
|
|
21
|
+
continue;
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
for (const v of value) {
|
|
24
|
+
params = params.append(key, String(v));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
params = params.set(key, String(value));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return params;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Coerces the request object to a plain shape for URL building */
|
|
35
|
+
function toRequestShape(req) {
|
|
36
|
+
return (req ?? {});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates a type-safe callable factory for Angular `httpResource`s backed by an OpenAPI schema.
|
|
40
|
+
*
|
|
41
|
+
* Must be called inside an Angular injection context (e.g. component/service field initializer
|
|
42
|
+
* or constructor), because the returned function uses `httpResource` internally.
|
|
43
|
+
*
|
|
44
|
+
* The returned function defaults to GET. Pass `{ method: 'post' }` (or other method) in the
|
|
45
|
+
* options to use a different HTTP method. Note: Angular recommends using `httpResource` only
|
|
46
|
+
* for reads; prefer `OpenApiHttpClient` for mutations.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import type { paths } from './api.types'; // generated by openapi-typescript
|
|
51
|
+
*
|
|
52
|
+
* const api = createOpenApiResource<paths>({ baseUrl: 'https://api.example.com' });
|
|
53
|
+
*
|
|
54
|
+
* // Reactive GET resource (default)
|
|
55
|
+
* const pets = api('/pets', () => ({ params: { query: { limit: 10 } } }));
|
|
56
|
+
*
|
|
57
|
+
* // GET with path params (signal-driven)
|
|
58
|
+
* const petId = signal(1);
|
|
59
|
+
* const pet = api('/pets/{petId}', () => ({ params: { path: { petId: petId() } } }));
|
|
60
|
+
*
|
|
61
|
+
* // Explicit method override (prefer OpenApiHttpClient for mutations)
|
|
62
|
+
* const created = api('/pets', () => ({ body: { name: 'Buddy' } }), { method: 'post' });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
function createOpenApiResource(config) {
|
|
66
|
+
const baseUrl = config?.baseUrl ?? '';
|
|
67
|
+
return ((path, reqFnOrOpts, maybeOpts) => {
|
|
68
|
+
const hasRequestFn = typeof reqFnOrOpts === 'function';
|
|
69
|
+
const requestFn = hasRequestFn ? reqFnOrOpts : () => ({});
|
|
70
|
+
const options = hasRequestFn ? maybeOpts : reqFnOrOpts;
|
|
71
|
+
const method = (options?.method ?? 'get').toUpperCase();
|
|
72
|
+
return httpResource(() => {
|
|
73
|
+
const req = requestFn();
|
|
74
|
+
if (req === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
const r = toRequestShape(req);
|
|
77
|
+
return {
|
|
78
|
+
url: buildUrl(baseUrl, path, r.params?.path),
|
|
79
|
+
method,
|
|
80
|
+
body: r.body,
|
|
81
|
+
params: toHttpParams(r.params?.query),
|
|
82
|
+
headers: r.headers,
|
|
83
|
+
context: r.context,
|
|
84
|
+
withCredentials: r.withCredentials,
|
|
85
|
+
};
|
|
86
|
+
}, options);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Coerces the options to a plain shape */
|
|
91
|
+
function toShape(opts) {
|
|
92
|
+
return (opts ?? {});
|
|
93
|
+
}
|
|
94
|
+
function buildOptions(r) {
|
|
95
|
+
return {
|
|
96
|
+
responseType: 'json',
|
|
97
|
+
observe: 'body',
|
|
98
|
+
params: toHttpParams(r.params?.query),
|
|
99
|
+
// Cast needed: our public API allows ReadonlyArray<string> (consistent with httpResource),
|
|
100
|
+
// but Angular HttpClient methods require string[].
|
|
101
|
+
headers: r.headers,
|
|
102
|
+
context: r.context,
|
|
103
|
+
withCredentials: r.withCredentials,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A type-safe HTTP client for OpenAPI-described APIs.
|
|
108
|
+
*
|
|
109
|
+
* Wraps Angular's `HttpClient` and binds request/response types to an OpenAPI
|
|
110
|
+
* schema generated by `openapi-typescript`.
|
|
111
|
+
*
|
|
112
|
+
* Designed to be subclassed per-API:
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* import type { paths } from './api.types'; // generated by openapi-typescript
|
|
117
|
+
*
|
|
118
|
+
* @Injectable({ providedIn: 'root' })
|
|
119
|
+
* export class PetApiClient extends OpenApiHttpClient<paths> {
|
|
120
|
+
* constructor() {
|
|
121
|
+
* super({ baseUrl: 'https://api.example.com' });
|
|
122
|
+
* }
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* Or used directly inside an injection context:
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* const client = new OpenApiHttpClient<paths>({ baseUrl: 'https://api.example.com' });
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
class OpenApiHttpClient {
|
|
134
|
+
http = inject(HttpClient);
|
|
135
|
+
baseUrl;
|
|
136
|
+
constructor(config) {
|
|
137
|
+
this.baseUrl = config?.baseUrl ?? '';
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Performs a typed GET request.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* client.get('/pets', { params: { query: { limit: 10 } } }).subscribe(pets => console.log(pets));
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
get(path, options) {
|
|
148
|
+
const r = toShape(options);
|
|
149
|
+
return this.http.get(buildUrl(this.baseUrl, String(path), r.params?.path), buildOptions(r));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Performs a typed POST request.
|
|
153
|
+
*/
|
|
154
|
+
post(path, options) {
|
|
155
|
+
const r = toShape(options);
|
|
156
|
+
return this.http.post(buildUrl(this.baseUrl, String(path), r.params?.path), r.body, buildOptions(r));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Performs a typed PUT request.
|
|
160
|
+
*/
|
|
161
|
+
put(path, options) {
|
|
162
|
+
const r = toShape(options);
|
|
163
|
+
return this.http.put(buildUrl(this.baseUrl, String(path), r.params?.path), r.body, buildOptions(r));
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Performs a typed PATCH request.
|
|
167
|
+
*/
|
|
168
|
+
patch(path, options) {
|
|
169
|
+
const r = toShape(options);
|
|
170
|
+
return this.http.patch(buildUrl(this.baseUrl, String(path), r.params?.path), r.body, buildOptions(r));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Performs a typed DELETE request.
|
|
174
|
+
*/
|
|
175
|
+
delete(path, options) {
|
|
176
|
+
const r = toShape(options);
|
|
177
|
+
const httpOptions = buildOptions(r);
|
|
178
|
+
if (r.body !== undefined) {
|
|
179
|
+
httpOptions.body = r.body;
|
|
180
|
+
}
|
|
181
|
+
return this.http.delete(buildUrl(this.baseUrl, String(path), r.params?.path), httpOptions);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/*
|
|
186
|
+
* Public API Surface of ngx-openapi-typescript
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Generated bundle index. Do not edit.
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
export { OpenApiHttpClient, createOpenApiResource };
|
|
194
|
+
//# sourceMappingURL=ngx-openapi-typescript.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngx-openapi-typescript.mjs","sources":["../../../projects/ngx-openapi-typescript/src/lib/url-utils.ts","../../../projects/ngx-openapi-typescript/src/lib/open-api-resource.ts","../../../projects/ngx-openapi-typescript/src/lib/open-api-http-client.ts","../../../projects/ngx-openapi-typescript/src/public-api.ts","../../../projects/ngx-openapi-typescript/src/ngx-openapi-typescript.ts"],"sourcesContent":["import { HttpParams } from '@angular/common/http';\r\n\r\n/** Builds a full URL by substituting path parameters into the path template */\r\nexport function buildUrl(\r\n baseUrl: string,\r\n path: string,\r\n pathParams?: Record<string, string | number | boolean>,\r\n): string {\r\n let url = baseUrl + path;\r\n if (pathParams) {\r\n for (const [key, value] of Object.entries(pathParams)) {\r\n url = url.replace(`{${key}}`, encodeURIComponent(String(value)));\r\n }\r\n }\r\n return url;\r\n}\r\n\r\n/** Converts a query parameter map to an `HttpParams` instance */\r\nexport function toHttpParams(\r\n query?: Record<\r\n string,\r\n string | number | boolean | ReadonlyArray<string | number | boolean> | null | undefined\r\n >,\r\n): HttpParams | undefined {\r\n if (!query) return undefined;\r\n let params = new HttpParams();\r\n for (const [key, value] of Object.entries(query)) {\r\n if (value === undefined || value === null) continue;\r\n if (Array.isArray(value)) {\r\n for (const v of value) {\r\n params = params.append(key, String(v));\r\n }\r\n } else {\r\n params = params.set(key, String(value));\r\n }\r\n }\r\n return params;\r\n}\r\n","import {\r\n HttpContext,\r\n HttpHeaders,\r\n HttpResourceOptions,\r\n HttpResourceRef,\r\n httpResource,\r\n} from '@angular/common/http';\r\nimport type { PathsWithMethod, SuccessResponseJSON } from 'openapi-typescript-helpers';\r\nimport type { HasRequiredKeys, HttpMethod, OpenApiOperationRequest, PathRecord } from './types';\r\nimport { buildUrl as _buildUrl, toHttpParams as _toHttpParams } from './url-utils';\r\n\r\n// Re-export helpers so consumers and tests can import them from this module.\r\nexport { buildUrl, toHttpParams } from './url-utils';\r\n\r\n/** Configuration for creating an OpenAPI resource factory */\r\nexport interface OpenApiResourceConfig {\r\n /** Base URL to prepend to all resource paths */\r\n baseUrl?: string;\r\n}\r\n\r\n/** Options for the resource call, extending Angular's HttpResourceOptions with an optional HTTP method */\r\nexport type OpenApiResourceCallOptions<TValue, TError = unknown> = HttpResourceOptions<\r\n TValue,\r\n TError\r\n> & { method?: HttpMethod };\r\n\r\n/** Extracts the operation type for a given path and method */\r\ntype OpOf<Paths, Path extends keyof Paths, Method extends string> = Paths[Path] extends {\r\n [K in Method]: infer Op;\r\n}\r\n ? Op\r\n : never;\r\n\r\n/** Coerces the request object to a plain shape for URL building */\r\nfunction toRequestShape(req: unknown): {\r\n params?: {\r\n path?: Record<string, string | number | boolean>;\r\n query?: Record<string, string | number | boolean | null | undefined>;\r\n };\r\n body?: unknown;\r\n headers?: HttpHeaders | Record<string, string | ReadonlyArray<string>>;\r\n context?: HttpContext;\r\n withCredentials?: boolean;\r\n} {\r\n return ((req ?? {}) as ReturnType<typeof toRequestShape>);\r\n}\r\n\r\n/** Extract which HTTP methods a given path supports */\r\ntype MethodsOfPath<Paths, Path extends keyof Paths> = {\r\n [M in HttpMethod]: Paths[Path] extends { [K in M]: unknown } ? M : never;\r\n}[HttpMethod];\r\n\r\n/**\r\n * Narrows to only those paths (for the given method) where the full request object\r\n * has no required keys — i.e. `requestFn` can be safely omitted.\r\n */\r\ntype PathsWithOptionalRequest<Paths extends PathRecord, Method extends HttpMethod> = {\r\n [P in PathsWithMethod<Paths, Method> & keyof Paths]: HasRequiredKeys<\r\n OpenApiOperationRequest<NonNullable<OpOf<Paths, P, Method>>>\r\n > extends true\r\n ? never\r\n : P;\r\n}[PathsWithMethod<Paths, Method> & keyof Paths];\r\n\r\n/** The callable type returned by `createOpenApiResource` */\r\nexport interface OpenApiResource<Paths extends PathRecord> {\r\n /**\r\n * Creates a reactive resource for an OpenAPI endpoint with an explicit HTTP method.\r\n *\r\n * The `method` option is restricted to the methods the given path supports.\r\n */\r\n <Path extends keyof Paths & string, Method extends MethodsOfPath<Paths, Path>>(\r\n path: Path,\r\n requestFn: () => OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, Method>>> | undefined,\r\n options: OpenApiResourceCallOptions<\r\n SuccessResponseJSON<NonNullable<OpOf<Paths, Path, Method>>>,\r\n unknown\r\n > & { method: Method },\r\n ): HttpResourceRef<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, Method>>> | undefined>;\r\n\r\n /**\r\n * Creates a reactive GET resource for an OpenAPI endpoint (default).\r\n *\r\n * The resource re-fetches automatically when the signals read inside `requestFn` change.\r\n * When `requestFn` returns `undefined` the resource is put into the Idle state.\r\n */\r\n <Path extends PathsWithMethod<Paths, 'get'> & keyof Paths>(\r\n path: Path,\r\n requestFn: () => OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'get'>>> | undefined,\r\n options?: HttpResourceOptions<\r\n SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'get'>>>,\r\n unknown\r\n >,\r\n ): HttpResourceRef<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'get'>>> | undefined>;\r\n\r\n /**\r\n * Creates a reactive resource for an OpenAPI endpoint with an explicit HTTP method,\r\n * omitting `requestFn`. Only available when the operation has no required parameters or body.\r\n */\r\n <Path extends keyof Paths & string, Method extends MethodsOfPath<Paths, Path>>(\r\n path: Path extends PathsWithOptionalRequest<Paths, Method> ? Path : never,\r\n options: OpenApiResourceCallOptions<\r\n SuccessResponseJSON<NonNullable<OpOf<Paths, Path, Method>>>,\r\n unknown\r\n > & { method: Method },\r\n ): HttpResourceRef<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, Method>>> | undefined>;\r\n\r\n /**\r\n * Creates a reactive GET resource for an OpenAPI endpoint, omitting `requestFn`.\r\n * Only available when the operation has no required parameters or body.\r\n * The resource fetches immediately and always stays active.\r\n */\r\n <Path extends PathsWithOptionalRequest<Paths, 'get'>>(\r\n path: Path,\r\n options?: HttpResourceOptions<\r\n SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'get'>>>,\r\n unknown\r\n >,\r\n ): HttpResourceRef<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'get'>>> | undefined>;\r\n}\r\n\r\n/**\r\n * Creates a type-safe callable factory for Angular `httpResource`s backed by an OpenAPI schema.\r\n *\r\n * Must be called inside an Angular injection context (e.g. component/service field initializer\r\n * or constructor), because the returned function uses `httpResource` internally.\r\n *\r\n * The returned function defaults to GET. Pass `{ method: 'post' }` (or other method) in the\r\n * options to use a different HTTP method. Note: Angular recommends using `httpResource` only\r\n * for reads; prefer `OpenApiHttpClient` for mutations.\r\n *\r\n * @example\r\n * ```ts\r\n * import type { paths } from './api.types'; // generated by openapi-typescript\r\n *\r\n * const api = createOpenApiResource<paths>({ baseUrl: 'https://api.example.com' });\r\n *\r\n * // Reactive GET resource (default)\r\n * const pets = api('/pets', () => ({ params: { query: { limit: 10 } } }));\r\n *\r\n * // GET with path params (signal-driven)\r\n * const petId = signal(1);\r\n * const pet = api('/pets/{petId}', () => ({ params: { path: { petId: petId() } } }));\r\n *\r\n * // Explicit method override (prefer OpenApiHttpClient for mutations)\r\n * const created = api('/pets', () => ({ body: { name: 'Buddy' } }), { method: 'post' });\r\n * ```\r\n */\r\nexport function createOpenApiResource<Paths extends PathRecord>(\r\n config?: OpenApiResourceConfig,\r\n): OpenApiResource<Paths> {\r\n const baseUrl = config?.baseUrl ?? '';\r\n\r\n return ((\r\n path: string,\r\n reqFnOrOpts?: (() => unknown) | OpenApiResourceCallOptions<unknown>,\r\n maybeOpts?: OpenApiResourceCallOptions<unknown>,\r\n ) => {\r\n const hasRequestFn = typeof reqFnOrOpts === 'function';\r\n const requestFn = hasRequestFn ? reqFnOrOpts : () => ({});\r\n const options = hasRequestFn ? maybeOpts : (reqFnOrOpts as OpenApiResourceCallOptions<unknown> | undefined);\r\n const method = (options?.method ?? 'get').toUpperCase();\r\n\r\n return httpResource(\r\n () => {\r\n const req = requestFn();\r\n if (req === undefined) return undefined;\r\n const r = toRequestShape(req);\r\n return {\r\n url: _buildUrl(baseUrl, path, r.params?.path),\r\n method,\r\n body: r.body,\r\n params: _toHttpParams(r.params?.query),\r\n headers: r.headers,\r\n context: r.context,\r\n withCredentials: r.withCredentials,\r\n };\r\n },\r\n options,\r\n );\r\n }) as OpenApiResource<Paths>;\r\n}\r\n","import { HttpClient, HttpContext, HttpHeaders, HttpParams } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport type { Observable } from 'rxjs';\r\nimport type {\r\n PathsWithMethod,\r\n RequestBodyJSON,\r\n SuccessResponseJSON,\r\n} from 'openapi-typescript-helpers';\r\nimport type {\r\n OpenApiOperationRequest,\r\n PathRecord,\r\n} from './types';\r\nimport { buildUrl, toHttpParams } from './url-utils';\r\n\r\n/** Configuration for `OpenApiHttpClient` */\r\nexport interface OpenApiHttpClientConfig {\r\n /** Base URL to prepend to all request paths */\r\n baseUrl?: string;\r\n}\r\n\r\n/** Extracts the operation type for a given path and method */\r\ntype OpOf<Paths, Path extends keyof Paths, Method extends string> = Paths[Path] extends {\r\n [K in Method]: infer Op;\r\n}\r\n ? Op\r\n : never;\r\n\r\n/** Coerces the options to a plain shape */\r\nfunction toShape(opts: unknown): {\r\n params?: {\r\n path?: Record<string, string | number | boolean>;\r\n query?: Record<string, string | number | boolean | null | undefined>;\r\n };\r\n body?: unknown;\r\n headers?: HttpHeaders | Record<string, string | ReadonlyArray<string>>;\r\n context?: HttpContext;\r\n withCredentials?: boolean;\r\n} {\r\n return ((opts ?? {}) as ReturnType<typeof toShape>);\r\n}\r\n\r\nfunction buildOptions(r: ReturnType<typeof toShape>): {\r\n responseType: 'json';\r\n observe: 'body';\r\n params?: HttpParams;\r\n headers?: HttpHeaders | Record<string, string | string[]>;\r\n context?: HttpContext;\r\n withCredentials?: boolean;\r\n} {\r\n return {\r\n responseType: 'json',\r\n observe: 'body',\r\n params: toHttpParams(r.params?.query),\r\n // Cast needed: our public API allows ReadonlyArray<string> (consistent with httpResource),\r\n // but Angular HttpClient methods require string[].\r\n headers: r.headers as HttpHeaders | Record<string, string | string[]> | undefined,\r\n context: r.context,\r\n withCredentials: r.withCredentials,\r\n };\r\n}\r\n\r\n/**\r\n * A type-safe HTTP client for OpenAPI-described APIs.\r\n *\r\n * Wraps Angular's `HttpClient` and binds request/response types to an OpenAPI\r\n * schema generated by `openapi-typescript`.\r\n *\r\n * Designed to be subclassed per-API:\r\n *\r\n * @example\r\n * ```ts\r\n * import type { paths } from './api.types'; // generated by openapi-typescript\r\n *\r\n * @Injectable({ providedIn: 'root' })\r\n * export class PetApiClient extends OpenApiHttpClient<paths> {\r\n * constructor() {\r\n * super({ baseUrl: 'https://api.example.com' });\r\n * }\r\n * }\r\n * ```\r\n *\r\n * Or used directly inside an injection context:\r\n *\r\n * @example\r\n * ```ts\r\n * const client = new OpenApiHttpClient<paths>({ baseUrl: 'https://api.example.com' });\r\n * ```\r\n */\r\nexport class OpenApiHttpClient<Paths extends PathRecord> {\r\n private readonly http = inject(HttpClient);\r\n private readonly baseUrl: string;\r\n\r\n constructor(config?: OpenApiHttpClientConfig) {\r\n this.baseUrl = config?.baseUrl ?? '';\r\n }\r\n\r\n /**\r\n * Performs a typed GET request.\r\n *\r\n * @example\r\n * ```ts\r\n * client.get('/pets', { params: { query: { limit: 10 } } }).subscribe(pets => console.log(pets));\r\n * ```\r\n */\r\n get<Path extends PathsWithMethod<Paths, 'get'> & keyof Paths>(\r\n path: Path,\r\n options?: OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'get'>>>,\r\n ): Observable<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'get'>>>> {\r\n const r = toShape(options);\r\n return this.http.get<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'get'>>>>(\r\n buildUrl(this.baseUrl, String(path), r.params?.path),\r\n buildOptions(r),\r\n );\r\n }\r\n\r\n /**\r\n * Performs a typed POST request.\r\n */\r\n post<Path extends PathsWithMethod<Paths, 'post'> & keyof Paths>(\r\n path: Path,\r\n options?: OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'post'>>>,\r\n ): Observable<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'post'>>>> {\r\n const r = toShape(options);\r\n return this.http.post<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'post'>>>>(\r\n buildUrl(this.baseUrl, String(path), r.params?.path),\r\n r.body as RequestBodyJSON<NonNullable<OpOf<Paths, Path, 'post'>>>,\r\n buildOptions(r),\r\n );\r\n }\r\n\r\n /**\r\n * Performs a typed PUT request.\r\n */\r\n put<Path extends PathsWithMethod<Paths, 'put'> & keyof Paths>(\r\n path: Path,\r\n options?: OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'put'>>>,\r\n ): Observable<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'put'>>>> {\r\n const r = toShape(options);\r\n return this.http.put<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'put'>>>>(\r\n buildUrl(this.baseUrl, String(path), r.params?.path),\r\n r.body as RequestBodyJSON<NonNullable<OpOf<Paths, Path, 'put'>>>,\r\n buildOptions(r),\r\n );\r\n }\r\n\r\n /**\r\n * Performs a typed PATCH request.\r\n */\r\n patch<Path extends PathsWithMethod<Paths, 'patch'> & keyof Paths>(\r\n path: Path,\r\n options?: OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'patch'>>>,\r\n ): Observable<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'patch'>>>> {\r\n const r = toShape(options);\r\n return this.http.patch<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'patch'>>>>(\r\n buildUrl(this.baseUrl, String(path), r.params?.path),\r\n r.body as RequestBodyJSON<NonNullable<OpOf<Paths, Path, 'patch'>>>,\r\n buildOptions(r),\r\n );\r\n }\r\n\r\n /**\r\n * Performs a typed DELETE request.\r\n */\r\n delete<Path extends PathsWithMethod<Paths, 'delete'> & keyof Paths>(\r\n path: Path,\r\n options?: OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'delete'>>>,\r\n ): Observable<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'delete'>>>> {\r\n const r = toShape(options);\r\n const httpOptions = buildOptions(r) as { body?: unknown } & ReturnType<typeof buildOptions>;\r\n\r\n if (r.body !== undefined) {\r\n httpOptions.body = r.body as RequestBodyJSON<NonNullable<OpOf<Paths, Path, 'delete'>>>;\r\n }\r\n\r\n return this.http.delete<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'delete'>>>>(\r\n buildUrl(this.baseUrl, String(path), r.params?.path),\r\n httpOptions,\r\n );\r\n }\r\n}\r\n","/*\r\n * Public API Surface of ngx-openapi-typescript\r\n */\r\n\r\nexport { createOpenApiResource } from './lib/open-api-resource';\r\nexport type {\r\n OpenApiResource, OpenApiResourceCallOptions, OpenApiResourceConfig\r\n} from './lib/open-api-resource';\r\n\r\nexport { OpenApiHttpClient } from './lib/open-api-http-client';\r\nexport type { OpenApiHttpClientConfig } from './lib/open-api-http-client';\r\n\r\nexport type {\r\n HasRequiredKeys,\r\n HttpMethod, OpenApiOperationRequest,\r\n OpenApiRequestOptions, OperationBody, OperationParamsObject, PathParameters, PathRecord, PathsWithMethod, QueryParameters, RequestBodyJSON, SuccessResponseJSON\r\n} from './lib/types';\r\n\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["_buildUrl","_toHttpParams"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ngx-openapi-typescript",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"peerDependencies": {
|
|
5
|
+
"@angular/common": "^21.2.0 || ^22.0.0",
|
|
6
|
+
"@angular/core": "^21.2.0 || ^22.0.0",
|
|
7
|
+
"openapi-typescript-helpers": "^0.1.0"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"tslib": "^2.3.0"
|
|
11
|
+
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"module": "fesm2022/ngx-openapi-typescript.mjs",
|
|
14
|
+
"typings": "types/ngx-openapi-typescript.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./package.json": {
|
|
17
|
+
"default": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./types/ngx-openapi-typescript.d.ts",
|
|
21
|
+
"default": "./fesm2022/ngx-openapi-typescript.mjs"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { HttpHeaders, HttpContext, HttpResourceOptions, HttpResourceRef } from '@angular/common/http';
|
|
2
|
+
import { RequestBodyJSON, IsOperationRequestBodyOptional, HttpMethod, SuccessResponseJSON, PathsWithMethod } from 'openapi-typescript-helpers';
|
|
3
|
+
export { HttpMethod, PathsWithMethod, RequestBodyJSON, SuccessResponseJSON } from 'openapi-typescript-helpers';
|
|
4
|
+
import { Observable } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents an OpenAPI paths record as generated by openapi-typescript.
|
|
8
|
+
* Uses `{}` to match the same constraint used by openapi-typescript-helpers.
|
|
9
|
+
*/
|
|
10
|
+
type PathRecord = {};
|
|
11
|
+
/** Extract path parameters from an OpenAPI operation */
|
|
12
|
+
type PathParameters<Op> = Op extends {
|
|
13
|
+
parameters: {
|
|
14
|
+
path?: infer P;
|
|
15
|
+
};
|
|
16
|
+
} ? NonNullable<P> extends Record<string, unknown> ? NonNullable<P> : never : never;
|
|
17
|
+
/** Extract query parameters from an OpenAPI operation */
|
|
18
|
+
type QueryParameters<Op> = Op extends {
|
|
19
|
+
parameters: {
|
|
20
|
+
query?: infer Q;
|
|
21
|
+
};
|
|
22
|
+
} ? NonNullable<Q> extends Record<string, unknown> ? NonNullable<Q> : never : never;
|
|
23
|
+
/** Check if a type has required (non-optional) keys */
|
|
24
|
+
type HasRequiredKeys<T> = [T] extends [never] ? false : ({
|
|
25
|
+
[K in keyof T]-?: undefined extends T[K] ? never : K;
|
|
26
|
+
}[keyof T]) extends never ? false : true;
|
|
27
|
+
/** Conditionally adds a key to the object; makes it required if T has required keys */
|
|
28
|
+
type ParamEntry<K extends string, T> = [T] extends [never] ? {} : HasRequiredKeys<T> extends true ? {
|
|
29
|
+
[P in K]: T;
|
|
30
|
+
} : {
|
|
31
|
+
[P in K]?: T;
|
|
32
|
+
};
|
|
33
|
+
/** Inner `{ path?, query? }` object for an OpenAPI operation */
|
|
34
|
+
type OperationParamsObject<Op> = ParamEntry<'path', PathParameters<Op>> & ParamEntry<'query', QueryParameters<Op>>;
|
|
35
|
+
/** Request body type for an OpenAPI operation */
|
|
36
|
+
type OperationBody<Op> = [RequestBodyJSON<Op>] extends [never] ? {
|
|
37
|
+
body?: never;
|
|
38
|
+
} : IsOperationRequestBodyOptional<Op> extends true ? {
|
|
39
|
+
body?: RequestBodyJSON<Op>;
|
|
40
|
+
} : {
|
|
41
|
+
body: RequestBodyJSON<Op>;
|
|
42
|
+
};
|
|
43
|
+
/** Common HTTP request options */
|
|
44
|
+
interface OpenApiRequestOptions {
|
|
45
|
+
headers?: HttpHeaders | Record<string, string | ReadonlyArray<string>>;
|
|
46
|
+
context?: HttpContext;
|
|
47
|
+
withCredentials?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** Full typed request for an OpenAPI operation */
|
|
50
|
+
type OpenApiOperationRequest<Op> = ParamEntry<'params', OperationParamsObject<Op>> & OperationBody<Op> & OpenApiRequestOptions;
|
|
51
|
+
|
|
52
|
+
/** Configuration for creating an OpenAPI resource factory */
|
|
53
|
+
interface OpenApiResourceConfig {
|
|
54
|
+
/** Base URL to prepend to all resource paths */
|
|
55
|
+
baseUrl?: string;
|
|
56
|
+
}
|
|
57
|
+
/** Options for the resource call, extending Angular's HttpResourceOptions with an optional HTTP method */
|
|
58
|
+
type OpenApiResourceCallOptions<TValue, TError = unknown> = HttpResourceOptions<TValue, TError> & {
|
|
59
|
+
method?: HttpMethod;
|
|
60
|
+
};
|
|
61
|
+
/** Extracts the operation type for a given path and method */
|
|
62
|
+
type OpOf$1<Paths, Path extends keyof Paths, Method extends string> = Paths[Path] extends {
|
|
63
|
+
[K in Method]: infer Op;
|
|
64
|
+
} ? Op : never;
|
|
65
|
+
/** Extract which HTTP methods a given path supports */
|
|
66
|
+
type MethodsOfPath<Paths, Path extends keyof Paths> = {
|
|
67
|
+
[M in HttpMethod]: Paths[Path] extends {
|
|
68
|
+
[K in M]: unknown;
|
|
69
|
+
} ? M : never;
|
|
70
|
+
}[HttpMethod];
|
|
71
|
+
/**
|
|
72
|
+
* Narrows to only those paths (for the given method) where the full request object
|
|
73
|
+
* has no required keys — i.e. `requestFn` can be safely omitted.
|
|
74
|
+
*/
|
|
75
|
+
type PathsWithOptionalRequest<Paths extends PathRecord, Method extends HttpMethod> = {
|
|
76
|
+
[P in PathsWithMethod<Paths, Method> & keyof Paths]: HasRequiredKeys<OpenApiOperationRequest<NonNullable<OpOf$1<Paths, P, Method>>>> extends true ? never : P;
|
|
77
|
+
}[PathsWithMethod<Paths, Method> & keyof Paths];
|
|
78
|
+
/** The callable type returned by `createOpenApiResource` */
|
|
79
|
+
interface OpenApiResource<Paths extends PathRecord> {
|
|
80
|
+
/**
|
|
81
|
+
* Creates a reactive resource for an OpenAPI endpoint with an explicit HTTP method.
|
|
82
|
+
*
|
|
83
|
+
* The `method` option is restricted to the methods the given path supports.
|
|
84
|
+
*/
|
|
85
|
+
<Path extends keyof Paths & string, Method extends MethodsOfPath<Paths, Path>>(path: Path, requestFn: () => OpenApiOperationRequest<NonNullable<OpOf$1<Paths, Path, Method>>> | undefined, options: OpenApiResourceCallOptions<SuccessResponseJSON<NonNullable<OpOf$1<Paths, Path, Method>>>, unknown> & {
|
|
86
|
+
method: Method;
|
|
87
|
+
}): HttpResourceRef<SuccessResponseJSON<NonNullable<OpOf$1<Paths, Path, Method>>> | undefined>;
|
|
88
|
+
/**
|
|
89
|
+
* Creates a reactive GET resource for an OpenAPI endpoint (default).
|
|
90
|
+
*
|
|
91
|
+
* The resource re-fetches automatically when the signals read inside `requestFn` change.
|
|
92
|
+
* When `requestFn` returns `undefined` the resource is put into the Idle state.
|
|
93
|
+
*/
|
|
94
|
+
<Path extends PathsWithMethod<Paths, 'get'> & keyof Paths>(path: Path, requestFn: () => OpenApiOperationRequest<NonNullable<OpOf$1<Paths, Path, 'get'>>> | undefined, options?: HttpResourceOptions<SuccessResponseJSON<NonNullable<OpOf$1<Paths, Path, 'get'>>>, unknown>): HttpResourceRef<SuccessResponseJSON<NonNullable<OpOf$1<Paths, Path, 'get'>>> | undefined>;
|
|
95
|
+
/**
|
|
96
|
+
* Creates a reactive resource for an OpenAPI endpoint with an explicit HTTP method,
|
|
97
|
+
* omitting `requestFn`. Only available when the operation has no required parameters or body.
|
|
98
|
+
*/
|
|
99
|
+
<Path extends keyof Paths & string, Method extends MethodsOfPath<Paths, Path>>(path: Path extends PathsWithOptionalRequest<Paths, Method> ? Path : never, options: OpenApiResourceCallOptions<SuccessResponseJSON<NonNullable<OpOf$1<Paths, Path, Method>>>, unknown> & {
|
|
100
|
+
method: Method;
|
|
101
|
+
}): HttpResourceRef<SuccessResponseJSON<NonNullable<OpOf$1<Paths, Path, Method>>> | undefined>;
|
|
102
|
+
/**
|
|
103
|
+
* Creates a reactive GET resource for an OpenAPI endpoint, omitting `requestFn`.
|
|
104
|
+
* Only available when the operation has no required parameters or body.
|
|
105
|
+
* The resource fetches immediately and always stays active.
|
|
106
|
+
*/
|
|
107
|
+
<Path extends PathsWithOptionalRequest<Paths, 'get'>>(path: Path, options?: HttpResourceOptions<SuccessResponseJSON<NonNullable<OpOf$1<Paths, Path, 'get'>>>, unknown>): HttpResourceRef<SuccessResponseJSON<NonNullable<OpOf$1<Paths, Path, 'get'>>> | undefined>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Creates a type-safe callable factory for Angular `httpResource`s backed by an OpenAPI schema.
|
|
111
|
+
*
|
|
112
|
+
* Must be called inside an Angular injection context (e.g. component/service field initializer
|
|
113
|
+
* or constructor), because the returned function uses `httpResource` internally.
|
|
114
|
+
*
|
|
115
|
+
* The returned function defaults to GET. Pass `{ method: 'post' }` (or other method) in the
|
|
116
|
+
* options to use a different HTTP method. Note: Angular recommends using `httpResource` only
|
|
117
|
+
* for reads; prefer `OpenApiHttpClient` for mutations.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* import type { paths } from './api.types'; // generated by openapi-typescript
|
|
122
|
+
*
|
|
123
|
+
* const api = createOpenApiResource<paths>({ baseUrl: 'https://api.example.com' });
|
|
124
|
+
*
|
|
125
|
+
* // Reactive GET resource (default)
|
|
126
|
+
* const pets = api('/pets', () => ({ params: { query: { limit: 10 } } }));
|
|
127
|
+
*
|
|
128
|
+
* // GET with path params (signal-driven)
|
|
129
|
+
* const petId = signal(1);
|
|
130
|
+
* const pet = api('/pets/{petId}', () => ({ params: { path: { petId: petId() } } }));
|
|
131
|
+
*
|
|
132
|
+
* // Explicit method override (prefer OpenApiHttpClient for mutations)
|
|
133
|
+
* const created = api('/pets', () => ({ body: { name: 'Buddy' } }), { method: 'post' });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
declare function createOpenApiResource<Paths extends PathRecord>(config?: OpenApiResourceConfig): OpenApiResource<Paths>;
|
|
137
|
+
|
|
138
|
+
/** Configuration for `OpenApiHttpClient` */
|
|
139
|
+
interface OpenApiHttpClientConfig {
|
|
140
|
+
/** Base URL to prepend to all request paths */
|
|
141
|
+
baseUrl?: string;
|
|
142
|
+
}
|
|
143
|
+
/** Extracts the operation type for a given path and method */
|
|
144
|
+
type OpOf<Paths, Path extends keyof Paths, Method extends string> = Paths[Path] extends {
|
|
145
|
+
[K in Method]: infer Op;
|
|
146
|
+
} ? Op : never;
|
|
147
|
+
/**
|
|
148
|
+
* A type-safe HTTP client for OpenAPI-described APIs.
|
|
149
|
+
*
|
|
150
|
+
* Wraps Angular's `HttpClient` and binds request/response types to an OpenAPI
|
|
151
|
+
* schema generated by `openapi-typescript`.
|
|
152
|
+
*
|
|
153
|
+
* Designed to be subclassed per-API:
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* import type { paths } from './api.types'; // generated by openapi-typescript
|
|
158
|
+
*
|
|
159
|
+
* @Injectable({ providedIn: 'root' })
|
|
160
|
+
* export class PetApiClient extends OpenApiHttpClient<paths> {
|
|
161
|
+
* constructor() {
|
|
162
|
+
* super({ baseUrl: 'https://api.example.com' });
|
|
163
|
+
* }
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* Or used directly inside an injection context:
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* const client = new OpenApiHttpClient<paths>({ baseUrl: 'https://api.example.com' });
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
declare class OpenApiHttpClient<Paths extends PathRecord> {
|
|
175
|
+
private readonly http;
|
|
176
|
+
private readonly baseUrl;
|
|
177
|
+
constructor(config?: OpenApiHttpClientConfig);
|
|
178
|
+
/**
|
|
179
|
+
* Performs a typed GET request.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* client.get('/pets', { params: { query: { limit: 10 } } }).subscribe(pets => console.log(pets));
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
get<Path extends PathsWithMethod<Paths, 'get'> & keyof Paths>(path: Path, options?: OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'get'>>>): Observable<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'get'>>>>;
|
|
187
|
+
/**
|
|
188
|
+
* Performs a typed POST request.
|
|
189
|
+
*/
|
|
190
|
+
post<Path extends PathsWithMethod<Paths, 'post'> & keyof Paths>(path: Path, options?: OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'post'>>>): Observable<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'post'>>>>;
|
|
191
|
+
/**
|
|
192
|
+
* Performs a typed PUT request.
|
|
193
|
+
*/
|
|
194
|
+
put<Path extends PathsWithMethod<Paths, 'put'> & keyof Paths>(path: Path, options?: OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'put'>>>): Observable<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'put'>>>>;
|
|
195
|
+
/**
|
|
196
|
+
* Performs a typed PATCH request.
|
|
197
|
+
*/
|
|
198
|
+
patch<Path extends PathsWithMethod<Paths, 'patch'> & keyof Paths>(path: Path, options?: OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'patch'>>>): Observable<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'patch'>>>>;
|
|
199
|
+
/**
|
|
200
|
+
* Performs a typed DELETE request.
|
|
201
|
+
*/
|
|
202
|
+
delete<Path extends PathsWithMethod<Paths, 'delete'> & keyof Paths>(path: Path, options?: OpenApiOperationRequest<NonNullable<OpOf<Paths, Path, 'delete'>>>): Observable<SuccessResponseJSON<NonNullable<OpOf<Paths, Path, 'delete'>>>>;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export { OpenApiHttpClient, createOpenApiResource };
|
|
206
|
+
export type { HasRequiredKeys, OpenApiHttpClientConfig, OpenApiOperationRequest, OpenApiRequestOptions, OpenApiResource, OpenApiResourceCallOptions, OpenApiResourceConfig, OperationBody, OperationParamsObject, PathParameters, PathRecord, QueryParameters };
|