@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
@@ -0,0 +1,202 @@
1
+ import type { Model } from "@typespec/compiler";
2
+ import { t } from "@typespec/compiler/testing";
3
+ import { $, type Typekit } from "@typespec/compiler/typekit";
4
+ import { expect, it } from "vitest";
5
+ import { Tester } from "../../test/test-host.js";
6
+ import type { MutationSubgraph } from "../mutation-node/mutation-subgraph.js";
7
+ import { ModelPropertyMutation } from "./model-property.js";
8
+ import { ModelMutation } from "./model.js";
9
+ import { MutationEngine, MutationOptions } from "./mutation-engine.js";
10
+ import { SimpleMutationEngine } from "./simple-mutation-engine.js";
11
+
12
+ class RenameMutationOptions extends MutationOptions {
13
+ prefix: string;
14
+ suffix: string;
15
+
16
+ constructor(prefix: string, suffix: string) {
17
+ super();
18
+ this.prefix = prefix;
19
+ this.suffix = suffix;
20
+ }
21
+
22
+ cacheKey() {
23
+ return `${this.prefix}-${this.suffix}`;
24
+ }
25
+ }
26
+
27
+ class RenameMutationEngine extends MutationEngine<RenameMutationClasses> {
28
+ constructor($: Typekit) {
29
+ super($, {
30
+ Model: RenameModelMutation,
31
+ });
32
+ this.registerSubgraph("prefix");
33
+ this.registerSubgraph("suffix");
34
+ }
35
+
36
+ getPrefixSubgraph(options: RenameMutationOptions): MutationSubgraph {
37
+ return this.getMutationSubgraph(options, "prefix");
38
+ }
39
+
40
+ getSuffixSubgraph(options: RenameMutationOptions): MutationSubgraph {
41
+ return this.getMutationSubgraph(options, "suffix");
42
+ }
43
+ }
44
+
45
+ interface RenameMutationClasses {
46
+ Model: RenameModelMutation;
47
+ }
48
+
49
+ class RenameModelMutation extends ModelMutation<
50
+ RenameMutationOptions,
51
+ RenameMutationClasses,
52
+ RenameMutationEngine
53
+ > {
54
+ get #prefixSubgraph() {
55
+ return this.engine.getPrefixSubgraph(this.options);
56
+ }
57
+
58
+ get #suffixSubgraph() {
59
+ return this.engine.getSuffixSubgraph(this.options);
60
+ }
61
+
62
+ get withPrefix() {
63
+ return this.getMutatedType(this.#prefixSubgraph);
64
+ }
65
+
66
+ get withSuffix() {
67
+ return this.getMutatedType(this.#suffixSubgraph);
68
+ }
69
+
70
+ mutate() {
71
+ if ("name" in this.sourceType && typeof this.sourceType.name === "string") {
72
+ this.mutateType(
73
+ this.#prefixSubgraph,
74
+ (m) => (m.name = `${this.options.prefix}${this.sourceType.name}`),
75
+ );
76
+ this.mutateType(
77
+ this.#suffixSubgraph,
78
+ (m) => (m.name = `${this.sourceType.name}${this.options.suffix}`),
79
+ );
80
+ }
81
+
82
+ // mutate all connected types passing on the same options
83
+ super.mutate();
84
+ }
85
+ }
86
+ it("creates mutations", async () => {
87
+ const runner = await Tester.createInstance();
88
+ const { Foo, Bar, prop, program } = await runner.compile(t.code`
89
+ model ${t.model("Foo")} {
90
+ ${t.modelProperty("prop")}: Bar;
91
+ }
92
+
93
+ model ${t.model("Bar")} {
94
+ prop: string;
95
+ }
96
+ `);
97
+
98
+ const tk = $(program);
99
+ const engine = new RenameMutationEngine(tk);
100
+ const options = new RenameMutationOptions("Pre", "Suf");
101
+ const fooMutation = engine.mutate(Foo, options);
102
+
103
+ // can navigate the mutation result to get prefix and suffix names side-by-side
104
+ expect(fooMutation.properties.size).toBe(1);
105
+
106
+ const barMutation = fooMutation.properties.get("prop")!.type as RenameModelMutation;
107
+ expect(barMutation.withPrefix.name).toBe("PreBar");
108
+
109
+ // Or you could get barMutation like:
110
+ const barMutation2 = engine.mutate(Bar, options);
111
+
112
+ // but these are not the same mutation node because the mutation accessed via
113
+ // the property is a distinct from the one accessed from the scalar.
114
+ expect(barMutation === barMutation2).toBe(false);
115
+ expect(barMutation.referenceTypes.length).toEqual(1);
116
+ expect(barMutation.referenceTypes[0] === prop).toBe(true);
117
+ expect(barMutation2.referenceTypes.length).toEqual(0);
118
+
119
+ // The graph is mutated
120
+ const prefixModel = fooMutation.withPrefix;
121
+ const suffixModel = fooMutation.withSuffix;
122
+
123
+ expect(prefixModel.name).toBe("PreFoo");
124
+ expect((prefixModel.properties.get("prop")!.type as Model).name).toBe("PreBar");
125
+ expect(suffixModel.name).toBe("FooSuf");
126
+ expect((suffixModel.properties.get("prop")!.type as Model).name).toBe("BarSuf");
127
+ });
128
+
129
+ interface UnionifyMutations {
130
+ Model: UnionifyModel;
131
+ ModelProperty: UnionifyProperty;
132
+ }
133
+
134
+ class UnionifyModel extends ModelMutation<
135
+ MutationOptions,
136
+ UnionifyMutations,
137
+ SimpleMutationEngine<UnionifyMutations>
138
+ > {
139
+ get unionified() {
140
+ return this.getMutatedType();
141
+ }
142
+ }
143
+
144
+ class UnionifyProperty extends ModelPropertyMutation<
145
+ MutationOptions,
146
+ UnionifyMutations,
147
+ SimpleMutationEngine<UnionifyMutations>
148
+ > {
149
+ get unionified() {
150
+ return this.getMutatedType();
151
+ }
152
+
153
+ mutate() {
154
+ if (!this.engine.$.union.is(this.sourceType.type)) {
155
+ // turn it into this union:
156
+ const newUnionType = this.engine.$.union.create({
157
+ variants: [
158
+ this.engine.$.unionVariant.create({ type: this.sourceType.type }),
159
+ this.engine.$.unionVariant.create({
160
+ type: this.engine.$.builtin.string,
161
+ }),
162
+ ],
163
+ });
164
+
165
+ this.type = this.replaceReferencedType(
166
+ this.engine.getDefaultMutationSubgraph(this.options),
167
+ newUnionType,
168
+ );
169
+ } else {
170
+ super.mutate();
171
+ }
172
+ }
173
+ }
174
+
175
+ it("mutates model properties into unions", async () => {
176
+ const runner = await Tester.createInstance();
177
+ const { Foo, program } = await runner.compile(t.code`
178
+ model ${t.model("Foo")} {
179
+ ${t.modelProperty("prop")}: Bar;
180
+ }
181
+
182
+ model ${t.model("Bar")} {
183
+ barProp: string;
184
+ }
185
+ `);
186
+
187
+ const tk = $(program);
188
+ const engine = new SimpleMutationEngine(tk, {
189
+ ModelProperty: UnionifyProperty,
190
+ Model: UnionifyModel,
191
+ });
192
+
193
+ const fooMutation = engine.mutate(Foo);
194
+ const propMutation = fooMutation.properties.get("prop")!;
195
+ const typeMutation = propMutation.type as UnionifyModel;
196
+ expect(typeMutation.kind).toBe("Union");
197
+ const propType = propMutation.unionified;
198
+ expect(tk.union.is(propType.type)).toBe(true);
199
+
200
+ const mutatedFoo = fooMutation.unionified;
201
+ expect(tk.union.is(mutatedFoo.properties.get("prop")!.type)).toBe(true);
202
+ });
@@ -0,0 +1,288 @@
1
+ import type { MemberType, Type } from "@typespec/compiler";
2
+ import type { Typekit } from "@typespec/compiler/typekit";
3
+ import type { MutationNodeForType } from "../mutation-node/factory.js";
4
+ import { MutationSubgraph } from "../mutation-node/mutation-subgraph.js";
5
+ import { InterfaceMutation } from "./interface.js";
6
+ import { IntrinsicMutation } from "./intrinsic.js";
7
+ import { LiteralMutation } from "./literal.js";
8
+ import { ModelPropertyMutation } from "./model-property.js";
9
+ import { ModelMutation } from "./model.js";
10
+ import { Mutation } from "./mutation.js";
11
+ import { OperationMutation } from "./operation.js";
12
+ import { ScalarMutation } from "./scalar.js";
13
+ import { UnionVariantMutation } from "./union-variant.js";
14
+ import { UnionMutation } from "./union.js";
15
+
16
+ export type MutationRegistry = Record<Type["kind"], Mutation<Type, any, any>>;
17
+
18
+ export interface DefaultMutationClasses<TCustomMutations extends CustomMutationClasses>
19
+ extends MutationRegistry {
20
+ Operation: OperationMutation<MutationOptions, TCustomMutations>;
21
+ Interface: InterfaceMutation<MutationOptions, TCustomMutations>;
22
+ Model: ModelMutation<MutationOptions, TCustomMutations>;
23
+ Scalar: ScalarMutation<MutationOptions, TCustomMutations>;
24
+ ModelProperty: ModelPropertyMutation<MutationOptions, TCustomMutations>;
25
+ Union: UnionMutation<MutationOptions, TCustomMutations>;
26
+ UnionVariant: UnionVariantMutation<MutationOptions, TCustomMutations>;
27
+ String: LiteralMutation<MutationOptions, TCustomMutations>;
28
+ Number: LiteralMutation<MutationOptions, TCustomMutations>;
29
+ Boolean: LiteralMutation<MutationOptions, TCustomMutations>;
30
+ Intrinsic: IntrinsicMutation<MutationOptions, TCustomMutations>;
31
+ }
32
+
33
+ export type CustomMutationClasses = Partial<MutationRegistry>;
34
+
35
+ export type WithDefaultMutations<TCustomMutationClasses extends CustomMutationClasses> =
36
+ TCustomMutationClasses & DefaultMutationClasses<TCustomMutationClasses>;
37
+
38
+ export type MutationFor<
39
+ TCustomMutations extends CustomMutationClasses,
40
+ TKind extends Type["kind"] = Type["kind"],
41
+ > = WithDefaultMutations<TCustomMutations>[TKind];
42
+
43
+ export type Constructor<T = object> = new (...args: any[]) => T;
44
+ export type ConstructorsFor<T> = { [K in keyof T]: Constructor<T[K]> };
45
+ export type InstancesFor<T extends Record<string, new (...args: any[]) => any>> = {
46
+ [K in keyof T]: InstanceType<T[K]>;
47
+ };
48
+
49
+ export class MutationEngine<TCustomMutations extends CustomMutationClasses> {
50
+ $: Typekit;
51
+
52
+ // Map of Type -> (Map of options.cacheKey() -> Mutation)
53
+ #mutationCache = new Map<Type, Map<string, MutationFor<TCustomMutations>>>();
54
+
55
+ // Map of MemberType -> (Map of options.cacheKey() -> Mutation)
56
+ #referenceMutationCache = new Map<MemberType, Map<string, MutationFor<TCustomMutations>>>();
57
+
58
+ #subgraphNames = new Set<string>();
59
+
60
+ // Map of subgraph names -> (Map of options.cacheKey() -> MutationSubgraph)
61
+ #subgraphs = new Map<string, Map<string, MutationSubgraph>>();
62
+
63
+ #mutatorClasses: MutationRegistry;
64
+
65
+ constructor($: Typekit, mutatorClasses: ConstructorsFor<TCustomMutations>) {
66
+ this.$ = $;
67
+ this.#mutatorClasses = {
68
+ Operation: mutatorClasses.Operation ?? OperationMutation,
69
+ Interface: mutatorClasses.Interface ?? InterfaceMutation,
70
+ Model: mutatorClasses.Model ?? ModelMutation,
71
+ Scalar: mutatorClasses.Scalar ?? ScalarMutation,
72
+ ModelProperty: mutatorClasses.ModelProperty ?? ModelPropertyMutation,
73
+ Union: mutatorClasses.Union ?? UnionMutation,
74
+ UnionVariant: mutatorClasses.UnionVariant ?? UnionVariantMutation,
75
+ String: mutatorClasses.String ?? LiteralMutation,
76
+ Number: mutatorClasses.Number ?? LiteralMutation,
77
+ Boolean: mutatorClasses.Boolean ?? LiteralMutation,
78
+ Intrinsic: mutatorClasses.Intrinsic ?? IntrinsicMutation,
79
+ } as any;
80
+ }
81
+
82
+ protected registerSubgraph(name: string) {
83
+ this.#subgraphNames.add(name);
84
+ }
85
+
86
+ protected getMutationSubgraph(options: MutationOptions, name?: string) {
87
+ const optionsKey = options?.cacheKey() ?? "default";
88
+ if (!this.#subgraphs.has(optionsKey)) {
89
+ this.#subgraphs.set(optionsKey, new Map());
90
+ }
91
+ const subgraphsForOptions = this.#subgraphs.get(optionsKey)!;
92
+
93
+ name = name ?? "default";
94
+ if (!subgraphsForOptions.has(name)) {
95
+ subgraphsForOptions.set(name, new MutationSubgraph(this));
96
+ }
97
+
98
+ return subgraphsForOptions.get(name)!;
99
+ }
100
+
101
+ getDefaultMutationSubgraph(options?: MutationOptions): MutationSubgraph {
102
+ throw new Error("This mutation engine does not provide a default mutation subgraph.");
103
+ }
104
+
105
+ /**
106
+ * Retrieve the mutated type from the default mutation subgraph for the given options.
107
+ */
108
+ getMutatedType<T extends Type>(options: MutationOptions, sourceType: T): T;
109
+ /**
110
+ * Retrieve the mutated type from a specific mutation subgraph.
111
+ */
112
+ getMutatedType<T extends Type>(subgraph: MutationSubgraph, sourceType: T): T;
113
+ /**
114
+ * Retrieve the mutated type from either the default subgraph with the given
115
+ * options or a specific subgraph.
116
+ */
117
+ getMutatedType<T extends Type>(
118
+ subgraphOrOptions: MutationOptions | MutationSubgraph,
119
+ sourceType: T,
120
+ ): T;
121
+ getMutatedType<T extends Type>(
122
+ subgraphOrOptions: MutationOptions | MutationSubgraph,
123
+ sourceType: T,
124
+ ) {
125
+ if (subgraphOrOptions instanceof MutationOptions) {
126
+ return this.getMutationNode(subgraphOrOptions, sourceType).mutatedType;
127
+ }
128
+ return this.getMutationNode(subgraphOrOptions, sourceType).mutatedType;
129
+ }
130
+
131
+ /**
132
+ * Get (and potentially create) the mutation node for the provided type in the default subgraph.
133
+ */
134
+ getMutationNode<T extends Type>(options: MutationOptions, type: T): MutationNodeForType<T>;
135
+ /**
136
+ * Get (and potentially create) the mutation node for the provided type in a specific subgraph.
137
+ */
138
+ getMutationNode<T extends Type>(subgraph: MutationSubgraph, type: T): MutationNodeForType<T>;
139
+
140
+ /**
141
+ * Get (and potentially create) the mutation node for the provided type in
142
+ * either the default subgraph with the given options or a specific subgraph.
143
+ */
144
+ getMutationNode<T extends Type>(
145
+ subgraphOrOptions: MutationOptions | MutationSubgraph,
146
+ type: T,
147
+ ): MutationNodeForType<T>;
148
+ getMutationNode<T extends Type>(subgraphOrOptions: MutationOptions | MutationSubgraph, type: T) {
149
+ let subgraph: MutationSubgraph;
150
+ if (subgraphOrOptions instanceof MutationOptions) {
151
+ subgraph = this.getDefaultMutationSubgraph(subgraphOrOptions);
152
+ } else {
153
+ subgraph = subgraphOrOptions;
154
+ }
155
+ return subgraph.getNode(type);
156
+ }
157
+
158
+ mutateType<T extends Type>(
159
+ subgraphOrOptions: MutationOptions | MutationSubgraph,
160
+ type: T,
161
+ initializeMutation: (type: T) => void,
162
+ ) {
163
+ const subgraph = this.#getSubgraphFromOptions(subgraphOrOptions);
164
+ this.getMutationNode(subgraph, type).mutate(initializeMutation as (type: Type) => void);
165
+ }
166
+
167
+ #getSubgraphFromOptions(subgraphOrOptions: MutationOptions | MutationSubgraph) {
168
+ if (subgraphOrOptions instanceof MutationOptions) {
169
+ return this.getDefaultMutationSubgraph(subgraphOrOptions);
170
+ } else {
171
+ return subgraphOrOptions;
172
+ }
173
+ }
174
+
175
+ mutate<TType extends Type>(
176
+ type: TType,
177
+ options: MutationOptions = new MutationOptions(),
178
+ ): MutationFor<TCustomMutations, TType["kind"]> {
179
+ if (!this.#mutationCache.has(type)) {
180
+ this.#mutationCache.set(type, new Map<string, MutationFor<TCustomMutations, Type["kind"]>>());
181
+ }
182
+
183
+ const byType = this.#mutationCache.get(type)!;
184
+ const key = options.cacheKey();
185
+ if (byType.has(key)) {
186
+ const existing = byType.get(key)! as any;
187
+ if (!existing.isMutated) {
188
+ existing.isMutated = true;
189
+ existing.mutate();
190
+ }
191
+ return existing;
192
+ }
193
+
194
+ this.#initializeSubgraphs(type, options);
195
+
196
+ const mutatorClass = this.#mutatorClasses[type.kind];
197
+ if (!mutatorClass) {
198
+ throw new Error("No mutator registered for type kind: " + type.kind);
199
+ }
200
+
201
+ // TS doesn't like this abstract class here, but it will be a derivative
202
+ // class in practice.
203
+ const mutation = new (mutatorClass as any)(this, type, [], options);
204
+
205
+ byType.set(key, mutation);
206
+ mutation.isMutated = true;
207
+ mutation.mutate();
208
+ return mutation;
209
+ }
210
+
211
+ mutateReference<TType extends MemberType>(
212
+ memberType: TType,
213
+ referencedMutationNode: Type,
214
+ options: MutationOptions,
215
+ ): MutationFor<TCustomMutations>;
216
+ mutateReference<TType extends MemberType>(
217
+ memberType: TType,
218
+ options: MutationOptions,
219
+ ): MutationFor<TCustomMutations>;
220
+ mutateReference<TType extends MemberType>(
221
+ memberType: TType,
222
+ referencedMutationNodeOrOptions: Type | MutationOptions,
223
+ options?: MutationOptions,
224
+ ): MutationFor<TCustomMutations> {
225
+ let referencedMutationNode: Type | undefined;
226
+ let finalOptions: MutationOptions;
227
+ if (referencedMutationNodeOrOptions instanceof MutationOptions) {
228
+ finalOptions = referencedMutationNodeOrOptions;
229
+ referencedMutationNode = undefined;
230
+ } else {
231
+ referencedMutationNode = referencedMutationNodeOrOptions as Type;
232
+ finalOptions = options!;
233
+ }
234
+
235
+ if (!this.#referenceMutationCache.has(memberType)) {
236
+ this.#referenceMutationCache.set(
237
+ memberType,
238
+ new Map<string, MutationFor<TCustomMutations>>(),
239
+ );
240
+ }
241
+
242
+ const byType = this.#referenceMutationCache.get(memberType)!;
243
+ const key = finalOptions.cacheKey();
244
+ if (byType.has(key)) {
245
+ const existing = byType.get(key)! as any;
246
+ if (!existing.isMutated) {
247
+ existing.isMutated = true;
248
+ existing.mutate();
249
+ }
250
+ return existing;
251
+ }
252
+
253
+ this.#initializeSubgraphs(memberType, finalOptions);
254
+ const sources: MemberType[] = [];
255
+
256
+ let referencedType: Type = memberType;
257
+ while (referencedType.kind === "ModelProperty" || referencedType.kind === "UnionVariant") {
258
+ sources.push(referencedType);
259
+ referencedType = referencedType.type;
260
+ }
261
+
262
+ const typeToMutate = referencedMutationNode ?? referencedType;
263
+ const mutatorClass = this.#mutatorClasses[typeToMutate.kind];
264
+ if (!mutatorClass) {
265
+ throw new Error("No mutator registered for type kind: " + typeToMutate.kind);
266
+ }
267
+
268
+ const mutation = new (mutatorClass as any)(this, typeToMutate, sources, finalOptions);
269
+
270
+ byType.set(key, mutation);
271
+ mutation.isMutated = true;
272
+ mutation.mutate();
273
+ return mutation;
274
+ }
275
+
276
+ #initializeSubgraphs(root: Type, options: MutationOptions) {
277
+ for (const name of this.#subgraphNames) {
278
+ const subgraph = this.getMutationSubgraph(options, name);
279
+ subgraph.getNode(root);
280
+ }
281
+ }
282
+ }
283
+
284
+ export class MutationOptions {
285
+ cacheKey(): string {
286
+ return "";
287
+ }
288
+ }
@@ -0,0 +1,90 @@
1
+ import type { MemberType, Type } from "@typespec/compiler";
2
+ import type { MutationNodeForType } from "../mutation-node/factory.js";
3
+ import type { MutationSubgraph } from "../mutation-node/mutation-subgraph.js";
4
+ import type { CustomMutationClasses, MutationEngine, MutationOptions } from "./mutation-engine.js";
5
+
6
+ export abstract class Mutation<
7
+ TSourceType extends Type,
8
+ TCustomMutations extends CustomMutationClasses,
9
+ TOptions extends MutationOptions = MutationOptions,
10
+ TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
11
+ > {
12
+ abstract readonly kind: string;
13
+
14
+ static readonly subgraphNames: string[] = [];
15
+
16
+ engine: TEngine;
17
+ sourceType: TSourceType;
18
+ options: TOptions;
19
+ isMutated: boolean = false;
20
+ referenceTypes: MemberType[];
21
+
22
+ constructor(
23
+ engine: TEngine,
24
+ sourceType: TSourceType,
25
+ referenceTypes: MemberType[],
26
+ options: TOptions,
27
+ ) {
28
+ this.engine = engine;
29
+ this.sourceType = sourceType;
30
+ this.options = options;
31
+ this.referenceTypes = referenceTypes;
32
+ }
33
+
34
+ abstract mutate(): void;
35
+
36
+ /**
37
+ * Retrieve the mutated type for this mutation's default subgraph.
38
+ */
39
+ protected getMutatedType(): TSourceType;
40
+ /**
41
+ * Retrieve the mutated type for the provided subgraph.
42
+ */
43
+ protected getMutatedType(subgraph: MutationSubgraph): TSourceType;
44
+ protected getMutatedType(subgraphOrOptions?: MutationSubgraph | MutationOptions) {
45
+ return this.engine.getMutatedType(subgraphOrOptions ?? this.options, this.sourceType);
46
+ }
47
+
48
+ /**
49
+ * Retrieve the mutation node for this mutation's default subgraph.
50
+ */
51
+ protected getMutationNode(): MutationNodeForType<TSourceType>;
52
+ /**
53
+ * Retrieve the mutation node for the provided subgraph.
54
+ */
55
+ protected getMutationNode(subgraph: MutationSubgraph): MutationNodeForType<TSourceType>;
56
+ /**
57
+ * Retrieve the mutation node for either the default subgraph with the given
58
+ * options or a specific subgraph.
59
+ */
60
+ protected getMutationNode(
61
+ subgraphOrOptions: MutationSubgraph | MutationOptions,
62
+ ): MutationNodeForType<TSourceType>;
63
+ protected getMutationNode(subgraphOrOptions?: MutationSubgraph | MutationOptions) {
64
+ return this.engine.getMutationNode(subgraphOrOptions ?? this.options, this.sourceType);
65
+ }
66
+
67
+ /**
68
+ * Mutate this type in the default subgraph.
69
+ */
70
+ protected mutateType(initializeMutation?: (type: TSourceType) => void): void;
71
+ /**
72
+ * Mutate this type in the given subgraph
73
+ */
74
+ protected mutateType(
75
+ subgraph: MutationSubgraph,
76
+ initializeMutation?: (type: TSourceType) => void,
77
+ ): void;
78
+
79
+ protected mutateType(
80
+ subgraphOrInit?: MutationSubgraph | ((type: TSourceType) => void),
81
+ initializeMutation?: (type: TSourceType) => void,
82
+ ) {
83
+ if (typeof subgraphOrInit === "function") {
84
+ initializeMutation = subgraphOrInit;
85
+ subgraphOrInit = undefined;
86
+ }
87
+ const node = this.getMutationNode(subgraphOrInit ?? this.options);
88
+ node.mutate(initializeMutation as (type: Type) => void);
89
+ }
90
+ }
@@ -0,0 +1,40 @@
1
+ import type { MemberType, Operation, 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 OperationMutation<
11
+ TOptions extends MutationOptions,
12
+ TCustomMutations extends CustomMutationClasses,
13
+ TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
14
+ > extends Mutation<Operation, TCustomMutations, TOptions, TEngine> {
15
+ readonly kind = "Operation";
16
+ parameters!: MutationFor<TCustomMutations, "Model">;
17
+ returnType!: MutationFor<TCustomMutations, Type["kind"]>;
18
+
19
+ constructor(
20
+ engine: TEngine,
21
+ sourceType: Operation,
22
+ referenceTypes: MemberType[] = [],
23
+ options: TOptions,
24
+ ) {
25
+ super(engine, sourceType, referenceTypes, options);
26
+ }
27
+
28
+ protected mutateParameters() {
29
+ this.parameters = this.engine.mutate(this.sourceType.parameters, this.options);
30
+ }
31
+
32
+ protected mutateReturnType() {
33
+ this.returnType = this.engine.mutate(this.sourceType.returnType, this.options);
34
+ }
35
+
36
+ mutate() {
37
+ this.mutateParameters();
38
+ this.mutateReturnType();
39
+ }
40
+ }
@@ -0,0 +1,36 @@
1
+ import type { MemberType, Scalar } 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 ScalarMutation<
11
+ TOptions extends MutationOptions,
12
+ TCustomMutations extends CustomMutationClasses,
13
+ TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
14
+ > extends Mutation<Scalar, TCustomMutations, TOptions, TEngine> {
15
+ readonly kind = "Scalar";
16
+ baseScalar?: MutationFor<TCustomMutations, "Scalar">;
17
+
18
+ constructor(
19
+ engine: TEngine,
20
+ sourceType: Scalar,
21
+ referenceTypes: MemberType[] = [],
22
+ options: TOptions,
23
+ ) {
24
+ super(engine, sourceType, referenceTypes, options);
25
+ }
26
+
27
+ protected mutateBaseScalar() {
28
+ if (this.sourceType.baseScalar) {
29
+ this.baseScalar = this.engine.mutate(this.sourceType.baseScalar, this.options);
30
+ }
31
+ }
32
+
33
+ mutate() {
34
+ this.mutateBaseScalar();
35
+ }
36
+ }
@@ -0,0 +1,21 @@
1
+ import type { Typekit } from "@typespec/compiler/typekit";
2
+ import type { MutationSubgraph } from "../mutation-node/mutation-subgraph.js";
3
+ import {
4
+ type ConstructorsFor,
5
+ type CustomMutationClasses,
6
+ MutationEngine,
7
+ MutationOptions,
8
+ } from "./mutation-engine.js";
9
+
10
+ export class SimpleMutationEngine<
11
+ TCustomMutations extends CustomMutationClasses,
12
+ > extends MutationEngine<TCustomMutations> {
13
+ constructor($: Typekit, mutatorClasses: ConstructorsFor<TCustomMutations>) {
14
+ super($, mutatorClasses);
15
+ this.registerSubgraph("subgraph");
16
+ }
17
+
18
+ getDefaultMutationSubgraph(options: MutationOptions): MutationSubgraph {
19
+ return super.getMutationSubgraph(options, "subgraph");
20
+ }
21
+ }