@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,408 @@
1
+ import { resolvePath } from "@typespec/compiler";
2
+ import { enumerateTypes } from "../../ir/ast.js";
3
+ import { filterNodes } from "../../emitter.js";
4
+ import * as YAML from 'yaml';
5
+ import { emitGeneratedFile } from "../../cleanup/generated-file.js";
6
+ function deepMerge(...objects) {
7
+ return objects.reduce((acc, obj) => {
8
+ Object.keys(obj).forEach((key) => {
9
+ const accValue = acc[key];
10
+ const objValue = obj[key];
11
+ if (typeof accValue === "object" && typeof objValue === "object") {
12
+ acc[key] = deepMerge(accValue, objValue);
13
+ }
14
+ else {
15
+ acc[key] = objValue;
16
+ }
17
+ });
18
+ return acc;
19
+ }, {});
20
+ }
21
+ function emitIndexMarkdown(types, rootObject, childTypes, compositionTypes) {
22
+ const typeMap = new Map(types.map((type) => [type.typeName.name, type]));
23
+ const renderMethodSignature = (method) => {
24
+ const params = Object.entries(method.params)
25
+ .map(([name, type]) => `${name}: ${type}`)
26
+ .join(", ");
27
+ const mode = method.sync ? "sync" : "async-capable";
28
+ return `+${method.name}(${params}) ${method.returns} [${mode}]`;
29
+ };
30
+ const renderClass = (typeName) => {
31
+ const type = typeMap.get(typeName);
32
+ if (!type) {
33
+ return `\n class ${typeName}`;
34
+ }
35
+ let result = `\n class ${type.typeName.name} {`;
36
+ if (type.isAbstract) {
37
+ result += `\n <<abstract>>`;
38
+ }
39
+ if (type.isProtocol) {
40
+ result += `\n <<protocol>>`;
41
+ }
42
+ for (const prop of type.properties) {
43
+ result += `\n +${prop.typeName.name}${prop.isCollection ? "[]" : ""} ${prop.name}`;
44
+ }
45
+ for (const method of type.methods) {
46
+ result += `\n ${renderMethodSignature(method)}`;
47
+ }
48
+ result += `\n }`;
49
+ return result;
50
+ };
51
+ const renderDiagram = (title, typeNames) => {
52
+ const included = new Set(typeNames);
53
+ let diagram = `\n## ${title}\n\n\`\`\`mermaid\nclassDiagram`;
54
+ for (const typeName of typeNames) {
55
+ diagram += renderClass(typeName);
56
+ }
57
+ for (const child of childTypes) {
58
+ if (included.has(child.source) && included.has(child.target)) {
59
+ diagram += `\n ${child.source} <|-- ${child.target}`;
60
+ }
61
+ }
62
+ for (const comp of compositionTypes) {
63
+ if (included.has(comp.source) && included.has(comp.target)) {
64
+ diagram += `\n ${comp.source} *-- ${comp.target}`;
65
+ }
66
+ }
67
+ diagram += `\n\`\`\`\n`;
68
+ return diagram;
69
+ };
70
+ const sections = [
71
+ [
72
+ "Prompt File Core",
73
+ [rootObject, "Model", "Template", "FormatConfig", "ParserConfig", "Property", "Tool"],
74
+ ],
75
+ [
76
+ "Properties and Schemas",
77
+ ["Property", "ObjectProperty", "ArrayProperty"],
78
+ ],
79
+ [
80
+ "Models and Connections",
81
+ [
82
+ "Model",
83
+ "ModelOptions",
84
+ "Connection",
85
+ "ApiKeyConnection",
86
+ "ReferenceConnection",
87
+ "RemoteConnection",
88
+ "AnonymousConnection",
89
+ "OAuthConnection",
90
+ "FoundryConnection",
91
+ ],
92
+ ],
93
+ [
94
+ "Tools",
95
+ [
96
+ "Tool",
97
+ "Binding",
98
+ "FunctionTool",
99
+ "PromptyTool",
100
+ "McpTool",
101
+ "McpApprovalMode",
102
+ "OpenApiTool",
103
+ "CustomTool",
104
+ "Connection",
105
+ "Property",
106
+ ],
107
+ ],
108
+ [
109
+ "Messages, Tool Calls, and Streaming",
110
+ [
111
+ "Message",
112
+ "ContentPart",
113
+ "TextPart",
114
+ "ImagePart",
115
+ "FilePart",
116
+ "AudioPart",
117
+ "ToolCall",
118
+ "ToolResult",
119
+ "ToolDispatchResult",
120
+ "StreamChunk",
121
+ "TextChunk",
122
+ "ThinkingChunk",
123
+ "ToolChunk",
124
+ "ErrorChunk",
125
+ ],
126
+ ],
127
+ [
128
+ "Agentic Runtime Controls",
129
+ ["TurnOptions", "CompactionConfig", "GuardrailResult"],
130
+ ],
131
+ [
132
+ "Token and Status Events",
133
+ ["TokenEventPayload", "ThinkingEventPayload", "StatusEventPayload", "ErrorEventPayload"],
134
+ ],
135
+ [
136
+ "Tool and Message Events",
137
+ ["ToolCallStartPayload", "ToolResultPayload", "MessagesUpdatedPayload", "ToolResult", "Message"],
138
+ ],
139
+ [
140
+ "Turn Completion and Compaction Events",
141
+ ["DoneEventPayload", "CompactionCompletePayload", "CompactionFailedPayload", "Message"],
142
+ ],
143
+ ];
144
+ let out = `---
145
+ title: "Prompty Schema"
146
+ description: "Overview of generated Prompty schema types."
147
+ slug: "reference"
148
+ sidebar:
149
+ order: 1
150
+ ---
151
+
152
+ This reference is generated from the in-repository TypeSpec model under
153
+ \`schema/model/\`. It documents the Prompty data model: the fields accepted in
154
+ \`.prompty\` frontmatter, runtime configuration objects, tool definitions,
155
+ message shapes, protocol contracts, events, and provider wire helper types.
156
+
157
+ Use this page for a map of the schema. Use each type page for field details,
158
+ examples, child types, helper methods, and alternate constructions. For public
159
+ functions, see the [API Reference](/api-reference/). Runtime behavior for these
160
+ types is specified in the [Prompty Specification](/specification/).
161
+
162
+ ## Source of Truth
163
+
164
+ - Type shapes are defined in \`schema/model/**/*.tsp\`.
165
+ - Generated runtime models are checked in under each runtime's \`model\`
166
+ directory.
167
+ - Generated Markdown reference pages are checked in here under
168
+ \`web/src/content/docs/reference/\`.
169
+ - If a generated page looks stale, update the TypeSpec or emitter and run
170
+ \`cd schema && npm run build\` rather than editing generated reference pages
171
+ by hand.
172
+ `;
173
+ for (const [title, typeNames] of sections) {
174
+ out += renderDiagram(title, typeNames.filter((typeName) => typeMap.has(typeName)));
175
+ }
176
+ return out;
177
+ }
178
+ function emitFileMarkdown(node, yml, md, compositionTypes, alternateCtors, parent) {
179
+ const renderMethodSignature = (method) => {
180
+ const params = Object.entries(method.params)
181
+ .map(([name, type]) => `${name}: ${type}`)
182
+ .join(", ");
183
+ const mode = method.sync ? "sync" : "async-capable";
184
+ return `+${method.name}(${params}) ${method.returns} [${mode}]`;
185
+ };
186
+ const renderDiagramClass = (type) => {
187
+ let result = `\n class ${type.typeName.name} {`;
188
+ if (type.isAbstract) {
189
+ result += `\n <<abstract>>`;
190
+ }
191
+ if (type.isProtocol) {
192
+ result += `\n <<protocol>>`;
193
+ }
194
+ for (const prop of type.properties) {
195
+ result += `\n +${prop.typeName.name}${prop.isCollection ? "[]" : ""} ${prop.name}`;
196
+ }
197
+ for (const method of type.methods) {
198
+ result += `\n ${renderMethodSignature(method)}`;
199
+ }
200
+ result += `\n }`;
201
+ return result;
202
+ };
203
+ let out = `---
204
+ title: "${node.typeName.name}"
205
+ description: "Documentation for the ${node.typeName.name} type."
206
+ slug: "reference/${node.typeName.name.toLowerCase()}"
207
+ ---
208
+
209
+ ${node.description}
210
+
211
+ ## Class Diagram
212
+
213
+ \`\`\`mermaid
214
+ ---
215
+ title: ${node.typeName.name}
216
+ config:
217
+ look: handDrawn
218
+ theme: colorful
219
+ class:
220
+ hideEmptyMembersBox: true
221
+ ---
222
+ classDiagram`;
223
+ if (parent) {
224
+ out += renderDiagramClass(parent);
225
+ out += `\n ${parent.typeName.name} <|-- ${node.typeName.name}`;
226
+ }
227
+ out += renderDiagramClass(node);
228
+ for (const type of node.childTypes) {
229
+ out += renderDiagramClass(type);
230
+ out += `\n ${node.typeName.name} <|-- ${type.typeName.name}`;
231
+ }
232
+ for (const type of compositionTypes) {
233
+ out += renderDiagramClass(type);
234
+ out += `\n ${node.typeName.name} *-- ${type.typeName.name}`;
235
+ }
236
+ out += `\n\`\`\``;
237
+ if (md) {
238
+ out += `\n\n## Markdown Example\n\n\`\`\`markdown\n${md.trim()}\n\`\`\``;
239
+ }
240
+ if (yml) {
241
+ out += `\n\n## Yaml Example\n\n\`\`\`yaml\n${yml.trim()}\n\`\`\``;
242
+ }
243
+ if (node.properties.length > 0) {
244
+ out += `\n\n## Properties\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |`;
245
+ for (const prop of node.properties) {
246
+ out += `\n| ${prop.name} | ${renderType(prop)} | ${prop.description.trim()}${renderChildTypes(prop)} |`;
247
+ }
248
+ }
249
+ if (node.methods.length > 0) {
250
+ out += `\n\n## Helper Methods\n\nThe following helper methods are declared via \`@method\` and must be implemented by every runtime. The schema declares the logical protocol contract; each runtime maps async-capable methods to idiomatic sync/async shapes for that language.\n\n| Name | Signature | Runtime shape | Description |\n| ---- | --------- | ------------- | ----------- |`;
251
+ for (const method of node.methods) {
252
+ const paramList = Object.entries(method.params)
253
+ .map(([n, t]) => `${n}: ${t}`)
254
+ .join(", ");
255
+ const sig = `${method.name}(${paramList}) -> ${method.returns}`;
256
+ const shape = method.sync ? "sync" : "async-capable";
257
+ const optional = method.optional ? " _(optional default)_" : "";
258
+ out += `\n| \`${method.name}\` | \`${sig}\` | ${shape}${optional} | ${method.description.trim()} |`;
259
+ }
260
+ }
261
+ if (node.factories.length > 0) {
262
+ out += `\n\n## Factory Methods\n\nThe following factory methods are declared via \`@factory\` and are generated automatically by the emitter in every language.\n`;
263
+ for (const factory of node.factories) {
264
+ const paramList = Object.entries(factory.params)
265
+ .map(([n, t]) => `${n}: ${t}`)
266
+ .join(", ");
267
+ out += `\n- \`${factory.name}(${paramList})\``;
268
+ }
269
+ }
270
+ if (node.childTypes.length > 0) {
271
+ out += `\n\n## Child Types\n\nThe following types extend \`${node.typeName.name}\`:\n`;
272
+ for (const type of node.childTypes) {
273
+ out += `\n- [${type.typeName.name}](../${type.typeName.name.toLowerCase()}/)`;
274
+ }
275
+ }
276
+ if (compositionTypes.length > 0) {
277
+ out += `\n\n## Composed Types\n\nThe following types are composed within \`${node.typeName.name}\`:\n`;
278
+ for (const type of compositionTypes) {
279
+ out += `\n- [${type.typeName.name}](../${type.typeName.name.toLowerCase()}/)`;
280
+ }
281
+ }
282
+ if (alternateCtors.length > 0) {
283
+ out += `\n\n## Alternate Constructions\n\nThe following alternate constructions are available for \`${node.typeName.name}\`.\nThese allow for simplified creation of instances using a single property.`;
284
+ for (const ctor of alternateCtors) {
285
+ out += `\n\n### ${ctor.scalar}`;
286
+ if (ctor.title) {
287
+ out += ` ${ctor.title}`;
288
+ }
289
+ out += `\n`;
290
+ if (ctor.description) {
291
+ out += `\n${ctor.description}\n`;
292
+ }
293
+ out += `\nThe following simplified representation can be used:\n\n\`\`\`yaml\n${ctor.simple.trim()}\n\`\`\`\n\nThis is equivalent to the full representation:\n\n\`\`\`yaml\n${ctor.expanded.trim()}\n\`\`\``;
294
+ }
295
+ }
296
+ out += `\n`;
297
+ return out;
298
+ }
299
+ export const generateMarkdown = async (context, node, emitTarget, options) => {
300
+ const rootObject = context.options["root-alias"] || "AgentDefinition";
301
+ const nodes = filterNodes(Array.from(enumerateTypes(node)), options);
302
+ const childTypes = nodes.map(n => {
303
+ return n.childTypes.map(c => {
304
+ return { source: n.typeName.name, target: c.typeName.name };
305
+ });
306
+ }).flat();
307
+ const compositionTypes = nodes.map(n => {
308
+ return n.properties.filter(p => !p.isScalar).map(c => {
309
+ return { source: n.typeName.name, target: c.typeName.name };
310
+ });
311
+ }).flat();
312
+ const readmeContent = emitIndexMarkdown(nodes, rootObject, childTypes, compositionTypes);
313
+ await emitMarkdownFile(context, "index", readmeContent, emitTarget["output-dir"]);
314
+ const findNodeByName = (name) => {
315
+ return nodes.find(n => n.typeName.name === name.name && n.typeName.namespace === name.namespace);
316
+ };
317
+ for (const node of nodes) {
318
+ const sample = node.properties.filter(p => p.samples.length > 0).map(p => p.samples[0].sample);
319
+ let yml = undefined;
320
+ let md = undefined;
321
+ if (sample.length > 0) {
322
+ const s = deepMerge(...sample);
323
+ yml = YAML.stringify(s, { indent: 2 });
324
+ if ("instructions" in s) {
325
+ const instructions = s.instructions;
326
+ delete s.instructions;
327
+ md = `---\n${YAML.stringify(s, { indent: 2 })}---\n${instructions}`;
328
+ }
329
+ }
330
+ const markdown = emitFileMarkdown(node, yml, md, getCompositionTypes(node), generateCoercions(node), node.base ? findNodeByName(node.base) : undefined);
331
+ await emitMarkdownFile(context, node.typeName.name, markdown, emitTarget["output-dir"]);
332
+ }
333
+ };
334
+ export const renderType = (prop) => {
335
+ const arrayString = prop.isCollection ? "[]" : "";
336
+ if (prop.isScalar) {
337
+ return prop.typeName.name + arrayString;
338
+ }
339
+ else if (prop.isDict) {
340
+ return `${prop.typeName.name + arrayString}`;
341
+ }
342
+ else {
343
+ return `[${prop.typeName.name + arrayString}](../${prop.typeName.name.toLowerCase()}/)`;
344
+ }
345
+ };
346
+ export const renderChildTypes = (node) => {
347
+ if (!node.isScalar && node.type) {
348
+ const childTypes = node.type.childTypes.map(c => {
349
+ return `[${c.typeName.name}](../${c.typeName.name.toLowerCase()}/)`;
350
+ });
351
+ if (childTypes.length === 0) {
352
+ return "";
353
+ }
354
+ return `(Related Types: ${childTypes.join(", ")})`;
355
+ }
356
+ return "";
357
+ };
358
+ export const getChildTypes = (node) => {
359
+ return node.childTypes.flatMap(c => [{
360
+ source: node.typeName.name,
361
+ target: c.typeName.name
362
+ }, ...getChildTypes(c)]);
363
+ };
364
+ export const getCompositionTypes = (node) => {
365
+ const nonScalars = node.properties.filter(p => !p.isScalar && !p.isDict);
366
+ return nonScalars.flatMap(c => c.type ? [c.type] : []);
367
+ };
368
+ const typeExampleMapper = {
369
+ "string": "\"example\"",
370
+ "number": "5",
371
+ "boolean": "true",
372
+ "int64": "5",
373
+ "int32": "5",
374
+ "float64": "3.14",
375
+ "float32": "3.14",
376
+ "integer": "5",
377
+ "float": "3.14",
378
+ "numeric": "3.14",
379
+ };
380
+ export const generateCoercions = (node) => {
381
+ if (node.coercions && node.coercions.length > 0) {
382
+ const alts = [];
383
+ for (const alt of node.coercions) {
384
+ const scalar = alt.scalar;
385
+ const sample = typeExampleMapper[scalar] ? typeExampleMapper[scalar] : "example";
386
+ const simple = {};
387
+ simple[alt.title || "value"] = "\"{value}\"";
388
+ const expansion = {};
389
+ expansion[alt.title || "value"] = alt.expansion;
390
+ alts.push({
391
+ title: alt.title || "",
392
+ description: alt.description || "",
393
+ scalar: scalar,
394
+ simple: YAML.stringify(simple, { indent: 2 }).replace("\"{value}\"", sample).replaceAll("'", ""),
395
+ expanded: YAML.stringify(expansion, { indent: 2 }).replace("\"{value}\"", sample)
396
+ });
397
+ }
398
+ return alts;
399
+ }
400
+ else {
401
+ return [];
402
+ }
403
+ };
404
+ const emitMarkdownFile = async (context, name, markdown, outputDir) => {
405
+ const dir = outputDir || `${context.emitterOutputDir}/markdown`;
406
+ await emitGeneratedFile(context, resolvePath(dir, `${name}.md`), markdown);
407
+ };
408
+ //# sourceMappingURL=driver.js.map
@@ -0,0 +1,14 @@
1
+ import { EmitContext } from "@typespec/compiler";
2
+ import { EmitTarget, TypraEmitterOptions } from "../../lib.js";
3
+ import { TypeNode } from "../../ir/ast.js";
4
+ import { GeneratorOptions } from "../../emitter.js";
5
+ /**
6
+ * Type mapping from TypeSpec scalar types to Python types.
7
+ * This is passed as data to templates, not used for inline rendering.
8
+ */
9
+ export declare const pythonTypeMapper: Record<string, string>;
10
+ /**
11
+ * Main entry point for Python code generation.
12
+ * Prepares data contexts and delegates rendering to inline emitter functions.
13
+ */
14
+ export declare const generatePython: (context: EmitContext<TypraEmitterOptions>, node: TypeNode, emitTarget: EmitTarget, options?: GeneratorOptions) => Promise<void>;