@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,43 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ /**
8
+ * Split a command line into tokens, honoring single and double quotes.
9
+ * A quote only opens a quoted span at a token boundary (when `current` is
10
+ * empty); a quote char mid-token is kept literally. Shared by the CLI
11
+ * `chain` command and the MCP `sf_gql_raw` parser.
12
+ */
13
+ export function tokenizeCommand(input: string): string[] {
14
+ const tokens: string[] = [];
15
+ let current = "";
16
+ let inSingle = false;
17
+ let inDouble = false;
18
+
19
+ for (const ch of input) {
20
+ if (ch === "'" && !inDouble) {
21
+ if (!inSingle && current.length > 0) {
22
+ current += ch;
23
+ } else {
24
+ inSingle = !inSingle;
25
+ }
26
+ } else if (ch === '"' && !inSingle) {
27
+ if (!inDouble && current.length > 0) {
28
+ current += ch;
29
+ } else {
30
+ inDouble = !inDouble;
31
+ }
32
+ } else if (ch === " " && !inSingle && !inDouble) {
33
+ if (current) {
34
+ tokens.push(current);
35
+ current = "";
36
+ }
37
+ } else {
38
+ current += ch;
39
+ }
40
+ }
41
+ if (current) tokens.push(current);
42
+ return tokens;
43
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ /**
8
+ * Salesforce UIAPI shape facts as a public library surface.
9
+ *
10
+ * The graphiti library below this module is GraphQL-generic — it knows
11
+ * about sessions, projection nodes, schema walking, and validation, but
12
+ * not about Salesforce. The MCP server above this module composes
13
+ * intent-shaped builders (`buildList`, `buildDetail`, etc.) that bake
14
+ * in product opinions like pagination defaults and mutation return
15
+ * shapes.
16
+ *
17
+ * In between, every consumer that targets Salesforce UIAPI ends up
18
+ * needing the same handful of facts: where the connection field lives,
19
+ * how mutation results are nested, when to unwrap a value wrapper,
20
+ * what the filter / orderBy / input type names look like. This module
21
+ * collects those facts so the MCP, the CLI, the eval harness, and any
22
+ * future surface can call into one place instead of re-discovering
23
+ * each convention.
24
+ *
25
+ * What belongs here:
26
+ * - Path constructors for UIAPI's nested shapes.
27
+ * - Type-shape detection (value wrappers).
28
+ * - Naming conventions for filter / orderBy / mutation input types.
29
+ *
30
+ * What does NOT belong here:
31
+ * - Pagination defaults, "always select Id on mutations", "$id: ID!"
32
+ * conventions — those are MCP-specific opinions.
33
+ * - Spec types (`ListSpec`, `DetailSpec`, etc.) — those are MCP tool
34
+ * surface, not graphiti API.
35
+ * - Schema priming, codegen packaging, warning filtering — those
36
+ * wrap library output for the MCP's `ToolOutput` and live in MCP.
37
+ */
38
+
39
+ import { isInterfaceType, isObjectType, type GraphQLSchema } from "graphql";
40
+ import { type OperationType } from "./session.js";
41
+ import { resolvePath } from "./walker.js";
42
+
43
+ // ── Path constructors ─────────────────────────────────────────────────────────
44
+
45
+ /** `["uiapi", "query", <object>]` — the connection field for an SObject. */
46
+ export function connectionPath(object: string): string[] {
47
+ return ["uiapi", "query", object];
48
+ }
49
+
50
+ /**
51
+ * Given any UIAPI connection-field path, returns the per-record selection
52
+ * scope inside it (`[...connection, "edges", "node"]`). Works for top-level
53
+ * connections AND for nested child-relationship connections.
54
+ */
55
+ export function connectionNodePath(connection: string[]): string[] {
56
+ return [...connection, "edges", "node"];
57
+ }
58
+
59
+ /**
60
+ * `["uiapi", "query", <object>, "edges", "node"]` — the per-record selection
61
+ * scope for a top-level list query. Composition of `connectionNodePath` +
62
+ * `connectionPath` for the common case.
63
+ */
64
+ export function nodePath(object: string): string[] {
65
+ return connectionNodePath(connectionPath(object));
66
+ }
67
+
68
+ /**
69
+ * `[...connection, "pageInfo"]` — the cursor-pagination metadata path on
70
+ * any UIAPI connection field. Selecting fields here is a caller opinion;
71
+ * the path itself is a UIAPI fact.
72
+ */
73
+ export function pageInfoPath(connection: string[]): string[] {
74
+ return [...connection, "pageInfo"];
75
+ }
76
+
77
+ /** `["uiapi", <object><Op>]` — the mutation field at the root of the operation. */
78
+ export function mutationFieldPath(object: string, op: "Create" | "Update" | "Delete"): string[] {
79
+ return ["uiapi", `${object}${op}`];
80
+ }
81
+
82
+ /**
83
+ * `["uiapi", <object><Op>, "Record"]` — the per-record selection scope inside
84
+ * a mutation result. Delete returns no record, so this only applies to
85
+ * Create / Update.
86
+ */
87
+ export function mutationRecordPath(object: string, op: "Create" | "Update"): string[] {
88
+ return [...mutationFieldPath(object, op), "Record"];
89
+ }
90
+
91
+ /** `["uiapi", "aggregate", <object>]` — the aggregate root for an SObject. */
92
+ export function aggregatePath(object: string): string[] {
93
+ return ["uiapi", "aggregate", object];
94
+ }
95
+
96
+ // ── Type-shape detection ──────────────────────────────────────────────────────
97
+
98
+ /**
99
+ * Returns true when the named type is a UIAPI value wrapper — an object
100
+ * (or interface) type whose name ends in `Value` and that exposes a
101
+ * `value` field (e.g. `StringValue`, `PicklistValue`, `DateTimeValue`).
102
+ *
103
+ * Callers use this to decide whether to select `field` directly or
104
+ * `field { value }`. The check is permissive enough to catch the full
105
+ * Salesforce wrapper family without false positives on unrelated
106
+ * `*Value`-named types — those would also need a `value` field to
107
+ * satisfy the second condition.
108
+ */
109
+ export function isValueWrapperType(schema: GraphQLSchema, typeName: string): boolean {
110
+ if (!typeName.endsWith("Value")) return false;
111
+ const t = schema.getType(typeName);
112
+ if (!t) return false;
113
+ if (!isObjectType(t) && !isInterfaceType(t)) return false;
114
+ return Object.prototype.hasOwnProperty.call(t.getFields(), "value");
115
+ }
116
+
117
+ /**
118
+ * Resolves the path that should actually be selected for a single
119
+ * (non-dotted) scalar field on `basePath`. Encapsulates two UIAPI
120
+ * facts in one call:
121
+ *
122
+ * - `Id` is a real scalar — return the path as-is.
123
+ * - `Name`-shaped fields resolve to a value wrapper — append "value".
124
+ * - Other shapes (raw scalars, leaves) — return the path as-is.
125
+ *
126
+ * Returns `null` when the path can't be resolved against the schema,
127
+ * letting the caller decide whether to throw, warn, or fall back. The
128
+ * helper deliberately does NOT call `selectLeaf` on the session — it
129
+ * returns the path so callers stay in control of the projection
130
+ * (alias handling, error reporting, etc.).
131
+ *
132
+ * For dotted paths that may traverse a polymorphic union, callers
133
+ * should use `selectDottedFieldPath` from `./path-selection.js`
134
+ * instead — that helper handles the union expansion AND calls into
135
+ * the equivalent of this resolver internally.
136
+ */
137
+ export function resolveScalarSelectionPath(
138
+ schema: GraphQLSchema,
139
+ operation: OperationType,
140
+ basePath: string[],
141
+ fieldName: string,
142
+ ): string[] | null {
143
+ const fullPath = [...basePath, fieldName];
144
+ if (fieldName === "Id") return fullPath;
145
+
146
+ try {
147
+ const wr = resolvePath(schema, operation, fullPath);
148
+ if (wr.isLeaf) return fullPath;
149
+ if (isValueWrapperType(schema, wr.typeName)) return [...fullPath, "value"];
150
+ return fullPath;
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+
156
+ // ── Naming conventions ────────────────────────────────────────────────────────
157
+
158
+ /** `<Object>_Filter` — the connection field's `where` input type name. */
159
+ export function filterTypeName(object: string): string {
160
+ return `${object}_Filter`;
161
+ }
162
+
163
+ /** `<Object>_OrderBy` — the connection field's `orderBy` singleton input type name. */
164
+ export function orderByTypeName(object: string): string {
165
+ return `${object}_OrderBy`;
166
+ }
167
+
168
+ /** `<Object>CreateInput` — the create mutation's `input` argument type name. */
169
+ export function createInputTypeName(object: string): string {
170
+ return `${object}CreateInput`;
171
+ }
172
+
173
+ /** `<Object>UpdateInput` — the update mutation's `input` argument type name. */
174
+ export function updateInputTypeName(object: string): string {
175
+ return `${object}UpdateInput`;
176
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ /* eslint-disable @typescript-eslint/no-explicit-any -- graphiti traverses untyped schema/introspection JSON; see follow-up to replace with `unknown` + narrowing */
7
+
8
+ import {
9
+ type GraphQLSchema,
10
+ type GraphQLInputObjectType,
11
+ parse,
12
+ validate,
13
+ isInputObjectType,
14
+ isEnumType,
15
+ isNonNullType,
16
+ getNamedType,
17
+ } from "graphql";
18
+
19
+ // ── Full query validation ────────────────────────────────────────────────────
20
+
21
+ export interface ValidationError {
22
+ message: string;
23
+ locations?: { line: number; column: number }[];
24
+ }
25
+
26
+ /**
27
+ * Validates a complete GraphQL query string against a schema.
28
+ * Uses graphql-js's parse() + validate() for comprehensive checking.
29
+ */
30
+ export function validateQuery(schema: GraphQLSchema, queryString: string): ValidationError[] {
31
+ let document;
32
+ try {
33
+ document = parse(queryString);
34
+ } catch (err: any) {
35
+ return [{ message: `Parse error: ${err.message}` }];
36
+ }
37
+
38
+ const errors = validate(schema, document);
39
+ return errors.map((e) => ({
40
+ message: e.message,
41
+ locations: e.locations?.map((l) => ({ line: l.line, column: l.column })),
42
+ }));
43
+ }
44
+
45
+ // ── Input type validation ────────────────────────────────────────────────────
46
+
47
+ export interface InputValidationError {
48
+ path: string;
49
+ message: string;
50
+ }
51
+
52
+ /**
53
+ * Validates a JSON value against a named input type from the schema.
54
+ * Recursively checks field names, nested types, and basic type compatibility.
55
+ */
56
+ export function validateInputValue(
57
+ schema: GraphQLSchema,
58
+ typeName: string,
59
+ value: unknown,
60
+ ): InputValidationError[] {
61
+ const type = schema.getType(typeName);
62
+ if (!type) {
63
+ return [{ path: "", message: `Type "${typeName}" not found in schema` }];
64
+ }
65
+
66
+ if (!isInputObjectType(type)) {
67
+ return [
68
+ {
69
+ path: "",
70
+ message: `"${typeName}" is not an input type (it's a ${type.astNode?.kind ?? "unknown"})`,
71
+ },
72
+ ];
73
+ }
74
+
75
+ const errors: InputValidationError[] = [];
76
+ validateInputObject(schema, type, value, "", errors);
77
+ return errors;
78
+ }
79
+
80
+ function validateInputObject(
81
+ schema: GraphQLSchema,
82
+ type: GraphQLInputObjectType,
83
+ value: unknown,
84
+ path: string,
85
+ errors: InputValidationError[],
86
+ ): void {
87
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
88
+ errors.push({
89
+ path: path || "(root)",
90
+ message: `Expected an object for ${type.name}, got ${typeof value}`,
91
+ });
92
+ return;
93
+ }
94
+
95
+ const fields = type.getFields();
96
+ const valueObj = value as Record<string, unknown>;
97
+
98
+ for (const key of Object.keys(valueObj)) {
99
+ if (!fields[key]) {
100
+ const available = Object.keys(fields).slice(0, 10).join(", ");
101
+ errors.push({
102
+ path: path ? `${path}.${key}` : key,
103
+ message: `Unknown field "${key}" on ${type.name}. Available: ${available}${Object.keys(fields).length > 10 ? "..." : ""}`,
104
+ });
105
+ }
106
+ }
107
+
108
+ for (const [fieldName, field] of Object.entries(fields)) {
109
+ const fieldPath = path ? `${path}.${fieldName}` : fieldName;
110
+ const fieldValue = valueObj[fieldName];
111
+
112
+ if (fieldValue === undefined) {
113
+ if (isNonNullType(field.type) && field.defaultValue === undefined) {
114
+ errors.push({ path: fieldPath, message: `Required field "${fieldName}" is missing` });
115
+ }
116
+ continue;
117
+ }
118
+
119
+ const namedType = getNamedType(field.type);
120
+ if (
121
+ namedType &&
122
+ isInputObjectType(namedType) &&
123
+ typeof fieldValue === "object" &&
124
+ fieldValue !== null
125
+ ) {
126
+ if (Array.isArray(fieldValue)) {
127
+ fieldValue.forEach((item, i) => {
128
+ validateInputObject(schema, namedType, item, `${fieldPath}[${i}]`, errors);
129
+ });
130
+ } else {
131
+ validateInputObject(schema, namedType, fieldValue, fieldPath, errors);
132
+ }
133
+ } else if (namedType && isEnumType(namedType) && typeof fieldValue === "string") {
134
+ const validValues = namedType.getValues().map((v) => v.name);
135
+ if (!validValues.includes(fieldValue)) {
136
+ errors.push({
137
+ path: fieldPath,
138
+ message: `Invalid enum value "${fieldValue}" for ${namedType.name}. Valid: ${validValues.join(", ")}`,
139
+ });
140
+ }
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ /**
8
+ * Library helpers that close two correctness gaps in any consumer
9
+ * (CLI, MCP intent layer, eval harness) that turns a typed JSON spec
10
+ * into a GraphQL operation:
11
+ *
12
+ * - `promoteVariables` finds `$varName` string leaves anywhere in
13
+ * a filter / orderBy / scope value, infers each one's GraphQL
14
+ * type from the schema, and registers it on the session so the
15
+ * rendered operation declares the variables it references.
16
+ *
17
+ * - `normalizeOrderBy` collapses an array-shaped orderBy input to
18
+ * a singleton object, matching Salesforce's connection-field
19
+ * `<Object>_OrderBy` schema. Tolerating the array shape is
20
+ * necessary for clients (and earlier MCP versions) that inferred
21
+ * a list type.
22
+ *
23
+ * Both are pure (or session-mutating but otherwise pure) and have no
24
+ * dependency on the MCP server, the CLI, or any I/O. They live next
25
+ * to `path-selection.ts` as the second piece of the "what every
26
+ * graphiti consumer needs" library surface.
27
+ */
28
+
29
+ import { type GraphQLSchema } from "graphql";
30
+ import { addVariable, type QuerySession } from "./session.js";
31
+ import { inferTypeFromArgsPath } from "../commands/query-helpers.js";
32
+
33
+ const VAR_PLACEHOLDER_RE = /^\$([A-Za-z_][\w]*)$/;
34
+
35
+ /**
36
+ * Recursively walks `value` looking for `$varName` string leaves. For
37
+ * each one, infers the GraphQL type at that input path via
38
+ * `inferTypeFromArgsPath` and calls `addVariable(session, name, type)`.
39
+ * Promoted variables strip a trailing `!` (query variables are
40
+ * nullable by convention; callers requiring NonNull use the `var`
41
+ * command directly).
42
+ *
43
+ * If type inference throws — for example because the input path
44
+ * doesn't exist on the schema — the helper falls back to `String`
45
+ * rather than failing the whole render. A wrong-type variable is
46
+ * easier for a user to debug than a hard error before they see their
47
+ * query.
48
+ *
49
+ * @param session Session to add variables to.
50
+ * @param schema The loaded GraphQL schema.
51
+ * @param fieldSchemaPath Path of the field whose argument we are
52
+ * promoting against (e.g. `["uiapi", "query",
53
+ * "Account"]`).
54
+ * @param argName The argument name (`"where"`, `"orderBy"`,
55
+ * `"scope"`).
56
+ * @param value The argument value — can be a primitive,
57
+ * array, or nested object.
58
+ * @param warnings Optional warnings sink. Strings starting with
59
+ * `$` that don't form a valid GraphQL Name
60
+ * (e.g. `$1var`, `$foo-bar`) push a warning
61
+ * here so callers see the typo instead of
62
+ * silently rendering the literal.
63
+ */
64
+ export function promoteVariables(
65
+ session: QuerySession,
66
+ schema: GraphQLSchema,
67
+ fieldSchemaPath: string[],
68
+ argName: string,
69
+ value: unknown,
70
+ warnings?: string[],
71
+ ): void {
72
+ walk(session, schema, fieldSchemaPath, argName, value, [], warnings);
73
+ }
74
+
75
+ function walk(
76
+ session: QuerySession,
77
+ schema: GraphQLSchema,
78
+ fieldSchemaPath: string[],
79
+ argName: string,
80
+ value: unknown,
81
+ pathInsideArg: string[],
82
+ warnings: string[] | undefined,
83
+ ): void {
84
+ if (typeof value === "string") {
85
+ const m = VAR_PLACEHOLDER_RE.exec(value);
86
+ if (!m || !m[1]) {
87
+ if (value.startsWith("$") && warnings) {
88
+ const argPath = [argName, ...pathInsideArg].join(".");
89
+ warnings.push(
90
+ `Variable: '${value}' at '${argPath}' starts with '$' but is not a valid variable placeholder; rendered as a literal string. Variable names must match /^[A-Za-z_][A-Za-z0-9_]*$/.`,
91
+ );
92
+ }
93
+ return;
94
+ }
95
+ const varName = m[1];
96
+ try {
97
+ const { inferredType } = inferTypeFromArgsPath(schema, session.operation, fieldSchemaPath, [
98
+ argName,
99
+ ...pathInsideArg,
100
+ ]);
101
+ const declared = inferredType.endsWith("!") ? inferredType.slice(0, -1) : inferredType;
102
+ addVariable(session, varName, declared);
103
+ } catch {
104
+ addVariable(session, varName, "String");
105
+ }
106
+ return;
107
+ }
108
+ if (Array.isArray(value)) {
109
+ for (let i = 0; i < value.length; i++) {
110
+ walk(
111
+ session,
112
+ schema,
113
+ fieldSchemaPath,
114
+ argName,
115
+ value[i],
116
+ [...pathInsideArg, String(i)],
117
+ warnings,
118
+ );
119
+ }
120
+ return;
121
+ }
122
+ if (value && typeof value === "object") {
123
+ for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
124
+ walk(session, schema, fieldSchemaPath, argName, v, [...pathInsideArg, k], warnings);
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Collapses an array-shaped orderBy input to its first element, so
131
+ * callers can pass either shape without knowing Salesforce's
132
+ * connection-field schema requires a singleton `<Object>_OrderBy`.
133
+ *
134
+ * - `[{Name: "ASC"}, ...]` → `{Name: "ASC"}`
135
+ * - `[]` → `undefined`
136
+ * - `{Name: "ASC"}` → unchanged
137
+ * - `undefined` → `undefined`
138
+ */
139
+ export function normalizeOrderBy<T>(orderBy: T | T[] | undefined): T | undefined {
140
+ if (orderBy === undefined) return undefined;
141
+ if (Array.isArray(orderBy)) {
142
+ return orderBy.length > 0 ? orderBy[0] : undefined;
143
+ }
144
+ return orderBy;
145
+ }