ajsc 5.2.4 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -0
- package/README.md +303 -144
- package/dist/converter/BaseConverter.d.ts +315 -0
- package/dist/converter/BaseConverter.js +131 -0
- package/dist/converter/BaseConverter.js.map +1 -0
- package/dist/converter/Emitter.d.ts +35 -0
- package/dist/converter/Emitter.js +50 -0
- package/dist/converter/Emitter.js.map +1 -0
- package/dist/converter/discriminatedUnions.d.ts +47 -0
- package/dist/converter/discriminatedUnions.js +168 -0
- package/dist/converter/discriminatedUnions.js.map +1 -0
- package/dist/converter/formatDefault.d.ts +20 -0
- package/dist/converter/formatDefault.js +31 -0
- package/dist/converter/formatDefault.js.map +1 -0
- package/dist/converter/index.d.ts +24 -0
- package/dist/converter/index.js +24 -0
- package/dist/converter/index.js.map +1 -0
- package/dist/converter/mergeUnions.d.ts +36 -0
- package/dist/converter/mergeUnions.js +189 -0
- package/dist/converter/mergeUnions.js.map +1 -0
- package/dist/converter/naming.d.ts +29 -0
- package/dist/converter/naming.js +137 -0
- package/dist/converter/naming.js.map +1 -0
- package/dist/converter/registry.d.ts +18 -0
- package/dist/converter/registry.js +50 -0
- package/dist/converter/registry.js.map +1 -0
- package/dist/converter/walk.d.ts +9 -0
- package/dist/converter/walk.js +40 -0
- package/dist/converter/walk.js.map +1 -0
- package/dist/index.d.ts +71 -3
- package/dist/index.js +63 -3
- package/dist/index.js.map +1 -1
- package/dist/{JSONSchemaConverter.d.ts → ir/JSONSchemaConverter.d.ts} +1 -1
- package/dist/{JSONSchemaConverter.js → ir/JSONSchemaConverter.js} +9 -3
- package/dist/ir/JSONSchemaConverter.js.map +1 -0
- package/dist/ir/index.d.ts +1 -0
- package/dist/ir/index.js +2 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/kotlin/KotlinBaseConverter.d.ts +18 -0
- package/dist/kotlin/KotlinBaseConverter.js +36 -0
- package/dist/kotlin/KotlinBaseConverter.js.map +1 -0
- package/dist/kotlin/KotlinConverter.d.ts +67 -0
- package/dist/kotlin/KotlinConverter.js +142 -0
- package/dist/kotlin/KotlinConverter.js.map +1 -0
- package/dist/kotlin/annotations.d.ts +26 -0
- package/dist/kotlin/annotations.js +35 -0
- package/dist/kotlin/annotations.js.map +1 -0
- package/dist/kotlin/enums.d.ts +15 -0
- package/dist/kotlin/enums.js +58 -0
- package/dist/kotlin/enums.js.map +1 -0
- package/dist/kotlin/index.d.ts +13 -0
- package/dist/kotlin/index.js +14 -0
- package/dist/kotlin/index.js.map +1 -0
- package/dist/kotlin/objectEmitter.d.ts +12 -0
- package/dist/kotlin/objectEmitter.js +74 -0
- package/dist/kotlin/objectEmitter.js.map +1 -0
- package/dist/kotlin/sealedUnion.d.ts +17 -0
- package/dist/kotlin/sealedUnion.js +74 -0
- package/dist/kotlin/sealedUnion.js.map +1 -0
- package/dist/kotlin/typeMapper.d.ts +17 -0
- package/dist/kotlin/typeMapper.js +107 -0
- package/dist/kotlin/typeMapper.js.map +1 -0
- package/dist/kotlin/unsupported.d.ts +13 -0
- package/dist/kotlin/unsupported.js +53 -0
- package/dist/kotlin/unsupported.js.map +1 -0
- package/dist/swift/SwiftBaseConverter.d.ts +18 -0
- package/dist/swift/SwiftBaseConverter.js +38 -0
- package/dist/swift/SwiftBaseConverter.js.map +1 -0
- package/dist/swift/SwiftConverter.d.ts +60 -0
- package/dist/swift/SwiftConverter.js +113 -0
- package/dist/swift/SwiftConverter.js.map +1 -0
- package/dist/swift/discriminatedEnum.d.ts +18 -0
- package/dist/swift/discriminatedEnum.js +99 -0
- package/dist/swift/discriminatedEnum.js.map +1 -0
- package/dist/swift/enums.d.ts +15 -0
- package/dist/swift/enums.js +62 -0
- package/dist/swift/enums.js.map +1 -0
- package/dist/swift/index.d.ts +13 -0
- package/dist/swift/index.js +14 -0
- package/dist/swift/index.js.map +1 -0
- package/dist/swift/structEmitter.d.ts +12 -0
- package/dist/swift/structEmitter.js +70 -0
- package/dist/swift/structEmitter.js.map +1 -0
- package/dist/swift/typeMapper.d.ts +18 -0
- package/dist/swift/typeMapper.js +106 -0
- package/dist/swift/typeMapper.js.map +1 -0
- package/dist/swift/unsupported.d.ts +19 -0
- package/dist/swift/unsupported.js +88 -0
- package/dist/swift/unsupported.js.map +1 -0
- package/dist/typescript/TypescriptBaseConverter.d.ts +25 -0
- package/dist/typescript/TypescriptBaseConverter.js +178 -0
- package/dist/typescript/TypescriptBaseConverter.js.map +1 -0
- package/dist/typescript/TypescriptConverter.d.ts +74 -0
- package/dist/typescript/TypescriptConverter.js +254 -0
- package/dist/typescript/TypescriptConverter.js.map +1 -0
- package/dist/typescript/index.d.ts +12 -0
- package/dist/typescript/index.js +13 -0
- package/dist/typescript/index.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +39 -6
- package/dist/JSONSchemaConverter.js.map +0 -1
- package/dist/JSONSchemaConverter.test.d.ts +0 -1
- package/dist/JSONSchemaConverter.test.js +0 -585
- package/dist/JSONSchemaConverter.test.js.map +0 -1
- package/dist/Typebox.test.d.ts +0 -1
- package/dist/Typebox.test.js +0 -88
- package/dist/Typebox.test.js.map +0 -1
- package/dist/TypescriptBaseConverter.d.ts +0 -75
- package/dist/TypescriptBaseConverter.js +0 -321
- package/dist/TypescriptBaseConverter.js.map +0 -1
- package/dist/TypescriptConverter.additionalProperties.test.d.ts +0 -1
- package/dist/TypescriptConverter.additionalProperties.test.js +0 -110
- package/dist/TypescriptConverter.additionalProperties.test.js.map +0 -1
- package/dist/TypescriptConverter.arrays.test.d.ts +0 -1
- package/dist/TypescriptConverter.arrays.test.js +0 -130
- package/dist/TypescriptConverter.arrays.test.js.map +0 -1
- package/dist/TypescriptConverter.composites.advanced.test.d.ts +0 -1
- package/dist/TypescriptConverter.composites.advanced.test.js +0 -1070
- package/dist/TypescriptConverter.composites.advanced.test.js.map +0 -1
- package/dist/TypescriptConverter.composites.test.d.ts +0 -1
- package/dist/TypescriptConverter.composites.test.js +0 -335
- package/dist/TypescriptConverter.composites.test.js.map +0 -1
- package/dist/TypescriptConverter.d.ts +0 -163
- package/dist/TypescriptConverter.js +0 -606
- package/dist/TypescriptConverter.js.map +0 -1
- package/dist/TypescriptConverter.jsdoc.test.d.ts +0 -1
- package/dist/TypescriptConverter.jsdoc.test.js +0 -194
- package/dist/TypescriptConverter.jsdoc.test.js.map +0 -1
- package/dist/TypescriptConverter.objects.test.d.ts +0 -1
- package/dist/TypescriptConverter.objects.test.js +0 -258
- package/dist/TypescriptConverter.objects.test.js.map +0 -1
- package/dist/TypescriptConverter.options.test.d.ts +0 -1
- package/dist/TypescriptConverter.options.test.js +0 -501
- package/dist/TypescriptConverter.options.test.js.map +0 -1
- package/dist/TypescriptConverter.primitives.test.d.ts +0 -1
- package/dist/TypescriptConverter.primitives.test.js +0 -26
- package/dist/TypescriptConverter.primitives.test.js.map +0 -1
- package/dist/utils/path-utils.test.d.ts +0 -1
- package/dist/utils/path-utils.test.js +0 -92
- package/dist/utils/path-utils.test.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
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/).
|
|
4
|
+
|
|
5
|
+
## [7.0.0] — 2026-04-24
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Kotlin converter** (`ajsc/kotlin`, or `emitKotlin` from `ajsc`).
|
|
12
|
+
- Emits idiomatic `data class` declarations.
|
|
13
|
+
- Default `serializer: "kotlinx"` adds `@Serializable`, `@SerialName`, `@Contextual`. Pass `serializer: "none"` for plain types.
|
|
14
|
+
- Discriminated `oneOf`/`anyOf` unions emit as `sealed interface` with `@JsonClassDiscriminator`.
|
|
15
|
+
- JVM stdlib type mapping for date-time (`java.time.Instant`), uuid (`java.util.UUID`), uri (`java.net.URI`), date / time.
|
|
16
|
+
- `Pair<A,B>` / `Triple<A,B,C>` for 2- and 3-element tuples.
|
|
17
|
+
- `packageName` option emits a `package …` line at the top of the output.
|
|
18
|
+
- **Swift converter** (`ajsc/swift`, or `emitSwift` from `ajsc`).
|
|
19
|
+
- Emits `struct` declarations with `: Codable` conformance by default.
|
|
20
|
+
- Foundation stdlib mapping (`Date`, `UUID`, `URL`).
|
|
21
|
+
- 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.
|
|
22
|
+
- `CodingKeys` blocks generated when JSON keys differ from Swift property names.
|
|
23
|
+
- Acronym preservation in enum case names: `case US` rather than `case uS = "US"`.
|
|
24
|
+
- `accessLevel` option (`"public"` default, `"internal"` available).
|
|
25
|
+
- **Top-level emit functions** at the package root: `emitTypescript`, `emitKotlin`, `emitSwift`, plus the shared `EmitResult` type.
|
|
26
|
+
- **`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.
|
|
27
|
+
- **`ajsc/converter` subpath** for downstream extension authors. Exports `BaseConverter`, `Emitter`, `walkIR`, `LanguageProfile`, `BaseConverterContext`, `RefTypeEntry`, and `BaseConverterOpts`.
|
|
28
|
+
- **`rootTypeName` option** on every converter — overrides the schema-derived root name without modifying the schema.
|
|
29
|
+
- **Introspection fields** on every converter: `rootTypeName: string`, `extractedTypeNames: string[]`, `imports: string[]`. Codegen pipelines can reference emitted type names without parsing the `code` string.
|
|
30
|
+
- **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.
|
|
31
|
+
- **Cross-language fixture corpus** of 18 schemas with 54 golden output files (TS + Kotlin + Swift) under `src/integrations/fixtures/`.
|
|
32
|
+
- **JSON Schema 2020-12 `prefixItems`** treated as equivalent to legacy array-form `items`.
|
|
33
|
+
- **Schema-level `default`** now propagated to Kotlin/Swift property declarations for primitive defaults (`val foo: String = "x"`, `let foo: Int64 = 0`).
|
|
34
|
+
- **Format `int32`** is now honored: emits `Int` (Kotlin) and `Int32` (Swift) where previously these always emitted `Long` / `Int64`.
|
|
35
|
+
- **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.
|
|
36
|
+
|
|
37
|
+
### Changed (breaking — TypeScript output)
|
|
38
|
+
|
|
39
|
+
- **`$ref: "#"` recursion in TypeScript** now emits direct self-reference. A schema like `{ "title": "Tree", "properties": { "children": { "type": "array", "items": { "$ref": "#" } } } }` now produces:
|
|
40
|
+
```ts
|
|
41
|
+
export type Tree = { children?: Array<Tree>; };
|
|
42
|
+
```
|
|
43
|
+
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.
|
|
44
|
+
- **`anyOf` merge** preserves the parent's `required` flag. A required schema field that gets anyOf-merged is now non-optional in output. Example:
|
|
45
|
+
```ts
|
|
46
|
+
// before: actor?: Actor
|
|
47
|
+
// after: actor: Actor
|
|
48
|
+
```
|
|
49
|
+
- **`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.
|
|
50
|
+
|
|
51
|
+
### Changed (breaking — extension API)
|
|
52
|
+
|
|
53
|
+
- **`./converter` subpath public surface refactored.** Most consumers won't notice — only authors who subclassed `BaseConverter` to override hook methods.
|
|
54
|
+
- 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.
|
|
55
|
+
- `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 }`.
|
|
56
|
+
- 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.
|
|
57
|
+
- **`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.
|
|
58
|
+
- **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.
|
|
59
|
+
|
|
60
|
+
### Changed (non-breaking)
|
|
61
|
+
|
|
62
|
+
- **Swift discriminated-enum emission** uses consistent 4-space indentation throughout (previously had 4/8-space mismatches in `init(from:)` / `encode(to:)` blocks).
|
|
63
|
+
- **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.
|
|
64
|
+
- **Swift module structure** split into focused files: `typeMapper.ts`, `structEmitter.ts`, `discriminatedEnum.ts`, `enums.ts`, `unsupported.ts`. Pure internal refactor.
|
|
65
|
+
- **`BaseConverter` split** into focused files: `walk.ts`, `naming.ts`, `registry.ts`, `mergeUnions.ts`, `discriminatedUnions.ts`. Pure internal refactor.
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- **Kotlin `init` reserved word** — was missing from `KOTLIN_RESERVED`; properties named `init` now correctly emit as `init_`.
|
|
70
|
+
- **Swift acronym case names** — `case US` instead of `case uS = "US"` for all-uppercase enum values.
|
|
71
|
+
- **Schema `default`** is no longer silently dropped in Kotlin and Swift output for primitive defaults.
|
|
72
|
+
|
|
73
|
+
### Removed
|
|
74
|
+
|
|
75
|
+
- The `dart` keyword from `package.json` keywords (no Dart converter is available; misleading for npm discoverability).
|
|
76
|
+
|
|
77
|
+
### Internal
|
|
78
|
+
|
|
79
|
+
- 90+ commits since v6.0.0. Test count grew from 188 → 473.
|
|
80
|
+
- 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.
|
|
81
|
+
- Cross-language golden fixtures (18 schemas × 3 languages = 54 goldens) protect against drift.
|
|
82
|
+
- 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.
|
|
83
|
+
|
|
84
|
+
### Known follow-ups (deferred to a future release)
|
|
85
|
+
|
|
86
|
+
These were surfaced during v7 development but intentionally not implemented to keep the release scope focused. Real, agreed-on next steps:
|
|
87
|
+
|
|
88
|
+
- **`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.
|
|
89
|
+
- **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.
|
|
90
|
+
- **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.
|
|
91
|
+
- **`additionalItems` support**: tuple-form `items: [...]` plus `additionalItems: { ... }` is currently silently dropped.
|
|
92
|
+
- **Schema `examples` lifting**: currently dropped (no clean target idiom across all three languages).
|
|
93
|
+
- **`@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.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## [6.0.0] — earlier
|
|
98
|
+
|
|
99
|
+
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).)
|
|
100
|
+
|
|
101
|
+
## [5.x] — earlier
|
|
102
|
+
|
|
103
|
+
Earlier releases. See `git log` for detail.
|
package/README.md
CHANGED
|
@@ -1,198 +1,357 @@
|
|
|
1
|
-
# ajsc
|
|
1
|
+
# ajsc — Another JSON Schema Converter
|
|
2
2
|
|
|
3
|
-
**ajsc**
|
|
3
|
+
**ajsc** transforms JSON Schema documents into idiomatic, language-native code for **TypeScript**, **Kotlin**, and **Swift**. It exposes a small function-style API for typical codegen pipelines, plus class-based converters for advanced extension.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm install ajsc
|
|
7
|
+
```
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
```ts
|
|
10
|
+
import { emitTypescript, emitKotlin, emitSwift } from "ajsc";
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
const schema = {
|
|
13
|
+
title: "User",
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
id: { type: "integer" },
|
|
17
|
+
name: { type: "string" },
|
|
18
|
+
email: { type: "string", format: "email" },
|
|
19
|
+
},
|
|
20
|
+
required: ["id", "name"],
|
|
21
|
+
};
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
23
|
+
const ts = emitTypescript(schema);
|
|
24
|
+
const kt = emitKotlin(schema);
|
|
25
|
+
const sw = emitSwift(schema);
|
|
26
|
+
```
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
Use the provided language plugin (currently, a [TypeScript converter](#typescriptconverter)) or write your own converter to support additional languages such as Kotlin or Swift.
|
|
28
|
+
Each emit function returns the same shape:
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
```ts
|
|
31
|
+
interface EmitResult {
|
|
32
|
+
code: string; // declarations only — no `import` lines
|
|
33
|
+
rootTypeName: string; // top-level emitted type ("User")
|
|
34
|
+
extractedTypeNames: string[]; // additional types emitted (nested objects, enums, variants)
|
|
35
|
+
imports: string[]; // language-native module/symbol paths to import
|
|
36
|
+
}
|
|
37
|
+
```
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
For objects and arrays, the library tracks unique signatures to avoid duplicate type definitions when converting to code.
|
|
39
|
+
`code` contains type declarations only. Consumers assemble the final source file by combining `imports` with `code`.
|
|
23
40
|
|
|
24
41
|
---
|
|
25
42
|
|
|
26
|
-
##
|
|
43
|
+
## Output examples
|
|
27
44
|
|
|
28
|
-
|
|
45
|
+
### TypeScript
|
|
29
46
|
|
|
30
|
-
```
|
|
31
|
-
|
|
47
|
+
```ts
|
|
48
|
+
import { emitTypescript } from "ajsc";
|
|
49
|
+
|
|
50
|
+
const { code } = emitTypescript({
|
|
51
|
+
title: "User", type: "object",
|
|
52
|
+
properties: { id: { type: "integer" }, name: { type: "string" } },
|
|
53
|
+
required: ["id", "name"],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// code:
|
|
57
|
+
// export type User = { id: number; name: string; };
|
|
32
58
|
```
|
|
33
59
|
|
|
34
|
-
|
|
60
|
+
### Kotlin (default: kotlinx-serialization)
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { emitKotlin } from "ajsc";
|
|
64
|
+
|
|
65
|
+
const { code, imports } = emitKotlin({
|
|
66
|
+
title: "User", type: "object",
|
|
67
|
+
properties: { id: { type: "integer" }, name: { type: "string" } },
|
|
68
|
+
required: ["id", "name"],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// imports: ["kotlinx.serialization.Serializable"]
|
|
72
|
+
// code:
|
|
73
|
+
// @Serializable
|
|
74
|
+
// data class User(
|
|
75
|
+
// val id: Long,
|
|
76
|
+
// val name: String,
|
|
77
|
+
// )
|
|
78
|
+
```
|
|
35
79
|
|
|
36
|
-
|
|
80
|
+
Pass `serializer: "none"` for plain types with no annotations.
|
|
37
81
|
|
|
38
|
-
|
|
82
|
+
### Swift (default: Codable)
|
|
39
83
|
|
|
40
|
-
```
|
|
41
|
-
import {
|
|
84
|
+
```ts
|
|
85
|
+
import { emitSwift } from "ajsc";
|
|
42
86
|
|
|
43
|
-
const
|
|
44
|
-
|
|
87
|
+
const { code, imports } = emitSwift({
|
|
88
|
+
title: "User", type: "object",
|
|
89
|
+
properties: { id: { type: "integer" }, name: { type: "string" } },
|
|
90
|
+
required: ["id", "name"],
|
|
91
|
+
});
|
|
45
92
|
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
// {
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
// path: "",
|
|
93
|
+
// imports: []
|
|
94
|
+
// code:
|
|
95
|
+
// public struct User: Codable {
|
|
96
|
+
// public let id: Int64
|
|
97
|
+
// public let name: String
|
|
52
98
|
// }
|
|
53
99
|
```
|
|
54
100
|
|
|
55
|
-
|
|
56
|
-
You can also convert more complex schemas including objects, arrays, unions, intersections, and even schemas with $defs:
|
|
57
|
-
|
|
58
|
-
```javascript
|
|
59
|
-
import { JSONSchemaConverter } from "ajsc";
|
|
60
|
-
|
|
61
|
-
const complexSchema = {
|
|
62
|
-
$defs: {
|
|
63
|
-
Person: {
|
|
64
|
-
type: "object",
|
|
65
|
-
properties: {
|
|
66
|
-
name: { type: "string" },
|
|
67
|
-
age: { type: "number" },
|
|
68
|
-
},
|
|
69
|
-
required: ["name"],
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
type: "object",
|
|
73
|
-
properties: {
|
|
74
|
-
person: { $ref: "#/$defs/Person" },
|
|
75
|
-
},
|
|
76
|
-
};
|
|
101
|
+
Pass `serializer: "none"` to drop `Codable` conformance.
|
|
77
102
|
|
|
78
|
-
|
|
79
|
-
|
|
103
|
+
### Assembling a final file
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const { code, imports } = emitKotlin(schema);
|
|
107
|
+
const file = [...imports.map((i) => `import ${i}`), "", code].join("\n");
|
|
80
108
|
```
|
|
81
109
|
|
|
82
|
-
|
|
110
|
+
Swift `imports` are module names (`["Foundation"]`); Kotlin `imports` are fully-qualified symbol paths. TypeScript `imports` is always empty.
|
|
83
111
|
|
|
84
|
-
|
|
112
|
+
---
|
|
85
113
|
|
|
86
|
-
|
|
87
|
-
`new JSONSchemaConverter(schema: object)`
|
|
88
|
-
- Properties:
|
|
89
|
-
- irNode: The resulting intermediate representation of the JSON Schema.
|
|
90
|
-
- Supported Schema Types:
|
|
91
|
-
- Primitive Types: string, number, boolean, null
|
|
92
|
-
- Literals: Using the const keyword.
|
|
93
|
-
- Enums: Using the enum property.
|
|
94
|
-
- Unions: When type is an array (e.g., ["string", "number"]).
|
|
95
|
-
- Intersections: Using allOf to combine multiple schemas.
|
|
96
|
-
- Arrays: With type: "array" and an items schema.
|
|
97
|
-
- Objects: With type: "object" and a properties definition.
|
|
98
|
-
- $defs and $ref: For defining and referencing reusable schema parts.
|
|
114
|
+
## Package layout
|
|
99
115
|
|
|
100
|
-
|
|
116
|
+
| Subpath | Exports |
|
|
117
|
+
|------------------|--------------------------------------------------------------------------------------------------------|
|
|
118
|
+
| `ajsc` | `emitTypescript`, `emitKotlin`, `emitSwift`, `EmitResult`, plus the converter classes (re-exported) |
|
|
119
|
+
| `ajsc/typescript`| `TypescriptConverter`, `TypescriptBaseConverter`, related types |
|
|
120
|
+
| `ajsc/kotlin` | `KotlinConverter`, `KotlinBaseConverter`, `sanitizeKotlinIdentifier`, `KOTLIN_RESERVED` |
|
|
121
|
+
| `ajsc/swift` | `SwiftConverter`, `SwiftBaseConverter`, `sanitizeSwiftIdentifier`, `SWIFT_RESERVED` |
|
|
122
|
+
| `ajsc/converter` | `BaseConverter`, `Emitter`, `walkIR`, `LanguageProfile`, `BaseConverterOpts`, `RefTypeEntry`, etc. |
|
|
123
|
+
| `ajsc/ir` | `JSONSchemaConverter` (JSON Schema → IRNode tree) |
|
|
124
|
+
| `ajsc/types` | `IRNode`, `ILanguageConverter`, signature types |
|
|
125
|
+
| `ajsc/utils` | `PathUtils`, `toPascalCase` |
|
|
126
|
+
|
|
127
|
+
The root (`ajsc`) is the typical entry point. Subpaths are for advanced use — extending a converter, accessing the IR layer, building a custom language target.
|
|
128
|
+
|
|
129
|
+
---
|
|
101
130
|
|
|
102
|
-
|
|
131
|
+
## Function-style vs class-style
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// Function-style — recommended for most consumers.
|
|
135
|
+
import { emitKotlin } from "ajsc";
|
|
136
|
+
const result = emitKotlin(schema, opts);
|
|
137
|
+
|
|
138
|
+
// Class-style — for subclassing or accessing converter internals.
|
|
139
|
+
import { KotlinConverter } from "ajsc/kotlin";
|
|
140
|
+
const converter = new KotlinConverter(schema, opts);
|
|
141
|
+
console.log(converter.code, converter.imports);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Both produce identical output; the function form just bundles `code`, `rootTypeName`, `extractedTypeNames`, `imports` into a single return value. For codegen pipelines, the function form is usually nicer — it avoids the `new` and gives a stable result shape.
|
|
145
|
+
|
|
146
|
+
---
|
|
103
147
|
|
|
104
|
-
|
|
105
|
-
`new TypescriptConverter(schema: object, options?: TypescriptConverterOpts)`
|
|
106
|
-
- Properties:
|
|
107
|
-
- code: A string containing the generated TypeScript code.
|
|
148
|
+
## Options
|
|
108
149
|
|
|
109
|
-
|
|
150
|
+
All three converters accept `BaseConverterOpts` plus language-specific options.
|
|
151
|
+
|
|
152
|
+
### Shared options (`BaseConverterOpts`)
|
|
110
153
|
|
|
111
154
|
| Option | Type | Default | Description |
|
|
112
155
|
|--------|------|---------|-------------|
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
156
|
+
| `rootTypeName` | `string` | `schema.title \|\| "Root"` | Override the top-level type name. |
|
|
157
|
+
| `arrayItemNaming` | `string \| false` | `false` | Postfix for array-item type names. `false` = property name, `"Item"` = `ContactsItem`. |
|
|
158
|
+
| `depluralize` | `boolean` | `true` | Singularize array-item path segments (`entries` → `Entry`). |
|
|
159
|
+
| `uncountableWords` | `string[]` | `[]` | Words to skip when singularizing. Built-ins: `data`, `metadata`. |
|
|
160
|
+
| `unsupportedUnions` | `"throw" \| "fallback"` | `"throw"` | What to do with untagged unions: throw with a path-bearing error, or emit a language fallback type (`JsonElement` / `AnyCodable` / `any`). |
|
|
118
161
|
|
|
119
|
-
|
|
162
|
+
### TypeScript (`TypescriptConverterOpts`)
|
|
120
163
|
|
|
121
|
-
|
|
164
|
+
| Option | Type | Default | Description |
|
|
165
|
+
|--------|------|---------|-------------|
|
|
166
|
+
| `inlineTypes` | `boolean` | `false` | If true, nested object types are inlined rather than extracted to named aliases. |
|
|
167
|
+
| `enumStyle` | `"union" \| "enum"` | `"union"` | `"union"` emits `"a" \| "b" \| "c"`; `"enum"` emits `export enum Status { ... }`. |
|
|
168
|
+
| `jsdoc` | `boolean` | `false` | Emit JSDoc comments from JSON Schema `description` and `title`. Requires `inlineTypes: false`. |
|
|
122
169
|
|
|
123
|
-
|
|
124
|
-
import { TypescriptConverter } from "ajsc";
|
|
170
|
+
### Kotlin (`KotlinConverterOpts`)
|
|
125
171
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
age: { type: "number" },
|
|
131
|
-
contacts: {
|
|
132
|
-
type: "array",
|
|
133
|
-
items: {
|
|
134
|
-
type: "object",
|
|
135
|
-
properties: {
|
|
136
|
-
email: { type: "string" },
|
|
137
|
-
},
|
|
138
|
-
required: ["email"],
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
profile: {
|
|
142
|
-
type: "object",
|
|
143
|
-
properties: {
|
|
144
|
-
email: { type: "string" },
|
|
145
|
-
},
|
|
146
|
-
required: ["email"],
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
required: ["name", "age"],
|
|
150
|
-
};
|
|
172
|
+
| Option | Type | Default | Description |
|
|
173
|
+
|--------|------|---------|-------------|
|
|
174
|
+
| `serializer` | `"kotlinx" \| "none"` | `"kotlinx"` | Emit `@Serializable`/`@SerialName`/`@Contextual` annotations and matching imports. `"none"` emits plain types. |
|
|
175
|
+
| `packageName` | `string` | `undefined` | If set, emit `package <name>` at the top of the output. |
|
|
151
176
|
|
|
152
|
-
|
|
153
|
-
console.log(tsConverter.code);
|
|
177
|
+
### Swift (`SwiftConverterOpts`)
|
|
154
178
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
179
|
+
| Option | Type | Default | Description |
|
|
180
|
+
|--------|------|---------|-------------|
|
|
181
|
+
| `serializer` | `"codable" \| "none"` | `"codable"` | Emit `: Codable` conformance, `CodingKeys` enums, and discriminated-union `init(from:)`/`encode(to:)` plumbing. `"none"` emits plain types. |
|
|
182
|
+
| `accessLevel` | `"public" \| "internal"` | `"public"` | Access modifier on emitted types and members. |
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Type mapping
|
|
187
|
+
|
|
188
|
+
### TypeScript
|
|
189
|
+
|
|
190
|
+
JSON Schema → idiomatic TS. `string` → `string`, `integer` → `number`, etc. Discriminated `oneOf`/`anyOf` → tagged union types. `$ref: "#"` → direct self-reference.
|
|
191
|
+
|
|
192
|
+
### Kotlin (target: JVM)
|
|
193
|
+
|
|
194
|
+
| JSON Schema | Kotlin | Notes |
|
|
195
|
+
|-------------|--------|-------|
|
|
196
|
+
| `string` | `String` | |
|
|
197
|
+
| `string, format: date-time` | `java.time.Instant` | `@Contextual` under kotlinx |
|
|
198
|
+
| `string, format: date` / `time` | `java.time.LocalDate` / `LocalTime` | `@Contextual` under kotlinx |
|
|
199
|
+
| `string, format: uuid` | `java.util.UUID` | `@Contextual` under kotlinx |
|
|
200
|
+
| `string, format: uri` | `java.net.URI` | `@Contextual` under kotlinx |
|
|
201
|
+
| `string, format: email` / `hostname` / `ipv4` / `ipv6` | `String` + KDoc note | |
|
|
202
|
+
| `integer` | `Long` | `format: int32` → `Int` |
|
|
203
|
+
| `number` | `Double` | |
|
|
204
|
+
| `boolean` | `Boolean` | |
|
|
205
|
+
| `array` | `List<T>` | |
|
|
206
|
+
| `object` | `data class` | `@Serializable` under kotlinx |
|
|
207
|
+
| `enum` (string) | `enum class` | `@Serializable` under kotlinx |
|
|
208
|
+
| `oneOf` w/ discriminator | `sealed interface` | `@JsonClassDiscriminator` under kotlinx |
|
|
209
|
+
| Tuple length 2 / 3 | `Pair<A,B>` / `Triple<A,B,C>` | length > 3 throws |
|
|
210
|
+
|
|
211
|
+
### Swift
|
|
212
|
+
|
|
213
|
+
| JSON Schema | Swift | Notes |
|
|
214
|
+
|-------------|-------|-------|
|
|
215
|
+
| `string` | `String` | |
|
|
216
|
+
| `string, format: date-time` | `Foundation.Date` | requires `JSONDecoder.dateDecodingStrategy = .iso8601` |
|
|
217
|
+
| `string, format: uuid` | `Foundation.UUID` | |
|
|
218
|
+
| `string, format: uri` | `Foundation.URL` | |
|
|
219
|
+
| `string, format: email` etc. | `String` + `///` doc note | |
|
|
220
|
+
| `integer` | `Int64` | `format: int32` → `Int32` |
|
|
221
|
+
| `number` | `Double` | |
|
|
222
|
+
| `boolean` | `Bool` | |
|
|
223
|
+
| `array` | `[T]` | |
|
|
224
|
+
| `object` | `struct` | `: Codable` by default |
|
|
225
|
+
| `enum` (string) | `enum: String, Codable` | raw-value enum |
|
|
226
|
+
| `oneOf` w/ discriminator | `enum` w/ associated values | hand-rolled `init(from:)`/`encode(to:)` |
|
|
227
|
+
| Tuple (homogeneous) | `[T]` | |
|
|
228
|
+
| Tuple (heterogeneous, codable) | throws | use `serializer: "none"` for native tuples |
|
|
229
|
+
|
|
230
|
+
Schema-level `default` values are emitted inline for primitive types in Kotlin/Swift (`val foo: String = "x"`, `let foo: Int64 = 0`).
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Emitting multiple schemas into one namespace
|
|
235
|
+
|
|
236
|
+
Codegen pipelines that emit several sibling schemas into a shared output (e.g., wrapping an endpoint's `pathParams`, `query`, `body`, `response`, and error types under a single Kotlin `object` or Swift `enum`) need to avoid duplicate-name collisions across the emit calls. Each emit call has its own private name-tracking state by default, so two slots with a nested `address: { type: "object" }` would each emit a `data class Address(...)` — a duplicate-class compile error in the merged output.
|
|
237
|
+
|
|
238
|
+
Pass a shared `nameRegistry: Set<string>` across calls. The converter uses it as its declaration registry and mutates it as new types are emitted. Pair with `namePrefix` for clean per-slot names:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
import { emitKotlin } from "ajsc";
|
|
242
|
+
|
|
243
|
+
const registry = new Set<string>();
|
|
244
|
+
|
|
245
|
+
const body = emitKotlin(bodySchema, {
|
|
246
|
+
nameRegistry: registry,
|
|
247
|
+
namePrefix: "Body",
|
|
248
|
+
rootTypeName: "Body",
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const response = emitKotlin(responseSchema, {
|
|
252
|
+
nameRegistry: registry,
|
|
253
|
+
namePrefix: "Response",
|
|
254
|
+
rootTypeName: "Response",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// body.code: `data class Body(...)` + `data class BodyAddress(...)`
|
|
258
|
+
// response.code: `data class Response(...)` + `data class ResponseAddress(...)`
|
|
259
|
+
// Concatenated under one `object Endpoint { ... }` — no name collisions.
|
|
160
260
|
```
|
|
161
261
|
|
|
162
|
-
|
|
262
|
+
### Without `namePrefix`
|
|
263
|
+
|
|
264
|
+
You can pass `nameRegistry` alone — collisions still resolve correctly via the standard fallback path. But the resulting names are awkward:
|
|
163
265
|
|
|
164
|
-
```
|
|
165
|
-
const
|
|
166
|
-
|
|
266
|
+
```ts
|
|
267
|
+
const registry = new Set<string>();
|
|
268
|
+
emitKotlin(bodySchema, { nameRegistry: registry, rootTypeName: "Body" });
|
|
269
|
+
emitKotlin(responseSchema, { nameRegistry: registry, rootTypeName: "Response" });
|
|
167
270
|
|
|
168
|
-
//
|
|
169
|
-
//
|
|
271
|
+
// body emits: `data class Address(...)` ← bare name (first-come-first-served)
|
|
272
|
+
// response emits: `data class AddressType(...)` ← postfix fallback (semantically wrong)
|
|
170
273
|
```
|
|
171
274
|
|
|
172
|
-
|
|
275
|
+
`AddressType` is a literal type name in the emitted Kotlin/Swift — but it's not a "type of address," it's just another `Address`. The fallback exists because path-collision escalation was designed for single-call use; in cross-call scenarios where every slot's path looks the same, the fallback fires immediately. **Recommendation: always pair `nameRegistry` with a per-slot `namePrefix`.** That's the pattern shown above and the one the integration tests assert against.
|
|
173
276
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
277
|
+
### Notes
|
|
278
|
+
|
|
279
|
+
`namePrefix` does **not** affect the root type name (use `rootTypeName` for that). It only applies to extracted nested types, and it's applied unconditionally — every nested type gets the prefix, not just colliding ones. That can produce slightly verbose names for unique types (e.g. `BodyContact` even when no other slot has a `Contact`), but it's predictable and avoids the alternative's two-pass complexity.
|
|
280
|
+
|
|
281
|
+
Both `nameRegistry` and `namePrefix` work for all three languages. They're most useful for Kotlin and Swift, which require named declarations for non-primitive types. TypeScript supports them too but rarely needs them — most codegen pipelines use `inlineTypes: true` to flatten nested types instead.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Working with the IR directly
|
|
181
286
|
|
|
182
|
-
|
|
183
|
-
console.log(tsConverter.code);
|
|
287
|
+
If you need the intermediate representation (e.g., to write a custom emitter), use `JSONSchemaConverter`:
|
|
184
288
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
289
|
+
```ts
|
|
290
|
+
import { JSONSchemaConverter } from "ajsc/ir";
|
|
291
|
+
|
|
292
|
+
const ir = new JSONSchemaConverter({ type: "string" }).irNode;
|
|
293
|
+
// { type: "string", path: "", constraints: {} }
|
|
189
294
|
```
|
|
190
295
|
|
|
191
|
-
|
|
296
|
+
`IRNode` shape is documented in `ajsc/types`.
|
|
192
297
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
298
|
+
## Building a custom language emitter
|
|
299
|
+
|
|
300
|
+
To target a new language, extend `BaseConverter`:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { BaseConverter, type LanguageProfile } from "ajsc/converter";
|
|
304
|
+
|
|
305
|
+
class DartConverter extends BaseConverter {
|
|
306
|
+
protected readonly languageProfile: LanguageProfile = {
|
|
307
|
+
language: "dart",
|
|
308
|
+
// ... per-language overrides
|
|
309
|
+
};
|
|
310
|
+
// ... implement generateObjectType and the emission orchestration
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
See `docs/architecture/README.md` for the layered design — `BaseConverter`, the `LanguageProfile` pattern, and the per-language helper module conventions used by Kotlin and Swift.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Migrating from v6 to v7
|
|
319
|
+
|
|
320
|
+
### Breaking changes
|
|
321
|
+
|
|
322
|
+
These are output changes for existing consumers. They're behavioral fixes, not regressions, but if you've been depending on the older shapes you'll see diffs:
|
|
323
|
+
|
|
324
|
+
- **`$ref: "#"` recursion** in TypeScript now emits direct self-reference (`Array<Tree>`) rather than a buggy `Child` extraction with `Array<any>` recursion.
|
|
325
|
+
- **`anyOf` merge** preserves the parent's `required` flag. A required schema field that gets anyOf-merged is now non-optional in output (`actor: Actor` instead of `actor?: Actor`).
|
|
326
|
+
- **`oneOf` variant naming** in TypeScript now uses discriminator-derived names (`WrapperOuter`, `ScalarOuter`) rather than path-collision suffix names (`Outer`, `OuterType`). Kotlin and Swift always used the discriminator-derived form; this brings TS into alignment.
|
|
327
|
+
- **`format: int32`** is now honored: emits `Int` (Kotlin) and `Int32` (Swift) where previously these always emitted `Long`/`Int64`. `format: int64` and unset format both still emit `Long`/`Int64`.
|
|
328
|
+
- **Schema-level `default`** is now emitted in Kotlin and Swift property declarations for primitive defaults (`val foo: String = "x"`). TS output is unchanged.
|
|
329
|
+
- **Swift acronym preservation**: enum case names for all-uppercase tokens (`US`, `URL`, `ID`) are no longer lowerCamelCased (`uS`/`uRL`/`iD`). They're preserved as-is.
|
|
330
|
+
- **`./converter` extension API** has been heavily refactored. If you subclassed `BaseConverter` to override hook methods, see the new `LanguageProfile` pattern in `docs/architecture/README.md`. Most legacy hook overrides should migrate to a single `protected readonly languageProfile` field.
|
|
331
|
+
|
|
332
|
+
### New features
|
|
333
|
+
|
|
334
|
+
- **Kotlin and Swift converters.** First-class language targets, not just IR.
|
|
335
|
+
- **Top-level emit functions.** `emitTypescript`, `emitKotlin`, `emitSwift` from `"ajsc"`.
|
|
336
|
+
- **`./converter` subpath** for downstream extension authors.
|
|
337
|
+
- **Root subpath restored.** `import { TypescriptConverter } from "ajsc"` works again (was subpath-only in v6).
|
|
338
|
+
|
|
339
|
+
### Migrating v5 → v7
|
|
340
|
+
|
|
341
|
+
If you're still on v5, the v6 → v7 changes apply on top of these:
|
|
342
|
+
|
|
343
|
+
```diff
|
|
344
|
+
- import { TypescriptConverter, JSONSchemaConverter } from "ajsc";
|
|
345
|
+
+ import { TypescriptConverter } from "ajsc"; // root entry restored
|
|
346
|
+
+ import { JSONSchemaConverter } from "ajsc/ir"; // moved to subpath in v6
|
|
198
347
|
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Architecture
|
|
352
|
+
|
|
353
|
+
For contributors and downstream extension authors, see [`docs/architecture/README.md`](./docs/architecture/README.md) — the data flow, layered class hierarchy, `LanguageProfile` pattern, per-language module structure, and the steps to add a new language target.
|
|
354
|
+
|
|
355
|
+
## License
|
|
356
|
+
|
|
357
|
+
MIT
|