ajsc 7.1.0 → 7.3.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 (40) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +139 -5
  3. package/dist/converter/BaseConverter.d.ts +18 -1
  4. package/dist/converter/BaseConverter.js +16 -1
  5. package/dist/converter/BaseConverter.js.map +1 -1
  6. package/dist/converter/index.d.ts +6 -3
  7. package/dist/converter/index.js +5 -3
  8. package/dist/converter/index.js.map +1 -1
  9. package/dist/converter/inlineEmission.d.ts +42 -0
  10. package/dist/converter/inlineEmission.js +49 -0
  11. package/dist/converter/inlineEmission.js.map +1 -0
  12. package/dist/index.d.ts +6 -0
  13. package/dist/index.js +1 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/ir/JSONSchemaConverter.d.ts +9 -0
  16. package/dist/ir/JSONSchemaConverter.js +23 -0
  17. package/dist/ir/JSONSchemaConverter.js.map +1 -1
  18. package/dist/kotlin/KotlinConverter.d.ts +23 -0
  19. package/dist/kotlin/KotlinConverter.js +13 -0
  20. package/dist/kotlin/KotlinConverter.js.map +1 -1
  21. package/dist/kotlin/objectEmitter.d.ts +6 -0
  22. package/dist/kotlin/objectEmitter.js +31 -3
  23. package/dist/kotlin/objectEmitter.js.map +1 -1
  24. package/dist/kotlin/typeMapper.js +2 -0
  25. package/dist/kotlin/typeMapper.js.map +1 -1
  26. package/dist/swift/SwiftConverter.d.ts +24 -0
  27. package/dist/swift/SwiftConverter.js +12 -0
  28. package/dist/swift/SwiftConverter.js.map +1 -1
  29. package/dist/swift/structEmitter.d.ts +5 -0
  30. package/dist/swift/structEmitter.js +29 -5
  31. package/dist/swift/structEmitter.js.map +1 -1
  32. package/dist/swift/typeMapper.js +2 -0
  33. package/dist/swift/typeMapper.js.map +1 -1
  34. package/dist/types.d.ts +7 -1
  35. package/dist/typescript/TypescriptBaseConverter.js +2 -0
  36. package/dist/typescript/TypescriptBaseConverter.js.map +1 -1
  37. package/dist/typescript/TypescriptConverter.d.ts +1 -0
  38. package/dist/typescript/TypescriptConverter.js +1 -0
  39. package/dist/typescript/TypescriptConverter.js.map +1 -1
  40. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,45 @@
2
2
 
3
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
4
 
5
+ ## [7.3.0] — 2026-06-06
6
+
7
+ ### Added
8
+
9
+ - **`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.
10
+
11
+ - **`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`.
12
+
13
+ ### Internal
14
+
15
+ - 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.
16
+ - `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.
17
+ - 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.
18
+ - 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.
19
+ - New cross-language golden fixture `named-types` (schema × 3 languages = 3 new golden files, corpus grows to 60).
20
+ - Test count: 500 → 525.
21
+
22
+ ## [7.2.0] — 2026-04-25
23
+
24
+ Maintenance release — version bump only. No source or public-API changes since
25
+ 7.1.0 (the published tag differs from 7.1.0 only in `package.json` /
26
+ `package-lock.json`). Output is byte-identical to 7.1.0.
27
+
28
+ ## [7.1.0] — 2026-04-25
29
+
30
+ ### Added
31
+
32
+ - **`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).
33
+ - Top-level enums and discriminated unions remain extracted regardless of `inlineTypes` — they have their own dedup story and don't benefit from nesting.
34
+ - 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.
35
+ - New `nested-emission` cross-language fixture covering the inline pattern across TypeScript, Kotlin, and Swift (19th fixture, brings the golden corpus to 57 files).
36
+ - **`generateInlineNestedDecl` and `InlineNestedCollector`** exported from `ajsc/converter` for downstream language authors implementing similar nested-decl semantics.
37
+
38
+ ### Internal
39
+
40
+ - 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.
41
+ - Test count: 473 → 500 (per-language inline-types coverage + sibling-parent dedup pinning + the cross-language nested-emission fixture).
42
+ - Existing TS / Kotlin / Swift goldens are byte-identical between v7.0.0 and v7.1.0 (the new opt defaults to `false`).
43
+
5
44
  ## [7.0.0] — 2026-04-24
6
45
 
7
46
  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.
package/README.md CHANGED
@@ -29,15 +29,18 @@ Each emit function returns the same shape:
29
29
 
30
30
  ```ts
