ajsc 5.2.3 → 7.0.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.
Files changed (141) hide show
  1. package/README.md +253 -145
  2. package/dist/converter/BaseConverter.d.ts +283 -0
  3. package/dist/converter/BaseConverter.js +131 -0
  4. package/dist/converter/BaseConverter.js.map +1 -0
  5. package/dist/converter/Emitter.d.ts +35 -0
  6. package/dist/converter/Emitter.js +50 -0
  7. package/dist/converter/Emitter.js.map +1 -0
  8. package/dist/converter/discriminatedUnions.d.ts +47 -0
  9. package/dist/converter/discriminatedUnions.js +168 -0
  10. package/dist/converter/discriminatedUnions.js.map +1 -0
  11. package/dist/converter/formatDefault.d.ts +20 -0
  12. package/dist/converter/formatDefault.js +31 -0
  13. package/dist/converter/formatDefault.js.map +1 -0
  14. package/dist/converter/index.d.ts +2 -0
  15. package/dist/converter/index.js +2 -0
  16. package/dist/converter/index.js.map +1 -0
  17. package/dist/converter/mergeUnions.d.ts +36 -0
  18. package/dist/converter/mergeUnions.js +189 -0
  19. package/dist/converter/mergeUnions.js.map +1 -0
  20. package/dist/converter/naming.d.ts +29 -0
  21. package/dist/converter/naming.js +130 -0
  22. package/dist/converter/naming.js.map +1 -0
  23. package/dist/converter/registry.d.ts +18 -0
  24. package/dist/converter/registry.js +50 -0
  25. package/dist/converter/registry.js.map +1 -0
  26. package/dist/converter/walk.d.ts +9 -0
  27. package/dist/converter/walk.js +40 -0
  28. package/dist/converter/walk.js.map +1 -0
  29. package/dist/index.d.ts +71 -3
  30. package/dist/index.js +63 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/{JSONSchemaConverter.d.ts → ir/JSONSchemaConverter.d.ts} +1 -1
  33. package/dist/{JSONSchemaConverter.js → ir/JSONSchemaConverter.js} +9 -3
  34. package/dist/ir/JSONSchemaConverter.js.map +1 -0
  35. package/dist/ir/index.d.ts +1 -0
  36. package/dist/ir/index.js +2 -0
  37. package/dist/ir/index.js.map +1 -0
  38. package/dist/kotlin/KotlinBaseConverter.d.ts +18 -0
  39. package/dist/kotlin/KotlinBaseConverter.js +36 -0
  40. package/dist/kotlin/KotlinBaseConverter.js.map +1 -0
  41. package/dist/kotlin/KotlinConverter.d.ts +67 -0
  42. package/dist/kotlin/KotlinConverter.js +140 -0
  43. package/dist/kotlin/KotlinConverter.js.map +1 -0
  44. package/dist/kotlin/annotations.d.ts +26 -0
  45. package/dist/kotlin/annotations.js +35 -0
  46. package/dist/kotlin/annotations.js.map +1 -0
  47. package/dist/kotlin/enums.d.ts +15 -0
  48. package/dist/kotlin/enums.js +58 -0
  49. package/dist/kotlin/enums.js.map +1 -0
  50. package/dist/kotlin/index.d.ts +2 -0
  51. package/dist/kotlin/index.js +3 -0
  52. package/dist/kotlin/index.js.map +1 -0
  53. package/dist/kotlin/objectEmitter.d.ts +12 -0
  54. package/dist/kotlin/objectEmitter.js +74 -0
  55. package/dist/kotlin/objectEmitter.js.map +1 -0
  56. package/dist/kotlin/sealedUnion.d.ts +17 -0
  57. package/dist/kotlin/sealedUnion.js +74 -0
  58. package/dist/kotlin/sealedUnion.js.map +1 -0
  59. package/dist/kotlin/typeMapper.d.ts +17 -0
  60. package/dist/kotlin/typeMapper.js +107 -0
  61. package/dist/kotlin/typeMapper.js.map +1 -0
  62. package/dist/kotlin/unsupported.d.ts +13 -0
  63. package/dist/kotlin/unsupported.js +53 -0
  64. package/dist/kotlin/unsupported.js.map +1 -0
  65. package/dist/swift/SwiftBaseConverter.d.ts +18 -0
  66. package/dist/swift/SwiftBaseConverter.js +38 -0
  67. package/dist/swift/SwiftBaseConverter.js.map +1 -0
  68. package/dist/swift/SwiftConverter.d.ts +60 -0
  69. package/dist/swift/SwiftConverter.js +111 -0
  70. package/dist/swift/SwiftConverter.js.map +1 -0
  71. package/dist/swift/discriminatedEnum.d.ts +18 -0
  72. package/dist/swift/discriminatedEnum.js +99 -0
  73. package/dist/swift/discriminatedEnum.js.map +1 -0
  74. package/dist/swift/enums.d.ts +15 -0
  75. package/dist/swift/enums.js +62 -0
  76. package/dist/swift/enums.js.map +1 -0
  77. package/dist/swift/index.d.ts +2 -0
  78. package/dist/swift/index.js +3 -0
  79. package/dist/swift/index.js.map +1 -0
  80. package/dist/swift/structEmitter.d.ts +12 -0
  81. package/dist/swift/structEmitter.js +70 -0
  82. package/dist/swift/structEmitter.js.map +1 -0
  83. package/dist/swift/typeMapper.d.ts +18 -0
  84. package/dist/swift/typeMapper.js +106 -0
  85. package/dist/swift/typeMapper.js.map +1 -0
  86. package/dist/swift/unsupported.d.ts +19 -0
  87. package/dist/swift/unsupported.js +88 -0
  88. package/dist/swift/unsupported.js.map +1 -0
  89. package/dist/typescript/TypescriptBaseConverter.d.ts +25 -0
  90. package/dist/typescript/TypescriptBaseConverter.js +178 -0
  91. package/dist/typescript/TypescriptBaseConverter.js.map +1 -0
  92. package/dist/typescript/TypescriptConverter.d.ts +74 -0
  93. package/dist/typescript/TypescriptConverter.js +252 -0
  94. package/dist/typescript/TypescriptConverter.js.map +1 -0
  95. package/dist/typescript/index.d.ts +2 -0
  96. package/dist/typescript/index.js +3 -0
  97. package/dist/typescript/index.js.map +1 -0
  98. package/dist/utils/index.d.ts +2 -0
  99. package/dist/utils/index.js +3 -0
  100. package/dist/utils/index.js.map +1 -0
  101. package/package.json +37 -5
  102. package/dist/JSONSchemaConverter.js.map +0 -1
  103. package/dist/JSONSchemaConverter.test.d.ts +0 -1
  104. package/dist/JSONSchemaConverter.test.js +0 -585
  105. package/dist/JSONSchemaConverter.test.js.map +0 -1
  106. package/dist/Typebox.test.d.ts +0 -1
  107. package/dist/Typebox.test.js +0 -88
  108. package/dist/Typebox.test.js.map +0 -1
  109. package/dist/TypescriptBaseConverter.d.ts +0 -75
  110. package/dist/TypescriptBaseConverter.js +0 -321
  111. package/dist/TypescriptBaseConverter.js.map +0 -1
  112. package/dist/TypescriptConverter.additionalProperties.test.d.ts +0 -1
  113. package/dist/TypescriptConverter.additionalProperties.test.js +0 -110
  114. package/dist/TypescriptConverter.additionalProperties.test.js.map +0 -1
  115. package/dist/TypescriptConverter.arrays.test.d.ts +0 -1
  116. package/dist/TypescriptConverter.arrays.test.js +0 -130
  117. package/dist/TypescriptConverter.arrays.test.js.map +0 -1
  118. package/dist/TypescriptConverter.composites.advanced.test.d.ts +0 -1
  119. package/dist/TypescriptConverter.composites.advanced.test.js +0 -924
  120. package/dist/TypescriptConverter.composites.advanced.test.js.map +0 -1
  121. package/dist/TypescriptConverter.composites.test.d.ts +0 -1
  122. package/dist/TypescriptConverter.composites.test.js +0 -335
  123. package/dist/TypescriptConverter.composites.test.js.map +0 -1
  124. package/dist/TypescriptConverter.d.ts +0 -163
  125. package/dist/TypescriptConverter.js +0 -595
  126. package/dist/TypescriptConverter.js.map +0 -1
  127. package/dist/TypescriptConverter.jsdoc.test.d.ts +0 -1
  128. package/dist/TypescriptConverter.jsdoc.test.js +0 -194
  129. package/dist/TypescriptConverter.jsdoc.test.js.map +0 -1
  130. package/dist/TypescriptConverter.objects.test.d.ts +0 -1
  131. package/dist/TypescriptConverter.objects.test.js +0 -258
  132. package/dist/TypescriptConverter.objects.test.js.map +0 -1
  133. package/dist/TypescriptConverter.options.test.d.ts +0 -1
  134. package/dist/TypescriptConverter.options.test.js +0 -501
  135. package/dist/TypescriptConverter.options.test.js.map +0 -1
  136. package/dist/TypescriptConverter.primitives.test.d.ts +0 -1
  137. package/dist/TypescriptConverter.primitives.test.js +0 -26
  138. package/dist/TypescriptConverter.primitives.test.js.map +0 -1
  139. package/dist/utils/path-utils.test.d.ts +0 -1
  140. package/dist/utils/path-utils.test.js +0 -92
  141. package/dist/utils/path-utils.test.js.map +0 -1
