{"id":51756041,"url":"https://github.com/ynnckrkn/ng-openapi-signals","last_synced_at":"2026-07-19T06:41:24.237Z","repository":{"id":369255857,"uuid":"1289052962","full_name":"ynnckrkn/ng-openapi-signals","owner":"ynnckrkn","description":"Signal-first OpenAPI client generator for Angular using `resource()` and `fetch()`.","archived":false,"fork":false,"pushed_at":"2026-07-11T19:53:58.000Z","size":447,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-11T21:12:18.130Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ynnckrkn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","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-07-04T09:30:11.000Z","updated_at":"2026-07-11T19:54:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ynnckrkn/ng-openapi-signals","commit_stats":null,"previous_names":["ynnckrkn/ng-openapi-signals"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ynnckrkn/ng-openapi-signals","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ynnckrkn%2Fng-openapi-signals","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ynnckrkn%2Fng-openapi-signals/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ynnckrkn%2Fng-openapi-signals/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ynnckrkn%2Fng-openapi-signals/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ynnckrkn","download_url":"https://codeload.github.com/ynnckrkn/ng-openapi-signals/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ynnckrkn%2Fng-openapi-signals/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35644212,"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-07-19T02:00:06.923Z","response_time":112,"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-07-19T06:41:23.493Z","updated_at":"2026-07-19T06:41:24.231Z","avatar_url":"https://github.com/ynnckrkn.png","language":"TypeScript","funding_links":[],"categories":["Development Utilities"],"sub_categories":["Generators and Scaffolding"],"readme":"# ng-openapi-signals\n\nSignal-first OpenAPI client generator for Angular using `resource()` and `fetch()`.\n\n`ng-openapi-signals` generates lightweight Angular API clients from OpenAPI specifications.\nGET endpoints are generated as Angular `resource()` APIs, while mutating endpoints such as POST, PUT, PATCH and DELETE are generated as Promise-based `fetch()` methods.\n\n## Features\n\n- Generate Angular API clients from OpenAPI 3.x specifications\n- Signal-first read APIs using Angular `resource()`\n- Lightweight runtime based on native `fetch()`\n- No dependency on Angular `HttpClient` (optional `httpClient` transport available)\n- Typed models generated from OpenAPI schemas\n- Path parameter support\n- Query parameter support\n- Advanced query parameter serialization (OpenAPI `style`/`explode`: `form`, `spaceDelimited`, `pipeDelimited`, `deepObject`)\n- Header parameter support\n- JSON request body support\n- Multipart form data (`multipart/form-data`) and file upload support\n- `application/x-www-form-urlencoded` request body support\n- Custom request content types\n- JSON, text, `Blob`, `ArrayBuffer` and `ReadableStream` response handling\n- File download support\n- Fetch middleware (onion-style `(request, next) =\u003e response`)\n- Auth header hooks\n- Custom default headers\n- Custom error mapping\n- Request and response hooks\n- Base URL configuration via `provideNgOpenapiSignals()`\n- Optional signal-based mutations — reactive `result`/`error`/`status`/`isLoading` signals for POST/PUT/PATCH/DELETE\n\n## Requirements\n\n- Node.js 22 or newer\n- Angular 22 or newer\n- TypeScript\n- OpenAPI 3.x JSON or YAML specification\n\n---\n\n## How to Start\n\nGet up and running in four steps.\n\n### 1. Install\n\n```bash\nnpm install -D ng-openapi-signals\n```\n\nOr run it directly with `npx` (no install needed):\n\n```bash\nnpx ng-openapi-signals generate --input openapi.json --output src/generated/api\n```\n\n### 2. Generate the API client\n\nCreate a config file `ng-openapi-signals.config.ts` in your project root (recommended):\n\n```ts\nimport {defineConfig} from 'ng-openapi-signals/config';\n\nexport default defineConfig({\n  input: './openapi.json',\n  output: 'src/generated/api',\n});\n```\n\nThen run:\n\n```bash\nng-openapi-signals generate --config ng-openapi-signals.config.ts\n```\n\nOr generate without a config file:\n\n```bash\nng-openapi-signals generate \\\n  --input ./openapi.json \\\n  --output ./src/generated/api\n```\n\nThis generates an Angular API client in `src/generated/api`:\n\n```text\nsrc/generated/api/\n  api-fetch-client.ts        # or api-http-client.ts (depends on transport)\n  api-error.ts\n  signal-utils.ts\n  providers.ts\n  index.ts\n  models/\n    user.ts\n    create-user-request.ts\n    index.ts\n  resources/\n    users.api.ts\n    index.ts\n```\n\n### 3. Configure Angular\n\nProvide the API base URL in your application config:\n\n```ts\nimport {ApplicationConfig} from '@angular/core';\nimport {provideNgOpenapiSignals} from './generated/api';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideNgOpenapiSignals({\n      basePath: 'https://api.example.com',\n    }),\n  ],\n};\n```\n\n### 4. Use the generated API\n\n```ts\nimport {Component, inject, signal} from '@angular/core';\nimport {UsersApi} from './generated/api';\n\n@Component({\n  selector: 'app-user-detail',\n  template: `\n    @if (user.isLoading()) {\n      \u003cp\u003eLoading...\u003c/p\u003e\n    }\n\n    @if (user.error()) {\n      \u003cp\u003eSomething went wrong.\u003c/p\u003e\n    }\n\n    @if (user.hasValue()) {\n      \u003ch1\u003e{{ user.value().name }}\u003c/h1\u003e\n    }\n  `,\n})\nexport class UserDetailComponent {\n  private readonly usersApi = inject(UsersApi);\n\n  readonly userId = signal('123');\n\n  readonly user = this.usersApi.getUserByIdResource({\n    id: this.userId,\n  });\n}\n```\n\nThat's it — you now have a fully typed, signal-first Angular API client.\n\n---\n\n## CLI Usage\n\n### Generate\n\n```bash\nng-openapi-signals generate --input \u003copenapi-file\u003e --output \u003coutput-directory\u003e\n```\n\n### Options\n\n| Option                            | Description                                                   |\n| --------------------------------- | ------------------------------------------------------------- |\n| `-i, --input \u003cpath\u003e`              | Path to the OpenAPI JSON or YAML file                         |\n| `-o, --output \u003cpath\u003e`             | Output directory for the generated Angular client             |\n| `-c, --config \u003cpath\u003e`             | Path to config file (default: `ng-openapi-signals.config.ts`) |\n| `--clean`                         | Clean output directory before generation (default: true)      |\n| `--no-clean`                      | Preserve existing files in output directory                   |\n| `--group-by \u003ctag\\|path\u003e`          | Group APIs by tag or path (default: tag)                      |\n| `--transport \u003cfetch\\|httpClient\u003e` | HTTP transport (default: fetch)                               |\n| `--default-query-style \u003cstyle\u003e`   | Default query param style: form, spaceDelimited, pipeDelimited, or deepObject |\n| `--default-query-explode \u003cbool\u003e` | Default query param explode (true/false)                      |\n| `--prefer-content-type \u003ctype\u003e`   | Preferred request content type when multiple are offered       |\n| `--signal-mutations`             | Enable signal-based mutation methods (default: false)            |\n| `--date-transformer`            | Convert ISO-8601 date strings in JSON responses to Date objects (default: false) |\n| `--dry-run`                       | Print the files that would be generated without writing to disk |\n| `--check`                         | Verify generated output is up to date (exits 1 on mismatch; for CI) |\n| `--verbose`                       | Show detailed progress and file lists                         |\n\n### CI: verify generated output\n\nUse `--check` in CI to verify the generated client is up to date:\n\n```bash\nng-openapi-signals generate --input ./openapi.json --output ./src/generated/api --check\n```\n\nThe command exits with code `1` when generated files are outdated or missing. Stale files (on disk but no longer in the spec) are reported as warnings but do not fail the check.\n\n### Preview without writing: `--dry-run`\n\n```bash\nng-openapi-signals generate --input ./openapi.json --output ./src/generated/api --dry-run --verbose\n```\n\nGenerates the client in memory and lists the files (path + line count) without touching disk.\n\n### Recommended: use a config file\n\nUsing a config file keeps your setup reproducible and version-controllable.\n\n```ts\n// ng-openapi-signals.config.ts\nimport {defineConfig} from 'ng-openapi-signals/config';\n\nexport default defineConfig({\n  input: './openapi.json',\n  output: './src/generated/api',\n  clean: true,\n  groupBy: 'tag',\n});\n```\n\nThen add a script to your `package.json`:\n\n```json\n{\n  \"scripts\": {\n    \"generate:api\": \"ng-openapi-signals generate --config ng-openapi-signals.config.ts\"\n  }\n}\n```\n\nRun it with:\n\n```bash\nnpm run generate:api\n```\n\nYou can also generate the API client before building your Angular app:\n\n```json\n{\n  \"scripts\": {\n    \"generate:api\": \"ng-openapi-signals generate --config ng-openapi-signals.config.ts\",\n    \"build\": \"npm run generate:api \u0026\u0026 ng build\",\n    \"start\": \"npm run generate:api \u0026\u0026 ng serve\"\n  }\n}\n```\n\n### CLI overrides\n\nCLI flags override config file values. Config file values override defaults.\n\n```bash\nng-openapi-signals generate \\\n  --input ./openapi.json \\\n  --output ./src/generated/api \\\n  --group-by path\n```\n\n---\n\n## Generated API Style\n\n- **GET endpoints** → Angular `resource()` APIs (accept plain values or signals)\n- **POST / PUT / PATCH / DELETE** → Promise-based `fetch()` methods\n- **Signal-based mutations** (opt-in via `runtime.signalMutations`) → `${operationId}Mutation()` methods returning a `Mutation` with `result`/`error`/`status`/`isLoading` signals\n\n```ts\n// GET — reactive resource\nreadonly user = this.usersApi.getUserByIdResource({\n  id: this.userId,  // signal or plain value\n});\n\n// POST — promise-based mutation (default)\nawait this.usersApi.createUser({\n  name: 'John Doe',\n  email: 'john@example.com',\n});\n```\n\n### Signal-based mutations (opt-in)\n\nWhen `runtime.signalMutations` is enabled, the generator additionally emits\na `${operationId}Mutation()` method for every POST/PUT/PATCH/DELETE endpoint,\nalongside the existing Promise-based method (strictly additive).\n\n```ts\n// Signal-based mutation — reactive state, no manual `busy` flag\nreadonly creating = this.usersApi.createUserMutation();\n\ncreate(): void {\n  this.creating.mutate({ name: 'John Doe', email: 'john@example.com' });\n}\n\n// In the template:\n//   creating.isLoading()  → boolean signal\n//   creating.result()     → the created user (or undefined)\n//   creating.error()      → the last error (or undefined)\n//   creating.status()     → 'idle' | 'loading' | 'success' | 'error'\n//   creating.reset()      → clears result/error, returns to 'idle'\n```\n\nFor endpoints with path/query/header parameters, the parameters are bound\nat construction time (captured in the closure), and only the request body\nis passed to `mutate(body)`:\n\n```ts\nreadonly uploading = this.usersApi.uploadUserAvatarMutation({\n  id: this.userId,  // signal or plain value\n});\n\nupload(): void {\n  this.uploading.mutate({ file: this.file, caption: 'Profile photo' });\n}\n```\n\nEnable the feature via the config file or CLI:\n\n```ts\n// ng-openapi-signals.config.ts\nexport default defineConfig({\n  input: './openapi.json',\n  output: './src/generated/api',\n  runtime: { signalMutations: true },\n});\n```\n\n```bash\nng-openapi-signals generate --signal-mutations\n```\n\n\u003e See [`RUNTIME.md`](./RUNTIME.md) for full details on `MaybeSignal\u003cT\u003e`, the `Mutation` interface, response parsing, and more.\n\n### Date Transformer\n\nWhen `runtime.dateTransformer` is enabled (default `false`), the generator emits a `date-utils.ts` runtime file with a recursive `transformDates()` function that converts ISO-8601 date-time strings (e.g. `2026-07-15T12:00:00Z`) found anywhere in a parsed JSON response body into `Date` instances. Non-JSON responses (text, blob, arrayBuffer, stream) are left untouched.\n\n```ts\n// ng-openapi-signals.config.ts\nimport {defineConfig} from 'ng-openapi-signals/config';\n\nexport default defineConfig({\n  input: './openapi.json',\n  output: './src/generated/api',\n  runtime: { dateTransformer: true },\n});\n```\n\n```bash\nng-openapi-signals generate --date-transformer\n```\n\nThe transformer is applied automatically inside the generated client's JSON parsing path — no additional setup is needed at runtime. Works with both `fetch` and `httpClient` transports.\n\n### Example snippets\n\nThe repository includes standalone, commented example files in [`examples/usage/`](./examples/usage/):\n\n- `resource-usage.ts` — GET endpoint with `resource()` and signals\n- `mutation-usage.ts` — POST/PUT/PATCH/DELETE as Promises\n- `mutation-signal-usage.ts` — signal-based mutation (`runtime.signalMutations`)\n- `mutation-signal-params-usage.ts` — signal-based mutation with path/query/header params\n- `auth-interceptor.ts` — auth headers and fetch middleware\n- `http-client-usage.ts` — `httpClient` transport setup\n- `multipart-upload.ts` — file upload with `FormData`\n- `date-transform-usage.ts` — automatic ISO-8601 date string → Date conversion (`runtime.dateTransformer`)\n\nThese are illustrative only — adjust the import paths to your generated client directory. They are not included in the npm package.\n\n---\n\n## Runtime\n\nThe generated client includes a small runtime:\n\n```text\napi-fetch-client.ts   (or api-http-client.ts)\napi-error.ts\nsignal-utils.ts\nmutation-utils.ts     (only when runtime.signalMutations is enabled)\ndate-utils.ts        (only when runtime.dateTransformer is enabled)\nproviders.ts\n```\n\n- **`ApiFetchClient`** — wraps native `fetch()`, handles base URL, JSON/text/Blob responses, query params, abort signals, middleware, hooks, and error mapping.\n- **`ApiHttpClient`** — wraps Angular `HttpClient` (when `transport: 'httpClient'`), same feature set, integrates with `HttpInterceptors`.\n- **`provideNgOpenapiSignals()`** — configures the runtime (base URL, headers, auth, middleware, hooks, error mapper).\n\n\u003e See [`RUNTIME.md`](./RUNTIME.md) for the full `provideNgOpenapiSignals()` API and all runtime extension points.\n\n---\n\n## Configuration\n\n`ng-openapi-signals` supports an optional config file for project-level defaults.\n\n### Options\n\n| Option    | Type              | Default | Description                                             |\n| --------- | ----------------- | ------- | ------------------------------------------------------- |\n| `input`   | `string`          | —       | Path to the OpenAPI JSON or YAML file                   |\n| `output`  | `string`          | —       | Output directory for the generated Angular client       |\n| `clean`   | `boolean`         | `true`  | Clean output directory before generation                |\n| `groupBy` | `'tag' \\| 'path'` | `'tag'` | Group generated APIs by OpenAPI tag or URL path segment |\n| `runtime` | `RuntimeConfig`   | `{}`    | Runtime options (see below)                             |\n\n#### `runtime`\n\n| Option              | Type                      | Default     | Description                                                                |\n| ------------------- | ------------------------- | ----------- | -------------------------------------------------------------------------- |\n| `transport`         | `'fetch' \\| 'httpClient'` | `'fetch'`   | HTTP transport (`fetch` = native fetch, `httpClient` = Angular HttpClient) |\n| `defaultHeaders`    | `Record\u003cstring, string\u003e`  | `{}`        | Static default headers baked into `provideNgOpenapiSignals` defaults       |\n| `responseTypeHints` | `boolean`                 | `true`      | Emit `responseType` hints in generated methods based on response content   |\n| `defaultQueryStyle` | `'form' \\| 'spaceDelimited' \\| 'pipeDelimited' \\| 'deepObject'` | `'form'` | Default query param serialization style when the spec doesn't specify `style` |\n| `defaultQueryExplode` | `boolean`               | `true`      | Default `explode` flag for query params when the spec doesn't specify it  |\n| `preferContentType` | `string`                  | `'application/json'` | Preferred content type when a request body offers multiple media types  |\n| `signalMutations`   | `boolean`                 | `false`     | Generate `${operationId}Mutation()` methods with reactive signals for POST/PUT/PATCH/DELETE |\n| `dateTransformer`  | `boolean`                 | `false`     | Convert ISO-8601 date-time strings in JSON responses to `Date` instances (emits `date-utils.ts`) |\n\n### Using the `httpClient` transport\n\nBy default the generated runtime uses native `fetch()`.\nTo use Angular `HttpClient` instead (e.g. to integrate with `HttpInterceptors`), set `transport: 'httpClient'`:\n\n```ts\n// ng-openapi-signals.config.ts\nimport {defineConfig} from 'ng-openapi-signals/config';\n\nexport default defineConfig({\n  input: './openapi.json',\n  output: './src/generated/api',\n  runtime: {\n    transport: 'httpClient',\n  },\n});\n```\n\nOr via the CLI:\n\n```bash\nng-openapi-signals generate --input ./openapi.json --output ./src/generated/api --transport httpClient\n```\n\nWhen `httpClient` is selected:\n\n- The generator emits `ApiHttpClient` instead of `ApiFetchClient`.\n- `provideNgOpenapiSignals()` does **not** include `provideHttpClient()` — register it yourself in your app config (e.g. `provideHttpClient(withInterceptors([...]))`) so you keep full control over interceptors and their order.\n- The `NG_OPENAPI_SIGNALS_MIDDLEWARE` token is not emitted.\n- Generated API service methods (`resource()` loaders, mutations) remain identical — only the underlying client changes.\n\n### Grouping\n\nBy default, APIs are grouped by OpenAPI tag (`groupBy: 'tag'`).\nEach tag becomes one service file: `resources/\u003ctag\u003e.api.ts`.\n\nSet `groupBy: 'path'` to group by the first path segment instead.\nFor example, `/users/{id}` and `/users` are grouped into `resources/users.api.ts`.\n\n### Preserving output\n\nSet `clean: false` to preserve existing files in the output directory:\n\n```ts\nexport default defineConfig({\n  input: './openapi.json',\n  output: './src/generated/api',\n  clean: false,\n});\n```\n\n---\n\n## Advanced Request Support\n\n### Query Parameter Serialization\n\nThe generator supports OpenAPI parameter `style` and `explode` for query parameters:\n\n| Style             | `explode: true`              | `explode: false`              |\n| ----------------- | ---------------------------- | ------------------------------ |\n| `form` (default)  | `tags=a\u0026tags=b` (repeated)    | `tags=a,b` (comma-separated)   |\n| `spaceDelimited`  | `tags=a\u0026tags=b` (repeated)    | `tags=a b` (space-separated)   |\n| `pipeDelimited`   | `tags=a\u0026tags=b` (repeated)    | `tags=a\\|b` (pipe-separated)    |\n| `deepObject`      | `filters[status]=active`     | —                              |\n\nParameters with default style (`form` + `explode: true`) are passed as plain values for backward compatibility.\nNon-default styles are wrapped with metadata: `{ value: params.tags, style: 'spaceDelimited', explode: false }`.\n\n### Multipart Form Data and File Upload\n\nWhen a request body uses `multipart/form-data`, the generator emits `formData: body` instead of `body:`.\nThe runtime builds a `FormData` object from the typed input. Binary parts (`format: binary`) are typed as `Blob`.\n\n```ts\n// OpenAPI: multipart/form-data with file + caption\nawait this.usersApi.uploadUserAvatar(\n  { file: blob, caption: 'Profile photo' },\n  { id: 'usr_123' },\n);\n```\n\nThe runtime automatically:\n- Builds `FormData` from the typed object\n- Appends `Blob` values directly (no JSON serialization)\n- Lets the browser set the `Content-Type` with the multipart boundary\n\n### `application/x-www-form-urlencoded`\n\nFor URL-encoded form bodies, the runtime builds `URLSearchParams` from the typed object.\n\n### Custom Content Types\n\nWhen a request body uses a non-JSON content type (e.g. `application/octet-stream`),\nthe generator emits a `contentType` field. The runtime passes `Blob`/`ArrayBuffer` bodies\nthrough without JSON serialization.\n\n### File Download and Stream Responses\n\nBinary responses (`image/*`, `application/octet-stream`, etc.) are handled as `Blob` or `ArrayBuffer`.\n`text/event-stream` responses are handled as `ReadableStream` (fetch transport) or `Blob` (httpClient transport).\n\n### Header Parameters\n\nHeader parameters (`in: header`) are generated as method arguments and merged into the request `headers` object.\nHeader names with hyphens (e.g. `X-Request-Id`) are properly quoted in TypeScript.\n\n---\n\n## Current Scope\n\nFor the full list of planned features and milestones, see the [Roadmap](./ROADMAP.md).  \nFor release notes and version history, see the [Changelog](./CHANGELOG.md).\n\n## Design Philosophy\n\n`ng-openapi-signals` follows a simple design:\n\n```text\nGET endpoints\n→ Angular resource() + fetch()\n\nPOST / PUT / PATCH / DELETE endpoints\n→ Promise-based fetch() methods\n```\n\nThe goal is to generate Angular code that feels natural in modern signal-based applications while keeping the runtime small and easy to understand.\n\n## Generated Code\n\nGenerated files include this header:\n\n```ts\n// Auto-generated by ng-openapi-signals.\n// Do not edit manually.\n```\n\nDo not manually edit generated files.\nChange your OpenAPI specification or generator configuration instead.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fynnckrkn%2Fng-openapi-signals","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fynnckrkn%2Fng-openapi-signals","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fynnckrkn%2Fng-openapi-signals/lists"}