@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/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/index.ts
4
- const ANGULAR_DEPENDENCIES = [
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 PRIMITIVE_RESPONSE_TYPES = [
44
- "string",
45
- "number",
46
- "boolean",
47
- "void",
48
- "unknown"
49
- ];
50
- const isPrimitiveResponseType = (typeName) => typeName != void 0 && PRIMITIVE_RESPONSE_TYPES.includes(typeName);
51
- const hasSchemaImport = (imports, typeName) => typeName != void 0 && imports.some((imp) => imp.name === typeName);
52
- const getSchemaValueRef = (typeName) => typeName === "Error" ? "ErrorSchema" : typeName;
53
- const getAngularDependencies = () => [...ANGULAR_DEPENDENCIES];
54
- const generateAngularTitle = (title) => {
55
- return `${pascal(sanitize(title))}Service`;
56
- };
57
- const createAngularHeader = () => ({ title, isRequestOptions, isMutator, isGlobalMutator, provideIn, verbOptions, tag }) => {
58
- const stringTag = tag;
59
- const hasQueryParams = (stringTag ? Object.values(verbOptions).filter((v) => v.tags.some((t) => camel(t) === camel(stringTag))) : Object.values(verbOptions)).some((v) => v.queryParams);
60
- return `
61
- ${isRequestOptions && !isGlobalMutator ? `interface HttpClientOptions {
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
- ${hasQueryParams ? getAngularFilteredParamsHelperBody() : ""}` : ""}
105
+ type HttpClientEventOptions = HttpClientOptions & {
106
+ readonly observe: 'events';
107
+ };
82
108
 
83
- ${isRequestOptions && isMutator ? `// eslint-disable-next-line
84
- type ThirdParameter<T extends (...args: any) => any> = T extends (
85
- config: any,
86
- httpClient: any,
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
- ) => any
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
- const generateAngularHeader = (params) => createAngularHeader()(params);
98
- const standaloneFooterReturnTypesToWrite = /* @__PURE__ */ new Map();
99
- const createAngularFooter = (returnTypesToWrite) => ({ operationNames }) => {
100
- let footer = "}\n\n";
101
- for (const operationName of operationNames) if (returnTypesToWrite.has(operationName)) footer += returnTypesToWrite.get(operationName) + "\n";
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
- const generateAngularFooter = (params) => createAngularFooter(standaloneFooterReturnTypesToWrite)(params);
105
- const generateImplementation = (returnTypesToWrite, { headers, queryParams, operationName, response, mutator, body, props, verb, override, formData, formUrlEncoded, paramsSerializer }, { route, context }) => {
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 isPrimitiveType = isPrimitiveResponseType(dataType);
403
+ const isPrimitive = isPrimitiveType(dataType);
119
404
  const hasSchema = hasSchemaImport(response.imports, dataType);
120
- const isZodOutput = typeof context.output.schemas === "object" && context.output.schemas.type === "zod";
121
- const shouldValidateResponse = override.angular.runtimeValidation && isZodOutput && !isPrimitiveType && hasSchema;
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
- returnTypesToWrite.set(operationName, `export type ${pascal(operationName)}ClientResult = NonNullable<${dataType}>`);
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`(\w*):\s?${body.definition}`), `$1: ${mutator.bodyTypeName}<${body.definition}>`) : toObjectString(props, "implementation")}\n ${isRequestOptions && mutator.hasThirdArg ? `options?: ThirdParameter<typeof ${mutator.name}>` : ""}) {${bodyForm}
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 = [...new Set(successTypes.map((t) => t.contentType).filter(Boolean))];
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 = needsObserveBranching && queryParams ? "filteredParams" : void 0;
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 ? uniqueContentTypes.includes("text/plain") ? "text/plain" : getDefaultContentType(uniqueContentTypes) : uniqueContentTypes[0] ?? "application/json";
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 = isPrimitiveResponseType(jsonType);
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 multiImplementationReturnType = `Observable<${jsonReturnType} | string | Blob>`;
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 = ${dataType}>`;
509
+ if (isModelType && !hasMultipleContentTypes) functionName += `<TData = ${parsedDataType}>`;
214
510
  let contentTypeOverloads = "";
215
511
  if (hasMultipleContentTypes && isRequestOptions) {
216
- const requiredProps = props.filter((p) => p.required && !p.default);
217
- const optionalProps = props.filter((p) => !p.required || p.default);
218
- contentTypeOverloads = successTypes.filter((t) => t.contentType).map(({ contentType, value }) => {
219
- const returnType = getContentTypeReturnType(contentType, value);
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
- requiredProps.map((p) => p.definition).join(",\n "),
517
+ requiredPart,
222
518
  `accept: '${contentType}'`,
223
- optionalProps.map((p) => p.definition).join(",\n ")
519
+ optionalPart
224
520
  ].filter(Boolean).join(",\n ")}, options?: HttpClientOptions): Observable<${returnType}>;`;
225
- }).join("\n ");
226
- const allParams = [
227
- requiredProps.map((p) => p.definition).join(",\n "),
228
- "accept?: string",
229
- optionalProps.map((p) => p.definition).join(",\n ")
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?: HttpClientOptions & { observe?: 'body' }): Observable<${isModelType ? "TData" : dataType}>;
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" : dataType;
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 requiredProps = props.filter((p) => p.required && !p.default);
241
- const optionalProps = props.filter((p) => !p.required || p.default);
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
- ): ${multiImplementationReturnType} {${bodyForm}
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}<${jsonReturnType}>(\`${route}\`, {
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?: HttpClientOptions & { observe?: 'body' | 'events' | 'response' }` : ""}): ${singleImplementationReturnType} {${bodyForm}
590
+ ${toObjectString(props, "implementation")} ${isRequestOptions ? `options?: HttpClientObserveOptions` : ""}): ${singleImplementationReturnType} {${bodyForm}
291
591
  ${observeImplementation}
292
592
  }
293
593
  `;
294
594
  };
295
- const createAngularClient = (returnTypesToWrite) => (verbOptions, options, _outputClient, _output) => {
296
- const isZodOutput = typeof options.context.output.schemas === "object" && options.context.output.schemas.type === "zod";
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 = isPrimitiveResponseType(responseType);
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 = verbOptions;
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 (!isPrimitiveResponseType(jsonType) && hasSchemaImport(result.response.imports, jsonType)) result = {
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 = generateImplementation(returnTypesToWrite, normalizedVerbOptions, options);
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
- const standaloneReturnTypesToWrite = /* @__PURE__ */ new Map();
343
- const generateAngular = (verbOptions, options, outputClient, output) => createAngularClient(standaloneReturnTypesToWrite)(verbOptions, options, outputClient, output);
344
- const createAngularClientBuilder = () => {
345
- const returnTypesToWrite = /* @__PURE__ */ new Map();
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
- client: createAngularClient(returnTypesToWrite),
348
- header: createAngularHeader(),
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 builder = () => {
355
- return () => createAngularClientBuilder();
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