@ng-openapi/http-resource 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.cjs +710 -84
  2. package/index.d.ts +5 -2
  3. package/index.js +710 -84
  4. package/package.json +1 -1
package/index.cjs CHANGED
@@ -38,6 +38,238 @@ module.exports = __toCommonJS(index_exports);
38
38
  // src/lib/http-resource.generator.ts
39
39
  var import_ts_morph = require("ts-morph");
40
40
 
41
+ // ../../shared/src/errors.ts
42
+ var NG_OPENAPI_ERROR_BRAND = "__ngOpenApiError";
43
+ var ERROR_LINEAGE = /* @__PURE__ */ new WeakMap();
44
+ var FALLBACK_LINEAGE = Object.freeze([
45
+ "NgOpenApiError"
46
+ ]);
47
+ function registerError(cls, lineage) {
48
+ ERROR_LINEAGE.set(cls, Object.freeze([
49
+ ...lineage
50
+ ]));
51
+ return cls;
52
+ }
53
+ __name(registerError, "registerError");
54
+ function inheritedLineage(cls) {
55
+ for (let current = cls; typeof current === "function"; current = Object.getPrototypeOf(current)) {
56
+ const lineage = ERROR_LINEAGE.get(current);
57
+ if (lineage) {
58
+ return lineage;
59
+ }
60
+ }
61
+ return FALLBACK_LINEAGE;
62
+ }
63
+ __name(inheritedLineage, "inheritedLineage");
64
+ var NgOpenApiError = class extends Error {
65
+ static {
66
+ __name(this, "NgOpenApiError");
67
+ }
68
+ /** The underlying error that caused this one, when there is one. */
69
+ cause;
70
+ constructor(message, cause) {
71
+ super(message);
72
+ const lineage = inheritedLineage(new.target);
73
+ this.name = lineage[0];
74
+ this.cause = cause;
75
+ Object.defineProperty(this, NG_OPENAPI_ERROR_BRAND, {
76
+ value: lineage,
77
+ enumerable: false,
78
+ writable: false,
79
+ configurable: false
80
+ });
81
+ }
82
+ /**
83
+ * `message` and `name` are non-enumerable on Error, so the default
84
+ * JSON.stringify dropped both — the least useful possible serialization for
85
+ * something that exists to be logged.
86
+ *
87
+ * The payload is spread rather than enumerated: `source`, `issues`,
88
+ * `operation`, `names` and `placeholders` are why these classes are typed
89
+ * in the first place, and listing fields by hand silently drops whichever
90
+ * ones a later subclass adds.
91
+ */
92
+ toJSON() {
93
+ const cause = this.cause instanceof Error ? this.cause.message : this.cause;
94
+ return {
95
+ name: this.name,
96
+ message: this.message,
97
+ ...cause === void 0 ? {} : {
98
+ cause
99
+ },
100
+ // Own enumerable properties: the brand is deliberately not one.
101
+ ...Object.fromEntries(Object.entries(this).filter(([key]) => key !== "cause"))
102
+ };
103
+ }
104
+ /**
105
+ * Recognizes branded errors from another bundled copy of this module, so
106
+ * `error instanceof SpecLoadError` works for a plugin-thrown error too.
107
+ * The prototype chain is checked first, so a caller's own subclass of these
108
+ * classes still matches even though it carries no lineage entry.
109
+ *
110
+ * Never throws and never runs foreign code: both reflective reads sit
111
+ * inside the try, because a Proxy traps `getPrototypeOf` just as readily as
112
+ * `getOwnPropertyDescriptor`.
113
+ */
114
+ static [Symbol.hasInstance](value) {
115
+ if (typeof value !== "object" || value === null) {
116
+ return false;
117
+ }
118
+ try {
119
+ if (Object.prototype.isPrototypeOf.call(this.prototype, value)) {
120
+ return true;
121
+ }
122
+ const expected = ERROR_LINEAGE.get(this)?.[0];
123
+ if (expected === void 0) {
124
+ return false;
125
+ }
126
+ const descriptor = Object.getOwnPropertyDescriptor(value, NG_OPENAPI_ERROR_BRAND);
127
+ if (!descriptor || descriptor.enumerable || !("value" in descriptor)) {
128
+ return false;
129
+ }
130
+ return Array.isArray(descriptor.value) && descriptor.value.includes(expected);
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+ };
136
+ registerError(NgOpenApiError, [
137
+ "NgOpenApiError"
138
+ ]);
139
+ var SpecLoadError = class extends NgOpenApiError {
140
+ static {
141
+ __name(this, "SpecLoadError");
142
+ }
143
+ /** The file path or URL that failed to load. */
144
+ source;
145
+ constructor(message, source, cause) {
146
+ super(message, cause);
147
+ this.source = source;
148
+ }
149
+ };
150
+ registerError(SpecLoadError, [
151
+ "SpecLoadError",
152
+ "NgOpenApiError"
153
+ ]);
154
+ var SpecParseError = class extends NgOpenApiError {
155
+ static {
156
+ __name(this, "SpecParseError");
157
+ }
158
+ /** The file path or URL the content came from, when known. */
159
+ source;
160
+ constructor(message, source, cause) {
161
+ super(message, cause);
162
+ this.source = source;
163
+ }
164
+ };
165
+ registerError(SpecParseError, [
166
+ "SpecParseError",
167
+ "NgOpenApiError"
168
+ ]);
169
+ function describeOperation(operation) {
170
+ const location = `(${operation.method}) ${operation.path}`;
171
+ return operation.operationId ? `${operation.operationId} (${location})` : location;
172
+ }
173
+ __name(describeOperation, "describeOperation");
174
+ function captureOperation(operation) {
175
+ return Object.freeze({
176
+ operationId: operation.operationId,
177
+ method: operation.method,
178
+ path: operation.path
179
+ });
180
+ }
181
+ __name(captureOperation, "captureOperation");
182
+ var InvalidIdentifierError = class extends NgOpenApiError {
183
+ static {
184
+ __name(this, "InvalidIdentifierError");
185
+ }
186
+ /** The rejected name, verbatim; absent when no name could be derived. */
187
+ identifier;
188
+ /** The operation whose name was being derived. */
189
+ operation;
190
+ constructor(message, operation, identifier) {
191
+ super(message);
192
+ this.operation = captureOperation(operation);
193
+ this.identifier = identifier;
194
+ }
195
+ };
196
+ registerError(InvalidIdentifierError, [
197
+ "InvalidIdentifierError",
198
+ "NgOpenApiError"
199
+ ]);
200
+ var DuplicateGeneratedNameError = class extends NgOpenApiError {
201
+ static {
202
+ __name(this, "DuplicateGeneratedNameError");
203
+ }
204
+ /** The colliding generated names. */
205
+ names;
206
+ /** The operations that produced them, when known. */
207
+ operations;
208
+ constructor(message, names, operations = []) {
209
+ super(message);
210
+ this.names = Object.freeze([
211
+ ...names
212
+ ]);
213
+ this.operations = Object.freeze(operations.map(captureOperation));
214
+ }
215
+ };
216
+ registerError(DuplicateGeneratedNameError, [
217
+ "DuplicateGeneratedNameError",
218
+ "NgOpenApiError"
219
+ ]);
220
+ var UnresolvedPathTemplateError = class extends NgOpenApiError {
221
+ static {
222
+ __name(this, "UnresolvedPathTemplateError");
223
+ }
224
+ /** The path as written in the spec. */
225
+ path;
226
+ /** Placeholder names with no declared parameter. */
227
+ placeholders;
228
+ constructor(message, path3, placeholders) {
229
+ super(message);
230
+ this.path = path3;
231
+ this.placeholders = Object.freeze([
232
+ ...placeholders
233
+ ]);
234
+ }
235
+ };
236
+ registerError(UnresolvedPathTemplateError, [
237
+ "UnresolvedPathTemplateError",
238
+ "NgOpenApiError"
239
+ ]);
240
+ var ConfigValidationError = class extends NgOpenApiError {
241
+ static {
242
+ __name(this, "ConfigValidationError");
243
+ }
244
+ issues;
245
+ constructor(issues) {
246
+ super(`Invalid ng-openapi configuration:
247
+ ${issues.map((issue) => ` - ${issue}`).join("\n")}`);
248
+ this.issues = Object.freeze([
249
+ ...issues
250
+ ]);
251
+ }
252
+ };
253
+ registerError(ConfigValidationError, [
254
+ "ConfigValidationError",
255
+ "NgOpenApiError"
256
+ ]);
257
+ var ConfigLoadError = class extends NgOpenApiError {
258
+ static {
259
+ __name(this, "ConfigLoadError");
260
+ }
261
+ /** Path of the config file that failed to load. */
262
+ source;
263
+ constructor(message, source, cause) {
264
+ super(message, cause);
265
+ this.source = source;
266
+ }
267
+ };
268
+ registerError(ConfigLoadError, [
269
+ "ConfigLoadError",
270
+ "NgOpenApiError"
271
+ ]);
272
+
41
273
  // ../../../node_modules/js-yaml/dist/js-yaml.mjs
42
274
  function isNothing(subject) {
43
275
  return typeof subject === "undefined" || subject === null;
@@ -2741,19 +2973,75 @@ var safeLoadAll = renamed("safeLoadAll", "loadAll");
2741
2973
  var safeDump = renamed("safeDump", "dump");
2742
2974
 
2743
2975
  // ../../shared/src/utils/string.utils.ts
2976
+ var IDENTIFIER_SEPARATORS = /(?:_|[^\p{ID_Continue}$])+(.)?/gu;
2977
+ var INVALID_IDENTIFIER_START = /^[^\p{ID_Start}$_]/u;
2978
+ function toIdentifier(converted) {
2979
+ if (converted === "") {
2980
+ return "_";
2981
+ }
2982
+ return converted.replace(INVALID_IDENTIFIER_START, (char) => `_${char}`);
2983
+ }
2984
+ __name(toIdentifier, "toIdentifier");
2985
+ function isValidIdentifier(name) {
2986
+ return /^[\p{ID_Start}$_][\p{ID_Continue}$]*$/u.test(name);
2987
+ }
2988
+ __name(isValidIdentifier, "isValidIdentifier");
2744
2989
  function camelCase(str2) {
2745
- return str2.replace(/[-_.\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^./, (char) => char.toLowerCase());
2990
+ const converted = str2.replace(IDENTIFIER_SEPARATORS, (_, char) => char ? char.toUpperCase() : "").replace(/^./u, (char) => char.toLowerCase());
2991
+ return toIdentifier(converted);
2746
2992
  }
2747
2993
  __name(camelCase, "camelCase");
2748
2994
  function pascalCase(str2) {
2749
- return str2.replace(/[-_.\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^./, (char) => char.toUpperCase());
2995
+ const converted = str2.replace(IDENTIFIER_SEPARATORS, (_, char) => char ? char.toUpperCase() : "").replace(/^./u, (char) => char.toUpperCase());
2996
+ return toIdentifier(converted);
2750
2997
  }
2751
2998
  __name(pascalCase, "pascalCase");
2752
2999
  function pascalCaseForEnums(str2) {
2753
- return str2.replace(/[^a-zA-Z0-9]/g, "_").replace(/(?:^|_)([a-z])/g, (_, char) => char.toUpperCase()).replace(/^([0-9])/, "_$1");
3000
+ const converted = str2.replace(/[^a-zA-Z0-9]/g, "_").replace(/(?:^|_)([a-z])/g, (_, char) => char.toUpperCase()).replace(/^([0-9])/, "_$1");
3001
+ return converted === "" ? "_" : converted;
2754
3002
  }
2755
3003
  __name(pascalCaseForEnums, "pascalCaseForEnums");
2756
3004
 
3005
+ // ../../shared/src/utils/content-types.constants.ts
3006
+ var CONTENT_TYPES = {
3007
+ MULTIPART: "multipart/form-data",
3008
+ FORM_URLENCODED: "application/x-www-form-urlencoded",
3009
+ JSON: "application/json"
3010
+ };
3011
+
3012
+ // ../../shared/src/emit/literal.emit.ts
3013
+ function quoteLiteral(value) {
3014
+ return `'${escapeSingleQuoted(value)}'`;
3015
+ }
3016
+ __name(quoteLiteral, "quoteLiteral");
3017
+ function escapeSingleQuoted(value) {
3018
+ return value.replace(/[\\']/g, (char) => `\\${char}`).replace(/\r/g, "\\r").replace(/\n/g, "\\n");
3019
+ }
3020
+ __name(escapeSingleQuoted, "escapeSingleQuoted");
3021
+ function escapeTemplateLiteral(value) {
3022
+ return value.replace(/[\\`]/g, (char) => `\\${char}`).replace(/\$\{/g, "\\${").replace(/\r/g, "\\r");
3023
+ }
3024
+ __name(escapeTemplateLiteral, "escapeTemplateLiteral");
3025
+ function emitObjectKey(name, quoteStyle = "double") {
3026
+ const quoted = quoteStyle === "single" ? quoteLiteral(name) : `"${escapeDoubleQuoted(name)}"`;
3027
+ return name === "__proto__" ? `[${quoted}]` : quoted;
3028
+ }
3029
+ __name(emitObjectKey, "emitObjectKey");
3030
+ function escapeDoubleQuoted(value) {
3031
+ return value.replace(/[\\"]/g, (char) => `\\${char}`).replace(/\r/g, "\\r").replace(/\n/g, "\\n");
3032
+ }
3033
+ __name(escapeDoubleQuoted, "escapeDoubleQuoted");
3034
+ function escapeJsDoc(text) {
3035
+ return text.replace(/\*\//g, "*\\/");
3036
+ }
3037
+ __name(escapeJsDoc, "escapeJsDoc");
3038
+ function emitDocs(description) {
3039
+ return typeof description === "string" && description ? [
3040
+ escapeJsDoc(description)
3041
+ ] : void 0;
3042
+ }
3043
+ __name(emitDocs, "emitDocs");
3044
+
2757
3045
  // ../../shared/src/utils/functions/class-names.ts
2758
3046
  function decorate(base, defaultSuffix, naming) {
2759
3047
  return `${naming?.prefix ?? ""}${base}${naming?.suffix ?? defaultSuffix}`;
@@ -2796,7 +3084,7 @@ function getTypeScriptType(schemaOrType, config, formatOrNullable, isNullable, c
2796
3084
  switch (schema2.type) {
2797
3085
  case "string":
2798
3086
  if (schema2.enum) {
2799
- return schema2.enum.map((value) => typeof value === "string" ? `'${escapeString2(value)}'` : String(value)).join(" | ");
3087
+ return schema2.enum.map((value) => typeof value === "string" ? `'${escapeSingleQuoted(value)}'` : String(value)).join(" | ");
2800
3088
  }
2801
3089
  if (schema2.format === "date" || schema2.format === "date-time") {
2802
3090
  const dateType = config.options.dateType === "Date" ? "Date" : "string";
@@ -2832,10 +3120,6 @@ function nullableType(type2, isNullable) {
2832
3120
  return type2 + (isNullable ? " | null" : "");
2833
3121
  }
2834
3122
  __name(nullableType, "nullableType");
2835
- function escapeString2(str2) {
2836
- return str2.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2837
- }
2838
- __name(escapeString2, "escapeString");
2839
3123
 
2840
3124
  // ../../shared/src/utils/functions/extract-swagger-response-type.ts
2841
3125
  function getResponseTypeFromResponse(response, responseTypeMapping) {
@@ -2984,7 +3268,7 @@ __name(getResponseType, "getResponseType");
2984
3268
 
2985
3269
  // ../../shared/src/emit/headers.emit.ts
2986
3270
  function emitDefaultHeadersMerge(optionsExpression, customHeaders) {
2987
- const defaultsLiteral = Object.entries(customHeaders).map(([key, value]) => `'${key}': '${value}'`).join(", ");
3271
+ const defaultsLiteral = Object.entries(customHeaders).map(([key, value]) => `${emitObjectKey(key, "single")}: ${quoteLiteral(value)}`).join(", ");
2988
3272
  return `
2989
3273
  // Add default headers if not already present
2990
3274
  let headers = ${optionsExpression}?.headers;
@@ -2996,12 +3280,238 @@ ${emitDefaultHeaderGuards(customHeaders)}
2996
3280
  }
2997
3281
  __name(emitDefaultHeadersMerge, "emitDefaultHeadersMerge");
2998
3282
  function emitDefaultHeaderGuards(customHeaders) {
2999
- return Object.entries(customHeaders).map(([key, value]) => `if (!headers.has('${key}')) {
3000
- headers = headers.set('${key}', '${value}');
3001
- }`).join("\n");
3283
+ return Object.entries(customHeaders).map(([key, value]) => {
3284
+ const name = quoteLiteral(key);
3285
+ return `if (!headers.has(${name})) {
3286
+ headers = headers.set(${name}, ${quoteLiteral(value)});
3287
+ }`;
3288
+ }).join("\n");
3002
3289
  }
3003
3290
  __name(emitDefaultHeaderGuards, "emitDefaultHeaderGuards");
3004
3291
 
3292
+ // ../../shared/src/utils/functions/get-request-body-type.ts
3293
+ function getRequestBodyType(requestBody, config) {
3294
+ const content = requestBody.content || {};
3295
+ const jsonContent = content[CONTENT_TYPES.JSON];
3296
+ if (jsonContent?.schema) {
3297
+ return getTypeScriptType(jsonContent.schema, config, jsonContent.schema.nullable);
3298
+ }
3299
+ return "any";
3300
+ }
3301
+ __name(getRequestBodyType, "getRequestBodyType");
3302
+
3303
+ // ../../shared/src/utils/functions/is-data-type-interface.ts
3304
+ function isDataTypeInterface(type2) {
3305
+ const invalidTypes = [
3306
+ "any",
3307
+ "File",
3308
+ "string",
3309
+ "number",
3310
+ "boolean",
3311
+ "object",
3312
+ "unknown",
3313
+ "[]",
3314
+ "Array"
3315
+ ];
3316
+ return !invalidTypes.some((invalidType) => type2.includes(invalidType));
3317
+ }
3318
+ __name(isDataTypeInterface, "isDataTypeInterface");
3319
+
3320
+ // ../../shared/src/utils/functions/argument-names.ts
3321
+ var RESERVED_WORDS = [
3322
+ "arguments",
3323
+ "await",
3324
+ "break",
3325
+ "case",
3326
+ "catch",
3327
+ "class",
3328
+ "const",
3329
+ "continue",
3330
+ "debugger",
3331
+ "default",
3332
+ "delete",
3333
+ "do",
3334
+ "else",
3335
+ "enum",
3336
+ "eval",
3337
+ "export",
3338
+ "extends",
3339
+ "false",
3340
+ "finally",
3341
+ "for",
3342
+ "function",
3343
+ "if",
3344
+ "implements",
3345
+ "import",
3346
+ "in",
3347
+ "instanceof",
3348
+ "interface",
3349
+ "let",
3350
+ "new",
3351
+ "null",
3352
+ "package",
3353
+ "private",
3354
+ "protected",
3355
+ "public",
3356
+ "return",
3357
+ "static",
3358
+ "super",
3359
+ "switch",
3360
+ "this",
3361
+ "throw",
3362
+ "true",
3363
+ "try",
3364
+ "typeof",
3365
+ "var",
3366
+ "void",
3367
+ "while",
3368
+ "with",
3369
+ "yield"
3370
+ ];
3371
+ var SERVICE_ARGUMENT_PROFILE = Object.freeze({
3372
+ reserved: Object.freeze([
3373
+ "observe",
3374
+ "options",
3375
+ "url",
3376
+ "params",
3377
+ "headers",
3378
+ "formData",
3379
+ "formBody"
3380
+ ]),
3381
+ bindsRequestBody: true
3382
+ });
3383
+ var RESOURCE_ARGUMENT_PROFILE = Object.freeze({
3384
+ reserved: Object.freeze([
3385
+ "resourceOptions",
3386
+ "requestOptions",
3387
+ "params",
3388
+ "headers"
3389
+ ]),
3390
+ bindsRequestBody: false
3391
+ });
3392
+ var REQUEST_BODY_KEY = /* @__PURE__ */ Symbol("requestBody");
3393
+ function resolveArgumentNames(operation, config, profile) {
3394
+ let byConfig = RESOLVED.get(operation);
3395
+ if (!byConfig) {
3396
+ byConfig = /* @__PURE__ */ new WeakMap();
3397
+ RESOLVED.set(operation, byConfig);
3398
+ }
3399
+ let byProfile = byConfig.get(config);
3400
+ if (!byProfile) {
3401
+ byProfile = /* @__PURE__ */ new WeakMap();
3402
+ byConfig.set(config, byProfile);
3403
+ }
3404
+ const cached = byProfile.get(profile);
3405
+ if (cached) {
3406
+ return cached;
3407
+ }
3408
+ const resolved = computeArgumentNames(operation, config, profile);
3409
+ byProfile.set(profile, resolved);
3410
+ return resolved;
3411
+ }
3412
+ __name(resolveArgumentNames, "resolveArgumentNames");
3413
+ var RESOLVED = /* @__PURE__ */ new WeakMap();
3414
+ function computeArgumentNames(operation, config, profile) {
3415
+ const entries = [
3416
+ ...operation.pathParams.map((param) => ({
3417
+ key: param.name,
3418
+ base: camelCase(param.name)
3419
+ })),
3420
+ ...operation.formDataFields.map((field) => ({
3421
+ key: field,
3422
+ base: camelCase(field)
3423
+ })),
3424
+ ...operation.urlEncodedFields.map((field) => ({
3425
+ key: field,
3426
+ base: camelCase(field)
3427
+ }))
3428
+ ];
3429
+ const bodyBase = profile.bindsRequestBody ? jsonBodyIdentifier(operation, config) : void 0;
3430
+ if (bodyBase !== void 0) {
3431
+ entries.push({
3432
+ key: REQUEST_BODY_KEY,
3433
+ base: bodyBase
3434
+ });
3435
+ }
3436
+ entries.push(...operation.queryParams.map((param) => ({
3437
+ key: param.name,
3438
+ base: camelCase(param.name)
3439
+ })));
3440
+ const identifiers = /* @__PURE__ */ new Map();
3441
+ const used = /* @__PURE__ */ new Set([
3442
+ ...profile.reserved,
3443
+ ...RESERVED_WORDS
3444
+ ]);
3445
+ const renamed2 = [];
3446
+ const merged = /* @__PURE__ */ new Set();
3447
+ for (const { key, base } of entries) {
3448
+ if (identifiers.has(key)) {
3449
+ if (typeof key === "string") {
3450
+ merged.add(key);
3451
+ }
3452
+ continue;
3453
+ }
3454
+ let identifier = base;
3455
+ let suffix = 2;
3456
+ while (used.has(identifier)) {
3457
+ identifier = `${base}${suffix}`;
3458
+ suffix++;
3459
+ }
3460
+ if (identifier !== base) {
3461
+ renamed2.push(Object.freeze({
3462
+ source: typeof key === "string" ? key : base,
3463
+ identifier
3464
+ }));
3465
+ }
3466
+ used.add(identifier);
3467
+ identifiers.set(key, identifier);
3468
+ }
3469
+ return {
3470
+ // The fallback runs the *same* rules the resolver would have, rather
3471
+ // than a bare camelCase: an unregistered wire name otherwise came back
3472
+ // as `class` or `options`, names the resolver never assigns.
3473
+ // Uniquified against everything already assigned, not just the reserved
3474
+ // words: an unregistered wire name that camelCases onto an identifier
3475
+ // this operation already bound would otherwise alias it — compiling
3476
+ // fine and sending the wrong value. Only reachable for operations built
3477
+ // outside the normalizer, but the resolver is public API.
3478
+ of: /* @__PURE__ */ __name((wireName) => identifiers.get(wireName) ?? deriveLocalName(camelCase(wireName), [
3479
+ ...used
3480
+ ]), "of"),
3481
+ body: identifiers.get(REQUEST_BODY_KEY),
3482
+ all: Object.freeze([
3483
+ ...identifiers.values()
3484
+ ]),
3485
+ renamed: Object.freeze(renamed2),
3486
+ merged: Object.freeze([
3487
+ ...merged
3488
+ ])
3489
+ };
3490
+ }
3491
+ __name(computeArgumentNames, "computeArgumentNames");
3492
+ function jsonBodyIdentifier(operation, config) {
3493
+ if (!operation.requestBody || operation.isMultipart) {
3494
+ return void 0;
3495
+ }
3496
+ if (!operation.requestBody.content?.[CONTENT_TYPES.JSON]) {
3497
+ return void 0;
3498
+ }
3499
+ const bodyType = getRequestBodyType(operation.requestBody, config);
3500
+ return isDataTypeInterface(bodyType) ? camelCase(bodyType) : "requestBody";
3501
+ }
3502
+ __name(jsonBodyIdentifier, "jsonBodyIdentifier");
3503
+ function deriveLocalName(base, taken) {
3504
+ const used = new Set(taken);
3505
+ let name = base;
3506
+ let suffix = 2;
3507
+ while (used.has(name)) {
3508
+ name = `${base}${suffix}`;
3509
+ suffix++;
3510
+ }
3511
+ return name;
3512
+ }
3513
+ __name(deriveLocalName, "deriveLocalName");
3514
+
3005
3515
  // ../../shared/src/emit/url.emit.ts
3006
3516
  function plainParamValue(identifier) {
3007
3517
  return identifier;
@@ -3011,24 +3521,34 @@ function signalAwareParamValue(identifier) {
3011
3521
  return `typeof ${identifier} === 'function' ? ${identifier}() : ${identifier}`;
3012
3522
  }
3013
3523
  __name(signalAwareParamValue, "signalAwareParamValue");
3014
- function emitUrlExpression(path3, pathParams, paramValue = plainParamValue) {
3015
- let urlExpression = `\`\${this.basePath}${path3}\``;
3524
+ function emitUrlExpression(path3, pathParams, argumentNames, paramValue = plainParamValue) {
3525
+ const declared = new Set(pathParams.map((param) => param.name));
3526
+ const unresolved = (path3.match(/{([^{}]*)}/g) ?? []).map((placeholder) => placeholder.slice(1, -1)).filter((name) => !declared.has(name));
3527
+ if (unresolved.length > 0) {
3528
+ throw new UnresolvedPathTemplateError(`Path "${path3}" declares no parameter for ${unresolved.map((name) => `{${name}}`).join(", ")}. Declare each placeholder as a path parameter, or remove it from the path.`, path3, unresolved);
3529
+ }
3530
+ let urlExpression = "`${this.basePath}" + escapeTemplateLiteral(path3) + "`";
3016
3531
  pathParams.forEach((param) => {
3017
- urlExpression = urlExpression.replace(`{${param.name}}`, `\${${paramValue(camelCase(param.name))}}`);
3532
+ const replacement = "${" + paramValue(argumentNames.of(param.name)) + "}";
3533
+ urlExpression = urlExpression.split(`{${escapeTemplateLiteral(param.name)}}`).join(replacement);
3018
3534
  });
3019
3535
  return urlExpression;
3020
3536
  }
3021
3537
  __name(emitUrlExpression, "emitUrlExpression");
3022
3538
 
3023
3539
  // ../../shared/src/emit/query-params.emit.ts
3024
- function emitSignalAwareQueryParams(queryParams) {
3540
+ function emitSignalAwareQueryParams(queryParams, argumentNames) {
3025
3541
  if (queryParams.length === 0) {
3026
3542
  return "";
3027
3543
  }
3028
- const paramMappings = queryParams.map((param) => `const ${camelCase(param.name)}Value = ${signalAwareParamValue(camelCase(param.name))};
3029
- if (${camelCase(param.name)}Value != null) {
3030
- params = HttpParamsBuilder.addToHttpParams(params, ${camelCase(param.name)}Value, '${param.name}');
3031
- }`).join("\n");
3544
+ const paramMappings = queryParams.map((param) => {
3545
+ const identifier = argumentNames.of(param.name);
3546
+ const local = deriveLocalName(`${identifier}Value`, argumentNames.all);
3547
+ return `const ${local} = ${signalAwareParamValue(identifier)};
3548
+ if (${local} != null) {
3549
+ params = HttpParamsBuilder.addToHttpParams(params, ${local}, ${quoteLiteral(param.name)});
3550
+ }`;
3551
+ }).join("\n");
3032
3552
  return `
3033
3553
  let params = new HttpParams();
3034
3554
  ${paramMappings}`;
@@ -3075,22 +3595,149 @@ function listGeneratedFileNames(project, directoryPath, suffix) {
3075
3595
  __name(listGeneratedFileNames, "listGeneratedFileNames");
3076
3596
 
3077
3597
  // ../../shared/src/utils/functions/token-names.ts
3078
- function getClientContextTokenName(clientName = "default") {
3079
- const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
3598
+ function effectiveClientName(clientName) {
3599
+ return clientName ? clientName : "default";
3600
+ }
3601
+ __name(effectiveClientName, "effectiveClientName");
3602
+ function tokenSuffix(clientName) {
3603
+ return effectiveClientName(clientName).toUpperCase().replace(/[^A-Z0-9]/g, "_");
3604
+ }
3605
+ __name(tokenSuffix, "tokenSuffix");
3606
+ function getClientContextTokenName(clientName) {
3607
+ const clientSuffix = tokenSuffix(clientName);
3080
3608
  return `CLIENT_CONTEXT_TOKEN_${clientSuffix}`;
3081
3609
  }
3082
3610
  __name(getClientContextTokenName, "getClientContextTokenName");
3083
- function getBasePathTokenName(clientName = "default") {
3084
- const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
3611
+ function getBasePathTokenName(clientName) {
3612
+ const clientSuffix = tokenSuffix(clientName);
3085
3613
  return `BASE_PATH_${clientSuffix}`;
3086
3614
  }
3087
3615
  __name(getBasePathTokenName, "getBasePathTokenName");
3088
3616
 
3089
- // ../../shared/src/utils/functions/duplicate-function-name.ts
3090
- function hasDuplicateFunctionNames(arr) {
3091
- return new Set(arr.map((fn) => fn.getName())).size !== arr.length;
3617
+ // ../../shared/src/utils/functions/controller-groups.ts
3618
+ function groupOperationsByController(operations, onWarning) {
3619
+ const groups = {};
3620
+ const controllerByFold = /* @__PURE__ */ new Map();
3621
+ const tagSpellings = /* @__PURE__ */ new Map();
3622
+ const namelessTags = /* @__PURE__ */ new Set();
3623
+ operations.forEach((operation) => {
3624
+ const tag = operation.tags?.[0];
3625
+ let rawName = "Default";
3626
+ if (tag !== void 0) {
3627
+ rawName = tag;
3628
+ } else {
3629
+ const pathParts = operation.path.split("/").filter((part) => part && !part.startsWith("{"));
3630
+ if (pathParts.length > 1) {
3631
+ rawName = pathParts[1];
3632
+ }
3633
+ }
3634
+ const sanitized = pascalCase(rawName);
3635
+ const isNameless = !/\p{L}/u.test(sanitized);
3636
+ const candidate = isNameless ? "Default" : sanitized;
3637
+ const fold = candidate.toLowerCase();
3638
+ const controllerName = controllerByFold.get(fold) ?? candidate;
3639
+ controllerByFold.set(fold, controllerName);
3640
+ if (tag !== void 0 && isNameless) {
3641
+ namelessTags.add(tag);
3642
+ } else if (tag !== void 0) {
3643
+ const spellings = tagSpellings.get(controllerName) ?? /* @__PURE__ */ new Set();
3644
+ spellings.add(tag);
3645
+ tagSpellings.set(controllerName, spellings);
3646
+ }
3647
+ if (!groups[controllerName]) {
3648
+ groups[controllerName] = [];
3649
+ }
3650
+ groups[controllerName].push(operation);
3651
+ });
3652
+ for (const tag of namelessTags) {
3653
+ onWarning?.(`Tag "${tag}" contains no characters usable in a name \u2014 its operations are generated into the "Default" controller. Rename the tag to give them a file of their own.`);
3654
+ }
3655
+ for (const [controllerName, spellings] of tagSpellings) {
3656
+ if (spellings.size > 1) {
3657
+ onWarning?.(`Tags ${[
3658
+ ...spellings
3659
+ ].map((name) => `"${name}"`).join(" and ")} all map to the controller "${controllerName}" \u2014 their operations are generated into one file. Rename one tag to keep them apart.`);
3660
+ }
3661
+ }
3662
+ return groups;
3663
+ }
3664
+ __name(groupOperationsByController, "groupOperationsByController");
3665
+
3666
+ // ../../shared/src/utils/functions/method-names.ts
3667
+ var RESERVED_MEMBER_NAMES = Object.freeze([
3668
+ "constructor",
3669
+ "httpClient",
3670
+ "basePath",
3671
+ "clientContextToken",
3672
+ "createContextWithClientId"
3673
+ ]);
3674
+ var RESERVED_MEMBER_SET = new Set(RESERVED_MEMBER_NAMES);
3675
+ function reservedMemberCollision(operation, config) {
3676
+ if (config.options.customizeMethodName || !operation.operationId) {
3677
+ return void 0;
3678
+ }
3679
+ const natural = camelCase(operation.operationId);
3680
+ return RESERVED_MEMBER_SET.has(natural) ? {
3681
+ from: natural,
3682
+ to: `_${natural}`
3683
+ } : void 0;
3092
3684
  }
3093
- __name(hasDuplicateFunctionNames, "hasDuplicateFunctionNames");
3685
+ __name(reservedMemberCollision, "reservedMemberCollision");
3686
+ function getOperationMethodName(operation, config) {
3687
+ const customize = config.options.customizeMethodName;
3688
+ if (!customize) {
3689
+ return defaultOperationMethodName(operation);
3690
+ }
3691
+ if (operation.operationId == null) {
3692
+ throw new InvalidIdentifierError(`customizeMethodName needs an operationId, and ${describeOperation(operation)} has none. Add one to the spec, or drop customizeMethodName to use the derived name.`, operation);
3693
+ }
3694
+ const customName = customize(operation.operationId);
3695
+ if (!isValidIdentifier(customName) || RESERVED_MEMBER_SET.has(customName)) {
3696
+ throw new InvalidIdentifierError(`customizeMethodName returned "${customName}" for ${describeOperation(operation)}, which is not a usable TypeScript method name. Return an identifier \u2014 letters, digits, "_" and "$", not starting with a digit \u2014 and not one of ${RESERVED_MEMBER_NAMES.map((name) => `"${name}"`).join(", ")}.`, operation, customName);
3697
+ }
3698
+ return customName;
3699
+ }
3700
+ __name(getOperationMethodName, "getOperationMethodName");
3701
+ function defaultOperationMethodName(operation) {
3702
+ if (operation.operationId) {
3703
+ const name = camelCase(operation.operationId);
3704
+ return RESERVED_MEMBER_SET.has(name) ? `_${name}` : name;
3705
+ }
3706
+ const method = pascalCase(operation.method.toLowerCase());
3707
+ const pathParts = operation.path.split("/").filter((segment) => segment !== "").map((segment) => pascalCase(segment));
3708
+ const resource = pathParts.join("") || "resource";
3709
+ return `${camelCase(resource)}${method}`;
3710
+ }
3711
+ __name(defaultOperationMethodName, "defaultOperationMethodName");
3712
+
3713
+ // ../../shared/src/utils/functions/distinct-member-names.ts
3714
+ function assertDistinctMemberNames(serviceClass, className, operations, methodNameOf) {
3715
+ const methodNames = serviceClass.getMethods().map((method) => method.getName());
3716
+ const propertyNames = new Set(serviceClass.getProperties().map((property) => property.getName()));
3717
+ const duplicates = [
3718
+ ...new Set(methodNames.filter((name, index) => methodNames.indexOf(name) !== index || propertyNames.has(name)))
3719
+ ];
3720
+ if (duplicates.length === 0) {
3721
+ return;
3722
+ }
3723
+ const byName = new Map(duplicates.map((name) => [
3724
+ name,
3725
+ []
3726
+ ]));
3727
+ for (const operation of operations) {
3728
+ byName.get(methodNameOf(operation))?.push(operation);
3729
+ }
3730
+ const detail = [
3731
+ ...byName
3732
+ ].map(([name, ops]) => {
3733
+ const from = ops.map(describeOperation).join(" and ");
3734
+ return propertyNames.has(name) ? `"${name}" from ${from} (a property of ${className})` : `"${name}" from ${from}`;
3735
+ }).join("; ");
3736
+ throw new DuplicateGeneratedNameError(`Operations map to the same member name in ${className}: ${detail}. Ensure each operationId maps to a unique name.`, duplicates, [
3737
+ ...byName.values()
3738
+ ].flat());
3739
+ }
3740
+ __name(assertDistinctMemberNames, "assertDistinctMemberNames");
3094
3741
 
3095
3742
  // ../../shared/src/config/constants.ts
3096
3743
  var disableLinting = `// @ts-nocheck
@@ -3143,6 +3790,14 @@ var HttpResourceMethodBodyGenerator = class {
3143
3790
  constructor(config) {
3144
3791
  this.config = config;
3145
3792
  }
3793
+ /**
3794
+ * The plugin reserves its own bindings (`resourceOptions`/`requestOptions`),
3795
+ * not the core service's — resolving against the wrong set would let a
3796
+ * `requestOptions` query parameter capture the plugin's own parameter.
3797
+ */
3798
+ argumentNames(operation) {
3799
+ return resolveArgumentNames(operation, this.config, RESOURCE_ARGUMENT_PROFILE);
3800
+ }
3146
3801
  generateMethodBody(operation) {
3147
3802
  const bodyParts = [
3148
3803
  this.generateDefaultHeaders(operation),
@@ -3174,7 +3829,7 @@ var HttpResourceMethodBodyGenerator = class {
3174
3829
  }
3175
3830
  generateRequestOptions(operation) {
3176
3831
  const entries = [];
3177
- const url = emitUrlExpression(operation.path, operation.pathParams, signalAwareParamValue);
3832
+ const url = emitUrlExpression(operation.path, operation.pathParams, this.argumentNames(operation), signalAwareParamValue);
3178
3833
  entries.push(`url: ${url}`);
3179
3834
  entries.push(`method: "GET"`);
3180
3835
  entries.push("...requestOptions");
@@ -3192,7 +3847,7 @@ var HttpResourceMethodBodyGenerator = class {
3192
3847
  }
3193
3848
  generateHttpResource(operation) {
3194
3849
  const resourceOptions = this.generateRequestOptions(operation);
3195
- const queryParams = emitSignalAwareQueryParams(operation.queryParams);
3850
+ const queryParams = emitSignalAwareQueryParams(operation.queryParams, this.argumentNames(operation));
3196
3851
  let httpResource = "httpResource";
3197
3852
  switch (operation.responseType) {
3198
3853
  case "blob":
@@ -3240,13 +3895,14 @@ var HttpResourceMethodParamsGenerator = class {
3240
3895
  }
3241
3896
  generateApiParameters(operation) {
3242
3897
  const params = [];
3898
+ const argumentNames = resolveArgumentNames(operation, this.config, RESOURCE_ARGUMENT_PROFILE);
3243
3899
  operation.pathParams.forEach((param) => {
3244
3900
  const paramType = getTypeScriptType(param.schema || {
3245
3901
  ...param
3246
3902
  }, this.config);
3247
3903
  const signalParamType = param.required ? `Signal<${paramType}>` : `Signal<${paramType} | undefined>`;
3248
3904
  params.push({
3249
- name: camelCase(param.name),
3905
+ name: argumentNames.of(param.name),
3250
3906
  type: `${signalParamType} | ${paramType}`,
3251
3907
  hasQuestionToken: !param.required
3252
3908
  });
@@ -3257,7 +3913,7 @@ var HttpResourceMethodParamsGenerator = class {
3257
3913
  }, this.config);
3258
3914
  const signalParamType = param.required ? `Signal<${paramType}>` : `Signal<${paramType} | undefined>`;
3259
3915
  params.push({
3260
- name: camelCase(param.name),
3916
+ name: argumentNames.of(param.name),
3261
3917
  type: `${signalParamType} | ${paramType}`,
3262
3918
  hasQuestionToken: !param.required
3263
3919
  });
@@ -3328,31 +3984,11 @@ var HttpResourceMethodGenerator = class {
3328
3984
  returnType,
3329
3985
  statements: methodBody,
3330
3986
  overloads,
3331
- docs: operation.description ? [
3332
- operation.description
3333
- ] : void 0
3987
+ docs: emitDocs(operation.description)
3334
3988
  });
3335
3989
  }
3336
3990
  generateMethodName(operation) {
3337
- if (this.config.options.customizeMethodName) {
3338
- if (operation.operationId == null) {
3339
- throw new Error(`Operation ID is required for method name customization of operation: (${operation.method}) ${operation.path}`);
3340
- }
3341
- return this.config.options.customizeMethodName(operation.operationId);
3342
- } else {
3343
- return this.defaultNameGenerator(operation);
3344
- }
3345
- }
3346
- defaultNameGenerator(operation) {
3347
- if (operation.operationId) {
3348
- return camelCase(operation.operationId);
3349
- }
3350
- const method = pascalCase(operation.method.toLowerCase());
3351
- const pathParts = operation.path.split("/").map((str2) => {
3352
- return pascalCase(pascalCase(str2).replace(/[^a-zA-Z0-9]/g, ""));
3353
- });
3354
- const resource = pathParts.join("") || "resource";
3355
- return `${camelCase(resource)}${method}`;
3991
+ return getOperationMethodName(operation, this.config);
3356
3992
  }
3357
3993
  generateReturnType(operation) {
3358
3994
  const response = operation.responses?.["200"] || operation.responses?.["201"] || operation.responses?.["204"];
@@ -3427,7 +4063,6 @@ var HttpResourceIndexGenerator = class {
3427
4063
  });
3428
4064
  });
3429
4065
  sourceFile.formatText();
3430
- sourceFile.saveSync();
3431
4066
  }
3432
4067
  };
3433
4068
 
@@ -3457,32 +4092,15 @@ var HttpResourceGenerator = class {
3457
4092
  this.onWarning?.("No API paths found in the specification");
3458
4093
  return;
3459
4094
  }
3460
- const controllerGroups = this.groupPathsByController(paths);
3461
- await Promise.all(Object.entries(controllerGroups).map(([controllerName, operations]) => {
3462
- this.generateServiceFile(controllerName, operations, outputDir);
3463
- }));
4095
+ const controllerGroups = groupOperationsByController(paths, this.onWarning);
4096
+ await Promise.all(
4097
+ // Must return the promise: without it Promise.all awaits [undefined],
4098
+ // and a generateServiceFile rejection escapes as an unhandled
4099
+ // rejection while generation reports success.
4100
+ Object.entries(controllerGroups).map(([controllerName, operations]) => this.generateServiceFile(controllerName, operations, outputDir))
4101
+ );
3464
4102
  this.indexGenerator.generateIndex(outputRoot);
3465
4103
  }
3466
- groupPathsByController(paths) {
3467
- const groups = {};
3468
- paths.forEach((path3) => {
3469
- let controllerName = "Default";
3470
- if (path3.tags && path3.tags.length > 0) {
3471
- controllerName = path3.tags[0];
3472
- } else {
3473
- const pathParts = path3.path.split("/").filter((p) => p && !p.startsWith("{"));
3474
- if (pathParts.length > 1) {
3475
- controllerName = pascalCase(pathParts[1]);
3476
- }
3477
- }
3478
- controllerName = pascalCase(controllerName);
3479
- if (!groups[controllerName]) {
3480
- groups[controllerName] = [];
3481
- }
3482
- groups[controllerName].push(path3);
3483
- });
3484
- return groups;
3485
- }
3486
4104
  async generateServiceFile(controllerName, operations, outputDir) {
3487
4105
  const fileName = `${camelCase(controllerName)}.resource.ts`;
3488
4106
  const filePath = path2.join(outputDir, fileName);
@@ -3492,7 +4110,6 @@ var HttpResourceGenerator = class {
3492
4110
  this.addServiceClass(sourceFile, controllerName, operations);
3493
4111
  sourceFile.fixMissingImports().formatText();
3494
4112
  sourceFile.insertText(0, HTTP_RESOURCE_GENERATOR_HEADER_COMMENT(getResourceClassName(controllerName, this.config.options.naming?.resources)));
3495
- sourceFile.saveSync();
3496
4113
  }
3497
4114
  addServiceClass(sourceFile, controllerName, operations) {
3498
4115
  const className = getResourceClassName(controllerName, this.config.options.naming?.resources);
@@ -3568,14 +4185,23 @@ var HttpResourceGenerator = class {
3568
4185
  ],
3569
4186
  returnType: "HttpContext",
3570
4187
  statements: `const context = existingContext || new HttpContext();
3571
- return context.set(this.clientContextToken, '${this.config.clientName || "default"}');`
4188
+ return context.set(this.clientContextToken, ${quoteLiteral(effectiveClientName(this.config.clientName))});`
3572
4189
  });
3573
4190
  operations.forEach((operation) => {
4191
+ const { renamed: renamed2, merged } = resolveArgumentNames(operation, this.config, RESOURCE_ARGUMENT_PROFILE);
4192
+ for (const { source, identifier } of renamed2) {
4193
+ this.onWarning?.(`Parameter "${source}" of ${describeOperation(operation)} is exposed as "${identifier}" \u2014 its natural name is already taken by another parameter or by the resource method itself.`);
4194
+ }
4195
+ for (const wireName of merged) {
4196
+ this.onWarning?.(`Parameter "${wireName}" of ${describeOperation(operation)} is declared in more than one location; they collapse into one argument, so the first declaration's type wins and the same value is sent for both.`);
4197
+ }
4198
+ const collision = reservedMemberCollision(operation, this.config);
4199
+ if (collision) {
4200
+ this.onWarning?.(`Operation ${describeOperation(operation)} would be named "${collision.from}", which the generated class already binds \u2014 it is emitted as "${collision.to}". Rename the operationId to choose the name.`);
4201
+ }
3574
4202
  this.methodGenerator.addResourceMethod(serviceClass, operation);
3575
4203
  });
3576
- if (hasDuplicateFunctionNames(serviceClass.getMethods())) {
3577
- throw new Error(`Duplicate method names found in service class ${className}. Please ensure unique method names for each operation.`);
3578
- }
4204
+ assertDistinctMemberNames(serviceClass, className, operations, (op) => this.methodGenerator.generateMethodName(op));
3579
4205
  }
3580
4206
  };
3581
4207
  // Annotate the CommonJS export names for ESM import in node: