@typra/emitter 0.2.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/dist/src/cleanup/generated-file.d.ts +6 -0
  2. package/dist/src/cleanup/generated-file.js +61 -0
  3. package/dist/src/cli.d.ts +2 -0
  4. package/dist/src/cli.js +110 -0
  5. package/dist/src/decorators.d.ts +56 -0
  6. package/dist/src/decorators.js +177 -0
  7. package/dist/src/emitter.d.ts +13 -0
  8. package/dist/src/emitter.js +137 -0
  9. package/dist/src/generate.d.ts +86 -0
  10. package/dist/src/generate.js +104 -0
  11. package/dist/src/index.d.ts +4 -0
  12. package/dist/src/index.js +5 -0
  13. package/dist/src/ir/ast.d.ts +235 -0
  14. package/dist/src/ir/ast.js +589 -0
  15. package/dist/src/ir/declarations.d.ts +364 -0
  16. package/dist/src/ir/declarations.js +23 -0
  17. package/dist/src/ir/expansion.d.ts +140 -0
  18. package/dist/src/ir/expansion.js +407 -0
  19. package/dist/src/ir/lower.d.ts +53 -0
  20. package/dist/src/ir/lower.js +480 -0
  21. package/dist/src/ir/utilities.d.ts +12 -0
  22. package/dist/src/ir/utilities.js +39 -0
  23. package/dist/src/ir/visitor.d.ts +29 -0
  24. package/dist/src/ir/visitor.js +48 -0
  25. package/dist/src/languages/csharp/driver.d.ts +5 -0
  26. package/dist/src/languages/csharp/driver.js +315 -0
  27. package/dist/src/languages/csharp/emitter.d.ts +33 -0
  28. package/dist/src/languages/csharp/emitter.js +1140 -0
  29. package/dist/src/languages/csharp/scaffolding.d.ts +18 -0
  30. package/dist/src/languages/csharp/scaffolding.js +591 -0
  31. package/dist/src/languages/csharp/test-emitter.d.ts +43 -0
  32. package/dist/src/languages/csharp/test-emitter.js +274 -0
  33. package/dist/src/languages/csharp/visitor.d.ts +14 -0
  34. package/dist/src/languages/csharp/visitor.js +79 -0
  35. package/dist/src/languages/go/driver.d.ts +12 -0
  36. package/dist/src/languages/go/driver.js +128 -0
  37. package/dist/src/languages/go/emitter.d.ts +33 -0
  38. package/dist/src/languages/go/emitter.js +879 -0
  39. package/dist/src/languages/go/scaffolding.d.ts +18 -0
  40. package/dist/src/languages/go/scaffolding.js +53 -0
  41. package/dist/src/languages/go/test-emitter.d.ts +20 -0
  42. package/dist/src/languages/go/test-emitter.js +300 -0
  43. package/dist/src/languages/go/visitor.d.ts +14 -0
  44. package/dist/src/languages/go/visitor.js +78 -0
  45. package/dist/src/languages/markdown/driver.d.ts +19 -0
  46. package/dist/src/languages/markdown/driver.js +408 -0
  47. package/dist/src/languages/python/driver.d.ts +14 -0
  48. package/dist/src/languages/python/driver.js +372 -0
  49. package/dist/src/languages/python/emitter.d.ts +31 -0
  50. package/dist/src/languages/python/emitter.js +856 -0
  51. package/dist/src/languages/python/scaffolding.d.ts +33 -0
  52. package/dist/src/languages/python/scaffolding.js +279 -0
  53. package/dist/src/languages/python/test-emitter.d.ts +29 -0
  54. package/dist/src/languages/python/test-emitter.js +388 -0
  55. package/dist/src/languages/python/visitor.d.ts +14 -0
  56. package/dist/src/languages/python/visitor.js +65 -0
  57. package/dist/src/languages/rust/driver.d.ts +13 -0
  58. package/dist/src/languages/rust/driver.js +624 -0
  59. package/dist/src/languages/rust/emitter.d.ts +45 -0
  60. package/dist/src/languages/rust/emitter.js +1596 -0
  61. package/dist/src/languages/rust/visitor.d.ts +25 -0
  62. package/dist/src/languages/rust/visitor.js +153 -0
  63. package/dist/src/languages/typescript/driver.d.ts +8 -0
  64. package/dist/src/languages/typescript/driver.js +209 -0
  65. package/dist/src/languages/typescript/emitter.d.ts +42 -0
  66. package/dist/src/languages/typescript/emitter.js +904 -0
  67. package/dist/src/languages/typescript/scaffolding.d.ts +32 -0
  68. package/dist/src/languages/typescript/scaffolding.js +303 -0
  69. package/dist/src/languages/typescript/test-emitter.d.ts +23 -0
  70. package/dist/src/languages/typescript/test-emitter.js +204 -0
  71. package/dist/src/languages/typescript/visitor.d.ts +14 -0
  72. package/dist/src/languages/typescript/visitor.js +64 -0
  73. package/dist/src/lib.d.ts +33 -0
  74. package/dist/src/lib.js +101 -0
  75. package/dist/src/testing/index.d.ts +2 -0
  76. package/dist/src/testing/index.js +8 -0
  77. package/dist/src/testing/test-context.d.ts +63 -0
  78. package/dist/src/testing/test-context.js +355 -0
  79. package/fixtures/shapes/main.tsp +43 -0
  80. package/fixtures/tspconfig.yaml +13 -0
  81. package/package.json +76 -0
  82. package/src/lib/main.tsp +110 -0