package/README.md CHANGED
@@ -1,198 +1,306 @@
1
- # ajsc - Another JSON Schema Parser
1
+ # ajsc Another JSON Schema Converter
2
2
 
3
- **ajsc** is an npm package that transforms JSON Schema definitions into an intermediate representation (IR) called `IRNode`. This intermediate form is designed to be consumed by language-specific converters—such as the built-in [TypeScript converter](#typescriptconverter)—or by any custom converter you create for languages like Kotlin or Swift. This library streamlines the process of converting API JSON Schema definitions into strong-typed code representations, ensuring consistency between your API contracts and client implementations.
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
- ## Features
9
+ ```ts
10
+ import { emitTypescript, emitKotlin, emitSwift } from "ajsc";
8
11
 
9
- - **Comprehensive Schema Parsing:**
10
- Convert JSON Schema types including strings, numbers, booleans, nulls, literals, enums, unions, intersections, arrays, and objects into a unified IR.
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
- - **Intermediate Representation (`IRNode`):**
13
- The `IRNode` provides a standard structure that captures type, constraints, and path information. This representation is ideal for further transformation into various target languages.
23
+ const ts = emitTypescript(schema);
24
+ const kt = emitKotlin(schema);
25
+ const sw = emitSwift(schema);
26
+ ```
14
27
 
15
- - **Plugin-Based Architecture:**
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
- - **$defs and References Handling:**
19
- Resolve JSON Schema definitions (`$defs`) and references (`$ref`) seamlessly, ensuring reusable schema components are properly processed.
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
- - **Unique Signature Tracking:**
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
- ## Installation
43
+ ## Output examples
27
44
 
28
- Install **ajsc** via npm:
45
+ ### TypeScript
29
46
 
30
- ```bash
31
- npm install ajsc
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
- ## Usage
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
- ### Basic Conversion to IRNode
80
+ Pass `serializer: "none"` for plain types with no annotations.
37
81
 
38
- The primary function of ajsc is to convert a JSON Schema into an IRNode. Here’s an example of converting a simple JSON Schema that defines a string type:
82
+ ### Swift (default: Codable)
39
83
 
40
- ```javascript
41
- import { JSONSchemaConverter } from "ajsc";
84
+ ```ts
85
+ import { emitSwift } from "ajsc";
42
86
 
43
- const schema = { type: "string" };
44
- const converter = new JSONSchemaConverter(schema);
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
- console.log(converter.irNode);
47
- // Output:
48
- // {
49
- // type: "string",
50
- // constraints: {},
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
- Converting Complex Schemas
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.
102
+
103
+ ### Assembling a final file
77
104
 
78
- const converter = new JSONSchemaConverter(complexSchema);
79
- console.log(converter.irNode);
105
+ ```ts
106
+ const { code, imports } = emitKotlin(schema);
107
+ const file = [...imports.map((i) => `import ${i}`), "", code].join("\n");
80
108
  ```
81
109
 
82
- ## API Reference
110
+ Swift `imports` are module names (`["Foundation"]`); Kotlin `imports` are fully-qualified symbol paths. TypeScript `imports` is always empty.
111
+
112
+ ---
113
+
114
+ ## Package layout
115
+
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
+ ---
130
+
131
+ ## Function-style vs class-style
83
132
 
84
- ### JSONSchemaConverter
133
+ ```ts
134
+ // Function-style — recommended for most consumers.
135
+ import { emitKotlin } from "ajsc";
136
+ const result = emitKotlin(schema, opts);
85
137
 
86
- - Constructor:
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.
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
+ ```
99
143
 
100
- ### TypescriptConverter
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.
101
145
 
102
- The package includes a TypeScript language plugin that converts the IRNode into TypeScript type definitions.
146
+ ---
103
147
 
104
- - Constructor:
105
- `new TypescriptConverter(schema: object, options?: TypescriptConverterOpts)`
106
- - Properties:
107
- - code: A string containing the generated TypeScript code.
148
+ ## Options
108
149
 
109
- #### Options
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
- | `inlineTypes` | `boolean` | `false` | If true, object types are inlined instead of extracted as named type aliases. |
114
- | `depluralize` | `boolean` | `true` | Singularize array item type names. Handles irregular plurals (e.g. `entries` `Entry`, `people` `Person`). |
115
- | `arrayItemNaming` | `string \| false` | `false` | Controls the postfix for array item type names. `false` = no postfix, `string` = custom postfix (e.g. `"Item"` → `ContactItem`). |
116
- | `enumStyle` | `"union" \| "enum"` | `"union"` | `"union"` emits `"a" \| "b"`, `"enum"` emits `export enum`. Only applies when `inlineTypes` is false and all values are strings. |
117
- | `uncountableWords` | `string[]` | `undefined` | Additional words that should not be singularized (built-in: `"data"`, `"metadata"`). |
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
- #### Usage Examples
162
+ ### TypeScript (`TypescriptConverterOpts`)
120
163
 
121
- Default behavior array item types are automatically singularized:
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
- ```javascript
124
- import { TypescriptConverter } from "ajsc";
170
+ ### Kotlin (`KotlinConverterOpts`)
125
171
 
126
- const schema = {
127
- type: "object",
128
- properties: {
129
- name: { type: "string" },
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
- const tsConverter = new TypescriptConverter(schema);
153
- console.log(tsConverter.code);
177
+ ### Swift (`SwiftConverterOpts`)
154
178
 
155
- // Output:
156
- // export type Contact = { email: string; };
157
- // export type Profile = { email: string; };
158
- //
159
- // export type Root = { name: string; age: number; contacts?: Array<Contact>; profile?: Profile; };
160
- ```
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
+ ## Working with the IR directly
161
235
 
162
- Inline types (no extracted type aliases):
236
+ If you need the intermediate representation (e.g., to write a custom emitter), use `JSONSchemaConverter`:
163
237
 
164
- ```javascript
165
- const tsConverter = new TypescriptConverter(schema, { inlineTypes: true });
166
- console.log(tsConverter.code);
238
+ ```ts
239
+ import { JSONSchemaConverter } from "ajsc/ir";
167
240
 
168
- // Output:
169
- // { name: string; age: number; contacts?: Array<{ email: string; }>; profile?: { email: string; }; }
241
+ const ir = new JSONSchemaConverter({ type: "string" }).irNode;
242
+ // { type: "string", path: "", constraints: {} }
170
243
  ```
171
244
 
172
- Enum style:
245
+ `IRNode` shape is documented in `ajsc/types`.
173
246
 
174
- ```javascript
175
- const enumSchema = {
176
- type: "object",
177
- properties: {
178
- status: { type: "string", enum: ["active", "inactive", "pending"] },
179
- },
180
- };
247
+ ## Building a custom language emitter
248
+
249
+ To target a new language, extend `BaseConverter`:
181
250
 
182
- const tsConverter = new TypescriptConverter(enumSchema, { enumStyle: "enum" });
183
- console.log(tsConverter.code);
251
+ ```ts
252
+ import { BaseConverter, type LanguageProfile } from "ajsc/converter";
184
253
 
185
- // Output:
186
- // export enum Status { Active = "active", Inactive = "inactive", Pending = "pending" }
187
- //
188
- // export type Root = { status?: Status; };
254
+ class DartConverter extends BaseConverter {
255
+ protected readonly languageProfile: LanguageProfile = {
256
+ language: "dart",
257
+ // ... per-language overrides
258
+ };
259
+ // ... implement generateObjectType and the emission orchestration
260
+ }
189
261
  ```
190
262
 
191
- Custom uncountable words:
263
+ 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.
192
264
 
193
- ```javascript
194
- const tsConverter = new TypescriptConverter(schema, {
195
- uncountableWords: ["criteria", "alumni"],
196
- });
197
- // "criteria" array items will produce type "Criteria", not "Criterium"
265
+ ---
266
+
267
+ ## Migrating from v6 to v7
268
+
269
+ ### Breaking changes
270
+
271
+ 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:
272
+
273
+ - **`$ref: "#"` recursion** in TypeScript now emits direct self-reference (`Array<Tree>`) rather than a buggy `Child` extraction with `Array<any>` recursion.
274
+ - **`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`).
275
+ - **`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.
276
+ - **`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`.
277
+ - **Schema-level `default`** is now emitted in Kotlin and Swift property declarations for primitive defaults (`val foo: String = "x"`). TS output is unchanged.
278
+ - **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.
279
+ - **`./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.
280
+
281
+ ### New features
282
+
283
+ - **Kotlin and Swift converters.** First-class language targets, not just IR.
284
+ - **Top-level emit functions.** `emitTypescript`, `emitKotlin`, `emitSwift` from `"ajsc"`.
285
+ - **`./converter` subpath** for downstream extension authors.
286
+ - **Root subpath restored.** `import { TypescriptConverter } from "ajsc"` works again (was subpath-only in v6).
287
+
288
+ ### Migrating v5 → v7
289
+
290
+ If you're still on v5, the v6 → v7 changes apply on top of these:
291
+
292
+ ```diff
293
+ - import { TypescriptConverter, JSONSchemaConverter } from "ajsc";
294
+ + import { TypescriptConverter } from "ajsc"; // root entry restored
295
+ + import { JSONSchemaConverter } from "ajsc/ir"; // moved to subpath in v6
198
296
  ```
297
+
298
+ ---
299
+
300
+ ## Architecture
301
+
302
+ 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.
303
+
304
+ ## License
305
+
306
+ MIT