# Changelog

All notable changes to ajsc are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project follows [Semantic Versioning](https://semver.org/).

## [7.5.0] — 2026-06-30

### Fixed

- **TypeScript: generated type names no longer collide with global utility types ajsc emits.** A generated declaration whose name (from a depluralized property, a `title`, or an enum property) matched a TypeScript global generic — most commonly `Record` (from a `records` property or `title: "Record"`) shadowing the `Record<string, any>` emitted for `additionalProperties: true`, or `Array` shadowing array element references — produced output that `tsc` rejected with `TS2315: Type 'Record' is not generic`. Such names are now reserved across **all** declaration-name producers (extracted/referenced types, the root type, `enumStyle: "enum"` enum names, and discriminated-union variant names) and disambiguated to the `<Name>Type` form (e.g. `Record` → `RecordType`, `Array` → `ArrayType`), leaving the global references intact. The reserved set covers `Record`, `Array`, `Map`, `Set`, `Promise`, `Partial`, `Readonly`, `Pick`, `Omit`, `Exclude`, and `Required`. An explicit `rootTypeName` is honored verbatim (the caller owns that choice). The reservation is unconditional, so a schema that produced a type literally named e.g. `Record` (even without any `Record<…>` in its output) now emits `RecordType` instead.

### Added

- **`RefTypeNamingConfig.reservedTypeNames`** (extension API, exported from `ajsc/converter`): a list of type names a language references as ambient globals, so generated declarations are never named one of them. Defaults to empty; the built-in TypeScript profile populates it. Accompanied by two `BaseConverterContext` helpers for downstream language authors — `isReservedTypeName(name)` and `claimDeclarationName(base)` (disambiguate-and-register in one step). Existing language targets and the default config are unaffected.

### Internal

- `getUniqueRefTypeName`, `findAvailableName`, `deriveEnumName`, and root-name selection all consult `reservedTypeNames` through the shared `isReservedTypeName` (memoized) so reserved names are treated as already-taken and disambiguated by the existing collision machinery. All non-path-derived declaration names now register through the single `claimDeclarationName` gate, so disambiguation and registration are atomic and cannot be bypassed. Kotlin and Swift leave the reserved set empty (Kotlin emits an explanatory KDoc note for `additionalProperties` rather than a `Map<String, …>`, so it has no analogous shadowing vector).

## [7.4.0] — 2026-06-29

### Changed

- **The TypeScript converter now emits JSDoc comments by default.** The `jsdoc` option defaults to `true`, so JSON Schema `description` (and property `title`) annotations are rendered as `/** … */` comments over generated root types, extracted/referenced types, enum declarations, and object properties. Previously this required opting in with `jsdoc: true`.
  - To restore the previous output (no comments), pass `jsdoc: false`.
  - Unchanged: JSDoc is only emitted when `inlineTypes` is `false` (the default); `inlineTypes: true` never emits comments regardless of the `jsdoc` setting.
  - Kotlin and Swift output is unaffected.

## [7.3.0] — 2026-06-06

### Added

- **`x-named-type` schema keyword** for verbatim references to external, already-defined types. Set `"x-named-type": "TypeName"` on any schema node; ajsc emits a bare reference to that type without inlining or extracting it. Supported in all three languages (TypeScript, Kotlin, Swift). At the root level, ajsc emits a type alias (`export type Root = Message;` / `typealias Root = Message` / `public typealias Root = Message`). The schema body (`type`, `properties`, `$ref`, etc.) is intentionally ignored when the keyword is present.

- **`referencedNamedTypes: string[]`** on `EmitResult`. Lists the names of all external types referenced via `x-named-type` in the emitted schema, deduped and sorted lexicographically. ajsc does not declare these — the caller is responsible for defining or importing them. Empty when `x-named-type` is not used. Available on `emitTypescript`, `emitKotlin`, and `emitSwift`.

### Internal

- New `normalizeNamedType` function exported from `ajsc/ir` — the single widen-able parsing boundary for the `x-named-type` keyword. Today accepts non-empty strings; a future per-language object form would be handled here without touching the IR shape or any emitter.
- `computeReferencedNamedTypes` helper on `BaseConverter` collects referenced names via `walkIR` in a single IR pass after construction (mirroring `computeExtractedTypeNames`). Emitters remain pure — no name accumulation during emission.
- New `LanguageProfile.formatRootTypeAlias` hook makes root-level alias syntax declarative for nominal-root languages (Kotlin/Swift) rather than hardcoded in each converter; TypeScript continues to alias via its generic root wrapper. Keeps the alias syntax discoverable in the extension API for future language targets.
- Documented the `x-named-type` name-collision limitation (a referenced name matching a generated type name is not disambiguated) in the README, pinned by a regression test.
- New cross-language golden fixture `named-types` (schema × 3 languages = 3 new golden files, corpus grows to 60).
- Test count: 500 → 525.

## [7.2.0] — 2026-04-25

Maintenance release — version bump only. No source or public-API changes since
7.1.0 (the published tag differs from 7.1.0 only in `package.json` /
`package-lock.json`). Output is byte-identical to 7.1.0.

## [7.1.0] — 2026-04-25

### Added

- **`inlineTypes` opt for Kotlin and Swift converters.** When `inlineTypes: true`, nested object types are emitted as nested `data class` (Kotlin) or `struct` (Swift) declarations inside their parent's body, rather than as top-level siblings. For codegen pipelines that emit multiple schemas into one namespace, this eliminates the cross-call name-collision class structurally — each parent's `Address` is name-scoped (`Body.Address` vs `Response.Address`), so no shared `nameRegistry` plumbing is required. Default: `false` (extract to top-level — the v7.0 behavior).
  - Top-level enums and discriminated unions remain extracted regardless of `inlineTypes` — they have their own dedup story and don't benefit from nesting.
  - The TypeScript converter has had `inlineTypes` since v6 (anonymous inline literals — a different language mechanism); the Kotlin/Swift addition uses the same opt name for the same consumer-facing intent.
- New `nested-emission` cross-language fixture covering the inline pattern across TypeScript, Kotlin, and Swift (19th fixture, brings the golden corpus to 57 files).
- **`generateInlineNestedDecl` and `InlineNestedCollector`** exported from `ajsc/converter` for downstream language authors implementing similar nested-decl semantics.

### Internal

- New shared module `src/converter/inlineEmission.ts` consolidates the per-parent collector type, the `getReferencedType` replacement, and the multi-line indent helper that previously existed (in identical form) inside both `kotlin/objectEmitter.ts` and `swift/structEmitter.ts`. Each language emitter now passes a small language-specific `formatNestedDecl(c, name, body, ir)` callback — the only part that genuinely differs.
- Test count: 473 → 500 (per-language inline-types coverage + sibling-parent dedup pinning + the cross-language nested-emission fixture).
- Existing TS / Kotlin / Swift goldens are byte-identical between v7.0.0 and v7.1.0 (the new opt defaults to `false`).

## [7.0.0] — 2026-04-24

This release adds first-class **Kotlin** and **Swift** language targets, restores the root subpath import that v6 dropped, and ships several behavioral fixes to the TypeScript converter that consumers depending on the older buggy output may need to migrate.

### Added

- **Kotlin converter** (`ajsc/kotlin`, or `emitKotlin` from `ajsc`).
  - Emits idiomatic `data class` declarations.
  - Default `serializer: "kotlinx"` adds `@Serializable`, `@SerialName`, `@Contextual`. Pass `serializer: "none"` for plain types.
  - Discriminated `oneOf`/`anyOf` unions emit as `sealed interface` with `@JsonClassDiscriminator`.
  - JVM stdlib type mapping for date-time (`java.time.Instant`), uuid (`java.util.UUID`), uri (`java.net.URI`), date / time.
  - `Pair<A,B>` / `Triple<A,B,C>` for 2- and 3-element tuples.
  - `packageName` option emits a `package …` line at the top of the output.
- **Swift converter** (`ajsc/swift`, or `emitSwift` from `ajsc`).
  - Emits `struct` declarations with `: Codable` conformance by default.
  - Foundation stdlib mapping (`Date`, `UUID`, `URL`).
  - Discriminated unions emit as `enum` with associated values, including a hand-written `init(from:)` and `encode(to:)` that dispatch by the schema's discriminator field.
  - `CodingKeys` blocks generated when JSON keys differ from Swift property names.
  - Acronym preservation in enum case names: `case US` rather than `case uS = "US"`.
  - `accessLevel` option (`"public"` default, `"internal"` available).
- **Top-level emit functions** at the package root: `emitTypescript`, `emitKotlin`, `emitSwift`, plus the shared `EmitResult` type.
- **`nameRegistry` and `namePrefix` opts** on `BaseConverterOpts`. Pass a shared `Set<string>` across multiple emit calls so types extracted from sibling schemas don't collide when concatenated into a single Kotlin `object` or Swift `enum` namespace. `namePrefix` prepends a per-slot prefix to extracted (nested) type names — `BodyAddress` vs `ResponseAddress` rather than `Address` vs `Address2`. See README "Emitting multiple schemas into one namespace" for the recommended pattern. Most useful for Kotlin and Swift; harmless for TypeScript.
- **`ajsc/converter` subpath** for downstream extension authors. Exports `BaseConverter`, `Emitter`, `walkIR`, `LanguageProfile`, `BaseConverterContext`, `RefTypeEntry`, and `BaseConverterOpts`.
- **`rootTypeName` option** on every converter — overrides the schema-derived root name without modifying the schema.
- **Introspection fields** on every converter: `rootTypeName: string`, `extractedTypeNames: string[]`, `imports: string[]`. Codegen pipelines can reference emitted type names without parsing the `code` string.
- **Architecture documentation** at `docs/architecture/README.md` — data flow, layered class hierarchy, `LanguageProfile` pattern, per-language module structure, and the steps to add a new language target.
- **Cross-language fixture corpus** of 18 schemas with 54 golden output files (TS + Kotlin + Swift) under `src/integrations/fixtures/`.
- **JSON Schema 2020-12 `prefixItems`** treated as equivalent to legacy array-form `items`.
- **Schema-level `default`** now propagated to Kotlin/Swift property declarations for primitive defaults (`val foo: String = "x"`, `let foo: Int64 = 0`).
- **Format `int32`** is now honored: emits `Int` (Kotlin) and `Int32` (Swift) where previously these always emitted `Long` / `Int64`.
- **Root-level enum schemas** (`{ type: "string", enum: [...] }`) now emit as `enum class` (Kotlin) and `public enum: String, Codable` (Swift), instead of empty data classes/structs.

### Changed (breaking — TypeScript output)

- **`$ref: "#"` recursion in TypeScript** now emits direct self-reference. A schema like `{ "title": "Tree", "properties": { "children": { "type": "array", "items": { "$ref": "#" } } } }` now produces:
  ```ts
  export type Tree = { children?: Array<Tree>; };
  ```
  instead of the previous bifurcated form (`Child` extracted with `Array<any>` recursion). Consumers depending on the old `Child` extracted type by name will need to update.
- **`anyOf` merge** preserves the parent's `required` flag. A required schema field that gets anyOf-merged is now non-optional in output. Example:
  ```ts
  // before:  actor?: Actor
  // after:   actor: Actor
  ```
- **`oneOf` variant naming in TypeScript** uses discriminator-derived names. A schema with two oneOf variants `{ kind: "wrapper" }` / `{ kind: "scalar" }` now emits `WrapperOuter` / `ScalarOuter` rather than collision-suffixed `Outer` / `OuterType`. Kotlin and Swift have always produced discriminator-derived names; this brings TypeScript into alignment.

### Changed (breaking — extension API)

- **`./converter` subpath public surface refactored.** Most consumers won't notice — only authors who subclassed `BaseConverter` to override hook methods.
  - 9 separate `protected` hook methods (`shouldEraseDiscriminator`, `processOneOfAsDiscriminatedUnion`, `getDiscriminatedVariantParentName`, `shouldPopulateDiscriminatorInfo`, `detectSelfReferenceToRoot`, etc.) consolidated into a single `protected readonly languageProfile: LanguageProfile` field. See `docs/architecture/README.md` for the migration pattern.
  - `RefTypes` is now `RefTypeEntry[]` (array of named records) instead of a tuple-of-tuples. Access changes from `entry[2].code` → `entry.code`, `[_s, name, { code }]` destructure → `{ name, code }`.
  - Several previously-protected helper methods (`findDiscriminatorProperty`, `collectUnionPropertyNames`, `stripDiscriminatorField`, `getConstStringValue`, `findAvailableName`) are now public (with `@internal` JSDoc) so they can be referenced by helper-module Context interfaces.
  - **`BaseConverterContext` interface** introduced as the surface helper modules operate on. `BaseConverter implements BaseConverterContext`. Helper modules in `src/converter/` (`registry.ts`, `naming.ts`, `mergeUnions.ts`, `discriminatedUnions.ts`, `walk.ts`) take a `BaseConverterContext` rather than the abstract class, decoupling helpers from the class hierarchy.
  - **Per-language `<Lang>ConverterContext` interfaces** (`KotlinConverterContext`, `SwiftConverterContext`) extend `BaseConverterContext` with language-specific state and methods. The Kotlin and Swift converter classes `implements` their respective Context interface; helper modules in `src/kotlin/` and `src/swift/` take the interface. Replaces the prior `_x` accessor wrapper pattern with a single declarative interface.

### Changed (non-breaking)

- **Swift discriminated-enum emission** uses consistent 4-space indentation throughout (previously had 4/8-space mismatches in `init(from:)` / `encode(to:)` blocks).
- **Kotlin module structure** split into focused files: `typeMapper.ts`, `objectEmitter.ts`, `sealedUnion.ts`, `enums.ts`, `unsupported.ts`, `annotations.ts`. Pure internal refactor; no public API change.
- **Swift module structure** split into focused files: `typeMapper.ts`, `structEmitter.ts`, `discriminatedEnum.ts`, `enums.ts`, `unsupported.ts`. Pure internal refactor.
- **`BaseConverter` split** into focused files: `walk.ts`, `naming.ts`, `registry.ts`, `mergeUnions.ts`, `discriminatedUnions.ts`. Pure internal refactor.

### Fixed

- **Kotlin `init` reserved word** — was missing from `KOTLIN_RESERVED`; properties named `init` now correctly emit as `init_`.
- **Swift acronym case names** — `case US` instead of `case uS = "US"` for all-uppercase enum values.
- **Schema `default`** is no longer silently dropped in Kotlin and Swift output for primitive defaults.

### Removed

- The `dart` keyword from `package.json` keywords (no Dart converter is available; misleading for npm discoverability).

### Internal

- 90+ commits since v6.0.0. Test count grew from 188 → 473.
- New regression-net pattern: vitest snapshots of the kitchen-sink schema (`_baseline-snapshot.test.ts.snap`) catch any drift in TS converter output across refactors. Used throughout this release as the gate for the `BaseConverter` refactor.
- Cross-language golden fixtures (18 schemas × 3 languages = 54 goldens) protect against drift.
- Cross-call integration tests (`src/__tests__/cross-call-name-registry.test.ts`, `src/__tests__/multi-call-integration.test.ts`) lock in the orchestration pattern for downstream codegen pipelines.

### Known follow-ups (deferred to a future release)

These were surfaced during v7 development but intentionally not implemented to keep the release scope focused. Real, agreed-on next steps:

- **`EmitSession` API**: an emit-session object that bundles `nameRegistry` plus `refTypes` across multiple calls so identical schema shapes (e.g., the same `address` structure in both `body` and `response`) emit as a single declaration referenced from both slots. The current `nameRegistry` fix avoids compile errors but emits structurally-identical types twice (`BodyAddress`, `ResponseAddress`). A session API would dedupe at the structural level.
- **Smart `namePrefix`**: prefix extracted names only when they would collide, instead of unconditionally. Today, `namePrefix: "Body"` produces `BodyAddress`, `BodyContact`, `BodySettings` even when only `Address` actually collides with another slot. A two-pass approach (collect names, detect overlaps, prefix the conflicting ones) would give more ergonomic output.
- **Cross-call collision fallback**: when `nameRegistry` is set without `namePrefix`, prefer numeric suffix (`Address2`) over postfix-list (`AddressType`) fallbacks. The postfix list was designed for single-call path-escalation; in cross-call mode, postfix names become semantically misleading.
- **`additionalItems` support**: tuple-form `items: [...]` plus `additionalItems: { ... }` is currently silently dropped.
- **Schema `examples` lifting**: currently dropped (no clean target idiom across all three languages).
- **`@internal` accessor consolidation**: `BaseConverter` exposes several public-with-`@internal` methods (`findDiscriminatorProperty`, `collectUnionPropertyNames`, etc.) for the `BaseConverterContext` interface. They work but the `@internal` JSDoc isn't enforced. A `Symbol`-keyed accessor pattern or a separate "internal" module would prevent downstream code from depending on them.

---

## [6.0.0] — earlier

Reorganized `src` by feature and switched to subpath exports. The previous root barrel (`import { TypescriptConverter } from "ajsc"`) stopped resolving in v6. (v7 restores the root entry; see [Migrating from v6 to v7](./README.md#migrating-from-v6-to-v7).)

## [5.x] — earlier

Earlier releases. See `git log` for detail.
