ng-openapi 0.3.1-pr-114-feat-functional-client-interceptors-52e79cb.0 → 0.3.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.
Files changed (5) hide show
  1. package/README.md +5 -9
  2. package/cli.cjs +99 -242
  3. package/index.d.ts +3 -11
  4. package/index.js +100 -247
  5. package/package.json +1 -1
package/README.md CHANGED
@@ -151,13 +151,11 @@ The simplest way to integrate ng-openapi is using the provider function:
151
151
  ```typescript
152
152
  // In your app.config.ts
153
153
  import { ApplicationConfig } from "@angular/core";
154
- import { provideHttpClient, withInterceptors } from "@angular/common/http";
155
154
  import { provideDefaultClient } from "./api/providers";
156
- import { defaultClientInterceptor } from "./api/utils/base-interceptor";
157
155
 
158
156
  export const appConfig: ApplicationConfig = {
159
157
  providers: [
160
- provideHttpClient(withInterceptors([defaultClientInterceptor])),
158
+ // One-line setup with automatic interceptor configuration
161
159
  provideDefaultClient({
162
160
  basePath: "https://api.example.com",
163
161
  }),
@@ -166,13 +164,12 @@ export const appConfig: ApplicationConfig = {
166
164
  };
167
165
  ```
168
166
 
169
- > The provider function is named after your `clientName` (e.g. `clientName: "PetStore"` → `providePetStoreClient`); without a `clientName` it is `provideDefaultClient`. The same goes for the functional interceptor (`petStoreClientInterceptor` / `defaultClientInterceptor`), which runs this client's scoped interceptor chain and leaves all other requests untouched.
167
+ > The provider function is named after your `clientName` (e.g. `clientName: "PetStore"` → `providePetStoreClient`); without a `clientName` it is `provideDefaultClient`.
170
168
 
171
- That's it! This configures:
169
+ That's it! This automatically configures:
172
170
 
173
171
  - ✅ BASE_PATH token
174
- - ✅ Client-scoped interceptor chain
175
- - ✅ Date transformation (if using Date type)
172
+ - ✅ Date transformation interceptor (if using Date type)
176
173
 
177
174
  ### Advanced Provider Options
178
175
 
@@ -183,11 +180,10 @@ provideDefaultClient({
183
180
  enableDateTransform: false,
184
181
  });
185
182
 
186
- // Client-specific interceptors: classes (not instances) and/or functional interceptors
183
+ // Client-specific interceptors (classes, not instances)
187
184
  provideDefaultClient({
188
185
  basePath: "https://api.example.com",
189
186
  interceptors: [AuthInterceptor, LoggingInterceptor],
190
- interceptorFns: [authInterceptorFn],
191
187
  });
192
188
  ```
193
189
 
package/cli.cjs CHANGED
@@ -260,14 +260,6 @@ function getServiceClassName(controllerName, naming) {
260
260
  return decorate(pascalCase(controllerName), "Service", naming);
261
261
  }
262
262
  __name(getServiceClassName, "getServiceClassName");
263
- function getBaseInterceptorClassName(clientName = "default") {
264
- return `${clientName.charAt(0).toUpperCase()}${clientName.slice(1)}BaseInterceptor`;
265
- }
266
- __name(getBaseInterceptorClassName, "getBaseInterceptorClassName");
267
- function getClientInterceptorFnName(clientName = "default") {
268
- return `${clientName.charAt(0).toLowerCase()}${clientName.slice(1)}ClientInterceptor`;
269
- }
270
- __name(getClientInterceptorFnName, "getClientInterceptorFnName");
271
263
  function getModelTypeName(rawName, naming) {
272
264
  return decorate(pascalCaseForEnums(rawName), "", naming);
273
265
  }
@@ -864,11 +856,11 @@ function getBasePathTokenName(clientName = "default") {
864
856
  return `BASE_PATH_${clientSuffix}`;
865
857
  }
866
858
  __name(getBasePathTokenName, "getBasePathTokenName");
867
- function getInterceptorFnsTokenName(clientName = "default") {
859
+ function getInterceptorsTokenName(clientName = "default") {
868
860
  const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
869
- return `HTTP_INTERCEPTOR_FNS_${clientSuffix}`;
861
+ return `HTTP_INTERCEPTORS_${clientSuffix}`;
870
862
  }
871
- __name(getInterceptorFnsTokenName, "getInterceptorFnsTokenName");
863
+ __name(getInterceptorsTokenName, "getInterceptorsTokenName");
872
864
 
873
865
  // ../shared/src/utils/functions/duplicate-function-name.ts
874
866
  function hasDuplicateFunctionNames(arr) {
@@ -953,7 +945,7 @@ var fs3 = __toESM(require("fs"));
953
945
  var path14 = __toESM(require("path"));
954
946
 
955
947
  // package.json
956
- var version = "0.3.0";
948
+ var version = "0.3.1";
957
949
 
958
950
  // src/lib/core/generator.ts
959
951
  var import_ts_morph10 = require("ts-morph");
@@ -1651,15 +1643,15 @@ var TokenGenerator = class {
1651
1643
  },
1652
1644
  {
1653
1645
  namedImports: [
1654
- "HttpInterceptorFn",
1646
+ "HttpInterceptor",
1655
1647
  "HttpContextToken"
1656
1648
  ],
1657
1649
  moduleSpecifier: "@angular/common/http"
1658
1650
  }
1659
1651
  ]);
1660
- const basePathTokenName = getBasePathTokenName(this.clientName);
1661
- const interceptorFnsTokenName = getInterceptorFnsTokenName(this.clientName);
1662
- const clientContextTokenName = getClientContextTokenName(this.clientName);
1652
+ const basePathTokenName = this.getBasePathTokenName();
1653
+ const interceptorsTokenName = this.getInterceptorsTokenName();
1654
+ const clientContextTokenName = this.getClientContextTokenName();
1663
1655
  sourceFile.addVariableStatement({
1664
1656
  isExported: true,
1665
1657
  declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
@@ -1682,16 +1674,15 @@ var TokenGenerator = class {
1682
1674
  declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
1683
1675
  declarations: [
1684
1676
  {
1685
- name: interceptorFnsTokenName,
1686
- initializer: `new InjectionToken<HttpInterceptorFn[]>('${interceptorFnsTokenName}', {
1677
+ name: interceptorsTokenName,
1678
+ initializer: `new InjectionToken<HttpInterceptor[]>('${interceptorsTokenName}', {
1687
1679
  providedIn: 'root',
1688
1680
  factory: () => [], // Default empty array
1689
1681
  })`
1690
1682
  }
1691
1683
  ],
1692
1684
  leadingTrivia: `/**
1693
- * Injection token carrying the ${this.clientName} client's scoped interceptor chain,
1694
- * normalized to functional interceptors. Populated by the client's provide function.
1685
+ * Injection token for the ${this.clientName} client HTTP interceptor instances
1695
1686
  */
1696
1687
  `
1697
1688
  });
@@ -1742,6 +1733,18 @@ var TokenGenerator = class {
1742
1733
  sourceFile.formatText();
1743
1734
  sourceFile.saveSync();
1744
1735
  }
1736
+ getBasePathTokenName() {
1737
+ const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1738
+ return `BASE_PATH_${clientSuffix}`;
1739
+ }
1740
+ getInterceptorsTokenName() {
1741
+ const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1742
+ return `HTTP_INTERCEPTORS_${clientSuffix}`;
1743
+ }
1744
+ getClientContextTokenName() {
1745
+ const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1746
+ return `CLIENT_CONTEXT_TOKEN_${clientSuffix}`;
1747
+ }
1745
1748
  };
1746
1749
 
1747
1750
  // src/lib/generators/utility/file-download.generator.ts
@@ -1907,7 +1910,6 @@ var DateTransformerGenerator = class {
1907
1910
  "HttpEvent",
1908
1911
  "HttpHandler",
1909
1912
  "HttpInterceptor",
1910
- "HttpInterceptorFn",
1911
1913
  "HttpRequest",
1912
1914
  "HttpResponse"
1913
1915
  ],
@@ -1979,58 +1981,6 @@ var DateTransformerGenerator = class {
1979
1981
  }
