@typespec/mutator-framework 0.12.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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/index.d.ts +3 -0
  3. package/dist/src/index.d.ts.map +1 -0
  4. package/dist/src/index.js +4 -0
  5. package/dist/src/index.js.map +1 -0
  6. package/dist/src/mutation/index.d.ts +13 -0
  7. package/dist/src/mutation/index.d.ts.map +1 -0
  8. package/dist/src/mutation/index.js +13 -0
  9. package/dist/src/mutation/index.js.map +1 -0
  10. package/dist/src/mutation/interface.d.ts +11 -0
  11. package/dist/src/mutation/interface.d.ts.map +1 -0
  12. package/dist/src/mutation/interface.js +17 -0
  13. package/dist/src/mutation/interface.js.map +1 -0
  14. package/dist/src/mutation/intrinsic.d.ts +9 -0
  15. package/dist/src/mutation/intrinsic.d.ts.map +1 -0
  16. package/dist/src/mutation/intrinsic.js +11 -0
  17. package/dist/src/mutation/intrinsic.js.map +1 -0
  18. package/dist/src/mutation/literal.d.ts +9 -0
  19. package/dist/src/mutation/literal.d.ts.map +1 -0
  20. package/dist/src/mutation/literal.js +11 -0
  21. package/dist/src/mutation/literal.js.map +1 -0
  22. package/dist/src/mutation/model-property.d.ts +12 -0
  23. package/dist/src/mutation/model-property.d.ts.map +1 -0
  24. package/dist/src/mutation/model-property.js +18 -0
  25. package/dist/src/mutation/model-property.js.map +1 -0
  26. package/dist/src/mutation/model.d.ts +18 -0
  27. package/dist/src/mutation/model.d.ts.map +1 -0
  28. package/dist/src/mutation/model.js +34 -0
  29. package/dist/src/mutation/model.js.map +1 -0
  30. package/dist/src/mutation/mutation-engine.d.ts +80 -0
  31. package/dist/src/mutation/mutation-engine.d.ts.map +1 -0
  32. package/dist/src/mutation/mutation-engine.js +165 -0
  33. package/dist/src/mutation/mutation-engine.js.map +1 -0
  34. package/dist/src/mutation/mutation-engine.test.d.ts +2 -0
  35. package/dist/src/mutation/mutation-engine.test.d.ts.map +1 -0
  36. package/dist/src/mutation/mutation-engine.test.js +145 -0
  37. package/dist/src/mutation/mutation-engine.test.js.map +1 -0
  38. package/dist/src/mutation/mutation.d.ts +45 -0
  39. package/dist/src/mutation/mutation.d.ts.map +1 -0
  40. package/dist/src/mutation/mutation.js +29 -0
  41. package/dist/src/mutation/mutation.js.map +1 -0
  42. package/dist/src/mutation/operation.d.ts +13 -0
  43. package/dist/src/mutation/operation.d.ts.map +1 -0
  44. package/dist/src/mutation/operation.js +20 -0
  45. package/dist/src/mutation/operation.js.map +1 -0
  46. package/dist/src/mutation/scalar.d.ts +11 -0
  47. package/dist/src/mutation/scalar.d.ts.map +1 -0
  48. package/dist/src/mutation/scalar.js +17 -0
  49. package/dist/src/mutation/scalar.js.map +1 -0
  50. package/dist/src/mutation/simple-mutation-engine.d.ts +8 -0
  51. package/dist/src/mutation/simple-mutation-engine.d.ts.map +1 -0
  52. package/dist/src/mutation/simple-mutation-engine.js +11 -0
  53. package/dist/src/mutation/simple-mutation-engine.js.map +1 -0
  54. package/dist/src/mutation/union-variant.d.ts +10 -0
  55. package/dist/src/mutation/union-variant.d.ts.map +1 -0
  56. package/dist/src/mutation/union-variant.js +12 -0
  57. package/dist/src/mutation/union-variant.js.map +1 -0
  58. package/dist/src/mutation/union.d.ts +11 -0
  59. package/dist/src/mutation/union.d.ts.map +1 -0
  60. package/dist/src/mutation/union.js +18 -0
  61. package/dist/src/mutation/union.js.map +1 -0
  62. package/dist/src/mutation-node/enum-member.d.ts +7 -0
  63. package/dist/src/mutation-node/enum-member.d.ts.map +1 -0
  64. package/dist/src/mutation-node/enum-member.js +6 -0
  65. package/dist/src/mutation-node/enum-member.js.map +1 -0
  66. package/dist/src/mutation-node/enum-member.test.d.ts +2 -0
  67. package/dist/src/mutation-node/enum-member.test.d.ts.map +1 -0
  68. package/dist/src/mutation-node/enum-member.test.js +25 -0
  69. package/dist/src/mutation-node/enum-member.test.js.map +1 -0
  70. package/dist/src/mutation-node/enum.d.ts +8 -0
  71. package/dist/src/mutation-node/enum.d.ts.map +1 -0
  72. package/dist/src/mutation-node/enum.js +30 -0
  73. package/dist/src/mutation-node/enum.js.map +1 -0
  74. package/dist/src/mutation-node/enum.test.d.ts +2 -0
  75. package/dist/src/mutation-node/enum.test.d.ts.map +1 -0
  76. package/dist/src/mutation-node/enum.test.js +25 -0
  77. package/dist/src/mutation-node/enum.test.js.map +1 -0
  78. package/dist/src/mutation-node/factory.d.ts +17 -0
  79. package/dist/src/mutation-node/factory.d.ts.map +1 -0
  80. package/dist/src/mutation-node/factory.js +45 -0
  81. package/dist/src/mutation-node/factory.js.map +1 -0
  82. package/dist/src/mutation-node/index.d.ts +15 -0
  83. package/dist/src/mutation-node/index.d.ts.map +1 -0
  84. package/dist/src/mutation-node/index.js +16 -0
  85. package/dist/src/mutation-node/index.js.map +1 -0
  86. package/dist/src/mutation-node/interface.d.ts +8 -0
  87. package/dist/src/mutation-node/interface.d.ts.map +1 -0
  88. package/dist/src/mutation-node/interface.js +30 -0
  89. package/dist/src/mutation-node/interface.js.map +1 -0
  90. package/dist/src/mutation-node/intrinsic.d.ts +7 -0
  91. package/dist/src/mutation-node/intrinsic.d.ts.map +1 -0
  92. package/dist/src/mutation-node/intrinsic.js +6 -0
  93. package/dist/src/mutation-node/intrinsic.js.map +1 -0
  94. package/dist/src/mutation-node/literal.d.ts +7 -0
  95. package/dist/src/mutation-node/literal.d.ts.map +1 -0
  96. package/dist/src/mutation-node/literal.js +6 -0
  97. package/dist/src/mutation-node/literal.js.map +1 -0
  98. package/dist/src/mutation-node/model-property.d.ts +10 -0
  99. package/dist/src/mutation-node/model-property.d.ts.map +1 -0
  100. package/dist/src/mutation-node/model-property.js +48 -0
  101. package/dist/src/mutation-node/model-property.js.map +1 -0
  102. package/dist/src/mutation-node/model-property.test.d.ts +2 -0
  103. package/dist/src/mutation-node/model-property.test.d.ts.map +1 -0
  104. package/dist/src/mutation-node/model-property.test.js +108 -0
  105. package/dist/src/mutation-node/model-property.test.js.map +1 -0
  106. package/dist/src/mutation-node/model.d.ts +10 -0
  107. package/dist/src/mutation-node/model.d.ts.map +1 -0
  108. package/dist/src/mutation-node/model.js +82 -0
  109. package/dist/src/mutation-node/model.js.map +1 -0
  110. package/dist/src/mutation-node/model.test.d.ts +2 -0
  111. package/dist/src/mutation-node/model.test.d.ts.map +1 -0
  112. package/dist/src/mutation-node/model.test.js +133 -0
  113. package/dist/src/mutation-node/model.test.js.map +1 -0
  114. package/dist/src/mutation-node/mutation-edge.d.ts +18 -0
  115. package/dist/src/mutation-node/mutation-edge.d.ts.map +1 -0
  116. package/dist/src/mutation-node/mutation-edge.js +31 -0
  117. package/dist/src/mutation-node/mutation-edge.js.map +1 -0
  118. package/dist/src/mutation-node/mutation-node.d.ts +29 -0
  119. package/dist/src/mutation-node/mutation-node.d.ts.map +1 -0
  120. package/dist/src/mutation-node/mutation-node.js +82 -0
  121. package/dist/src/mutation-node/mutation-node.js.map +1 -0
  122. package/dist/src/mutation-node/mutation-node.test.d.ts +2 -0
  123. package/dist/src/mutation-node/mutation-node.test.d.ts.map +1 -0
  124. package/dist/src/mutation-node/mutation-node.test.js +88 -0
  125. package/dist/src/mutation-node/mutation-node.test.js.map +1 -0
  126. package/dist/src/mutation-node/mutation-subgraph.d.ts +17 -0
  127. package/dist/src/mutation-node/mutation-subgraph.d.ts.map +1 -0
  128. package/dist/src/mutation-node/mutation-subgraph.js +45 -0
  129. package/dist/src/mutation-node/mutation-subgraph.js.map +1 -0
  130. package/dist/src/mutation-node/operation.d.ts +9 -0
  131. package/dist/src/mutation-node/operation.d.ts.map +1 -0
  132. package/dist/src/mutation-node/operation.js +44 -0
  133. package/dist/src/mutation-node/operation.js.map +1 -0
  134. package/dist/src/mutation-node/scalar.d.ts +8 -0
  135. package/dist/src/mutation-node/scalar.d.ts.map +1 -0
  136. package/dist/src/mutation-node/scalar.js +28 -0
  137. package/dist/src/mutation-node/scalar.js.map +1 -0
  138. package/dist/src/mutation-node/scalar.test.d.ts +2 -0
  139. package/dist/src/mutation-node/scalar.test.d.ts.map +1 -0
  140. package/dist/src/mutation-node/scalar.test.js +40 -0
  141. package/dist/src/mutation-node/scalar.test.js.map +1 -0
  142. package/dist/src/mutation-node/tuple.d.ts +9 -0
  143. package/dist/src/mutation-node/tuple.d.ts.map +1 -0
  144. package/dist/src/mutation-node/tuple.js +32 -0
  145. package/dist/src/mutation-node/tuple.js.map +1 -0
  146. package/dist/src/mutation-node/tuple.test.d.ts +2 -0
  147. package/dist/src/mutation-node/tuple.test.d.ts.map +1 -0
  148. package/dist/src/mutation-node/tuple.test.js +29 -0
  149. package/dist/src/mutation-node/tuple.test.js.map +1 -0
  150. package/dist/src/mutation-node/union-variant.d.ts +8 -0
  151. package/dist/src/mutation-node/union-variant.d.ts.map +1 -0
  152. package/dist/src/mutation-node/union-variant.js +23 -0
  153. package/dist/src/mutation-node/union-variant.js.map +1 -0
  154. package/dist/src/mutation-node/union-variant.test.d.ts +2 -0
  155. package/dist/src/mutation-node/union-variant.test.d.ts.map +1 -0
  156. package/dist/src/mutation-node/union-variant.test.js +27 -0
  157. package/dist/src/mutation-node/union-variant.test.js.map +1 -0
  158. package/dist/src/mutation-node/union.d.ts +8 -0
  159. package/dist/src/mutation-node/union.d.ts.map +1 -0
  160. package/dist/src/mutation-node/union.js +30 -0
  161. package/dist/src/mutation-node/union.js.map +1 -0
  162. package/dist/src/mutation-node/union.test.d.ts +2 -0
  163. package/dist/src/mutation-node/union.test.d.ts.map +1 -0
  164. package/dist/src/mutation-node/union.test.js +25 -0
  165. package/dist/src/mutation-node/union.test.js.map +1 -0
  166. package/dist/test/test-host.d.ts +2 -0
  167. package/dist/test/test-host.d.ts.map +1 -0
  168. package/dist/test/test-host.js +6 -0
  169. package/dist/test/test-host.js.map +1 -0
  170. package/dist/test/utils.d.ts +4 -0
  171. package/dist/test/utils.d.ts.map +1 -0
  172. package/dist/test/utils.js +8 -0
  173. package/dist/test/utils.js.map +1 -0
  174. package/package.json +40 -0
  175. package/package.json.bak +42 -0
  176. package/readme.md +339 -0
  177. package/src/index.ts +4 -0
  178. package/src/mutation/index.ts +12 -0
  179. package/src/mutation/interface.ts +38 -0
  180. package/src/mutation/intrinsic.ts +23 -0
  181. package/src/mutation/literal.ts +29 -0
  182. package/src/mutation/model-property.ts +35 -0
  183. package/src/mutation/model.ts +58 -0
  184. package/src/mutation/mutation-engine.test.ts +202 -0
  185. package/src/mutation/mutation-engine.ts +288 -0
  186. package/src/mutation/mutation.ts +90 -0
  187. package/src/mutation/operation.ts +40 -0
  188. package/src/mutation/scalar.ts +36 -0
  189. package/src/mutation/simple-mutation-engine.ts +21 -0
  190. package/src/mutation/union-variant.ts +30 -0
  191. package/src/mutation/union.ts +39 -0
  192. package/src/mutation-node/enum-member.test.ts +26 -0
  193. package/src/mutation-node/enum-member.ts +8 -0
  194. package/src/mutation-node/enum.test.ts +26 -0
  195. package/src/mutation-node/enum.ts +33 -0
  196. package/src/mutation-node/factory.ts +95 -0
  197. package/src/mutation-node/index.ts +16 -0
  198. package/src/mutation-node/interface.ts +33 -0
  199. package/src/mutation-node/intrinsic.ts +8 -0
  200. package/src/mutation-node/literal.ts +10 -0
  201. package/src/mutation-node/model-property.test.ts +136 -0
  202. package/src/mutation-node/model-property.ts +53 -0
  203. package/src/mutation-node/model.test.ts +151 -0
  204. package/src/mutation-node/model.ts +89 -0
  205. package/src/mutation-node/mutation-edge.ts +43 -0
  206. package/src/mutation-node/mutation-node.test.ts +94 -0
  207. package/src/mutation-node/mutation-node.ts +110 -0
  208. package/src/mutation-node/mutation-subgraph.ts +59 -0
  209. package/src/mutation-node/operation.ts +49 -0
  210. package/src/mutation-node/scalar.test.ts +44 -0
  211. package/src/mutation-node/scalar.ts +32 -0
  212. package/src/mutation-node/tuple.test.ts +30 -0
  213. package/src/mutation-node/tuple.ts +35 -0
  214. package/src/mutation-node/union-variant.test.ts +28 -0
  215. package/src/mutation-node/union-variant.ts +26 -0
  216. package/src/mutation-node/union.test.ts +26 -0
  217. package/src/mutation-node/union.ts +33 -0
  218. package/test/test-host.ts +6 -0
  219. package/test/utils.ts +9 -0
  220. package/tsconfig.json +19 -0
  221. package/vitest.config.ts +4 -0
