Documentation Index: Fetch llms.txt first to discover every published page. This page is also available as Markdown at /codegen-architecture.md.
Verified · 8/11/2026

Architecture: Shared Codegen Pipeline

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.

What the shared codegen pipeline is#

The 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.

The 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.

Why api-core exists and is never published#

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.

This 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:

  • sdk-node and cli-node would each generate and maintain their own copy of the TypeScript client (drift risk), or
  • one package would depend on the other (coupling the CLI's release cadence to the SDK's, or vice versa).

api-core solves both by being a shared build-time-only dependency that disappears from the published artifact.

What api-core exports#

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:

ExportWhat it is
operations, generated typesThe full generated operation surface (./api/index.js, ./api/sdk.gen.js), one function per OpenAPI operationId
createClient, Client, ConfigThe generated fetch client primitives (./api/client/index.js)
openapiDocument, operationManifestThe raw OpenAPI document and the operation manifest used by CLI tooling (primitive describe, fish completion, command generation)
PrimitiveApiClient, PrimitiveApiError, createPrimitiveApiClient, DEFAULT_API_BASE_URLThe host-aware client wrapper and its typed error; see PrimitiveApiClient
isMemoryJsonValueThe Memories JSON value validator

Everything else in api-core (the generation scripts, the raw generated files) is implementation detail behind this one entry point.

How the spec becomes TypeScript#

Two 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.

# from packages/api-core

pnpm generate:openapi   # tsx scripts/generate-openapi-artifacts.ts
pnpm generate:api       # openapi-ts -f openapi-ts.config.ts && tsx scripts/fix-generated-api-imports.ts
pnpm generate           # runs both, in order

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 and the generation scripts themselves are covered in depth elsewhere; this page is about how the pieces connect.

@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.

How sdk-node and cli-node consume api-core#

Both 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.

sdk-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? for that layering.

cli-node consumes api-core more directly: it drives the 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?.

Why one spec instead of three#

One spec keeps all three SDKs in lockstep; three hand-maintained clients drift.

ApproachOutcome
One openapi/primitive-api.yaml, three per-language generatorsEvery 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
Per-language hand-maintained clientsNode, Python, and Go clients drift independently; a new field or endpoint needs three manual implementations, and mismatches surface only at runtime

Python and Go each run their own generator against the same normalized spec (see Non-TypeScript Codegen: Go and Python Clients), but openapi/primitive-api.yaml is authored once and never duplicated.

Note

Author 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.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer