ng-openapi 0.0.21 → 0.0.23

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/cli.cjs +270 -102
  2. package/index.d.ts +2 -0
  3. package/index.js +157 -92
  4. package/package.json +1 -1
package/cli.cjs CHANGED
@@ -7,6 +7,10 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
10
14
  var __copyProps = (to, from, except, desc) => {
11
15
  if (from && typeof from === "object" || typeof from === "function") {
12
16
  for (let key of __getOwnPropNames(from))
@@ -23,8 +27,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
27
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
28
  mod
25
29
  ));
30
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
26
31
 
27
32
  // src/lib/cli.ts
33
+ var cli_exports = {};
34
+ __export(cli_exports, {
35
+ generateFromOptions: () => generateFromOptions
36
+ });
37
+ module.exports = __toCommonJS(cli_exports);
28
38
  var import_commander = require("commander");
29
39
  var path8 = __toESM(require("path"));
30
40
  var fs4 = __toESM(require("fs"));
@@ -402,8 +412,10 @@ var TokenGenerator = class {
402
412
  __name(this, "TokenGenerator");
403
413
  }
404
414
  project;
405
- constructor(project) {
415
+ config;
416
+ constructor(project, config) {
406
417
  this.project = project;
418
+ this.config = config;
407
419
  }
408
420
  generate(outputDir) {
409
421
  const tokensDir = path.join(outputDir, "tokens");
@@ -417,23 +429,55 @@ var TokenGenerator = class {
417
429
  ],
418
430
  moduleSpecifier: "@angular/core"
419
431
  });