1980
1982
 
1981
1983
  return obj;`
1982
- });
1983
- sourceFile.addFunction({
1984
- name: "transformDateResponse",
1985
- parameters: [
1986
- {
1987
- name: "event",
1988
- type: "HttpEvent<any>"
1989
- },
1990
- {
1991
- name: "dateRegex",
1992
- type: "RegExp"
1993
- }
1994
- ],
1995
- returnType: "HttpEvent<any>",
1996
- statements: `
1997
- if (event instanceof HttpResponse && event.body) {
1998
- return event.clone({ body: transformDates(event.body, dateRegex) });
1999
- }
2000
- return event;`
2001
- });
2002
- sourceFile.addFunction({
2003
- name: "dateInterceptorWithRegex",
2004
- isExported: true,
2005
- docs: [
2006
- "Builds a functional date interceptor for `provideHttpClient(withInterceptors([...]))`.\n@param dateRegex Optional override for the pattern used to detect ISO date strings."
2007
- ],
2008
- parameters: [
2009
- {
2010
- name: "dateRegex",
2011
- type: "RegExp",
2012
- initializer: "ISO_DATE_REGEX"
2013
- }
2014
- ],
2015
- returnType: "HttpInterceptorFn",
2016
- statements: `
2017
- return (req, next) => next(req).pipe(map((event) => transformDateResponse(event, dateRegex)));`
2018
- });
2019
- sourceFile.addVariableStatement({
2020
- isExported: true,
2021
- declarationKind: import_ts_morph6.VariableDeclarationKind.Const,
2022
- declarations: [
2023
- {
2024
- name: "dateInterceptor",
2025
- type: "HttpInterceptorFn",
2026
- initializer: "dateInterceptorWithRegex()"
2027
- }
2028
- ],
2029
- leadingTrivia: `/**
2030
- * Functional date interceptor using the default ISO_DATE_REGEX.
2031
- * Use dateInterceptorWithRegex(...) to customize the pattern.
2032
- */
2033
- `
2034
1984
  });
2035
1985
  sourceFile.addClass({
2036
1986
  name: "DateInterceptor",
@@ -2075,7 +2025,14 @@ var DateTransformerGenerator = class {
2075
2025
  ],
2076
2026
  returnType: "Observable<HttpEvent<any>>",
2077
2027
  statements: `
2078
- return next.handle(req).pipe(map((event) => transformDateResponse(event, this.dateRegex)));`
2028
+ return next.handle(req).pipe(
2029
+ map(event => {
2030
+ if (event instanceof HttpResponse && event.body) {
2031
+ return event.clone({ body: transformDates(event.body, this.dateRegex) });
2032
+ }
2033
+ return event;
2034
+ })
2035
+ );`
2079
2036
  }
2080
2037
  ]
2081
2038
  });
@@ -2156,14 +2113,13 @@ var ProviderGenerator = class {
2156
2113
  });
2157
2114
  sourceFile.insertText(0, PROVIDER_GENERATOR_HEADER_COMMENT);
2158
2115
  const basePathTokenName = getBasePathTokenName(this.clientName);