31
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
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
+ referencedNamedTypes: string[]; // external types referenced via x-named-type (not declared by ajsc)
36
37
  }
37
38
  ```
38
39
 
39
40
  `code` contains type declarations only. Consumers assemble the final source file by combining `imports` with `code`.
40
41
 
42
+ `referencedNamedTypes` lists the external type names this output refers to via the `x-named-type` keyword. ajsc does **not** declare these — the caller owns those definitions and is responsible for making them available (e.g., via an import or a sibling emit call). The list is deduped and sorted lexicographically. It is empty when no `x-named-type` annotations are present.
43
+
41
44
  ---
42
45
 
43
46
  ## Output examples
@@ -107,7 +110,7 @@ const { code, imports } = emitKotlin(schema);
107
110
  const file = [...imports.map((i) => `import ${i}`), "", code].join("\n");
108
111
  ```
109
112
 
110
- Swift `imports` are module names (`["Foundation"]`); Kotlin `imports` are fully-qualified symbol paths. TypeScript `imports` is always empty.
113
+ Swift `imports` are module names (`["Foundation"]`); Kotlin `imports` are fully-qualified symbol paths. TypeScript `imports` is always empty — for TypeScript consumers that need to know which external named types are referenced (via `x-named-type`), use `referencedNamedTypes` instead.
111
114
 
112
115
  ---
113
116
 
@@ -173,6 +176,7 @@ All three converters accept `BaseConverterOpts` plus language-specific options.
173
176
  |--------|------|---------|-------------|
174
177
  | `serializer` | `"kotlinx" \| "none"` | `"kotlinx"` | Emit `@Serializable`/`@SerialName`/`@Contextual` annotations and matching imports. `"none"` emits plain types. |
175
178
  | `packageName` | `string` | `undefined` | If set, emit `package <name>` at the top of the output. |
179
+ | `inlineTypes` | `boolean` | `false` | If true, nested object types emit as nested `data class` declarations inside their parent's body block instead of extracting to top-level siblings. Top-level enums and discriminated unions remain extracted. See "Emitting multiple schemas into one namespace" for the motivation. |
176
180
 
177
181
  ### Swift (`SwiftConverterOpts`)
178
182
 
@@ -180,6 +184,7 @@ All three converters accept `BaseConverterOpts` plus language-specific options.
180
184
  |--------|------|---------|-------------|
181
185
  | `serializer` | `"codable" \| "none"` | `"codable"` | Emit `: Codable` conformance, `CodingKeys` enums, and discriminated-union `init(from:)`/`encode(to:)` plumbing. `"none"` emits plain types. |
182
186
  | `accessLevel` | `"public" \| "internal"` | `"public"` | Access modifier on emitted types and members. |
187
+ | `inlineTypes` | `boolean` | `false` | If true, nested object types emit as nested `struct` declarations inside their parent's body block instead of extracting to top-level siblings. Top-level enums and discriminated `enum`s remain extracted. See "Emitting multiple schemas into one namespace" for the motivation. |
183
188
 
184
189
  ---
185
190
 
@@ -235,6 +240,39 @@ Schema-level `default` values are emitted inline for primitive types in Kotlin/S
235
240
 
236
241
  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
242
 
243
+ There are two ways to fix this: nest extracted types inside the parent (recommended for Kotlin/Swift), or share a name registry across calls. Pick whichever output shape your pipeline wants.
244
+
245
+ ### Recommended: `inlineTypes: true` (Kotlin / Swift)
246
+
247
+ Set `inlineTypes: true` per emit call. Each parent's nested object types are emitted as nested `data class` (Kotlin) or `struct` (Swift) declarations inside the parent's body block, scoped to its namespace. Cross-call collisions disappear structurally — no shared registry plumbing required.
248
+
249
+ ```ts
250
+ import { emitKotlin } from "ajsc";
251
+
252
+ const body = emitKotlin(bodySchema, { inlineTypes: true, rootTypeName: "Body" });
253
+ const response = emitKotlin(responseSchema, { inlineTypes: true, rootTypeName: "Response" });
254
+
255
+ // body.code:
256
+ // data class Body(val address: Address, ...) {
257
+ // data class Address(val street: String, ...)
258
+ // }
259
+ // response.code:
260
+ // data class Response(val address: Address, ...) {
261
+ // data class Address(val street: String, ...)
262
+ // }
263
+ //
264
+ // Concatenated under one `object Endpoint { ... }` — no name collisions:
265
+ // Body.Address and Response.Address are different scoped names.
266
+ ```
267
+
268
+ `inlineTypes: true` only nests plain object types. Top-level enums and discriminated unions (sealed interface in Kotlin, enum w/ associated values in Swift) remain at the same level as the parent — they have their own dedup story (canonical-key for enums; self-contained namespace for discriminated unions) and don't benefit from nesting.
269
+
270
+ The TypeScript converter has had `inlineTypes` since v6 — it produces anonymous inline type literals (a different language mechanism, same `inlineTypes` option name).
271
+
272
+ ### Alternative: shared `nameRegistry` + per-slot `namePrefix`
273
+
274
+ Use this when you specifically want flat top-level output (cleaner imports, simpler navigation in IDE outline panels).
275
+
238
276
  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
277
 
240
278
  ```ts
@@ -280,6 +318,102 @@ emitKotlin(responseSchema, { nameRegistry: registry, rootTypeName: "Response" })
280
318
 
281
319
  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
320
 
321
+ ### Choosing between `inlineTypes` and `nameRegistry`
322
+
323
+ | If you want… | Use |
324
+ |--------------------------------------------------|----------------------------------------------|
325
+ | Flat top-level decls (`Body`, `BodyAddress`, …) | `nameRegistry` + `namePrefix` |
326
+ | Nested decls inside each parent (idiomatic K/S) | `inlineTypes: true` |
327
+ | TypeScript anonymous inline literals | `inlineTypes: true` (TS-specific mechanism) |
328
+
329
+ ---
330
+
331
+ ## x-named-type — referencing external types verbatim
332
+
333
+ The `x-named-type` keyword lets you tell ajsc to emit a bare reference to an already-defined external type instead of inlining or extracting it from the schema.
334
+
335
+ ### When to use it
336
+
337
+ Use `x-named-type` when a property's type is defined elsewhere (a shared library, a hand-written class, a type from another emit call) and you want ajsc to reference it without redeclaring it.
338
+
339
+ ### Usage
340
+
341
+ Set `"x-named-type": "TypeName"` on any schema node. ajsc short-circuits that node to a verbatim reference — the schema body (`type`, `properties`, `$ref`, etc.) is intentionally ignored.
342
+
343
+ ```json
344
+ {
345
+ "type": "object",
346
+ "title": "Thread",
347
+ "properties": {
348
+ "author": { "x-named-type": "Message" },
349
+ "lastReply": { "x-named-type": "Message" },
350
+ "replies": { "type": "array", "items": { "x-named-type": "Message" } },
351
+ "id": { "type": "string" }
352
+ }
353
+ }
354
+ ```
355
+
356
+ **TypeScript output:**
357
+ ```ts
358
+ export type Thread = { author?: Message; lastReply?: Message; replies?: Array<Message>; id?: string; };
359
+ ```
360
+
361
+ **Kotlin output:**
362
+ ```kotlin
363
+ @Serializable
364
+ data class Thread(
365
+ val author: Message? = null,
366
+ val lastReply: Message? = null,
367
+ val replies: List<Message>? = null,
368
+ val id: String? = null,
369
+ )
370
+ ```
371
+
372
+ **Swift output:**
373
+ ```swift
374
+ public struct Thread: Codable {
375
+ public let author: Message?
376
+ public let lastReply: Message?
377
+ public let replies: [Message]?
378
+ public let id: String?
379
+ }
380
+ ```
381
+
382
+ ### At the root
383
+
384
+ When `x-named-type` appears at the root level, ajsc emits a type alias:
385
+
386
+ - TypeScript: `export type Root = Message;`
387
+ - Kotlin: `typealias Root = Message`
388
+ - Swift: `public typealias Root = Message`
389
+
390
+ ### Reporting referenced names
391
+
392
+ Referenced names are collected in `referencedNamedTypes` on the emit result (deduped and sorted). ajsc does **not** declare these types — the caller is responsible for importing or defining them:
393
+
394
+ ```ts
395
+ const { code, referencedNamedTypes } = emitTypescript(schema);
396
+ // referencedNamedTypes: ["Message"]
397
+ // → caller must import or declare Message separately
398
+ ```
399
+
400
+ ### Constraints
401
+
402
+ - The value must be a non-empty string (the type name to emit verbatim).
403
+ - The schema body is ignored — ajsc never inlines or extracts the node.
404
+ - `Message` will **not** appear in `extractedTypeNames` (ajsc did not declare it).
405
+
406
+ ### Limitations
407
+
408
+ - **Name collisions are not disambiguated.** If an `x-named-type` value matches a
409
+ name ajsc generates for a sibling structural type (e.g. you reference
410
+ `"Profile"` while a sibling object would also extract as `Profile`), ajsc emits
411
+ the generated declaration and the reference resolves to *that* type — the two
412
+ are silently merged. Keep `x-named-type` names distinct from the names ajsc
413
+ derives from your property/path structure. Both names appearing in
414
+ `extractedTypeNames` **and** `referencedNamedTypes` is the signal that a
415
+ collision occurred.
416
+
283
417
  ---
284
418
 
285
419
  ## Working with the IR directly
@@ -1,5 +1,6 @@
1
1
  import { ILanguageConverter, IRNode, SignatureOccurrenceValue } from "../types.js";
2
- export { walkIR } from "./walk.js";
2
+ import { walkIR } from "./walk.js";
3
+ export { walkIR };
3
4
  export type RefTypeName = string;
4
5
  export interface RefTypeEntry {
5
6
  signature: SignatureOccurrenceValue["signature"];
@@ -67,6 +68,15 @@ export interface LanguageProfile {
67
68
  * Language-specific signature dedup decision. Default: false. TS = `dedupSignatures.has(sig)`.
68
69
  */
69
70
  shouldReuseExistingSignature?: (signature: string) => boolean;
71
+ /**
72
+ * Formats a root-level type alias when the schema root is an `x-named-type`
73
+ * reference (e.g. Kotlin `typealias Root = Message`). Languages that render
74
+ * the root through a generic alias wrapper — TypeScript's
75
+ * `export type Root = <type>` — omit this and get the alias for free.
76
+ * Languages whose roots are nominal declarations (Kotlin `data class`, Swift
77
+ * `struct`) provide it so a bare root reference becomes a typealias instead.
78
+ */
79
+ formatRootTypeAlias?: (rootName: string, target: string) => string;
70
80
  }
71
81
  export interface RefTypeNamingConfig {
72
82
  /** Base postfixes to try for name collision resolution */
@@ -197,6 +207,7 @@ export declare abstract class BaseConverter implements Partial<ILanguageConverte
197
207
  abstract readonly rootTypeName: string;
198
208
  abstract readonly extractedTypeNames: string[];
199
209
  abstract readonly imports: string[];
210
+ abstract readonly referencedNamedTypes: string[];
200
211
  get language(): string;
201
212
  /** @internal Public for `BaseConverterContext`; treat as protected for subclasses. */
202
213
  refTypes: RefTypes;
@@ -251,6 +262,12 @@ export declare abstract class BaseConverter implements Partial<ILanguageConverte
251
262
  * Subclasses may filter further (e.g. to exclude the root type).
252
263
  */
253
264
  protected computeExtractedTypeNames(): string[];
265
+ /**
266
+ * Collects the names of all external types referenced via `x-named-type`,
267
+ * deduped and lexicographically sorted. Derived purely from the IR (the set
268
+ * is independent of language emission), mirroring computeExtractedTypeNames.
269
+ */
270
+ protected computeReferencedNamedTypes(ir: IRNode): string[];
254
271
  /** Path-derived, collision-free ref type name. Delegates to `naming.ts`. */
255
272
  protected getUniqueRefTypeName(signature: string, nodePath: string): RefTypeName;
256
273
  /**
@@ -2,7 +2,8 @@ import { computeExtractedTypeNames, getUniqueRefTypeName, } from "./naming.js";
2
2
  import { getReferencedType } from "./registry.js";
3
3
  import { isDiscriminatedUnion, mergeCompatibleUnions, tryMergeObjectUnion, tryMergeProperty, } from "./mergeUnions.js";
4
4
  import { applyDiscriminatedUnionEnhancements, collectUnionPropertyNames, enhanceDiscriminatedUnions, findDiscriminatorProperty, getConstStringValue, stripDiscriminatorField, } from "./discriminatedUnions.js";
5
- export { walkIR } from "./walk.js";
5
+ import { walkIR } from "./walk.js";
6
+ export { walkIR };
6
7
  export class BaseConverter {
7
8
  constructor() {
8
9
  /** @internal Public for `BaseConverterContext`; treat as protected for subclasses. */
@@ -44,6 +45,20 @@ export class BaseConverter {
44
45
  computeExtractedTypeNames() {
45
46
  return computeExtractedTypeNames(this);
46
47
  }
48
+ /**
49
+ * Collects the names of all external types referenced via `x-named-type`,
50
+ * deduped and lexicographically sorted. Derived purely from the IR (the set
51
+ * is independent of language emission), mirroring computeExtractedTypeNames.
52
+ */
53
+ computeReferencedNamedTypes(ir) {
54
+ const names = new Set();
55
+ walkIR(ir, (node) => {
56
+ if (node.type === "namedTypeRef" && node.namedType) {
57
+ names.add(node.namedType);
58
+ }
59
+ });
60
+ return [...names].sort();
61
+ }
47
62
  /** Path-derived, collision-free ref type name. Delegates to `naming.ts`. */
48
63
  getUniqueRefTypeName(signature, nodePath) {
49
64
  return getUniqueRefTypeName(this, signature, nodePath);
@@ -1 +1 @@
1
- {"version":3,"file":"BaseConverter.js","sourceRoot":"","sources":["../../src/converter/BaseConverter.ts"],"names":[],"mappings":"AACA,OAAO,EACL,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mCAAmC,EACnC,yBAAyB,EACzB,0BAA0B,EAC1B,yBAAyB,EACzB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AA4NnC,MAAM,OAAgB,aAAa;IAAnC;QAYE,sFAAsF;QAC/E,aAAQ,GAAa,EAAE,CAAC;QAC/B;;;WAGG;QACI,yBAAoB,GAAG,IAAI,GAAG,EAAU,CAAC;QAkBhD,qEAAqE;QAC9D,oBAAe,GAAG,CAAC,CAAC;QAE3B,sFAAsF;QAC/E,iBAAY,GAAG,CAAC,CAAC;QAExB;;;;;WAKG;QACI,sBAAiB,GAAG,IAAI,GAAG,EAG/B,CAAC;QACJ;;;WAGG;QACI,iBAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;QAChD;;;WAGG;QACI,oBAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAwH7C,CAAC;IA7KC,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC;IACvC,CAAC;IAqDD;;;OAGG;IACO,yBAAyB;QACjC,OAAO,yBAAyB,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,4EAA4E;IAClE,oBAAoB,CAAC,SAAiB,EAAE,QAAgB;QAChE,OAAO,oBAAoB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACzD,CAAC;IASD,uFAAuF;IAC7E,iBAAiB,CAAC,EAAU;QACpC,OAAO,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACO,qBAAqB,CAAC,EAAU;QACxC,OAAO,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,qCAAqC;IAC3B,mBAAmB,CAAC,EAAU;QACtC,OAAO,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,qCAAqC;IAC3B,gBAAgB,CAAC,SAAmB;QAC5C,OAAO,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC;IAED,qCAAqC;IAC3B,oBAAoB,CAC5B,OAAiB,EACjB,eAAyB,EACzB,YAAsB;QAEtB,OAAO,oBAAoB,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,CAAC,CAAC;IACtE,CAAC;IAED;;;OAGG;IACO,0BAA0B,CAAC,EAAU;QAC7C,0BAA0B,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACvC,CAAC;IAED;;;OAGG;IACI,uBAAuB,CAAC,GAAW,EAAE,aAAqB;QAC/D,OAAO,uBAAuB,CAAC,IAAI,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;IAC3D,CAAC;IAED;;;;;;;OAOG;IACI,iBAAiB,CAAC,IAAY;QACnC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACtD,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,OAAO,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;YAAE,CAAC,EAAE,CAAC;QACpD,OAAO,IAAI,GAAG,CAAC,CAAC;IAClB,CAAC;IAED,6CAA6C;IACnC,mCAAmC,CAAC,EAAU;QACtD,mCAAmC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAChD,CAAC;IAED;;;OAGG;IACI,yBAAyB,CAAC,OAAiB;QAIhD,OAAO,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED;;;OAGG;IACI,yBAAyB,CAC9B,OAAiB,EACjB,eAAyB;QAEzB,OAAO,yBAAyB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IAC7D,CAAC;IAED;;;OAGG;IACI,mBAAmB,CAAC,EAAsB;QAC/C,OAAO,mBAAmB,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC;CACF"}
1
+ {"version":3,"file":"BaseConverter.js","sourceRoot":"","sources":["../../src/converter/BaseConverter.ts"],"names":[],"mappings":"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"}
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * This subpath exposes the abstract {@link BaseConverter} class, the
5
5
  * {@link LanguageProfile} pattern that consolidates per-language behavior,
6
- * the {@link RefTypeEntry} registry shape, the indent-aware {@link Emitter}
7
- * helper (re-exported from `./Emitter.js` if needed by subclasses), and the
8
- * {@link walkIR} tree-walking helper.
6
+ * the {@link RefTypeEntry} registry shape, the indent-aware Emitter helper
7
+ * (importable from `./Emitter.js`), the {@link walkIR} tree-walking helper,
8
+ * and the {@link generateInlineNestedDecl} helper used by language emitters
9
+ * to implement `inlineTypes`-style nested-class output.
9
10
  *
10
11
  * Most consumers do NOT need this subpath — use the function-style emitters
11
12
  * (`emitTypescript` / `emitKotlin` / `emitSwift` from `ajsc`) or the
@@ -22,3 +23,5 @@
22
23
  */
23
24
  export { BaseConverter, walkIR } from "./BaseConverter.js";
24
25
  export type { BaseConverterOpts, RefTypeNamingConfig, RefTypeEntry, GenerateTypeUtils, LanguageProfile, } from "./BaseConverter.js";
26
+ export { generateInlineNestedDecl, indentLines } from "./inlineEmission.js";
27
+ export type { InlineNestedCollector } from "./inlineEmission.js";
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * This subpath exposes the abstract {@link BaseConverter} class, the
5
5
  * {@link LanguageProfile} pattern that consolidates per-language behavior,
6
- * the {@link RefTypeEntry} registry shape, the indent-aware {@link Emitter}
7
- * helper (re-exported from `./Emitter.js` if needed by subclasses), and the
8
- * {@link walkIR} tree-walking helper.
6
+ * the {@link RefTypeEntry} registry shape, the indent-aware Emitter helper
7
+ * (importable from `./Emitter.js`), the {@link walkIR} tree-walking helper,
8
+ * and the {@link generateInlineNestedDecl} helper used by language emitters
9
+ * to implement `inlineTypes`-style nested-class output.
9
10
  *
10
11
  * Most consumers do NOT need this subpath — use the function-style emitters
11
12
  * (`emitTypescript` / `emitKotlin` / `emitSwift` from `ajsc`) or the
@@ -21,4 +22,5 @@
21
22
  * Treat `@internal`-tagged symbols as protected.
22
23
  */
23
24
  export { BaseConverter, walkIR } from "./BaseConverter.js";
25
+ export { generateInlineNestedDecl, indentLines } from "./inlineEmission.js";
24
26
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/converter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/converter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAQ3D,OAAO,EAAE,wBAAwB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { BaseConverterContext, GenerateTypeUtils } from "./BaseConverter.js";
2
+ import type { IRNode } from "../types.js";
3
+ /**
4
+ * Per-parent collector used by language emitters under `inlineTypes: true` to
5
+ * accumulate nested type declarations destined for the parent's body block.
6
+ *
7
+ * Each invocation of `generate<Lang>ObjectType` allocates its own collector,
8
+ * so grandchildren nest into the child's body — preserving the schema's
9
+ * containment structure rather than flattening to the outermost parent.
10
+ *
11
+ * - `decls`: fully-formatted nested type declaration strings, in emission order.
12
+ * - `bySignature`: within-parent dedup map (signature → already-assigned name).
13
+ * Two properties of the same parent referencing the same shape share one
14
+ * nested decl; two properties of *different* parents do not (each parent's
15
+ * nested decls are scoped to its own namespace).
16
+ */
17
+ export interface InlineNestedCollector {
18
+ decls: string[];
19
+ bySignature: Map<string, string>;
20
+ }
21
+ /**
22
+ * Inline-mode replacement for `getReferencedType`. Returns the name to use in
23
+ * place of an extracted top-level reference, side-effecting `collector` with a
24
+ * fully-formatted nested type declaration when this signature is first seen.
25
+ *
26
+ * Language-specific decl assembly is delegated to `formatDecl` (e.g. Kotlin's
27
+ * `[@Serializable\n]data class ${name}${body}` vs Swift's `${access} struct
28
+ * ${name}: Codable ${body}`). The recursive emit step is delegated to
29
+ * `recurseObjectType` — typically the language's `generate<Lang>ObjectType`
30
+ * function. We pass it our parent's collector-aware utils as a defensive
31
+ * default; the recursive call sees `c.inlineTypes === true` and will still
32
+ * allocate its own collector internally for *its* nested types.
33
+ *
34
+ * Side effect to be aware of: `defaultResolveRefTypeName` reserves the chosen
35
+ * name in `c.usedDeclarationNames`. That reservation is what prevents nested
36
+ * names from colliding with top-level enum / discriminated-union names emitted
37
+ * elsewhere in the same converter run, and it's why a shared `nameRegistry`
38
+ * across emit calls flows through to nested-decl naming.
39
+ */
40
+ export declare function generateInlineNestedDecl<C extends BaseConverterContext>(c: C, ir: IRNode, collector: InlineNestedCollector, formatDecl: (c: C, name: string, body: string, ir: IRNode) => string, recurseObjectType: (c: C, ir: IRNode, utils: GenerateTypeUtils) => string): string | undefined;
41
+ /** Indents each non-empty line of `text` by `n` spaces. */
42
+ export declare function indentLines(text: string, n: number): string;
@@ -0,0 +1,49 @@
1
+ import { defaultResolveRefTypeName } from "./naming.js";
2
+ /**
3
+ * Inline-mode replacement for `getReferencedType`. Returns the name to use in
4
+ * place of an extracted top-level reference, side-effecting `collector` with a
5
+ * fully-formatted nested type declaration when this signature is first seen.
6
+ *
7
+ * Language-specific decl assembly is delegated to `formatDecl` (e.g. Kotlin's
8
+ * `[@Serializable\n]data class ${name}${body}` vs Swift's `${access} struct
9
+ * ${name}: Codable ${body}`). The recursive emit step is delegated to
10
+ * `recurseObjectType` — typically the language's `generate<Lang>ObjectType`
11
+ * function. We pass it our parent's collector-aware utils as a defensive
12
+ * default; the recursive call sees `c.inlineTypes === true` and will still
13
+ * allocate its own collector internally for *its* nested types.
14
+ *
15
+ * Side effect to be aware of: `defaultResolveRefTypeName` reserves the chosen
16
+ * name in `c.usedDeclarationNames`. That reservation is what prevents nested
17
+ * names from colliding with top-level enum / discriminated-union names emitted
18
+ * elsewhere in the same converter run, and it's why a shared `nameRegistry`
19
+ * across emit calls flows through to nested-decl naming.
20
+ */
21
+ export function generateInlineNestedDecl(c, ir, collector, formatDecl, recurseObjectType) {
22
+ if (!ir.signature)
23
+ return undefined;
24
+ if ((c.languageProfile.detectSelfReferenceToRoot ?? true) &&
25
+ ir.name &&
26
+ ir.name === c.rootName) {
27
+ return c.rootName;
28
+ }
29
+ const cached = collector.bySignature.get(ir.signature);
30
+ if (cached)
31
+ return cached;
32
+ const name = defaultResolveRefTypeName(c, ir, ir.signature);
33
+ collector.bySignature.set(ir.signature, name);
34
+ const childUtils = {
35
+ getReferencedType: (node) => generateInlineNestedDecl(c, node, collector, formatDecl, recurseObjectType),
36
+ };
37
+ const body = recurseObjectType(c, ir, childUtils);
38
+ collector.decls.push(formatDecl(c, name, body, ir));
39
+ return name;
40
+ }
41
+ /** Indents each non-empty line of `text` by `n` spaces. */
42
+ export function indentLines(text, n) {
43
+ const pad = " ".repeat(n);
44
+ return text
45
+ .split("\n")
46
+ .map((line) => (line.length ? pad + line : line))
47
+ .join("\n");
48
+ }
49
+ //# sourceMappingURL=inlineEmission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inlineEmission.js","sourceRoot":"","sources":["../../src/converter/inlineEmission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAuBxD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,wBAAwB,CACtC,CAAI,EACJ,EAAU,EACV,SAAgC,EAChC,UAAoE,EACpE,iBAAyE;IAEzE,IAAI,CAAC,EAAE,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IAEpC,IACE,CAAC,CAAC,CAAC,eAAe,CAAC,yBAAyB,IAAI,IAAI,CAAC;QACrD,EAAE,CAAC,IAAI;QACP,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,EACtB,CAAC;QACD,OAAO,CAAC,CAAC,QAAQ,CAAC;IACpB,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;IACvD,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,IAAI,GAAG,yBAAyB,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5D,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAE9C,MAAM,UAAU,GAAsB;QACpC,iBAAiB,EAAE,CAAC,IAAI,EAAE,EAAE,CAC1B,wBAAwB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,iBAAiB,CAAC;KAC9E,CAAC;IACF,MAAM,IAAI,GAAG,iBAAiB,CAAC,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;IAElD,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,CAAS;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1B,OAAO,IAAI;SACR,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SAChD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -33,6 +33,12 @@ export interface EmitResult {
33
33
  * const file = [...imports.map((i) => `import ${i}`), "", code].join("\n");
34
34
  */
35
35
  imports: string[];
36
+ /**
37
+ * Names of external, already-defined types this output references via the
38
+ * `x-named-type` schema keyword. ajsc does NOT declare these — the caller
39
+ * defines them elsewhere and wires the import/reference. Deduped and sorted.
40
+ */
41
+ referencedNamedTypes: string[];
36
42
  }
37
43
  /**
38
44
  * Emits TypeScript source for the given JSON Schema.
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ function toEmitResult(c) {
18
18
  rootTypeName: c.rootTypeName,
19
19
  extractedTypeNames: c.extractedTypeNames,
20
20
  imports: c.imports,
21
+ referencedNamedTypes: c.referencedNamedTypes,
21
22
  };
22
23
  }
23
24
  /**
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,0CAA0C;AAC1C,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,uCAAuC;AACvC,oFAAoF;AACpF,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AAGjF,OAAO,EACL,mBAAmB,GAEpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,eAAe,GAEhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,GAEf,MAAM,kBAAkB,CAAC;AAsC1B,SAAS,YAAY,CAAC,CAKrB;IACC,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,kBAAkB,EAAE,CAAC,CAAC,kBAAkB;QACxC,OAAO,EAAE,CAAC,CAAC,OAAO;KACnB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,MAA6B,EAC7B,IAA8B;IAE9B,OAAO,YAAY,CAAC,IAAI,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CACxB,MAA6B,EAC7B,IAA0B;IAE1B,OAAO,YAAY,CAAC,IAAI,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CACvB,MAA6B,EAC7B,IAAyB;IAEzB,OAAO,YAAY,CAAC,IAAI,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,oEAAoE;AACpE,iFAAiF;AACjF,qEAAqE;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,0CAA0C;AAC1C,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,uCAAuC;AACvC,oFAAoF;AACpF,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AAGjF,OAAO,EACL,mBAAmB,GAEpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,eAAe,GAEhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,GAEf,MAAM,kBAAkB,CAAC;AA6C1B,SAAS,YAAY,CAAC,CAMrB;IACC,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,kBAAkB,EAAE,CAAC,CAAC,kBAAkB;QACxC,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,oBAAoB,EAAE,CAAC,CAAC,oBAAoB;KAC7C,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,MAA6B,EAC7B,IAA8B;IAE9B,OAAO,YAAY,CAAC,IAAI,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CACxB,MAA6B,EAC7B,IAA0B;IAE1B,OAAO,YAAY,CAAC,IAAI,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CACvB,MAA6B,EAC7B,IAAyB;IAEzB,OAAO,YAAY,CAAC,IAAI,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,oEAAoE;AACpE,iFAAiF;AACjF,qEAAqE;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1,5 +1,14 @@
1
1
  import { JSONSchema7Definition } from "json-schema";
2
2
  import { ConverterOptions, IRNode, SignatureOccurrences } from "../types.js";
3
+ /**
4
+ * Normalizes the `x-named-type` keyword value into a single external type name.
5
+ *
6
+ * This is the one widen-able boundary for the keyword: today only the string
7
+ * form is supported. A future per-language object form (e.g.
8
+ * `{ ts, kotlin, swift }`) would be resolved here without touching the IR shape
9
+ * or any emitter.
10
+ */
11
+ export declare function normalizeNamedType(value: unknown): string | undefined;
3
12
  /**
4
13
  * JSONSchemaConverter converts a JSON Schema (Draft-07) into an
5
14
  * intermediate representation (IR) that can later be transformed
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Normalizes the `x-named-type` keyword value into a single external type name.
3
+ *
4
+ * This is the one widen-able boundary for the keyword: today only the string
5
+ * form is supported. A future per-language object form (e.g.
6
+ * `{ ts, kotlin, swift }`) would be resolved here without touching the IR shape
7
+ * or any emitter.
8
+ */
9
+ export function normalizeNamedType(value) {
10
+ if (typeof value === "string" && value.trim() !== "")
11
+ return value;
12
+ return undefined;
13
+ }
1
14
  /**
2
15
  * JSONSchemaConverter converts a JSON Schema (Draft-07) into an
3
16
  * intermediate representation (IR) that can later be transformed
@@ -105,6 +118,16 @@ export class JSONSchemaConverter {
105
118
  };
106
119
  }
107
120
  }
121
+ // `x-named-type`: short-circuit to an external named-type reference, ahead
122
+ // of $ref resolution. The subschema body (type/properties/$ref) is
123
+ // intentionally ignored — this node is a verbatim reference to a
124
+ // caller-defined type, never inlined or extracted.
125
+ const namedType = normalizeNamedType(schema["x-named-type"]);
126
+ if (namedType) {
127
+ const node = { type: "namedTypeRef", namedType, path: ctx.path };
128
+ this.applyMetadata(node, schema);
129
+ return node;
130
+ }
108
131
  // Step 1.4: If the schema contains a $ref, resolve it with cycle detection.
109
132
  if (schema.$ref) {
110
133
  const ref = schema.$ref;