432
+ const clientName = this.config.clientName || "DEFAULT";
433
+ const upperCaseClientName = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
420
434
  sourceFile.addVariableStatement({
421
435
  isExported: true,
422
436
  declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
423
437
  declarations: [
424
438
  {
425
- name: "BASE_PATH",
426
- initializer: `new InjectionToken<string>('BASE_PATH', {
439
+ name: `${upperCaseClientName}_BASE_PATH`,
440
+ initializer: `new InjectionToken<string>('${upperCaseClientName}_BASE_PATH', {
427
441
  providedIn: 'root',
428
- factory: () => '/api', // Default fallback
442
+ factory: () => '/api',
429
443
  })`
430
444
  }
431
445
  ],
432
446
  leadingTrivia: `/**
433
- * Injection token for the base API path
447
+ * Base path token for ${clientName} client
448
+ */
449
+ `
450
+ });
451
+ sourceFile.addVariableStatement({
452
+ isExported: true,
453
+ declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
454
+ declarations: [
455
+ {
456
+ name: `${upperCaseClientName}_HTTP_CLIENT`,
457
+ initializer: `new InjectionToken<HttpClient>('${upperCaseClientName}_HTTP_CLIENT')`
458
+ }
459
+ ],
460
+ leadingTrivia: `/**
461
+ * HTTP client token for ${clientName} client
434
462
  */
435
463
  `
436
464
  });
465
+ if (!this.config.clientName) {
466
+ sourceFile.addVariableStatement({
467
+ isExported: true,
468
+ declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
469
+ declarations: [
470
+ {
471
+ name: "BASE_PATH",
472
+ initializer: `${upperCaseClientName}_BASE_PATH`
473
+ }
474
+ ],
475
+ leadingTrivia: `/**
476
+ * @deprecated Use ${upperCaseClientName}_BASE_PATH instead
477
+ */
478
+ `
479
+ });
480
+ }
437
481
  sourceFile.saveSync();
438
482
  }
439
483
  };
@@ -799,6 +843,9 @@ function getTypeScriptType(schemaOrType, config, formatOrNullable, isNullable, c
799
843
  }
800
844
  switch (schema.type) {
801
845
  case "string":
846
+ if (schema.enum) {
847
+ return schema.enum.map((value) => typeof value === "string" ? `'${escapeString(value)}'` : String(value)).join(" | ");
848
+ }
802
849
  if (schema.format === "date" || schema.format === "date-time") {
803
850
  const dateType = config.options.dateType === "Date" ? "Date" : "string";
804
851
  return nullableType(dateType, nullable);
@@ -830,6 +877,10 @@ function nullableType(type, isNullable) {
830
877
  return type + (isNullable ? " | null" : "");
831
878
  }
832
879
  __name(nullableType, "nullableType");
880
+ function escapeString(str) {
881
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
882
+ }
883
+ __name(escapeString, "escapeString");
833
884
 
834
885
  // src/lib/generators/service/service-method/service-method-body.generator.ts
835
886
  var ServiceMethodBodyGenerator = class {
@@ -1695,6 +1746,8 @@ var ServiceGenerator = class {
1695
1746
  });
1696
1747
  }
1697
1748
  addImports(sourceFile, usedTypes) {
1749
+ const clientName = this.config.clientName || "DEFAULT";
1750
+ const upperCaseClientName = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1698
1751
  sourceFile.addImportDeclarations([
1699
1752
  {
1700
1753
  namedImports: [
@@ -1722,11 +1775,15 @@ var ServiceGenerator = class {
1722
1775
  },
1723
1776
  {
1724
1777
  namedImports: [
1725
- "BASE_PATH"
1778
+ `${upperCaseClientName}_BASE_PATH`,
1779
+ `${upperCaseClientName}_HTTP_CLIENT`
1726
1780
  ],
1727
1781
  moduleSpecifier: "../tokens"
1728
1782
  }
1729
1783
  ]);
1784
+ if (!this.config.clientName) {
1785
+ sourceFile.getImportDeclaration("../tokens")?.addNamedImport("BASE_PATH");
1786
+ }
1730
1787
  if (usedTypes.size > 0) {
1731
1788
  sourceFile.addImportDeclaration({
1732
1789
  namedImports: Array.from(usedTypes).sort(),
@@ -1736,6 +1793,8 @@ var ServiceGenerator = class {
1736
1793
  }
1737
1794
  addServiceClass(sourceFile, controllerName, operations) {
1738
1795
  const className = `${controllerName}Service`;
1796
+ const clientName = this.config.clientName || "DEFAULT";
1797
+ const upperCaseClientName = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1739
1798
  sourceFile.insertText(0, SERVICE_GENERATOR_HEADER_COMMENT(controllerName));
1740
1799
  const serviceClass = sourceFile.addClass({
1741
1800
  name: className,
@@ -1754,14 +1813,14 @@ var ServiceGenerator = class {
1754
1813
  type: "HttpClient",
1755
1814
  scope: import_ts_morph3.Scope.Private,
1756
1815
  isReadonly: true,
1757
- initializer: "inject(HttpClient)"
1816
+ initializer: `inject(${upperCaseClientName}_HTTP_CLIENT, { optional: true }) ?? inject(HttpClient)`
1758
1817
  });
1759
1818
  serviceClass.addProperty({
1760
1819
  name: "basePath",
1761
1820
  type: "string",
1762
1821
  scope: import_ts_morph3.Scope.Private,
1763
1822
  isReadonly: true,
1764
- initializer: "inject(BASE_PATH)"
1823
+ initializer: `inject(${upperCaseClientName}_BASE_PATH)`
1765
1824
  });
1766
1825
  operations.forEach((operation) => {
1767
1826
  this.methodGenerator.addServiceMethod(serviceClass, operation);
@@ -1825,24 +1884,34 @@ var ProviderGenerator = class {
1825
1884
  overwrite: true
1826
1885
  });
1827
1886
  sourceFile.insertText(0, PROVIDER_GENERATOR_HEADER_COMMENT);
1887
+ const clientName = this.config.clientName || "Default";
1888
+ const pascalClientName = this.pascalCase(clientName);
1889
+ const upperCaseClientName = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1828
1890
  sourceFile.addImportDeclarations([
1829
1891
  {
1830
1892
  namedImports: [
1831
1893
  "EnvironmentProviders",
1832
1894
  "Provider",
1833
- "makeEnvironmentProviders"
1895
+ "makeEnvironmentProviders",
1896
+ "Type",
1897
+ "Injector",
1898
+ "inject"
1834
1899
  ],
1835
1900
  moduleSpecifier: "@angular/core"
1836
1901
  },
1837
1902
  {
1838
1903
  namedImports: [
1839
- "HTTP_INTERCEPTORS"
1904
+ "HttpClient",
1905
+ "HttpInterceptor",
1906
+ "HttpHandler",
1907
+ "HttpRequest"
1840
1908
  ],
1841
1909
  moduleSpecifier: "@angular/common/http"
1842
1910
  },
1843
1911
  {
1844
1912
  namedImports: [
1845
- "BASE_PATH"
1913
+ `${upperCaseClientName}_BASE_PATH`,
1914
+ `${upperCaseClientName}_HTTP_CLIENT`
1846
1915
  ],
1847
1916
  moduleSpecifier: "./tokens"
1848
1917
  }
@@ -1856,10 +1925,10 @@ var ProviderGenerator = class {
1856
1925
  });
1857
1926
  }
1858
1927
  sourceFile.addInterface({
1859
- name: "NgOpenapiConfig",
1928
+ name: `${pascalClientName}Config`,
1860
1929
  isExported: true,
1861
1930
  docs: [
1862
- "Configuration options for ng-openapi providers"
1931
+ `Configuration options for ${clientName} API client`
1863
1932
  ],
1864
1933
  properties: [
1865
1934
  {
@@ -1869,6 +1938,14 @@ var ProviderGenerator = class {
1869
1938
  "Base API URL"
1870
1939
  ]
1871
1940
  },
1941
+ {
1942
+ name: "interceptors",
1943
+ type: "Type<HttpInterceptor>[]",
1944
+ hasQuestionToken: true,
1945
+ docs: [
1946
+ "HTTP interceptors to apply to this client's requests"
1947
+ ]
1948
+ },
1872
1949
  {
1873
1950
  name: "enableDateTransform",
1874
1951
  type: "boolean",
@@ -1879,105 +1956,102 @@ var ProviderGenerator = class {
1879
1956
  }
1880
1957
  ]
1881
1958
  });
1882
- this.addMainProviderFunction(sourceFile);
1883
- this.addAsyncProviderFunction(sourceFile);
1959
+ this.addInterceptorChainHelper(sourceFile);
1960
+ this.addClientProviderFunction(sourceFile, pascalClientName, upperCaseClientName);
1884
1961
  sourceFile.saveSync();
1885
1962
  }
1886
- addMainProviderFunction(sourceFile) {
1887
- const hasDateInterceptor = this.config.options.dateType === "Date";
1888
- const functionBody = `
1889
- const providers: Provider[] = [
1890
- // Base path token
1891
- {
1892
- provide: BASE_PATH,
1893
- useValue: config.basePath
1894
- }
1895
- ];
1896
-
1897
- ${hasDateInterceptor ? `// Add date interceptor if enabled (default: true)
1898
- if (config.enableDateTransform !== false) {
1899
- providers.push({
1900
- provide: HTTP_INTERCEPTORS,
1901
- useClass: DateInterceptor,
1902
- multi: true
1903
- });
1904
- }` : `// Date transformation not available (dateType: 'string' was used in generation)`}
1905
-
1906
- return makeEnvironmentProviders(providers);`;
1963
+ addInterceptorChainHelper(sourceFile) {
1907
1964
  sourceFile.addFunction({
1908
- name: "provideNgOpenapi",
1909
- isExported: true,
1965
+ name: "createHttpClientWithInterceptors",
1910
1966
  docs: [
1911
- "Provides all necessary configuration for ng-openapi generated services",
1912
- "",
1913
- "@example",
1914
- "```typescript",
1915
- "// In your app.config.ts",
1916
- "import { provideNgOpenapi } from './api/providers';",
1917
- "",
1918
- "export const appConfig: ApplicationConfig = {",
1919
- " providers: [",
1920
- " provideNgOpenapi({",
1921
- " basePath: 'https://api.example.com'",
1922
- " }),",
1923
- " // other providers...",
1924
- " ]",
1925
- "};",
1926
- "```"
1967
+ "Creates an HttpClient with a custom interceptor chain"
1927
1968
  ],
1928
1969
  parameters: [
1929
1970
  {
1930
- name: "config",
1931
- type: "NgOpenapiConfig"
1971
+ name: "baseClient",
1972
+ type: "HttpClient"
1973
+ },
1974
+ {
1975
+ name: "interceptors",
1976
+ type: "HttpInterceptor[]"
1932
1977
  }
1933
1978
  ],
1934
- returnType: "EnvironmentProviders",
1935
- statements: functionBody
1979
+ returnType: "HttpClient",
1980
+ statements: `
1981
+ if (!interceptors.length) {
1982
+ return baseClient;
1983
+ }
1984
+
1985
+ // Create a custom handler that applies interceptors in sequence
1986
+ let handler = baseClient.handler;
1987
+
1988
+ // Apply interceptors in reverse order (last interceptor wraps the original handler)
1989
+ for (let i = interceptors.length - 1; i >= 0; i--) {
1990
+ const currentHandler = handler;
1991
+ const interceptor = interceptors[i];
1992
+
1993
+ handler = {
1994
+ handle: (req: HttpRequest<any>) => interceptor.intercept(req, currentHandler)
1995
+ };
1996
+ }
1997
+
1998
+ // Return a new HttpClient with the custom handler
1999
+ return new (baseClient.constructor as any)(handler);`
1936
2000
  });
1937
2001
  }