package/readme.md ADDED
@@ -0,0 +1,339 @@
1
+ # Mutator Framework
2
+
3
+ ** WARNING: THIS PACKAGE IS EXPERIMENTAL AND WILL CHANGE **
4
+
5
+ This package provides utilities for building mutations of the TypeSpec type
6
+ graph. Mutations are modifications to the original type graph that live in a
7
+ parallel type graph and contain additional metadata relevant to consumers of
8
+ those mutated types.
9
+
10
+ At a high level you:
11
+
12
+ - Create mutation classes that control how each TypeSpec type (models,
13
+ properties, unions, scalars, literals, operations, etc.) is traversed and
14
+ transformed.
15
+ - Use strongly-typed helper APIs on mutation nodes to mutate into new types or
16
+ traverse to related nodes.
17
+ - Instantiate a `MutationEngine` subtype (e.g. `SimpleMutationEngine`) with the
18
+ `Typekit` from the TypeSpec program you want to mutate.
19
+
20
+ The key APIs are:
21
+
22
+ - `MutationEngine` – orchestrates creation, caching, and traversal of mutation nodes.
23
+ - `SimpleMutationEngine` – a convenience engine that exposes a single default mutation subgraph.
24
+ - `MutationOptions` – lets you parameterize a mutation run and cache its results.
25
+ - `ModelMutation`, `ModelPropertyMutation`, `UnionMutation`, `UnionVariantMutation`,
26
+ `OperationMutation`, etc. – base classes for crafting custom mutations per TypeSpec kind.
27
+ - `MutationSubgraph` – creates an isolated graph of mutated types that can be inspected or
28
+ retrieved later.
29
+ - `ModelMutationNode`, `ModelPropertyMutationNode`, `UnionMutationNode`, etc. -
30
+ nodes which represent the possible mutation of a particular type graph type.
31
+
32
+ ## Getting Started
33
+
34
+ ```ts
35
+ import type { Model, Program } from "@typespec/compiler";
36
+ import { $, type Typekit } from "@typespec/compiler/typekit";
37
+ import { SimpleMutationEngine } from "@typespec/mutator-framework";
38
+
39
+ // Create a typekit for the program
40
+ const tk: Typekit = $(program);
41
+
42
+ // Instantiate an engine for running the mutations.
43
+ // Might be the built-in SimpleMutationEngine, or a
44
+ // custom `MutationEngine` subclass.
45
+ const engine = new SimpleMutationEngine(tk, {
46
+ Model: RenameModelMutation, // defined later in this guide
47
+ });
48
+ const renamedMutation = engine.mutate(someType);
49
+ const mutatedType = renamedMutation.mutatedType;
50
+ ```
51
+
52
+ ## Defining Custom Mutation Options
53
+
54
+ Options derive from `MutationOptions`. They let you tune mutations (for example,
55
+ to switch on features or change naming rules) and provide a cache key used to
56
+ memoize results. Extend the class and override `cacheKey()` to represent your
57
+ configuration.
58
+
59
+ ```ts
60
+ // rename-mutations.ts
61
+ import { MutationOptions } from "@typespec/mutator-framework";
62
+
63
+ export class RenameMutationOptions extends MutationOptions {
64
+ constructor(
65
+ readonly prefix: string,
66
+ readonly suffix: string,
67
+ ) {
68
+ super();
69
+ }
70
+
71
+ override cacheKey() {
72
+ return `${this.prefix}-${this.suffix}`;
73
+ }
74
+ }
75
+ ```
76
+
77
+ ## Creating a Custom Mutation Engine
78
+
79
+ `MutationEngine` is responsible for coordinating mutation nodes and subgraphs. Supply constructors
80
+ for each type kind you want to customize. Anything you omit defaults to the base implementations
81
+ (`ModelMutation`, `ModelPropertyMutation`, etc.).
82
+
83
+ You can also register additional mutation subgraphs. Each subgraph represents an isolated view of
84
+ the mutated graph. This is useful when you want to compare alternative transformations side by side
85
+ (for example, with different naming conventions).
86
+
87
+ ```ts
88
+ // rename-mutations.ts
89
+ import { MutationEngine, type MutationSubgraph } from "@typespec/mutator-framework";
90
+ import type { Typekit } from "@typespec/compiler/typekit";
91
+
92
+ export class RenameMutationEngine extends MutationEngine<{ Model: RenameModelMutation }> {
93
+ constructor(typekit: Typekit) {
94
+ super(typekit, { Model: RenameModelMutation });
95
+ this.registerSubgraph("prefix");
96
+ this.registerSubgraph("suffix");
97
+ }
98
+
99
+ getPrefix(options: RenameMutationOptions): MutationSubgraph {
100
+ return this.getMutationSubgraph(options, "prefix");
101
+ }
102
+
103
+ getSuffix(options: RenameMutationOptions): MutationSubgraph {
104
+ return this.getMutationSubgraph(options, "suffix");
105
+ }
106
+ }
107
+ ```
108
+
109
+ The base `MutationEngine` does not define a default subgraph. If you just need a single mutated
110
+ view, use the `SimpleMutationEngine`. It auto-registers a `"subgraph"` and wires
111
+ `getDefaultMutationSubgraph` for you:
112
+
113
+ ```ts
114
+ import { SimpleMutationEngine } from "@typespec/mutator-framework";
115
+
116
+ const engine = new SimpleMutationEngine(tk, {
117
+ Model: RenameModelMutation,
118
+ });
119
+ ```
120
+
121
+ ## Writing Mutation Classes
122
+
123
+ Mutation classes derive from the base classes included in the framework. Each class receives the
124
+ engine, the source TypeSpec type, the list of reference members that referenced that type (if any),
125
+ and the options. Override `mutate()` to perform your transformations.
126
+
127
+ Inside `mutate()` you can:
128
+
129
+ - Traverse connected types via `this.engine.mutate(...)` or `this.engine.mutateReference(...)`.
130
+ - Retrieve or create mutation nodes with `this.getMutationNode()`.
131
+ - Mutate values using `this.mutateType()` or `engine.mutateType(...)`.
132
+ - Switch subgraphs by calling `this.engine.getMutationSubgraph(...)`.
133
+
134
+ ### Example: Renaming Models in Multiple Subgraphs
135
+
136
+ ```ts
137
+ // rename-mutations.ts
138
+ import { ModelMutation } from "@typespec/mutator-framework";
139
+
140
+ export class RenameModelMutation extends ModelMutation<
141
+ RenameMutationOptions,
142
+ { Model: RenameModelMutation },
143
+ RenameMutationEngine
144
+ > {
145
+ get withPrefix() {
146
+ return this.getMutatedType(this.engine.getPrefix(this.options));
147
+ }
148
+
149
+ get withSuffix() {
150
+ return this.getMutatedType(this.engine.getSuffix(this.options));
151
+ }
152
+
153
+ mutate() {
154
+ if ("name" in this.sourceType && typeof this.sourceType.name === "string") {
155
+ this.mutateType(this.engine.getPrefix(this.options), (model) => {
156
+ model.name = `${this.options.prefix}${model.name}`;
157
+ });
158
+
159
+ this.mutateType(this.engine.getSuffix(this.options), (model) => {
160
+ model.name = `${model.name}${this.options.suffix}`;
161
+ });
162
+ }
163
+
164
+ // Always call super.mutate() if you still want the base implementation
165
+ // to traverse properties, base models, indexers, etc. with the same options.
166
+ super.mutate();
167
+ }
168
+ }
169
+ ```
170
+
171
+ ### Running the Mutation
172
+
173
+ ```ts
174
+ import type { Model, Program } from "@typespec/compiler";
175
+ import { $ } from "@typespec/compiler/typekit";
176
+ import { RenameMutationEngine, RenameMutationOptions } from "./rename-mutations.js";
177
+
178
+ export function applyRename(program: Program, fooModel: Model) {
179
+ const engine = new RenameMutationEngine($(program));
180
+ const options = new RenameMutationOptions("Pre", "Suf");
181
+
182
+ const fooMutation = engine.mutate(fooModel, options);
183
+ const prefixFoo = fooMutation.withPrefix;
184
+ const suffixFoo = fooMutation.withSuffix;
185
+
186
+ const propMutation = fooMutation.properties.get("prop")!;
187
+ const barMutation = propMutation.type as RenameModelMutation;
188
+
189
+ return {
190
+ prefixFoo,
191
+ suffixFoo,
192
+ barWithSuffix: barMutation.withSuffix,
193
+ };
194
+ }
195
+ ```
196
+
197
+ ### Example: Mutating Referenced Types
198
+
199
+ `ModelPropertyMutation` exposes `mutateReference` and `replaceReferencedType`
200
+ helpers that make it easy to mutate types referenced by properties. When
201
+ mutating references, a clone of the referenced type is made, so changes to not
202
+ affect the referenced type. This enables references to reference a unique type
203
+ with mutations that are particular to that type when referenced in that context.
204
+ For example, if the model property contains a decorator that affects the
205
+ mutation of a referenced scalar.
206
+
207
+ ```ts
208
+ import type { Model, Program } from "@typespec/compiler";
209
+ import { $ } from "@typespec/compiler/typekit";
210
+ import {
211
+ ModelMutation,
212
+ ModelPropertyMutation,
213
+ MutationOptions,
214
+ SimpleMutationEngine,
215
+ } from "@typespec/mutator-framework";
216
+
217
+ class UnionifyOptions extends MutationOptions {}
218
+
219
+ class UnionifyModel extends ModelMutation<
220
+ UnionifyOptions,
221
+ UnionifyMutations,
222
+ SimpleMutationEngine<UnionifyMutations>
223
+ > {
224
+ get unionified() {
225
+ return this.getMutatedType();
226
+ }
227
+ }
228
+
229
+ class UnionifyProperty extends ModelPropertyMutation<
230
+ UnionifyOptions,
231
+ UnionifyMutations,
232
+ SimpleMutationEngine<UnionifyMutations>
233
+ > {
234
+ get unionified() {
235
+ return this.getMutatedType();
236
+ }
237
+
238
+ mutate() {
239
+ if (!this.engine.$.union.is(this.sourceType.type)) {
240
+ const unionVariant = this.engine.$.unionVariant.create({ type: this.sourceType.type });
241
+ const fallbackVariant = this.engine.$.unionVariant.create({
242
+ type: this.engine.$.builtin.string,
243
+ });
244
+
245
+ const unionType = this.engine.$.union.create({ variants: [unionVariant, fallbackVariant] });
246
+
247
+ this.type = this.replaceReferencedType(
248
+ this.engine.getDefaultMutationSubgraph(this.options),
249
+ unionType,
250
+ );
251
+ } else {
252
+ super.mutate();
253
+ }
254
+ }
255
+ }
256
+
257
+ interface UnionifyMutations {
258
+ Model: UnionifyModel;
259
+ ModelProperty: UnionifyProperty;
260
+ }
261
+
262
+ export function createUnionifyEngine(program: Program) {
263
+ const tk = $(program);
264
+ return new SimpleMutationEngine(tk, {
265
+ Model: UnionifyModel,
266
+ ModelProperty: UnionifyProperty,
267
+ });
268
+ }
269
+
270
+ export function unionifyModel(program: Program, fooModel: Model) {
271
+ const engine = createUnionifyEngine(program);
272
+ const fooMutation = engine.mutate(fooModel, new UnionifyOptions());
273
+ const propMutation = fooMutation.properties.get("prop")!;
274
+
275
+ return {
276
+ property: propMutation.unionified,
277
+ model: fooMutation.unionified,
278
+ };
279
+ }
280
+ ```
281
+
282
+ ## Core Mutation Base Classes
283
+
284
+ | Class | Source Type | Responsibilities |
285
+ | ----------------------- | ------------------------------ | ---------------------------------------------------------- |
286
+ | `ModelMutation` | `Model` | Traverses base models, properties, and indexers. |
287
+ | `ModelPropertyMutation` | `ModelProperty` | Mutates referenced types, exposes `replaceReferencedType`. |
288
+ | `UnionMutation` | `Union` | Iterates over variants and lazy-loads their mutations. |
289
+ | `UnionVariantMutation` | `UnionVariant` | Handles referenced variant types. |
290
+ | `ScalarMutation` | `Scalar` | Provides access to scalar definitions and projections. |
291
+ | `LiteralMutation` | string/number/boolean literals | Provides literal values and traversal control. |
292
+ | `OperationMutation` | `Operation` | Mutates parameters, return types, and decorators. |
293
+ | `InterfaceMutation` | `Interface` | Walks operations declared on the interface. |
294
+ | `IntrinsicMutation` | `Intrinsic` | Surfaces intrinsic TypeSpec types. |
295
+
296
+ Each class inherits from the foundational `Mutation` class, which provides
297
+ shared helpers for mutated types (`getMutatedType`) and nodes
298
+ (`getMutationNode`). Override them or add convenience getters/setters to tailor
299
+ the experience for your consumers.
300
+
301
+ ## Working with Mutation Subgraphs
302
+
303
+ The engine builds mutation nodes inside a `MutationSubgraph`. Each subgraph
304
+ captures a set of mutations that share the same options and transformation
305
+ logic.
306
+
307
+ ```ts
308
+ const prefixGraph = engine.getPrefix(renameOptions);
309
+ const prefixFoo = engine.getMutatedType(prefixGraph, Foo);
310
+
311
+ const suffixGraph = engine.getSuffix(renameOptions);
312
+ const suffixFoo = engine.getMutatedType(suffixGraph, Foo);
313
+
314
+ console.log(prefixFoo.name, suffixFoo.name);
315
+ ```
316
+
317
+ When you call `engine.mutate(type, options)` the engine automatically creates mutation nodes in all
318
+ registered subgraphs for the provided options. Subsequent calls reuse the cached nodes, so you can
319
+ freely navigate the mutation graph without re-running your transformation logic.
320
+
321
+ ## Tips for Building Mutations
322
+
323
+ - **Always call `super.mutate()`** when you want the default traversal logic after your custom
324
+ changes. Skipping it gives you full control, but you must handle traversal yourself.
325
+ - **Use `MutationOptions` subclasses** whenever your mutation behavior depends on input
326
+ configuration. Return a stable `cacheKey()` to reuse work.
327
+ - **Inspect `referenceTypes`** to learn which `ModelProperty` or `UnionVariant` led to the current
328
+ mutation node. This helps you emit diagnostics or perform context-sensitive logic.
329
+ - **Mutate lazily**. Mutations only run once per `(type, options)` pair. If you expose getters that
330
+ trigger work, they should go through `engine.mutate(...)` so the cache stays consistent.
331
+ - **Prefer `SimpleMutationEngine`** unless you need named subgraphs. You can graduate to a custom
332
+ engine later.
333
+
334
+ ## Additional Resources
335
+
336
+ - Browse the rest of the files under `packages/mutator-framework/src/mutation` to see the built-in
337
+ mutation implementations.
338
+ - The unit tests in `mutation-engine.test.ts` demonstrate more end-to-end usage patterns, including
339
+ multi-subgraph mutations and reference replacements.
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./mutation/index.js";
2
+
3
+ // this ordering is important to avoid circular reference errors.
4
+ export * from "./mutation-node/index.js";
@@ -0,0 +1,12 @@
1
+ export * from "./interface.js";
2
+ export * from "./intrinsic.js";
3
+ export * from "./literal.js";
4
+ export * from "./model-property.js";
5
+ export * from "./model.js";
6
+ export * from "./mutation-engine.js";
7
+ export * from "./mutation.js";
8
+ export * from "./operation.js";
9
+ export * from "./scalar.js";
10
+ export * from "./simple-mutation-engine.js";
11
+ export * from "./union-variant.js";
12
+ export * from "./union.js";
@@ -0,0 +1,38 @@
1
+ import type { Interface, MemberType } from "@typespec/compiler";
2
+ import type {
3
+ CustomMutationClasses,
4
+ MutationEngine,
5
+ MutationFor,
6
+ MutationOptions,
7
+ } from "./mutation-engine.js";
8
+ import { Mutation } from "./mutation.js";
9
+
10
+ export class InterfaceMutation<
11
+ TOptions extends MutationOptions,
12
+ TCustomMutations extends CustomMutationClasses,
13
+ > extends Mutation<Interface, TCustomMutations, TOptions> {
14
+ readonly kind = "Interface";
15
+ operations: Map<string, MutationFor<TCustomMutations, "Operation">> = new Map();
16
+
17
+ constructor(
18
+ engine: MutationEngine<TCustomMutations>,
19
+ sourceType: Interface,
20
+ referenceTypes: MemberType[] = [],
21
+ options: TOptions,
22
+ ) {
23
+ super(engine, sourceType, referenceTypes, options);
24
+ }
25
+
26
+ protected mutateOperations() {
27
+ for (const op of this.sourceType.operations.values()) {
28
+ this.operations.set(
29
+ op.name,
30
+ this.engine.mutate(op, this.options) as MutationFor<TCustomMutations, "Operation">,
31
+ );
32
+ }
33
+ }
34
+
35
+ mutate() {
36
+ this.mutateOperations();
37
+ }
38
+ }
@@ -0,0 +1,23 @@
1
+ import type { IntrinsicType, MemberType } from "@typespec/compiler";
2
+ import type { CustomMutationClasses, MutationEngine, MutationOptions } from "./mutation-engine.js";
3
+ import { Mutation } from "./mutation.js";
4
+
5
+ export class IntrinsicMutation<
6
+ TOptions extends MutationOptions,
7
+ TCustomMutations extends CustomMutationClasses,
8
+ TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
9
+ > extends Mutation<IntrinsicType, TCustomMutations, TOptions, TEngine> {
10
+ readonly kind = "Intrinsic";
11
+ constructor(
12
+ engine: TEngine,
13
+ sourceType: IntrinsicType,
14
+ referenceTypes: MemberType[] = [],
15
+ options: TOptions,
16
+ ) {
17
+ super(engine, sourceType, referenceTypes, options);
18
+ }
19
+
20
+ mutate() {
21
+ // No mutations needed for intrinsic types
22
+ }
23
+ }
@@ -0,0 +1,29 @@
1
+ import type { BooleanLiteral, MemberType, NumericLiteral, StringLiteral } from "@typespec/compiler";
2
+ import type { CustomMutationClasses, MutationEngine, MutationOptions } from "./mutation-engine.js";
3
+ import { Mutation } from "./mutation.js";
4
+
5
+ export class LiteralMutation<
6
+ TOptions extends MutationOptions,
7
+ TCustomMutations extends CustomMutationClasses,
8
+ TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
9
+ > extends Mutation<
10
+ StringLiteral | NumericLiteral | BooleanLiteral,
11
+ TCustomMutations,
12
+ TOptions,
13
+ TEngine
14
+ > {
15
+ readonly kind = "Literal";
16
+
17
+ constructor(
18
+ engine: TEngine,
19
+ sourceType: StringLiteral | NumericLiteral | BooleanLiteral,
20
+ referenceTypes: MemberType[] = [],
21
+ options: TOptions,
22
+ ) {
23
+ super(engine, sourceType, referenceTypes, options);
24
+ }
25
+
26
+ mutate() {
27
+ // No mutations needed for literal types
28
+ }
29
+ }
@@ -0,0 +1,35 @@
1
+ import type { ModelProperty, Type } from "@typespec/compiler";
2
+ import type { MutationSubgraph } from "../mutation-node/mutation-subgraph.js";
3
+ import type {
4
+ CustomMutationClasses,
5
+ MutationEngine,
6
+ MutationFor,
7
+ MutationOptions,
8
+ } from "./mutation-engine.js";
9
+ import { Mutation } from "./mutation.js";
10
+
11
+ export class ModelPropertyMutation<
12
+ TOptions extends MutationOptions,
13
+ TCustomMutations extends CustomMutationClasses,
14
+ TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
15
+ > extends Mutation<ModelProperty, TCustomMutations, TOptions, TEngine> {
16
+ readonly kind = "ModelProperty";
17
+ type!: MutationFor<TCustomMutations, Type["kind"]>;
18
+
19
+ mutate() {
20
+ this.type = this.engine.mutateReference(this.sourceType, this.options);
21
+ }
22
+
23
+ getReferenceMutationNode(
24
+ subgraph: MutationSubgraph = this.engine.getDefaultMutationSubgraph(this.options),
25
+ ) {
26
+ return subgraph.getReferenceNode(this.sourceType);
27
+ }
28
+
29
+ replaceReferencedType(subgraph: MutationSubgraph, newType: Type) {
30
+ // First, update the mutation node
31
+ subgraph.getReferenceNode(this.sourceType).replace(newType);
32
+ // then return a new reference mutation for the new type
33
+ return this.engine.mutateReference(this.sourceType, newType, this.options);
34
+ }
35
+ }
@@ -0,0 +1,58 @@
1
+ import type { MemberType, Model, Type } from "@typespec/compiler";
2
+ import type {
3
+ CustomMutationClasses,
4
+ MutationEngine,
5
+ MutationFor,
6
+ MutationOptions,
7
+ } from "./mutation-engine.js";
8
+ import { Mutation } from "./mutation.js";
9
+
10
+ export class ModelMutation<
11
+ TOptions extends MutationOptions,
12
+ TCustomMutations extends CustomMutationClasses,
13
+ TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
14
+ > extends Mutation<Model, TCustomMutations, TOptions, TEngine> {
15
+ readonly kind = "Model";
16
+ baseModel?: MutationFor<TCustomMutations, "Model">;
17
+ properties: Map<string, MutationFor<TCustomMutations, "ModelProperty">> = new Map();
18
+ indexer?: {
19
+ key: MutationFor<TCustomMutations, "Scalar">;
20
+ value: MutationFor<TCustomMutations, Type["kind"]>;
21
+ };
22
+
23
+ constructor(
24
+ engine: TEngine,
25
+ sourceType: Model,
26
+ referenceTypes: MemberType[] = [],
27
+ options: TOptions,
28
+ ) {
29
+ super(engine, sourceType, referenceTypes, options);
30
+ }
31
+
32
+ protected mutateBaseModel() {
33
+ if (this.sourceType.baseModel) {
34
+ this.baseModel = this.engine.mutate(this.sourceType.baseModel, this.options);
35
+ }
36
+ }
37
+
38
+ protected mutateProperties() {
39
+ for (const prop of this.sourceType.properties.values()) {
40
+ this.properties.set(prop.name, this.engine.mutate(prop, this.options));
41
+ }
42
+ }
43
+
44
+ protected mutateIndexer() {
45
+ if (this.sourceType.indexer) {
46
+ this.indexer = {
47
+ key: this.engine.mutate(this.sourceType.indexer.key, this.options),
48
+ value: this.engine.mutate(this.sourceType.indexer.value, this.options),
49
+ };
50
+ }
51
+ }
52
+
53
+ mutate() {
54
+ this.mutateBaseModel();
55
+ this.mutateProperties();
56
+ this.mutateIndexer();
57
+ }
58
+ }