{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/codegen-architecture","markdown_url":"https://test.abhinandan.one/codegen-architecture.md","article":{"id":"ae23d173-9aa8-454f-bbaf-d427ea821052","article_slug":"codegen-architecture","parent_article_slug":null,"parent_article_title":null,"kind":"concept","published_at":"2026-08-11T18:55:04.116589+00:00","keywords":["api-core","@primitivedotdev/api-core","primitive-api.yaml","primitive-api.codegen.json","PrimitiveApiClient","operation manifest"],"meta_description":"One OpenAPI 3.1 spec at openapi/primitive-api.yaml feeds api-core's TypeScript generator, then Node, Python, and Go each generate their own typed client from it.","og_image_url":null,"source_file_paths":["packages/api-core/package.json","packages/api-core/src/index.ts","openapi/primitive-api.yaml"],"recording_id":null,"replayable":false,"task_name":"Architecture: Shared Codegen Pipeline","category":"API Core / OpenAPI Generation","summary":null,"description":"The codegen pipeline turns one hand-written OpenAPI 3.1 spec into typed clients for Node, Python, and Go, with api-core as the never-published TypeScript hub that sdk-node and cli-node bundle inline.","content_kind":"repo_page","content_markdown":"## What the shared codegen pipeline is\n\nThe shared codegen pipeline is the flow that turns one hand-written OpenAPI 3.1 document, `openapi/primitive-api.yaml`, into every generated HTTP client in this repository: the Node SDK's fetch client, the CLI's operation manifest, the Python SDK's `openapi-python-client` output, and the Go SDK's `ogen` client. Nothing downstream hand-edits generated code; every language regenerates from the same spec.\n\nThe TypeScript half of that pipeline lives in one workspace-internal package, `@primitivedotdev/api-core`, which both `@primitivedotdev/sdk` (sdk-node) and `primitive` (cli-node) depend on and bundle inline.\n\n## Why api-core exists and is never published\n\n`api-core` exists so sdk-node and cli-node share one generated TypeScript client, and it is never published because `packages/api-core/package.json` sets `\"private\": true` (version `0.0.0`). Its description states the role directly: a workspace-internal TypeScript OpenAPI surface holding the generated client, the operation manifest, and the host-aware `PrimitiveApiClient`, shared by the Node SDK and the CLI. Both consumers bundle it inline at build time, so the published npm tarballs for `@primitivedotdev/sdk` and `primitive` declare no dependency on api-core and no dependency on each other.\n\nThis split exists to keep sdk-node and cli-node decoupled at the source level while avoiding two copies of the generated OpenAPI client. Without api-core, either:\n\n- sdk-node and cli-node would each generate and maintain their own copy of the TypeScript client (drift risk), or\n- one package would depend on the other (coupling the CLI's release cadence to the SDK's, or vice versa).\n\napi-core solves both by being a shared build-time-only dependency that disappears from the published artifact.\n\n## What api-core exports\n\n`packages/api-core/src/index.ts` is the single entry point, re-exporting the generated operations and types, the client primitives, the OpenAPI document and operation manifest, `PrimitiveApiClient`, and `isMemoryJsonValue`:\n\n| Export | What it is |\n|---|---|\n| `operations`, generated types | The full generated operation surface (`./api/index.js`, `./api/sdk.gen.js`), one function per OpenAPI `operationId` |\n| `createClient`, `Client`, `Config` | The generated fetch client primitives (`./api/client/index.js`) |\n| `openapiDocument`, `operationManifest` | The raw OpenAPI document and the [operation manifest](operation-manifest) used by CLI tooling (`primitive describe`, fish completion, command generation) |\n| `PrimitiveApiClient`, `PrimitiveApiError`, `createPrimitiveApiClient`, `DEFAULT_API_BASE_URL` | The host-aware client wrapper and its typed error; see [PrimitiveApiClient](primitive-api-client) |\n| `isMemoryJsonValue` | The [Memories JSON value validator](memory-json-value-helper) |\n\nEverything else in api-core (the generation scripts, the raw generated files) is implementation detail behind this one entry point.\n\n## How the spec becomes TypeScript\n\nTwo scripts defined in `packages/api-core/package.json` run in sequence: `generate:openapi` normalizes the spec and emits the manifest, then `generate:api` runs the TypeScript generator and its fixup pass.\n\n```bash\n# from packages/api-core\n\npnpm generate:openapi   # tsx scripts/generate-openapi-artifacts.ts\npnpm generate:api       # openapi-ts -f openapi-ts.config.ts && tsx scripts/fix-generated-api-imports.ts\npnpm generate           # runs both, in order\n```\n\n```mermaid\nflowchart LR\n  A[\"openapi/primitive-api.yaml\\n(OpenAPI 3.1, hand-written)\"] --> B[\"generate-openapi-artifacts.ts\"]\n  B --> C[\"primitive-api.codegen.json\\n(OpenAPI 3.0.3, normalized)\"]\n  B --> D[\"openapi.generated.ts\\n(embedded document constant)\"]\n  B --> E[\"operations.generated.ts\\n(operation manifest)\"]\n  C --> F[\"@hey-api/openapi-ts\"]\n  F --> G[\"generated TS client + types\"]\n  G --> H[\"fix-generated-api-imports.ts\"]\n  H --> I[\"api-core src/api/*\"]\n  I --> J[\"sdk-node (bundled inline)\"]\n  I --> K[\"cli-node (bundled inline)\"]\n```\n\n`generate-openapi-artifacts.ts` reads the YAML spec once and produces three outputs: the normalized `primitive-api.codegen.json` that the TypeScript generator consumes, an embedded copy of the raw document, and the operation manifest. The [normalization step and manifest shape](openapi-spec-normalization) and the [generation scripts themselves](api-core-generation-scripts) are covered in depth elsewhere; this page is about how the pieces connect.\n\n`@hey-api/openapi-ts` then generates the actual TypeScript client and types from the normalized JSON. `fix-generated-api-imports.ts` runs immediately after to repair known gaps in that generator's output: adding `.js` import extensions, guarding optional-body `Content-Type` headers, and fixing the `MemoryJsonValue` type. See [Generated TypeScript Client Fixups](typescript-client-fixups).\n\n## How sdk-node and cli-node consume api-core\n\nBoth packages depend on api-core at build time only and inline its output into their own tarballs. During development and testing they import from `@primitivedotdev/api-core` as an ordinary workspace dependency. At publish time, each package's bundler inlines api-core's compiled output, so `npm install @primitivedotdev/sdk` or `npm install -g primitive` gets a self-contained package with no runtime dependency on a package named `api-core`.\n\nsdk-node layers one thing on top of the re-exported api-core surface: the higher-level `PrimitiveClient` (`send`/`reply`/`forward`). Those methods need the parsed `ReceivedEmail` shape from sdk-node's own webhook module, a dependency api-core intentionally does not carry. See [What is the Primitive Node.js SDK?](node-sdk-overview) for that layering.\n\ncli-node consumes api-core more directly: it drives the [operation manifest](operation-manifest) to build the CLI's generic `api-command` surface, `describe`, and shell-completion helpers, and it uses `PrimitiveApiClient` for every generated command. See [What is the Primitive CLI?](cli-overview).\n\n## Why one spec instead of three\n\nOne spec keeps all three SDKs in lockstep; three hand-maintained clients drift.\n\n| Approach | Outcome |\n|---|---|\n| One `openapi/primitive-api.yaml`, three per-language generators | Every SDK's request/response shapes, error envelope, and endpoint set stay in lockstep automatically; a spec change propagates everywhere on the next `make *-generate` run |\n| Per-language hand-maintained clients | Node, Python, and Go clients drift independently; a new field or endpoint needs three manual implementations, and mismatches surface only at runtime |\n\nPython and Go each run their own generator against the same normalized spec (see [Non-TypeScript Codegen: Go and Python Clients](non-typescript-codegen)), but `openapi/primitive-api.yaml` is authored once and never duplicated.\n\n<Note>\n\nAuthor and edit `openapi/primitive-api.yaml` directly as OpenAPI 3.1. The normalized `primitive-api.codegen.json` (OpenAPI 3.0.3) is a generated build artifact for the code generators only. Never edit it by hand; regenerate it via the api-core generate scripts.\n\n</Note>\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Regenerating SDK Code from the OpenAPI Spec\" href=\"codegen-workflow\">\n\nRun the end-to-end pipeline across all three SDK languages in one pass.\n\n</Card>\n\n<Card title=\"OpenAPI Spec Normalization and Codegen Artifacts\" href=\"openapi-spec-normalization\">\n\nSee exactly how the 3.1 spec is normalized into codegen-friendly 3.0.3 JSON.\n\n</Card>\n\n<Card title=\"PrimitiveApiClient\" href=\"primitive-api-client\">\n\nConstruct the host-aware client and handle its typed PrimitiveApiError.\n\n</Card>\n\n<Card title=\"Non-TypeScript Codegen: Go and Python Clients\" href=\"non-typescript-codegen\">\n\nSee how the same spec generates the Go and Python SDK clients.\n\n</Card>\n\n</CardGroup>","canonical_base_url":"https://test.abhinandan.one","seo_indexing_enabled":true,"last_modified":"2026-08-21T18:22:43.359885+00:00","video_url":null,"voiceover_url":null,"tools_used":[],"demonstrated_by":[],"steps":[],"related_links":[],"intro":null,"prerequisites":[],"verification":[],"troubleshooting":[],"suggest_edit_url":"https://github.com/abhi-browzer/primitive-sdks/edit/main/packages/api-core/package.json","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Architecture%3A+Shared+Codegen+Pipeline&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fcodegen-architecture","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}