@@ -0,0 +1,6 @@
1
+ import { EmitContext } from "@typespec/compiler";
2
+ import { TypraEmitterOptions } from "../lib.js";
3
+ export declare function emitGeneratedFile(context: EmitContext<TypraEmitterOptions>, filePath: string, content: string, options?: {
4
+ marker?: boolean;
5
+ }): Promise<void>;
6
+ export declare function emitGeneratedManifest(context: EmitContext<TypraEmitterOptions>): Promise<void>;
@@ -0,0 +1,61 @@
1
+ import { emitFile, resolvePath } from "@typespec/compiler";
2
+ import { dirname, relative, resolve } from "path";
3
+ const generatedFilesByProgram = new WeakMap();
4
+ export async function emitGeneratedFile(context, filePath, content, options = {}) {
5
+ const marker = options.marker ?? shouldMark(filePath);
6
+ const finalContent = marker ? addMarker(filePath, content) : content;
7
+ recordGeneratedFile(context.program, filePath, marker);
8
+ await emitFile(context.program, {
9
+ path: filePath,
10
+ content: finalContent,
11
+ });
12
+ }
13
+ export async function emitGeneratedManifest(context) {
14
+ const entries = [...(generatedFilesByProgram.get(context.program)?.values() ?? [])]
15
+ .sort((left, right) => left.path.localeCompare(right.path));
16
+ const manifest = {
17
+ emitter: "typra-emitter",
18
+ version: 1,
19
+ generatedAt: new Date().toISOString(),
20
+ files: entries,
21
+ };
22
+ await emitFile(context.program, {
23
+ path: resolvePath(context.emitterOutputDir, ".typra-generated", "manifest.json"),
24
+ content: JSON.stringify(manifest, null, 2),
25
+ });
26
+ }
27
+ function recordGeneratedFile(program, filePath, marker) {
28
+ let entries = generatedFilesByProgram.get(program);
29
+ if (!entries) {
30
+ entries = new Map();
31
+ generatedFilesByProgram.set(program, entries);
32
+ }
33
+ entries.set(normalizePath(filePath), {
34
+ path: normalizePath(filePath),
35
+ marker,
36
+ });
37
+ }
38
+ function shouldMark(filePath) {
39
+ return !filePath.endsWith(".json");
40
+ }
41
+ function addMarker(filePath, content) {
42
+ const marker = markerFor(filePath);
43
+ return content.startsWith(marker) ? content : `${marker}\n${content}`;
44
+ }
45
+ function markerFor(filePath) {
46
+ if (filePath.endsWith(".md")) {
47
+ return "<!-- <auto-generated by typra-emitter> -->";
48
+ }
49
+ if (filePath.endsWith(".py") || filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
50
+ return "# <auto-generated by typra-emitter>";
51
+ }
52
+ return "// <auto-generated by typra-emitter>";
53
+ }
54
+ function normalizePath(filePath) {
55
+ const absolute = resolve(filePath);
56
+ const base = dirname(absolute);
57
+ return relative(base, absolute).startsWith("..")
58
+ ? absolute.replace(/\\/g, "/")
59
+ : filePath.replace(/\\/g, "/");
60
+ }
61
+ //# sourceMappingURL=generated-file.js.map
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ import { generate } from "./generate.js";
3
+ import { parseArgs } from "util";
4
+ const HELP = `
5
+ typra-generate - Generate Typra runtime surfaces
6
+
7
+ Usage:
8
+ npx typra-generate [options]
9
+
10
+ Options:
11
+ -o, --output <dir> Output directory (required)
12
+ -t, --targets <list> Comma-separated list of targets (default: python,csharp,typescript,go)
13
+ -r, --root-object <name> Root object to generate from (default: Typra.FixtureRoot)
14
+ --omit <list> Comma-separated list of models to omit
15
+ -n, --namespace <name> Root namespace for generated code (default: Typra)
16
+ --no-tests Skip generating test files
17
+ --no-format Skip running formatters
18
+ -h, --help Show this help message
19
+
20
+ Examples:
21
+ # Generate all runtimes to ./generated
22
+ npx typra-generate -o ./generated
23
+
24
+ # Generate only Python and C#
25
+ npx typra-generate -o ./lib -t python,csharp
26
+
27
+ # Generate a different root object
28
+ npx typra-generate -o ./lib -r Typra.Widget
29
+
30
+ # Omit specific models
31
+ npx typra-generate -o ./lib --omit LegacyWidget
32
+
33
+ Targets:
34
+ python Python dataclasses with YAML/JSON serialization
35
+ csharp C# classes with System.Text.Json serialization
36
+ typescript TypeScript interfaces with js-yaml serialization
37
+ go Go structs with encoding/json and gopkg.in/yaml.v3
38
+ markdown Markdown documentation
39
+ `;
40
+ async function main() {
41
+ const { values, positionals } = parseArgs({
42
+ options: {
43
+ output: { type: "string", short: "o" },
44
+ targets: { type: "string", short: "t" },
45
+ "root-object": { type: "string", short: "r" },
46
+ omit: { type: "string" },
47
+ namespace: { type: "string", short: "n" },
48
+ "no-tests": { type: "boolean", default: false },
49
+ "no-format": { type: "boolean", default: false },
50
+ help: { type: "boolean", short: "h" },
51
+ },
52
+ allowPositionals: true,
53
+ });
54
+ if (values.help) {
55
+ console.log(HELP);
56
+ process.exit(0);
57
+ }
58
+ // Output is required
59
+ const output = values.output || positionals[0];
60
+ if (!output) {
61
+ console.error("Error: Output directory is required. Use -o <dir> or --output <dir>\n");
62
+ console.log(HELP);
63
+ process.exit(1);
64
+ }
65
+ // Parse targets
66
+ const targetsString = values.targets || "python,csharp,typescript,go";
67
+ const targets = targetsString.split(",").map(t => t.trim().toLowerCase());
68
+ // Validate targets
69
+ const validTargets = ["python", "csharp", "typescript", "go", "markdown"];
70
+ for (const target of targets) {
71
+ if (!validTargets.includes(target)) {
72
+ console.error(`Error: Invalid target "${target}". Valid targets: ${validTargets.join(", ")}`);
73
+ process.exit(1);
74
+ }
75
+ }
76
+ // Parse omit list
77
+ const omit = values.omit ? values.omit.split(",").map(m => m.trim()) : [];
78
+ console.log(`\n🚀 Typra Generator\n`);
79
+ console.log(` Output: ${output}`);
80
+ console.log(` Targets: ${targets.join(", ")}`);
81
+ console.log(` Root Object: ${values["root-object"] || "Typra.FixtureRoot"}`);
82
+ if (omit.length > 0) {
83
+ console.log(` Omitting: ${omit.join(", ")}`);
84
+ }
85
+ console.log();
86
+ const result = await generate({
87
+ output,
88
+ targets,
89
+ rootObject: values["root-object"] || "Typra.FixtureRoot",
90
+ omit,
91
+ namespace: values.namespace,
92
+ generateTests: !values["no-tests"],
93
+ format: !values["no-format"],
94
+ });
95
+ if (result.success) {
96
+ console.log(`✅ Successfully generated code for: ${result.targets.join(", ")}`);
97
+ console.log(` Output directory: ${result.outputDir}\n`);
98
+ process.exit(0);
99
+ }
100
+ else {
101
+ console.error(`❌ Generation failed:`);
102
+ result.errors?.forEach(e => console.error(` - ${e}`));
103
+ process.exit(1);
104
+ }
105
+ }
106
+ main().catch(error => {
107
+ console.error("Fatal error:", error);
108
+ process.exit(1);
109
+ });
110
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,56 @@
1
+ import { type DecoratorContext, type Model, Program, Type, ModelProperty, ObjectValue } from "@typespec/compiler";
2
+ export declare const appendStateValue: <T>(context: DecoratorContext, key: symbol, target: Type, value: T | T[]) => void;
3
+ export declare const getStateValue: <T>(program: Program, key: symbol, target: Type) => T[];
4
+ export declare const setStateScalar: <T>(context: DecoratorContext, key: symbol, target: Type, value: T) => void;
5
+ export declare const getStateScalar: <T>(program: Program, key: symbol, target: Type) => T | undefined;
6
+ export interface SampleOptions {
7
+ title?: string;
8
+ description?: string;
9
+ }
10
+ export interface SampleEntry {
11
+ sample: object;
12
+ title?: string;
13
+ description?: string;
14
+ }
15
+ export declare function $sample(context: DecoratorContext, target: ModelProperty, sample: ObjectValue | object, options?: SampleOptions): void;
16
+ export declare function $abstract(context: DecoratorContext, target: Model): void;
17
+ export declare function $coerce(context: DecoratorContext, target: Model, scalar: Type, expansion: ObjectValue | object, title?: string, description?: string, example?: string): void;
18
+ export interface FactoryEntry {
19
+ /** Factory method name (e.g., "allow", "deny") */
20
+ name: string;
21
+ /** Field assignments — { fieldName: value } */
22
+ sets: Record<string, any>;
23
+ /** Optional parameters — { paramName: typeString } */
24
+ params: Record<string, string>;
25
+ }
26
+ export interface MethodEntry {
27
+ /** Method name (e.g., "text") */
28
+ name: string;
29
+ /** Return type as a string (e.g., "string") */
30
+ returns: string;
31
+ /** Human-readable description of what the method does */
32
+ description: string;
33
+ /** Method parameters as an ordered map of name → type string */
34
+ params: Record<string, string>;
35
+ /** Whether this method is optional (has a default implementation) */
36
+ optional: boolean;
37
+ /** Whether this method is synchronous (not wrapped in async/Promise/Task) */
38
+ sync: boolean;
39
+ }
40
+ export declare function $factory(context: DecoratorContext, target: Model, name: string, sets: object, params?: object): void;
41
+ export declare function $method(context: DecoratorContext, target: Model, name: string, returns: string, description?: string, params?: object, optional?: boolean, sync?: boolean): void;
42
+ export interface KnownAsEntry {
43
+ /** Provider identifier (e.g., "openai", "anthropic") */
44
+ provider: string;
45
+ /** Wire field name for that provider */
46
+ name: string;
47
+ }
48
+ export declare function $knownAs(context: DecoratorContext, target: ModelProperty, provider: string, name: string): void;
49
+ export interface DefaultForEntry {
50
+ /** Provider identifier (e.g., "openai", "anthropic") */
51
+ provider: string;
52
+ /** Default value for that provider */
53
+ defaultValue: any;
54
+ }
55
+ export declare function $defaultFor(context: DecoratorContext, target: ModelProperty, provider: string, defaultValue: ObjectValue | object | string | number | boolean): void;
56
+ export declare function $protocol(context: DecoratorContext, target: Model): void;
@@ -0,0 +1,177 @@
1
+ import { serializeValueAsJson } from "@typespec/compiler";
2
+ import { StateKeys } from "./lib.js";
3
+ export const appendStateValue = (context, key, target, value) => {
4
+ const state = context.program.stateMap(key).get(target) || [];
5
+ if (Array.isArray(value)) {
6
+ const newState = [...state, ...value];
7
+ context.program.stateMap(key).set(target, newState);
8
+ }
9
+ else {
10
+ const newState = [...state, value];
11
+ context.program.stateMap(key).set(target, newState);
12
+ }
13
+ };
14
+ export const getStateValue = (program, key, target) => {
15
+ return program.stateMap(key).get(target) || [];
16
+ };
17
+ export const setStateScalar = (context, key, target, value) => {
18
+ context.program.stateMap(key).set(target, value);
19
+ };
20
+ export const getStateScalar = (program, key, target) => {
21
+ const value = program.stateMap(key).get(target);
22
+ return value ? value : undefined;
23
+ };
24
+ export function $sample(context, target, sample, options) {
25
+ // With valueof unknown, TypeSpec passes a plain JavaScript object
26
+ // With unknown (no valueof), TypeSpec passes an ObjectValue with a type property
27
+ let s;
28
+ if (sample && typeof sample === 'object' && 'type' in sample && sample.type) {
29
+ // Old-style ObjectValue with type property
30
+ const sampleValue = sample;
31
+ const serialized = serializeValueAsJson(context.program, sampleValue, sampleValue.type);
32
+ if (!serialized) {
33
+ context.program.reportDiagnostic({
34
+ code: "typra-emitter-sample-serialization",
35
+ message: `Failed to serialize sample value.`,
36
+ severity: "error",
37
+ target: sampleValue,
38
+ });
39
+ return;
40
+ }
41
+ s = serialized;
42
+ }
43
+ else {
44
+ // New-style: plain JavaScript object from valueof unknown
45
+ s = sample;
46
+ }
47
+ if (!s.hasOwnProperty(target.name)) {
48
+ context.program.reportDiagnostic({
49
+ code: "typra-emitter-sample-name-mismatch",
50
+ message: `Sample object must have a property named '${target.name}' to match the target property.`,
51
+ severity: "error",
52
+ target: target,
53
+ });
54
+ return;
55
+ }
56
+ const entry = {
57
+ sample: s,
58
+ title: options?.title ?? "",
59
+ description: options?.description ?? "",
60
+ };
61
+ appendStateValue(context, StateKeys.samples, target, entry);
62
+ }
63
+ export function $abstract(context, target) {
64
+ setStateScalar(context, StateKeys.abstracts, target, true);
65
+ }
66
+ export function $coerce(context, target, scalar, expansion, title, description, example) {
67
+ if (scalar.kind !== "Scalar") {
68
+ context.program.reportDiagnostic({
69
+ code: "typra-emitter-coerce-scalar-type",
70
+ message: `Coerce decorator requires a scalar type for the scalar representation.`,
71
+ severity: "error",
72
+ target: scalar,
73
+ });
74
+ return;
75
+ }
76
+ // Handle both ObjectValue (old style) and plain object (valueof unknown)
77
+ let exp;
78
+ if (expansion && typeof expansion === 'object' && 'type' in expansion && expansion.type) {
79
+ const serialized = serializeValueAsJson(context.program, expansion, expansion.type);
80
+ if (!serialized) {
81
+ context.program.reportDiagnostic({
82
+ code: "typra-emitter-coerce-serialization",
83
+ message: `Failed to serialize expansion value.`,
84
+ severity: "error",
85
+ target: target,
86
+ });
87
+ return;
88
+ }
89
+ exp = serialized;
90
+ }
91
+ else {
92
+ exp = expansion;
93
+ }
94
+ // Handle string parameters that come as plain strings from valueof
95
+ const titleValue = typeof title === 'object' && title !== null && 'value' in title ? title.value : title;
96
+ const descValue = typeof description === 'object' && description !== null && 'value' in description ? description.value : description;
97
+ const exampleValue = typeof example === 'object' && example !== null && 'value' in example ? example.value : example;
98
+ const entry = {
99
+ scalar: scalar.name,
100
+ expansion: exp,
101
+ example: exampleValue,
102
+ title: titleValue ?? "",
103
+ description: descValue ?? "",
104
+ };
105
+ appendStateValue(context, StateKeys.coercions, target, entry);
106
+ }
107
+ function deserializeValue(value) {
108
+ if (value && typeof value === 'object' && 'type' in value && value.type) {
109
+ // ObjectValue from TypeSpec — shouldn't happen with valueof but handle defensively
110
+ return value;
111
+ }
112
+ return value;
113
+ }
114
+ export function $factory(context, target, name, sets, params) {
115
+ // Handle string values from valueof
116
+ const nameValue = typeof name === 'object' && name !== null && 'value' in name ? name.value : name;
117
+ const setsValue = deserializeValue(sets);
118
+ const paramsValue = params ? deserializeValue(params) : {};
119
+ const entry = {
120
+ name: nameValue,
121
+ sets: setsValue,
122
+ params: paramsValue,
123
+ };
124
+ appendStateValue(context, StateKeys.factories, target, entry);
125
+ }
126
+ export function $method(context, target, name, returns, description, params, optional, sync) {
127
+ const nameValue = typeof name === 'object' && name !== null && 'value' in name ? name.value : name;
128
+ const returnsValue = typeof returns === 'object' && returns !== null && 'value' in returns ? returns.value : returns;
129
+ const descValue = typeof description === 'object' && description !== null && 'value' in description ? description.value : description;
130
+ const paramsValue = params ? deserializeValue(params) : {};
131
+ const optionalValue = typeof optional === 'object' && optional !== null && 'value' in optional ? optional.value : optional ?? false;
132
+ const syncValue = typeof sync === 'object' && sync !== null && 'value' in sync ? sync.value : sync ?? false;
133
+ const entry = {
134
+ name: nameValue,
135
+ returns: returnsValue,
136
+ description: descValue ?? "",
137
+ params: paramsValue,
138
+ optional: optionalValue,
139
+ sync: syncValue,
140
+ };
141
+ appendStateValue(context, StateKeys.methods, target, entry);
142
+ }
143
+ export function $knownAs(context, target, provider, name) {
144
+ const providerValue = typeof provider === 'object' && provider !== null && 'value' in provider ? provider.value : provider;
145
+ const nameValue = typeof name === 'object' && name !== null && 'value' in name ? name.value : name;
146
+ const entry = { provider: providerValue, name: nameValue };
147
+ appendStateValue(context, StateKeys.knownAs, target, entry);
148
+ }
149
+ export function $defaultFor(context, target, provider, defaultValue) {
150
+ const providerValue = typeof provider === 'object' && provider !== null && 'value' in provider ? provider.value : provider;
151
+ let val;
152
+ if (defaultValue && typeof defaultValue === 'object' && 'type' in defaultValue && defaultValue.type) {
153
+ const serialized = serializeValueAsJson(context.program, defaultValue, defaultValue.type);
154
+ if (!serialized) {
155
+ context.program.reportDiagnostic({
156
+ code: "typra-emitter-defaultfor-serialization",
157
+ message: `Failed to serialize default value.`,
158
+ severity: "error",
159
+ target: target,
160
+ });
161
+ return;
162
+ }
163
+ val = serialized;
164
+ }
165
+ else {
166
+ val = defaultValue;
167
+ }
168
+ const entry = { provider: providerValue, defaultValue: val };
169
+ appendStateValue(context, StateKeys.defaultFor, target, entry);
170
+ }
171
+ // ============================================================================
172
+ // Protocol decorator
173
+ // ============================================================================
174
+ export function $protocol(context, target) {
175
+ setStateScalar(context, StateKeys.protocols, target, true);
176
+ }
177
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1,13 @@
1
+ import { EmitContext } from "@typespec/compiler";
2
+ import { TypeNode } from "./ir/ast.js";
3
+ import { TypraEmitterOptions } from "./lib.js";
4
+ export interface GeneratorOptions {
5
+ omitModels?: string[];
6
+ additionalModels?: TypeNode[];
7
+ }
8
+ /**
9
+ * Filter nodes based on omit-models option.
10
+ * Matches against model name (e.g., "AgentManifest") or fully qualified name (e.g., "Prompty.AgentManifest")
11
+ */
12
+ export declare function filterNodes(nodes: TypeNode[], options?: GeneratorOptions): TypeNode[];
13
+ export declare function $onEmit(context: EmitContext<TypraEmitterOptions>): Promise<void>;
@@ -0,0 +1,137 @@
1
+ import { resolvePath } from "@typespec/compiler";
2
+ import { resolveModel, enumerateTypes } from "./ir/ast.js";
3
+ import { generateMarkdown } from "./languages/markdown/driver.js";
4
+ import { generatePython } from "./languages/python/driver.js";
5
+ import { generateCsharp } from "./languages/csharp/driver.js";
6
+ import { generateTypeScript } from "./languages/typescript/driver.js";
7
+ import { generateGo } from "./languages/go/driver.js";
8
+ import { generateRust } from "./languages/rust/driver.js";
9
+ import { emitGeneratedFile, emitGeneratedManifest } from "./cleanup/generated-file.js";
10
+ /**
11
+ * Filter nodes based on omit-models option.
12
+ * Matches against model name (e.g., "AgentManifest") or fully qualified name (e.g., "Prompty.AgentManifest")
13
+ */
14
+ export function filterNodes(nodes, options) {
15
+ const omitModels = options?.omitModels || [];
16
+ // Include additional root models and their type trees
17
+ const additionalModels = options?.additionalModels || [];
18
+ if (additionalModels.length > 0) {
19
+ const existingNames = new Set(nodes.map(n => `${n.typeName.namespace}.${n.typeName.name}`));
20
+ const visited = new Set(existingNames);
21
+ for (const additionalModel of additionalModels) {
22
+ for (const subNode of enumerateTypes(additionalModel, new Set())) {
23
+ const fullName = `${subNode.typeName.namespace}.${subNode.typeName.name}`;
24
+ if (!visited.has(fullName)) {
25
+ nodes.push(subNode);
26
+ visited.add(fullName);
27
+ }
28
+ }
29
+ }
30
+ }
31
+ if (omitModels.length === 0)
32
+ return nodes;
33
+ return nodes.filter(node => {
34
+ const name = node.typeName.name;
35
+ const fullName = `${node.typeName.namespace}.${name}`;
36
+ return !omitModels.includes(name) && !omitModels.includes(fullName);
37
+ });
38
+ }
39
+ // Registry of available code generators
40
+ const generators = {
41
+ markdown: generateMarkdown,
42
+ python: generatePython,
43
+ csharp: generateCsharp,
44
+ typescript: generateTypeScript,
45
+ go: generateGo,
46
+ rust: generateRust,
47
+ };
48
+ export async function $onEmit(context) {
49
+ const options = {
50
+ emitterOutputDir: context.emitterOutputDir,
51
+ ...context.options,
52
+ };
53
+ // resolving top level model
54
+ // this is the "Model" entry point for the emitter
55
+ const rootObject = options["root-object"];
56
+ const m = context.program.resolveTypeReference(rootObject);
57
+ if (!m[0] || m[0].kind !== "Model") {
58
+ throw new Error(`${rootObject} model not found or is not a model type.`);
59
+ }
60
+ const model = resolveModel(context.program, m[0], new Set(), options["root-namespace"] || "Typra", options["root-alias"] || "Typra");
61
+ if (options["root-alias"]) {
62
+ model.typeName = {
63
+ namespace: model.typeName.namespace,
64
+ name: options["root-alias"]
65
+ };
66
+ }
67
+ // Discover additional models not reachable from the root.
68
+ // If root-namespace is specified, resolve all models in that namespace
69
+ // so new types are automatically emitted without manual additional-roots.
70
+ const rootNamespace = options["root-namespace"] || "Typra";
71
+ const additionalModels = [];
72
+ const visited = new Set();
73
+ // Collect names already in the main model tree to avoid duplicates
74
+ const collectNames = (node) => {
75
+ visited.add(`${node.typeName.namespace}.${node.typeName.name}`);
76
+ for (const child of node.childTypes) {
77
+ collectNames(child);
78
+ }
79
+ for (const prop of node.properties) {
80
+ if (prop.type) {
81
+ collectNames(prop.type);
82
+ for (const child of prop.type.childTypes) {
83
+ collectNames(child);
84
+ }
85
+ }
86
+ }
87
+ };
88
+ collectNames(model);
89
+ // Resolve the namespace and iterate all models
90
+ const nsRef = context.program.resolveTypeReference(rootNamespace);
91
+ if (nsRef[0] && nsRef[0].kind === "Namespace") {
92
+ const ns = nsRef[0];
93
+ for (const [, nsModel] of ns.models) {
94
+ const fullName = `${rootNamespace}.${nsModel.name}`;
95
+ if (visited.has(fullName))
96
+ continue;
97
+ // Skip uninstantiated template declarations (e.g., Named<T>, Id<T>)
98
+ if (nsModel.node && 'templateParameters' in nsModel.node &&
99
+ nsModel.node.templateParameters.length > 0 && !nsModel.templateMapper) {
100
+ continue;
101
+ }
102
+ const additionalNode = resolveModel(context.program, nsModel, new Set(), rootNamespace, options["root-alias"] || "Typra");
103
+ additionalModels.push(additionalNode);
104
+ visited.add(fullName);
105
+ }
106
+ }
107
+ // Also process any explicit additional-roots (for types outside the namespace)
108
+ const additionalRoots = options["additional-roots"] || [];
109
+ for (const rootName of additionalRoots) {
110
+ if (visited.has(rootName))
111
+ continue;
112
+ const ref = context.program.resolveTypeReference(rootName);
113
+ if (!ref[0] || ref[0].kind !== "Model") {
114
+ console.warn(`Warning: additional-root '${rootName}' not found or is not a model type. Skipping.`);
115
+ continue;
116
+ }
117
+ const additionalNode = resolveModel(context.program, ref[0], new Set(), rootNamespace, options["root-alias"] || "Typra");
118
+ additionalModels.push(additionalNode);
119
+ visited.add(rootName);
120
+ }
121
+ const targets = options["emit-targets"] || [];
122
+ const generatorOptions = {
123
+ omitModels: options["omit-models"] || [],
124
+ additionalModels: additionalModels,
125
+ };
126
+ // Dispatch to registered generators
127
+ for (const target of targets) {
128
+ const generatorName = target.type.toLowerCase().trim();
129
+ const generator = generators[generatorName];
130
+ if (generator) {
131
+ await generator(context, model, target, generatorOptions);
132
+ }
133
+ }
134
+ await emitGeneratedFile(context, resolvePath(context.emitterOutputDir, "json-ast", "model.json"), JSON.stringify(model.getSanitizedObject(), null, 2), { marker: false });
135
+ await emitGeneratedManifest(context);
136
+ }
137
+ //# sourceMappingURL=emitter.js.map
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Target language for code generation.
3
+ */
4
+ export type TargetLanguage = "python" | "csharp" | "typescript" | "go" | "rust" | "markdown";
5
+ /**
6
+ * Options for a specific target language.
7
+ */
8
+ export interface TargetOptions {
9
+ /** Output directory for generated code */
10
+ outputDir: string;
11
+ /** Output directory for generated tests (optional) */
12
+ testDir?: string;
13
+ /** Override the namespace for generated code */
14
+ namespace?: string;
15
+ /** Run formatters on emitted files (default: true) */
16
+ format?: boolean;
17
+ }
18
+ /**
19
+ * Options for the generate function.
20
+ */
21
+ export interface GenerateOptions {
22
+ /**
23
+ * Output directory for generated code.
24
+ * Each target will create a subdirectory (e.g., output/python, output/csharp)
25
+ */
26
+ output: string;
27
+ /**
28
+ * Target languages to generate code for.
29
+ * @default ["python", "csharp", "typescript", "go"]
30
+ */
31
+ targets?: TargetLanguage[] | Record<TargetLanguage, TargetOptions>;
32
+ /**
33
+ * Root object to start generation from.
34
+ * @default "Typra.FixtureRoot"
35
+ */
36
+ rootObject?: string;
37
+ /**
38
+ * List of model names to omit from generation.
39
+ * Can be simple names (e.g., "Widget") or fully qualified (e.g., "Typra.Widget")
40
+ */
41
+ omit?: string[];
42
+ /**
43
+ * Root namespace for the generated code.
44
+ * @default "Typra"
45
+ */
46
+ namespace?: string;
47
+ /**
48
+ * Alias for the root object in generated code.
49
+ */
50
+ rootAlias?: string;
51
+ /**
52
+ * Generate test files.
53
+ * @default true
54
+ */
55
+ generateTests?: boolean;
56
+ /**
57
+ * Run formatters on emitted files.
58
+ * @default true
59
+ */
60
+ format?: boolean;
61
+ }
62
+ /**
63
+ * Result of the generate function.
64
+ */
65
+ export interface GenerateResult {
66
+ success: boolean;
67
+ outputDir: string;
68
+ targets: string[];
69
+ errors?: string[];
70
+ }
71
+ /**
72
+ * Generate Typra runtime surfaces.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * import { generate } from '@typra/emitter/generate';
77
+ *
78
+ * await generate({
79
+ * output: './generated',
80
+ * targets: ['python', 'csharp'],
81
+ * rootObject: 'Typra.Widget',
82
+ * omit: ['LegacyWidget']
83
+ * });
84
+ * ```
85
+ */
86
+ export declare function generate(options: GenerateOptions): Promise<GenerateResult>;