---
title: "Generated TypeScript Client Fixups"
canonical: "https://test.abhinandan.one/typescript-client-fixups"
markdown_url: "https://test.abhinandan.one/typescript-client-fixups.md"
publisher: "Primitive SDKs"
kind: "concept"
content_type: "reference"
category: "API Core / OpenAPI Generation"
description: "Three targeted repairs applied after @hey-api/openapi-ts codegen: ESM.js import extensions, guarded optional-body Content-Type headers, and a fixed MemoryJsonValue type."
keywords: ["fix-generated-api-imports.ts", "guardOptionalBodyContentType", "fixMemoryJsonValueType", "MemoryJsonValue", "hey-api/openapi-ts", ".js import extension"]
last_modified: "2026-08-11T18:55:05.213597+00:00"
published_at: "2026-08-11T18:55:05.064781+00:00"
source_files:
  - "packages/api-core/scripts/fix-generated-api-imports.ts"
sections:
  - {anchor: "what-the-fixup-script-does", title: "What the fixup script does"}
  - {anchor: "fix-1-add-js-extensions-to-relative-imports", title: "Fix 1: add `.js` extensions to relative imports"}
  - {anchor: "fix-2-guard-the-optional-body-content-type-header", title: "Fix 2: guard the optional-body `Content-Type` header"}
  - {anchor: "fix-3-repair-the-memoryjsonvalue-type", title: "Fix 3: repair the `MemoryJsonValue` type"}
  - {anchor: "where-this-fits-in-the-pipeline", title: "Where this fits in the pipeline"}
  - {anchor: "next-steps", title: "Next steps"}
---

> Documentation index: https://test.abhinandan.one/llms.txt

# Generated TypeScript Client Fixups

A post-processing script repairs three specific defects in the @hey-api/openapi-ts generated output before api-core ships it: missing.js import extensions, an unconditional optional-body Content-Type header, and a widened MemoryJsonValue type.

## What the fixup script does

`fix-generated-api-imports.ts`, the post-processing script in `packages/api-core/scripts`, applies three repairs to the TypeScript client that `@hey-api/openapi-ts` generates into `packages/api-core/src/api`. It walks every generated `.ts` file and rewrites what the generator gets wrong. It runs as the second half of `api-core`'s `generate:api` script (`openapi-ts -f openapi-ts.config.ts && tsx scripts/fix-generated-api-imports.ts`), so it always follows codegen and is never run standalone. See [Regenerating SDK Code from the OpenAPI Spec](https://test.abhinandan.one/codegen-workflow.md) for the full pipeline this fits into.

None of these are spec authoring changes. They exist because the generator's output needs correcting before `api-core` is bundled into `@primitivedotdev/sdk` and `primitive`.

## Fix 1: add `.js` extensions to relative imports

The script appends `.js` (or `/index.js` for directories) to every relative import specifier, because `@hey-api/openapi-ts` emits them without an extension. `api-core` is a pure ESM package, and Node's ESM resolver requires explicit extensions on relative specifiers; an extensionless import fails at runtime with `ERR_MODULE_NOT_FOUND`.

The script rewrites every relative `import ... from` and dynamic `import(...)` specifier:

```typescript
// packages/api-core/scripts/fix-generated-api-imports.ts
function addJsExtension(file: string, specifier: string): string {
  if (!specifier.startsWith("./") && !specifier.startsWith("../")) {
    return specifier;
  }
  if (specifier.endsWith(".js") || specifier.endsWith(".json")) {
    return specifier;
  }
  const absolute = resolve(dirname(file), specifier);
  if (existsSync(absolute) && statSync(absolute).isDirectory() && existsSync(join(absolute, "index.ts"))) {
    return `${specifier}/index.js`;
  }
  if (existsSync(`${absolute}.ts`)) {
    return `${specifier}.js`;
  }
  return `${specifier}.js`;
}
```

It resolves each specifier against the source file's directory to detect whether it points at a directory (needs `/index.js`) or a file (`.js` suffix), leaving absolute imports (bare package names) untouched.

## Fix 2: guard the optional-body `Content-Type` header

The script makes the generated `Content-Type: application/json` header conditional on a body actually being present. `@hey-api/openapi-ts` emits `headers: { 'Content-Type': 'application/json', ...options.headers }` on every operation whose OpenAPI spec declares a request body, regardless of whether that body is required or optional. For optional-body operations (the script names `testFunction`, `cli_logout`, `start_cli_login`, and `search_emails`), that sends the header on the wire even when the caller omits the body, which is wrong: a request with no body should carry no `Content-Type`.

`guardOptionalBodyContentType` rewrites the header assignment so it only fires when `options.body` is actually defined:

```typescript
// packages/api-core/scripts/fix-generated-api-imports.ts
function guardOptionalBodyContentType(content: string): string {
  return content.replace(
    /(headers:\s*\{\n\s*)'Content-Type':\s*'application\/json',\n(\s*\.\.\.options\.headers\n\s*\})/g,
    "$1...(options.body !== undefined && { 'Content-Type': 'application/json' }),\n$2",
  );
}
```

Required-body operations are unaffected: the type system guarantees `body` is always defined for those, so the header still ships in practice.

> **Note:** The Python generator produces the same defect, and `sdk-python/scripts/generate_api_client.py` applies an equivalent repair in `guard_optional_body_content_type`. See [Non-TypeScript Codegen: Go and Python Clients](https://test.abhinandan.one/non-typescript-codegen.md).

## Fix 3: repair the `MemoryJsonValue` type

The script replaces the generated `MemoryJsonValue` alias with the exact recursive JSON type. The spec's `MemoryJsonValue` schema is a recursive JSON-value union including a `type: "null"` branch (OpenAPI 3.1 syntax), and `@hey-api/openapi-ts` currently treats that branch as `unknown`, widening the whole alias.

`fixMemoryJsonValueType` substitutes the correct recursive type:

```typescript
// packages/api-core/scripts/fix-generated-api-imports.ts
const MEMORY_JSON_VALUE_TYPE = `export type MemoryJsonValue = string | number | boolean | Array<MemoryJsonValue> | {
    [key: string]: MemoryJsonValue;
} | null;`;
```

It matches the generated block up to the doc comment for the next declaration (`Memory scope.`) and substitutes the corrected type in place. If the substitution doesn't find or produce the expected string, the script throws immediately rather than silently shipping a broken type:

```typescript
// packages/api-core/scripts/fix-generated-api-imports.ts
if (updated === content || !updated.includes(MEMORY_JSON_VALUE_TYPE)) {
  throw new Error(
    `Unable to repair generated MemoryJsonValue type in ${file}. The codegen output shape changed; update fixMemoryJsonValueType before publishing.`,
  );
}
```

That hard failure is intentional: a codegen version bump that reshapes the output is exactly the kind of change this repair is fragile against, and a loud build break beats a silently-reintroduced `unknown` leaking into `@primitivedotdev/sdk`'s public types.

## Where this fits in the pipeline

The fixup pass is stage 4 of five, between TypeScript codegen and bundling.

| Stage | What runs | Output |
|---|---|---|
| 1. Spec authoring | Hand-edit `openapi/primitive-api.yaml` | OpenAPI 3.1 source of truth |
| 2. Normalization | `generate-openapi-artifacts.ts` | `primitive-api.codegen.json` (3.0.3), operation manifest |
| 3. TS codegen | `openapi-ts` (`@hey-api/openapi-ts`) | Raw generated client in `packages/api-core/src/api` |
| 4. This fixup pass | `fix-generated-api-imports.ts` | Corrected `.js` imports, guarded headers, fixed `MemoryJsonValue` |
| 5. Bundle | `sdk-node` / `cli-node` inline `api-core` | Published packages |

For how stage 2 produces the normalized spec these fixups build on top of, see [OpenAPI Spec Normalization and Codegen Artifacts](https://test.abhinandan.one/openapi-spec-normalization.md). For the scripts that produce the manifest and embedded OpenAPI document alongside this one, see [Codegen Artifact Generation Scripts](https://test.abhinandan.one/codegen-workflow/api-core-generation-scripts.md).