1938
- addAsyncProviderFunction(sourceFile) {
2002
+ addClientProviderFunction(sourceFile, pascalClientName, upperCaseClientName) {
1939
2003
  const hasDateInterceptor = this.config.options.dateType === "Date";
1940
2004
  const functionBody = `
1941
- const providers: Provider[] = [];
1942
-
1943
- // Handle async base path
1944
- if (typeof config.basePath === 'string') {
1945
- providers.push({
1946
- provide: BASE_PATH,
2005
+ const providers: Provider[] = [
2006
+ // Base path token
2007
+ {
2008
+ provide: ${upperCaseClientName}_BASE_PATH,
1947
2009
  useValue: config.basePath
1948
- });
1949
- } else {
1950
- providers.push({
1951
- provide: BASE_PATH,
1952
- useFactory: config.basePath
1953
- });
1954
- }
1955
-
1956
- ${hasDateInterceptor ? `// Add date interceptor if enabled (default: true)
1957
- if (config.enableDateTransform !== false) {
1958
- providers.push({
1959
- provide: HTTP_INTERCEPTORS,
1960
- useClass: DateInterceptor,
1961
- multi: true
1962
- });
1963
- }` : `// Date transformation not available (dateType: 'string' was used in generation)`}
2010
+ },
2011
+
2012
+ // HTTP client with custom interceptors
2013
+ {
2014
+ provide: ${upperCaseClientName}_HTTP_CLIENT,
2015
+ useFactory: (baseClient: HttpClient, injector: Injector) => {
2016
+ const interceptorInstances: HttpInterceptor[] = [];
2017
+
2018
+ // Add custom interceptors
2019
+ if (config.interceptors?.length) {
2020
+ config.interceptors.forEach(interceptorClass => {
2021
+ interceptorInstances.push(injector.get(interceptorClass));
2022
+ });
2023
+ }
2024
+
2025
+ ${hasDateInterceptor ? `
2026
+ // Add date interceptor if enabled (default: true)
2027
+ if (config.enableDateTransform !== false) {
2028
+ interceptorInstances.push(injector.get(DateInterceptor));
2029
+ }` : ""}
2030
+
2031
+ return createHttpClientWithInterceptors(baseClient, interceptorInstances);
2032
+ },
2033
+ deps: [HttpClient, Injector]
2034
+ }
2035
+ ];
1964
2036
 
1965
2037
  return makeEnvironmentProviders(providers);`;
1966
2038
  sourceFile.addFunction({
1967
- name: "provideNgOpenapiAsync",
2039
+ name: `provide${pascalClientName}`,
1968
2040
  isExported: true,
1969
2041
  docs: [
1970
- "Alternative function for cases where you need to handle async configuration",
2042
+ `Provides configuration for ${pascalClientName} API client`,
1971
2043
  "",
1972
2044
  "@example",
1973
2045
  "```typescript",
1974
- "// In your app.config.ts",
1975
- "import { provideNgOpenapiAsync } from './api/providers';",
2046
+ `// In your app.config.ts`,
2047
+ `import { provide${pascalClientName} } from './api/providers';`,
2048
+ `import { AuthInterceptor } from './interceptors/auth.interceptor';`,
1976
2049
  "",