2159
- const interceptorFnsTokenName = getInterceptorFnsTokenName(this.clientName);
2160
- const baseInterceptorClassName = getBaseInterceptorClassName(this.clientName);
2116
+ const interceptorsTokenName = getInterceptorsTokenName(this.clientName);
2117
+ const baseInterceptorClassName = `${this.capitalizeFirst(this.clientName)}BaseInterceptor`;
2161
2118
  sourceFile.addImportDeclarations([
2162
2119
  {
2163
2120
  namedImports: [
2164
2121
  "EnvironmentProviders",
2165
2122
  "Provider",
2166
- "inject",
2167
2123
  "makeEnvironmentProviders"
2168
2124
  ],
2169
2125
  moduleSpecifier: "@angular/core"
@@ -2171,15 +2127,14 @@ var ProviderGenerator = class {
2171
2127
  {
2172
2128
  namedImports: [
2173
2129
  "HTTP_INTERCEPTORS",
2174
- "HttpInterceptor",
2175
- "HttpInterceptorFn"
2130
+ "HttpInterceptor"
2176
2131
  ],
2177
2132
  moduleSpecifier: "@angular/common/http"
2178
2133
  },
2179
2134
  {
2180
2135
  namedImports: [
2181
2136
  basePathTokenName,
2182
- interceptorFnsTokenName
2137
+ interceptorsTokenName
2183
2138
  ],
2184
2139
  moduleSpecifier: "./tokens"
2185
2140
  },
@@ -2193,7 +2148,7 @@ var ProviderGenerator = class {
2193
2148
  if (this.config.options.dateType === "Date") {
2194
2149
  sourceFile.addImportDeclaration({
2195
2150
  namedImports: [
2196
- "dateInterceptorWithRegex"
2151
+ "DateInterceptor"
2197
2152
  ],
2198
2153
  moduleSpecifier: "./utils/date-transformer"
2199
2154
  });
@@ -2207,50 +2162,24 @@ var ProviderGenerator = class {
2207
2162
  ]
2208
2163
  },
2209
2164
  {
2210
- name: "interceptors",
2211
- type: "(new (...args: any[]) => HttpInterceptor)[]",
2212
- hasQuestionToken: true,
2213
- docs: [
2214
- "Class-based HTTP interceptors to apply to this client.",
2215
- "Classes are resolved through DI when provided; classes that are not",
2216
- "provided anywhere are instantiated without constructor arguments, so",
2217
- "classes with required dependencies must be registered as providers."
2218
- ]
2219
- },
2220
- {
2221
- name: "interceptorFns",
2222
- type: "HttpInterceptorFn[]",
2165
+ name: "enableDateTransform",
2166
+ type: "boolean",
2223
2167
  hasQuestionToken: true,
2224
2168
  docs: [
2225
- "Functional HTTP interceptors to apply to this client. Run after class-based interceptors."
2169
+ "Enable automatic date transformation (default: true)"
2226
2170
  ]
2227
2171
  },
2228
2172
  {
2229
- name: "registerDiInterceptor",
2230
- type: "boolean",
2173
+ name: "interceptors",
2174
+ type: "(new (...args: HttpInterceptor[]) => HttpInterceptor)[]",
2231
2175
  hasQuestionToken: true,
2232
2176
  docs: [
2233
- "Register the class-based interceptor on HTTP_INTERCEPTORS (default: true).",
2234
- "Set to false when your app needs withInterceptorsFromDi() for its own",
2235
- "interceptors but this client's chain is registered functionally via",
2236
- "withInterceptors([...]) \u2014 otherwise the chain would run twice.",
2237
- "",
2238
- "WARNING: with false you MUST register the functional interceptor yourself",
2239
- "(provideHttpClient(withInterceptors([...]))); if neither variant is",
2240
- "registered, this client's entire chain (date transform, interceptors,",
2241
- "interceptorFns) silently does nothing."
2177
+ "Array of HTTP interceptor classes to apply to this client"
2242
2178
  ]
2243
2179
  }
2244
2180
  ];
2245
2181
  if (this.config.options.dateType === "Date") {
2246
2182
  configProperties.push({
2247
- name: "enableDateTransform",
2248
- type: "boolean",
2249
- hasQuestionToken: true,
2250
- docs: [
2251
- "Enable automatic date transformation (default: true)"
2252
- ]
2253
- }, {
2254
2183
  name: "dateTransformRegex",
2255
2184
  type: "RegExp",
2256
2185
  hasQuestionToken: true,
@@ -2268,25 +2197,14 @@ var ProviderGenerator = class {
2268
2197
  ],
2269
2198
  properties: configProperties
2270
2199
  });
2271
- this.addMainProviderFunction(sourceFile, basePathTokenName, interceptorFnsTokenName, baseInterceptorClassName);
2200
+ this.addMainProviderFunction(sourceFile, basePathTokenName, interceptorsTokenName, baseInterceptorClassName);
2272
2201
  sourceFile.formatText();
2273
2202
  sourceFile.saveSync();
2274
2203
  }
2275
- addMainProviderFunction(sourceFile, basePathTokenName, interceptorFnsTokenName, baseInterceptorClassName) {
2204
+ addMainProviderFunction(sourceFile, basePathTokenName, interceptorsTokenName, baseInterceptorClassName) {
2276
2205
  const hasDateInterceptor = this.config.options.dateType === "Date";
2277
2206
  const functionName = `provide${this.capitalizeFirst(this.clientName)}Client`;
2278
2207
  const configTypeName = `${this.capitalizeFirst(this.clientName)}Config`;
2279
- const interceptorFnName = getClientInterceptorFnName(this.clientName);
2280
- const dateInterceptorBlock = hasDateInterceptor ? `
2281
- // Date interceptor first: it runs first on requests (a no-op) and, being
2282
- // outermost, its response transform applies last \u2014 the other client
2283
- // interceptors see the raw body; only the service sees Date instances
2284
- if (config.enableDateTransform !== false) {
2285
- interceptorFns.push(dateInterceptorWithRegex(config.dateTransformRegex));
2286
- }
2287
- ` : `
2288
- // Date transformation not available (dateType: 'string' was used in generation)
2289
- `;
2290
2208
  const functionBody = `
2291
2209
  const providers: Provider[] = [
2292
2210
  // Base path token for this client
@@ -2294,36 +2212,38 @@ const providers: Provider[] = [
2294
2212
  provide: ${basePathTokenName},
2295
2213
  useValue: config.basePath
2296
2214
  },
2297
- // This client's interceptor chain, normalized to functional interceptors
2215
+ // Base interceptor that handles client-specific interceptors
2298
2216
  {
2299
- provide: ${interceptorFnsTokenName},
2300
- useFactory: (): HttpInterceptorFn[] => {
2301
- const interceptorFns: HttpInterceptorFn[] = [];
2302
- ${dateInterceptorBlock}
2303
- // Class-based interceptors are resolved through DI when provided,
2304
- // otherwise instantiated directly, and adapted to functional form
2305
- for (const interceptorClass of config.interceptors ?? []) {
2306
- const instance = inject(interceptorClass, { optional: true }) ?? new interceptorClass();
2307
- interceptorFns.push((req, next) => instance.intercept(req, { handle: next }));
2308
- }
2309
-
2310
- interceptorFns.push(...(config.interceptorFns ?? []));
2311
-
2312
- return interceptorFns;
2313
- }
2217
+ provide: HTTP_INTERCEPTORS,
2218
+ useClass: ${baseInterceptorClassName},
2219
+ multi: true
2314
2220
  }
2315
2221
  ];
2316
2222
 
2317
- // Class-based registration of the scoped chain; only active together with
2318
- // withInterceptorsFromDi(). Disable it (registerDiInterceptor: false) when the
2319
- // app uses withInterceptorsFromDi() for its own interceptors but this client's
2320
- // chain is registered via withInterceptors([${interceptorFnName}]) \u2014
2321
- // otherwise the chain would run twice.
2322
- if (config.registerDiInterceptor !== false) {
2223
+ // Add client-specific interceptor instances
2224
+ if (config.interceptors && config.interceptors.length > 0) {
2225
+ const interceptorInstances = config.interceptors.map(InterceptorClass => new InterceptorClass());
2226
+
2227
+ ${hasDateInterceptor ? `// Add date interceptor if enabled (default: true)
2228
+ if (config.enableDateTransform !== false) {
2229
+ interceptorInstances.unshift(new DateInterceptor(config.dateTransformRegex));
2230
+ }` : `// Date transformation not available (dateType: 'string' was used in generation)`}
2231
+
2323
2232
  providers.push({
2324
- provide: HTTP_INTERCEPTORS,
2325
- useClass: ${baseInterceptorClassName},
2326
- multi: true
2233
+ provide: ${interceptorsTokenName},
2234
+ useValue: interceptorInstances
2235
+ });
2236
+ } ${hasDateInterceptor ? `else if (config.enableDateTransform !== false) {
2237
+ // Only date interceptor enabled
2238
+ providers.push({
2239
+ provide: ${interceptorsTokenName},
2240
+ useValue: [new DateInterceptor(config.dateTransformRegex)]
2241
+ });
2242
+ }` : ``} else {
2243
+ // No interceptors
2244
+ providers.push({
2245
+ provide: ${interceptorsTokenName},
2246
+ useValue: []
2327
2247
  });
2328
2248
  }
2329
2249
 
@@ -2337,17 +2257,13 @@ return makeEnvironmentProviders(providers);`;
2337
2257
  "@example",
2338
2258
  "```typescript",
2339
2259
  "// In your app.config.ts",
2340
- "import { provideHttpClient, withInterceptors } from '@angular/common/http';",
2341
2260
  `import { ${functionName} } from './api/providers';`,
2342
- `import { ${interceptorFnName} } from './api/utils/base-interceptor';`,
2343
2261
  "",
2344
2262
  "export const appConfig: ApplicationConfig = {",
2345
2263
  " providers: [",
2346
- ` provideHttpClient(withInterceptors([${interceptorFnName}])),`,
2347
2264
  ` ${functionName}({`,
2348
2265
  " basePath: 'https://api.example.com',",
2349
- " interceptors: [AuthInterceptor], // Classes, not instances",
2350
- " interceptorFns: [loggingInterceptor] // Functional interceptors",
2266
+ " interceptors: [AuthInterceptor, LoggingInterceptor] // Classes, not instances",
2351
2267
  " }),",
2352
2268
  " // other providers...",
2353
2269
  " ]",
@@ -2407,28 +2323,23 @@ var BaseInterceptorGenerator = class {
2407
2323
  overwrite: true
2408
2324
  });
2409
2325
  sourceFile.insertText(0, BASE_INTERCEPTOR_HEADER_COMMENT(this.#clientName));
2410
- const interceptorFnsTokenName = getInterceptorFnsTokenName(this.#clientName);
2326
+ const interceptorsTokenName = getInterceptorsTokenName(this.#clientName);
2411
2327
  const clientContextTokenName = getClientContextTokenName(this.#clientName);
2412
- const interceptorFnName = getClientInterceptorFnName(this.#clientName);
2413
2328
  sourceFile.addImportDeclarations([
2414
2329
  {
2415
2330
  namedImports: [
2416
2331
  "HttpContextToken",
2417
2332
  "HttpEvent",
2418
2333
  "HttpHandler",
2419
- "HttpHandlerFn",
2420
2334
  "HttpInterceptor",
2421
- "HttpInterceptorFn",
2422
2335
  "HttpRequest"
2423
2336
  ],
2424
2337
  moduleSpecifier: "@angular/common/http"
2425
2338
  },
2426
2339
  {
2427
2340
  namedImports: [
2428
- "EnvironmentInjector",
2429
2341
  "inject",
2430
- "Injectable",
2431
- "runInInjectionContext"
2342
+ "Injectable"
2432
2343
  ],
2433
2344
  moduleSpecifier: "@angular/core"
2434
2345
  },
@@ -2441,80 +2352,14 @@ var BaseInterceptorGenerator = class {
2441
2352
  {
2442
2353
  namedImports: [
2443
2354
  clientContextTokenName,
2444
- interceptorFnsTokenName
2355
+ interceptorsTokenName
2445
2356
  ],
2446
2357
  moduleSpecifier: "../tokens"
2447
2358
  }
2448
2359
  ]);
2449
- sourceFile.addFunction({
2450
- name: "interceptClientRequest",
2451
- parameters: [
2452
- {
2453
- name: "req",
2454
- type: "HttpRequest<any>"
2455
- },
2456
- {
2457
- name: "next",
2458
- type: "HttpHandlerFn"
2459
- },
2460
- {
2461
- name: "interceptorFns",
2462
- type: "HttpInterceptorFn[]"
2463
- },
2464
- {
2465
- name: "clientContextToken",
2466
- type: "HttpContextToken<string>"
2467
- },
2468
- {
2469
- name: "injector",
2470
- type: "EnvironmentInjector"
2471
- }
2472
- ],
2473
- returnType: "Observable<HttpEvent<any>>",
2474
- statements: `
2475
- // Check if this request belongs to this client using HttpContext
2476
- if (!req.context.has(clientContextToken)) {
2477
- // This request doesn't belong to this client, pass it through
2478
- return next(req);
2479
- }
2480
-
2481
- // Compose right-to-left so the interceptors run in array order. Each fn is
2482
- // invoked inside an injection context, mirroring Angular's own interceptor
2483
- // chain, so client interceptors can use inject().
2484
- const chain = interceptorFns.reduceRight<HttpHandlerFn>(
2485
- (nextFn, interceptorFn) => (request) => runInInjectionContext(injector, () => interceptorFn(request, nextFn)),
2486
- next
2487
- );
2488
-
2489
- return chain(req);`
2490
- });
2491
- sourceFile.addVariableStatement({
2492
- isExported: true,
2493
- declarationKind: import_ts_morph7.VariableDeclarationKind.Const,
2494
- declarations: [
2495
- {
2496
- name: interceptorFnName,
2497
- type: "HttpInterceptorFn",
2498
- initializer: `(req, next) =>
2499
- interceptClientRequest(req, next, inject(${interceptorFnsTokenName}), ${clientContextTokenName}, inject(EnvironmentInjector))`
2500
- }
2501
- ],
2502
- leadingTrivia: `/**
2503
- * Functional interceptor running the ${this.#clientName} client's scoped interceptor chain.
2504
- * Register it once with \`provideHttpClient(withInterceptors([${interceptorFnName}]))\`.
2505
- * If your app also uses \`withInterceptorsFromDi()\`, pass \`registerDiInterceptor: false\`
2506
- * to the client's provide function \u2014 otherwise the class-based registration it adds
2507
- * would run the same chain twice.
2508
- */
2509
- `
2510
- });
2511
2360
  sourceFile.addClass({
2512
- name: getBaseInterceptorClassName(this.#clientName),
2361
+ name: `${this.capitalizeFirst(this.#clientName)}BaseInterceptor`,
2513
2362
  isExported: true,
2514
- docs: [
2515
- `Class-based equivalent of \`${interceptorFnName}\` for DI registration via
2516
- \`withInterceptorsFromDi()\`. Register exactly one of the two variants.`
2517
- ],
2518
2363
  decorators: [
2519
2364
  {
2520
2365
  name: "Injectable",
@@ -2526,11 +2371,11 @@ var BaseInterceptorGenerator = class {
2526
2371
  ],
2527
2372
  properties: [
2528
2373
  {
2529
- name: "interceptorFns",
2530
- type: "HttpInterceptorFn[]",
2374
+ name: "httpInterceptors",
2375
+ type: "HttpInterceptor[]",
2531
2376
  scope: import_ts_morph7.Scope.Private,
2532
2377
  isReadonly: true,
2533
- initializer: `inject(${interceptorFnsTokenName})`
2378
+ initializer: `inject(${interceptorsTokenName})`
2534
2379
  },
2535
2380
  {
2536
2381
  name: "clientContextToken",
@@ -2538,13 +2383,6 @@ var BaseInterceptorGenerator = class {
2538
2383
  scope: import_ts_morph7.Scope.Private,
2539
2384
  isReadonly: true,
2540
2385
  initializer: clientContextTokenName
2541
- },
2542
- {
2543
- name: "injector",
2544
- type: "EnvironmentInjector",
2545
- scope: import_ts_morph7.Scope.Private,
2546
- isReadonly: true,
2547
- initializer: "inject(EnvironmentInjector)"
2548
2386
  }
2549
2387
  ],
2550
2388
  methods: [
@@ -2562,13 +2400,32 @@ var BaseInterceptorGenerator = class {
2562
2400
  ],
2563
2401
  returnType: "Observable<HttpEvent<any>>",
2564
2402
  statements: `
2565
- return interceptClientRequest(req, (request) => next.handle(request), this.interceptorFns, this.clientContextToken, this.injector);`
2403
+ // Check if this request belongs to this client using HttpContext
2404
+ if (!req.context.has(this.clientContextToken)) {
2405
+ // This request doesn't belong to this client, pass it through
2406
+ return next.handle(req);
2407
+ }
2408
+
2409
+ // Apply client-specific interceptors in reverse order
2410
+ let handler = next;
2411
+
2412
+ handler = this.httpInterceptors.reduceRight(
2413
+ (next, interceptor) => ({
2414
+ handle: (request: HttpRequest<any>) => interceptor.intercept(request, next)
2415
+ }),
2416
+ handler
2417
+ );
2418
+
2419
+ return handler.handle(req);`
2566
2420
  }
2567
2421
  ]
2568
2422
  });
2569
2423
  sourceFile.formatText();
2570
2424
  sourceFile.saveSync();
2571
2425
  }
2426
+ capitalizeFirst(str) {
2427
+ return str.charAt(0).toUpperCase() + str.slice(1);
2428
+ }
2572
2429
  };
2573
2430
 
2574
2431
  // src/lib/generators/utility/http-params-builder.generator.ts
package/index.d.ts CHANGED
@@ -685,21 +685,13 @@ declare function listGeneratedFileNames(project: Project, directoryPath: string,
685
685
  declare function getClientContextTokenName(clientName?: string): string;
686
686
  /** Token providing the API base path for the client. */
687
687
  declare function getBasePathTokenName(clientName?: string): string;
688
- /** Token carrying the client's interceptor chain, normalized to `HttpInterceptorFn[]`. */
689
- declare function getInterceptorFnsTokenName(clientName?: string): string;
688
+ /** Token carrying the client's interceptor chain. */
689
+ declare function getInterceptorsTokenName(clientName?: string): string;
690
690
 
691
691
  /** Class name of a generated service ("Role" → "RoleService", or decorated). */
692
692
  declare function getServiceClassName(controllerName: string, naming?: NameDecoration): string;
693
693
  /** Class name of a generated httpResource class ("Role" → "RoleResource", or decorated). */
694
694
  declare function getResourceClassName(controllerName: string, naming?: NameDecoration): string;
695
- /**
696
- * Class name of the generated scoped interceptor ("users" → "UsersBaseInterceptor").
697
- * Deliberately capitalizes only the first character (not pascalCase) to keep
698
- * parity with the client-name handling of the token helpers.
699
- */
700
- declare function getBaseInterceptorClassName(clientName?: string): string;
701
- /** Identifier of the generated functional scoped interceptor ("users" → "usersClientInterceptor"). */
702
- declare function getClientInterceptorFnName(clientName?: string): string;
703
695
  /**
704
696
  * TypeScript identifier of a schema-derived model type. Used both where
705
697
  * models are declared (models/index.ts) and where they are referenced
@@ -880,4 +872,4 @@ declare class ConfigValidationError extends Error {
880
872
  }
881
873
  declare function validateGeneratorConfig(config: unknown): asserts config is GeneratorConfig;
882
874
 
883
- export { BASE_INTERCEPTOR_HEADER_COMMENT, CONTENT_TYPES, ConfigValidationError, type EnumValueObject, type GenerationPhase, type GenerationResult, type GeneratorConfig, type GetMethodGenerationContext, HTTP_RESOURCE_GENERATOR_HEADER_COMMENT, type HeadersEmitOptions, type IPluginGenerator, type IPluginGeneratorClass, MAIN_INDEX_GENERATOR_HEADER_COMMENT, type MethodGenOptions, type MethodGenerationContext, type NameDecoration, type NamingOptions, NgOpenApiError, type NgOpenapiClientConfig, type NormalizedOperation, type NormalizedSpec, type OpenApiSecurityScheme, PROVIDER_GENERATOR_HEADER_COMMENT, type Parameter, type PathInfo, type PluginGeneratorContext, REQUEST_PARAMS_GENERATOR_HEADER_COMMENT, type Reporter, type RequestBody, type ResponseKind, type ResponseTypeInfo, SERVICE_GENERATOR_HEADER_COMMENT, SERVICE_INDEX_GENERATOR_HEADER_COMMENT, type ServiceDecoratorEmit, type ServiceDecoratorEmitOptions, SpecLoadError, SpecParseError, type SpecVersion, type SwaggerDefinition, SwaggerParser, type SwaggerResponse, type SwaggerSpec, TYPE_GENERATOR_HEADER_COMMENT, type TypeGenOptions, type TypeMappingConfig, type TypeSchema, ZOD_PLUGIN_GENERATOR_HEADER_COMMENT, ZOD_PLUGIN_INDEX_GENERATOR_HEADER_COMMENT, camelCase, defineConfig, emitDefaultHeadersMerge, emitHeaders, emitQueryParams, emitResponseTypeOption, emitServiceDecorator, emitSignalAwareQueryParams, emitUrlConstruction, emitUrlExpression, escapeString, extractPaths, generateFromConfig, generateParseRequestTypeParams, getBaseInterceptorClassName, getBasePathTokenName, getClientContextTokenName, getClientInterceptorFnName, getInterceptorFnsTokenName, getModelTypeName, getRequestBodyType, getResourceClassName, getResponseInfoFromResponse, getResponseType, getResponseTypeFromResponse, getServiceClassName, getTypeScriptType, hasDuplicateFunctionNames, inferResponseTypeFromContentType, isDataTypeInterface, isPrimitiveType, isUrl, joinRequestOptionEntries, kebabCase, listGeneratedFileNames, normalizeSchema, normalizeSpec, nullableType, pascalCase, pascalCaseForEnums, plainParamValue, screamingSnakeCase, signalAwareParamValue, validateGeneratorConfig, validateInput };
875
+ export { BASE_INTERCEPTOR_HEADER_COMMENT, CONTENT_TYPES, ConfigValidationError, type EnumValueObject, type GenerationPhase, type GenerationResult, type GeneratorConfig, type GetMethodGenerationContext, HTTP_RESOURCE_GENERATOR_HEADER_COMMENT, type HeadersEmitOptions, type IPluginGenerator, type IPluginGeneratorClass, MAIN_INDEX_GENERATOR_HEADER_COMMENT, type MethodGenOptions, type MethodGenerationContext, type NameDecoration, type NamingOptions, NgOpenApiError, type NgOpenapiClientConfig, type NormalizedOperation, type NormalizedSpec, type OpenApiSecurityScheme, PROVIDER_GENERATOR_HEADER_COMMENT, type Parameter, type PathInfo, type PluginGeneratorContext, REQUEST_PARAMS_GENERATOR_HEADER_COMMENT, type Reporter, type RequestBody, type ResponseKind, type ResponseTypeInfo, SERVICE_GENERATOR_HEADER_COMMENT, SERVICE_INDEX_GENERATOR_HEADER_COMMENT, type ServiceDecoratorEmit, type ServiceDecoratorEmitOptions, SpecLoadError, SpecParseError, type SpecVersion, type SwaggerDefinition, SwaggerParser, type SwaggerResponse, type SwaggerSpec, TYPE_GENERATOR_HEADER_COMMENT, type TypeGenOptions, type TypeMappingConfig, type TypeSchema, ZOD_PLUGIN_GENERATOR_HEADER_COMMENT, ZOD_PLUGIN_INDEX_GENERATOR_HEADER_COMMENT, camelCase, defineConfig, emitDefaultHeadersMerge, emitHeaders, emitQueryParams, emitResponseTypeOption, emitServiceDecorator, emitSignalAwareQueryParams, emitUrlConstruction, emitUrlExpression, escapeString, extractPaths, generateFromConfig, generateParseRequestTypeParams, getBasePathTokenName, getClientContextTokenName, getInterceptorsTokenName, getModelTypeName, getRequestBodyType, getResourceClassName, getResponseInfoFromResponse, getResponseType, getResponseTypeFromResponse, getServiceClassName, getTypeScriptType, hasDuplicateFunctionNames, inferResponseTypeFromContentType, isDataTypeInterface, isPrimitiveType, isUrl, joinRequestOptionEntries, kebabCase, listGeneratedFileNames, normalizeSchema, normalizeSpec, nullableType, pascalCase, pascalCaseForEnums, plainParamValue, screamingSnakeCase, signalAwareParamValue, validateGeneratorConfig, validateInput };
package/index.js CHANGED
@@ -106,11 +106,9 @@ __export(index_exports, {
106
106
  extractPaths: () => extractPaths,
107
107
  generateFromConfig: () => generateFromConfig,
108
108
  generateParseRequestTypeParams: () => generateParseRequestTypeParams,
109
- getBaseInterceptorClassName: () => getBaseInterceptorClassName,
110
109
  getBasePathTokenName: () => getBasePathTokenName,
111
110
  getClientContextTokenName: () => getClientContextTokenName,
112
- getClientInterceptorFnName: () => getClientInterceptorFnName,
113
- getInterceptorFnsTokenName: () => getInterceptorFnsTokenName,
111
+ getInterceptorsTokenName: () => getInterceptorsTokenName,
114
112
  getModelTypeName: () => getModelTypeName,
115
113
  getRequestBodyType: () => getRequestBodyType,
116
114
  getResourceClassName: () => getResourceClassName,
@@ -389,14 +387,6 @@ function getResourceClassName(controllerName, naming) {
389
387
  return decorate(pascalCase(controllerName), "Resource", naming);
390
388
  }
391
389
  __name(getResourceClassName, "getResourceClassName");
392
- function getBaseInterceptorClassName(clientName = "default") {
393
- return `${clientName.charAt(0).toUpperCase()}${clientName.slice(1)}BaseInterceptor`;
394
- }
395
- __name(getBaseInterceptorClassName, "getBaseInterceptorClassName");
396
- function getClientInterceptorFnName(clientName = "default") {
397
- return `${clientName.charAt(0).toLowerCase()}${clientName.slice(1)}ClientInterceptor`;
398
- }
399
- __name(getClientInterceptorFnName, "getClientInterceptorFnName");
400
390
  function getModelTypeName(rawName, naming) {
401
391
  return decorate(pascalCaseForEnums(rawName), "", naming);
402
392
  }
@@ -1025,11 +1015,11 @@ function getBasePathTokenName(clientName = "default") {
1025
1015
  return `BASE_PATH_${clientSuffix}`;
1026
1016
  }
1027
1017
  __name(getBasePathTokenName, "getBasePathTokenName");
1028
- function getInterceptorFnsTokenName(clientName = "default") {
1018
+ function getInterceptorsTokenName(clientName = "default") {
1029
1019
  const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1030
- return `HTTP_INTERCEPTOR_FNS_${clientSuffix}`;
1020
+ return `HTTP_INTERCEPTORS_${clientSuffix}`;
1031
1021
  }
1032
- __name(getInterceptorFnsTokenName, "getInterceptorFnsTokenName");
1022
+ __name(getInterceptorsTokenName, "getInterceptorsTokenName");
1033
1023
 
1034
1024
  // ../shared/src/utils/functions/duplicate-function-name.ts
1035
1025
  function hasDuplicateFunctionNames(arr) {
@@ -1836,15 +1826,15 @@ var _TokenGenerator = class _TokenGenerator {
1836
1826
  },
1837
1827
  {
1838
1828
  namedImports: [
1839
- "HttpInterceptorFn",
1829
+ "HttpInterceptor",
1840
1830
  "HttpContextToken"
1841
1831
  ],
1842
1832
  moduleSpecifier: "@angular/common/http"
1843
1833
  }
1844
1834
  ]);
1845
- const basePathTokenName = getBasePathTokenName(this.clientName);
1846
- const interceptorFnsTokenName = getInterceptorFnsTokenName(this.clientName);
1847
- const clientContextTokenName = getClientContextTokenName(this.clientName);
1835
+ const basePathTokenName = this.getBasePathTokenName();
1836
+ const interceptorsTokenName = this.getInterceptorsTokenName();
1837
+ const clientContextTokenName = this.getClientContextTokenName();
1848
1838
  sourceFile.addVariableStatement({
1849
1839
  isExported: true,
1850
1840
  declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
@@ -1867,16 +1857,15 @@ var _TokenGenerator = class _TokenGenerator {
1867
1857
  declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
1868
1858
  declarations: [
1869
1859
  {
1870
- name: interceptorFnsTokenName,
1871
- initializer: `new InjectionToken<HttpInterceptorFn[]>('${interceptorFnsTokenName}', {
1860
+ name: interceptorsTokenName,
1861
+ initializer: `new InjectionToken<HttpInterceptor[]>('${interceptorsTokenName}', {
1872
1862
  providedIn: 'root',
1873
1863
  factory: () => [], // Default empty array
1874
1864
  })`
1875
1865
  }
1876
1866
  ],
1877
1867
  leadingTrivia: `/**
1878
- * Injection token carrying the ${this.clientName} client's scoped interceptor chain,
1879
- * normalized to functional interceptors. Populated by the client's provide function.
1868
+ * Injection token for the ${this.clientName} client HTTP interceptor instances
1880
1869
  */
1881
1870
  `
1882
1871
  });
@@ -1927,6 +1916,18 @@ var _TokenGenerator = class _TokenGenerator {
1927
1916
  sourceFile.formatText();
1928
1917
  sourceFile.saveSync();
1929
1918
  }
1919
+ getBasePathTokenName() {
1920
+ const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1921
+ return `BASE_PATH_${clientSuffix}`;
1922
+ }
1923
+ getInterceptorsTokenName() {
1924
+ const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1925
+ return `HTTP_INTERCEPTORS_${clientSuffix}`;
1926
+ }
1927
+ getClientContextTokenName() {
1928
+ const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1929
+ return `CLIENT_CONTEXT_TOKEN_${clientSuffix}`;
1930
+ }
1930
1931
  };
1931
1932
  __name(_TokenGenerator, "TokenGenerator");
1932
1933
  var TokenGenerator = _TokenGenerator;
@@ -2090,7 +2091,6 @@ var _DateTransformerGenerator = class _DateTransformerGenerator {
2090
2091
  "HttpEvent",
2091
2092
  "HttpHandler",
2092
2093
  "HttpInterceptor",
2093
- "HttpInterceptorFn",
2094
2094
  "HttpRequest",
2095
2095
  "HttpResponse"
2096
2096
  ],
@@ -2162,58 +2162,6 @@ var _DateTransformerGenerator = class _DateTransformerGenerator {
2162
2162
  }
2163
2163
 
2164
2164
  return obj;`
2165
- });
2166
- sourceFile.addFunction({
2167
- name: "transformDateResponse",
2168
- parameters: [
2169
- {
2170
- name: "event",
2171
- type: "HttpEvent<any>"
2172
- },
2173
- {
2174
- name: "dateRegex",
2175
- type: "RegExp"
2176
- }
2177
- ],
2178
- returnType: "HttpEvent<any>",
2179
- statements: `
2180
- if (event instanceof HttpResponse && event.body) {
2181
- return event.clone({ body: transformDates(event.body, dateRegex) });
2182
- }
2183
- return event;`
2184
- });
2185
- sourceFile.addFunction({
2186
- name: "dateInterceptorWithRegex",
2187
- isExported: true,
2188
- docs: [
2189
- "Builds a functional date interceptor for `provideHttpClient(withInterceptors([...]))`.\n@param dateRegex Optional override for the pattern used to detect ISO date strings."
2190
- ],
2191
- parameters: [
2192
- {
2193
- name: "dateRegex",
2194
- type: "RegExp",
2195
- initializer: "ISO_DATE_REGEX"
2196
- }
2197
- ],
2198
- returnType: "HttpInterceptorFn",
2199
- statements: `
2200
- return (req, next) => next(req).pipe(map((event) => transformDateResponse(event, dateRegex)));`
2201
- });
2202
- sourceFile.addVariableStatement({
2203
- isExported: true,
2204
- declarationKind: import_ts_morph6.VariableDeclarationKind.Const,
2205
- declarations: [
2206
- {
2207
- name: "dateInterceptor",
2208
- type: "HttpInterceptorFn",
2209
- initializer: "dateInterceptorWithRegex()"
2210
- }
2211
- ],
2212
- leadingTrivia: `/**
2213
- * Functional date interceptor using the default ISO_DATE_REGEX.
2214
- * Use dateInterceptorWithRegex(...) to customize the pattern.
2215
- */
2216
- `
2217
2165
  });
2218
2166
  sourceFile.addClass({
2219
2167
  name: "DateInterceptor",
@@ -2258,7 +2206,14 @@ var _DateTransformerGenerator = class _DateTransformerGenerator {
2258
2206
  ],
2259
2207
  returnType: "Observable<HttpEvent<any>>",
2260
2208
  statements: `
2261
- return next.handle(req).pipe(map((event) => transformDateResponse(event, this.dateRegex)));`
2209
+ return next.handle(req).pipe(
2210
+ map(event => {
2211
+ if (event instanceof HttpResponse && event.body) {
2212
+ return event.clone({ body: transformDates(event.body, this.dateRegex) });
2213
+ }
2214
+ return event;
2215
+ })
2216
+ );`
2262
2217
  }
2263
2218
  ]
2264
2219
  });
@@ -2337,14 +2292,13 @@ var _ProviderGenerator = class _ProviderGenerator {
2337
2292
  });
2338
2293
  sourceFile.insertText(0, PROVIDER_GENERATOR_HEADER_COMMENT);
2339
2294
  const basePathTokenName = getBasePathTokenName(this.clientName);
2340
- const interceptorFnsTokenName = getInterceptorFnsTokenName(this.clientName);
2341
- const baseInterceptorClassName = getBaseInterceptorClassName(this.clientName);
2295
+ const interceptorsTokenName = getInterceptorsTokenName(this.clientName);
2296
+ const baseInterceptorClassName = `${this.capitalizeFirst(this.clientName)}BaseInterceptor`;
2342
2297
  sourceFile.addImportDeclarations([
2343
2298
  {
2344
2299
  namedImports: [
2345
2300
  "EnvironmentProviders",
2346
2301
  "Provider",
2347
- "inject",
2348
2302
  "makeEnvironmentProviders"
2349
2303
  ],
2350
2304
  moduleSpecifier: "@angular/core"
@@ -2352,15 +2306,14 @@ var _ProviderGenerator = class _ProviderGenerator {
2352
2306
  {
2353
2307
  namedImports: [
2354
2308
  "HTTP_INTERCEPTORS",
2355
- "HttpInterceptor",
2356
- "HttpInterceptorFn"
2309
+ "HttpInterceptor"
2357
2310
  ],
2358
2311
  moduleSpecifier: "@angular/common/http"
2359
2312
  },
2360
2313
  {
2361
2314
  namedImports: [
2362
2315
  basePathTokenName,
2363
- interceptorFnsTokenName
2316
+ interceptorsTokenName
2364
2317
  ],
2365
2318
  moduleSpecifier: "./tokens"
2366
2319
  },
@@ -2374,7 +2327,7 @@ var _ProviderGenerator = class _ProviderGenerator {
2374
2327
  if (this.config.options.dateType === "Date") {
2375
2328
  sourceFile.addImportDeclaration({
2376
2329
  namedImports: [
2377
- "dateInterceptorWithRegex"
2330
+ "DateInterceptor"
2378
2331
  ],
2379
2332
  moduleSpecifier: "./utils/date-transformer"
2380
2333
  });
@@ -2388,50 +2341,24 @@ var _ProviderGenerator = class _ProviderGenerator {
2388
2341
  ]
2389
2342
  },
2390
2343
  {
2391
- name: "interceptors",
2392
- type: "(new (...args: any[]) => HttpInterceptor)[]",
2393
- hasQuestionToken: true,
2394
- docs: [
2395
- "Class-based HTTP interceptors to apply to this client.",
2396
- "Classes are resolved through DI when provided; classes that are not",
2397
- "provided anywhere are instantiated without constructor arguments, so",
2398
- "classes with required dependencies must be registered as providers."
2399
- ]
2400
- },
2401
- {
2402
- name: "interceptorFns",
2403
- type: "HttpInterceptorFn[]",
2344
+ name: "enableDateTransform",
2345
+ type: "boolean",
2404
2346
  hasQuestionToken: true,
2405
2347
  docs: [
2406
- "Functional HTTP interceptors to apply to this client. Run after class-based interceptors."
2348
+ "Enable automatic date transformation (default: true)"
2407
2349
  ]
2408
2350
  },
2409
2351
  {
2410
- name: "registerDiInterceptor",
2411
- type: "boolean",
2352
+ name: "interceptors",
2353
+ type: "(new (...args: HttpInterceptor[]) => HttpInterceptor)[]",
2412
2354
  hasQuestionToken: true,
2413
2355
  docs: [
2414
- "Register the class-based interceptor on HTTP_INTERCEPTORS (default: true).",
2415
- "Set to false when your app needs withInterceptorsFromDi() for its own",
2416
- "interceptors but this client's chain is registered functionally via",
2417
- "withInterceptors([...]) \u2014 otherwise the chain would run twice.",
2418
- "",
2419
- "WARNING: with false you MUST register the functional interceptor yourself",
2420
- "(provideHttpClient(withInterceptors([...]))); if neither variant is",
2421
- "registered, this client's entire chain (date transform, interceptors,",
2422
- "interceptorFns) silently does nothing."
2356
+ "Array of HTTP interceptor classes to apply to this client"
2423
2357
  ]
2424
2358
  }
2425
2359
  ];
2426
2360
  if (this.config.options.dateType === "Date") {
2427
2361
  configProperties.push({
2428
- name: "enableDateTransform",
2429
- type: "boolean",
2430
- hasQuestionToken: true,
2431
- docs: [
2432
- "Enable automatic date transformation (default: true)"
2433
- ]
2434
- }, {
2435
2362
  name: "dateTransformRegex",
2436
2363
  type: "RegExp",
2437
2364
  hasQuestionToken: true,
@@ -2449,25 +2376,14 @@ var _ProviderGenerator = class _ProviderGenerator {
2449
2376
  ],
2450
2377
  properties: configProperties
2451
2378
  });
2452
- this.addMainProviderFunction(sourceFile, basePathTokenName, interceptorFnsTokenName, baseInterceptorClassName);
2379
+ this.addMainProviderFunction(sourceFile, basePathTokenName, interceptorsTokenName, baseInterceptorClassName);
2453
2380
  sourceFile.formatText();
2454
2381
  sourceFile.saveSync();
2455
2382
  }
2456
- addMainProviderFunction(sourceFile, basePathTokenName, interceptorFnsTokenName, baseInterceptorClassName) {
2383
+ addMainProviderFunction(sourceFile, basePathTokenName, interceptorsTokenName, baseInterceptorClassName) {
2457
2384
  const hasDateInterceptor = this.config.options.dateType === "Date";
2458
2385
  const functionName = `provide${this.capitalizeFirst(this.clientName)}Client`;
2459
2386
  const configTypeName = `${this.capitalizeFirst(this.clientName)}Config`;
2460
- const interceptorFnName = getClientInterceptorFnName(this.clientName);
2461
- const dateInterceptorBlock = hasDateInterceptor ? `
2462
- // Date interceptor first: it runs first on requests (a no-op) and, being
2463
- // outermost, its response transform applies last \u2014 the other client
2464
- // interceptors see the raw body; only the service sees Date instances
2465
- if (config.enableDateTransform !== false) {
2466
- interceptorFns.push(dateInterceptorWithRegex(config.dateTransformRegex));
2467
- }
2468
- ` : `
2469
- // Date transformation not available (dateType: 'string' was used in generation)
2470
- `;
2471
2387
  const functionBody = `
2472
2388
  const providers: Provider[] = [
2473
2389
  // Base path token for this client
@@ -2475,36 +2391,38 @@ const providers: Provider[] = [
2475
2391
  provide: ${basePathTokenName},
2476
2392
  useValue: config.basePath
2477
2393
  },
2478
- // This client's interceptor chain, normalized to functional interceptors
2394
+ // Base interceptor that handles client-specific interceptors
2479
2395
  {
2480
- provide: ${interceptorFnsTokenName},
2481
- useFactory: (): HttpInterceptorFn[] => {
2482
- const interceptorFns: HttpInterceptorFn[] = [];
2483
- ${dateInterceptorBlock}
2484
- // Class-based interceptors are resolved through DI when provided,
2485
- // otherwise instantiated directly, and adapted to functional form
2486
- for (const interceptorClass of config.interceptors ?? []) {
2487
- const instance = inject(interceptorClass, { optional: true }) ?? new interceptorClass();
2488
- interceptorFns.push((req, next) => instance.intercept(req, { handle: next }));
2489
- }
2490
-
2491
- interceptorFns.push(...(config.interceptorFns ?? []));
2492
-
2493
- return interceptorFns;
2494
- }
2396
+ provide: HTTP_INTERCEPTORS,
2397
+ useClass: ${baseInterceptorClassName},
2398
+ multi: true
2495
2399
  }
2496
2400
  ];
2497
2401
 
2498
- // Class-based registration of the scoped chain; only active together with
2499
- // withInterceptorsFromDi(). Disable it (registerDiInterceptor: false) when the
2500
- // app uses withInterceptorsFromDi() for its own interceptors but this client's
2501
- // chain is registered via withInterceptors([${interceptorFnName}]) \u2014
2502
- // otherwise the chain would run twice.
2503
- if (config.registerDiInterceptor !== false) {
2402
+ // Add client-specific interceptor instances
2403
+ if (config.interceptors && config.interceptors.length > 0) {
2404
+ const interceptorInstances = config.interceptors.map(InterceptorClass => new InterceptorClass());
2405
+
2406
+ ${hasDateInterceptor ? `// Add date interceptor if enabled (default: true)
2407
+ if (config.enableDateTransform !== false) {
2408
+ interceptorInstances.unshift(new DateInterceptor(config.dateTransformRegex));
2409
+ }` : `// Date transformation not available (dateType: 'string' was used in generation)`}
2410
+
2504
2411
  providers.push({
2505
- provide: HTTP_INTERCEPTORS,
2506
- useClass: ${baseInterceptorClassName},
2507
- multi: true
2412
+ provide: ${interceptorsTokenName},
2413
+ useValue: interceptorInstances
2414
+ });
2415
+ } ${hasDateInterceptor ? `else if (config.enableDateTransform !== false) {
2416
+ // Only date interceptor enabled
2417
+ providers.push({
2418
+ provide: ${interceptorsTokenName},
2419
+ useValue: [new DateInterceptor(config.dateTransformRegex)]
2420
+ });
2421
+ }` : ``} else {
2422
+ // No interceptors
2423
+ providers.push({
2424
+ provide: ${interceptorsTokenName},
2425
+ useValue: []
2508
2426
  });
2509
2427
  }
2510
2428
 
@@ -2518,17 +2436,13 @@ return makeEnvironmentProviders(providers);`;
2518
2436
  "@example",
2519
2437
  "```typescript",
2520
2438
  "// In your app.config.ts",
2521
- "import { provideHttpClient, withInterceptors } from '@angular/common/http';",
2522
2439
  `import { ${functionName} } from './api/providers';`,
2523
- `import { ${interceptorFnName} } from './api/utils/base-interceptor';`,
2524
2440
  "",
2525
2441
  "export const appConfig: ApplicationConfig = {",
2526
2442
  " providers: [",
2527
- ` provideHttpClient(withInterceptors([${interceptorFnName}])),`,
2528
2443
  ` ${functionName}({`,
2529
2444
  " basePath: 'https://api.example.com',",
2530
- " interceptors: [AuthInterceptor], // Classes, not instances",
2531
- " interceptorFns: [loggingInterceptor] // Functional interceptors",
2445
+ " interceptors: [AuthInterceptor, LoggingInterceptor] // Classes, not instances",
2532
2446
  " }),",
2533
2447
  " // other providers...",
2534
2448
  " ]",
@@ -2588,28 +2502,23 @@ var _BaseInterceptorGenerator = class _BaseInterceptorGenerator {
2588
2502
  overwrite: true
2589
2503
  });
2590
2504
  sourceFile.insertText(0, BASE_INTERCEPTOR_HEADER_COMMENT(__privateGet(this, _clientName)));
2591
- const interceptorFnsTokenName = getInterceptorFnsTokenName(__privateGet(this, _clientName));
2505
+ const interceptorsTokenName = getInterceptorsTokenName(__privateGet(this, _clientName));
2592
2506
  const clientContextTokenName = getClientContextTokenName(__privateGet(this, _clientName));
2593
- const interceptorFnName = getClientInterceptorFnName(__privateGet(this, _clientName));
2594
2507
  sourceFile.addImportDeclarations([
2595
2508
  {
2596
2509
  namedImports: [
2597
2510
  "HttpContextToken",
2598
2511
  "HttpEvent",
2599
2512
  "HttpHandler",
2600
- "HttpHandlerFn",
2601
2513
  "HttpInterceptor",
2602
- "HttpInterceptorFn",
2603
2514
  "HttpRequest"
2604
2515
  ],
2605
2516
  moduleSpecifier: "@angular/common/http"
2606
2517
  },
2607
2518
  {
2608
2519
  namedImports: [
2609
- "EnvironmentInjector",
2610
2520
  "inject",
2611
- "Injectable",
2612
- "runInInjectionContext"
2521
+ "Injectable"
2613
2522
  ],
2614
2523
  moduleSpecifier: "@angular/core"
2615
2524
  },
@@ -2622,80 +2531,14 @@ var _BaseInterceptorGenerator = class _BaseInterceptorGenerator {
2622
2531
  {
2623
2532
  namedImports: [
2624
2533
  clientContextTokenName,
2625
- interceptorFnsTokenName
2534
+ interceptorsTokenName
2626
2535
  ],
2627
2536
  moduleSpecifier: "../tokens"
2628
2537
  }
2629
2538
  ]);
2630
- sourceFile.addFunction({
2631
- name: "interceptClientRequest",
2632
- parameters: [
2633
- {
2634
- name: "req",
2635
- type: "HttpRequest<any>"
2636
- },
2637
- {
2638
- name: "next",
2639
- type: "HttpHandlerFn"
2640
- },
2641
- {
2642
- name: "interceptorFns",
2643
- type: "HttpInterceptorFn[]"
2644
- },
2645
- {
2646
- name: "clientContextToken",
2647
- type: "HttpContextToken<string>"
2648
- },
2649
- {
2650
- name: "injector",
2651
- type: "EnvironmentInjector"
2652
- }
2653
- ],
2654
- returnType: "Observable<HttpEvent<any>>",
2655
- statements: `
2656
- // Check if this request belongs to this client using HttpContext
2657
- if (!req.context.has(clientContextToken)) {
2658
- // This request doesn't belong to this client, pass it through
2659
- return next(req);
2660
- }
2661
-
2662
- // Compose right-to-left so the interceptors run in array order. Each fn is
2663
- // invoked inside an injection context, mirroring Angular's own interceptor
2664
- // chain, so client interceptors can use inject().
2665
- const chain = interceptorFns.reduceRight<HttpHandlerFn>(
2666
- (nextFn, interceptorFn) => (request) => runInInjectionContext(injector, () => interceptorFn(request, nextFn)),
2667
- next
2668
- );
2669
-
2670
- return chain(req);`
2671
- });
2672
- sourceFile.addVariableStatement({
2673
- isExported: true,
2674
- declarationKind: import_ts_morph7.VariableDeclarationKind.Const,
2675
- declarations: [
2676
- {
2677
- name: interceptorFnName,
2678
- type: "HttpInterceptorFn",
2679
- initializer: `(req, next) =>
2680
- interceptClientRequest(req, next, inject(${interceptorFnsTokenName}), ${clientContextTokenName}, inject(EnvironmentInjector))`
2681
- }
2682
- ],
2683
- leadingTrivia: `/**
2684
- * Functional interceptor running the ${__privateGet(this, _clientName)} client's scoped interceptor chain.
2685
- * Register it once with \`provideHttpClient(withInterceptors([${interceptorFnName}]))\`.
2686
- * If your app also uses \`withInterceptorsFromDi()\`, pass \`registerDiInterceptor: false\`
2687
- * to the client's provide function \u2014 otherwise the class-based registration it adds
2688
- * would run the same chain twice.
2689
- */
2690
- `
2691
- });
2692
2539
  sourceFile.addClass({
2693
- name: getBaseInterceptorClassName(__privateGet(this, _clientName)),
2540
+ name: `${this.capitalizeFirst(__privateGet(this, _clientName))}BaseInterceptor`,
2694
2541
  isExported: true,
2695
- docs: [
2696
- `Class-based equivalent of \`${interceptorFnName}\` for DI registration via
2697
- \`withInterceptorsFromDi()\`. Register exactly one of the two variants.`
2698
- ],
2699
2542
  decorators: [
2700
2543
  {
2701
2544
  name: "Injectable",
@@ -2707,11 +2550,11 @@ var _BaseInterceptorGenerator = class _BaseInterceptorGenerator {
2707
2550
  ],
2708
2551
  properties: [
2709
2552
  {
2710
- name: "interceptorFns",
2711
- type: "HttpInterceptorFn[]",
2553
+ name: "httpInterceptors",
2554
+ type: "HttpInterceptor[]",
2712
2555
  scope: import_ts_morph7.Scope.Private,
2713
2556
  isReadonly: true,
2714
- initializer: `inject(${interceptorFnsTokenName})`
2557
+ initializer: `inject(${interceptorsTokenName})`
2715
2558
  },
2716
2559
  {
2717
2560
  name: "clientContextToken",
@@ -2719,13 +2562,6 @@ var _BaseInterceptorGenerator = class _BaseInterceptorGenerator {
2719
2562
  scope: import_ts_morph7.Scope.Private,
2720
2563
  isReadonly: true,
2721
2564
  initializer: clientContextTokenName
2722
- },
2723
- {
2724
- name: "injector",
2725
- type: "EnvironmentInjector",
2726
- scope: import_ts_morph7.Scope.Private,
2727
- isReadonly: true,
2728
- initializer: "inject(EnvironmentInjector)"
2729
2565
  }
2730
2566
  ],
2731
2567
  methods: [
@@ -2743,13 +2579,32 @@ var _BaseInterceptorGenerator = class _BaseInterceptorGenerator {
2743
2579
  ],
2744
2580
  returnType: "Observable<HttpEvent<any>>",
2745
2581
  statements: `
2746
- return interceptClientRequest(req, (request) => next.handle(request), this.interceptorFns, this.clientContextToken, this.injector);`
2582
+ // Check if this request belongs to this client using HttpContext
2583
+ if (!req.context.has(this.clientContextToken)) {
2584
+ // This request doesn't belong to this client, pass it through
2585
+ return next.handle(req);
2586
+ }
2587
+
2588
+ // Apply client-specific interceptors in reverse order
2589
+ let handler = next;
2590
+
2591
+ handler = this.httpInterceptors.reduceRight(
2592
+ (next, interceptor) => ({
2593
+ handle: (request: HttpRequest<any>) => interceptor.intercept(request, next)
2594
+ }),
2595
+ handler
2596
+ );
2597
+
2598
+ return handler.handle(req);`
2747
2599
  }
2748
2600
  ]
2749
2601
  });
2750
2602
  sourceFile.formatText();
2751
2603
  sourceFile.saveSync();
2752
2604
  }
2605
+ capitalizeFirst(str) {
2606
+ return str.charAt(0).toUpperCase() + str.slice(1);
2607
+ }
2753
2608
  };
2754
2609
  _project = new WeakMap();
2755
2610
  _clientName = new WeakMap();
@@ -4037,11 +3892,9 @@ __name(generateFromConfig, "generateFromConfig");
4037
3892
  extractPaths,
4038
3893
  generateFromConfig,
4039
3894
  generateParseRequestTypeParams,
4040
- getBaseInterceptorClassName,
4041
3895
  getBasePathTokenName,
4042
3896
  getClientContextTokenName,
4043
- getClientInterceptorFnName,
4044
- getInterceptorFnsTokenName,
3897
+ getInterceptorsTokenName,
4045
3898
  getModelTypeName,
4046
3899
  getRequestBodyType,
4047
3900
  getResourceClassName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-openapi",
3
- "version": "0.3.1-pr-114-feat-functional-client-interceptors-52e79cb.0",
3
+ "version": "0.3.1",
4
4
  "description": "Generate Angular services and TypeScript types from OpenAPI/Swagger specifications",
5
5
  "keywords": [
6
6
  "ng-openapi",