@salesforce/graphiti 10.10.2

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 (282) hide show
  1. package/AGENT_GUIDE.md +424 -0
  2. package/CHANGELOG.md +448 -0
  3. package/LICENSE.txt +82 -0
  4. package/README.md +204 -0
  5. package/TASK.md +249 -0
  6. package/dist/cli.d.ts +7 -0
  7. package/dist/cli.js +683 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/args.d.ts +13 -0
  10. package/dist/commands/args.js +207 -0
  11. package/dist/commands/args.js.map +1 -0
  12. package/dist/commands/build.d.ts +11 -0
  13. package/dist/commands/build.js +209 -0
  14. package/dist/commands/build.js.map +1 -0
  15. package/dist/commands/connect.d.ts +8 -0
  16. package/dist/commands/connect.js +55 -0
  17. package/dist/commands/connect.js.map +1 -0
  18. package/dist/commands/describe.d.ts +6 -0
  19. package/dist/commands/describe.js +229 -0
  20. package/dist/commands/describe.js.map +1 -0
  21. package/dist/commands/meta.d.ts +9 -0
  22. package/dist/commands/meta.js +140 -0
  23. package/dist/commands/meta.js.map +1 -0
  24. package/dist/commands/navigate.d.ts +14 -0
  25. package/dist/commands/navigate.js +105 -0
  26. package/dist/commands/navigate.js.map +1 -0
  27. package/dist/commands/query-helpers.d.ts +80 -0
  28. package/dist/commands/query-helpers.js +865 -0
  29. package/dist/commands/query-helpers.js.map +1 -0
  30. package/dist/commands/query.d.ts +26 -0
  31. package/dist/commands/query.js +901 -0
  32. package/dist/commands/query.js.map +1 -0
  33. package/dist/commands/review.d.ts +18 -0
  34. package/dist/commands/review.js +533 -0
  35. package/dist/commands/review.js.map +1 -0
  36. package/dist/commands/session-mgmt.d.ts +25 -0
  37. package/dist/commands/session-mgmt.js +206 -0
  38. package/dist/commands/session-mgmt.js.map +1 -0
  39. package/dist/commands/type.d.ts +6 -0
  40. package/dist/commands/type.js +342 -0
  41. package/dist/commands/type.js.map +1 -0
  42. package/dist/commands/validate-input.d.ts +6 -0
  43. package/dist/commands/validate-input.js +32 -0
  44. package/dist/commands/validate-input.js.map +1 -0
  45. package/dist/intent/build-aggregate.d.ts +33 -0
  46. package/dist/intent/build-aggregate.js +134 -0
  47. package/dist/intent/build-aggregate.js.map +1 -0
  48. package/dist/intent/build-create.d.ts +14 -0
  49. package/dist/intent/build-create.js +16 -0
  50. package/dist/intent/build-create.js.map +1 -0
  51. package/dist/intent/build-delete.d.ts +30 -0
  52. package/dist/intent/build-delete.js +53 -0
  53. package/dist/intent/build-delete.js.map +1 -0
  54. package/dist/intent/build-detail.d.ts +32 -0
  55. package/dist/intent/build-detail.js +80 -0
  56. package/dist/intent/build-detail.js.map +1 -0
  57. package/dist/intent/build-discover.d.ts +30 -0
  58. package/dist/intent/build-discover.js +149 -0
  59. package/dist/intent/build-discover.js.map +1 -0
  60. package/dist/intent/build-list.d.ts +28 -0
  61. package/dist/intent/build-list.js +75 -0
  62. package/dist/intent/build-list.js.map +1 -0
  63. package/dist/intent/build-mutation.d.ts +23 -0
  64. package/dist/intent/build-mutation.js +54 -0
  65. package/dist/intent/build-mutation.js.map +1 -0
  66. package/dist/intent/build-output.d.ts +27 -0
  67. package/dist/intent/build-output.js +60 -0
  68. package/dist/intent/build-output.js.map +1 -0
  69. package/dist/intent/build-raw.d.ts +23 -0
  70. package/dist/intent/build-raw.js +54 -0
  71. package/dist/intent/build-raw.js.map +1 -0
  72. package/dist/intent/build-update.d.ts +14 -0
  73. package/dist/intent/build-update.js +16 -0
  74. package/dist/intent/build-update.js.map +1 -0
  75. package/dist/intent/get-schema-with-priming.d.ts +26 -0
  76. package/dist/intent/get-schema-with-priming.js +32 -0
  77. package/dist/intent/get-schema-with-priming.js.map +1 -0
  78. package/dist/intent/select-child-relationship.d.ts +19 -0
  79. package/dist/intent/select-child-relationship.js +38 -0
  80. package/dist/intent/select-child-relationship.js.map +1 -0
  81. package/dist/intent/types.d.ts +159 -0
  82. package/dist/intent/types.js +21 -0
  83. package/dist/intent/types.js.map +1 -0
  84. package/dist/lib/apply-command.d.ts +15 -0
  85. package/dist/lib/apply-command.js +238 -0
  86. package/dist/lib/apply-command.js.map +1 -0
  87. package/dist/lib/auth.d.ts +38 -0
  88. package/dist/lib/auth.js +113 -0
  89. package/dist/lib/auth.js.map +1 -0
  90. package/dist/lib/codegen.d.ts +32 -0
  91. package/dist/lib/codegen.js +700 -0
  92. package/dist/lib/codegen.js.map +1 -0
  93. package/dist/lib/command-registry.d.ts +59 -0
  94. package/dist/lib/command-registry.js +366 -0
  95. package/dist/lib/command-registry.js.map +1 -0
  96. package/dist/lib/formatter.d.ts +76 -0
  97. package/dist/lib/formatter.js +419 -0
  98. package/dist/lib/formatter.js.map +1 -0
  99. package/dist/lib/fs-utils.d.ts +23 -0
  100. package/dist/lib/fs-utils.js +46 -0
  101. package/dist/lib/fs-utils.js.map +1 -0
  102. package/dist/lib/graphql-name.d.ts +27 -0
  103. package/dist/lib/graphql-name.js +32 -0
  104. package/dist/lib/graphql-name.js.map +1 -0
  105. package/dist/lib/interactive.d.ts +6 -0
  106. package/dist/lib/interactive.js +562 -0
  107. package/dist/lib/interactive.js.map +1 -0
  108. package/dist/lib/introspect.d.ts +40 -0
  109. package/dist/lib/introspect.js +280 -0
  110. package/dist/lib/introspect.js.map +1 -0
  111. package/dist/lib/object-info.d.ts +79 -0
  112. package/dist/lib/object-info.js +313 -0
  113. package/dist/lib/object-info.js.map +1 -0
  114. package/dist/lib/path-selection.d.ts +50 -0
  115. package/dist/lib/path-selection.js +146 -0
  116. package/dist/lib/path-selection.js.map +1 -0
  117. package/dist/lib/prime-schema.d.ts +59 -0
  118. package/dist/lib/prime-schema.js +158 -0
  119. package/dist/lib/prime-schema.js.map +1 -0
  120. package/dist/lib/query-builder.d.ts +10 -0
  121. package/dist/lib/query-builder.js +168 -0
  122. package/dist/lib/query-builder.js.map +1 -0
  123. package/dist/lib/query-commands.d.ts +19 -0
  124. package/dist/lib/query-commands.js +262 -0
  125. package/dist/lib/query-commands.js.map +1 -0
  126. package/dist/lib/session.d.ts +205 -0
  127. package/dist/lib/session.js +1075 -0
  128. package/dist/lib/session.js.map +1 -0
  129. package/dist/lib/tokenize.d.ts +12 -0
  130. package/dist/lib/tokenize.js +48 -0
  131. package/dist/lib/tokenize.js.map +1 -0
  132. package/dist/lib/uiapi.d.ts +109 -0
  133. package/dist/lib/uiapi.js +159 -0
  134. package/dist/lib/uiapi.js.map +1 -0
  135. package/dist/lib/validator.d.ts +27 -0
  136. package/dist/lib/validator.js +100 -0
  137. package/dist/lib/validator.js.map +1 -0
  138. package/dist/lib/variable-promotion.d.ts +69 -0
  139. package/dist/lib/variable-promotion.js +95 -0
  140. package/dist/lib/variable-promotion.js.map +1 -0
  141. package/dist/lib/walker.d.ts +147 -0
  142. package/dist/lib/walker.js +723 -0
  143. package/dist/lib/walker.js.map +1 -0
  144. package/dist/mcp/server.d.ts +7 -0
  145. package/dist/mcp/server.js +34 -0
  146. package/dist/mcp/server.js.map +1 -0
  147. package/dist/mcp/stdio.d.ts +7 -0
  148. package/dist/mcp/stdio.js +25 -0
  149. package/dist/mcp/stdio.js.map +1 -0
  150. package/dist/mcp/tools/echo.d.ts +7 -0
  151. package/dist/mcp/tools/echo.js +17 -0
  152. package/dist/mcp/tools/echo.js.map +1 -0
  153. package/dist/mcp/tools/sf-gql-aggregate.d.ts +11 -0
  154. package/dist/mcp/tools/sf-gql-aggregate.js +75 -0
  155. package/dist/mcp/tools/sf-gql-aggregate.js.map +1 -0
  156. package/dist/mcp/tools/sf-gql-create.d.ts +11 -0
  157. package/dist/mcp/tools/sf-gql-create.js +35 -0
  158. package/dist/mcp/tools/sf-gql-create.js.map +1 -0
  159. package/dist/mcp/tools/sf-gql-delete.d.ts +11 -0
  160. package/dist/mcp/tools/sf-gql-delete.js +31 -0
  161. package/dist/mcp/tools/sf-gql-delete.js.map +1 -0
  162. package/dist/mcp/tools/sf-gql-detail.d.ts +11 -0
  163. package/dist/mcp/tools/sf-gql-detail.js +58 -0
  164. package/dist/mcp/tools/sf-gql-detail.js.map +1 -0
  165. package/dist/mcp/tools/sf-gql-discover.d.ts +9 -0
  166. package/dist/mcp/tools/sf-gql-discover.js +51 -0
  167. package/dist/mcp/tools/sf-gql-discover.js.map +1 -0
  168. package/dist/mcp/tools/sf-gql-list.d.ts +11 -0
  169. package/dist/mcp/tools/sf-gql-list.js +53 -0
  170. package/dist/mcp/tools/sf-gql-list.js.map +1 -0
  171. package/dist/mcp/tools/sf-gql-raw.d.ts +11 -0
  172. package/dist/mcp/tools/sf-gql-raw.js +39 -0
  173. package/dist/mcp/tools/sf-gql-raw.js.map +1 -0
  174. package/dist/mcp/tools/sf-gql-update.d.ts +11 -0
  175. package/dist/mcp/tools/sf-gql-update.js +35 -0
  176. package/dist/mcp/tools/sf-gql-update.js.map +1 -0
  177. package/dist/mcp/tools/shared/zod-schemas.d.ts +49 -0
  178. package/dist/mcp/tools/shared/zod-schemas.js +46 -0
  179. package/dist/mcp/tools/shared/zod-schemas.js.map +1 -0
  180. package/package.json +36 -0
  181. package/ralph-loop.sh +120 -0
  182. package/scripts/smoke-orderby.sh +190 -0
  183. package/src/__tests__/helpers/prime-deps.ts +46 -0
  184. package/src/__tests__/helpers/schema.ts +73 -0
  185. package/src/__tests__/helpers/stdout.ts +33 -0
  186. package/src/__tests__/setup.ts +19 -0
  187. package/src/cli.ts +764 -0
  188. package/src/commands/__tests__/query.spec.ts +137 -0
  189. package/src/commands/args.ts +306 -0
  190. package/src/commands/build.ts +288 -0
  191. package/src/commands/connect.ts +60 -0
  192. package/src/commands/describe.ts +246 -0
  193. package/src/commands/meta.ts +171 -0
  194. package/src/commands/navigate.ts +134 -0
  195. package/src/commands/query-helpers.ts +1202 -0
  196. package/src/commands/query.ts +1085 -0
  197. package/src/commands/review.ts +670 -0
  198. package/src/commands/session-mgmt.ts +256 -0
  199. package/src/commands/type.ts +437 -0
  200. package/src/commands/validate-input.ts +38 -0
  201. package/src/intent/__tests__/build-aggregate.spec.ts +931 -0
  202. package/src/intent/__tests__/build-create-validation.spec.ts +135 -0
  203. package/src/intent/__tests__/build-delete.spec.ts +121 -0
  204. package/src/intent/__tests__/build-detail.spec.ts +333 -0
  205. package/src/intent/__tests__/build-discover.spec.ts +432 -0
  206. package/src/intent/__tests__/build-list.spec.ts +284 -0
  207. package/src/intent/__tests__/build-mutation.spec.ts +108 -0
  208. package/src/intent/__tests__/build-output.spec.ts +55 -0
  209. package/src/intent/__tests__/build-raw.spec.ts +153 -0
  210. package/src/intent/__tests__/build-update-validation.spec.ts +134 -0
  211. package/src/intent/build-aggregate.ts +182 -0
  212. package/src/intent/build-create.ts +19 -0
  213. package/src/intent/build-delete.ts +62 -0
  214. package/src/intent/build-detail.ts +95 -0
  215. package/src/intent/build-discover.ts +199 -0
  216. package/src/intent/build-list.ts +91 -0
  217. package/src/intent/build-mutation.ts +75 -0
  218. package/src/intent/build-output.ts +72 -0
  219. package/src/intent/build-raw.ts +61 -0
  220. package/src/intent/build-update.ts +19 -0
  221. package/src/intent/get-schema-with-priming.ts +43 -0
  222. package/src/intent/select-child-relationship.ts +48 -0
  223. package/src/intent/types.ts +181 -0
  224. package/src/lib/__tests__/apply-command.parity.spec.ts +97 -0
  225. package/src/lib/__tests__/apply-command.spec.ts +171 -0
  226. package/src/lib/__tests__/auth.spec.ts +292 -0
  227. package/src/lib/__tests__/formatter.spec.ts +86 -0
  228. package/src/lib/__tests__/graphql-name.spec.ts +64 -0
  229. package/src/lib/__tests__/introspect.spec.ts +399 -0
  230. package/src/lib/__tests__/object-info.spec.ts +124 -0
  231. package/src/lib/__tests__/path-selection.spec.ts +219 -0
  232. package/src/lib/__tests__/prime-schema.spec.ts +269 -0
  233. package/src/lib/__tests__/query-builder.spec.ts +95 -0
  234. package/src/lib/__tests__/query-commands.spec.ts +74 -0
  235. package/src/lib/__tests__/session.spec.ts +292 -0
  236. package/src/lib/__tests__/tokenize.spec.ts +33 -0
  237. package/src/lib/__tests__/uiapi.spec.ts +192 -0
  238. package/src/lib/__tests__/variable-promotion.spec.ts +211 -0
  239. package/src/lib/__tests__/walker.spec.ts +250 -0
  240. package/src/lib/apply-command.ts +286 -0
  241. package/src/lib/auth.ts +157 -0
  242. package/src/lib/codegen.ts +899 -0
  243. package/src/lib/command-registry.ts +434 -0
  244. package/src/lib/formatter.ts +587 -0
  245. package/src/lib/fs-utils.ts +46 -0
  246. package/src/lib/graphql-name.ts +35 -0
  247. package/src/lib/interactive.ts +634 -0
  248. package/src/lib/introspect.ts +320 -0
  249. package/src/lib/object-info.ts +409 -0
  250. package/src/lib/path-selection.ts +162 -0
  251. package/src/lib/prime-schema.ts +195 -0
  252. package/src/lib/query-builder.ts +193 -0
  253. package/src/lib/query-commands.ts +290 -0
  254. package/src/lib/session.ts +1304 -0
  255. package/src/lib/tokenize.ts +43 -0
  256. package/src/lib/uiapi.ts +176 -0
  257. package/src/lib/validator.ts +143 -0
  258. package/src/lib/variable-promotion.ts +145 -0
  259. package/src/lib/walker.ts +975 -0
  260. package/src/mcp/__tests__/server.spec.ts +155 -0
  261. package/src/mcp/server.ts +38 -0
  262. package/src/mcp/stdio.ts +29 -0
  263. package/src/mcp/tools/__tests__/sf-gql-aggregate.spec.ts +173 -0
  264. package/src/mcp/tools/__tests__/sf-gql-create.spec.ts +235 -0
  265. package/src/mcp/tools/__tests__/sf-gql-delete.spec.ts +194 -0
  266. package/src/mcp/tools/__tests__/sf-gql-detail.spec.ts +246 -0
  267. package/src/mcp/tools/__tests__/sf-gql-discover.spec.ts +320 -0
  268. package/src/mcp/tools/__tests__/sf-gql-list.spec.ts +128 -0
  269. package/src/mcp/tools/__tests__/sf-gql-raw.spec.ts +141 -0
  270. package/src/mcp/tools/__tests__/sf-gql-update.spec.ts +207 -0
  271. package/src/mcp/tools/echo.ts +24 -0
  272. package/src/mcp/tools/sf-gql-aggregate.ts +102 -0
  273. package/src/mcp/tools/sf-gql-create.ts +55 -0
  274. package/src/mcp/tools/sf-gql-delete.ts +49 -0
  275. package/src/mcp/tools/sf-gql-detail.ts +85 -0
  276. package/src/mcp/tools/sf-gql-discover.ts +67 -0
  277. package/src/mcp/tools/sf-gql-list.ts +73 -0
  278. package/src/mcp/tools/sf-gql-raw.ts +56 -0
  279. package/src/mcp/tools/sf-gql-update.ts +55 -0
  280. package/src/mcp/tools/shared/zod-schemas.ts +55 -0
  281. package/tsconfig.json +18 -0
  282. package/vitest.config.ts +14 -0
@@ -0,0 +1,700 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import { isEnumType, isScalarType, isInputObjectType, isListType, isNonNullType, getNamedType, } from "graphql";
7
+ import { getCachedObjectInfo, getRequiredCreateFields } from "./object-info.js";
8
+ import { getChildren } from "./session.js";
9
+ import { resolvePath, getRawFieldType, getFieldDescription } from "./walker.js";
10
+ /**
11
+ * Collects all SObject names referenced in a session's projection nodes.
12
+ * Includes primary SObjects (from query/mutation paths) and parent/child
13
+ * relationship SObjects detected via schema type resolution.
14
+ * Used to pre-warm the ObjectInfo cache before generating types.
15
+ */
16
+ export function collectSessionSObjects(session, schema) {
17
+ const sObjects = new Set();
18
+ for (const node of session.nodes) {
19
+ if (node.kind !== "field")
20
+ continue;
21
+ const fieldNode = node;
22
+ const name = detectSObjectFromPath(fieldNode.schemaPath);
23
+ if (name)
24
+ sObjects.add(name);
25
+ // Also resolve the node's schema path to detect relationship SObjects
26
+ try {
27
+ const wr = resolvePath(schema, session.operation, fieldNode.schemaPath);
28
+ // If the resolved type looks like an SObject name (PascalCase, no Connection/Edge suffix)
29
+ if (wr.typeName &&
30
+ !wr.typeName.endsWith("Connection") &&
31
+ !wr.typeName.endsWith("Edge") &&
32
+ !wr.typeName.endsWith("Value") &&
33
+ !wr.isLeaf &&
34
+ /^[A-Z]/.test(wr.typeName)) {
35
+ sObjects.add(wr.typeName);
36
+ }
37
+ }
38
+ catch {
39
+ /* ignore resolution failures */
40
+ }
41
+ }
42
+ return sObjects;
43
+ }
44
+ const OUTPUT_SCALAR_MAP = {
45
+ String: "string",
46
+ ID: "string",
47
+ Int: "number",
48
+ Float: "number",
49
+ Boolean: "boolean",
50
+ Picklist: "string",
51
+ MultiPicklist: "string",
52
+ Currency: "number",
53
+ Date: "string",
54
+ DateTime: "string",
55
+ Time: "string",
56
+ Double: "number",
57
+ Percent: "number",
58
+ Email: "string",
59
+ PhoneNumber: "string",
60
+ Url: "string",
61
+ TextArea: "string",
62
+ LongTextArea: "string",
63
+ RichTextArea: "string",
64
+ EncryptedString: "string",
65
+ Base64: "string",
66
+ IdOrRef: "string",
67
+ JSON: "unknown",
68
+ Long: "number",
69
+ BigInteger: "number",
70
+ BigDecimal: "number",
71
+ Short: "number",
72
+ Byte: "number",
73
+ Char: "number",
74
+ Latitude: "number",
75
+ Longitude: "number",
76
+ };
77
+ const INPUT_SCALAR_MAP = {
78
+ ...OUTPUT_SCALAR_MAP,
79
+ Currency: "number | string",
80
+ BigDecimal: "number | string",
81
+ Double: "number | string",
82
+ Percent: "number | string",
83
+ Longitude: "number | string",
84
+ Latitude: "number | string",
85
+ };
86
+ /**
87
+ * Supported target languages for `codegen`.
88
+ * Currently only `typescript` is implemented; add new entries here when
89
+ * introducing additional emitters.
90
+ */
91
+ export const SUPPORTED_CODEGEN_LANGUAGES = ["typescript"];
92
+ /**
93
+ * Accepts either the canonical language name (`typescript`) or a common
94
+ * alias (e.g. `ts`), normalising to a `CodegenLanguage`. Returns `null`
95
+ * when the input does not match a supported language.
96
+ */
97
+ export function normalizeCodegenLanguage(value) {
98
+ const v = value.trim().toLowerCase();
99
+ if (v === "typescript" || v === "ts")
100
+ return "typescript";
101
+ return null;
102
+ }
103
+ export function generateTypes(session, schema, options = {}) {
104
+ const language = options.language ?? "typescript";
105
+ if (language !== "typescript") {
106
+ throw new Error(`Unsupported codegen language: "${language}". Supported languages: ${SUPPORTED_CODEGEN_LANGUAGES.join(", ")}.`);
107
+ }
108
+ const typeName = options.typeName;
109
+ const rootChildren = getChildren(session, null);
110
+ if (rootChildren.length === 0) {
111
+ return `// No fields selected in this session.\n`;
112
+ }
113
+ const picklists = [];
114
+ const resultType = buildNodeType(session, schema, null, 1, session.operation, [], picklists);
115
+ const name = typeName ?? session.name ?? session.id;
116
+ const pascalName = toPascalCase(name);
117
+ const parts = [];
118
+ // Collect input type declarations first (may add picklist types)
119
+ const inputTypeDecls = new Map();
120
+ if (session.variables.length > 0) {
121
+ for (const v of session.variables) {
122
+ collectInputTypeDeclarations(schema, v.type, inputTypeDecls, 0, session, picklists);
123
+ }
124
+ }
125
+ // Picklist type aliases (emitted after input collection so mutation input picklists are included)
126
+ for (const pl of picklists) {
127
+ parts.push(`/** ${pl.objectName}.${pl.fieldLabel} picklist values */`);
128
+ const union = pl.values.map((v) => `"${v.replace(/"/g, '\\"')}"`).join(" | ");
129
+ parts.push(`type ${pl.typeName} = ${union};`);
130
+ parts.push("");
131
+ }
132
+ // Input type declarations
133
+ if (session.variables.length > 0) {
134
+ for (const [, code] of inputTypeDecls) {
135
+ if (code) {
136
+ parts.push(code);
137
+ parts.push("");
138
+ }
139
+ }
140
+ }
141
+ // Variables interface
142
+ if (session.variables.length > 0) {
143
+ parts.push(`export interface ${pascalName}Variables {`);
144
+ for (const v of session.variables) {
145
+ const tsType = variableToTsType(v, session, schema, picklists, inputTypeDecls);
146
+ parts.push(` ${v.name}${v.type.endsWith("!") ? "" : "?"}: ${tsType};`);
147
+ }
148
+ parts.push("}");
149
+ parts.push("");
150
+ }
151
+ // Result interface
152
+ parts.push(`export interface ${pascalName}Result {`);
153
+ parts.push(resultType);
154
+ parts.push("}");
155
+ return parts.join("\n") + "\n";
156
+ }
157
+ function buildNodeType(session, schema, parentId, depth, operation, currentSchemaPath, picklists) {
158
+ const _indent = " ".repeat(depth);
159
+ const children = getChildren(session, parentId);
160
+ const lines = [];
161
+ for (const child of children) {
162
+ if (child.kind === "field") {
163
+ const fieldLine = buildFieldType(session, schema, child, depth, operation, currentSchemaPath, picklists);
164
+ lines.push(fieldLine);
165
+ }
166
+ else if (child.kind === "fragment") {
167
+ const fragLine = buildFragmentType(session, schema, child, depth, operation, currentSchemaPath, picklists);
168
+ lines.push(fragLine);
169
+ }
170
+ }
171
+ return lines.join("\n");
172
+ }
173
+ function buildFieldType(session, schema, node, depth, operation, parentSchemaPath, picklists) {
174
+ const indent = " ".repeat(depth);
175
+ const fieldSchemaPath = [...parentSchemaPath, node.fieldName];
176
+ const displayName = node.alias ?? node.fieldName;
177
+ const subChildren = getChildren(session, node.id);
178
+ const hasOptional = node.directives.some((d) => d.name === "optional");
179
+ const optionalMarker = hasOptional ? "?" : "";
180
+ const undefinedSuffix = hasOptional ? " | undefined" : "";
181
+ // Resolve the type of this field
182
+ let wr = null;
183
+ try {
184
+ wr = resolvePath(schema, operation, fieldSchemaPath);
185
+ }
186
+ catch {
187
+ return `${indent}${displayName}${optionalMarker}: unknown${undefinedSuffix};`;
188
+ }
189
+ // Get the raw field type from the schema to determine nullability and list-ness
190
+ const rawType = getRawFieldType(schema, operation, parentSchemaPath, node.fieldName);
191
+ const { isList: schemaIsList } = unwrapTypeInfo(rawType);
192
+ let { nullable: schemaNullable } = unwrapTypeInfo(rawType);
193
+ // UIAPI connection edges never contain null nodes — the API returns fewer edges
194
+ // rather than edges with null nodes. Override the schema nullability for `node`
195
+ // fields inside `edges` arrays to avoid unnecessary null checks in generated types.
196
+ if (node.fieldName === "node" &&
197
+ parentSchemaPath.length > 0 &&
198
+ parentSchemaPath[parentSchemaPath.length - 1] === "edges") {
199
+ schemaNullable = false;
200
+ }
201
+ // UIAPI always returns an `edges` array (possibly empty), never null.
202
+ // Override the schema nullability for `edges` fields inside connection types.
203
+ if (node.fieldName === "edges" && schemaIsList) {
204
+ schemaNullable = false;
205
+ }
206
+ const fieldDesc = getFieldDescription(schema, operation, parentSchemaPath, node.fieldName);
207
+ const jsdocPrefix = fieldDesc ? `${indent}/** ${fieldDesc} */\n` : "";
208
+ if (wr.isLeaf) {
209
+ let tsType;
210
+ if (isEnumType(wr.type)) {
211
+ tsType = wr.type
212
+ .getValues()
213
+ .map((v) => `"${v.name}"`)
214
+ .join(" | ");
215
+ }
216
+ else {
217
+ tsType = scalarToTs(wr.typeName);
218
+ }
219
+ const nullSuffix = schemaNullable ? " | null" : "";
220
+ return `${jsdocPrefix}${indent}${displayName}${optionalMarker}: ${tsType}${nullSuffix}${undefinedSuffix};`;
221
+ }
222
+ if (subChildren.length === 0) {
223
+ return `${jsdocPrefix}${indent}${displayName}${optionalMarker}: Record<string, unknown>${undefinedSuffix};`;
224
+ }
225
+ const nested = buildNodeType(session, schema, node.id, depth + 1, operation, fieldSchemaPath, picklists);
226
+ const enriched = tryEnrichPicklist(session, node, parentSchemaPath, wr, picklists, schema, operation);
227
+ if (enriched) {
228
+ let result = enriched.replaceAll("__INDENT__", indent).replaceAll("__NAME__", displayName);
229
+ if (hasOptional) {
230
+ result = result.replace(`${displayName}:`, `${displayName}?:`);
231
+ result = result.replace(/;$/, `${undefinedSuffix};`);
232
+ }
233
+ return `${jsdocPrefix}${result}`;
234
+ }
235
+ const nullSuffix = schemaNullable ? " | null" : "";
236
+ if (schemaIsList) {
237
+ return `${jsdocPrefix}${indent}${displayName}${optionalMarker}: Array<{\n${nested}\n${indent}}>${nullSuffix}${undefinedSuffix};`;
238
+ }
239
+ // Check if children contain inline fragments — produce discriminated union
240
+ const fragmentChildren = subChildren.filter((c) => c.kind === "fragment");
241
+ if (fragmentChildren.length > 0) {
242
+ const branches = [];
243
+ for (const frag of fragmentChildren) {
244
+ const fragLine = buildFragmentType(session, schema, frag, depth, operation, fieldSchemaPath, picklists);
245
+ if (fragLine)
246
+ branches.push(fragLine);
247
+ }
248
+ const fieldChildren = subChildren.filter((c) => c.kind === "field");
249
+ const fieldLines = [];
250
+ for (const fc of fieldChildren) {
251
+ fieldLines.push(buildFieldType(session, schema, fc, depth + 1, operation, fieldSchemaPath, picklists));
252
+ }
253
+ const _fieldBlock = fieldLines.length > 0 ? fieldLines.join("\n") + "\n" : "";
254
+ if (branches.length === 1) {
255
+ return `${jsdocPrefix}${indent}${displayName}${optionalMarker}: ${branches[0]}${nullSuffix}${undefinedSuffix};`;
256
+ }
257
+ const unionType = branches.join(" | ");
258
+ return `${jsdocPrefix}${indent}${displayName}${optionalMarker}: ${unionType}${nullSuffix}${undefinedSuffix};`;
259
+ }
260
+ return `${jsdocPrefix}${indent}${displayName}${optionalMarker}: {\n${nested}\n${indent}}${nullSuffix}${undefinedSuffix};`;
261
+ }
262
+ function buildFragmentType(session, schema, node, depth, operation, parentSchemaPath, picklists) {
263
+ const indent = " ".repeat(depth);
264
+ const subChildren = getChildren(session, node.id);
265
+ if (subChildren.length === 0) {
266
+ return "";
267
+ }
268
+ const fragmentSchemaPath = [...parentSchemaPath, `[${node.onType}]`];
269
+ const nested = buildNodeType(session, schema, node.id, depth + 1, operation, fragmentSchemaPath, picklists);
270
+ // Return as an object block with __typename for discriminated unions (no leading indent — parent handles it)
271
+ return `{\n${indent} __typename: "${node.onType}";\n${nested}\n${indent}}`;
272
+ }
273
+ function tryEnrichPicklist(session, node, parentSchemaPath, wr, picklists, schema, operation) {
274
+ // Check if this is a PicklistValue or PicklistAggregate type
275
+ if (!wr.typeName.includes("Picklist"))
276
+ return null;
277
+ // Try to find the SObject and field name for this picklist
278
+ let sObjectName = detectSObjectFromPath(parentSchemaPath);
279
+ let objInfo = sObjectName ? getCachedObjectInfo(session.orgAlias, sObjectName) : null;
280
+ let plInfo = objInfo?.picklists.find((p) => p.apiName === node.fieldName);
281
+ // Fallback: resolve the parent schema path to get the actual type name.
282
+ // This handles parent relationships (e.g. Case→Account→Industry) and child
283
+ // relationships (e.g. Order→OrderItems→node→Status) where the root SObject
284
+ // detection returns the wrong SObject.
285
+ if ((!plInfo || plInfo.values.length === 0) && parentSchemaPath.length > 0) {
286
+ try {
287
+ const parentWr = resolvePath(schema, operation, parentSchemaPath);
288
+ const parentType = parentWr.typeName;
289
+ if (parentType && parentType !== sObjectName) {
290
+ const altInfo = getCachedObjectInfo(session.orgAlias, parentType);
291
+ if (altInfo) {
292
+ const altPl = altInfo.picklists.find((p) => p.apiName === node.fieldName);
293
+ if (altPl && altPl.values.length > 0) {
294
+ sObjectName = parentType;
295
+ objInfo = altInfo;
296
+ plInfo = altPl;
297
+ }
298
+ }
299
+ }
300
+ }
301
+ catch {
302
+ /* ignore resolution failures */
303
+ }
304
+ }
305
+ if (!sObjectName || !objInfo)
306
+ return null;
307
+ if (!plInfo || plInfo.values.length === 0)
308
+ return null;
309
+ const values = plInfo.values.map((v) => v.value).filter((v) => v !== null);
310
+ if (values.length === 0)
311
+ return null;
312
+ // Suppress single-"None" picklist unions — they're placeholder values, not meaningful constraints
313
+ if (values.length === 1 && values[0] === "None")
314
+ return null;
315
+ const picklistTypeName = `${sObjectName}${toPascalCase(node.fieldName)}`;
316
+ if (!picklists.some((p) => p.typeName === picklistTypeName)) {
317
+ picklists.push({
318
+ typeName: picklistTypeName,
319
+ values,
320
+ fieldLabel: node.fieldName,
321
+ objectName: sObjectName,
322
+ });
323
+ }
324
+ // Build the sub-fields with the enriched picklist type
325
+ const subChildren = getChildren(session, node.id);
326
+ if (subChildren.length === 0)
327
+ return null;
328
+ // `PicklistValue` and `PicklistAggregate` both pass the typeName check
329
+ // above, but their children have different shapes. Branch on the exact
330
+ // type so each child renders correctly.
331
+ //
332
+ // PicklistAggregate children (min/max/count/countDistinct/value) wrap
333
+ // the picklist value in their own object: e.g. `min: PicklistValue`,
334
+ // not `min: Picklist`. Without this branch the loop would hit the
335
+ // `unknown` fallback for every child and silently violate FR-10.2 +
336
+ // FR-10.5 (e2e Gap 4: aggregate min/max emits `string | null` instead
337
+ // of the picklist literal union).
338
+ const isAggregate = wr.typeName === "PicklistAggregate";
339
+ const lines = [];
340
+ for (const child of subChildren) {
341
+ if (child.kind !== "field")
342
+ continue;
343
+ const fc = child;
344
+ const name = fc.alias ?? fc.fieldName;
345
+ if (isAggregate) {
346
+ if (fc.fieldName === "min" || fc.fieldName === "max" || fc.fieldName === "value") {
347
+ // `value` here is the mode-style aggregation (rare). All three
348
+ // resolve to a PicklistValue wrapper around the picklist union.
349
+ lines.push(`__INDENT__ ${name}: { value: ${picklistTypeName} | null } | null;`);
350
+ }
351
+ else if (fc.fieldName === "count" || fc.fieldName === "countDistinct") {
352
+ lines.push(`__INDENT__ ${name}: { value: number | null } | null;`);
353
+ }
354
+ else {
355
+ lines.push(`__INDENT__ ${name}: unknown;`);
356
+ }
357
+ }
358
+ else {
359
+ if (fc.fieldName === "value") {
360
+ lines.push(`__INDENT__ ${name}: ${picklistTypeName} | null;`);
361
+ }
362
+ else if (fc.fieldName === "displayValue" || fc.fieldName === "label") {
363
+ lines.push(`__INDENT__ ${name}: string | null;`);
364
+ }
365
+ else {
366
+ lines.push(`__INDENT__ ${name}: unknown;`);
367
+ }
368
+ }
369
+ }
370
+ return `__INDENT____NAME__: {\n${lines.join("\n")}\n__INDENT__} | null;`;
371
+ }
372
+ function detectSObjectFromPath(schemaPath) {
373
+ const queryIdx = schemaPath.indexOf("query");
374
+ if (queryIdx !== -1 && schemaPath[queryIdx + 1]) {
375
+ return schemaPath[queryIdx + 1];
376
+ }
377
+ const aggIdx = schemaPath.indexOf("aggregate");
378
+ if (aggIdx !== -1 && schemaPath[aggIdx + 1]) {
379
+ return schemaPath[aggIdx + 1];
380
+ }
381
+ // Mutation: look for *Create/*Update patterns
382
+ for (const seg of schemaPath) {
383
+ const match = seg.match(/^(\w+?)(?:Create|Update|Delete)$/);
384
+ if (match)
385
+ return match[1];
386
+ }
387
+ return null;
388
+ }
389
+ /**
390
+ * Unwraps a GraphQLOutputType to determine nullability and list-ness.
391
+ * Falls back to nullable:true when the raw type is unavailable (e.g. root
392
+ * query fields or unresolvable paths) as a safe default.
393
+ */
394
+ function unwrapTypeInfo(rawType) {
395
+ if (!rawType)
396
+ return { nullable: true, isList: false };
397
+ const nonNull = isNonNullType(rawType);
398
+ const inner = nonNull ? rawType.ofType : rawType;
399
+ return {
400
+ nullable: !nonNull,
401
+ isList: isListType(inner) || (isNonNullType(inner) && isListType(inner.ofType)),
402
+ };
403
+ }
404
+ function scalarToTs(typeName) {
405
+ return OUTPUT_SCALAR_MAP[typeName] ?? "unknown";
406
+ }
407
+ /**
408
+ * Large INPUT_OBJECT types (entity filters with many fields) are only expanded
409
+ * at depth 0 (the variable's own type). Small types (operators, value inputs)
410
+ * are always expanded regardless of depth. This prevents a combinatorial explosion
411
+ * when filter types cross-reference other entity filters (e.g. Case_Filter →
412
+ * Account_Filter → Contact_Filter → ...).
413
+ */
414
+ const LARGE_TYPE_FIELD_THRESHOLD = 25;
415
+ const LARGE_TYPE_MAX_DEPTH = 1;
416
+ /**
417
+ * Recursively collects TypeScript type declarations for all INPUT_OBJECT and ENUM
418
+ * types transitively referenced by the given GraphQL type string.
419
+ */
420
+ function collectInputTypeDeclarations(schema, graphqlTypeString, declarations, depth = 0, session, picklists) {
421
+ const baseTypeName = graphqlTypeString.replace(/[![\]]/g, "");
422
+ if (declarations.has(baseTypeName))
423
+ return;
424
+ if (INPUT_SCALAR_MAP[baseTypeName])
425
+ return;
426
+ const schemaType = schema.getType(baseTypeName);
427
+ if (!schemaType)
428
+ return;
429
+ if (isScalarType(schemaType))
430
+ return;
431
+ if (isEnumType(schemaType)) {
432
+ const values = schemaType
433
+ .getValues()
434
+ .map((v) => `"${v.name}"`)
435
+ .join(" | ");
436
+ declarations.set(baseTypeName, `type ${baseTypeName} = ${values};`);
437
+ return;
438
+ }
439
+ if (isInputObjectType(schemaType)) {
440
+ const fields = Object.values(schemaType.getFields());
441
+ // Only depth-limit types that are BOTH large themselves AND reference other
442
+ // large INPUT_OBJECT types. This targets the entity-filter cross-reference
443
+ // pattern (Case_Filter → Account_Filter → Contact_Filter → ...) while
444
+ // always expanding small operator types (IdOperators, DateTimeOperators)
445
+ // even when they reference large utility types like JoinInput.
446
+ const isLargeType = fields.length > LARGE_TYPE_FIELD_THRESHOLD;
447
+ if (isLargeType && depth >= LARGE_TYPE_MAX_DEPTH) {
448
+ const referencesOtherLargeInputTypes = fields.some((f) => {
449
+ const ft = getNamedType(f.type);
450
+ if (!ft || ft.name === baseTypeName)
451
+ return false;
452
+ if (!isInputObjectType(ft))
453
+ return false;
454
+ return Object.keys(ft.getFields()).length > LARGE_TYPE_FIELD_THRESHOLD;
455
+ });
456
+ if (referencesOtherLargeInputTypes) {
457
+ // Emit a named type alias so references use the type name instead of
458
+ // Record<string, unknown>. The body is opaque but the name is preserved.
459
+ declarations.set(baseTypeName, `type ${baseTypeName} = Record<string, unknown>;`);
460
+ return;
461
+ }
462
+ }
463
+ // Register placeholder to break circular references (e.g. Case_Filter.and: [Case_Filter])
464
+ declarations.set(baseTypeName, "");
465
+ // Detect SObject name from representation types (e.g. CaseCreateRepresentation → Case)
466
+ const reprMatch = baseTypeName.match(/^(\w+?)(?:Create|Update)Representation$/);
467
+ const reprSObject = reprMatch ? reprMatch[1] : null;
468
+ const objInfo = reprSObject && session ? getCachedObjectInfo(session.orgAlias, reprSObject) : null;
469
+ // Detect SObject name from filter types (e.g. Case_Filter → Case)
470
+ const filterMatch = baseTypeName.match(/^(\w+?)_Filter$/);
471
+ const filterSObject = filterMatch ? filterMatch[1] : null;
472
+ const filterObjInfo = filterSObject && session ? getCachedObjectInfo(session.orgAlias, filterSObject) : null;
473
+ const lines = [];
474
+ for (const field of fields) {
475
+ const namedFieldType = getNamedType(field.type);
476
+ if (namedFieldType) {
477
+ collectInputTypeDeclarations(schema, namedFieldType.name, declarations, depth + 1, session, picklists);
478
+ }
479
+ // Enrich picklist fields in filter types with field-specific operator types
480
+ let tsType = inputTypeToTs(field.type, declarations);
481
+ if (filterObjInfo && picklists && tsType === "PicklistOperators") {
482
+ const plInfo = filterObjInfo.picklists.find((p) => p.apiName === field.name);
483
+ if (plInfo && plInfo.values.length > 0) {
484
+ const values = plInfo.values.map((v) => v.value).filter((v) => v !== null);
485
+ if (values.length > 0 && !(values.length === 1 && values[0] === "None")) {
486
+ const picklistTypeName = `${filterSObject}${toPascalCase(field.name)}`;
487
+ if (!picklists.some((p) => p.typeName === picklistTypeName)) {
488
+ picklists.push({
489
+ typeName: picklistTypeName,
490
+ values,
491
+ fieldLabel: field.name,
492
+ objectName: filterSObject,
493
+ });
494
+ }
495
+ // Generate a field-specific operator type with the picklist union
496
+ const opsTypeName = `${picklistTypeName}Operators`;
497
+ if (!declarations.has(opsTypeName)) {
498
+ const opsLines = [
499
+ ` eq?: ${picklistTypeName};`,
500
+ ` ne?: ${picklistTypeName};`,
501
+ ` in?: ${picklistTypeName}[];`,
502
+ ` nin?: ${picklistTypeName}[];`,
503
+ ` like?: string;`,
504
+ ` lt?: string;`,
505
+ ` gt?: string;`,
506
+ ` lte?: string;`,
507
+ ` gte?: string;`,
508
+ ];
509
+ declarations.set(opsTypeName, `interface ${opsTypeName} {\n${opsLines.join("\n")}\n}`);
510
+ }
511
+ tsType = opsTypeName;
512
+ }
513
+ }
514
+ }
515
+ // Enrich picklist fields in mutation representations with union types
516
+ if (objInfo && picklists && tsType === "string") {
517
+ const plInfo = objInfo.picklists.find((p) => p.apiName === field.name);
518
+ if (plInfo && plInfo.values.length > 0) {
519
+ const values = plInfo.values.map((v) => v.value).filter((v) => v !== null);
520
+ if (values.length > 0 && !(values.length === 1 && values[0] === "None")) {
521
+ const picklistTypeName = `${reprSObject}${toPascalCase(field.name)}`;
522
+ if (picklists && !picklists.some((p) => p.typeName === picklistTypeName)) {
523
+ picklists.push({
524
+ typeName: picklistTypeName,
525
+ values,
526
+ fieldLabel: field.name,
527
+ objectName: reprSObject,
528
+ });
529
+ }
530
+ tsType = picklistTypeName;
531
+ }
532
+ }
533
+ }
534
+ let optional = !isNonNullType(field.type);
535
+ // For Create representations, check ObjectInfo for required-on-create fields
536
+ if (optional && objInfo && baseTypeName.endsWith("CreateRepresentation")) {
537
+ const requiredFields = getRequiredCreateFields(objInfo);
538
+ if (requiredFields.some((f) => f.apiName === field.name)) {
539
+ optional = false;
540
+ }
541
+ }
542
+ lines.push(` ${field.name}${optional ? "?" : ""}: ${tsType};`);
543
+ }
544
+ declarations.set(baseTypeName, `interface ${baseTypeName} {\n${lines.join("\n")}\n}`);
545
+ }
546
+ }
547
+ /**
548
+ * Maps a GraphQL input type (possibly wrapped in NonNull/List) to its TypeScript
549
+ * representation string. NonNull wrappers are stripped because optionality is
550
+ * handled at the field declaration site. Types not present in the declarations
551
+ * map (skipped due to depth limits) fall back to Record<string, unknown>.
552
+ */
553
+ function inputTypeToTs(type, declarations) {
554
+ let t = type;
555
+ if (isNonNullType(t))
556
+ t = t.ofType;
557
+ if (isListType(t)) {
558
+ return `${inputTypeToTs(t.ofType, declarations)}[]`;
559
+ }
560
+ const named = getNamedType(t);
561
+ if (!named)
562
+ return "unknown";
563
+ if (INPUT_SCALAR_MAP[named.name])
564
+ return INPUT_SCALAR_MAP[named.name];
565
+ if (declarations.has(named.name))
566
+ return named.name;
567
+ if (isInputObjectType(named))
568
+ return "Record<string, unknown>";
569
+ if (isEnumType(named))
570
+ return "string";
571
+ return "unknown";
572
+ }
573
+ function variableToTsType(v, session, schema, picklists, inputDecls) {
574
+ const baseType = v.type.replace(/[![\]]/g, "");
575
+ const isList = v.type.includes("[");
576
+ const isNN = v.type.endsWith("!");
577
+ let tsType;
578
+ if (INPUT_SCALAR_MAP[baseType]) {
579
+ tsType = INPUT_SCALAR_MAP[baseType];
580
+ }
581
+ else if (inputDecls.has(baseType)) {
582
+ tsType = baseType;
583
+ }
584
+ else {
585
+ const schemaType = schema.getType(baseType);
586
+ if (schemaType && isInputObjectType(schemaType)) {
587
+ tsType = "Record<string, unknown>";
588
+ }
589
+ else {
590
+ tsType = "unknown";
591
+ }
592
+ }
593
+ // Enrich Picklist variables with union types by tracing the variable's binding
594
+ if (baseType === "Picklist" || baseType === "MultiPicklist") {
595
+ const enriched = enrichPicklistVariable(session, v.name, picklists);
596
+ if (enriched)
597
+ tsType = enriched;
598
+ }
599
+ if (isList)
600
+ tsType = `${tsType}[]`;
601
+ if (!isNN)
602
+ tsType += " | null";
603
+ return tsType;
604
+ }
605
+ /**
606
+ * Traces a Picklist variable's binding through the session's node args to find
607
+ * the SObject and field name, then returns the picklist union type alias.
608
+ * For example, $status bound to Case/@args/where/Status/eq → CaseStatus.
609
+ *
610
+ * Args are stored as flat key-value pairs on nodes. The `where` arg is a JSON
611
+ * string like `{"Status":{"eq":"$status"}}`. We search for `$varName` in the
612
+ * JSON and extract the field name from the enclosing key path.
613
+ */
614
+ function enrichPicklistVariable(session, varName, picklists) {
615
+ const varRef = `$${varName}`;
616
+ for (const node of session.nodes) {
617
+ if (node.kind !== "field")
618
+ continue;
619
+ const fieldNode = node;
620
+ for (const [argKey, argVal] of Object.entries(fieldNode.args)) {
621
+ if (!argVal.includes(varRef))
622
+ continue;
623
+ // For direct arg bindings like "after": "$after"
624
+ if (argVal === varRef && argKey !== "where")
625
+ continue;
626
+ // For JSON where clauses, parse and find the field name containing $varRef
627
+ let fieldName = null;
628
+ if (argKey === "where") {
629
+ try {
630
+ fieldName = findPicklistFieldInWhere(JSON.parse(argVal), varRef);
631
+ }
632
+ catch {
633
+ continue;
634
+ }
635
+ }
636
+ if (!fieldName)
637
+ continue;
638
+ const sObjectName = detectSObjectFromPath(fieldNode.schemaPath);
639
+ if (!sObjectName)
640
+ continue;
641
+ const objInfo = getCachedObjectInfo(session.orgAlias, sObjectName);
642
+ if (!objInfo)
643
+ continue;
644
+ const plInfo = objInfo.picklists.find((p) => p.apiName === fieldName);
645
+ if (!plInfo || plInfo.values.length === 0)
646
+ continue;
647
+ const values = plInfo.values.map((v) => v.value).filter((v) => v !== null);
648
+ if (values.length === 0)
649
+ continue;
650
+ if (values.length === 1 && values[0] === "None")
651
+ continue;
652
+ const picklistTypeName = `${sObjectName}${toPascalCase(fieldName)}`;
653
+ if (!picklists.some((p) => p.typeName === picklistTypeName)) {
654
+ picklists.push({
655
+ typeName: picklistTypeName,
656
+ values,
657
+ fieldLabel: fieldName,
658
+ objectName: sObjectName,
659
+ });
660
+ }
661
+ return picklistTypeName;
662
+ }
663
+ }
664
+ return null;
665
+ }
666
+ /**
667
+ * Recursively searches a parsed where clause object for a variable reference
668
+ * and returns the field name it's bound to.
669
+ * E.g. {"Status":{"eq":"$status"}} → "Status"
670
+ */
671
+ function findPicklistFieldInWhere(obj, varRef) {
672
+ if (typeof obj !== "object" || obj === null)
673
+ return null;
674
+ for (const [key, val] of Object.entries(obj)) {
675
+ if (typeof val === "string" && val === varRef) {
676
+ // We're at the operator level (eq/ne/in). Return null — parent will return the field name.
677
+ return null;
678
+ }
679
+ if (typeof val === "object" && val !== null) {
680
+ // Check if this value directly contains the varRef at operator level
681
+ for (const [, opVal] of Object.entries(val)) {
682
+ if (opVal === varRef)
683
+ return key;
684
+ }
685
+ // Recurse for nested and/or
686
+ const found = findPicklistFieldInWhere(val, varRef);
687
+ if (found)
688
+ return found;
689
+ }
690
+ }
691
+ return null;
692
+ }
693
+ function toPascalCase(str) {
694
+ return str
695
+ .replace(/[^a-zA-Z0-9]/g, " ")
696
+ .split(/\s+/)
697
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
698
+ .join("");
699
+ }
700
+ //# sourceMappingURL=codegen.js.map