{"id":50660428,"url":"https://github.com/constantant/angular-openapi-gen","last_synced_at":"2026-06-15T01:07:46.065Z","repository":{"id":363158783,"uuid":"1260809112","full_name":"constantant/angular-openapi-gen","owner":"constantant","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-08T00:36:56.000Z","size":5277,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-08T02:24:10.518Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/constantant.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-05T22:35:46.000Z","updated_at":"2026-06-08T00:37:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/constantant/angular-openapi-gen","commit_stats":null,"previous_names":["constantant/angular-openapi-gen"],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/constantant/angular-openapi-gen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/constantant%2Fangular-openapi-gen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/constantant%2Fangular-openapi-gen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/constantant%2Fangular-openapi-gen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/constantant%2Fangular-openapi-gen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/constantant","download_url":"https://codeload.github.com/constantant/angular-openapi-gen/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/constantant%2Fangular-openapi-gen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34343404,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-14T02:00:07.365Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-08T02:02:31.728Z","updated_at":"2026-06-15T01:07:46.059Z","avatar_url":"https://github.com/constantant.png","language":"TypeScript","funding_links":[],"categories":["Development Utilities"],"sub_categories":["Generators and Scaffolding"],"readme":"# angular-openapi-gen\n\n[![CI](https://github.com/constantant/angular-openapi-gen/actions/workflows/ci.yml/badge.svg)](https://github.com/constantant/angular-openapi-gen/actions/workflows/ci.yml)\n[![npm: openapi-resource-gen](https://img.shields.io/npm/v/@constantant/openapi-resource-gen?label=openapi-resource-gen)](https://www.npmjs.com/package/@constantant/openapi-resource-gen)\n[![npm: openapi-resource-mocks](https://img.shields.io/npm/v/@constantant/openapi-resource-mocks?label=openapi-resource-mocks)](https://www.npmjs.com/package/@constantant/openapi-resource-mocks)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Angular 22+](https://img.shields.io/badge/Angular-22%2B-dd0031?logo=angular\u0026logoColor=white)](https://angular.dev)\n\n\u003e **Requires Angular 22+ and Nx 22+.**\n\u003e The generated code uses [`httpResource()`](https://angular.dev/guide/http/http-resource),\n\u003e which is only available from Angular 22 onwards.\n\nAn Angular 22 · Nx monorepo that demonstrates **tree-shakeable, signal-native API clients** generated from OpenAPI 3.x specs.\n\nThe core idea: one `InjectionToken` per API endpoint, each in its own `.ts` file.\nBecause esbuild tree-shakes at file boundaries, any token you never `inject()` costs zero bytes in your bundle.\n\n---\n\n## What's in this repo\n\n| Path | Type | Description |\n|------|------|-------------|\n| `tools/openapi-resource-gen/` | Nx generator · npm package | Reads an OpenAPI spec, emits one token file per endpoint |\n| `tools/openapi-resource-mocks/` | npm package | Zero-HTTP mock bus for generated tokens — Playwright E2E + Chrome Extension integration |\n| `tools/openapi-resource-devtools/` | Chrome Extension shell | Manifest, content script, service worker, devtools page |\n| `apps/devtools-panel/` | Angular 22 app | Panel UI bundled inside the Chrome Extension |\n| `libs/github-data-access/` | Generated data-access lib | GitHub REST API (~38 endpoints used) |\n| `libs/petstore-data-access/` | Generated data-access lib | OAI Petstore v3 (12 endpoints) |\n| `libs/weather-data-access/` | Generated data-access lib | Open-Meteo forecast API |\n| `libs/youtube-data-access/` | Generated data-access lib | YouTube Data API v3 (76 endpoints) |\n| `apps/api-explorer/` | Angular 22 app | Demo app that consumes all data-access libs |\n\n---\n\n## The generator — `@constantant/openapi-resource-gen`\n\nPublished on npm: **[`@constantant/openapi-resource-gen`](https://www.npmjs.com/package/@constantant/openapi-resource-gen)**\n\n### Quick start\n\n**Step 1 — install the generator** (once per workspace):\n\n```bash\nnpm install -D @constantant/openapi-resource-gen\n```\n\n**Step 2 — generate a data-access lib** from any OpenAPI 3.x spec (local file or URL):\n\n```bash\nnpx nx g @constantant/openapi-resource-gen:api-resource \\\n  --specPath=https://petstore3.swagger.io/api/v3/openapi.yaml \\\n  --outputDir=libs/petstore-data-access/src \\\n  --baseUrlToken=PETSTORE_BASE_URL\n```\n\n**Step 3 — wire up providers and inject in your component:**\n\n```typescript\n// app.config.ts\nimport { provideHttpClient } from '@angular/common/http';\nimport { PETSTORE_BASE_URL, provideFindPetsByStatus } from './libs/petstore-data-access/src';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideHttpClient(),\n    { provide: PETSTORE_BASE_URL, useValue: 'https://petstore3.swagger.io/api/v3' },\n    provideFindPetsByStatus(),\n  ],\n};\n```\n\n```typescript\n// pets-page.component.ts\n@Component({ ... })\nexport class PetsPageComponent {\n  private findPetsByStatus = inject(FIND_PETS_BY_STATUS);\n  readonly status = signal\u003c'available' | 'pending' | 'sold'\u003e('available');\n  readonly pets = this.findPetsByStatus(() =\u003e ({ status: this.status() }));\n}\n```\n\nRe-run the generator command whenever your spec changes — it overwrites generated files and removes any that no longer exist in the spec.\n\n### Generator options\n\n| Option | Required | Default | Description |\n|--------|----------|---------|-------------|\n| `specPath` | yes | — | Local path **or** `https://` URL to the OpenAPI 3.x YAML or JSON spec |\n| `outputDir` | yes | — | Output directory relative to workspace root |\n| `baseUrlToken` | no | `API_BASE_URL` | Name of the base-URL injection token |\n| `tagFilter` | no | all tags | Comma-separated list of tags to include |\n| `namingConvention` | no | `kebab` | `kebab` or `camel` — controls file names |\n| `providedIn` | no | `none` | `none` (use `provideX()` helpers) or `root` (self-registering) |\n| `includeMocks` | no | `false` | Co-generate `.mock.ts` providers, `index.mock.ts` barrels, and `mocks.manifest.json` — requires `@constantant/openapi-resource-mocks` |\n| `specId` | no | derived | Identifier embedded in `MockResourceMeta` and `mocks.manifest.json`. Defaults to `baseUrlToken` with `_BASE_URL` stripped (e.g. `PETSTORE_BASE_URL` → `petstore`). Must match when importing into the DevTools panel. |\n| `verbose` | no | `false` | Print a `+`/`~`/`-` summary of created, updated, and deleted files after generation. |\n\nSee [`tools/openapi-resource-gen/README.md`](tools/openapi-resource-gen/README.md) for full documentation.\n\n---\n\n## Generated token pattern\n\nEvery endpoint becomes a typed `InjectionToken` whose factory returns an `httpResource`.\nFor GET endpoints with query params, the reactive lambda uses a block-body form so it can\nreturn `undefined` to suppress the request when a thunk-based params arg returns `undefined`:\n\n```typescript\n// libs/petstore-data-access/src/pet/find-pets-by-status.token.ts\nimport { InjectionToken, inject, FactoryProvider } from '@angular/core';\nimport { httpResource } from '@angular/common/http';\nimport type { paths } from '../schema.d';\nimport { PETSTORE_BASE_URL } from '../api-base-url.token';\n\nexport type FindPetsByStatusParams =\n  paths['/pet/findByStatus']['get']['parameters']['query'];\nexport type FindPetsByStatusResponse =\n  paths['/pet/findByStatus']['get']['responses']['200']['content']['application/json'];\n\nexport const FIND_PETS_BY_STATUS = new InjectionToken\u003c\n  (params?: FindPetsByStatusParams | (() =\u003e FindPetsByStatusParams | undefined))\n    =\u003e ReturnType\u003ctypeof httpResource\u003cFindPetsByStatusResponse\u003e\u003e\n\u003e('FIND_PETS_BY_STATUS');\n\nexport function provideFindPetsByStatus(): FactoryProvider {\n  return {\n    provide: FIND_PETS_BY_STATUS,\n    useFactory: () =\u003e {\n      const base = inject(PETSTORE_BASE_URL);\n      return (params?) =\u003e\n        httpResource\u003cFindPetsByStatusResponse\u003e(() =\u003e {\n          const _params = typeof params === 'function' ? params() : params;\n          if (typeof params === 'function' \u0026\u0026 _params === undefined) return undefined;\n          return {\n            url: `${base}/pet/findByStatus`,\n            params: _params as unknown as Record\u003cstring, string | number | boolean | readonly (string | number | boolean)[]\u003e,\n          };\n        });\n    },\n  };\n}\n```\n\nKey properties of every generated file:\n- **Zero runtime overhead** — all types come from `schema.d.ts` generated by `openapi-typescript`\n- **Tree-shakeable** — a token not injected anywhere is never imported, so esbuild drops the entire file\n- **Signal-native** — `httpResource` re-fires the request automatically when any signal inside the reactive lambda changes\n- **Request suppression** — returning `undefined` from the lambda keeps the resource idle (no request)\n- **Scoped base URL** — each lib has its own `InjectionToken\u003cstring\u003e` so different parts of an app can point at different environments\n- **Header params** — `in: header` parameters become named string args on the factory function and are merged into the `headers` object alongside any auth scheme headers\n- **Cookie params** — `in: cookie` parameters become named string args (after header params) and are combined into a single `Cookie` header value; optional cookies are conditionally included\n- **Binary body** — non-json/form/multipart request bodies (e.g. `application/octet-stream`, `image/*`) emit `Blob | ArrayBuffer` as the body type\n- **Response type unions** — when an endpoint returns multiple 2xx JSON response codes (e.g. 200 and 201), the generated `Response` type alias is a union of all of them\n- **`@deprecated` JSDoc** — operations marked `deprecated: true` in the spec emit `/** @deprecated */` above the token constant, surfacing the warning at every `inject()` call site\n- **Security tokens** — signal-based schemes (`bearer`, `basic`, `apiKey`) emit `InjectionToken\u003cSignal\u003cstring | null\u003e\u003e`; `digest` schemes emit `InjectionToken\u003cHttpInterceptorFn\u003e` + a named, host-scoped interceptor that delegates only to requests matching the lib's base URL, preventing cross-API conflicts\n\n---\n\n## Using generated tokens\n\n### 1. Register providers in `app.config.ts`\n\n```typescript\nimport { provideHttpClient } from '@angular/common/http';\nimport { PETSTORE_BASE_URL, provideFindPetsByStatus } from '@angular-openapi-gen/petstore-data-access';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideHttpClient(),\n    { provide: PETSTORE_BASE_URL, useValue: 'https://petstore3.swagger.io/api/v3' },\n    provideFindPetsByStatus(),\n  ],\n};\n```\n\n### 2. Inject and call in a component\n\n```typescript\n@Component({ ... })\nexport class PetsPageComponent {\n  private findPetsByStatus = inject(FIND_PETS_BY_STATUS);\n\n  readonly status = signal\u003c'available' | 'pending' | 'sold'\u003e('available');\n\n  // Thunk → httpResource re-fetches whenever status() changes\n  readonly pets = this.findPetsByStatus(() =\u003e ({ status: this.status() }));\n}\n```\n\n```html\n@if (pets.isLoading()) { \u003cmat-progress-bar mode=\"indeterminate\" /\u003e }\n@for (pet of pets.value() ?? []; track pet.id) {\n  \u003cp\u003e{{ pet.name }}\u003c/p\u003e\n}\n```\n\n### Conditional requests (thunk returning undefined)\n\nPass a thunk that returns `undefined` to suppress the request until conditions are met:\n\n```typescript\n// No request fires until both apiKey and query are set\nreadonly results = this.youtubeSearch(() =\u003e\n  this.apiKey() \u0026\u0026 this.query()\n    ? { q: this.query(), key: this.apiKey()! }\n    : undefined\n);\n```\n\n---\n\n## Common commands\n\n```bash\n# Serve the demo app\nnpx nx serve api-explorer\n\n# Production build\nnpx nx build api-explorer\nnpx nx build api-explorer --stats-json   # include esbuild bundle stats\n\n# Run tests\nnpx nx test openapi-resource-gen         # generator unit tests\nnpx nx e2e api-explorer-e2e             # Playwright E2E tests\n\n# Lint everything\nnpx nx run-many -t lint\n\n# Type-check everything\nnpx nx run-many -t typecheck\n\n# Generate from a local file\nnpx nx g @constantant/openapi-resource-gen:api-resource \\\n  --specPath=specs/myapi.yaml \\\n  --outputDir=libs/myapi-data-access/src \\\n  --baseUrlToken=MYAPI_BASE_URL\n\n# Generate from a URL (no curl step needed)\nnpx nx g @constantant/openapi-resource-gen:api-resource \\\n  --specPath=https://petstore3.swagger.io/api/v3/openapi.yaml \\\n  --outputDir=libs/petstore-data-access/src \\\n  --baseUrlToken=PETSTORE_BASE_URL\n\n# Or declare a generate target in project.json and run:\nnpx nx run petstore-data-access:generate\n```\n\n---\n\n## Mock bus — `@constantant/openapi-resource-mocks`\n\nPublished on npm: **[`@constantant/openapi-resource-mocks`](https://www.npmjs.com/package/@constantant/openapi-resource-mocks)**\n\nA companion package that provides zero-HTTP, pure-DI mocks for generated tokens. Key features:\n\n- `provideMockResourceBus()` — registers the bus; exposes `window.__openApiMocks__` and `openApiMock(key)` for Playwright\n- `provideMockResource(token, key, initialBehavior?, meta?)` — replaces a token's factory with a mock; the optional `meta` (`MockResourceMeta`) is embedded automatically in generated `.mock.ts` files and used by the DevTools panel to resolve response schemas\n- DOM event bridge (`openapi-mock-event` / `openapi-mock-control`) — lets the Chrome Extension DevTools panel observe and control mocks in real time\n- `MockResourceRef\u003cT\u003e` — `resolve()`, `setLoading()`, `fail()`, `reset()`, `simulateProgress()`, `getHistory()`\n\nSee [`tools/openapi-resource-mocks/README.md`](tools/openapi-resource-mocks/README.md) for full documentation.\n\n---\n\n## Chrome DevTools Extension — OpenAPI Resource Mocks DevTools\n\nCurrent version: **0.6.0** | Status: pending Chrome Web Store review\n\nA Chrome DevTools panel that connects to any Angular app running `@constantant/openapi-resource-mocks`. It lists every registered mock token, shows live state, and lets you resolve, fail, catch, or reset mocks without touching code. The **Specs** tab lets you import a `mocks.manifest.json` or full OpenAPI spec to enable schema-aware features in the Respond tab: auto-generated example payloads (⚡ Example) and JSON schema validation (✓ Validate).\n\n### Install\n\n\u003e **Chrome Web Store:** pending review — use **Load unpacked** for now.\n\n1. Clone the repo and install dependencies: `npm ci`\n2. Build the extension: `npx nx run openapi-resource-devtools:build`\n3. Open `chrome://extensions` → enable **Developer mode** → **Load unpacked** → select `dist/tools/openapi-resource-devtools/`\n\nSee [`tools/openapi-resource-devtools/README.md`](tools/openapi-resource-devtools/README.md) for full documentation.\n\n---\n\n## Release workflow\n\nThe generator is released via the **Release** GitHub Actions workflow (`.github/workflows/release.yml`), triggered manually from the Actions tab.\n\nWhat the workflow does:\n1. Runs `nx release --skip-publish` — determines the version bump from conventional commits (`fix:` → patch, `feat:` → minor), updates `package.json`, writes `CHANGELOG.md`, creates a git commit and tag\n2. Pushes the version commit and tag to `master`\n3. Creates a GitHub Release with changelog notes extracted from `CHANGELOG.md`\n4. Builds the package with `nx build openapi-resource-gen --skip-nx-cache`\n5. Publishes to npm as `@constantant/openapi-resource-gen` using `NPM_TOKEN` stored in GitHub secrets\n\nThe workflow is idempotent — if the current version is already on npm it skips publishing gracefully.\n\n**Note on nx release commit detection**: nx release counts only commits that touch files within `tools/openapi-resource-gen/`. Workflow-only changes (e.g. editing `.github/`) do not trigger a version bump.\n\n**Note on branch protection**: `master` is protected (PRs require CI + a code-owner review, linear history). The release workflow checks out with `GH_PAT` (a repo admin PAT stored in GitHub secrets) instead of `GITHUB_TOKEN` — `GITHUB_TOKEN` cannot bypass branch protection's required status checks even with `enforce_admins: off`, so the version-bump commit and tag push would fail without a PAT.\n\nThe Chrome Extension is released via a separate **Release Extension** workflow (`.github/workflows/release-extension.yml`). It bumps `manifest.json`, builds and zips the extension, creates a GitHub Release, and uploads to the Chrome Web Store. Required secrets: `GH_PAT`, `CHROME_EXTENSION_ID`, `CHROME_PUBLISHER_ID`, `CHROME_CLIENT_ID`, `CHROME_CLIENT_SECRET`, `CHROME_REFRESH_TOKEN`.\n\n---\n\n## Contributing\n\nContributions are welcome! Please read **[CONTRIBUTING.md](CONTRIBUTING.md)** before\nopening a PR — in particular the rule that **generated code under `libs/*/src/` is\nnever hand-edited** (fix the generator and regenerate instead).\n\n- Code of Conduct: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)\n- Security policy: [SECURITY.md](SECURITY.md)\n- Questions \u0026 ideas: [GitHub Discussions](https://github.com/constantant/angular-openapi-gen/discussions)\n\n---\n\n## Angular 22 patterns used\n\n- **`httpResource()`** — signal-native HTTP wrapper; re-fires when signals inside its reactive lambda change; returns `undefined` from the lambda to suppress the request\n- **`InjectionToken` with factory** — tree-shakeable when `providedIn: 'root'`; or use the emitted `provideX()` helper for scope control\n- **Standalone components** — no `NgModule`, no `zone.js`\n- **`@if` / `@for` / `@switch`** — Angular 17+ control flow syntax throughout\n- **`OnPush`** — default change detection; do not set it explicitly\n- **Signals** — `signal()` + `computed()` for all local state; no RxJS in components\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconstantant%2Fangular-openapi-gen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconstantant%2Fangular-openapi-gen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconstantant%2Fangular-openapi-gen/lists"}