@pajecawav/json2code 0.0.0-stage → 0.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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -2
  3. package/dist/cli.d.mts +1 -0
  4. package/dist/cli.mjs +126 -0
  5. package/dist/cli.mjs.map +1 -0
  6. package/dist/core/config.mjs +53 -0
  7. package/dist/core/config.mjs.map +1 -0
  8. package/dist/core/discovery/index.d.mts +10 -0
  9. package/dist/core/discovery/index.d.mts.map +1 -0
  10. package/dist/core/discovery/index.mjs +83 -0
  11. package/dist/core/discovery/index.mjs.map +1 -0
  12. package/dist/core/generate.d.mts +11 -0
  13. package/dist/core/generate.d.mts.map +1 -0
  14. package/dist/core/generate.mjs +89 -0
  15. package/dist/core/generate.mjs.map +1 -0
  16. package/dist/core/ir/types.d.mts +100 -0
  17. package/dist/core/ir/types.d.mts.map +1 -0
  18. package/dist/core/ir/visit.d.mts +8 -0
  19. package/dist/core/ir/visit.d.mts.map +1 -0
  20. package/dist/core/ir/visit.mjs +38 -0
  21. package/dist/core/ir/visit.mjs.map +1 -0
  22. package/dist/core/linker/index.d.mts +32 -0
  23. package/dist/core/linker/index.d.mts.map +1 -0
  24. package/dist/core/linker/index.mjs +103 -0
  25. package/dist/core/linker/index.mjs.map +1 -0
  26. package/dist/core/printer/abstract.d.mts +26 -0
  27. package/dist/core/printer/abstract.d.mts.map +1 -0
  28. package/dist/core/types.d.mts +74 -0
  29. package/dist/core/types.d.mts.map +1 -0
  30. package/dist/core/types.mjs +27 -0
  31. package/dist/core/types.mjs.map +1 -0
  32. package/dist/core/walker/abstract.d.mts +18 -0
  33. package/dist/core/walker/abstract.d.mts.map +1 -0
  34. package/dist/core/walker/json-schema.d.mts +10 -0
  35. package/dist/core/walker/json-schema.d.mts.map +1 -0
  36. package/dist/core/walker/json-schema.mjs +311 -0
  37. package/dist/core/walker/json-schema.mjs.map +1 -0
  38. package/dist/core/walker/ref-resolver.d.mts +22 -0
  39. package/dist/core/walker/ref-resolver.d.mts.map +1 -0
  40. package/dist/core/walker/ref-resolver.mjs +66 -0
  41. package/dist/core/walker/ref-resolver.mjs.map +1 -0
  42. package/dist/core/writer/index.d.mts +19 -0
  43. package/dist/core/writer/index.d.mts.map +1 -0
  44. package/dist/core/writer/index.mjs +84 -0
  45. package/dist/core/writer/index.mjs.map +1 -0
  46. package/dist/diagnostics/errors.d.mts +39 -0
  47. package/dist/diagnostics/errors.d.mts.map +1 -0
  48. package/dist/diagnostics/errors.mjs +50 -0
  49. package/dist/diagnostics/errors.mjs.map +1 -0
  50. package/dist/diagnostics/logger.d.mts +10 -0
  51. package/dist/diagnostics/logger.d.mts.map +1 -0
  52. package/dist/diagnostics/logger.mjs +18 -0
  53. package/dist/diagnostics/logger.mjs.map +1 -0
  54. package/dist/index.d.mts +24 -0
  55. package/dist/index.d.mts.map +1 -0
  56. package/dist/index.mjs +26 -0
  57. package/dist/index.mjs.map +1 -0
  58. package/dist/package.mjs +6 -0
  59. package/dist/package.mjs.map +1 -0
  60. package/dist/targets/index.d.mts +9 -0
  61. package/dist/targets/index.d.mts.map +1 -0
  62. package/dist/targets/index.mjs +30 -0
  63. package/dist/targets/index.mjs.map +1 -0
  64. package/dist/targets/shared.mjs +137 -0
  65. package/dist/targets/shared.mjs.map +1 -0
  66. package/dist/targets/typescript/printer.d.mts +13 -0
  67. package/dist/targets/typescript/printer.d.mts.map +1 -0
  68. package/dist/targets/typescript/printer.mjs +47 -0
  69. package/dist/targets/typescript/printer.mjs.map +1 -0
  70. package/dist/targets/valibot/printer.d.mts +21 -0
  71. package/dist/targets/valibot/printer.d.mts.map +1 -0
  72. package/dist/targets/valibot/printer.mjs +198 -0
  73. package/dist/targets/valibot/printer.mjs.map +1 -0
  74. package/dist/targets/zod/lazy.d.mts +29 -0
  75. package/dist/targets/zod/lazy.d.mts.map +1 -0
  76. package/dist/targets/zod/lazy.mjs +100 -0
  77. package/dist/targets/zod/lazy.mjs.map +1 -0
  78. package/dist/targets/zod/printer.d.mts +21 -0
  79. package/dist/targets/zod/printer.d.mts.map +1 -0
  80. package/dist/targets/zod/printer.mjs +193 -0
  81. package/dist/targets/zod/printer.mjs.map +1 -0
  82. package/package.json +71 -3