1977
2050
  "export const appConfig: ApplicationConfig = {",
1978
2051
  " providers: [",
1979
- " provideNgOpenapiAsync({",
1980
- " basePath: () => import('./config').then(c => c.apiConfig.baseUrl)",
2052
+ ` provide${pascalClientName}({`,
2053
+ " basePath: 'https://api.example.com',",
2054
+ " interceptors: [AuthInterceptor]",
1981
2055
  " }),",
1982
2056
  " // other providers...",
1983
2057
  " ]",
@@ -1987,16 +2061,16 @@ return makeEnvironmentProviders(providers);`;
1987
2061
  parameters: [
1988
2062
  {
1989
2063
  name: "config",
1990
- type: `{
1991
- basePath: string | (() => Promise<string>);
1992
- enableDateTransform?: boolean;
1993
- }`
2064
+ type: `${pascalClientName}Config`
1994
2065
  }
1995
2066
  ],
1996
2067
  returnType: "EnvironmentProviders",
1997
2068
  statements: functionBody
1998
2069
  });
1999
2070
  }
2071
+ pascalCase(str) {
2072
+ return str.replace(/(?:^|[-_])([a-z])/g, (_, char) => char.toUpperCase());
2073
+ }
2000
2074
  };
2001
2075
 
2002
2076
  // src/lib/core/generator.ts
@@ -2026,7 +2100,7 @@ async function generateFromConfig(config) {
2026
2100
  typeGenerator.generate();
2027
2101
  console.log(`\u2705 TypeScript interfaces generated`);
2028
2102
  if (generateServices) {
2029
- const tokenGenerator = new TokenGenerator(project);
2103
+ const tokenGenerator = new TokenGenerator(project, config);
2030
2104
  tokenGenerator.generate(outputPath);
2031
2105
  if (config.options.dateType === "Date") {
2032
2106
  const dateTransformer = new DateTransformerGenerator(project);
@@ -2059,6 +2133,94 @@ async function generateFromConfig(config) {
2059
2133
  }
2060
2134
  __name(generateFromConfig, "generateFromConfig");
2061
2135
 
2136
+ // package.json
2137
+ var package_default = {
2138
+ name: "ng-openapi",
2139
+ version: "0.0.22",
2140
+ description: "Generate Angular services and TypeScript types from OpenAPI/Swagger specifications",
2141
+ keywords: [
2142
+ "angular",
2143
+ "openapi",
2144
+ "swagger",
2145
+ "codegen",
2146
+ "typescript",
2147
+ "generator",
2148
+ "code-generator",
2149
+ "api",
2150
+ "rest",
2151
+ "http",
2152
+ "cli"
2153
+ ],
2154
+ author: {
2155
+ name: "Tareq Jami",
2156
+ email: "info@jami-it.de",
2157
+ url: "http://tareqjami.de"
2158
+ },
2159
+ license: "MIT",
2160
+ homepage: "https://ng-openapi.dev/",
2161
+ bugs: {
2162
+ url: "https://github.com/ng-openapi/ng-openapi/issues"
2163
+ },
2164
+ repository: {
2165
+ type: "git",
2166
+ url: "git+https://github.com/ng-openapi/ng-openapi.git",
2167
+ directory: "packages/ng-openapi"
2168
+ },
2169
+ funding: {
2170
+ type: "github",
2171
+ url: "https://github.com/sponsors/ng-openapi"
2172
+ },
2173
+ main: "./index.cjs",
2174
+ module: "./index.js",
2175
+ types: "./index.d.ts",
2176
+ bin: {
2177
+ "ng-openapi": "./cli.cjs"
2178
+ },
2179
+ files: [
2180
+ "index.js",
2181
+ "index.cjs",
2182
+ "index.d.ts",
2183
+ "cli.cjs",
2184
+ "lib/**/*.js",
2185
+ "lib/**/*.cjs",
2186
+ "lib/**/*.d.ts",
2187
+ "README.md",
2188
+ "LICENSE",
2189
+ "CHANGELOG.md"
2190
+ ],
2191
+ scripts: {
2192
+ prepublishOnly: "echo 'Build the package using: npm run build:ng-openapi from workspace root'",
2193
+ build: "tsup"
2194
+ },
2195
+ dependencies: {
2196
+ commander: "^14.0.0",
2197
+ "ts-morph": "^26.0.0",
2198
+ "ts-node": "^10.9.2",
2199
+ typescript: "^5.8.3",
2200
+ "@types/swagger-schema-official": "^2.0.25"
2201
+ },
2202
+ peerDependencies: {
2203
+ "@angular/core": ">=15",
2204
+ "@angular/common": ">=15"
2205
+ },
2206
+ peerDependenciesMeta: {
2207
+ "@angular/core": {
2208
+ optional: false
2209
+ },
2210
+ "@angular/common": {
2211
+ optional: false
2212
+ }
2213
+ },
2214
+ engines: {
2215
+ node: ">=18.0.0",
2216
+ npm: ">=8.0.0"
2217
+ },
2218
+ publishConfig: {
2219
+ access: "public",
2220
+ registry: "https://registry.npmjs.org/"
2221
+ }
2222
+ };
2223
+
2062
2224
  // src/lib/cli.ts
2063
2225
  var program = new import_commander.Command();
2064
2226
  async function loadConfigFile(configPath) {
@@ -2085,17 +2247,19 @@ __name(loadConfigFile, "loadConfigFile");
2085
2247
  async function generateFromOptions(options) {
2086
2248
  try {
2087
2249
  if (options.config) {
2088
- const config = await loadConfigFile(options.config);
2089
- await generateFromConfig(config);
2090
- } else if (options.input) {
2091
- const inputPath = path8.resolve(options.input);
2092
- if (!fs4.existsSync(inputPath)) {
2093
- console.error(`Error: Input file not found: ${inputPath}`);
2094
- process.exit(1);
2250
+ const configPaths = Array.isArray(options.config) ? options.config : [
2251
+ options.config
2252
+ ];
2253
+ for (const configPath of configPaths) {
2254
+ const config = await loadConfigFile(configPath);
2255
+ await generateFromConfig(config);
2256
+ console.log(`\u2728 Generated client: ${config.clientName || "default"}`);
2095
2257
  }
2258
+ } else if (options.input) {
2096
2259
  const config = {
2097
- input: inputPath,
2260
+ input: path8.resolve(options.input),
2098
2261
  output: options.output || "./src/generated",
2262
+ clientName: options.clientName,
2099
2263
  options: {
2100
2264
  dateType: options.dateType || "Date",
2101
2265
  enumStyle: "enum",
@@ -2109,14 +2273,14 @@ async function generateFromOptions(options) {
2109
2273
  program.help();
2110
2274
  process.exit(1);
2111
2275
  }
2112
- console.log("\u2728 Generation completed successfully!");
2276
+ console.log("\u2728 All clients generated successfully!");
2113
2277
  } catch (error) {
2114
2278
  console.error("\u274C Generation failed:", error instanceof Error ? error.message : error);
2115
2279
  process.exit(1);
2116
2280
  }
2117
2281
  }
2118
2282
  __name(generateFromOptions, "generateFromOptions");
2119
- program.name("ng-openapi").description("Generate Angular services and types from Swagger/OpenAPI spec").version("0.0.1").option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path to Swagger/OpenAPI specification file").option("-o, --output <path>", "Output directory", "./src/generated").option("--types-only", "Generate only TypeScript interfaces").option("--date-type <type>", "Date type to use (string | Date)", "Date").action(async (options) => {
2283
+ program.name("ng-openapi").description("Generate Angular services and types from Swagger/OpenAPI spec").version(package_default.version).option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path to Swagger/OpenAPI specification file").option("-o, --output <path>", "Output directory", "./src/generated").option("--types-only", "Generate only TypeScript interfaces").option("--date-type <type>", "Date type to use (string | Date)", "Date").action(async (options) => {
2120
2284
  await generateFromOptions(options);
2121
2285
  });
2122
2286
  program.command("generate").alias("gen").description("Generate code from Swagger specification").option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path to Swagger/OpenAPI specification file").option("-o, --output <path>", "Output directory", "./src/generated").option("--types-only", "Generate only TypeScript interfaces").option("--date-type <type>", "Date type to use (string | Date)", "Date").action(async (options) => {
@@ -2131,4 +2295,8 @@ program.on("--help", () => {
2131
2295
  console.log(" $ ng-openapi generate -i ./api.yaml --types-only");
2132
2296
  });
2133
2297
  program.parse();
2298
+ // Annotate the CommonJS export names for ESM import in node:
2299
+ 0 && (module.exports = {
2300
+ generateFromOptions
2301
+ });
2134
2302
  //# sourceMappingURL=cli.cjs.map
package/index.d.ts CHANGED
@@ -21,12 +21,14 @@ interface TypeSchema {
21
21
  $ref?: string;
22
22
  items?: any;
23
23
  nullable?: boolean;
24
+ enum?: Array<string | number>;
24
25
  [key: string]: any;
25
26
  }
26
27
 
27
28
  interface GeneratorConfig {
28
29
  input: string;
29
30
  output: string;
31
+ clientName?: string;
30
32
  options: {
31
33
  dateType: "string" | "Date";
32
34
  enumStyle: "enum" | "union";
package/index.js CHANGED
@@ -441,9 +441,11 @@ var TypeGenerator = _TypeGenerator;
441
441
  var import_ts_morph2 = require("ts-morph");
442
442
  var path = __toESM(require("path"));
443
443
  var _TokenGenerator = class _TokenGenerator {
444
- constructor(project) {
444
+ constructor(project, config) {
445
445
  __publicField(this, "project");
446
+ __publicField(this, "config");
446
447
  this.project = project;
448
+ this.config = config;
447
449
  }
448
450
  generate(outputDir) {
449
451
  const tokensDir = path.join(outputDir, "tokens");
@@ -457,23 +459,55 @@ var _TokenGenerator = class _TokenGenerator {
457
459
  ],
458
460
  moduleSpecifier: "@angular/core"
459
461
  });
462
+ const clientName = this.config.clientName || "DEFAULT";
463
+ const upperCaseClientName = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
460
464
  sourceFile.addVariableStatement({
461
465
  isExported: true,
462
466
  declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
463
467
  declarations: [
464
468
  {
465
- name: "BASE_PATH",
466
- initializer: `new InjectionToken<string>('BASE_PATH', {
469
+ name: `${upperCaseClientName}_BASE_PATH`,
470
+ initializer: `new InjectionToken<string>('${upperCaseClientName}_BASE_PATH', {
467
471
  providedIn: 'root',
468
- factory: () => '/api', // Default fallback
472
+ factory: () => '/api',
469
473
  })`
470
474
  }
471
475
  ],
472
476
  leadingTrivia: `/**
473
- * Injection token for the base API path
477
+ * Base path token for ${clientName} client
478
+ */
479
+ `
480
+ });
481
+ sourceFile.addVariableStatement({
482
+ isExported: true,
483
+ declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
484
+ declarations: [
485
+ {
486
+ name: `${upperCaseClientName}_HTTP_CLIENT`,
487
+ initializer: `new InjectionToken<HttpClient>('${upperCaseClientName}_HTTP_CLIENT')`
488
+ }
489
+ ],
490
+ leadingTrivia: `/**
491
+ * HTTP client token for ${clientName} client
474
492
  */
475
493
  `
476
494
  });
495
+ if (!this.config.clientName) {
496
+ sourceFile.addVariableStatement({
497
+ isExported: true,
498
+ declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
499
+ declarations: [
500
+ {
501
+ name: "BASE_PATH",
502
+ initializer: `${upperCaseClientName}_BASE_PATH`
503
+ }
504
+ ],
505
+ leadingTrivia: `/**
506
+ * @deprecated Use ${upperCaseClientName}_BASE_PATH instead
507
+ */
508
+ `
509
+ });
510
+ }
477
511
  sourceFile.saveSync();
478
512
  }
479
513
  };
@@ -838,6 +872,9 @@ function getTypeScriptType(schemaOrType, config, formatOrNullable, isNullable, c
838
872
  }
