{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/typescript-client-fixups","markdown_url":"https://test.abhinandan.one/typescript-client-fixups.md","article":{"id":"416d1137-023c-42c3-b262-96ae86b5a1d0","article_slug":"typescript-client-fixups","parent_article_slug":null,"parent_article_title":null,"kind":"concept","published_at":"2026-08-11T18:55:05.064781+00:00","keywords":["fix-generated-api-imports.ts","guardOptionalBodyContentType","fixMemoryJsonValueType","MemoryJsonValue","hey-api/openapi-ts",".js import extension"],"meta_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.","og_image_url":null,"source_file_paths":["packages/api-core/scripts/fix-generated-api-imports.ts"],"recording_id":null,"replayable":false,"task_name":"Generated TypeScript Client Fixups","category":"API Core / OpenAPI Generation","summary":null,"description":"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.","content_kind":"repo_page","content_markdown":"## What the fixup script does\n\n`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](codegen-workflow) for the full pipeline this fits into.\n\nNone 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`.\n\n## Fix 1: add `.js` extensions to relative imports\n\nThe 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`.\n\nThe script rewrites every relative `import ... from` and dynamic `import(...)` specifier:\n\n```typescript\n// packages/api-core/scripts/fix-generated-api-imports.ts\nfunction addJsExtension(file: string, specifier: string): string {\n  if (!specifier.startsWith(\"./\") && !specifier.startsWith(\"../\")) {\n    return specifier;\n  }\n  if (specifier.endsWith(\".js\") || specifier.endsWith(\".json\")) {\n    return specifier;\n  }\n  const absolute = resolve(dirname(file), specifier);\n  if (existsSync(absolute) && statSync(absolute).isDirectory() && existsSync(join(absolute, \"index.ts\"))) {\n    return `${specifier}/index.js`;\n  }\n  if (existsSync(`${absolute}.ts`)) {\n    return `${specifier}.js`;\n  }\n  return `${specifier}.js`;\n}\n```\n\nIt 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.\n\n## Fix 2: guard the optional-body `Content-Type` header\n\nThe 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`.\n\n`guardOptionalBodyContentType` rewrites the header assignment so it only fires when `options.body` is actually defined:\n\n```typescript\n// packages/api-core/scripts/fix-generated-api-imports.ts\nfunction guardOptionalBodyContentType(content: string): string {\n  return content.replace(\n    /(headers:\\s*\\{\\n\\s*)'Content-Type':\\s*'application\\/json',\\n(\\s*\\.\\.\\.options\\.headers\\n\\s*\\})/g,\n    \"$1...(options.body !== undefined && { 'Content-Type': 'application/json' }),\\n$2\",\n  );\n}\n```\n\nRequired-body operations are unaffected: the type system guarantees `body` is always defined for those, so the header still ships in practice.\n\n<Note>\n\nThe 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](non-typescript-codegen).\n\n</Note>\n\n## Fix 3: repair the `MemoryJsonValue` type\n\nThe 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.\n\n`fixMemoryJsonValueType` substitutes the correct recursive type:\n\n```typescript\n// packages/api-core/scripts/fix-generated-api-imports.ts\nconst MEMORY_JSON_VALUE_TYPE = `export type MemoryJsonValue = string | number | boolean | Array<MemoryJsonValue> | {\n    [key: string]: MemoryJsonValue;\n} | null;`;\n```\n\nIt 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:\n\n```typescript\n// packages/api-core/scripts/fix-generated-api-imports.ts\nif (updated === content || !updated.includes(MEMORY_JSON_VALUE_TYPE)) {\n  throw new Error(\n    `Unable to repair generated MemoryJsonValue type in ${file}. The codegen output shape changed; update fixMemoryJsonValueType before publishing.`,\n  );\n}\n```\n\nThat 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.\n\n## Where this fits in the pipeline\n\nThe fixup pass is stage 4 of five, between TypeScript codegen and bundling.\n\n| Stage | What runs | Output |\n|---|---|---|\n| 1. Spec authoring | Hand-edit `openapi/primitive-api.yaml` | OpenAPI 3.1 source of truth |\n| 2. Normalization | `generate-openapi-artifacts.ts` | `primitive-api.codegen.json` (3.0.3), operation manifest |\n| 3. TS codegen | `openapi-ts` (`@hey-api/openapi-ts`) | Raw generated client in `packages/api-core/src/api` |\n| 4. This fixup pass | `fix-generated-api-imports.ts` | Corrected `.js` imports, guarded headers, fixed `MemoryJsonValue` |\n| 5. Bundle | `sdk-node` / `cli-node` inline `api-core` | Published packages |\n\nFor how stage 2 produces the normalized spec these fixups build on top of, see [OpenAPI Spec Normalization and Codegen Artifacts](openapi-spec-normalization). For the scripts that produce the manifest and embedded OpenAPI document alongside this one, see [Codegen Artifact Generation Scripts](api-core-generation-scripts).\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 full pipeline this fixup script is one step of.\n\n</Card>\n\n<Card title=\"Codegen Artifact Generation Scripts\" href=\"api-core-generation-scripts\">\n\nSee the companion script that produces the operation manifest and embedded OpenAPI document.\n\n</Card>\n\n<Card title=\"Non-TypeScript Codegen: Go and Python Clients\" href=\"non-typescript-codegen\">\n\nCompare the equivalent post-processing fixups applied to the Go and Python generators.\n\n</Card>\n\n<Card title=\"Codegen Troubleshooting\" href=\"codegen-troubleshooting\">\n\nDiagnose failures when this fixup's assumptions about generated output drift.\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/scripts/fix-generated-api-imports.ts","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Generated+TypeScript+Client+Fixups&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Ftypescript-client-fixups","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}