@@ -0,0 +1,100 @@
1
+ //#region src/core/ir/types.d.ts
2
+ /**
3
+ * Intermediate representation (IR) — the only "language" between a Walker and a Printer.
4
+ * Any custom Walker must produce IR; any Printer must consume it.
5
+ */
6
+ export interface IRMeta {
7
+ readonly title?: string;
8
+ readonly description?: string;
9
+ readonly deprecated?: boolean;
10
+ readonly default?: unknown;
11
+ readonly examples?: readonly unknown[];
12
+ }
13
+ /** Abstract reference to a document export. Resolved to a relative import by the Linker/Printer. */
14
+ export interface Ref {
15
+ readonly docId: string;
16
+ readonly exportName: string;
17
+ }
18
+ interface IRNodeBase {
19
+ readonly meta?: IRMeta;
20
+ }
21
+ export type IRType = (IRNodeBase & {
22
+ readonly kind: "ref";
23
+ readonly ref: Ref;
24
+ }) | (IRNodeBase & {
25
+ readonly kind: "string";
26
+ readonly enum?: readonly string[];
27
+ readonly const?: string;
28
+ readonly pattern?: string;
29
+ readonly format?: string;
30
+ readonly minLength?: number;
31
+ readonly maxLength?: number;
32
+ }) | (IRNodeBase & {
33
+ readonly kind: "number";
34
+ readonly enum?: readonly number[];
35
+ readonly const?: number;
36
+ readonly minimum?: number;
37
+ readonly maximum?: number;
38
+ readonly exclusiveMinimum?: number;
39
+ readonly exclusiveMaximum?: number;
40
+ readonly multipleOf?: number;
41
+ }) | (IRNodeBase & {
42
+ readonly kind: "integer";
43
+ readonly enum?: readonly number[];
44
+ readonly const?: number;
45
+ readonly minimum?: number;
46
+ readonly maximum?: number;
47
+ readonly exclusiveMinimum?: number;
48
+ readonly exclusiveMaximum?: number;
49
+ readonly multipleOf?: number;
50
+ }) | (IRNodeBase & {
51
+ readonly kind: "boolean";
52
+ readonly const?: boolean;
53
+ }) | (IRNodeBase & {
54
+ readonly kind: "null";
55
+ }) | (IRNodeBase & {
56
+ readonly kind: "literal";
57
+ readonly value: string | number | boolean | null;
58
+ }) | (IRNodeBase & {
59
+ readonly kind: "array";
60
+ readonly items?: IRType;
61
+ readonly minItems?: number;
62
+ readonly maxItems?: number;
63
+ readonly uniqueItems?: boolean;
64
+ }) | (IRNodeBase & {
65
+ readonly kind: "tuple";
66
+ readonly items: readonly IRType[];
67
+ readonly additionalItems?: IRType;
68
+ }) | (IRNodeBase & {
69
+ readonly kind: "object";
70
+ readonly properties: ReadonlyMap<string, IRType>;
71
+ readonly required: readonly string[];
72
+ readonly additionalProperties?: IRType | false;
73
+ }) | (IRNodeBase & {
74
+ readonly kind: "record";
75
+ readonly values: IRType;
76
+ }) | (IRNodeBase & {
77
+ readonly kind: "union";
78
+ readonly variants: readonly IRType[];
79
+ }) | (IRNodeBase & {
80
+ readonly kind: "intersection";
81
+ readonly variants: readonly IRType[];
82
+ }) | (IRNodeBase & {
83
+ readonly kind: "unknown";
84
+ });
85
+ export type IRKind = IRType["kind"];
86
+ export interface SourceDoc {
87
+ /** Normalized document id (posix path relative to the input root). */
88
+ readonly docId: string;
89
+ /** Absolute path of the source file (for diagnostics). */
90
+ readonly sourcePath: string;
91
+ /** Named exports: $defs/definitions + the schema root. */
92
+ readonly exports: ReadonlyMap<string, IRType>;
93
+ /** Deterministic order of export names. */
94
+ readonly exportOrder: readonly string[];
95
+ /** Export name for the schema root (PascalCase of the file name). */
96
+ readonly rootExportName: string;
97
+ }
98
+ //#endregion
99
+ export {};
100
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/core/ir/types.ts"],"mappings":";;;;;iBAKiB;WACP;WACA;WACA;WACA;WACA;;;iBAIO;WACP;WACA;;UAGA;WACA,OAAO;;YAGL,UACR;WACQ;WACA,KAAK;MAEb;WACQ;WACA;WACA;WACA;WACA;WACA;WACA;MAER;WACQ;WACA;WACA;WACA;WACA;WACA;WACA;WACA;MAER;WACQ;WACA;WACA;WACA;WACA;WACA;WACA;WACA;MAER;WACQ;WACA;MAER;WACQ;MAER;WACQ;WACA;MAER;WACQ;WACA,QAAQ;WACR;WACA;WACA;MAER;WACQ;WACA,gBAAgB;WAChB,kBAAkB;MAE1B;WACQ;WACA,YAAY,oBAAoB;WAChC;WACA,uBAAuB;MAE/B;WACQ;WACA,QAAQ;MAEhB;WACQ;WACA,mBAAmB;MAE3B;WACQ;WACA,mBAAmB;MAE3B;WACQ;;YAGA,SAAS;iBAEJ;;WAEP;;WAEA;;WAEA,SAAS,oBAAoB;;WAE7B;;WAEA"}
@@ -0,0 +1,8 @@
1
+ import { IRType, Ref } from "./types.mjs";
2
+ //#region src/core/ir/visit.d.ts
3
+ /** Depth-first traversal of an IR node, visiting every {@link Ref} leaf. */
4
+ export declare function visitRefs(node: IRType, visit: (ref: Ref) => void): void;
5
+ /** Collect the set of documents referenced (directly) by an IR node. */
6
+ export declare function referencedDocs(node: IRType): Set<string>;
7
+ //#endregion
8
+ //# sourceMappingURL=visit.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visit.d.mts","names":[],"sources":["../../../src/core/ir/visit.ts"],"mappings":";;;wBAGgB,UAAU,MAAM,QAAQ,QAAQ,KAAK;;wBAyCrC,eAAe,MAAM,SAAS"}
@@ -0,0 +1,38 @@
1
+ //#region src/core/ir/visit.ts
2
+ /** Depth-first traversal of an IR node, visiting every {@link Ref} leaf. */
3
+ function visitRefs(node, visit) {
4
+ switch (node.kind) {
5
+ case "ref":
6
+ visit(node.ref);
7
+ return;
8
+ case "array":
9
+ if (node.items) visitRefs(node.items, visit);
10
+ return;
11
+ case "tuple":
12
+ for (const item of node.items) visitRefs(item, visit);
13
+ if (node.additionalItems) visitRefs(node.additionalItems, visit);
14
+ return;
15
+ case "object":
16
+ for (const [, value] of node.properties) visitRefs(value, visit);
17
+ if (node.additionalProperties) visitRefs(node.additionalProperties, visit);
18
+ return;
19
+ case "record":
20
+ visitRefs(node.values, visit);
21
+ return;
22
+ case "union":
23
+ case "intersection":
24
+ for (const variant of node.variants) visitRefs(variant, visit);
25
+ return;
26
+ default: return;
27
+ }
28
+ }
29
+ /** Collect the set of documents referenced (directly) by an IR node. */
30
+ function referencedDocs(node) {
31
+ const docs = /* @__PURE__ */ new Set();
32
+ visitRefs(node, (ref) => docs.add(ref.docId));
33
+ return docs;
34
+ }
35
+ //#endregion
36
+ export { referencedDocs, visitRefs };
37
+
38
+ //# sourceMappingURL=visit.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visit.mjs","names":[],"sources":["../../../src/core/ir/visit.ts"],"sourcesContent":["import type { IRType, Ref } from \"./types.js\";\n\n/** Depth-first traversal of an IR node, visiting every {@link Ref} leaf. */\nexport function visitRefs(node: IRType, visit: (ref: Ref) => void): void {\n\tswitch (node.kind) {\n\t\tcase \"ref\":\n\t\t\tvisit(node.ref);\n\t\t\treturn;\n\t\tcase \"array\":\n\t\t\tif (node.items) {\n\t\t\t\tvisitRefs(node.items, visit);\n\t\t\t}\n\t\t\treturn;\n\t\tcase \"tuple\":\n\t\t\tfor (const item of node.items) {\n\t\t\t\tvisitRefs(item, visit);\n\t\t\t}\n\t\t\tif (node.additionalItems) {\n\t\t\t\tvisitRefs(node.additionalItems, visit);\n\t\t\t}\n\t\t\treturn;\n\t\tcase \"object\":\n\t\t\tfor (const [, value] of node.properties) {\n\t\t\t\tvisitRefs(value, visit);\n\t\t\t}\n\t\t\tif (node.additionalProperties) {\n\t\t\t\tvisitRefs(node.additionalProperties, visit);\n\t\t\t}\n\t\t\treturn;\n\t\tcase \"record\":\n\t\t\tvisitRefs(node.values, visit);\n\t\t\treturn;\n\t\tcase \"union\":\n\t\tcase \"intersection\":\n\t\t\tfor (const variant of node.variants) {\n\t\t\t\tvisitRefs(variant, visit);\n\t\t\t}\n\t\t\treturn;\n\t\tdefault:\n\t\t\treturn;\n\t}\n}\n\n/** Collect the set of documents referenced (directly) by an IR node. */\nexport function referencedDocs(node: IRType): Set<string> {\n\tconst docs = new Set<string>();\n\tvisitRefs(node, ref => docs.add(ref.docId));\n\treturn docs;\n}\n"],"mappings":";;AAGA,SAAgB,UAAU,MAAc,OAAiC;CACxE,QAAQ,KAAK,MAAb;EACC,KAAK;GACJ,MAAM,KAAK,GAAG;GACd;EACD,KAAK;GACJ,IAAI,KAAK,OACR,UAAU,KAAK,OAAO,KAAK;GAE5B;EACD,KAAK;GACJ,KAAK,MAAM,QAAQ,KAAK,OACvB,UAAU,MAAM,KAAK;GAEtB,IAAI,KAAK,iBACR,UAAU,KAAK,iBAAiB,KAAK;GAEtC;EACD,KAAK;GACJ,KAAK,MAAM,GAAG,UAAU,KAAK,YAC5B,UAAU,OAAO,KAAK;GAEvB,IAAI,KAAK,sBACR,UAAU,KAAK,sBAAsB,KAAK;GAE3C;EACD,KAAK;GACJ,UAAU,KAAK,QAAQ,KAAK;GAC5B;EACD,KAAK;EACL,KAAK;GACJ,KAAK,MAAM,WAAW,KAAK,UAC1B,UAAU,SAAS,KAAK;GAEzB;EACD,SACC;CACF;AACD;;AAGA,SAAgB,eAAe,MAA2B;CACzD,MAAM,uBAAO,IAAI,IAAY;CAC7B,UAAU,OAAM,QAAO,KAAK,IAAI,IAAI,KAAK,CAAC;CAC1C,OAAO;AACR"}
@@ -0,0 +1,32 @@
1
+ import { Diagnostic } from "../../diagnostics/errors.mjs";
2
+ import { Logger } from "../../diagnostics/logger.mjs";
3
+ import { SourceDoc } from "../ir/types.mjs";
4
+ import { PrintContext } from "../printer/abstract.mjs";
5
+ import { ResolvedOptions } from "../types.mjs";
6
+ //#region src/core/linker/index.d.ts
7
+ export interface LinkerOptions {
8
+ readonly options: ResolvedOptions;
9
+ readonly targetName: string;
10
+ /** Maps a doc id to its output subpath (relative to output root, without extension). */
11
+ readonly mapOutput: (docId: string) => string;
12
+ /** Diagnostics sink for naming warnings. */
13
+ readonly diagnostics: Diagnostic[];
14
+ }
15
+ /**
16
+ * Resolves abstract {@link Ref}s into relative import specifiers and output paths.
17
+ * One Linker per target; builds the {@link PrintContext} handed to printers.
18
+ */
19
+ export declare class Linker {
20
+ private readonly opts;
21
+ private readonly namesByDoc;
22
+ constructor(opts: LinkerOptions);
23
+ /** Default output subpath for a doc id: `<targetName>/<stem>` (strips the source extension). */
24
+ static defaultMapOutput(targetName: string, docId: string): string;
25
+ outputPath(docId: string): string;
26
+ resolveImport(fromDocId: string, toDocId: string): string;
27
+ resolveExportName(doc: SourceDoc, exportName: string): string;
28
+ createPrintContext(logger: Logger, diagnostics: Diagnostic[]): PrintContext;
29
+ private extension;
30
+ }
31
+ //#endregion
32
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/linker/index.ts"],"mappings":";;;;;;iBAOiB;WACP,SAAS;WACT;;WAEA,YAAY;;WAEZ,aAAa;;;;;;qBAOV;mBACK;mBACA;EAEjB,YAAmB,MAAM;;SAKX,iBAAiB,oBAAoB;EAKnD,WAAkB;EAIlB,cAAqB,mBAAmB;EAUxC,kBAAyB,KAAK,WAAW;EAazC,mBAA0B,QAAQ,QAAQ,aAAa,eAAe;UAY9D"}
@@ -0,0 +1,103 @@
1
+ import { DiagnosticCode, createDiagnostic } from "../../diagnostics/errors.mjs";
2
+ import { posix } from "node:path";
3
+ //#region src/core/linker/index.ts
4
+ /**
5
+ * Resolves abstract {@link Ref}s into relative import specifiers and output paths.
6
+ * One Linker per target; builds the {@link PrintContext} handed to printers.
7
+ */
8
+ var Linker = class {
9
+ opts;
10
+ namesByDoc = /* @__PURE__ */ new Map();
11
+ constructor(opts) {
12
+ this.opts = opts;
13
+ }
14
+ /** Default output subpath for a doc id: `<targetName>/<stem>` (strips the source extension). */
15
+ static defaultMapOutput(targetName, docId) {
16
+ return `${targetName}/${stripExt(docId)}`;
17
+ }
18
+ outputPath(docId) {
19
+ return `${this.opts.mapOutput(docId)}.ts`;
20
+ }
21
+ resolveImport(fromDocId, toDocId) {
22
+ const fromDir = posix.dirname(this.opts.mapOutput(fromDocId));
23
+ const toPath = this.opts.mapOutput(toDocId);
24
+ let rel = posix.relative(fromDir, toPath);
25
+ if (!rel.startsWith(".")) rel = `./${rel}`;
26
+ return `${rel}${this.extension()}`;
27
+ }
28
+ resolveExportName(doc, exportName) {
29
+ const style = this.opts.options.naming;
30
+ if (style === "origin") return exportName;
31
+ let names = this.namesByDoc.get(doc.docId);
32
+ if (!names) {
33
+ names = buildNames(doc, style, this.opts.diagnostics ?? []);
34
+ this.namesByDoc.set(doc.docId, names);
35
+ }
36
+ return names.get(exportName) ?? exportName;
37
+ }
38
+ createPrintContext(logger, diagnostics) {
39
+ return {
40
+ logger,
41
+ options: this.opts.options,
42
+ diagnostics,
43
+ targetName: this.opts.targetName,
44
+ outputPath: (docId) => this.outputPath(docId),
45
+ resolveImport: (from, to) => this.resolveImport(from, to),
46
+ resolveExportName: (doc, name) => this.resolveExportName(doc, name)
47
+ };
48
+ }
49
+ extension() {
50
+ switch (this.opts.options.importExtension) {
51
+ case "js": return ".js";
52
+ case "ts": return ".ts";
53
+ case "none": return "";
54
+ }
55
+ }
56
+ };
57
+ /**
58
+ * Assign final export names for a document in the given style.
59
+ * Deterministic: follows `exportOrder`; collisions get numeric suffixes + a warning.
60
+ */
61
+ function buildNames(doc, style, diagnostics) {
62
+ const names = /* @__PURE__ */ new Map();
63
+ const used = /* @__PURE__ */ new Set();
64
+ for (const name of doc.exportOrder) {
65
+ let candidate = styleName(name, style);
66
+ if (used.has(candidate)) {
67
+ const base = candidate;
68
+ let i = 2;
69
+ while (used.has(candidate)) {
70
+ candidate = `${base}${i}`;
71
+ i++;
72
+ }
73
+ diagnostics.push(createDiagnostic(DiagnosticCode.NameCollision, "warning", `Export "${name}" maps to an existing "${base}" name; using "${candidate}"`, { docId: doc.docId }));
74
+ }
75
+ names.set(name, candidate);
76
+ used.add(candidate);
77
+ }
78
+ return names;
79
+ }
80
+ /** Split a name into words: by separators (non-alphanumerics) and camelCase boundaries. */
81
+ function words(name) {
82
+ return name.split(/[^$A-Za-z0-9]+/).filter(Boolean).flatMap((w) => w.split(/(?<=[a-z0-9])(?=[A-Z])/)).filter(Boolean);
83
+ }
84
+ function styleName(name, style) {
85
+ const ws = words(name);
86
+ if (ws.length === 0) return name;
87
+ const [first, ...rest] = ws;
88
+ const joined = [style === "camel" ? (first ?? "").toLowerCase() : first, ...rest.map((w) => w.charAt(0).toUpperCase() + w.slice(1))].join("");
89
+ switch (style) {
90
+ case "camel": return `${joined.charAt(0).toLowerCase()}${joined.slice(1)}`;
91
+ case "pascal": return `${joined.charAt(0).toUpperCase()}${joined.slice(1)}`;
92
+ case "origin": return name;
93
+ }
94
+ }
95
+ function stripExt(p) {
96
+ const dot = p.lastIndexOf(".");
97
+ if (dot > p.lastIndexOf("/")) return p.slice(0, dot);
98
+ return p;
99
+ }
100
+ //#endregion
101
+ export { Linker, styleName };
102
+
103
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/core/linker/index.ts"],"sourcesContent":["import { posix } from \"node:path\";\nimport { createDiagnostic, DiagnosticCode, type Diagnostic } from \"../../diagnostics/errors.js\";\nimport type { Logger } from \"../../diagnostics/logger.js\";\nimport type { SourceDoc } from \"../ir/types.js\";\nimport type { PrintContext } from \"../printer/abstract.js\";\nimport type { NamingStyle, PrintedFile, ResolvedOptions } from \"../types.js\";\n\nexport interface LinkerOptions {\n\treadonly options: ResolvedOptions;\n\treadonly targetName: string;\n\t/** Maps a doc id to its output subpath (relative to output root, without extension). */\n\treadonly mapOutput: (docId: string) => string;\n\t/** Diagnostics sink for naming warnings. */\n\treadonly diagnostics: Diagnostic[];\n}\n\n/**\n * Resolves abstract {@link Ref}s into relative import specifiers and output paths.\n * One Linker per target; builds the {@link PrintContext} handed to printers.\n */\nexport class Linker {\n\tprivate readonly opts: LinkerOptions;\n\tprivate readonly namesByDoc = new Map<string, ReadonlyMap<string, string>>();\n\n\tpublic constructor(opts: LinkerOptions) {\n\t\tthis.opts = opts;\n\t}\n\n\t/** Default output subpath for a doc id: `<targetName>/<stem>` (strips the source extension). */\n\tpublic static defaultMapOutput(targetName: string, docId: string): string {\n\t\tconst stem = stripExt(docId);\n\t\treturn `${targetName}/${stem}`;\n\t}\n\n\tpublic outputPath(docId: string): string {\n\t\treturn `${this.opts.mapOutput(docId)}.ts`;\n\t}\n\n\tpublic resolveImport(fromDocId: string, toDocId: string): string {\n\t\tconst fromDir = posix.dirname(this.opts.mapOutput(fromDocId));\n\t\tconst toPath = this.opts.mapOutput(toDocId);\n\t\tlet rel = posix.relative(fromDir, toPath);\n\t\tif (!rel.startsWith(\".\")) {\n\t\t\trel = `./${rel}`;\n\t\t}\n\t\treturn `${rel}${this.extension()}`;\n\t}\n\n\tpublic resolveExportName(doc: SourceDoc, exportName: string): string {\n\t\tconst style = this.opts.options.naming;\n\t\tif (style === \"origin\") {\n\t\t\treturn exportName;\n\t\t}\n\t\tlet names = this.namesByDoc.get(doc.docId);\n\t\tif (!names) {\n\t\t\tnames = buildNames(doc, style, this.opts.diagnostics ?? []);\n\t\t\tthis.namesByDoc.set(doc.docId, names);\n\t\t}\n\t\treturn names.get(exportName) ?? exportName;\n\t}\n\n\tpublic createPrintContext(logger: Logger, diagnostics: Diagnostic[]): PrintContext {\n\t\treturn {\n\t\t\tlogger,\n\t\t\toptions: this.opts.options,\n\t\t\tdiagnostics,\n\t\t\ttargetName: this.opts.targetName,\n\t\t\toutputPath: docId => this.outputPath(docId),\n\t\t\tresolveImport: (from, to) => this.resolveImport(from, to),\n\t\t\tresolveExportName: (doc, name) => this.resolveExportName(doc, name),\n\t\t};\n\t}\n\n\tprivate extension(): string {\n\t\tswitch (this.opts.options.importExtension) {\n\t\t\tcase \"js\":\n\t\t\t\treturn \".js\";\n\t\t\tcase \"ts\":\n\t\t\t\treturn \".ts\";\n\t\t\tcase \"none\":\n\t\t\t\treturn \"\";\n\t\t}\n\t}\n}\n\nexport interface PrintPlan {\n\treadonly targetName: string;\n\treadonly files: readonly PrintedFile[];\n}\n\n/**\n * Assign final export names for a document in the given style.\n * Deterministic: follows `exportOrder`; collisions get numeric suffixes + a warning.\n */\nfunction buildNames(\n\tdoc: SourceDoc,\n\tstyle: NamingStyle,\n\tdiagnostics: Diagnostic[],\n): ReadonlyMap<string, string> {\n\tconst names = new Map<string, string>();\n\tconst used = new Set<string>();\n\tfor (const name of doc.exportOrder) {\n\t\tlet candidate = styleName(name, style);\n\t\tif (used.has(candidate)) {\n\t\t\tconst base = candidate;\n\t\t\tlet i = 2;\n\t\t\twhile (used.has(candidate)) {\n\t\t\t\tcandidate = `${base}${i}`;\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tdiagnostics.push(\n\t\t\t\tcreateDiagnostic(\n\t\t\t\t\tDiagnosticCode.NameCollision,\n\t\t\t\t\t\"warning\",\n\t\t\t\t\t`Export \"${name}\" maps to an existing \"${base}\" name; using \"${candidate}\"`,\n\t\t\t\t\t{ docId: doc.docId },\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tnames.set(name, candidate);\n\t\tused.add(candidate);\n\t}\n\treturn names;\n}\n\n/** Split a name into words: by separators (non-alphanumerics) and camelCase boundaries. */\nfunction words(name: string): readonly string[] {\n\treturn name\n\t\t.split(/[^$A-Za-z0-9]+/)\n\t\t.filter(Boolean)\n\t\t.flatMap(w => w.split(/(?<=[a-z0-9])(?=[A-Z])/))\n\t\t.filter(Boolean);\n}\n\nexport function styleName(name: string, style: NamingStyle): string {\n\tconst ws = words(name);\n\tif (ws.length === 0) {\n\t\treturn name;\n\t}\n\tconst [first, ...rest] = ws;\n\tconst joined = [\n\t\tstyle === \"camel\" ? (first ?? \"\").toLowerCase() : first,\n\t\t...rest.map(w => w.charAt(0).toUpperCase() + w.slice(1)),\n\t].join(\"\");\n\tswitch (style) {\n\t\tcase \"camel\":\n\t\t\treturn `${joined.charAt(0).toLowerCase()}${joined.slice(1)}`;\n\t\tcase \"pascal\":\n\t\t\treturn `${joined.charAt(0).toUpperCase()}${joined.slice(1)}`;\n\t\tcase \"origin\":\n\t\t\treturn name;\n\t}\n}\n\nfunction stripExt(p: string): string {\n\tconst dot = p.lastIndexOf(\".\");\n\tconst slash = p.lastIndexOf(\"/\");\n\tif (dot > slash) {\n\t\treturn p.slice(0, dot);\n\t}\n\treturn p;\n}\n"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { Diagnostic } from "../../diagnostics/errors.mjs";
2
+ import { Logger } from "../../diagnostics/logger.mjs";
3
+ import { SourceDoc } from "../ir/types.mjs";
4
+ import { PrintedFile, ResolvedOptions } from "../types.mjs";
5
+ //#region src/core/printer/abstract.d.ts
6
+ export interface PrintContext {
7
+ readonly logger: Logger;
8
+ readonly options: ResolvedOptions;
9
+ /** Sink for structured diagnostics produced during printing. */
10
+ readonly diagnostics: Diagnostic[];
11
+ /** Name of the target being printed. */
12
+ readonly targetName: string;
13
+ /** Output path (relative to the output root, with extension) for a document. */
14
+ outputPath(docId: string): string;
15
+ /** Relative import specifier between two documents (with extension per settings). */
16
+ resolveImport(fromDocId: string, toDocId: string): string;
17
+ /** Final export name for a Ref, taking custom naming into account. */
18
+ resolveExportName(doc: SourceDoc, exportName: string): string;
19
+ }
20
+ /** Prints IR documents into target code. Receives ALL docs at once so a single-file output is just another printer. */
21
+ export interface AbstractPrinter {
22
+ readonly name: string;
23
+ print(docs: readonly SourceDoc[], ctx: PrintContext): Promise<PrintedFile[]>;
24
+ }
25
+ //#endregion
26
+ //# sourceMappingURL=abstract.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abstract.d.mts","names":[],"sources":["../../../src/core/printer/abstract.ts"],"mappings":";;;;;iBAKiB;WACP,QAAQ;WACR,SAAS;;WAET,aAAa;;WAEb;;EAET,WAAW;;EAEX,cAAc,mBAAmB;;EAEjC,kBAAkB,KAAK,WAAW;;;iBAIlB;WACP;EACT,MAAM,eAAe,aAAa,KAAK,eAAe,QAAQ"}
@@ -0,0 +1,74 @@
1
+ import { AbstractPrinter } from "./printer/abstract.mjs";
2
+ import { AbstractWalker } from "./walker/abstract.mjs";
3
+ //#region src/core/types.d.ts
4
+ /** A discovered input schema file. */
5
+ export interface SchemaFile {
6
+ /** Absolute path on disk. */
7
+ readonly path: string;
8
+ /** Normalized document id (posix, relative to the input root). */
9
+ readonly docId: string;
10
+ /** Raw JSON text. */
11
+ readonly content: string;
12
+ }
13
+ /** A file produced by a Printer. */
14
+ export interface PrintedFile {
15
+ /** Path relative to the target's output root. */
16
+ readonly path: string;
17
+ readonly content: string;
18
+ }
19
+ export type ImportExtension = "js" | "ts" | "none";
20
+ /** Import specifiers per validator target. */
21
+ export interface ImportPaths {
22
+ readonly zod?: string;
23
+ readonly valibot?: string;
24
+ }
25
+ /** Style applied to exported type/schema names. */
26
+ export type NamingStyle = "origin" | "camel" | "pascal";
27
+ /** User-tunable codegen options (with defaults). */
28
+ export interface GenerateOptions {
29
+ readonly importExtension: ImportExtension;
30
+ readonly readonlyArrays: boolean;
31
+ /** Import specifiers per validator target (default: the package name). */
32
+ readonly importPaths: ImportPaths;
33
+ /** Apply JSON Schema `default` in validator targets. */
34
+ readonly defaults: boolean;
35
+ /** Export name style (default: `pascal`). */
36
+ readonly naming: NamingStyle;
37
+ /** File header template; `false` disables it. */
38
+ readonly header: string | false;
39
+ }
40
+ export declare const DEFAULT_OPTIONS: GenerateOptions;
41
+ /** Resolved options (paths made absolute). Passed through contexts. */
42
+ export interface ResolvedOptions extends GenerateOptions {
43
+ /** Absolute output directory. */
44
+ readonly output: string;
45
+ /** Absolute common input root (used to compute doc ids). */
46
+ readonly rootDir: string;
47
+ }
48
+ export type TargetName = "typescript" | "zod" | "valibot";
49
+ /** Either a built-in preset name or a fully custom generator definition. */
50
+ export type TargetSpec = TargetName | GeneratorDefinition;
51
+ /**
52
+ * Defines a target generator. Built-in targets are preset instances of this;
53
+ * users supply their own to build custom generators.
54
+ */
55
+ export interface GeneratorDefinition {
56
+ readonly name: string;
57
+ readonly printer: AbstractPrinter;
58
+ /** Custom walker. Defaults to the built-in JsonSchemaWalker. */
59
+ readonly walker?: AbstractWalker;
60
+ /** Custom input doc-id → output subpath mapping. Default mirrors input structure. */
61
+ readonly mapOutput?: (docId: string) => string;
62
+ }
63
+ /** User-facing top-level config for the API and the config file. */
64
+ export interface GenerateConfig {
65
+ readonly input: string | string[];
66
+ readonly output: string;
67
+ readonly targets: TargetSpec[];
68
+ readonly options?: Partial<GenerateOptions>;
69
+ readonly clean?: boolean;
70
+ readonly dryRun?: boolean;
71
+ readonly verbose?: boolean;
72
+ }
73
+ //#endregion
74
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/core/types.ts"],"mappings":";;;;iBAIiB;;WAEP;;WAEA;;WAEA;;;iBAIO;;WAEP;WACA;;YAGE;;iBAGK;WACP;WACA;;;YAIE;;iBAYK;WACP,iBAAiB;WACjB;;WAEA,aAAa;;WAEb;;WAEA,QAAQ;;WAER;;qBAGG,iBAAiB;;iBAUb,wBAAwB;;WAE/B;;WAEA;;YAGE;;YAGA,aAAa,aAAa;;;;;iBAMrB;WACP;WACA,SAAS;;WAET,SAAS;;WAET,aAAa;;;iBAIN;WACP;WACA;WACA,SAAS;WACT,UAAU,QAAQ;WAClB;WACA;WACA"}
@@ -0,0 +1,27 @@
1
+ //#region src/core/types.ts
2
+ const NAMING_STYLES = [
3
+ "origin",
4
+ "camel",
5
+ "pascal"
6
+ ];
7
+ /**
8
+ * Template of the `@generated` header prepended to every written file.
9
+ * Supported placeholders: `{{version}}` (generator version), `{{date}}` (generation date, YYYY-MM-DD).
10
+ * `false` disables the header entirely.
11
+ */
12
+ const DEFAULT_HEADER_TEMPLATE = "// @generated by json2code v{{version}} — DO NOT EDIT";
13
+ const DEFAULT_OPTIONS = {
14
+ importExtension: "js",
15
+ readonlyArrays: false,
16
+ importPaths: {
17
+ zod: "zod",
18
+ valibot: "valibot"
19
+ },
20
+ defaults: true,
21
+ naming: "pascal",
22
+ header: DEFAULT_HEADER_TEMPLATE
23
+ };
24
+ //#endregion
25
+ export { DEFAULT_HEADER_TEMPLATE, DEFAULT_OPTIONS, NAMING_STYLES };
26
+
27
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../../src/core/types.ts"],"sourcesContent":["import type { AbstractPrinter } from \"./printer/abstract.js\";\nimport type { AbstractWalker } from \"./walker/abstract.js\";\n\n/** A discovered input schema file. */\nexport interface SchemaFile {\n\t/** Absolute path on disk. */\n\treadonly path: string;\n\t/** Normalized document id (posix, relative to the input root). */\n\treadonly docId: string;\n\t/** Raw JSON text. */\n\treadonly content: string;\n}\n\n/** A file produced by a Printer. */\nexport interface PrintedFile {\n\t/** Path relative to the target's output root. */\n\treadonly path: string;\n\treadonly content: string;\n}\n\nexport type ImportExtension = \"js\" | \"ts\" | \"none\";\n\n/** Import specifiers per validator target. */\nexport interface ImportPaths {\n\treadonly zod?: string;\n\treadonly valibot?: string;\n}\n\n/** Style applied to exported type/schema names. */\nexport type NamingStyle = \"origin\" | \"camel\" | \"pascal\";\n\nexport const NAMING_STYLES: readonly NamingStyle[] = [\"origin\", \"camel\", \"pascal\"];\n\n/**\n * Template of the `@generated` header prepended to every written file.\n * Supported placeholders: `{{version}}` (generator version), `{{date}}` (generation date, YYYY-MM-DD).\n * `false` disables the header entirely.\n */\nexport const DEFAULT_HEADER_TEMPLATE = \"// @generated by json2code v{{version}} — DO NOT EDIT\";\n\n/** User-tunable codegen options (with defaults). */\nexport interface GenerateOptions {\n\treadonly importExtension: ImportExtension;\n\treadonly readonlyArrays: boolean;\n\t/** Import specifiers per validator target (default: the package name). */\n\treadonly importPaths: ImportPaths;\n\t/** Apply JSON Schema `default` in validator targets. */\n\treadonly defaults: boolean;\n\t/** Export name style (default: `pascal`). */\n\treadonly naming: NamingStyle;\n\t/** File header template; `false` disables it. */\n\treadonly header: string | false;\n}\n\nexport const DEFAULT_OPTIONS: GenerateOptions = {\n\timportExtension: \"js\",\n\treadonlyArrays: false,\n\timportPaths: { zod: \"zod\", valibot: \"valibot\" },\n\tdefaults: true,\n\tnaming: \"pascal\",\n\theader: DEFAULT_HEADER_TEMPLATE,\n};\n\n/** Resolved options (paths made absolute). Passed through contexts. */\nexport interface ResolvedOptions extends GenerateOptions {\n\t/** Absolute output directory. */\n\treadonly output: string;\n\t/** Absolute common input root (used to compute doc ids). */\n\treadonly rootDir: string;\n}\n\nexport type TargetName = \"typescript\" | \"zod\" | \"valibot\";\n\n/** Either a built-in preset name or a fully custom generator definition. */\nexport type TargetSpec = TargetName | GeneratorDefinition;\n\n/**\n * Defines a target generator. Built-in targets are preset instances of this;\n * users supply their own to build custom generators.\n */\nexport interface GeneratorDefinition {\n\treadonly name: string;\n\treadonly printer: AbstractPrinter;\n\t/** Custom walker. Defaults to the built-in JsonSchemaWalker. */\n\treadonly walker?: AbstractWalker;\n\t/** Custom input doc-id → output subpath mapping. Default mirrors input structure. */\n\treadonly mapOutput?: (docId: string) => string;\n}\n\n/** User-facing top-level config for the API and the config file. */\nexport interface GenerateConfig {\n\treadonly input: string | string[];\n\treadonly output: string;\n\treadonly targets: TargetSpec[];\n\treadonly options?: Partial<GenerateOptions>;\n\treadonly clean?: boolean;\n\treadonly dryRun?: boolean;\n\treadonly verbose?: boolean;\n}\n"],"mappings":";AA+BA,MAAa,gBAAwC;CAAC;CAAU;CAAS;AAAQ;;;;;;AAOjF,MAAa,0BAA0B;AAgBvC,MAAa,kBAAmC;CAC/C,iBAAiB;CACjB,gBAAgB;CAChB,aAAa;EAAE,KAAK;EAAO,SAAS;CAAU;CAC9C,UAAU;CACV,QAAQ;CACR,QAAQ;AACT"}
@@ -0,0 +1,18 @@
1
+ import { Diagnostic } from "../../diagnostics/errors.mjs";
2
+ import { Logger } from "../../diagnostics/logger.mjs";
3
+ import { SourceDoc } from "../ir/types.mjs";
4
+ import { ResolvedOptions, SchemaFile } from "../types.mjs";
5
+ //#region src/core/walker/abstract.d.ts
6
+ export interface WalkerContext {
7
+ readonly logger: Logger;
8
+ readonly options: ResolvedOptions;
9
+ /** Sink for structured diagnostics produced during the walk. */
10
+ readonly diagnostics: Diagnostic[];
11
+ }
12
+ /** Traverses input schema files and produces normalized IR documents. */
13
+ export interface AbstractWalker {
14
+ readonly name: string;
15
+ walk(files: readonly SchemaFile[], ctx: WalkerContext): Promise<SourceDoc[]>;
16
+ }
17
+ //#endregion
18
+ //# sourceMappingURL=abstract.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abstract.d.mts","names":[],"sources":["../../../src/core/walker/abstract.ts"],"mappings":";;;;;iBAKiB;WACP,QAAQ;WACR,SAAS;;WAET,aAAa;;;iBAIN;WACP;EACT,KAAK,gBAAgB,cAAc,KAAK,gBAAgB,QAAQ"}
@@ -0,0 +1,10 @@
1
+ import { SourceDoc } from "../ir/types.mjs";
2
+ import { AbstractWalker, WalkerContext } from "./abstract.mjs";
3
+ import { SchemaFile } from "../types.mjs";
4
+ //#region src/core/walker/json-schema.d.ts
5
+ export declare class JsonSchemaWalker implements AbstractWalker {
6
+ readonly name = "json-schema";
7
+ walk(files: readonly SchemaFile[], ctx: WalkerContext): Promise<SourceDoc[]>;
8
+ }
9
+ //#endregion
10
+ //# sourceMappingURL=json-schema.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-schema.d.mts","names":[],"sources":["../../../src/core/walker/json-schema.ts"],"mappings":";;;;qBA6Ca,4BAA4B;WACxB;EAEhB,KAAkB,gBAAgB,cAAc,KAAK,gBAAgB,QAAQ"}