839
873
  switch (schema.type) {
840
874
  case "string":
875
+ if (schema.enum) {
876
+ return schema.enum.map((value) => typeof value === "string" ? `'${escapeString(value)}'` : String(value)).join(" | ");
877
+ }
841
878
  if (schema.format === "date" || schema.format === "date-time") {
842
879
  const dateType = config.options.dateType === "Date" ? "Date" : "string";
843
880
  return nullableType(dateType, nullable);
@@ -869,6 +906,10 @@ function nullableType(type, isNullable) {
869
906
  return type + (isNullable ? " | null" : "");
870
907
  }
871
908
  __name(nullableType, "nullableType");
909
+ function escapeString(str) {
910
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
911
+ }
912
+ __name(escapeString, "escapeString");
872
913
 
873
914
  // src/lib/generators/service/service-method/service-method-body.generator.ts
874
915
  var _ServiceMethodBodyGenerator = class _ServiceMethodBodyGenerator {
@@ -1739,6 +1780,9 @@ var _ServiceGenerator = class _ServiceGenerator {
1739
1780
  });
1740
1781
  }
1741
1782
  addImports(sourceFile, usedTypes) {
1783
+ var _a;
1784
+ const clientName = this.config.clientName || "DEFAULT";
1785
+ const upperCaseClientName = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1742
1786
  sourceFile.addImportDeclarations([
1743
1787
  {
1744
1788
  namedImports: [
@@ -1766,11 +1810,15 @@ var _ServiceGenerator = class _ServiceGenerator {
1766
1810
  },
1767
1811
  {
1768
1812
  namedImports: [
1769
- "BASE_PATH"
1813
+ `${upperCaseClientName}_BASE_PATH`,
1814
+ `${upperCaseClientName}_HTTP_CLIENT`
1770
1815
  ],
1771
1816
  moduleSpecifier: "../tokens"
1772
1817
  }
1773
1818
  ]);
1819
+ if (!this.config.clientName) {
1820
+ (_a = sourceFile.getImportDeclaration("../tokens")) == null ? void 0 : _a.addNamedImport("BASE_PATH");
1821
+ }
1774
1822
  if (usedTypes.size > 0) {
1775
1823
  sourceFile.addImportDeclaration({
1776
1824
  namedImports: Array.from(usedTypes).sort(),
@@ -1780,6 +1828,8 @@ var _ServiceGenerator = class _ServiceGenerator {
1780
1828
  }
1781
1829
  addServiceClass(sourceFile, controllerName, operations) {
1782
1830
  const className = `${controllerName}Service`;
1831
+ const clientName = this.config.clientName || "DEFAULT";
1832
+ const upperCaseClientName = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1783
1833
  sourceFile.insertText(0, SERVICE_GENERATOR_HEADER_COMMENT(controllerName));
1784
1834
  const serviceClass = sourceFile.addClass({
1785
1835
  name: className,
@@ -1798,14 +1848,14 @@ var _ServiceGenerator = class _ServiceGenerator {
1798
1848
  type: "HttpClient",
1799
1849
  scope: import_ts_morph3.Scope.Private,
1800
1850
  isReadonly: true,
1801
- initializer: "inject(HttpClient)"
1851
+ initializer: `inject(${upperCaseClientName}_HTTP_CLIENT, { optional: true }) ?? inject(HttpClient)`
1802
1852
  });
1803
1853
  serviceClass.addProperty({
1804
1854
  name: "basePath",
1805
1855
  type: "string",
1806
1856
  scope: import_ts_morph3.Scope.Private,
1807
1857
  isReadonly: true,
1808
- initializer: "inject(BASE_PATH)"
1858
+ initializer: `inject(${upperCaseClientName}_BASE_PATH)`
1809
1859
  });
1810
1860
  operations.forEach((operation) => {
1811
1861
  this.methodGenerator.addServiceMethod(serviceClass, operation);
@@ -1867,24 +1917,34 @@ var _ProviderGenerator = class _ProviderGenerator {
1867
1917
  overwrite: true
1868
1918
  });
1869
1919
  sourceFile.insertText(0, PROVIDER_GENERATOR_HEADER_COMMENT);
1920
+ const clientName = this.config.clientName || "Default";
1921
+ const pascalClientName = this.pascalCase(clientName);
1922
+ const upperCaseClientName = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1870
1923
  sourceFile.addImportDeclarations([
1871
1924
  {
1872
1925
  namedImports: [
1873
1926
  "EnvironmentProviders",
1874
1927
  "Provider",
1875
- "makeEnvironmentProviders"
1928
+ "makeEnvironmentProviders",
1929
+ "Type",
1930
+ "Injector",
1931
+ "inject"
1876
1932
  ],
1877
1933
  moduleSpecifier: "@angular/core"
1878
1934
  },
1879
1935
  {
1880
1936
  namedImports: [
1881
- "HTTP_INTERCEPTORS"
1937
+ "HttpClient",
1938
+ "HttpInterceptor",
1939
+ "HttpHandler",
1940
+ "HttpRequest"
1882
1941
  ],
1883
1942
  moduleSpecifier: "@angular/common/http"
1884
1943
  },
1885
1944
  {
1886
1945
  namedImports: [
1887
- "BASE_PATH"
1946
+ `${upperCaseClientName}_BASE_PATH`,
1947
+ `${upperCaseClientName}_HTTP_CLIENT`
1888
1948
  ],
1889
1949
  moduleSpecifier: "./tokens"
1890
1950
  }
@@ -1898,10 +1958,10 @@ var _ProviderGenerator = class _ProviderGenerator {
1898
1958
  });
1899
1959
  }
1900
1960
  sourceFile.addInterface({
1901
- name: "NgOpenapiConfig",
1961
+ name: `${pascalClientName}Config`,
1902
1962
  isExported: true,
1903
1963
  docs: [
1904
- "Configuration options for ng-openapi providers"
1964
+ `Configuration options for ${clientName} API client`
1905
1965
  ],
1906
1966
  properties: [
1907
1967
  {
@@ -1911,6 +1971,14 @@ var _ProviderGenerator = class _ProviderGenerator {
1911
1971
  "Base API URL"
1912
1972
  ]
1913
1973
  },
1974
+ {
1975
+ name: "interceptors",
1976
+ type: "Type<HttpInterceptor>[]",
1977
+ hasQuestionToken: true,
1978
+ docs: [
1979
+ "HTTP interceptors to apply to this client's requests"
1980
+ ]
1981
+ },
1914
1982
  {
1915
1983
  name: "enableDateTransform",
1916
1984
  type: "boolean",
@@ -1921,105 +1989,102 @@ var _ProviderGenerator = class _ProviderGenerator {
1921
1989
  }
1922
1990
  ]
1923
1991
  });
1924
- this.addMainProviderFunction(sourceFile);
1925
- this.addAsyncProviderFunction(sourceFile);
1992
+ this.addInterceptorChainHelper(sourceFile);
1993
+ this.addClientProviderFunction(sourceFile, pascalClientName, upperCaseClientName);
1926
1994
  sourceFile.saveSync();
1927
1995
  }
1928
- addMainProviderFunction(sourceFile) {
1929
- const hasDateInterceptor = this.config.options.dateType === "Date";
1930
- const functionBody = `
1931
- const providers: Provider[] = [
1932
- // Base path token
1933
- {
1934
- provide: BASE_PATH,
1935
- useValue: config.basePath
1936
- }
1937
- ];
1938
-
1939
- ${hasDateInterceptor ? `// Add date interceptor if enabled (default: true)
1940
- if (config.enableDateTransform !== false) {
1941
- providers.push({
1942
- provide: HTTP_INTERCEPTORS,
1943
- useClass: DateInterceptor,
1944
- multi: true
1945
- });
1946
- }` : `// Date transformation not available (dateType: 'string' was used in generation)`}
1947
-
1948
- return makeEnvironmentProviders(providers);`;
1996
+ addInterceptorChainHelper(sourceFile) {
1949
1997
  sourceFile.addFunction({
1950
- name: "provideNgOpenapi",
1951
- isExported: true,
1998
+ name: "createHttpClientWithInterceptors",
1952
1999
  docs: [
1953
- "Provides all necessary configuration for ng-openapi generated services",
1954
- "",
1955
- "@example",
1956
- "```typescript",
1957
- "// In your app.config.ts",
1958
- "import { provideNgOpenapi } from './api/providers';",
1959
- "",
1960
- "export const appConfig: ApplicationConfig = {",
1961
- " providers: [",
1962
- " provideNgOpenapi({",
1963
- " basePath: 'https://api.example.com'",
1964
- " }),",
1965
- " // other providers...",
1966
- " ]",
1967
- "};",
1968
- "```"
2000
+ "Creates an HttpClient with a custom interceptor chain"
1969
2001
  ],
1970
2002
  parameters: [
1971
2003
  {
1972
- name: "config",
1973
- type: "NgOpenapiConfig"
2004
+ name: "baseClient",
2005
+ type: "HttpClient"
2006
+ },
2007
+ {
2008
+ name: "interceptors",
2009
+ type: "HttpInterceptor[]"
1974
2010
  }
1975
2011
  ],
1976
- returnType: "EnvironmentProviders",
1977
- statements: functionBody
2012
+ returnType: "HttpClient",
2013
+ statements: `
2014
+ if (!interceptors.length) {
2015
+ return baseClient;
2016
+ }
2017
+
2018
+ // Create a custom handler that applies interceptors in sequence
2019
+ let handler = baseClient.handler;
2020
+
2021
+ // Apply interceptors in reverse order (last interceptor wraps the original handler)
2022
+ for (let i = interceptors.length - 1; i >= 0; i--) {
2023
+ const currentHandler = handler;
2024
+ const interceptor = interceptors[i];
2025
+
2026
+ handler = {
2027
+ handle: (req: HttpRequest<any>) => interceptor.intercept(req, currentHandler)
2028
+ };
2029
+ }
2030
+
2031
+ // Return a new HttpClient with the custom handler
2032
+ return new (baseClient.constructor as any)(handler);`
1978
2033
  });
1979
2034
  }
1980
- addAsyncProviderFunction(sourceFile) {
2035
+ addClientProviderFunction(sourceFile, pascalClientName, upperCaseClientName) {
1981
2036
  const hasDateInterceptor = this.config.options.dateType === "Date";
1982
2037
  const functionBody = `
1983
- const providers: Provider[] = [];
1984
-
1985
- // Handle async base path
1986
- if (typeof config.basePath === 'string') {
1987
- providers.push({
1988
- provide: BASE_PATH,
2038
+ const providers: Provider[] = [
2039
+ // Base path token
2040
+ {
2041
+ provide: ${upperCaseClientName}_BASE_PATH,
1989
2042
  useValue: config.basePath
1990
- });
1991
- } else {
1992
- providers.push({
1993
- provide: BASE_PATH,
1994
- useFactory: config.basePath
1995
- });
1996
- }
1997
-
1998
- ${hasDateInterceptor ? `// Add date interceptor if enabled (default: true)
1999
- if (config.enableDateTransform !== false) {
2000
- providers.push({
2001
- provide: HTTP_INTERCEPTORS,
2002
- useClass: DateInterceptor,
2003
- multi: true
2004
- });
2005
- }` : `// Date transformation not available (dateType: 'string' was used in generation)`}
2043
+ },
2044
+
2045
+ // HTTP client with custom interceptors
2046
+ {
2047
+ provide: ${upperCaseClientName}_HTTP_CLIENT,
2048
+ useFactory: (baseClient: HttpClient, injector: Injector) => {
2049
+ const interceptorInstances: HttpInterceptor[] = [];
2050
+
2051
+ // Add custom interceptors
2052
+ if (config.interceptors?.length) {
2053
+ config.interceptors.forEach(interceptorClass => {
2054
+ interceptorInstances.push(injector.get(interceptorClass));
2055
+ });
2056
+ }
2057
+
2058
+ ${hasDateInterceptor ? `
2059
+ // Add date interceptor if enabled (default: true)
2060
+ if (config.enableDateTransform !== false) {
2061
+ interceptorInstances.push(injector.get(DateInterceptor));
2062
+ }` : ""}
2063
+
2064
+ return createHttpClientWithInterceptors(baseClient, interceptorInstances);
2065
+ },
2066
+ deps: [HttpClient, Injector]
2067
+ }
2068
+ ];
2006
2069
 
2007
2070
  return makeEnvironmentProviders(providers);`;
2008
2071
  sourceFile.addFunction({
2009
- name: "provideNgOpenapiAsync",
2072
+ name: `provide${pascalClientName}`,
2010
2073
  isExported: true,
2011
2074
  docs: [
2012
- "Alternative function for cases where you need to handle async configuration",
2075
+ `Provides configuration for ${pascalClientName} API client`,
2013
2076
  "",
2014
2077
  "@example",
2015
2078
  "```typescript",
2016
- "// In your app.config.ts",
2017
- "import { provideNgOpenapiAsync } from './api/providers';",
2079
+ `// In your app.config.ts`,
2080
+ `import { provide${pascalClientName} } from './api/providers';`,
2081
+ `import { AuthInterceptor } from './interceptors/auth.interceptor';`,
2018
2082
  "",
2019
2083
  "export const appConfig: ApplicationConfig = {",
2020
2084
  " providers: [",
2021
- " provideNgOpenapiAsync({",
2022
- " basePath: () => import('./config').then(c => c.apiConfig.baseUrl)",
2085
+ ` provide${pascalClientName}({`,
2086
+ " basePath: 'https://api.example.com',",
2087
+ " interceptors: [AuthInterceptor]",
2023
2088
  " }),",
2024
2089
  " // other providers...",
2025
2090
  " ]",
@@ -2029,16 +2094,16 @@ return makeEnvironmentProviders(providers);`;
2029
2094
  parameters: [
2030
2095
  {
2031
2096
  name: "config",
2032
- type: `{
2033
- basePath: string | (() => Promise<string>);
2034
- enableDateTransform?: boolean;
2035
- }`
2097
+ type: `${pascalClientName}Config`
2036
2098
  }
2037
2099
  ],
2038
2100
  returnType: "EnvironmentProviders",
2039
2101
  statements: functionBody
2040
2102
  });
2041
2103
  }
2104
+ pascalCase(str) {
2105
+ return str.replace(/(?:^|[-_])([a-z])/g, (_, char) => char.toUpperCase());
2106
+ }
2042
2107
  };
2043
2108
  __name(_ProviderGenerator, "ProviderGenerator");
2044
2109
  var ProviderGenerator = _ProviderGenerator;
@@ -2071,7 +2136,7 @@ function generateFromConfig(config) {
2071
2136
  typeGenerator.generate();
2072
2137
  console.log(`\u2705 TypeScript interfaces generated`);
2073
2138
  if (generateServices) {
2074
- const tokenGenerator = new TokenGenerator(project);
2139
+ const tokenGenerator = new TokenGenerator(project, config);
2075
2140
  tokenGenerator.generate(outputPath);
2076
2141
  if (config.options.dateType === "Date") {
2077
2142
  const dateTransformer = new DateTransformerGenerator(project);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-openapi",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "Generate Angular services and TypeScript types from OpenAPI/Swagger specifications",
5
5
  "keywords": [
6
6
  "angular",