@constantant/openapi-resource-gen 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # openapi-resource-gen
2
+
3
+ Nx generator that reads an OpenAPI 3.x spec and emits one `InjectionToken` per
4
+ endpoint, each in its own `.ts` file. The result is a tree-shakeable Angular
5
+ data-access library: only tokens that are actually injected end up in the bundle.
6
+
7
+ ## Concept
8
+
9
+ Each endpoint becomes a typed `InjectionToken` whose value is a factory function.
10
+ Calling the token's factory function returns an `httpResource` — Angular 22's
11
+ signal-native HTTP wrapper.
12
+
13
+ ```
14
+ OpenAPI spec → generator → one .token.ts per endpoint
15
+ └─ InjectionToken + typed factory function
16
+ └─ export type Params / Body / Response
17
+ ```
18
+
19
+ All types are derived from the generated `schema.d.ts` (via `openapi-typescript`)
20
+ so there is zero hand-written interface code and no runtime overhead from a
21
+ generated client library.
22
+
23
+ ### Why one file per endpoint?
24
+
25
+ esbuild performs tree-shaking at the file boundary. A token that is never
26
+ `inject()`-ed is never imported, so the entire file is dropped from the bundle.
27
+ Bundling all endpoints into a single file would prevent this.
28
+
29
+ ### `providedIn: 'none'` vs `'root'`
30
+
31
+ | Mode | Token has factory? | How to provide |
32
+ |------|--------------------|----------------|
33
+ | `'none'` (default) | No | Call the emitted `provide{Name}()` helper in `app.config.ts` or a route provider |
34
+ | `'root'` | Yes — self-registers | Just `inject()` it anywhere; Angular handles registration |
35
+
36
+ `'none'` is the default because it lets you inject the same token with different
37
+ `API_BASE_URL` values in different route sub-trees (e.g. staging vs production,
38
+ or different micro-frontends). `'root'` is simpler but prevents per-scope
39
+ base URL overrides.
40
+
41
+ ---
42
+
43
+ ## Running the generator
44
+
45
+ ```bash
46
+ npx nx g openapi-resource-gen:api-resource \
47
+ --specPath=specs/petstore.yaml \
48
+ --outputDir=libs/petstore-data-access/src
49
+ ```
50
+
51
+ ### Options
52
+
53
+ | Option | Required | Default | Description |
54
+ |--------|----------|---------|-------------|
55
+ | `specPath` | yes | — | Path to the OpenAPI 3.x YAML or JSON spec file |
56
+ | `outputDir` | yes | — | Output directory relative to the workspace root |
57
+ | `baseUrlToken` | no | `API_BASE_URL` | Name of the base-URL `InjectionToken` emitted alongside the endpoint tokens |
58
+ | `tagFilter` | no | all tags | Comma-separated list of OpenAPI tags to include |
59
+ | `namingConvention` | no | `kebab` | `kebab` → `find-pets-by-status.token.ts`; `camel` → `findPetsByStatus.token.ts` |
60
+ | `providedIn` | no | `none` | `none` or `root` — see table above |
61
+
62
+ ---
63
+
64
+ ## Output structure
65
+
66
+ ```
67
+ {outputDir}/
68
+ schema.d.ts # openapi-typescript output, never edit manually
69
+ api-base-url.token.ts # InjectionToken<string> for the API root URL
70
+ index.ts # re-exports all tag barrels + api-base-url.token
71
+ {tag}/
72
+ index.ts # re-exports all token files in this tag folder
73
+ {operation-id}.token.ts # one file per endpoint
74
+ ```
75
+
76
+ Tags map to subfolders; untagged operations go into `default/`.
77
+
78
+ ---
79
+
80
+ ## Generated token anatomy
81
+
82
+ ### GET with query params — `find-pets-by-status.token.ts`
83
+
84
+ ```typescript
85
+ import { InjectionToken, inject, FactoryProvider } from '@angular/core';
86
+ import { httpResource } from '@angular/common/http';
87
+ import type { paths } from '../schema.d';
88
+ import { PETSTORE_BASE_URL } from '../api-base-url.token';
89
+
90
+ export type FindPetsByStatusParams =
91
+ paths['/pet/findByStatus']['get']['parameters']['query'];
92
+
93
+ export type FindPetsByStatusResponse =
94
+ paths['/pet/findByStatus']['get']['responses']['200']['content']['application/json'];
95
+
96
+ export const FIND_PETS_BY_STATUS = new InjectionToken<
97
+ (params?: FindPetsByStatusParams | (() => FindPetsByStatusParams | undefined))
98
+ => ReturnType<typeof httpResource<FindPetsByStatusResponse>>
99
+ >('FIND_PETS_BY_STATUS');
100
+
101
+ export function provideFindPetsByStatus(): FactoryProvider {
102
+ return {
103
+ provide: FIND_PETS_BY_STATUS,
104
+ useFactory: () => {
105
+ const base = inject(PETSTORE_BASE_URL);
106
+ return (params?) =>
107
+ httpResource<FindPetsByStatusResponse>(() => ({
108
+ url: `${base}/pet/findByStatus`,
109
+ params: (typeof params === 'function' ? params() : params) as ...,
110
+ }));
111
+ },
112
+ };
113
+ }
114
+ ```
115
+
116
+ ### GET with path params — `repos-get.token.ts`
117
+
118
+ Path params (`/repos/{owner}/{repo}`) become required positional arguments on
119
+ the returned function and are interpolated into the URL template:
120
+
121
+ ```typescript
122
+ export const REPOS_GET = new InjectionToken<
123
+ (owner: string, repo: string) => ReturnType<typeof httpResource<ReposGetResponse>>
124
+ >('REPOS_GET');
125
+ ```
126
+
127
+ ### Mutation (POST/PUT/PATCH/DELETE)
128
+
129
+ The factory returns `(body: BodyType | Signal<BodyType>) => httpResource(...)`.
130
+ The resource config receives `method: 'POST'` and `body` automatically.
131
+
132
+ ---
133
+
134
+ ## Consuming tokens in a component
135
+
136
+ ### 1. Register providers in `app.config.ts`
137
+
138
+ ```typescript
139
+ import { PETSTORE_BASE_URL, provideFindPetsByStatus } from '@angular-openapi-gen/petstore-data-access';
140
+
141
+ export const appConfig: ApplicationConfig = {
142
+ providers: [
143
+ provideHttpClient(),
144
+ { provide: PETSTORE_BASE_URL, useValue: 'https://petstore3.swagger.io/api/v3' },
145
+ provideFindPetsByStatus(),
146
+ ],
147
+ };
148
+ ```
149
+
150
+ ### 2. Inject and call in a component
151
+
152
+ ```typescript
153
+ @Component({ ... })
154
+ export class PetsPageComponent {
155
+ private findPetsByStatus = inject(FIND_PETS_BY_STATUS);
156
+
157
+ readonly status = signal<'available' | 'pending' | 'sold'>('available');
158
+
159
+ // Pass a thunk so httpResource re-fetches whenever status() changes
160
+ readonly pets = this.findPetsByStatus(() => ({ status: this.status() }));
161
+ }
162
+ ```
163
+
164
+ ```html
165
+ @if (pets.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
166
+ @for (pet of pets.value() ?? []; track pet.id) {
167
+ <p>{{ pet.name }}</p>
168
+ }
169
+ ```
170
+
171
+ ### Reactive params (signal-driven re-fetch)
172
+
173
+ The `params` argument accepts either a plain object or a **thunk**
174
+ `() => ParamsType`. When a thunk is passed it is called inside the
175
+ `httpResource` reactive lambda, so any signal reads inside it register as
176
+ dependencies — the request re-fires automatically when a signal changes.
177
+
178
+ ---
179
+
180
+ ## Exported types
181
+
182
+ Every token file exports `Params`, `Body`, and `Response` type aliases so
183
+ consumers can derive domain types without duplicating type expressions:
184
+
185
+ ```typescript
186
+ import type { FindPetsByStatusParams } from '@angular-openapi-gen/petstore-data-access';
187
+
188
+ type PetStatus = FindPetsByStatusParams['status']; // 'available' | 'pending' | 'sold'
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Adding a new data-access lib
194
+
195
+ 1. Fetch the spec into `specs/`:
196
+ ```bash
197
+ curl -L https://example.com/openapi.yaml -o specs/myapi.yaml
198
+ ```
199
+
200
+ 2. Run the generator:
201
+ ```bash
202
+ npx nx g openapi-resource-gen:api-resource \
203
+ --specPath=specs/myapi.yaml \
204
+ --outputDir=libs/myapi-data-access/src
205
+ ```
206
+
207
+ 3. Create `libs/myapi-data-access/package.json` (or add a path alias to
208
+ `tsconfig.base.json`) so the lib is importable as
209
+ `@angular-openapi-gen/myapi-data-access`.
210
+
211
+ 4. Add base URL provider and token providers to `app.config.ts`.
212
+
213
+ To regenerate after a spec update, re-run the same command — the generator
214
+ overwrites all files in `outputDir`.
215
+
216
+ ---
217
+
218
+ ## Implementation notes
219
+
220
+ | Step | Tool | Purpose |
221
+ |------|------|---------|
222
+ | Spec loading | `js-yaml` + custom `stripNonSchemaRefs()` | Handle YAML and remove non-spec `$ref` links (markdown, images) that would break parsing |
223
+ | Type generation | `openapi-typescript` CLI | Emit `schema.d.ts` — the single source of truth for all request/response types |
224
+ | Spec dereferencing | `@apidevtools/swagger-parser` | Resolve all `$ref` chains for endpoint extraction |
225
+ | Code generation | `@nx/devkit` `generateFiles()` + inline string building | EJS template for `api-base-url.token.ts`; direct string assembly for token files |
226
+ | Formatting | `@nx/devkit` `formatFiles()` | Runs Prettier over all written files |
227
+
228
+ Hyphenated path parameter names (e.g. `{enterprise-team}` in the GitHub spec)
229
+ are converted to camelCase in the function signature and the URL template via
230
+ `toCamelCase()` to produce valid JavaScript identifiers.
@@ -0,0 +1,9 @@
1
+ {
2
+ "generators": {
3
+ "api-resource": {
4
+ "factory": "./src/generators/api-resource/generator",
5
+ "schema": "./src/generators/api-resource/schema.json",
6
+ "description": "Generate InjectionToken-based API resource files from an OpenAPI 3.x spec"
7
+ }
8
+ }
9
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@constantant/openapi-resource-gen",
3
+ "version": "1.1.0",
4
+ "description": "Nx generator: one InjectionToken per OpenAPI endpoint — tree-shakeable Angular data-access libs via httpResource",
5
+ "license": "MIT",
6
+ "main": "src/index.js",
7
+ "types": "src/index.d.ts",
8
+ "generators": "./generators.json",
9
+ "keywords": [
10
+ "nx",
11
+ "nx-plugin",
12
+ "angular",
13
+ "openapi",
14
+ "generator",
15
+ "httpResource",
16
+ "InjectionToken",
17
+ "tree-shaking"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "peerDependencies": {
23
+ "@nx/devkit": ">=22.0.0",
24
+ "nx": ">=22.0.0"
25
+ },
26
+ "dependencies": {
27
+ "@apidevtools/swagger-parser": "^12.1.0",
28
+ "js-yaml": "^4.2.0",
29
+ "openapi-typescript": "^6.7.6",
30
+ "openapi-types": "^12.1.3",
31
+ "tslib": "^2.3.0"
32
+ },
33
+ "type": "commonjs"
34
+ }
@@ -0,0 +1,14 @@
1
+ export interface EndpointModel {
2
+ tag: string;
3
+ operationId: string;
4
+ method: string;
5
+ apiPath: string;
6
+ pathParams: string[];
7
+ tokenName: string;
8
+ fileName: string;
9
+ hasQueryParams: boolean;
10
+ hasBody: boolean;
11
+ hasResponse: boolean;
12
+ responseStatus: string | null;
13
+ bodyContentType: string | null;
14
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=endpoint-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint-model.js","sourceRoot":"","sources":["../../../../../../tools/openapi-resource-gen/src/generators/api-resource/endpoint-model.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import { InjectionToken } from '@angular/core';
2
+
3
+ export const <%= baseUrlToken %> = new InjectionToken<string>(
4
+ '<%= baseUrlToken %>',
5
+ { providedIn: 'root', factory: () => '' }
6
+ );
@@ -0,0 +1,11 @@
1
+ import { Tree } from '@nx/devkit';
2
+ export interface ApiResourceGeneratorSchema {
3
+ specPath: string;
4
+ outputDir: string;
5
+ baseUrlToken?: string;
6
+ tagFilter?: string;
7
+ namingConvention?: 'camel' | 'kebab';
8
+ providedIn?: 'root' | 'none';
9
+ }
10
+ export declare function apiResourceGenerator(tree: Tree, options: ApiResourceGeneratorSchema): Promise<void>;
11
+ export default apiResourceGenerator;
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.apiResourceGenerator = apiResourceGenerator;
40
+ const devkit_1 = require("@nx/devkit");
41
+ const fs = __importStar(require("fs"));
42
+ const jsYaml = __importStar(require("js-yaml"));
43
+ // openapi-typescript ships as ESM-only; use the bundled CJS build so this
44
+ // CommonJS generator can call it without a dynamic import().
45
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
46
+ const openapiTS = require('openapi-typescript/dist/index.cjs');
47
+ const path = __importStar(require("path"));
48
+ const swagger_parser_1 = __importDefault(require("@apidevtools/swagger-parser"));
49
+ const parse_spec_1 = require("./parse-spec");
50
+ const render_token_1 = require("./render-token");
51
+ /** Replace $ref values that point to non-YAML/JSON files with {} so
52
+ * swagger-parser doesn't try to load markdown or other binary assets. */
53
+ function stripNonSchemaRefs(obj) {
54
+ if (Array.isArray(obj))
55
+ return obj.map(stripNonSchemaRefs);
56
+ if (obj && typeof obj === 'object') {
57
+ const record = obj;
58
+ if ('$ref' in record && typeof record['$ref'] === 'string') {
59
+ const ref = record['$ref'];
60
+ if (!ref.startsWith('#') && !/\.(json|ya?ml)(#.*)?$/i.test(ref)) {
61
+ return {};
62
+ }
63
+ }
64
+ const result = {};
65
+ for (const [key, value] of Object.entries(record)) {
66
+ result[key] = stripNonSchemaRefs(value);
67
+ }
68
+ return result;
69
+ }
70
+ return obj;
71
+ }
72
+ async function apiResourceGenerator(tree, options) {
73
+ const { specPath, outputDir, baseUrlToken = 'API_BASE_URL', tagFilter, namingConvention = 'kebab', providedIn = 'none', } = options;
74
+ const allowedTags = tagFilter
75
+ ? tagFilter
76
+ .split(',')
77
+ .map((t) => t.trim())
78
+ .filter(Boolean)
79
+ : null;
80
+ // 1. Parse spec with js-yaml, strip any $refs pointing to non-spec files
81
+ // (e.g. x-topics.$ref: ./docs/getting-started.md in the travel spec).
82
+ // Write the cleaned spec next to the original so relative file $refs
83
+ // within the spec still resolve when swagger-parser dereferences.
84
+ const absoluteSpecPath = path.resolve(specPath);
85
+ const rawParsed = jsYaml.load(fs.readFileSync(absoluteSpecPath, 'utf-8'));
86
+ const cleanedParsed = stripNonSchemaRefs(rawParsed);
87
+ const tmpClean = path
88
+ .join(path.dirname(absoluteSpecPath), `_tmp_oas_${Date.now()}.json`)
89
+ .replace(/\\/g, '/');
90
+ try {
91
+ fs.writeFileSync(tmpClean, JSON.stringify(cleanedParsed));
92
+ // 2. Emit schema.d.ts via openapi-typescript programmatic API, using the
93
+ // cleaned spec (not the dereferenced result — dereferenced Stripe has
94
+ // circular refs; openapi-typescript resolves $refs itself).
95
+ const schemaDts = await openapiTS(tmpClean);
96
+ tree.write((0, devkit_1.joinPathFragments)(outputDir, 'schema.d.ts'), schemaDts);
97
+ // 3. Dereference for endpoint extraction (may produce circular objects —
98
+ // that's fine because we only iterate over it, never serialize it).
99
+ const api = (await swagger_parser_1.default.dereference(tmpClean));
100
+ // 4. Emit api-base-url.token.ts from the EJS template in files/
101
+ (0, devkit_1.generateFiles)(tree, path.join(__dirname, 'files'), outputDir, {
102
+ baseUrlToken,
103
+ tmpl: '',
104
+ });
105
+ // 5. Build EndpointModels from the already-dereferenced API
106
+ const endpoints = (0, parse_spec_1.buildEndpoints)(api, allowedTags, namingConvention);
107
+ // 6. Group by tag
108
+ const byTag = new Map();
109
+ for (const ep of endpoints) {
110
+ if (!byTag.has(ep.tag))
111
+ byTag.set(ep.tag, []);
112
+ byTag.get(ep.tag).push(ep);
113
+ }
114
+ // 7. One token file per endpoint + per-tag barrel index
115
+ for (const [tag, tagEndpoints] of byTag) {
116
+ const tagDir = (0, devkit_1.joinPathFragments)(outputDir, tag);
117
+ for (const ep of tagEndpoints) {
118
+ tree.write((0, devkit_1.joinPathFragments)(tagDir, `${ep.fileName}.token.ts`), (0, render_token_1.renderTokenFile)(ep, baseUrlToken, providedIn));
119
+ }
120
+ const tagBarrel = tagEndpoints
121
+ .map((ep) => `export * from './${ep.fileName}.token';`)
122
+ .join('\n') + '\n';
123
+ tree.write((0, devkit_1.joinPathFragments)(tagDir, 'index.ts'), tagBarrel);
124
+ }
125
+ // 8. Root barrel index (includes api-base-url token for app.config.ts providers)
126
+ const rootBarrel = `export * from './api-base-url.token';\n` +
127
+ [...byTag.keys()]
128
+ .map((tag) => `export * from './${tag}';`)
129
+ .join('\n') + '\n';
130
+ tree.write((0, devkit_1.joinPathFragments)(outputDir, 'index.ts'), rootBarrel);
131
+ }
132
+ finally {
133
+ try {
134
+ fs.unlinkSync(tmpClean);
135
+ }
136
+ catch {
137
+ /* ignore */
138
+ }
139
+ }
140
+ await (0, devkit_1.formatFiles)(tree);
141
+ }
142
+ exports.default = apiResourceGenerator;
143
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../tools/openapi-resource-gen/src/generators/api-resource/generator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,oDA8FC;AAhJD,uCAKoB;AACpB,uCAAyB;AACzB,gDAAkC;AAClC,0EAA0E;AAC1E,6DAA6D;AAC7D,iEAAiE;AACjE,MAAM,SAAS,GAAG,OAAO,CAAC,mCAAmC,CAEzC,CAAC;AACrB,2CAA6B;AAC7B,iFAAwD;AAExD,6CAA8C;AAC9C,iDAAiD;AAWjD;0EAC0E;AAC1E,SAAS,kBAAkB,CAAC,GAAY;IACtC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC3D,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,GAA8B,CAAC;QAC9C,IAAI,MAAM,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC3D,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChE,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,MAAM,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAEM,KAAK,UAAU,oBAAoB,CACxC,IAAU,EACV,OAAmC;IAEnC,MAAM,EACJ,QAAQ,EACR,SAAS,EACT,YAAY,GAAG,cAAc,EAC7B,SAAS,EACT,gBAAgB,GAAG,OAAO,EAC1B,UAAU,GAAG,MAAM,GACpB,GAAG,OAAO,CAAC;IAEZ,MAAM,WAAW,GAAG,SAAS;QAC3B,CAAC,CAAC,SAAS;aACN,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC;QACpB,CAAC,CAAC,IAAI,CAAC;IAET,yEAAyE;IACzE,yEAAyE;IACzE,wEAAwE;IACxE,qEAAqE;IACrE,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1E,MAAM,aAAa,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,IAAI;SAClB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,YAAY,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC;SACnE,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAEvB,IAAI,CAAC;QACH,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;QAE1D,yEAAyE;QACzE,yEAAyE;QACzE,+DAA+D;QAC/D,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,CAAC,IAAA,0BAAiB,EAAC,SAAS,EAAE,aAAa,CAAC,EAAE,SAAS,CAAC,CAAC;QAEnE,yEAAyE;QACzE,uEAAuE;QACvE,MAAM,GAAG,GAAG,CAAC,MAAM,wBAAa,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAuB,CAAC;QAE9E,gEAAgE;QAChE,IAAA,sBAAa,EAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE;YAC5D,YAAY;YACZ,IAAI,EAAE,EAAE;SACT,CAAC,CAAC;QAEH,4DAA4D;QAC5D,MAAM,SAAS,GAAG,IAAA,2BAAc,EAAC,GAAG,EAAE,WAAW,EAAE,gBAAgB,CAAC,CAAC;QAErE,kBAAkB;QAClB,MAAM,KAAK,GAAG,IAAI,GAAG,EAA4B,CAAC;QAClD,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC9C,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,CAAC;QAED,wDAAwD;QACxD,KAAK,MAAM,CAAC,GAAG,EAAE,YAAY,CAAC,IAAI,KAAK,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,IAAA,0BAAiB,EAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAEjD,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;gBAC9B,IAAI,CAAC,KAAK,CACR,IAAA,0BAAiB,EAAC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,WAAW,CAAC,EACpD,IAAA,8BAAe,EAAC,EAAE,EAAE,YAAY,EAAE,UAAU,CAAC,CAC9C,CAAC;YACJ,CAAC;YAED,MAAM,SAAS,GACb,YAAY;iBACT,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,oBAAoB,EAAE,CAAC,QAAQ,UAAU,CAAC;iBACtD,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,KAAK,CAAC,IAAA,0BAAiB,EAAC,MAAM,EAAE,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC;QAC/D,CAAC;QAED,iFAAiF;QACjF,MAAM,UAAU,GACd,yCAAyC;YACzC,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;iBACd,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,oBAAoB,GAAG,IAAI,CAAC;iBACzC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,IAAA,0BAAiB,EAAC,SAAS,EAAE,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC;IACnE,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC;IAED,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,kBAAe,oBAAoB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { OpenAPIV3 } from 'openapi-types';
2
+ import type { EndpointModel } from './endpoint-model';
3
+ export declare function toScreamingSnake(str: string): string;
4
+ export declare function toKebabCase(str: string): string;
5
+ export declare function buildEndpoints(api: OpenAPIV3.Document, allowedTags: string[] | null, namingConvention: 'camel' | 'kebab'): EndpointModel[];
6
+ /** Convenience wrapper that dereferences the spec before building endpoints. */
7
+ export declare function parseSpec(specPath: string, allowedTags: string[] | null, namingConvention: 'camel' | 'kebab'): Promise<EndpointModel[]>;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.toScreamingSnake = toScreamingSnake;
7
+ exports.toKebabCase = toKebabCase;
8
+ exports.buildEndpoints = buildEndpoints;
9
+ exports.parseSpec = parseSpec;
10
+ const swagger_parser_1 = __importDefault(require("@apidevtools/swagger-parser"));
11
+ const openapi_types_1 = require("openapi-types");
12
+ const HTTP_METHODS = [
13
+ openapi_types_1.OpenAPIV3.HttpMethods.GET,
14
+ openapi_types_1.OpenAPIV3.HttpMethods.POST,
15
+ openapi_types_1.OpenAPIV3.HttpMethods.PUT,
16
+ openapi_types_1.OpenAPIV3.HttpMethods.PATCH,
17
+ openapi_types_1.OpenAPIV3.HttpMethods.DELETE,
18
+ ];
19
+ function toScreamingSnake(str) {
20
+ return str
21
+ .replace(/\//g, '_')
22
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
23
+ .replace(/[-\s.]+/g, '_')
24
+ .toUpperCase()
25
+ .replace(/_+/g, '_')
26
+ .replace(/^_|_$/g, '');
27
+ }
28
+ function toKebabCase(str) {
29
+ return str
30
+ .replace(/\//g, '-')
31
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
32
+ .replace(/[_\s.]+/g, '-')
33
+ .toLowerCase()
34
+ .replace(/-+/g, '-')
35
+ .replace(/^-|-$/g, '');
36
+ }
37
+ function buildEndpoints(api, allowedTags, namingConvention) {
38
+ const endpoints = [];
39
+ for (const [apiPath, pathItem] of Object.entries(api.paths ?? {})) {
40
+ if (!pathItem)
41
+ continue;
42
+ for (const method of HTTP_METHODS) {
43
+ const operation = pathItem[method];
44
+ if (!operation)
45
+ continue;
46
+ const tag = operation.tags?.[0] ?? 'default';
47
+ if (allowedTags && !allowedTags.includes(tag))
48
+ continue;
49
+ const rawId = operation.operationId ??
50
+ `${method}_${apiPath.replace(/\W+/g, '_').replace(/^_|_$/g, '')}`;
51
+ const fileName = namingConvention === 'kebab' ? toKebabCase(rawId) : rawId;
52
+ const tokenName = toScreamingSnake(rawId);
53
+ const allParams = [
54
+ ...(pathItem.parameters ?? []),
55
+ ...(operation.parameters ?? []),
56
+ ];
57
+ const pathParams = allParams
58
+ .filter((p) => p.in === 'path')
59
+ .map((p) => p.name);
60
+ const hasQueryParams = allParams.some((p) => p.in === 'query');
61
+ const requestBody = operation.requestBody;
62
+ const bodyContent = requestBody?.content ?? {};
63
+ const BODY_CONTENT_TYPES = [
64
+ 'application/json',
65
+ 'application/x-www-form-urlencoded',
66
+ 'multipart/form-data',
67
+ ];
68
+ const bodyContentType = BODY_CONTENT_TYPES.find((ct) => bodyContent[ct]) ??
69
+ Object.keys(bodyContent)[0] ??
70
+ null;
71
+ const hasBody = bodyContentType !== null;
72
+ const rawStatus = operation.responses?.['200']
73
+ ? '200'
74
+ : operation.responses?.['201']
75
+ ? '201'
76
+ : null;
77
+ // Only treat the response as typed JSON when the response actually has
78
+ // application/json content (not PDF, octet-stream, etc.)
79
+ const responseObj = rawStatus
80
+ ? operation.responses?.[rawStatus]
81
+ : null;
82
+ const responseStatus = responseObj?.content?.['application/json'] ? rawStatus : null;
83
+ const hasResponse = responseStatus !== null;
84
+ endpoints.push({
85
+ tag: toKebabCase(tag),
86
+ operationId: rawId,
87
+ method,
88
+ apiPath,
89
+ pathParams,
90
+ tokenName,
91
+ fileName,
92
+ hasQueryParams,
93
+ hasBody,
94
+ hasResponse,
95
+ responseStatus,
96
+ bodyContentType,
97
+ });
98
+ }
99
+ }
100
+ return endpoints;
101
+ }
102
+ /** Convenience wrapper that dereferences the spec before building endpoints. */
103
+ async function parseSpec(specPath, allowedTags, namingConvention) {
104
+ const api = (await swagger_parser_1.default.dereference(specPath));
105
+ return buildEndpoints(api, allowedTags, namingConvention);
106
+ }
107
+ //# sourceMappingURL=parse-spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-spec.js","sourceRoot":"","sources":["../../../../../../tools/openapi-resource-gen/src/generators/api-resource/parse-spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { EndpointModel } from './endpoint-model';
2
+ export declare function renderTokenFile(ep: EndpointModel, baseUrlToken: string, providedIn?: 'root' | 'none'): string;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderTokenFile = renderTokenFile;
4
+ function toPascalCase(str) {
5
+ return str
6
+ .replace(/\//g, '-')
7
+ .split(/[-_\s]+/)
8
+ .filter(Boolean)
9
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
10
+ .join('');
11
+ }
12
+ function toCamelCase(str) {
13
+ const parts = str.split(/[-_]+/).filter(Boolean);
14
+ return parts[0] + parts.slice(1).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join('');
15
+ }
16
+ function renderTokenFile(ep, baseUrlToken, providedIn = 'none') {
17
+ const pascal = toPascalCase(ep.operationId);
18
+ const urlTemplate = ep.apiPath.replace(/\{([\w-]+)\}/g, (_, p) => `\${${toCamelCase(p)}}`);
19
+ const isGet = ep.method === 'get';
20
+ const { responseStatus } = ep;
21
+ const lines = [];
22
+ // Imports
23
+ const coreImports = ['InjectionToken', 'inject'];
24
+ if (!isGet && ep.hasBody)
25
+ coreImports.push('Signal');
26
+ if (providedIn === 'none')
27
+ coreImports.push('FactoryProvider');
28
+ lines.push(`import { ${coreImports.join(', ')} } from '@angular/core';`);
29
+ lines.push(`import { httpResource } from '@angular/common/http';`);
30
+ lines.push(`import type { paths } from '../schema.d';`);
31
+ lines.push(`import { ${baseUrlToken} } from '../api-base-url.token';`);
32
+ lines.push('');
33
+ // Exported type aliases sourced directly from the generated paths type.
34
+ // NonNullable<> guards optional requestBody fields that are typed as T | undefined.
35
+ if (isGet && ep.hasQueryParams) {
36
+ lines.push(`export type ${pascal}Params =`, ` paths['${ep.apiPath}']['${ep.method}']['parameters']['query'];`, '');
37
+ }
38
+ if (!isGet && ep.hasBody && ep.bodyContentType) {
39
+ lines.push(`export type ${pascal}Body =`, ` NonNullable<paths['${ep.apiPath}']['${ep.method}']['requestBody']>['content']['${ep.bodyContentType}'];`, '');
40
+ }
41
+ if (responseStatus) {
42
+ lines.push(`export type ${pascal}Response =`, ` paths['${ep.apiPath}']['${ep.method}']['responses']['${responseStatus}']['content']['application/json'];`, '');
43
+ }
44
+ const responseT = responseStatus ? `${pascal}Response` : 'unknown';
45
+ const fnArgs = buildFnArgs(ep, pascal, isGet);
46
+ lines.push(`export const ${ep.tokenName} = new InjectionToken<`, ` (${fnArgs}) => ReturnType<typeof httpResource<${responseT}>>`, `>('${ep.tokenName}'${providedIn === 'root' ? `, {` : ')'}`);
47
+ if (providedIn === 'root') {
48
+ lines.push(` providedIn: 'root',`, ` factory: () => {`, ` const base = inject(${baseUrlToken});`, ` return (${fnArgs}) =>`, ` httpResource<${responseT}>(() => ({`, ` url: \`\${base}${urlTemplate}\`,`);
49
+ appendResourceOptions(lines, ep, isGet, ' ');
50
+ lines.push(` }));`, ` },`, `});`, '');
51
+ }
52
+ else {
53
+ // providedIn: 'none' — token has no factory; provide via the helper below
54
+ lines.push('');
55
+ lines.push(`export function provide${pascal}(): FactoryProvider {`, ` return {`, ` provide: ${ep.tokenName},`, ` useFactory: () => {`, ` const base = inject(${baseUrlToken});`, ` return (${fnArgs}) =>`, ` httpResource<${responseT}>(() => ({`, ` url: \`\${base}${urlTemplate}\`,`);
56
+ appendResourceOptions(lines, ep, isGet, ' ');
57
+ lines.push(` }));`, ` },`, ` };`, `}`, '');
58
+ }
59
+ return lines.join('\n');
60
+ }
61
+ function appendResourceOptions(lines, ep, isGet, indent) {
62
+ if (!isGet) {
63
+ lines.push(`${indent}method: '${ep.method.toUpperCase()}',`);
64
+ }
65
+ if (isGet && ep.hasQueryParams) {
66
+ lines.push(`${indent}params: (typeof params === 'function' ? params() : params) as unknown as Record<string, string | number | boolean | readonly (string | number | boolean)[]>,`);
67
+ }
68
+ if (!isGet && ep.hasBody) {
69
+ lines.push(`${indent}body,`);
70
+ }
71
+ }
72
+ function buildFnArgs(ep, pascal, isGet) {
73
+ const args = ep.pathParams.map((p) => `${toCamelCase(p)}: string`);
74
+ if (isGet && ep.hasQueryParams)
75
+ args.push(`params?: ${pascal}Params | (() => ${pascal}Params | undefined)`);
76
+ if (!isGet && ep.hasBody)
77
+ args.push(`body: ${pascal}Body | Signal<${pascal}Body>`);
78
+ return args.join(', ');
79
+ }
80
+ //# sourceMappingURL=render-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-token.js","sourceRoot":"","sources":["../../../../../../tools/openapi-resource-gen/src/generators/api-resource/render-token.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "cli": "nx",
4
+ "$id": "ApiResource",
5
+ "title": "Generate API resource tokens from an OpenAPI spec",
6
+ "type": "object",
7
+ "properties": {
8
+ "specPath": {
9
+ "type": "string",
10
+ "description": "Path to the OpenAPI 3.x YAML or JSON spec file",
11
+ "$default": { "$source": "argv", "index": 0 }
12
+ },
13
+ "outputDir": {
14
+ "type": "string",
15
+ "description": "Output directory for generated files (relative to workspace root)"
16
+ },
17
+ "baseUrlToken": {
18
+ "type": "string",
19
+ "description": "Name of the InjectionToken holding the API base URL",
20
+ "default": "API_BASE_URL"
21
+ },
22
+ "tagFilter": {
23
+ "type": "string",
24
+ "description": "Comma-separated list of OpenAPI tags to include (empty = all tags)"
25
+ },
26
+ "namingConvention": {
27
+ "type": "string",
28
+ "enum": ["camel", "kebab"],
29
+ "default": "kebab",
30
+ "description": "File naming convention: kebab-case filenames with SCREAMING_SNAKE token names"
31
+ },
32
+ "providedIn": {
33
+ "type": "string",
34
+ "enum": ["root", "none"],
35
+ "default": "none",
36
+ "description": "none: token has no factory; a provide{Name}() helper is exported for explicit scoped provision. root: token self-registers at root (tree-shakeable singleton, no multi-context support)"
37
+ }
38
+ },
39
+ "required": ["specPath", "outputDir"]
40
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { apiResourceGenerator as default } from './generators/api-resource/generator';
2
+ export { apiResourceGenerator } from './generators/api-resource/generator';
3
+ export type { ApiResourceGeneratorSchema } from './generators/api-resource/generator';
package/src/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiResourceGenerator = exports.default = void 0;
4
+ var generator_1 = require("./generators/api-resource/generator");
5
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return generator_1.apiResourceGenerator; } });
6
+ var generator_2 = require("./generators/api-resource/generator");
7
+ Object.defineProperty(exports, "apiResourceGenerator", { enumerable: true, get: function () { return generator_2.apiResourceGenerator; } });
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../tools/openapi-resource-gen/src/index.ts"],"names":[],"mappings":";;;AAAA,iEAAsF;AAA7E,oGAAA,oBAAoB,OAAW;AACxC,iEAA2E;AAAlE,iHAAA,oBAAoB,OAAA"}