@orval/angular 8.5.3 → 8.6.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 +92 -13
- package/dist/index.d.mts +375 -5
- package/dist/index.mjs +1096 -108
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -4
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { camel, generateFormDataAndUrlEncodedFunction, generateMutatorConfig, generateMutatorRequestOptions, generateOptions, generateVerbImports, getAngularFilteredParamsCallExpression, getAngularFilteredParamsHelperBody, getDefaultContentType, isBoolean, pascal, sanitize, toObjectString } from "@orval/core";
|
|
1
|
+
import { GetterPropType, camel, conventionName, escapeRegExp, generateDependencyImports, generateFormDataAndUrlEncodedFunction, generateMutatorConfig, generateMutatorImports, generateMutatorRequestOptions, generateOptions, generateVerbImports, getAngularFilteredParamsCallExpression, getAngularFilteredParamsHelperBody, getDefaultContentType, getEnumImplementation, getFileInfo, getFullRoute, isBoolean, isObject, isSyntheticDefaultImportsAllow, jsDoc, pascal, sanitize, toObjectString, upath } from "@orval/core";
|
|
2
2
|
|
|
3
|
-
//#region src/
|
|
4
|
-
const
|
|
3
|
+
//#region src/constants.ts
|
|
4
|
+
const ANGULAR_HTTP_CLIENT_DEPENDENCIES = [
|
|
5
5
|
{
|
|
6
6
|
exports: [
|
|
7
7
|
{
|
|
@@ -16,7 +16,8 @@ const ANGULAR_DEPENDENCIES = [
|
|
|
16
16
|
{ name: "HttpContext" },
|
|
17
17
|
{
|
|
18
18
|
name: "HttpResponse",
|
|
19
|
-
alias: "AngularHttpResponse"
|
|
19
|
+
alias: "AngularHttpResponse",
|
|
20
|
+
values: true
|
|
20
21
|
},
|
|
21
22
|
{ name: "HttpEvent" }
|
|
22
23
|
],
|
|
@@ -40,25 +41,41 @@ const ANGULAR_DEPENDENCIES = [
|
|
|
40
41
|
dependency: "rxjs"
|
|
41
42
|
}
|
|
42
43
|
];
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
44
|
+
const ANGULAR_HTTP_RESOURCE_DEPENDENCIES = [{
|
|
45
|
+
exports: [
|
|
46
|
+
{
|
|
47
|
+
name: "httpResource",
|
|
48
|
+
values: true
|
|
49
|
+
},
|
|
50
|
+
{ name: "HttpResourceOptions" },
|
|
51
|
+
{ name: "HttpResourceRef" },
|
|
52
|
+
{ name: "HttpResourceRequest" },
|
|
53
|
+
{
|
|
54
|
+
name: "HttpHeaders",
|
|
55
|
+
values: true
|
|
56
|
+
},
|
|
57
|
+
{ name: "HttpParams" },
|
|
58
|
+
{ name: "HttpContext" }
|
|
59
|
+
],
|
|
60
|
+
dependency: "@angular/common/http"
|
|
61
|
+
}, {
|
|
62
|
+
exports: [{ name: "Signal" }, { name: "ResourceStatus" }],
|
|
63
|
+
dependency: "@angular/core"
|
|
64
|
+
}];
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/types.ts
|
|
68
|
+
/**
|
|
69
|
+
* Code template for the `HttpClientOptions` interface emitted into generated files.
|
|
70
|
+
*
|
|
71
|
+
* This is NOT an import of Angular's type — Angular's HttpClient methods accept
|
|
72
|
+
* inline option objects, not a single unified interface. Orval generates this
|
|
73
|
+
* convenience wrapper so users have a single referenceable type.
|
|
74
|
+
*
|
|
75
|
+
* Properties sourced from Angular HttpClient public API (angular/angular
|
|
76
|
+
* packages/common/http/src/client.ts).
|
|
77
|
+
*/
|
|
78
|
+
const HTTP_CLIENT_OPTIONS_TEMPLATE = `interface HttpClientOptions {
|
|
62
79
|
readonly headers?: HttpHeaders | Record<string, string | string[]>;
|
|
63
80
|
readonly context?: HttpContext;
|
|
64
81
|
readonly params?:
|
|
@@ -76,33 +93,301 @@ ${isRequestOptions && !isGlobalMutator ? `interface HttpClientOptions {
|
|
|
76
93
|
readonly integrity?: string;
|
|
77
94
|
readonly referrerPolicy?: ReferrerPolicy;
|
|
78
95
|
readonly transferCache?: {includeHeaders?: string[]} | boolean;
|
|
79
|
-
|
|
96
|
+
readonly timeout?: number;
|
|
97
|
+
}`;
|
|
98
|
+
/**
|
|
99
|
+
* Code templates for reusable observe option helpers emitted into generated files.
|
|
100
|
+
*/
|
|
101
|
+
const HTTP_CLIENT_OBSERVE_OPTIONS_TEMPLATE = `type HttpClientBodyOptions = HttpClientOptions & {
|
|
102
|
+
readonly observe?: 'body';
|
|
103
|
+
};
|
|
80
104
|
|
|
81
|
-
|
|
105
|
+
type HttpClientEventOptions = HttpClientOptions & {
|
|
106
|
+
readonly observe: 'events';
|
|
107
|
+
};
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
109
|
+
type HttpClientResponseOptions = HttpClientOptions & {
|
|
110
|
+
readonly observe: 'response';
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
type HttpClientObserveOptions = HttpClientOptions & {
|
|
114
|
+
readonly observe?: 'body' | 'events' | 'response';
|
|
115
|
+
};`;
|
|
116
|
+
/**
|
|
117
|
+
* Code template for the `ThirdParameter` utility type used with custom mutators.
|
|
118
|
+
*/
|
|
119
|
+
const THIRD_PARAMETER_TEMPLATE = `// eslint-disable-next-line
|
|
120
|
+
type ThirdParameter<T extends (...args: never[]) => unknown> = T extends (
|
|
121
|
+
config: unknown,
|
|
122
|
+
httpClient: unknown,
|
|
87
123
|
args: infer P,
|
|
88
|
-
) =>
|
|
124
|
+
) => unknown
|
|
89
125
|
? P
|
|
90
|
-
: never
|
|
126
|
+
: never;`;
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/utils.ts
|
|
130
|
+
const PRIMITIVE_TYPE_VALUES = [
|
|
131
|
+
"string",
|
|
132
|
+
"number",
|
|
133
|
+
"boolean",
|
|
134
|
+
"void",
|
|
135
|
+
"unknown"
|
|
136
|
+
];
|
|
137
|
+
const PRIMITIVE_TYPES = new Set(PRIMITIVE_TYPE_VALUES);
|
|
138
|
+
const PRIMITIVE_TYPE_LOOKUP = {
|
|
139
|
+
string: true,
|
|
140
|
+
number: true,
|
|
141
|
+
boolean: true,
|
|
142
|
+
void: true,
|
|
143
|
+
unknown: true
|
|
144
|
+
};
|
|
145
|
+
const isPrimitiveType = (t) => t != void 0 && Object.prototype.hasOwnProperty.call(PRIMITIVE_TYPE_LOOKUP, t);
|
|
146
|
+
const isZodSchemaOutput = (output) => isObject(output.schemas) && output.schemas.type === "zod";
|
|
147
|
+
const isDefined = (v) => v != void 0;
|
|
148
|
+
const generateAngularTitle = (title) => {
|
|
149
|
+
return `${pascal(sanitize(title))}Service`;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Builds the opening of an @Injectable Angular service class.
|
|
153
|
+
* Shared between httpClient-only mode and the mutation section of httpResource mode.
|
|
154
|
+
*/
|
|
155
|
+
const buildServiceClassOpen = ({ title, isRequestOptions, isMutator, isGlobalMutator, provideIn, hasQueryParams }) => {
|
|
156
|
+
const provideInValue = provideIn ? `{ providedIn: '${isBoolean(provideIn) ? "root" : provideIn}' }` : "";
|
|
157
|
+
return `
|
|
158
|
+
${isRequestOptions && !isGlobalMutator ? `${HTTP_CLIENT_OPTIONS_TEMPLATE}
|
|
159
|
+
|
|
160
|
+
${HTTP_CLIENT_OBSERVE_OPTIONS_TEMPLATE}
|
|
161
|
+
|
|
162
|
+
${hasQueryParams ? getAngularFilteredParamsHelperBody() : ""}` : ""}
|
|
163
|
+
|
|
164
|
+
${isRequestOptions && isMutator ? THIRD_PARAMETER_TEMPLATE : ""}
|
|
165
|
+
|
|
166
|
+
@Injectable(${provideInValue})
|
|
167
|
+
export class ${title} {
|
|
168
|
+
private readonly http = inject(HttpClient);
|
|
169
|
+
`;
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Registry that maps operationName → full route (with baseUrl).
|
|
173
|
+
*
|
|
174
|
+
* Populated during client builder calls (which receive the full route via
|
|
175
|
+
* GeneratorOptions.route) and read during header/footer builder calls
|
|
176
|
+
* (which only receive verbOptions without routes).
|
|
177
|
+
*
|
|
178
|
+
* This avoids monkey-patching verbOptions with a non-standard `fullRoute` property.
|
|
179
|
+
*/
|
|
180
|
+
const createRouteRegistry = () => {
|
|
181
|
+
const routes = /* @__PURE__ */ new Map();
|
|
182
|
+
return {
|
|
183
|
+
reset() {
|
|
184
|
+
routes.clear();
|
|
185
|
+
},
|
|
186
|
+
set(operationName, route) {
|
|
187
|
+
routes.set(operationName, route);
|
|
188
|
+
},
|
|
189
|
+
get(operationName, fallback) {
|
|
190
|
+
return routes.get(operationName) ?? fallback;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
const createReturnTypesRegistry = () => {
|
|
195
|
+
const returnTypesToWrite = /* @__PURE__ */ new Map();
|
|
196
|
+
return {
|
|
197
|
+
reset() {
|
|
198
|
+
returnTypesToWrite.clear();
|
|
199
|
+
},
|
|
200
|
+
set(operationName, typeDefinition) {
|
|
201
|
+
returnTypesToWrite.set(operationName, typeDefinition);
|
|
202
|
+
},
|
|
203
|
+
getFooter(operationNames) {
|
|
204
|
+
const collected = [];
|
|
205
|
+
for (const operationName of operationNames) {
|
|
206
|
+
const value = returnTypesToWrite.get(operationName);
|
|
207
|
+
if (value) collected.push(value);
|
|
208
|
+
}
|
|
209
|
+
return collected.join("\n");
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Determines whether an operation should be generated as an `httpResource()`
|
|
215
|
+
* (retrieval) or as an `HttpClient` method in a service class (mutation).
|
|
216
|
+
*
|
|
217
|
+
* Resolution order:
|
|
218
|
+
* 1. **Per-operation override** — `override.operations.<operationId>.angular.client`
|
|
219
|
+
* in the orval config. `httpResource` forces retrieval, `httpClient` forces mutation.
|
|
220
|
+
* 2. **HTTP verb** — absent a per-operation override, `GET` is treated as a retrieval.
|
|
221
|
+
* 3. **Name heuristic** — For `POST`, if the operationName starts with a
|
|
222
|
+
* retrieval-like prefix (search, list, find, query, get, fetch, lookup)
|
|
223
|
+
* it is treated as a retrieval. This handles common patterns like
|
|
224
|
+
* `POST /search` or `POST /graphql` with query-style operation names.
|
|
225
|
+
*
|
|
226
|
+
* If the heuristic misclassifies an operation, users can override it
|
|
227
|
+
* per-operation in their orval config:
|
|
228
|
+
*
|
|
229
|
+
* ```ts
|
|
230
|
+
* override: {
|
|
231
|
+
* operations: {
|
|
232
|
+
* myPostSearch: { angular: { retrievalClient: 'httpResource' } },
|
|
233
|
+
* getOrCreateUser: { angular: { retrievalClient: 'httpClient' } },
|
|
234
|
+
* }
|
|
235
|
+
* }
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
function isRetrievalVerb(verb, operationName, clientOverride) {
|
|
239
|
+
if (clientOverride === "httpResource") return true;
|
|
240
|
+
if (clientOverride === "httpClient") return false;
|
|
241
|
+
if (verb === "get") return true;
|
|
242
|
+
if (verb === "post" && operationName) {
|
|
243
|
+
const lower = operationName.toLowerCase();
|
|
244
|
+
return /^(search|list|find|query|get|fetch|lookup|filter)/.test(lower);
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
function isMutationVerb(verb, operationName, clientOverride) {
|
|
249
|
+
return !isRetrievalVerb(verb, operationName, clientOverride);
|
|
250
|
+
}
|
|
251
|
+
function getDefaultSuccessType(successTypes, fallback) {
|
|
252
|
+
const uniqueContentTypes = [...new Set(successTypes.map((t) => t.contentType).filter(Boolean))];
|
|
253
|
+
const defaultContentType = uniqueContentTypes.find((contentType) => contentType.includes("json")) ?? (uniqueContentTypes.length > 1 ? getDefaultContentType(uniqueContentTypes) : uniqueContentTypes[0] ?? "application/json");
|
|
254
|
+
return {
|
|
255
|
+
contentType: defaultContentType,
|
|
256
|
+
value: successTypes.find((t) => t.contentType === defaultContentType)?.value ?? fallback
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/http-client.ts
|
|
262
|
+
const returnTypesRegistry = createReturnTypesRegistry();
|
|
263
|
+
const hasSchemaImport = (imports, typeName) => typeName != void 0 && imports.some((imp) => imp.name === typeName);
|
|
264
|
+
const getSchemaValueRef = (typeName) => typeName === "Error" ? "ErrorSchema" : typeName;
|
|
265
|
+
const getSchemaOutputTypeRef$1 = (typeName) => typeName === "Error" ? "ErrorOutput" : `${typeName}Output`;
|
|
266
|
+
const getContentTypeReturnType$1 = (contentType, value) => {
|
|
267
|
+
if (!contentType) return value;
|
|
268
|
+
if (contentType.includes("json") || contentType.includes("+json")) return value;
|
|
269
|
+
if (contentType.startsWith("text/") || contentType.includes("xml")) return "string";
|
|
270
|
+
return "Blob";
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* Returns the dependency list required by the Angular `HttpClient` generator.
|
|
274
|
+
*
|
|
275
|
+
* These imports are consumed by Orval's generic dependency-import emitter when
|
|
276
|
+
* composing the generated Angular client file.
|
|
277
|
+
*
|
|
278
|
+
* @returns The Angular `HttpClient` dependency descriptors used during import generation.
|
|
279
|
+
*/
|
|
280
|
+
const getAngularDependencies = () => [...ANGULAR_HTTP_CLIENT_DEPENDENCIES];
|
|
281
|
+
/**
|
|
282
|
+
* Builds the generated TypeScript helper name used for multi-content-type
|
|
283
|
+
* `Accept` header unions.
|
|
284
|
+
*
|
|
285
|
+
* Example: `listPets` -> `ListPetsAccept`.
|
|
286
|
+
*
|
|
287
|
+
* @returns A PascalCase helper type/const name for the operation's `Accept` values.
|
|
288
|
+
*/
|
|
289
|
+
const getAcceptHelperName = (operationName) => `${pascal(operationName)}Accept`;
|
|
290
|
+
/**
|
|
291
|
+
* Collects the distinct successful response content types for a single
|
|
292
|
+
* operation.
|
|
293
|
+
*
|
|
294
|
+
* The Angular generators use this to decide whether they need `Accept`
|
|
295
|
+
* overloads or content-type-specific branching logic.
|
|
296
|
+
*
|
|
297
|
+
* @returns A de-duplicated list of response content types, excluding empty entries.
|
|
298
|
+
*/
|
|
299
|
+
const getUniqueContentTypes = (successTypes) => [...new Set(successTypes.map((t) => t.contentType).filter(Boolean))];
|
|
300
|
+
const toAcceptHelperKey = (contentType) => contentType.replaceAll(/[^A-Za-z0-9]+/g, "_").replaceAll(/^_+|_+$/g, "").toLowerCase();
|
|
301
|
+
const buildAcceptHelper = (operationName, contentTypes, output) => {
|
|
302
|
+
const acceptHelperName = getAcceptHelperName(operationName);
|
|
303
|
+
return `export type ${acceptHelperName} = typeof ${acceptHelperName}[keyof typeof ${acceptHelperName}];
|
|
304
|
+
|
|
305
|
+
export const ${acceptHelperName} = {
|
|
306
|
+
${getEnumImplementation(contentTypes.map((contentType) => `'${contentType}'`).join(" | "), contentTypes.map((contentType) => toAcceptHelperKey(contentType)), void 0, output.override.namingConvention.enum)}} as const;`;
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* Builds the shared `Accept` helper declarations for all operations in the
|
|
310
|
+
* current Angular generation scope.
|
|
311
|
+
*
|
|
312
|
+
* @remarks
|
|
313
|
+
* Helpers are emitted only for operations with more than one successful
|
|
314
|
+
* response content type.
|
|
315
|
+
*
|
|
316
|
+
* @returns Concatenated type/const declarations or an empty string when no helpers are needed.
|
|
317
|
+
*/
|
|
318
|
+
const buildAcceptHelpers = (verbOptions, output) => verbOptions.flatMap((verbOption) => {
|
|
319
|
+
const contentTypes = getUniqueContentTypes(verbOption.response.types.success);
|
|
320
|
+
if (contentTypes.length <= 1) return [];
|
|
321
|
+
return [buildAcceptHelper(verbOption.operationName, contentTypes, output)];
|
|
322
|
+
}).join("\n\n");
|
|
323
|
+
/**
|
|
324
|
+
* Generates the static header section for Angular `HttpClient` output.
|
|
325
|
+
*
|
|
326
|
+
* Depending on the current generation options this may include:
|
|
327
|
+
* - reusable request option helper types
|
|
328
|
+
* - filtered query-param helper utilities
|
|
329
|
+
* - mutator support types
|
|
330
|
+
* - `Accept` helper unions/constants for multi-content-type operations
|
|
331
|
+
* - the `@Injectable()` service class shell
|
|
332
|
+
*
|
|
333
|
+
* @returns A string containing the prelude and service class opening for the generated file.
|
|
334
|
+
*/
|
|
335
|
+
const generateAngularHeader = ({ title, isRequestOptions, isMutator, isGlobalMutator, provideIn, verbOptions, tag, output }) => {
|
|
336
|
+
returnTypesRegistry.reset();
|
|
337
|
+
const relevantVerbs = tag ? Object.values(verbOptions).filter((v) => v.tags.some((t) => camel(t) === camel(tag))) : Object.values(verbOptions);
|
|
338
|
+
const hasQueryParams = relevantVerbs.some((v) => v.queryParams);
|
|
339
|
+
const acceptHelpers = buildAcceptHelpers(relevantVerbs, output);
|
|
340
|
+
return `
|
|
341
|
+
${isRequestOptions && !isGlobalMutator ? `${HTTP_CLIENT_OPTIONS_TEMPLATE}
|
|
342
|
+
|
|
343
|
+
${HTTP_CLIENT_OBSERVE_OPTIONS_TEMPLATE}
|
|
344
|
+
|
|
345
|
+
${hasQueryParams ? getAngularFilteredParamsHelperBody() : ""}` : ""}
|
|
346
|
+
|
|
347
|
+
${isRequestOptions && isMutator ? THIRD_PARAMETER_TEMPLATE : ""}
|
|
348
|
+
|
|
349
|
+
${acceptHelpers}
|
|
91
350
|
|
|
92
351
|
@Injectable(${provideIn ? `{ providedIn: '${isBoolean(provideIn) ? "root" : provideIn}' }` : ""})
|
|
93
352
|
export class ${title} {
|
|
94
353
|
private readonly http = inject(HttpClient);
|
|
95
354
|
`;
|
|
96
355
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Generates the closing section for Angular `HttpClient` output.
|
|
358
|
+
*
|
|
359
|
+
* @remarks
|
|
360
|
+
* Besides closing the generated service class, this appends any collected
|
|
361
|
+
* `ClientResult` aliases registered while individual operations were emitted.
|
|
362
|
+
*
|
|
363
|
+
* @returns The footer text for the generated Angular client file.
|
|
364
|
+
*/
|
|
365
|
+
const generateAngularFooter = ({ operationNames }) => {
|
|
366
|
+
let footer = "};\n\n";
|
|
367
|
+
const returnTypes = returnTypesRegistry.getFooter(operationNames);
|
|
368
|
+
if (returnTypes) footer += `${returnTypes}\n`;
|
|
102
369
|
return footer;
|
|
103
370
|
};
|
|
104
|
-
|
|
105
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Generates the Angular `HttpClient` method implementation for a single
|
|
373
|
+
* OpenAPI operation.
|
|
374
|
+
*
|
|
375
|
+
* This function is responsible for:
|
|
376
|
+
* - method signatures and overloads
|
|
377
|
+
* - observe-mode branching
|
|
378
|
+
* - multi-content-type `Accept` handling
|
|
379
|
+
* - mutator integration
|
|
380
|
+
* - runtime Zod validation hooks for Angular output
|
|
381
|
+
* - registering the operation's `ClientResult` alias for footer emission
|
|
382
|
+
*
|
|
383
|
+
* @remarks
|
|
384
|
+
* This is the central implementation builder shared by the dedicated
|
|
385
|
+
* `httpClient` mode and the mutation side of Angular `both` / `httpResource`
|
|
386
|
+
* generation.
|
|
387
|
+
*
|
|
388
|
+
* @returns The complete TypeScript method declaration and implementation for the operation.
|
|
389
|
+
*/
|
|
390
|
+
const generateHttpClientImplementation = ({ headers, queryParams, operationName, response, mutator, body, props, verb, override, formData, formUrlEncoded, paramsSerializer }, { route, context }) => {
|
|
106
391
|
const isRequestOptions = override.requestOptions !== false;
|
|
107
392
|
const isFormData = !override.formData.disabled;
|
|
108
393
|
const isFormUrlEncoded = override.formUrlEncoded !== false;
|
|
@@ -115,13 +400,21 @@ const generateImplementation = (returnTypesToWrite, { headers, queryParams, oper
|
|
|
115
400
|
isFormUrlEncoded
|
|
116
401
|
});
|
|
117
402
|
const dataType = response.definition.success || "unknown";
|
|
118
|
-
const
|
|
403
|
+
const isPrimitive = isPrimitiveType(dataType);
|
|
119
404
|
const hasSchema = hasSchemaImport(response.imports, dataType);
|
|
120
|
-
const isZodOutput =
|
|
121
|
-
const shouldValidateResponse = override.angular.runtimeValidation && isZodOutput && !
|
|
405
|
+
const isZodOutput = isZodSchemaOutput(context.output);
|
|
406
|
+
const shouldValidateResponse = override.angular.runtimeValidation && isZodOutput && !isPrimitive && hasSchema;
|
|
407
|
+
const parsedDataType = shouldValidateResponse ? getSchemaOutputTypeRef$1(dataType) : dataType;
|
|
408
|
+
const getGeneratedResponseType = (value, contentType) => {
|
|
409
|
+
if (override.angular.runtimeValidation && isZodOutput && !!contentType && (contentType.includes("json") || contentType.includes("+json")) && !isPrimitiveType(value) && hasSchemaImport(response.imports, value)) return getSchemaOutputTypeRef$1(value);
|
|
410
|
+
return getContentTypeReturnType$1(contentType, value);
|
|
411
|
+
};
|
|
412
|
+
const resultAliasType = mutator ? dataType : response.types.success.length <= 1 ? parsedDataType : [...new Set(response.types.success.map(({ value, contentType }) => getGeneratedResponseType(value, contentType)))].join(" | ") || parsedDataType;
|
|
122
413
|
const schemaValueRef = shouldValidateResponse ? getSchemaValueRef(dataType) : dataType;
|
|
123
414
|
const validationPipe = shouldValidateResponse ? `.pipe(map(data => ${schemaValueRef}.parse(data) as TData))` : "";
|
|
124
|
-
|
|
415
|
+
const responseValidationPipe = shouldValidateResponse ? `.pipe(map(response => response.clone({ body: ${schemaValueRef}.parse(response.body) as TData })))` : "";
|
|
416
|
+
const eventValidationPipe = shouldValidateResponse ? `.pipe(map(event => event instanceof AngularHttpResponse ? event.clone({ body: ${schemaValueRef}.parse(event.body) as TData }) : event))` : "";
|
|
417
|
+
returnTypesRegistry.set(operationName, `export type ${pascal(operationName)}ClientResult = NonNullable<${resultAliasType}>`);
|
|
125
418
|
if (mutator) {
|
|
126
419
|
const mutatorConfig = generateMutatorConfig({
|
|
127
420
|
route,
|
|
@@ -137,7 +430,7 @@ const generateImplementation = (returnTypesToWrite, { headers, queryParams, oper
|
|
|
137
430
|
isAngular: true
|
|
138
431
|
});
|
|
139
432
|
const requestOptions = isRequestOptions ? generateMutatorRequestOptions(override.requestOptions, mutator.hasThirdArg) : "";
|
|
140
|
-
return ` ${operationName}<TData = ${dataType}>(\n ${mutator.bodyTypeName && body.definition ? toObjectString(props, "implementation").replace(new RegExp(String.raw`(
|
|
433
|
+
return ` ${operationName}<TData = ${dataType}>(\n ${mutator.bodyTypeName && body.definition ? toObjectString(props, "implementation").replace(new RegExp(String.raw`(\\w*):\\s?${body.definition}`), `$1: ${mutator.bodyTypeName}<${body.definition}>`) : toObjectString(props, "implementation")}\n ${isRequestOptions && mutator.hasThirdArg ? `options?: ThirdParameter<typeof ${mutator.name}>` : ""}) {${bodyForm}
|
|
141
434
|
return ${mutator.name}<TData>(
|
|
142
435
|
${mutatorConfig},
|
|
143
436
|
this.http,
|
|
@@ -163,10 +456,11 @@ const generateImplementation = (returnTypesToWrite, { headers, queryParams, oper
|
|
|
163
456
|
};
|
|
164
457
|
const propsDefinition = toObjectString(props, "definition");
|
|
165
458
|
const successTypes = response.types.success;
|
|
166
|
-
const uniqueContentTypes =
|
|
459
|
+
const uniqueContentTypes = getUniqueContentTypes(successTypes);
|
|
167
460
|
const hasMultipleContentTypes = uniqueContentTypes.length > 1;
|
|
461
|
+
const acceptTypeName = hasMultipleContentTypes ? getAcceptHelperName(operationName) : void 0;
|
|
168
462
|
const needsObserveBranching = isRequestOptions && !hasMultipleContentTypes;
|
|
169
|
-
const angularParamsRef =
|
|
463
|
+
const angularParamsRef = isRequestOptions && queryParams ? "filteredParams" : void 0;
|
|
170
464
|
let paramsDeclaration = "";
|
|
171
465
|
if (angularParamsRef && queryParams) {
|
|
172
466
|
const callExpr = getAngularFilteredParamsCallExpression("{...params, ...options?.params}", queryParams.requiredNullableKeys ?? []);
|
|
@@ -177,23 +471,25 @@ const generateImplementation = (returnTypesToWrite, { headers, queryParams, oper
|
|
|
177
471
|
...angularParamsRef ? { angularParamsRef } : {}
|
|
178
472
|
};
|
|
179
473
|
const options = generateOptions(optionsInput);
|
|
180
|
-
const defaultContentType = hasMultipleContentTypes ?
|
|
181
|
-
const getContentTypeReturnType = (contentType, value) => {
|
|
182
|
-
if (!contentType) return value;
|
|
183
|
-
if (contentType.includes("json") || contentType.includes("+json")) return value;
|
|
184
|
-
if (contentType.startsWith("text/") || contentType.includes("xml")) return "string";
|
|
185
|
-
return "Blob";
|
|
186
|
-
};
|
|
474
|
+
const defaultContentType = hasMultipleContentTypes ? successTypes.find(({ contentType }) => !!contentType && (contentType.includes("json") || contentType.includes("+json")))?.contentType ?? getDefaultContentType(uniqueContentTypes) : uniqueContentTypes[0] ?? "application/json";
|
|
187
475
|
const jsonSuccessValues = [...new Set(successTypes.filter(({ contentType }) => !!contentType && (contentType.includes("json") || contentType.includes("+json"))).map(({ value }) => value))];
|
|
188
476
|
const jsonReturnType = jsonSuccessValues.length > 0 ? jsonSuccessValues.join(" | ") : "unknown";
|
|
477
|
+
const parsedJsonReturnType = jsonSuccessValues.length === 1 && override.angular.runtimeValidation && isZodOutput && !isPrimitiveType(jsonSuccessValues[0]) && hasSchemaImport(response.imports, jsonSuccessValues[0]) ? getSchemaOutputTypeRef$1(jsonSuccessValues[0]) : jsonReturnType;
|
|
189
478
|
let jsonValidationPipe = shouldValidateResponse ? `.pipe(map(data => ${schemaValueRef}.parse(data)))` : "";
|
|
190
479
|
if (hasMultipleContentTypes && !shouldValidateResponse && override.angular.runtimeValidation && isZodOutput && jsonSuccessValues.length === 1) {
|
|
191
480
|
const jsonType = jsonSuccessValues[0];
|
|
192
|
-
const jsonIsPrimitive =
|
|
481
|
+
const jsonIsPrimitive = isPrimitiveType(jsonType);
|
|
193
482
|
const jsonHasSchema = hasSchemaImport(response.imports, jsonType);
|
|
194
483
|
if (!jsonIsPrimitive && jsonHasSchema) jsonValidationPipe = `.pipe(map(data => ${getSchemaValueRef(jsonType)}.parse(data)))`;
|
|
195
484
|
}
|
|
196
|
-
const
|
|
485
|
+
const textSuccessTypes = successTypes.filter(({ contentType, value }) => !!contentType && (contentType.startsWith("text/") || contentType.includes("xml") || value === "string"));
|
|
486
|
+
const blobSuccessTypes = successTypes.filter(({ contentType }) => !!contentType && !contentType.includes("json") && !contentType.includes("+json") && !contentType.startsWith("text/") && !contentType.includes("xml"));
|
|
487
|
+
const multiReturnMembers = [
|
|
488
|
+
parsedJsonReturnType,
|
|
489
|
+
...textSuccessTypes.length > 0 ? ["string"] : [],
|
|
490
|
+
...blobSuccessTypes.length > 0 ? ["Blob"] : []
|
|
491
|
+
];
|
|
492
|
+
const refinedMultiImplementationReturnType = `Observable<${[...new Set(multiReturnMembers)].join(" | ")}>`;
|
|
197
493
|
const observeOptions = needsObserveBranching ? {
|
|
198
494
|
body: generateOptions({
|
|
199
495
|
...optionsInput,
|
|
@@ -208,106 +504,128 @@ const generateImplementation = (returnTypesToWrite, { headers, queryParams, oper
|
|
|
208
504
|
angularObserve: "response"
|
|
209
505
|
})
|
|
210
506
|
} : void 0;
|
|
211
|
-
const isModelType = dataType !== "Blob" && dataType !== "string";
|
|
507
|
+
const isModelType = dataType !== "Blob" && dataType !== "string" && dataType !== "ArrayBuffer";
|
|
212
508
|
let functionName = operationName;
|
|
213
|
-
if (isModelType && !hasMultipleContentTypes) functionName += `<TData = ${
|
|
509
|
+
if (isModelType && !hasMultipleContentTypes) functionName += `<TData = ${parsedDataType}>`;
|
|
214
510
|
let contentTypeOverloads = "";
|
|
215
511
|
if (hasMultipleContentTypes && isRequestOptions) {
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
contentTypeOverloads = successTypes.filter((
|
|
219
|
-
const returnType =
|
|
512
|
+
const requiredPart = props.filter((p) => p.required && !p.default).map((p) => p.definition).join(",\n ");
|
|
513
|
+
const optionalPart = props.filter((p) => !p.required || p.default).map((p) => p.definition).join(",\n ");
|
|
514
|
+
contentTypeOverloads = `${successTypes.filter(({ contentType }) => !!contentType).map(({ contentType, value }) => {
|
|
515
|
+
const returnType = getGeneratedResponseType(value, contentType);
|
|
220
516
|
return `${operationName}(${[
|
|
221
|
-
|
|
517
|
+
requiredPart,
|
|
222
518
|
`accept: '${contentType}'`,
|
|
223
|
-
|
|
519
|
+
optionalPart
|
|
224
520
|
].filter(Boolean).join(",\n ")}, options?: HttpClientOptions): Observable<${returnType}>;`;
|
|
225
|
-
}).join("\n ")
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
].filter(Boolean).join(",\n ");
|
|
231
|
-
contentTypeOverloads += `\n ${operationName}(${allParams}, options?: HttpClientOptions): ${multiImplementationReturnType};`;
|
|
521
|
+
}).join("\n ")}\n ${operationName}(${[
|
|
522
|
+
requiredPart,
|
|
523
|
+
`accept?: ${acceptTypeName ?? "string"}`,
|
|
524
|
+
optionalPart
|
|
525
|
+
].filter(Boolean).join(",\n ")}, options?: HttpClientOptions): ${refinedMultiImplementationReturnType};`;
|
|
232
526
|
}
|
|
233
|
-
const observeOverloads = isRequestOptions && !hasMultipleContentTypes ? `${functionName}(${propsDefinition} options?:
|
|
234
|
-
${functionName}(${propsDefinition} options?: HttpClientOptions & { observe: 'events' }): Observable<HttpEvent<${isModelType ? "TData" : dataType}>>;
|
|
235
|
-
${functionName}(${propsDefinition} options?: HttpClientOptions & { observe: 'response' }): Observable<AngularHttpResponse<${isModelType ? "TData" : dataType}>>;` : "";
|
|
527
|
+
const observeOverloads = isRequestOptions && !hasMultipleContentTypes ? `${functionName}(${propsDefinition} options?: HttpClientBodyOptions): Observable<${isModelType ? "TData" : parsedDataType}>;\n ${functionName}(${propsDefinition} options?: HttpClientEventOptions): Observable<HttpEvent<${isModelType ? "TData" : parsedDataType}>>;\n ${functionName}(${propsDefinition} options?: HttpClientResponseOptions): Observable<AngularHttpResponse<${isModelType ? "TData" : parsedDataType}>>;` : "";
|
|
236
528
|
const overloads = contentTypeOverloads || observeOverloads;
|
|
237
|
-
const observableDataType = isModelType ? "TData" :
|
|
529
|
+
const observableDataType = isModelType ? "TData" : parsedDataType;
|
|
238
530
|
const singleImplementationReturnType = isRequestOptions ? `Observable<${observableDataType} | HttpEvent<${observableDataType}> | AngularHttpResponse<${observableDataType}>>` : `Observable<${observableDataType}>`;
|
|
239
531
|
if (hasMultipleContentTypes) {
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
const requiredPart = requiredProps.map((p) => p.implementation).join(",\n ");
|
|
243
|
-
const optionalPart = optionalProps.map((p) => p.implementation).join(",\n ");
|
|
532
|
+
const requiredPart = props.filter((p) => p.required && !p.default).map((p) => p.implementation).join(",\n ");
|
|
533
|
+
const optionalPart = props.filter((p) => !p.required || p.default).map((p) => p.implementation).join(",\n ");
|
|
244
534
|
return ` ${overloads}
|
|
245
535
|
${operationName}(
|
|
246
536
|
${[
|
|
247
537
|
requiredPart,
|
|
248
|
-
`accept: string = '${defaultContentType}'`,
|
|
538
|
+
`accept: ${acceptTypeName ?? "string"} = '${defaultContentType}'`,
|
|
249
539
|
optionalPart
|
|
250
540
|
].filter(Boolean).join(",\n ")},
|
|
251
541
|
${isRequestOptions ? "options?: HttpClientOptions" : ""}
|
|
252
|
-
): ${
|
|
253
|
-
const headers = options?.headers instanceof HttpHeaders
|
|
542
|
+
): ${refinedMultiImplementationReturnType} {${bodyForm}
|
|
543
|
+
${paramsDeclaration}const headers = options?.headers instanceof HttpHeaders
|
|
254
544
|
? options.headers.set('Accept', accept)
|
|
255
545
|
: { ...(options?.headers ?? {}), Accept: accept };
|
|
256
546
|
|
|
257
547
|
if (accept.includes('json') || accept.includes('+json')) {
|
|
258
|
-
return this.http.${verb}<${
|
|
548
|
+
return this.http.${verb}<${parsedJsonReturnType}>(\`${route}\`, {
|
|
259
549
|
...options,
|
|
260
550
|
responseType: 'json',
|
|
261
551
|
headers,
|
|
552
|
+
${angularParamsRef ? `params: ${angularParamsRef},` : ""}
|
|
262
553
|
})${jsonValidationPipe};
|
|
263
554
|
} else if (accept.startsWith('text/') || accept.includes('xml')) {
|
|
264
555
|
return this.http.${verb}(\`${route}\`, {
|
|
265
556
|
...options,
|
|
266
557
|
responseType: 'text',
|
|
267
558
|
headers,
|
|
559
|
+
${angularParamsRef ? `params: ${angularParamsRef},` : ""}
|
|
268
560
|
}) as Observable<string>;
|
|
269
|
-
} else {
|
|
561
|
+
}${blobSuccessTypes.length > 0 ? ` else {
|
|
270
562
|
return this.http.${verb}(\`${route}\`, {
|
|
271
563
|
...options,
|
|
272
564
|
responseType: 'blob',
|
|
273
565
|
headers,
|
|
566
|
+
${angularParamsRef ? `params: ${angularParamsRef},` : ""}
|
|
274
567
|
}) as Observable<Blob>;
|
|
275
|
-
}
|
|
568
|
+
}` : `
|
|
569
|
+
|
|
570
|
+
return this.http.${verb}<${parsedJsonReturnType}>(\`${route}\`, {
|
|
571
|
+
...options,
|
|
572
|
+
responseType: 'json',
|
|
573
|
+
headers,
|
|
574
|
+
${angularParamsRef ? `params: ${angularParamsRef},` : ""}
|
|
575
|
+
})${jsonValidationPipe};`}
|
|
276
576
|
}
|
|
277
577
|
`;
|
|
278
578
|
}
|
|
279
579
|
const observeImplementation = isRequestOptions ? `${paramsDeclaration}if (options?.observe === 'events') {
|
|
280
|
-
return this.http.${verb}${isModelType ? "<TData>" : ""}(${observeOptions?.events ?? options});
|
|
580
|
+
return this.http.${verb}${isModelType ? "<TData>" : ""}(${observeOptions?.events ?? options})${eventValidationPipe};
|
|
281
581
|
}
|
|
282
582
|
|
|
283
583
|
if (options?.observe === 'response') {
|
|
284
|
-
return this.http.${verb}${isModelType ? "<TData>" : ""}(${observeOptions?.response ?? options});
|
|
584
|
+
return this.http.${verb}${isModelType ? "<TData>" : ""}(${observeOptions?.response ?? options})${responseValidationPipe};
|
|
285
585
|
}
|
|
286
586
|
|
|
287
587
|
return this.http.${verb}${isModelType ? "<TData>" : ""}(${observeOptions?.body ?? options})${validationPipe};` : `return this.http.${verb}${isModelType ? "<TData>" : ""}(${options})${validationPipe};`;
|
|
288
588
|
return ` ${overloads}
|
|
289
589
|
${functionName}(
|
|
290
|
-
${toObjectString(props, "implementation")} ${isRequestOptions ? `options?:
|
|
590
|
+
${toObjectString(props, "implementation")} ${isRequestOptions ? `options?: HttpClientObserveOptions` : ""}): ${singleImplementationReturnType} {${bodyForm}
|
|
291
591
|
${observeImplementation}
|
|
292
592
|
}
|
|
293
593
|
`;
|
|
294
594
|
};
|
|
295
|
-
|
|
296
|
-
|
|
595
|
+
/**
|
|
596
|
+
* Orval client builder entry point for Angular `HttpClient` output.
|
|
597
|
+
*
|
|
598
|
+
* It normalizes imports needed for runtime validation, delegates the actual
|
|
599
|
+
* method implementation to `generateHttpClientImplementation`, and returns the
|
|
600
|
+
* generated code plus imports for the current operation.
|
|
601
|
+
*
|
|
602
|
+
* @returns The generated implementation fragment and imports for one operation.
|
|
603
|
+
*/
|
|
604
|
+
const generateAngular = (verbOptions, options) => {
|
|
605
|
+
const isZodOutput = isZodSchemaOutput(options.context.output);
|
|
297
606
|
const responseType = verbOptions.response.definition.success;
|
|
298
|
-
const isPrimitiveResponse =
|
|
607
|
+
const isPrimitiveResponse = isPrimitiveType(responseType);
|
|
299
608
|
const shouldUseRuntimeValidation = verbOptions.override.angular.runtimeValidation && isZodOutput;
|
|
300
609
|
const normalizedVerbOptions = (() => {
|
|
301
610
|
if (!shouldUseRuntimeValidation) return verbOptions;
|
|
302
|
-
let result =
|
|
611
|
+
let result = {
|
|
612
|
+
...verbOptions,
|
|
613
|
+
response: {
|
|
614
|
+
...verbOptions.response,
|
|
615
|
+
imports: verbOptions.response.imports.map((imp) => ({
|
|
616
|
+
...imp,
|
|
617
|
+
values: true
|
|
618
|
+
}))
|
|
619
|
+
}
|
|
620
|
+
};
|
|
303
621
|
if (!isPrimitiveResponse && hasSchemaImport(result.response.imports, responseType)) result = {
|
|
304
622
|
...result,
|
|
305
623
|
response: {
|
|
306
624
|
...result.response,
|
|
307
|
-
imports: result.response.imports.map((imp) => imp.name === responseType ? {
|
|
625
|
+
imports: [...result.response.imports.map((imp) => imp.name === responseType ? {
|
|
308
626
|
...imp,
|
|
309
627
|
values: true
|
|
310
|
-
} : imp)
|
|
628
|
+
} : imp), { name: getSchemaOutputTypeRef$1(responseType) }]
|
|
311
629
|
}
|
|
312
630
|
};
|
|
313
631
|
const successTypes = result.response.types.success;
|
|
@@ -315,21 +633,21 @@ const createAngularClient = (returnTypesToWrite) => (verbOptions, options, _outp
|
|
|
315
633
|
const jsonSchemaNames = [...new Set(successTypes.filter(({ contentType }) => !!contentType && (contentType.includes("json") || contentType.includes("+json"))).map(({ value }) => value))];
|
|
316
634
|
if (jsonSchemaNames.length === 1) {
|
|
317
635
|
const jsonType = jsonSchemaNames[0];
|
|
318
|
-
if (!
|
|
636
|
+
if (!isPrimitiveType(jsonType) && hasSchemaImport(result.response.imports, jsonType)) result = {
|
|
319
637
|
...result,
|
|
320
638
|
response: {
|
|
321
639
|
...result.response,
|
|
322
|
-
imports: result.response.imports.map((imp) => imp.name === jsonType ? {
|
|
640
|
+
imports: [...result.response.imports.map((imp) => imp.name === jsonType ? {
|
|
323
641
|
...imp,
|
|
324
642
|
values: true
|
|
325
|
-
} : imp)
|
|
643
|
+
} : imp), { name: getSchemaOutputTypeRef$1(jsonType) }]
|
|
326
644
|
}
|
|
327
645
|
};
|
|
328
646
|
}
|
|
329
647
|
}
|
|
330
648
|
return result;
|
|
331
649
|
})();
|
|
332
|
-
const implementation =
|
|
650
|
+
const implementation = generateHttpClientImplementation(normalizedVerbOptions, options);
|
|
333
651
|
return {
|
|
334
652
|
implementation,
|
|
335
653
|
imports: [...generateVerbImports(normalizedVerbOptions), ...implementation.includes(".pipe(map(") ? [{
|
|
@@ -339,22 +657,692 @@ const createAngularClient = (returnTypesToWrite) => (verbOptions, options, _outp
|
|
|
339
657
|
}] : []]
|
|
340
658
|
};
|
|
341
659
|
};
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
660
|
+
/**
|
|
661
|
+
* Returns the footer aliases collected for the provided operation names.
|
|
662
|
+
*
|
|
663
|
+
* The Angular generators use these aliases to expose stable `ClientResult`
|
|
664
|
+
* helper types such as `ListPetsClientResult`.
|
|
665
|
+
*
|
|
666
|
+
* @returns Concatenated `ClientResult` aliases for the requested operation names.
|
|
667
|
+
*/
|
|
668
|
+
const getHttpClientReturnTypes = (operationNames) => returnTypesRegistry.getFooter(operationNames);
|
|
669
|
+
/**
|
|
670
|
+
* Clears the module-level return type registry used during Angular client
|
|
671
|
+
* generation.
|
|
672
|
+
*
|
|
673
|
+
* This must be called at the start of each generation pass to avoid leaking
|
|
674
|
+
* aliases across files or tags.
|
|
675
|
+
*
|
|
676
|
+
* @returns Nothing.
|
|
677
|
+
*/
|
|
678
|
+
const resetHttpClientReturnTypes = () => {
|
|
679
|
+
returnTypesRegistry.reset();
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
//#endregion
|
|
683
|
+
//#region src/http-resource.ts
|
|
684
|
+
const isAngularHttpResourceOptions = (value) => value === void 0 || isObject(value) && (value.defaultValue === void 0 || typeof value.defaultValue === "string" || typeof value.defaultValue === "number" || typeof value.defaultValue === "boolean" || value.defaultValue === null || Array.isArray(value.defaultValue) || isObject(value.defaultValue)) && (value.debugName === void 0 || typeof value.debugName === "string") && (value.injector === void 0 || typeof value.injector === "string") && (value.equal === void 0 || typeof value.equal === "string");
|
|
685
|
+
const isAngularOperationOverride = (value) => value !== void 0 && typeof value === "object" && value !== null && (!("client" in value) || value.client === "httpClient" || value.client === "httpResource" || value.client === "both") && (!("httpResource" in value) || isAngularHttpResourceOptions(value.httpResource));
|
|
686
|
+
const getClientOverride = (verbOption) => {
|
|
687
|
+
const angular = verbOption.override.operations[verbOption.operationId]?.angular;
|
|
688
|
+
return isAngularOperationOverride(angular) ? angular.client : void 0;
|
|
689
|
+
};
|
|
690
|
+
/**
|
|
691
|
+
* Resolves the effective `httpResource` option override for an operation.
|
|
692
|
+
*
|
|
693
|
+
* Operation-level configuration takes precedence over the global
|
|
694
|
+
* `override.angular.httpResource` block while still inheriting unspecified
|
|
695
|
+
* values from the global configuration.
|
|
696
|
+
*
|
|
697
|
+
* @returns The merged resource options for the operation, or `undefined` when no override exists.
|
|
698
|
+
*/
|
|
699
|
+
const getHttpResourceOverride = (verbOption, output) => {
|
|
700
|
+
const operationAngular = verbOption.override.operations[verbOption.operationId]?.angular;
|
|
701
|
+
const operationOverride = isAngularOperationOverride(operationAngular) ? operationAngular.httpResource : void 0;
|
|
702
|
+
const angularOverride = output.override.angular;
|
|
703
|
+
const globalOverride = isObject(angularOverride) && "httpResource" in angularOverride && isAngularHttpResourceOptions(angularOverride.httpResource) ? angularOverride.httpResource : void 0;
|
|
704
|
+
if (globalOverride === void 0) return operationOverride;
|
|
705
|
+
if (operationOverride === void 0) return globalOverride;
|
|
706
|
+
return {
|
|
707
|
+
...globalOverride,
|
|
708
|
+
...operationOverride
|
|
709
|
+
};
|
|
710
|
+
};
|
|
711
|
+
const resourceReturnTypesRegistry = createReturnTypesRegistry();
|
|
712
|
+
/** @internal Exported for testing only */
|
|
713
|
+
const routeRegistry = createRouteRegistry();
|
|
714
|
+
const getHeader = (option, info) => {
|
|
715
|
+
if (!option || !info) return "";
|
|
716
|
+
const header = option(info);
|
|
717
|
+
return Array.isArray(header) ? jsDoc({ description: header }) : header;
|
|
718
|
+
};
|
|
719
|
+
const mergeDependencies = (deps) => {
|
|
720
|
+
const merged = /* @__PURE__ */ new Map();
|
|
721
|
+
for (const dep of deps) {
|
|
722
|
+
const existing = merged.get(dep.dependency);
|
|
723
|
+
if (!existing) {
|
|
724
|
+
merged.set(dep.dependency, {
|
|
725
|
+
exports: [...dep.exports],
|
|
726
|
+
dependency: dep.dependency
|
|
727
|
+
});
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
for (const exp of dep.exports) if (!existing.exports.some((current) => current.name === exp.name && current.alias === exp.alias)) existing.exports.push(exp);
|
|
731
|
+
}
|
|
732
|
+
return [...merged.values()];
|
|
733
|
+
};
|
|
734
|
+
const cloneDependencies = (deps) => deps.map((dep) => ({
|
|
735
|
+
...dep,
|
|
736
|
+
exports: [...dep.exports]
|
|
737
|
+
}));
|
|
738
|
+
/**
|
|
739
|
+
* Returns the merged dependency list required when Angular `httpResource`
|
|
740
|
+
* output coexists with Angular `HttpClient` service generation.
|
|
741
|
+
*
|
|
742
|
+
* This is used for pure `httpResource` mode as well as mixed generation paths
|
|
743
|
+
* that still need Angular common HTTP symbols and service helpers.
|
|
744
|
+
*
|
|
745
|
+
* @returns The de-duplicated dependency descriptors for Angular resource generation.
|
|
746
|
+
*/
|
|
747
|
+
const getAngularHttpResourceDependencies = () => mergeDependencies([...ANGULAR_HTTP_CLIENT_DEPENDENCIES, ...ANGULAR_HTTP_RESOURCE_DEPENDENCIES]);
|
|
748
|
+
/**
|
|
749
|
+
* Returns only the dependencies required by standalone generated resource
|
|
750
|
+
* files, such as the sibling `*.resource.ts` output used in `both` mode.
|
|
751
|
+
*
|
|
752
|
+
* @returns The dependency descriptors required by resource-only files.
|
|
753
|
+
*/
|
|
754
|
+
const getAngularHttpResourceOnlyDependencies = () => cloneDependencies(ANGULAR_HTTP_RESOURCE_DEPENDENCIES);
|
|
755
|
+
const isResponseText = (contentType, dataType) => {
|
|
756
|
+
if (dataType === "string") return true;
|
|
757
|
+
if (!contentType) return false;
|
|
758
|
+
return contentType.startsWith("text/") || contentType.includes("xml");
|
|
759
|
+
};
|
|
760
|
+
const isResponseArrayBuffer = (contentType) => {
|
|
761
|
+
if (!contentType) return false;
|
|
762
|
+
return contentType.includes("application/octet-stream") || contentType.includes("application/pdf");
|
|
763
|
+
};
|
|
764
|
+
const isResponseBlob = (contentType, isBlob) => {
|
|
765
|
+
if (isBlob) return true;
|
|
766
|
+
if (!contentType) return false;
|
|
767
|
+
return contentType.startsWith("image/") || contentType.includes("blob");
|
|
768
|
+
};
|
|
769
|
+
const HTTP_RESOURCE_OPTIONS_TYPE_NAME = "OrvalHttpResourceOptions";
|
|
770
|
+
const getHttpResourceFactory = (response, contentType, dataType) => {
|
|
771
|
+
if (isResponseText(contentType, dataType)) return "httpResource.text";
|
|
772
|
+
if (isResponseBlob(contentType, response.isBlob)) return "httpResource.blob";
|
|
773
|
+
if (isResponseArrayBuffer(contentType)) return "httpResource.arrayBuffer";
|
|
774
|
+
return "httpResource";
|
|
775
|
+
};
|
|
776
|
+
const getHttpResourceRawType = (factory) => {
|
|
777
|
+
switch (factory) {
|
|
778
|
+
case "httpResource.text": return "string";
|
|
779
|
+
case "httpResource.arrayBuffer": return "ArrayBuffer";
|
|
780
|
+
case "httpResource.blob": return "Blob";
|
|
781
|
+
default: return "unknown";
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
const getTypeWithoutDefault = (definition) => {
|
|
785
|
+
const match = /^([^:]+):\s*(.+)$/.exec(definition);
|
|
786
|
+
if (!match) return definition;
|
|
787
|
+
return match[2].replace(/\s*=\s*.*$/, "").trim();
|
|
788
|
+
};
|
|
789
|
+
const getDefaultValueFromImplementation = (implementation) => {
|
|
790
|
+
const match = /=\s*(.+)$/.exec(implementation);
|
|
791
|
+
return match ? match[1].trim() : void 0;
|
|
792
|
+
};
|
|
793
|
+
const withSignal = (prop, options = {}) => {
|
|
794
|
+
const type = getTypeWithoutDefault(prop.definition);
|
|
795
|
+
const derivedDefault = getDefaultValueFromImplementation(prop.implementation) !== void 0 || prop.default !== void 0;
|
|
796
|
+
const hasDefault = options.hasDefault ?? derivedDefault;
|
|
797
|
+
const nameMatch = /^([^:]+):/.exec(prop.definition);
|
|
798
|
+
const hasOptionalMark = (nameMatch ? nameMatch[1] : prop.name).includes("?");
|
|
799
|
+
const optional = prop.required && !hasDefault && !hasOptionalMark ? "" : "?";
|
|
800
|
+
const definition = `${prop.name}${optional}: Signal<${type}>`;
|
|
801
|
+
return {
|
|
802
|
+
definition,
|
|
803
|
+
implementation: definition
|
|
804
|
+
};
|
|
805
|
+
};
|
|
806
|
+
const buildSignalProps = (props, params) => {
|
|
807
|
+
const paramDefaults = /* @__PURE__ */ new Map();
|
|
808
|
+
for (const param of params) {
|
|
809
|
+
const hasDefault = getDefaultValueFromImplementation(param.implementation) !== void 0 || param.default !== void 0;
|
|
810
|
+
paramDefaults.set(param.name, hasDefault);
|
|
811
|
+
}
|
|
812
|
+
return props.map((prop) => {
|
|
813
|
+
switch (prop.type) {
|
|
814
|
+
case GetterPropType.NAMED_PATH_PARAMS: return {
|
|
815
|
+
...prop,
|
|
816
|
+
name: "pathParams",
|
|
817
|
+
definition: `pathParams: Signal<${prop.schema.name}>`,
|
|
818
|
+
implementation: `pathParams: Signal<${prop.schema.name}>`
|
|
819
|
+
};
|
|
820
|
+
case GetterPropType.PARAM:
|
|
821
|
+
case GetterPropType.QUERY_PARAM:
|
|
822
|
+
case GetterPropType.BODY:
|
|
823
|
+
case GetterPropType.HEADER: {
|
|
824
|
+
const signalProp = withSignal(prop, { hasDefault: prop.type === GetterPropType.PARAM ? paramDefaults.get(prop.name) ?? false : void 0 });
|
|
825
|
+
return {
|
|
826
|
+
...prop,
|
|
827
|
+
definition: signalProp.definition,
|
|
828
|
+
implementation: signalProp.implementation
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
default: return prop;
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
};
|
|
835
|
+
const applySignalRoute = (route, params, useNamedParams) => {
|
|
836
|
+
let updatedRoute = route;
|
|
837
|
+
for (const param of params) {
|
|
838
|
+
const template = "${" + param.name + "}";
|
|
839
|
+
const defaultValue = getDefaultValueFromImplementation(param.implementation);
|
|
840
|
+
let replacement;
|
|
841
|
+
if (useNamedParams) replacement = defaultValue === void 0 ? "${pathParams()." + param.name + "}" : "${pathParams()?." + param.name + " ?? " + defaultValue + "}";
|
|
842
|
+
else replacement = defaultValue === void 0 ? "${" + param.name + "()}" : "${" + param.name + "?.() ?? " + defaultValue + "}";
|
|
843
|
+
updatedRoute = updatedRoute.replaceAll(template, replacement);
|
|
844
|
+
}
|
|
845
|
+
return updatedRoute;
|
|
846
|
+
};
|
|
847
|
+
const buildResourceRequest = ({ verb, body, headers, queryParams, paramsSerializer, override, formData, formUrlEncoded }, route) => {
|
|
848
|
+
const isFormData = !override.formData.disabled;
|
|
849
|
+
const isFormUrlEncoded = override.formUrlEncoded !== false;
|
|
850
|
+
const bodyForm = generateFormDataAndUrlEncodedFunction({
|
|
851
|
+
formData,
|
|
852
|
+
formUrlEncoded,
|
|
853
|
+
body,
|
|
854
|
+
isFormData,
|
|
855
|
+
isFormUrlEncoded
|
|
856
|
+
});
|
|
857
|
+
const hasFormData = isFormData && body.formData;
|
|
858
|
+
const hasFormUrlEncoded = isFormUrlEncoded && body.formUrlEncoded;
|
|
859
|
+
const bodyAccess = body.definition ? body.isOptional ? `${body.implementation}?.()` : `${body.implementation}()` : void 0;
|
|
860
|
+
const bodyValue = hasFormData ? "formData" : hasFormUrlEncoded ? "formUrlEncoded" : bodyAccess;
|
|
861
|
+
const paramsAccess = queryParams ? "params?.()" : void 0;
|
|
862
|
+
const headersAccess = headers ? "headers?.()" : void 0;
|
|
863
|
+
const filteredParamsValue = paramsAccess ? getAngularFilteredParamsCallExpression(`${paramsAccess} ?? {}`, queryParams?.requiredNullableKeys ?? [], !!paramsSerializer) : void 0;
|
|
864
|
+
const paramsValue = paramsAccess ? paramsSerializer ? `params?.() ? ${paramsSerializer.name}(${filteredParamsValue}) : undefined` : filteredParamsValue : void 0;
|
|
865
|
+
const isGet = verb === "get";
|
|
866
|
+
const isUrlOnly = !(!isGet || !!bodyValue || !!paramsValue || !!headersAccess) && !bodyForm;
|
|
867
|
+
const requestLines = [
|
|
868
|
+
`url: \`${route}\``,
|
|
869
|
+
isGet ? void 0 : `method: '${verb.toUpperCase()}'`,
|
|
870
|
+
bodyValue ? `body: ${bodyValue}` : void 0,
|
|
871
|
+
paramsValue ? `params: ${paramsValue}` : void 0,
|
|
872
|
+
headersAccess ? `headers: ${headersAccess}` : void 0
|
|
873
|
+
].filter(Boolean);
|
|
874
|
+
return {
|
|
875
|
+
bodyForm,
|
|
876
|
+
request: isUrlOnly ? `\`${route}\`` : `({\n ${requestLines.join(",\n ")}\n })`,
|
|
877
|
+
isUrlOnly
|
|
878
|
+
};
|
|
879
|
+
};
|
|
880
|
+
const getSchemaOutputTypeRef = (typeName) => typeName === "Error" ? "ErrorOutput" : `${typeName}Output`;
|
|
881
|
+
const getHttpResourceResponseImports = (response) => {
|
|
882
|
+
const successDefinition = response.definition.success;
|
|
883
|
+
if (!successDefinition) return [];
|
|
884
|
+
return response.imports.filter((imp) => {
|
|
885
|
+
const name = imp.alias ?? imp.name;
|
|
886
|
+
return new RegExp(String.raw`\b${escapeRegExp(name)}\b`, "g").test(successDefinition);
|
|
887
|
+
});
|
|
888
|
+
};
|
|
889
|
+
const getHttpResourceVerbImports = (verbOptions, output) => {
|
|
890
|
+
const { response, body, queryParams, props, headers, params } = verbOptions;
|
|
891
|
+
return [
|
|
892
|
+
...isZodSchemaOutput(output) ? [...getHttpResourceResponseImports(response).map((imp) => ({
|
|
893
|
+
...imp,
|
|
894
|
+
values: true
|
|
895
|
+
})), ...getHttpResourceResponseImports(response).filter((imp) => !isPrimitiveType(imp.name)).map((imp) => ({ name: getSchemaOutputTypeRef(imp.name) }))] : getHttpResourceResponseImports(response),
|
|
896
|
+
...body.imports,
|
|
897
|
+
...props.flatMap((prop) => prop.type === GetterPropType.NAMED_PATH_PARAMS ? [{ name: prop.schema.name }] : []),
|
|
898
|
+
...queryParams ? [{ name: queryParams.schema.name }] : [],
|
|
899
|
+
...headers ? [{ name: headers.schema.name }] : [],
|
|
900
|
+
...params.flatMap(({ imports }) => imports),
|
|
901
|
+
{
|
|
902
|
+
name: "map",
|
|
903
|
+
values: true,
|
|
904
|
+
importPath: "rxjs"
|
|
905
|
+
}
|
|
906
|
+
];
|
|
907
|
+
};
|
|
908
|
+
const getParseExpression = (response, factory, output, responseTypeOverride) => {
|
|
909
|
+
if (factory !== "httpResource") return void 0;
|
|
910
|
+
const zodSchema = response.imports.find((imp) => imp.isZodSchema);
|
|
911
|
+
if (zodSchema) return `${zodSchema.name}.parse`;
|
|
912
|
+
if (!output.override.angular.runtimeValidation) return void 0;
|
|
913
|
+
if (!isZodSchemaOutput(output)) return void 0;
|
|
914
|
+
const responseType = responseTypeOverride ?? response.definition.success;
|
|
915
|
+
if (!responseType) return void 0;
|
|
916
|
+
if (isPrimitiveType(responseType)) return void 0;
|
|
917
|
+
if (!response.imports.some((imp) => imp.name === responseType)) return void 0;
|
|
918
|
+
return `${responseType}.parse`;
|
|
919
|
+
};
|
|
920
|
+
/**
|
|
921
|
+
* Builds the literal option entries that Orval injects into generated
|
|
922
|
+
* `httpResource()` calls.
|
|
923
|
+
*
|
|
924
|
+
* This merges user-supplied generator configuration such as `defaultValue` or
|
|
925
|
+
* `debugName` with automatically derived runtime-validation hooks like
|
|
926
|
+
* `parse: Schema.parse`.
|
|
927
|
+
*
|
|
928
|
+
* @returns The option entries plus metadata about whether a configured default value exists.
|
|
929
|
+
*/
|
|
930
|
+
const buildHttpResourceOptionsLiteral = (verbOption, factory, output, responseTypeOverride) => {
|
|
931
|
+
const override = getHttpResourceOverride(verbOption, output);
|
|
932
|
+
const parseExpression = getParseExpression(verbOption.response, factory, output, responseTypeOverride);
|
|
933
|
+
const defaultValueLiteral = override?.defaultValue === void 0 ? void 0 : JSON.stringify(override.defaultValue);
|
|
934
|
+
return {
|
|
935
|
+
entries: [
|
|
936
|
+
parseExpression ? `parse: ${parseExpression}` : void 0,
|
|
937
|
+
defaultValueLiteral ? `defaultValue: ${defaultValueLiteral}` : void 0,
|
|
938
|
+
override?.debugName === void 0 ? void 0 : `debugName: ${JSON.stringify(override.debugName)}`,
|
|
939
|
+
override?.injector ? `injector: ${override.injector}` : void 0,
|
|
940
|
+
override?.equal ? `equal: ${override.equal}` : void 0
|
|
941
|
+
].filter((value) => value !== void 0),
|
|
942
|
+
hasDefaultValue: defaultValueLiteral !== void 0
|
|
943
|
+
};
|
|
944
|
+
};
|
|
945
|
+
const appendArgument = (args, argument) => {
|
|
946
|
+
const normalizedArgs = args.trim().replace(/,\s*$/, "");
|
|
947
|
+
return normalizedArgs.length > 0 ? `${normalizedArgs},
|
|
948
|
+
${argument}` : argument;
|
|
949
|
+
};
|
|
950
|
+
const normalizeOptionalParametersForRequiredTrailingArg = (args) => args.replaceAll(/(\w+)\?:\s*([^,\n]+)(,?)/g, "$1: $2 | undefined$3");
|
|
951
|
+
const buildHttpResourceOptionsArgument = (valueType, rawType, options, omitParse = false) => {
|
|
952
|
+
const baseType = `${HTTP_RESOURCE_OPTIONS_TYPE_NAME}<${valueType}, ${rawType}${omitParse ? ", true" : ""}>`;
|
|
953
|
+
return options.requiresDefaultValue ? `options: ${baseType} & { defaultValue: NoInfer<${valueType}> }` : `options?: ${baseType}`;
|
|
954
|
+
};
|
|
955
|
+
const buildHttpResourceOptionsExpression = (configuredEntries) => {
|
|
956
|
+
if (configuredEntries.length === 0) return "options";
|
|
957
|
+
return `{
|
|
958
|
+
...(options ?? {}),
|
|
959
|
+
${configuredEntries.join(",\n ")}
|
|
960
|
+
}`;
|
|
961
|
+
};
|
|
962
|
+
const buildHttpResourceFunctionSignatures = (resourceName, args, valueType, rawType, hasConfiguredDefaultValue, omitParse = false) => {
|
|
963
|
+
if (hasConfiguredDefaultValue) return `export function ${resourceName}(${appendArgument(args, buildHttpResourceOptionsArgument(valueType, rawType, { requiresDefaultValue: false }, omitParse))}): HttpResourceRef<${valueType}>`;
|
|
964
|
+
return `export function ${resourceName}(${appendArgument(normalizeOptionalParametersForRequiredTrailingArg(args), buildHttpResourceOptionsArgument(valueType, rawType, { requiresDefaultValue: true }, omitParse))}): HttpResourceRef<${valueType}>;
|
|
965
|
+
export function ${resourceName}(${appendArgument(args, buildHttpResourceOptionsArgument(valueType, rawType, { requiresDefaultValue: false }, omitParse))}): HttpResourceRef<${valueType} | undefined>`;
|
|
966
|
+
};
|
|
967
|
+
/**
|
|
968
|
+
* Generates a single Angular `httpResource` helper function for an operation.
|
|
969
|
+
*
|
|
970
|
+
* The generated output handles signal-wrapped parameters, route interpolation,
|
|
971
|
+
* request-body construction, content-type branching, runtime validation, and
|
|
972
|
+
* optional mutator integration when the mutator is compatible with standalone
|
|
973
|
+
* resource functions.
|
|
974
|
+
*
|
|
975
|
+
* @remarks
|
|
976
|
+
* This function emits overloads when content negotiation or caller-supplied
|
|
977
|
+
* `defaultValue` support requires multiple signatures.
|
|
978
|
+
*
|
|
979
|
+
* @returns A string containing the complete generated resource helper.
|
|
980
|
+
*/
|
|
981
|
+
const buildHttpResourceFunction = (verbOption, route, output) => {
|
|
982
|
+
const { operationName, response, props, params, mutator } = verbOption;
|
|
983
|
+
const dataType = response.definition.success || "unknown";
|
|
984
|
+
const omitParse = isZodSchemaOutput(output);
|
|
985
|
+
const responseSchemaImports = getHttpResourceResponseImports(response);
|
|
986
|
+
const hasResponseSchemaImport = responseSchemaImports.some((imp) => imp.name === dataType);
|
|
987
|
+
const resourceName = `${operationName}Resource`;
|
|
988
|
+
const parsedDataType = omitParse && output.override.angular.runtimeValidation && !isPrimitiveType(dataType) && hasResponseSchemaImport ? getSchemaOutputTypeRef(dataType) : dataType;
|
|
989
|
+
const successTypes = response.types.success;
|
|
990
|
+
const overallReturnType = successTypes.length <= 1 ? parsedDataType : [...new Set(successTypes.map((type) => getHttpResourceGeneratedResponseType(type.value, type.contentType, responseSchemaImports, output)))].join(" | ") || parsedDataType;
|
|
991
|
+
resourceReturnTypesRegistry.set(operationName, `export type ${pascal(operationName)}ResourceResult = NonNullable<${overallReturnType}>`);
|
|
992
|
+
const uniqueContentTypes = getUniqueContentTypes(successTypes);
|
|
993
|
+
const defaultSuccess = getDefaultSuccessType(successTypes, dataType);
|
|
994
|
+
const jsonContentType = successTypes.find((type) => type.contentType.includes("json"))?.contentType;
|
|
995
|
+
const resourceFactory = getHttpResourceFactory(response, jsonContentType ?? defaultSuccess.contentType, dataType);
|
|
996
|
+
const signalRoute = applySignalRoute(route, params, props.some((prop) => prop.type === GetterPropType.NAMED_PATH_PARAMS));
|
|
997
|
+
const signalProps = buildSignalProps(props, params);
|
|
998
|
+
const args = toObjectString(signalProps, "implementation");
|
|
999
|
+
const { bodyForm, request, isUrlOnly } = buildResourceRequest(verbOption, signalRoute);
|
|
1000
|
+
if (uniqueContentTypes.length > 1) {
|
|
1001
|
+
const defaultContentType = jsonContentType ?? defaultSuccess.contentType;
|
|
1002
|
+
const acceptTypeName = getAcceptHelperName(operationName);
|
|
1003
|
+
const requiredProps = signalProps.filter((_, index) => props[index]?.required && !props[index]?.default);
|
|
1004
|
+
const optionalProps = signalProps.filter((_, index) => !props[index]?.required || props[index]?.default);
|
|
1005
|
+
const requiredPart = requiredProps.map((prop) => prop.implementation).join(",\n ");
|
|
1006
|
+
const optionalPart = optionalProps.map((prop) => prop.implementation).join(",\n ");
|
|
1007
|
+
const getBranchReturnType = (type) => getHttpResourceGeneratedResponseType(type.value, type.contentType, responseSchemaImports, output);
|
|
1008
|
+
const unionReturnType = [...new Set(successTypes.filter((type) => type.contentType).map((type) => getBranchReturnType(type)))].join(" | ");
|
|
1009
|
+
const branchOverloads = successTypes.filter((type) => type.contentType).map((type) => {
|
|
1010
|
+
const returnType = getBranchReturnType(type);
|
|
1011
|
+
return `export function ${resourceName}(${[
|
|
1012
|
+
requiredPart,
|
|
1013
|
+
`accept: '${type.contentType}'`,
|
|
1014
|
+
optionalPart,
|
|
1015
|
+
`options?: ${buildBranchOptionsType(unionReturnType, "unknown", omitParse)}`
|
|
1016
|
+
].filter(Boolean).join(",\n ")}): HttpResourceRef<${returnType} | undefined>;`;
|
|
1017
|
+
}).join("\n");
|
|
1018
|
+
const implementationArgs = [
|
|
1019
|
+
requiredPart,
|
|
1020
|
+
`accept?: ${acceptTypeName}`,
|
|
1021
|
+
optionalPart,
|
|
1022
|
+
`options?: ${buildBranchOptionsType(unionReturnType, "unknown", omitParse)}`
|
|
1023
|
+
].filter(Boolean).join(",\n ");
|
|
1024
|
+
const implementationArgsWithDefault = [
|
|
1025
|
+
requiredPart,
|
|
1026
|
+
`accept: ${acceptTypeName} = '${defaultContentType}'`,
|
|
1027
|
+
optionalPart,
|
|
1028
|
+
`options?: ${buildBranchOptionsType(unionReturnType, "unknown", omitParse)}`
|
|
1029
|
+
].filter(Boolean).join(",\n ");
|
|
1030
|
+
const getBranchOptions = (type) => {
|
|
1031
|
+
if (!type) return `options as ${buildBranchOptionsType(unionReturnType, "unknown", omitParse)}`;
|
|
1032
|
+
const factory = getHttpResourceFactory(response, type.contentType, type.value);
|
|
1033
|
+
return `${buildHttpResourceOptionsExpression(buildHttpResourceOptionsLiteral(verbOption, factory, output, type.value).entries) ?? "options"} as unknown as ${buildBranchOptionsType(getBranchReturnType(type), getHttpResourceRawType(factory), omitParse)}`;
|
|
1034
|
+
};
|
|
1035
|
+
const jsonType = successTypes.find((type) => type.contentType.includes("json") || type.contentType.includes("+json"));
|
|
1036
|
+
const textType = successTypes.find((type) => isResponseText(type.contentType, type.value));
|
|
1037
|
+
const arrayBufferType = successTypes.find((type) => isResponseArrayBuffer(type.contentType));
|
|
1038
|
+
const blobType = successTypes.find((type) => isResponseBlob(type.contentType, response.isBlob));
|
|
1039
|
+
const fallbackReturn = blobType ? `return httpResource.blob<Blob>(() => ({
|
|
1040
|
+
...normalizedRequest,
|
|
1041
|
+
headers,
|
|
1042
|
+
}), ${getBranchOptions(blobType)});` : textType ? `return httpResource.text<string>(() => ({
|
|
1043
|
+
...normalizedRequest,
|
|
1044
|
+
headers,
|
|
1045
|
+
}), ${getBranchOptions(textType)});` : `return httpResource<${jsonType ? getBranchReturnType(jsonType) : parsedDataType}>(() => ({
|
|
1046
|
+
...normalizedRequest,
|
|
1047
|
+
headers,
|
|
1048
|
+
}), ${getBranchOptions(jsonType)});`;
|
|
1049
|
+
const normalizeRequest = isUrlOnly ? `const normalizedRequest: HttpResourceRequest = { url: request };` : `const normalizedRequest: HttpResourceRequest = request;`;
|
|
1050
|
+
return `/**
|
|
1051
|
+
* @experimental httpResource is experimental (Angular v19.2+)
|
|
1052
|
+
*/
|
|
1053
|
+
${branchOverloads}
|
|
1054
|
+
export function ${resourceName}(
|
|
1055
|
+
${implementationArgs}
|
|
1056
|
+
): HttpResourceRef<${unionReturnType} | undefined>;
|
|
1057
|
+
export function ${resourceName}(
|
|
1058
|
+
${implementationArgsWithDefault}
|
|
1059
|
+
): HttpResourceRef<${unionReturnType} | undefined> {
|
|
1060
|
+
${bodyForm ? `${bodyForm};` : ""}
|
|
1061
|
+
const request = ${request};
|
|
1062
|
+
${normalizeRequest}
|
|
1063
|
+
const headers = normalizedRequest.headers instanceof HttpHeaders
|
|
1064
|
+
? normalizedRequest.headers.set('Accept', accept)
|
|
1065
|
+
: { ...(normalizedRequest.headers ?? {}), Accept: accept };
|
|
1066
|
+
|
|
1067
|
+
if (accept.includes('json') || accept.includes('+json')) {
|
|
1068
|
+
return httpResource<${jsonType ? getBranchReturnType(jsonType) : parsedDataType}>(() => ({
|
|
1069
|
+
...normalizedRequest,
|
|
1070
|
+
headers,
|
|
1071
|
+
}), ${getBranchOptions(jsonType)});
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (accept.startsWith('text/') || accept.includes('xml')) {
|
|
1075
|
+
return httpResource.text<string>(() => ({
|
|
1076
|
+
...normalizedRequest,
|
|
1077
|
+
headers,
|
|
1078
|
+
}), ${getBranchOptions(textType)});
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
${arrayBufferType ? `if (accept.includes('octet-stream') || accept.includes('pdf')) {
|
|
1082
|
+
return httpResource.arrayBuffer<ArrayBuffer>(() => ({
|
|
1083
|
+
...normalizedRequest,
|
|
1084
|
+
headers,
|
|
1085
|
+
}), ${getBranchOptions(arrayBufferType)});
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
` : ""}${fallbackReturn}
|
|
1089
|
+
}
|
|
1090
|
+
`;
|
|
1091
|
+
}
|
|
1092
|
+
const resourceOptions = buildHttpResourceOptionsLiteral(verbOption, resourceFactory, output);
|
|
1093
|
+
const rawType = getHttpResourceRawType(resourceFactory);
|
|
1094
|
+
const resourceValueType = resourceOptions.hasDefaultValue ? parsedDataType : `${parsedDataType} | undefined`;
|
|
1095
|
+
const functionSignatures = buildHttpResourceFunctionSignatures(resourceName, args, parsedDataType, rawType, resourceOptions.hasDefaultValue, omitParse);
|
|
1096
|
+
const implementationArgs = appendArgument(args, buildHttpResourceOptionsArgument(parsedDataType, rawType, { requiresDefaultValue: false }, omitParse));
|
|
1097
|
+
const optionsExpression = buildHttpResourceOptionsExpression(resourceOptions.entries);
|
|
1098
|
+
const resourceCallOptions = optionsExpression ? `, ${optionsExpression}` : "";
|
|
1099
|
+
const isResourceCompatibleMutator = mutator !== void 0 && !mutator.hasSecondArg;
|
|
1100
|
+
const returnExpression = isResourceCompatibleMutator ? `${mutator.name}(request)` : "request";
|
|
1101
|
+
if (isUrlOnly && !isResourceCompatibleMutator) return `/**
|
|
1102
|
+
* @experimental httpResource is experimental (Angular v19.2+)
|
|
1103
|
+
*/
|
|
1104
|
+
${functionSignatures};
|
|
1105
|
+
export function ${resourceName}(${implementationArgs}): HttpResourceRef<${resourceValueType}> {
|
|
1106
|
+
return ${resourceFactory}<${parsedDataType}>(() => ${request}${resourceCallOptions});
|
|
1107
|
+
}
|
|
1108
|
+
`;
|
|
1109
|
+
return `/**
|
|
1110
|
+
* @experimental httpResource is experimental (Angular v19.2+)
|
|
1111
|
+
*/
|
|
1112
|
+
${functionSignatures};
|
|
1113
|
+
export function ${resourceName}(${implementationArgs}): HttpResourceRef<${resourceValueType}> {
|
|
1114
|
+
return ${resourceFactory}<${parsedDataType}>(() => {
|
|
1115
|
+
${bodyForm ? `${bodyForm};` : ""}
|
|
1116
|
+
const request = ${request};
|
|
1117
|
+
return ${returnExpression};
|
|
1118
|
+
}${resourceCallOptions});
|
|
1119
|
+
}
|
|
1120
|
+
`;
|
|
1121
|
+
};
|
|
1122
|
+
const buildHttpResourceOptionsUtilities = (omitParse) => `
|
|
1123
|
+
export type ${HTTP_RESOURCE_OPTIONS_TYPE_NAME}<TValue, TRaw = unknown, TOmitParse extends boolean = ${omitParse}> = TOmitParse extends true
|
|
1124
|
+
? Omit<HttpResourceOptions<TValue, TRaw>, 'parse'>
|
|
1125
|
+
: HttpResourceOptions<TValue, TRaw>;
|
|
1126
|
+
`;
|
|
1127
|
+
const getContentTypeReturnType = (contentType, value) => {
|
|
1128
|
+
if (!contentType) return value;
|
|
1129
|
+
if (contentType.includes("json") || contentType.includes("+json")) return value;
|
|
1130
|
+
if (contentType.startsWith("text/") || contentType.includes("xml")) return "string";
|
|
1131
|
+
if (isResponseArrayBuffer(contentType)) return "ArrayBuffer";
|
|
1132
|
+
return "Blob";
|
|
1133
|
+
};
|
|
1134
|
+
const getHttpResourceGeneratedResponseType = (value, contentType, responseImports, output) => {
|
|
1135
|
+
if (isZodSchemaOutput(output) && output.override.angular.runtimeValidation && !!contentType && (contentType.includes("json") || contentType.includes("+json")) && !isPrimitiveType(value) && responseImports.some((imp) => imp.name === value)) return getSchemaOutputTypeRef(value);
|
|
1136
|
+
return getContentTypeReturnType(contentType, value);
|
|
1137
|
+
};
|
|
1138
|
+
const buildBranchOptionsType = (valueType, rawType, omitParse) => `${HTTP_RESOURCE_OPTIONS_TYPE_NAME}<${valueType}, ${rawType}${omitParse ? ", true" : ""}>`;
|
|
1139
|
+
const buildResourceStateUtilities = () => `
|
|
1140
|
+
/**
|
|
1141
|
+
* Utility type for httpResource results with status tracking.
|
|
1142
|
+
* Inspired by @angular-architects/ngrx-toolkit withResource pattern.
|
|
1143
|
+
*
|
|
1144
|
+
* Uses \`globalThis.Error\` to avoid collision with API model types named \`Error\`.
|
|
1145
|
+
*/
|
|
1146
|
+
export interface ResourceState<T> {
|
|
1147
|
+
readonly value: Signal<T | undefined>;
|
|
1148
|
+
readonly status: Signal<ResourceStatus>;
|
|
1149
|
+
readonly error: Signal<globalThis.Error | undefined>;
|
|
1150
|
+
readonly isLoading: Signal<boolean>;
|
|
1151
|
+
readonly hasValue: () => boolean;
|
|
1152
|
+
readonly reload: () => boolean;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Wraps an HttpResourceRef to expose a consistent ResourceState interface.
|
|
1157
|
+
* Useful when integrating with NgRx SignalStore via withResource().
|
|
1158
|
+
*/
|
|
1159
|
+
export function toResourceState<T>(ref: HttpResourceRef<T>): ResourceState<T> {
|
|
1160
|
+
return {
|
|
1161
|
+
value: ref.value,
|
|
1162
|
+
status: ref.status,
|
|
1163
|
+
error: ref.error,
|
|
1164
|
+
isLoading: ref.isLoading,
|
|
1165
|
+
hasValue: () => ref.hasValue(),
|
|
1166
|
+
reload: () => ref.reload(),
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
`;
|
|
1170
|
+
/**
|
|
1171
|
+
* Generates the header section for Angular `httpResource` output.
|
|
1172
|
+
*
|
|
1173
|
+
* @remarks
|
|
1174
|
+
* Resource functions are emitted in the header phase because their final shape
|
|
1175
|
+
* depends on the full set of operations in scope, including generated `Accept`
|
|
1176
|
+
* helpers and any shared mutation service methods.
|
|
1177
|
+
*
|
|
1178
|
+
* @returns The generated header, resource helpers, optional mutation service class, and resource result aliases.
|
|
1179
|
+
*/
|
|
1180
|
+
const generateHttpResourceHeader = ({ title, isRequestOptions, isMutator, isGlobalMutator, provideIn, output, verbOptions }) => {
|
|
1181
|
+
resetHttpClientReturnTypes();
|
|
1182
|
+
resourceReturnTypesRegistry.reset();
|
|
1183
|
+
const retrievals = Object.values(verbOptions).filter((verbOption) => isRetrievalVerb(verbOption.verb, verbOption.operationName, getClientOverride(verbOption)));
|
|
1184
|
+
const hasResourceQueryParams = retrievals.some((verbOption) => !!verbOption.queryParams);
|
|
1185
|
+
const filterParamsHelper = hasResourceQueryParams ? `\n${getAngularFilteredParamsHelperBody()}\n` : "";
|
|
1186
|
+
const acceptHelpers = buildAcceptHelpers(retrievals, output);
|
|
1187
|
+
const resources = retrievals.map((verbOption) => {
|
|
1188
|
+
return buildHttpResourceFunction(verbOption, routeRegistry.get(verbOption.operationName, verbOption.route), output);
|
|
1189
|
+
}).join("\n");
|
|
1190
|
+
const resourceTypes = resourceReturnTypesRegistry.getFooter(retrievals.map((verbOption) => verbOption.operationName));
|
|
1191
|
+
const mutations = Object.values(verbOptions).filter((verbOption) => isMutationVerb(verbOption.verb, verbOption.operationName, getClientOverride(verbOption)));
|
|
1192
|
+
const hasMutationQueryParams = mutations.some((verbOption) => !!verbOption.queryParams);
|
|
1193
|
+
const mutationImplementation = mutations.map((verbOption) => {
|
|
1194
|
+
return generateHttpClientImplementation(verbOption, {
|
|
1195
|
+
route: routeRegistry.get(verbOption.operationName, verbOption.route),
|
|
1196
|
+
context: { output }
|
|
1197
|
+
});
|
|
1198
|
+
}).join("\n");
|
|
1199
|
+
const classImplementation = mutationImplementation ? `
|
|
1200
|
+
${buildServiceClassOpen({
|
|
1201
|
+
title,
|
|
1202
|
+
isRequestOptions,
|
|
1203
|
+
isMutator,
|
|
1204
|
+
isGlobalMutator,
|
|
1205
|
+
provideIn,
|
|
1206
|
+
hasQueryParams: hasMutationQueryParams && !hasResourceQueryParams
|
|
1207
|
+
})}
|
|
1208
|
+
${mutationImplementation}
|
|
1209
|
+
};
|
|
1210
|
+
` : "";
|
|
1211
|
+
return `${buildHttpResourceOptionsUtilities(isZodSchemaOutput(output))}${filterParamsHelper}${acceptHelpers ? `${acceptHelpers}\n\n` : ""}${resources}${classImplementation}${resourceTypes ? `\n${resourceTypes}\n` : ""}`;
|
|
1212
|
+
};
|
|
1213
|
+
/**
|
|
1214
|
+
* Generates the footer for Angular `httpResource` output.
|
|
1215
|
+
*
|
|
1216
|
+
* The footer appends any registered `ClientResult` aliases coming from shared
|
|
1217
|
+
* `HttpClient` mutation methods and the resource-state helper utilities emitted
|
|
1218
|
+
* for generated Angular resources.
|
|
1219
|
+
*
|
|
1220
|
+
* @returns The footer text for the generated Angular resource file.
|
|
1221
|
+
*/
|
|
1222
|
+
const generateHttpResourceFooter = ({ operationNames }) => {
|
|
1223
|
+
const clientTypes = getHttpClientReturnTypes(operationNames);
|
|
1224
|
+
const utilities = buildResourceStateUtilities();
|
|
1225
|
+
return `${clientTypes ? `${clientTypes}\n` : ""}${utilities}`;
|
|
1226
|
+
};
|
|
1227
|
+
/**
|
|
1228
|
+
* Per-operation builder used during Angular `httpResource` generation.
|
|
1229
|
+
*
|
|
1230
|
+
* Unlike the `HttpClient` builder, the actual implementation body is emitted in
|
|
1231
|
+
* the header phase after all operations are known. This function mainly records
|
|
1232
|
+
* the resolved route and returns the imports required by the current operation.
|
|
1233
|
+
*
|
|
1234
|
+
* @returns An empty implementation plus the imports required by the operation.
|
|
1235
|
+
*/
|
|
1236
|
+
const generateHttpResourceClient = (verbOptions, options) => {
|
|
1237
|
+
routeRegistry.set(verbOptions.operationName, options.route);
|
|
346
1238
|
return {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
dependencies: getAngularDependencies,
|
|
350
|
-
footer: createAngularFooter(returnTypesToWrite),
|
|
351
|
-
title: generateAngularTitle
|
|
1239
|
+
implementation: "\n",
|
|
1240
|
+
imports: getHttpResourceVerbImports(verbOptions, options.context.output)
|
|
352
1241
|
};
|
|
353
1242
|
};
|
|
354
|
-
const
|
|
355
|
-
|
|
1243
|
+
const buildHttpResourceFile = (verbOptions, output, context) => {
|
|
1244
|
+
resourceReturnTypesRegistry.reset();
|
|
1245
|
+
const retrievals = Object.values(verbOptions).filter((verbOption) => isRetrievalVerb(verbOption.verb, verbOption.operationName, getClientOverride(verbOption)));
|
|
1246
|
+
const filterParamsHelper = retrievals.some((verbOption) => !!verbOption.queryParams) ? `\n${getAngularFilteredParamsHelperBody()}\n` : "";
|
|
1247
|
+
const resources = retrievals.map((verbOption) => {
|
|
1248
|
+
return buildHttpResourceFunction(verbOption, getFullRoute(verbOption.route, context.spec.servers, output.baseUrl), output);
|
|
1249
|
+
}).join("\n");
|
|
1250
|
+
const resourceTypes = resourceReturnTypesRegistry.getFooter(Object.values(verbOptions).map((verbOption) => verbOption.operationName));
|
|
1251
|
+
const utilities = buildResourceStateUtilities();
|
|
1252
|
+
const acceptHelpers = buildAcceptHelpers(retrievals, output);
|
|
1253
|
+
return `${buildHttpResourceOptionsUtilities(isZodSchemaOutput(output))}${filterParamsHelper}${acceptHelpers ? `${acceptHelpers}\n\n` : ""}${resources}\n${resourceTypes ? `${resourceTypes}\n` : ""}${utilities}`;
|
|
1254
|
+
};
|
|
1255
|
+
const buildSchemaImportDependencies = (output, imports, relativeSchemasPath) => {
|
|
1256
|
+
const isZod = isZodSchemaOutput(output);
|
|
1257
|
+
const uniqueImports = [...new Map(imports.map((imp) => [imp.name, imp])).values()];
|
|
1258
|
+
if (!output.indexFiles) return [...uniqueImports].map((imp) => {
|
|
1259
|
+
const name = conventionName(imp.schemaName ?? imp.name, output.namingConvention);
|
|
1260
|
+
const suffix = isZod ? ".zod" : "";
|
|
1261
|
+
const importExtension = output.fileExtension.replace(/\.ts$/, "");
|
|
1262
|
+
return {
|
|
1263
|
+
exports: isZod ? [{
|
|
1264
|
+
...imp,
|
|
1265
|
+
values: true
|
|
1266
|
+
}] : [imp],
|
|
1267
|
+
dependency: upath.joinSafe(relativeSchemasPath, `${name}${suffix}${importExtension}`)
|
|
1268
|
+
};
|
|
1269
|
+
});
|
|
1270
|
+
if (isZod) return [{
|
|
1271
|
+
exports: uniqueImports.map((imp) => ({
|
|
1272
|
+
...imp,
|
|
1273
|
+
values: true
|
|
1274
|
+
})),
|
|
1275
|
+
dependency: upath.joinSafe(relativeSchemasPath, "index.zod")
|
|
1276
|
+
}];
|
|
1277
|
+
return [{
|
|
1278
|
+
exports: uniqueImports,
|
|
1279
|
+
dependency: relativeSchemasPath
|
|
1280
|
+
}];
|
|
1281
|
+
};
|
|
1282
|
+
/**
|
|
1283
|
+
* Generates the extra sibling resource file used by Angular `both` mode.
|
|
1284
|
+
*
|
|
1285
|
+
* @remarks
|
|
1286
|
+
* The main generated file keeps the `HttpClient` service class while retrieval
|
|
1287
|
+
* resources are emitted into `*.resource.ts` so consumers can opt into both
|
|
1288
|
+
* access patterns without mixing the generated surfaces.
|
|
1289
|
+
*
|
|
1290
|
+
* @returns A single extra file descriptor representing the generated resource file.
|
|
1291
|
+
*/
|
|
1292
|
+
const generateHttpResourceExtraFiles = (verbOptions, output, context) => {
|
|
1293
|
+
const { extension, dirname, filename } = getFileInfo(output.target);
|
|
1294
|
+
const outputPath = upath.join(dirname, `${filename}.resource${extension}`);
|
|
1295
|
+
const header = getHeader(output.override.header, context.spec.info);
|
|
1296
|
+
const implementation = buildHttpResourceFile(verbOptions, output, context);
|
|
1297
|
+
const schemasPath = typeof output.schemas === "string" ? output.schemas : output.schemas?.path;
|
|
1298
|
+
const basePath = schemasPath ? getFileInfo(schemasPath).dirname : void 0;
|
|
1299
|
+
const relativeSchemasPath = basePath ? output.indexFiles ? upath.relativeSafe(dirname, basePath) : upath.relativeSafe(dirname, basePath) : `./${filename}.schemas`;
|
|
1300
|
+
const schemaImports = buildSchemaImportDependencies(output, Object.values(verbOptions).filter((verbOption) => isRetrievalVerb(verbOption.verb, verbOption.operationName, getClientOverride(verbOption))).flatMap((verbOption) => getHttpResourceVerbImports(verbOption, output)), relativeSchemasPath);
|
|
1301
|
+
const dependencies = getAngularHttpResourceOnlyDependencies(false, false);
|
|
1302
|
+
const importImplementation = generateDependencyImports(implementation, [...schemaImports, ...dependencies], context.projectName, !!output.schemas, isSyntheticDefaultImportsAllow(output.tsconfig));
|
|
1303
|
+
const mutators = Object.values(verbOptions).filter((verbOption) => isRetrievalVerb(verbOption.verb, verbOption.operationName, getClientOverride(verbOption))).flatMap((verbOption) => {
|
|
1304
|
+
return [
|
|
1305
|
+
verbOption.mutator && !verbOption.mutator.hasSecondArg ? verbOption.mutator : void 0,
|
|
1306
|
+
verbOption.formData,
|
|
1307
|
+
verbOption.formUrlEncoded,
|
|
1308
|
+
verbOption.paramsSerializer
|
|
1309
|
+
].filter((value) => value !== void 0);
|
|
1310
|
+
});
|
|
1311
|
+
const content = `${header}${importImplementation}${mutators.length > 0 ? generateMutatorImports({ mutators }) : ""}${implementation}`;
|
|
1312
|
+
return Promise.resolve([{
|
|
1313
|
+
content,
|
|
1314
|
+
path: outputPath
|
|
1315
|
+
}]);
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
//#endregion
|
|
1319
|
+
//#region src/index.ts
|
|
1320
|
+
const httpClientBuilder = {
|
|
1321
|
+
client: generateAngular,
|
|
1322
|
+
header: generateAngularHeader,
|
|
1323
|
+
dependencies: getAngularDependencies,
|
|
1324
|
+
footer: generateAngularFooter,
|
|
1325
|
+
title: generateAngularTitle
|
|
1326
|
+
};
|
|
1327
|
+
const httpResourceBuilder = {
|
|
1328
|
+
client: generateHttpResourceClient,
|
|
1329
|
+
header: generateHttpResourceHeader,
|
|
1330
|
+
dependencies: getAngularHttpResourceDependencies,
|
|
1331
|
+
footer: generateHttpResourceFooter,
|
|
1332
|
+
title: generateAngularTitle
|
|
1333
|
+
};
|
|
1334
|
+
const bothClientBuilder = {
|
|
1335
|
+
...httpClientBuilder,
|
|
1336
|
+
extraFiles: generateHttpResourceExtraFiles
|
|
1337
|
+
};
|
|
1338
|
+
const builder = () => (options) => {
|
|
1339
|
+
switch (options?.client) {
|
|
1340
|
+
case "httpResource": return httpResourceBuilder;
|
|
1341
|
+
case "both": return bothClientBuilder;
|
|
1342
|
+
default: return httpClientBuilder;
|
|
1343
|
+
}
|
|
356
1344
|
};
|
|
357
1345
|
|
|
358
1346
|
//#endregion
|
|
359
|
-
export { builder, builder as default, generateAngular, generateAngularFooter, generateAngularHeader, generateAngularTitle, getAngularDependencies };
|
|
1347
|
+
export { ANGULAR_HTTP_CLIENT_DEPENDENCIES, ANGULAR_HTTP_RESOURCE_DEPENDENCIES, HTTP_CLIENT_OBSERVE_OPTIONS_TEMPLATE, HTTP_CLIENT_OPTIONS_TEMPLATE, PRIMITIVE_TYPES, THIRD_PARAMETER_TEMPLATE, buildAcceptHelpers, buildServiceClassOpen, builder, builder as default, createReturnTypesRegistry, createRouteRegistry, generateAngular, generateAngularFooter, generateAngularHeader, generateAngularTitle, generateHttpClientImplementation, generateHttpResourceClient, generateHttpResourceExtraFiles, generateHttpResourceFooter, generateHttpResourceHeader, getAcceptHelperName, getAngularDependencies, getAngularHttpResourceDependencies, getAngularHttpResourceOnlyDependencies, getDefaultSuccessType, getHttpClientReturnTypes, getUniqueContentTypes, isDefined, isMutationVerb, isPrimitiveType, isRetrievalVerb, isZodSchemaOutput, resetHttpClientReturnTypes, routeRegistry };
|
|
360
1348
|
//# sourceMappingURL=index.mjs.map
|