@salesforce/graphiti 10.20.1 → 10.21.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 (83) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/commands/args.js +15 -2
  3. package/dist/commands/args.js.map +1 -1
  4. package/dist/intent/build-aggregate.js +6 -2
  5. package/dist/intent/build-aggregate.js.map +1 -1
  6. package/dist/intent/build-detail.js +3 -2
  7. package/dist/intent/build-detail.js.map +1 -1
  8. package/dist/intent/build-list.js +11 -7
  9. package/dist/intent/build-list.js.map +1 -1
  10. package/dist/intent/select-child-relationship.d.ts +1 -1
  11. package/dist/intent/select-child-relationship.js +3 -3
  12. package/dist/intent/select-child-relationship.js.map +1 -1
  13. package/dist/lib/auth.js +3 -2
  14. package/dist/lib/auth.js.map +1 -1
  15. package/dist/lib/errors.d.ts +38 -0
  16. package/dist/lib/errors.js +42 -0
  17. package/dist/lib/errors.js.map +1 -0
  18. package/dist/lib/introspect.js +4 -3
  19. package/dist/lib/introspect.js.map +1 -1
  20. package/dist/lib/prime-schema.js +13 -5
  21. package/dist/lib/prime-schema.js.map +1 -1
  22. package/dist/lib/query-builder.js +19 -6
  23. package/dist/lib/query-builder.js.map +1 -1
  24. package/dist/lib/session.d.ts +10 -1
  25. package/dist/lib/session.js +9 -2
  26. package/dist/lib/session.js.map +1 -1
  27. package/dist/lib/variable-promotion.js +7 -3
  28. package/dist/lib/variable-promotion.js.map +1 -1
  29. package/dist/lib/walker.js +8 -6
  30. package/dist/lib/walker.js.map +1 -1
  31. package/dist/mcp/tools/sf-gql-aggregate.js +2 -6
  32. package/dist/mcp/tools/sf-gql-aggregate.js.map +1 -1
  33. package/dist/mcp/tools/sf-gql-connect.js +3 -7
  34. package/dist/mcp/tools/sf-gql-connect.js.map +1 -1
  35. package/dist/mcp/tools/sf-gql-create.js +2 -6
  36. package/dist/mcp/tools/sf-gql-create.js.map +1 -1
  37. package/dist/mcp/tools/sf-gql-delete.js +2 -6
  38. package/dist/mcp/tools/sf-gql-delete.js.map +1 -1
  39. package/dist/mcp/tools/sf-gql-detail.js +2 -6
  40. package/dist/mcp/tools/sf-gql-detail.js.map +1 -1
  41. package/dist/mcp/tools/sf-gql-discover.js +2 -6
  42. package/dist/mcp/tools/sf-gql-discover.js.map +1 -1
  43. package/dist/mcp/tools/sf-gql-list.js +2 -6
  44. package/dist/mcp/tools/sf-gql-list.js.map +1 -1
  45. package/dist/mcp/tools/sf-gql-raw.js +2 -6
  46. package/dist/mcp/tools/sf-gql-raw.js.map +1 -1
  47. package/dist/mcp/tools/sf-gql-update.js +2 -6
  48. package/dist/mcp/tools/sf-gql-update.js.map +1 -1
  49. package/dist/schemas/tool-adapter.d.ts +56 -0
  50. package/dist/schemas/tool-adapter.js +129 -0
  51. package/dist/schemas/tool-adapter.js.map +1 -0
  52. package/package.json +1 -1
  53. package/src/commands/args.ts +23 -2
  54. package/src/intent/__tests__/build-aggregate.spec.ts +64 -0
  55. package/src/intent/__tests__/build-list.spec.ts +115 -2
  56. package/src/intent/build-aggregate.ts +7 -3
  57. package/src/intent/build-detail.ts +4 -2
  58. package/src/intent/build-list.ts +13 -8
  59. package/src/intent/select-child-relationship.ts +3 -2
  60. package/src/lib/__tests__/apply-command.spec.ts +17 -1
  61. package/src/lib/__tests__/query-builder.spec.ts +68 -0
  62. package/src/lib/__tests__/session.spec.ts +44 -0
  63. package/src/lib/__tests__/variable-promotion.spec.ts +58 -0
  64. package/src/lib/auth.ts +4 -2
  65. package/src/lib/errors.ts +45 -0
  66. package/src/lib/introspect.ts +6 -3
  67. package/src/lib/prime-schema.ts +12 -4
  68. package/src/lib/query-builder.ts +19 -6
  69. package/src/lib/session.ts +20 -3
  70. package/src/lib/variable-promotion.ts +9 -3
  71. package/src/lib/walker.ts +8 -6
  72. package/src/mcp/tools/__tests__/error-surface.contract.spec.ts +261 -0
  73. package/src/mcp/tools/sf-gql-aggregate.ts +2 -6
  74. package/src/mcp/tools/sf-gql-connect.ts +3 -7
  75. package/src/mcp/tools/sf-gql-create.ts +2 -6
  76. package/src/mcp/tools/sf-gql-delete.ts +2 -6
  77. package/src/mcp/tools/sf-gql-detail.ts +2 -6
  78. package/src/mcp/tools/sf-gql-discover.ts +2 -6
  79. package/src/mcp/tools/sf-gql-list.ts +2 -6
  80. package/src/mcp/tools/sf-gql-raw.ts +2 -6
  81. package/src/mcp/tools/sf-gql-update.ts +2 -6
  82. package/src/schemas/__tests__/tool-adapter.spec.ts +299 -0
  83. package/src/schemas/tool-adapter.ts +165 -0
@@ -1 +1 @@
1
- {"version":3,"file":"sf-gql-create.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-create.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAM9D,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC;AAEvC,MAAM,UAAU,uBAAuB,CACtC,MAAiB,EACjB,OAA+B,EAAE;IAEjC,MAAM,CAAC,YAAY,CAClB,eAAe,EACf;QACC,WAAW,EACV,4QAA4Q;QAC7Q,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACvD,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;SACzD,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"sf-gql-create.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-create.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAMxD,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC;AAEvC,MAAM,UAAU,uBAAuB,CACtC,MAAiB,EACjB,OAA+B,EAAE;IAEjC,MAAM,CAAC,YAAY,CAClB,eAAe,EACf;QACC,WAAW,EACV,4QAA4Q;QAC7Q,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAChE,CAAC;AACH,CAAC"}
@@ -5,16 +5,12 @@
5
5
  */
6
6
  import { buildDelete } from "../../intent/build-delete.js";
7
7
  import { DELETE_INPUT } from "../../schemas/input-schemas.js";
8
+ import { runTool } from "../../schemas/tool-adapter.js";
8
9
  const inputSchema = DELETE_INPUT.shape;
9
10
  export function registerSfGqlDeleteTool(server, opts = {}) {
10
11
  server.registerTool("sf_gql_delete", {
11
12
  description: "Build a UIAPI delete mutation against a Salesforce org. Declares a typed input variable ($input: RecordDeleteInput!) and selects Id on the deleted record. The input type is schema-wide (not <Object>-specific) and the result is Id only. Returns rendered GraphQL, declared variables, generated TypeScript types, and validation warnings.",
12
13
  inputSchema,
13
- }, async (args) => {
14
- const output = await buildDelete(args, opts.primeDeps);
15
- return {
16
- content: [{ type: "text", text: JSON.stringify(output) }],
17
- };
18
- });
14
+ }, async (args) => runTool(() => buildDelete(args, opts.primeDeps)));
19
15
  }
20
16
  //# sourceMappingURL=sf-gql-delete.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sf-gql-delete.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-delete.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAM9D,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC;AAEvC,MAAM,UAAU,uBAAuB,CACtC,MAAiB,EACjB,OAA+B,EAAE;IAEjC,MAAM,CAAC,YAAY,CAClB,eAAe,EACf;QACC,WAAW,EACV,gVAAgV;QACjV,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACvD,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;SACzD,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"sf-gql-delete.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-delete.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAMxD,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC;AAEvC,MAAM,UAAU,uBAAuB,CACtC,MAAiB,EACjB,OAA+B,EAAE;IAEjC,MAAM,CAAC,YAAY,CAClB,eAAe,EACf;QACC,WAAW,EACV,gVAAgV;QACjV,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAChE,CAAC;AACH,CAAC"}
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { buildDetail } from "../../intent/build-detail.js";
7
7
  import { DETAIL_INPUT } from "../../schemas/input-schemas.js";
8
+ import { runTool } from "../../schemas/tool-adapter.js";
8
9
  const inputSchema = DETAIL_INPUT;
9
10
  export function registerSfGqlDetailTool(server, opts = {}) {
10
11
  server.registerTool("sf_gql_detail", {
@@ -20,11 +21,6 @@ export function registerSfGqlDetailTool(server, opts = {}) {
20
21
  ' - With child relationships and a custom id variable name: { org: "ebikes", object: "Account", fields: ["Id"], idVariable: "accountId", childRelationships: [{ relationshipName: "Contacts", fields: ["LastName"], first: 5 }] }',
21
22
  ].join("\n"),
22
23
  inputSchema,
23
- }, async (args) => {
24
- const output = await buildDetail(args, opts.primeDeps);
25
- return {
26
- content: [{ type: "text", text: JSON.stringify(output) }],
27
- };
28
- });
24
+ }, async (args) => runTool(() => buildDetail(args, opts.primeDeps)));
29
25
  }
30
26
  //# sourceMappingURL=sf-gql-detail.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sf-gql-detail.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-detail.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAM9D,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,MAAM,UAAU,uBAAuB,CACtC,MAAiB,EACjB,OAA+B,EAAE;IAEjC,MAAM,CAAC,YAAY,CAClB,eAAe,EACf;QACC,WAAW,EAAE;YACZ,iGAAiG;YACjG,kKAAkK;YAClK,sGAAsG;YACtG,kFAAkF;YAClF,EAAE;YACF,WAAW;YACX,uFAAuF;YACvF,oIAAoI;YACpI,mOAAmO;SACnO,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACvD,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;SACzD,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"sf-gql-detail.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-detail.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAMxD,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,MAAM,UAAU,uBAAuB,CACtC,MAAiB,EACjB,OAA+B,EAAE;IAEjC,MAAM,CAAC,YAAY,CAClB,eAAe,EACf;QACC,WAAW,EAAE;YACZ,iGAAiG;YACjG,kKAAkK;YAClK,sGAAsG;YACtG,kFAAkF;YAClF,EAAE;YACF,WAAW;YACX,uFAAuF;YACvF,oIAAoI;YACpI,mOAAmO;SACnO,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAChE,CAAC;AACH,CAAC"}
@@ -5,16 +5,12 @@
5
5
  */
6
6
  import { buildDiscover } from "../../intent/build-discover.js";
7
7
  import { DISCOVER_INPUT } from "../../schemas/input-schemas.js";
8
+ import { runTool } from "../../schemas/tool-adapter.js";
8
9
  const inputSchema = DISCOVER_INPUT.shape;
9
10
  export function registerSfGqlDiscoverTool(server, opts = {}) {
10
11
  server.registerTool("sf_gql_discover", {
11
12
  description: "Discover Salesforce UIAPI schema metadata: list queryable SObjects, describe an object's fields/picklists/child relationships, or describe a single field. Returns mode-specific metadata (not the standard ToolOutput envelope).",
12
13
  inputSchema,
13
- }, async (args) => {
14
- const output = await buildDiscover(args, opts);
15
- return {
16
- content: [{ type: "text", text: JSON.stringify(output) }],
17
- };
18
- });
14
+ }, async (args) => runTool(() => buildDiscover(args, opts)));
19
15
  }
20
16
  //# sourceMappingURL=sf-gql-discover.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sf-gql-discover.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-discover.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,aAAa,EAAqB,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAIhE,MAAM,WAAW,GAAG,cAAc,CAAC,KAAK,CAAC;AAEzC,MAAM,UAAU,yBAAyB,CACxC,MAAiB,EACjB,OAAiC,EAAE;IAEnC,MAAM,CAAC,YAAY,CAClB,iBAAiB,EACjB;QACC,WAAW,EACV,mOAAmO;QACpO,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC/C,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;SACzD,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"sf-gql-discover.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-discover.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,aAAa,EAAqB,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAIxD,MAAM,WAAW,GAAG,cAAc,CAAC,KAAK,CAAC;AAEzC,MAAM,UAAU,yBAAyB,CACxC,MAAiB,EACjB,OAAiC,EAAE;IAEnC,MAAM,CAAC,YAAY,CAClB,iBAAiB,EACjB;QACC,WAAW,EACV,mOAAmO;QACpO,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CACxD,CAAC;AACH,CAAC"}
@@ -5,16 +5,12 @@
5
5
  */
6
6
  import { buildList } from "../../intent/build-list.js";
7
7
  import { LIST_INPUT } from "../../schemas/input-schemas.js";
8
+ import { runTool } from "../../schemas/tool-adapter.js";
8
9
  const inputSchema = LIST_INPUT.shape;
9
10
  export function registerSfGqlListTool(server, opts = {}) {
10
11
  server.registerTool("sf_gql_list", {
11
12
  description: "Build a UIAPI list query against a Salesforce org. Returns rendered GraphQL, declared variables, generated TypeScript types, and validation warnings.",
12
13
  inputSchema,
13
- }, async (args) => {
14
- const output = await buildList(args, opts.primeDeps);
15
- return {
16
- content: [{ type: "text", text: JSON.stringify(output) }],
17
- };
18
- });
14
+ }, async (args) => runTool(() => buildList(args, opts.primeDeps)));
19
15
  }
20
16
  //# sourceMappingURL=sf-gql-list.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sf-gql-list.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-list.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAM5D,MAAM,WAAW,GAAG,UAAU,CAAC,KAAK,CAAC;AAErC,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,OAA6B,EAAE;IACvF,MAAM,CAAC,YAAY,CAClB,aAAa,EACb;QACC,WAAW,EACV,uJAAuJ;QACxJ,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACrD,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;SACzD,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"sf-gql-list.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-list.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAMxD,MAAM,WAAW,GAAG,UAAU,CAAC,KAAK,CAAC;AAErC,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,OAA6B,EAAE;IACvF,MAAM,CAAC,YAAY,CAClB,aAAa,EACb;QACC,WAAW,EACV,uJAAuJ;QACxJ,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAC9D,CAAC;AACH,CAAC"}
@@ -5,16 +5,12 @@
5
5
  */
6
6
  import { buildRaw } from "../../intent/build-raw.js";
7
7
  import { RAW_INPUT } from "../../schemas/input-schemas.js";
8
+ import { runTool } from "../../schemas/tool-adapter.js";
8
9
  const inputSchema = RAW_INPUT.shape;
9
10
  export function registerSfGqlRawTool(server, opts = {}) {
10
11
  server.registerTool("sf_gql_raw", {
11
12
  description: "Low-level escape hatch: build an arbitrary UIAPI query, mutation, or aggregate from CLI-style commands (select/set/var) when the typed tools don't model the case (cross-union selections, custom mutations). Returns rendered GraphQL, declared variables, generated TypeScript types, and validation warnings. Fails fast on a bad command.",
12
13
  inputSchema,
13
- }, async (args) => {
14
- const output = await buildRaw(args, opts.primeDeps);
15
- return {
16
- content: [{ type: "text", text: JSON.stringify(output) }],
17
- };
18
- });
14
+ }, async (args) => runTool(() => buildRaw(args, opts.primeDeps)));
19
15
  }
20
16
  //# sourceMappingURL=sf-gql-raw.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sf-gql-raw.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-raw.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAErD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAM3D,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC;AAEpC,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,OAA4B,EAAE;IACrF,MAAM,CAAC,YAAY,CAClB,YAAY,EACZ;QACC,WAAW,EACV,+UAA+U;QAChV,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACpD,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;SACzD,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"sf-gql-raw.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-raw.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAErD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAMxD,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC;AAEpC,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,OAA4B,EAAE;IACrF,MAAM,CAAC,YAAY,CAClB,YAAY,EACZ;QACC,WAAW,EACV,+UAA+U;QAChV,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAC7D,CAAC;AACH,CAAC"}
@@ -5,16 +5,12 @@
5
5
  */
6
6
  import { buildUpdate } from "../../intent/build-update.js";
7
7
  import { UPDATE_INPUT } from "../../schemas/input-schemas.js";
8
+ import { runTool } from "../../schemas/tool-adapter.js";
8
9
  const inputSchema = UPDATE_INPUT.shape;
9
10
  export function registerSfGqlUpdateTool(server, opts = {}) {
10
11
  server.registerTool("sf_gql_update", {
11
12
  description: "Build a UIAPI update mutation against a Salesforce org. Declares a typed input variable ($input: <Object>UpdateInput!) and selects return fields on the updated record. Returns rendered GraphQL, declared variables, generated TypeScript types, and validation warnings.",
12
13
  inputSchema,
13
- }, async (args) => {
14
- const output = await buildUpdate(args, opts.primeDeps);
15
- return {
16
- content: [{ type: "text", text: JSON.stringify(output) }],
17
- };
18
- });
14
+ }, async (args) => runTool(() => buildUpdate(args, opts.primeDeps)));
19
15
  }
20
16
  //# sourceMappingURL=sf-gql-update.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sf-gql-update.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-update.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAM9D,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC;AAEvC,MAAM,UAAU,uBAAuB,CACtC,MAAiB,EACjB,OAA+B,EAAE;IAEjC,MAAM,CAAC,YAAY,CAClB,eAAe,EACf;QACC,WAAW,EACV,4QAA4Q;QAC7Q,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACvD,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;SACzD,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"sf-gql-update.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-update.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAMxD,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC;AAEvC,MAAM,UAAU,uBAAuB,CACtC,MAAiB,EACjB,OAA+B,EAAE;IAEjC,MAAM,CAAC,YAAY,CAClB,eAAe,EACf;QACC,WAAW,EACV,4QAA4Q;QAC7Q,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAChE,CAAC;AACH,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ /**
7
+ * Shared MCP tool adapter (W-22697673). Wraps an intent invocation so that any
8
+ * throw becomes a sanitized, category-prefixed error envelope instead of leaking
9
+ * a raw stack/file path to the MCP host. Categories (message prefix):
10
+ * - `UserInput:` — bad agent input / spec violations (FR-8.3/8.4, GraphQL Name,
11
+ * walker navigation).
12
+ * - `Auth:` — credential resolution failures.
13
+ * - `Schema:` — introspection / priming / schema-build failures.
14
+ * - `Internal:` — everything else; message + a truncated stack (the full error
15
+ * is logged to stderr, off the stdio channel).
16
+ *
17
+ * Auth/Schema/UserInput are carried by typed markers (AuthError, SchemaError,
18
+ * SchemaRefreshError, UserInputError, MutationContextError) classified by
19
+ * `instanceof`; the message-shape heuristics below are a secondary fallback for
20
+ * untyped throws. The sanitized message is returned for EVERY category — not
21
+ * just Internal — because typed Auth/Schema errors embed cache/lock paths and
22
+ * wrapped jsforce/@salesforce/core causes embed `~/.sfdx/...` paths.
23
+ */
24
+ export type ErrorCategory = "UserInput" | "Auth" | "Schema" | "Internal";
25
+ interface ToolTextResult {
26
+ [key: string]: unknown;
27
+ content: {
28
+ type: "text";
29
+ text: string;
30
+ }[];
31
+ isError?: boolean;
32
+ }
33
+ /**
34
+ * Markers substituted for redacted local paths. Exported as the single source of
35
+ * truth so tests assert against these constants instead of re-typing the literals
36
+ * (closes the drift between the sanitizer and its tests).
37
+ */
38
+ export declare const PATH_MARKERS: {
39
+ readonly schemaCache: "<schema-cache>";
40
+ readonly graphitiHome: "<graphiti-home>";
41
+ readonly home: "~";
42
+ readonly redacted: "<path>";
43
+ };
44
+ /** Classify a thrown error into a category + sanitized message text. */
45
+ export declare function classifyError(e: unknown): {
46
+ category: ErrorCategory;
47
+ text: string;
48
+ };
49
+ /**
50
+ * Run an MCP tool's intent invocation. Returns the success envelope
51
+ * (`JSON.stringify(output)`) or a category-prefixed `{ isError: true }` envelope.
52
+ * Internal (unexpected) errors are additionally logged in full to stderr, which
53
+ * is separate from the stdio JSON-RPC channel, so operators keep the real stack.
54
+ */
55
+ export declare function runTool(fn: () => Promise<unknown>): Promise<ToolTextResult>;
56
+ export {};
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import os from "node:os";
7
+ import { AuthError, SchemaError, UserInputError } from "../lib/errors.js";
8
+ import { graphitiHome, schemaDir } from "../lib/introspect.js";
9
+ import { SchemaRefreshError } from "../lib/prime-schema.js";
10
+ import { MutationContextError } from "../lib/walker.js";
11
+ // UserInput failures thrown by the intent/lib layer that aren't already carried
12
+ // by a typed error. Each alternative is anchored to (or contextualized by) the
13
+ // actual emitting message so a generic Node/library error does NOT get
14
+ // mislabeled UserInput (which would also hide it from the Internal stderr log).
15
+ // New UserInput sites should prefer throwing `UserInputError` over extending this.
16
+ const USER_INPUT_RE = /(^build[A-Z]\w*:|^sf_gql_discover |^command \d+ \(|^empty command\b|^unknown command\b|^(?:select|set|var)\b.*\brequires\b|^set: usage is|is not a valid GraphQL Name|not found on (?:type|input type|field|any member of union)|not found on "|not found in schema|Cannot select field|Cannot apply an inline fragment|Cannot navigate into|Cannot index into non-list type|^Argument "|Empty field name|not supported in v1|Invalid org alias or username)/;
17
+ // Defensive fallbacks in case a schema/auth failure ever reaches the adapter
18
+ // untyped (the typed AuthError/SchemaError markers are the primary signal).
19
+ const SCHEMA_RE = /(No cached schema|Introspection query|Schema has no |did not return a __schema|Schema priming)/;
20
+ const AUTH_RE = /(Failed to get org info|Missing accessToken or instanceUrl|sf org login)/;
21
+ /**
22
+ * Markers substituted for redacted local paths. Exported as the single source of
23
+ * truth so tests assert against these constants instead of re-typing the literals
24
+ * (closes the drift between the sanitizer and its tests).
25
+ */
26
+ export const PATH_MARKERS = {
27
+ schemaCache: "<schema-cache>",
28
+ graphitiHome: "<graphiti-home>",
29
+ home: "~",
30
+ redacted: "<path>",
31
+ };
32
+ // Unicode line separators (U+2028/U+2029) are legal in JSON strings but are NOT
33
+ // escaped by JSON.stringify; left raw in `content.text` they trip a Claude.AI 408
34
+ // timeout (MCP TS SDK #2155). Strip them from every host-visible envelope.
35
+ const LINE_SEPARATOR_RE = /[\u2028\u2029]/g;
36
+ function stripLineSeparators(s) {
37
+ return s.replace(LINE_SEPARATOR_RE, "");
38
+ }
39
+ /**
40
+ * Redact local filesystem layout from error text so the MCP host never sees the
41
+ * developer's home dir, OS username, repo checkout location, or schema-cache
42
+ * path (W-22697673 info-disclosure). Applied to every category's message.
43
+ */
44
+ function sanitizePaths(s) {
45
+ let out = stripLineSeparators(s).replace(/file:\/\//g, "");
46
+ // 1) Repo-internal: drop any absolute prefix before packages/ or node_modules/
47
+ // so a frame relativizes to `packages/graphiti/...` regardless of checkout.
48
+ out = out.replace(/(?:\/[^\s/]+)+\/(?=packages\/|node_modules\/)/g, "");
49
+ // 2) Relativize known graphiti roots to stable, non-sensitive markers. Longest
50
+ // first: schemaDir() is under graphitiHome() is (usually) under homedir().
51
+ // Relativizing against schemaDir()/graphitiHome() — not just homedir() —
52
+ // closes the leak when GRAPHITI_HOME lives outside the home dir (CI/shared mounts).
53
+ const roots = [
54
+ [schemaDir(), PATH_MARKERS.schemaCache],
55
+ [graphitiHome(), PATH_MARKERS.graphitiHome],
56
+ [os.homedir(), PATH_MARKERS.home],
57
+ ]
58
+ .filter(([root]) => root.length > 1)
59
+ .sort((a, b) => b[0].length - a[0].length);
60
+ for (const [root, marker] of roots)
61
+ out = out.split(root).join(marker);
62
+ // 3) Redact any remaining absolute path (POSIX or Windows) at a token boundary
63
+ // — /tmp, /var/folders, a foreign user's home, etc. The leading-boundary
64
+ // guard avoids mangling URLs, whose path follows a non-boundary char (":"/host).
65
+ out = out.replace(/(^|[\s("'=])(?:[A-Za-z]:)?(?:[/\\][^\s:)"',\\]+){2,}/g, (_m, pre) => `${pre}${PATH_MARKERS.redacted}`);
66
+ return out;
67
+ }
68
+ function truncatedStack(e) {
69
+ if (!(e instanceof Error) || !e.stack)
70
+ return [];
71
+ return e.stack
72
+ .split("\n")
73
+ .slice(1, 4) // first ~3 frames after the message line
74
+ .map((line) => sanitizePaths(line.trim()));
75
+ }
76
+ function categoryOf(e, message) {
77
+ if (e instanceof SchemaRefreshError || e instanceof SchemaError)
78
+ return "Schema";
79
+ if (e instanceof AuthError)
80
+ return "Auth";
81
+ if (e instanceof UserInputError || e instanceof MutationContextError)
82
+ return "UserInput";
83
+ if (USER_INPUT_RE.test(message))
84
+ return "UserInput";
85
+ if (AUTH_RE.test(message))
86
+ return "Auth";
87
+ if (SCHEMA_RE.test(message))
88
+ return "Schema";
89
+ return "Internal";
90
+ }
91
+ /** Classify a thrown error into a category + sanitized message text. */
92
+ export function classifyError(e) {
93
+ const message = e instanceof Error ? e.message : String(e);
94
+ const category = categoryOf(e, message);
95
+ const safeMessage = sanitizePaths(message);
96
+ if (category !== "Internal")
97
+ return { category, text: safeMessage };
98
+ // Internal: unexpected. Attach a truncated, path-stripped stack for the host.
99
+ const frames = truncatedStack(e);
100
+ const text = frames.length
101
+ ? `${safeMessage}\n${frames.map((f) => ` ${f}`).join("\n")}`
102
+ : safeMessage;
103
+ return { category, text };
104
+ }
105
+ /**
106
+ * Run an MCP tool's intent invocation. Returns the success envelope
107
+ * (`JSON.stringify(output)`) or a category-prefixed `{ isError: true }` envelope.
108
+ * Internal (unexpected) errors are additionally logged in full to stderr, which
109
+ * is separate from the stdio JSON-RPC channel, so operators keep the real stack.
110
+ */
111
+ export async function runTool(fn) {
112
+ try {
113
+ const output = await fn();
114
+ // Strip U+2028/2029 from the success envelope too: codegen/GraphQL output can
115
+ // carry them and JSON.stringify won't escape them (MCP TS SDK #2155).
116
+ return { content: [{ type: "text", text: stripLineSeparators(JSON.stringify(output)) }] };
117
+ }
118
+ catch (e) {
119
+ const { category, text } = classifyError(e);
120
+ if (category === "Internal") {
121
+ console.error("[graphiti-mcp] Internal tool error:", e);
122
+ }
123
+ return {
124
+ isError: true,
125
+ content: [{ type: "text", text: stripLineSeparators(`${category}: ${text}`) }],
126
+ };
127
+ }
128
+ }
129
+ //# sourceMappingURL=tool-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-adapter.js","sourceRoot":"","sources":["../../src/schemas/tool-adapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AA+BxD,gFAAgF;AAChF,+EAA+E;AAC/E,uEAAuE;AACvE,gFAAgF;AAChF,mFAAmF;AACnF,MAAM,aAAa,GAClB,8bAA8b,CAAC;AAEhc,6EAA6E;AAC7E,4EAA4E;AAC5E,MAAM,SAAS,GACd,gGAAgG,CAAC;AAClG,MAAM,OAAO,GAAG,0EAA0E,CAAC;AAE3F;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,WAAW,EAAE,gBAAgB;IAC7B,YAAY,EAAE,iBAAiB;IAC/B,IAAI,EAAE,GAAG;IACT,QAAQ,EAAE,QAAQ;CACT,CAAC;AAEX,gFAAgF;AAChF,kFAAkF;AAClF,2EAA2E;AAC3E,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAC5C,SAAS,mBAAmB,CAAC,CAAS;IACrC,OAAO,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,CAAS;IAC/B,IAAI,GAAG,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC3D,+EAA+E;IAC/E,+EAA+E;IAC/E,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,gDAAgD,EAAE,EAAE,CAAC,CAAC;IACxE,+EAA+E;IAC/E,8EAA8E;IAC9E,4EAA4E;IAC5E,uFAAuF;IACvF,MAAM,KAAK,GACV;QACC,CAAC,SAAS,EAAE,EAAE,YAAY,CAAC,WAAW,CAAC;QACvC,CAAC,YAAY,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC;QAC3C,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC;KAElC;SACC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;SACnC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK;QAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvE,+EAA+E;IAC/E,4EAA4E;IAC5E,oFAAoF;IACpF,GAAG,GAAG,GAAG,CAAC,OAAO,CAChB,uDAAuD,EACvD,CAAC,EAAE,EAAE,GAAW,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,CACrD,CAAC;IACF,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,SAAS,cAAc,CAAC,CAAU;IACjC,IAAI,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACjD,OAAO,CAAC,CAAC,KAAK;SACZ,KAAK,CAAC,IAAI,CAAC;SACX,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,yCAAyC;SACrD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,UAAU,CAAC,CAAU,EAAE,OAAe;IAC9C,IAAI,CAAC,YAAY,kBAAkB,IAAI,CAAC,YAAY,WAAW;QAAE,OAAO,QAAQ,CAAC;IACjF,IAAI,CAAC,YAAY,SAAS;QAAE,OAAO,MAAM,CAAC;IAC1C,IAAI,CAAC,YAAY,cAAc,IAAI,CAAC,YAAY,oBAAoB;QAAE,OAAO,WAAW,CAAC;IACzF,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,WAAW,CAAC;IACpD,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,MAAM,CAAC;IACzC,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC7C,OAAO,UAAU,CAAC;AACnB,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAAC,CAAU;IACvC,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACxC,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,QAAQ,KAAK,UAAU;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAEpE,8EAA8E;IAC9E,MAAM,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM;QACzB,CAAC,CAAC,GAAG,WAAW,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC7D,CAAC,CAAC,WAAW,CAAC;IACf,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAA0B;IACvD,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC;QAC1B,8EAA8E;QAC9E,sEAAsE;QACtE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAC3F,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACZ,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QAC5C,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,OAAO;YACN,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,CAAC,GAAG,QAAQ,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;SAC9E,CAAC;IACH,CAAC;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/graphiti",
3
- "version": "10.20.1",
3
+ "version": "10.21.0",
4
4
  "description": "Progressive GraphQL query builder CLI for Salesforce orgs",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "type": "module",
@@ -210,6 +210,27 @@ export function queryDefine(sessionId: string, rest: string[]): void {
210
210
  throw new CommandError("Usage: define $name <path> [default]\n e.g. define $filter @args/where");
211
211
  }
212
212
 
213
+ /**
214
+ * Declare a variable, or throw a CommandError if it collides with an existing
215
+ * declaration of a different type (first-wins). Single-sources the collision
216
+ * message for the two `define` entry points so they can't drift. The
217
+ * variable-promotion path intentionally warns instead of throwing and does not
218
+ * use this helper.
219
+ */
220
+ function addVariableOrThrow(
221
+ session: QuerySession,
222
+ cleanName: string,
223
+ type: string,
224
+ defaultValue?: string,
225
+ ): void {
226
+ const collision = addVariable(session, cleanName, type, defaultValue);
227
+ if (collision) {
228
+ throw new CommandError(
229
+ `$${cleanName} is already declared as ${collision.existingType}; cannot redefine it as ${collision.ignoredType}. Use a different variable name, or \`undo\` the earlier definition first.`,
230
+ );
231
+ }
232
+ }
233
+
213
234
  function defineFromCurrentArgsPosition(session: QuerySession, cleanName: string): void {
214
235
  const schema = getSessionSchema(session);
215
236
  const fieldSchemaPath = getArgsFieldPath(session.navigationPath);
@@ -227,7 +248,7 @@ function defineFromCurrentArgsPosition(session: QuerySession, cleanName: string)
227
248
  fieldSchemaPath,
228
249
  inputSubPath,
229
250
  );
230
- addVariable(session, cleanName, inferredType);
251
+ addVariableOrThrow(session, cleanName, inferredType);
231
252
  deepSetArg(session, fieldSchemaPath, argName, argInputPath, `$${cleanName}`);
232
253
 
233
254
  const pathDesc = inputSubPath.join(".");
@@ -296,7 +317,7 @@ export function defineAtPath(
296
317
  fieldSchemaPath,
297
318
  inputSubPath,
298
319
  );
299
- addVariable(session, cleanName, inferredType, defaultValue);
320
+ addVariableOrThrow(session, cleanName, inferredType, defaultValue);
300
321
  deepSetArg(session, fieldSchemaPath, argName, argInputPath, `$${cleanName}`);
301
322
 
302
323
  const pathDesc = inputSubPath.join(".");
@@ -795,6 +795,70 @@ describe("intent/build-aggregate", () => {
795
795
  expect(out.warnings.some((w) => w.startsWith("Variable:") && w.includes("$1var"))).toBe(true);
796
796
  });
797
797
 
798
+ // GAP 1 (W-22697670) — the same `$x` referenced under two filter fields of
799
+ // differing GraphQL types infers two different types. `addVariable` keeps
800
+ // the first-declared type (first-wins) and reports the conflict, which
801
+ // `buildAggregate` threads into `warnings`. Account_Filter.Industry is a
802
+ // PicklistOperators (String operands) and Account_Filter.AnnualRevenue is a
803
+ // DoubleOperators (Float operands), so `$x` is inferred as String then Float.
804
+ it("type collision on a reused $var keeps first type and surfaces a warning (GAP 1)", async () => {
805
+ const out = await buildAggregate(
806
+ {
807
+ org: ORG,
808
+ object: "Account",
809
+ groupBy: [],
810
+ aggregations: [{ function: "count" }],
811
+ filter: { Industry: { eq: "$x" }, AnnualRevenue: { gt: "$x" } },
812
+ },
813
+ noopPrimeDeps(),
814
+ );
815
+
816
+ // A collision warning naming $x is surfaced (not silently dropped).
817
+ expect(
818
+ out.warnings.some(
819
+ (w) =>
820
+ w.startsWith("Variable:") &&
821
+ w.includes("type collision for $x") &&
822
+ w.includes("String") &&
823
+ w.includes("Float"),
824
+ ),
825
+ ).toBe(true);
826
+
827
+ // First-wins: $x is declared exactly once, with the first-inferred type (String).
828
+ const declarations = out.query.match(/\$x\s*:/g) ?? [];
829
+ expect(declarations).toHaveLength(1);
830
+ expect(out.query).toMatch(/\$x\s*:\s*String/);
831
+ expect(out.query).not.toMatch(/\$x\s*:\s*Float/);
832
+
833
+ // Both references still render as the bare variable in the where arg.
834
+ expect(out.query).toMatch(/Industry\s*:\s*\{\s*eq\s*:\s*\$x\s*\}/s);
835
+ expect(out.query).toMatch(/AnnualRevenue\s*:\s*\{\s*gt\s*:\s*\$x\s*\}/s);
836
+ });
837
+
838
+ // C1 (W-22697670): a filter reusing the reserved cursor variable $after must keep
839
+ // its String type (first-wins) so pagination stays valid, and surface a collision
840
+ // warning — regression guard for the addVariable("after") ordering in buildAggregate.
841
+ it("a filter reusing $after keeps String (first-wins) and warns", async () => {
842
+ const out = await buildAggregate(
843
+ {
844
+ org: ORG,
845
+ object: "Account",
846
+ groupBy: [],
847
+ aggregations: [{ function: "count" }],
848
+ filter: { AnnualRevenue: { gt: "$after" } },
849
+ },
850
+ noopPrimeDeps(),
851
+ );
852
+ // $after stays String (not overwritten to the filter's Float), so pagination is valid.
853
+ expect(out.query).toMatch(/\$after\s*:\s*String/);
854
+ expect(out.query).not.toMatch(/\$after\s*:\s*Float/);
855
+ expect(
856
+ out.warnings.some(
857
+ (w) => w.startsWith("Variable:") && w.includes("type collision for $after"),
858
+ ),
859
+ ).toBe(true);
860
+ });
861
+
798
862
  // FR-10.2 + FR-10.5: picklist `min`/`max` must emit the picklist literal
799
863
  // union under each aggregator wrapper, not `string | null`. Documented as
800
864
  // e2e Gap 4 — `tryEnrichPicklist` previously bailed when it found
@@ -28,12 +28,13 @@ const SCHEMA_SDL = `
28
28
  enum Scope { MINE EVERYTHING }
29
29
  enum Order { ASC DESC }
30
30
 
31
- input Account_Filter { Industry: PicklistOperators, Name: StringOperators }
31
+ input Account_Filter { Industry: PicklistOperators, Name: StringOperators, AnnualRevenue: DoubleOperators }
32
32
  input Account_OrderBy { Name: OrderByClause, Industry: OrderByClause }
33
33
  input Case_Filter { Status: PicklistOperators, Priority: PicklistOperators }
34
34
  input Case_OrderBy { CreatedDate: OrderByClause }
35
35
  input PicklistOperators { eq: String, ne: String, in: [String!] }
36
36
  input StringOperators { eq: String, like: String }
37
+ input DoubleOperators { eq: Float, gt: Float, lt: Float }
37
38
  input OrderByClause { order: Order!, nulls: NullsOrder }
38
39
  enum NullsOrder { FIRST LAST }
39
40
 
@@ -67,7 +68,7 @@ const SCHEMA_SDL = `
67
68
 
68
69
  type ContactConnection { edges: [ContactEdge!]! }
69
70
  type ContactEdge { node: Contact! }
70
- input Contact_Filter { Title: StringOperators }
71
+ input Contact_Filter { Title: StringOperators, Rank: DoubleOperators }
71
72
  input Contact_OrderBy { LastName: OrderByClause }
72
73
 
73
74
  type Contact {
@@ -298,4 +299,116 @@ describe("intent/build-list", () => {
298
299
  await buildList({ org: ORG, object: "Case", fields: ["Id"] }, noopPrimeDeps());
299
300
  expect(spy).toHaveBeenCalledWith(ORG, "query", ORG_URL);
300
301
  });
302
+
303
+ // GAP 1 (W-22697670) — the same `$x` referenced under two filter fields of
304
+ // differing GraphQL types infers two different types. `addVariable` keeps the
305
+ // first-declared type (first-wins) and reports the conflict, which `buildList`
306
+ // now threads into `warnings`. Account_Filter.Name is a StringOperators (String
307
+ // operands) and Account_Filter.AnnualRevenue is a DoubleOperators (Float
308
+ // operands), so `$x` is inferred as String then Float.
309
+ it("type collision on a reused $var keeps first type and surfaces a warning (GAP 1)", async () => {
310
+ const out = await buildList(
311
+ {
312
+ org: ORG,
313
+ object: "Account",
314
+ fields: ["Id"],
315
+ filter: { Name: { eq: "$x" }, AnnualRevenue: { gt: "$x" } },
316
+ },
317
+ noopPrimeDeps(),
318
+ );
319
+
320
+ // A collision warning naming $x is surfaced (not silently dropped). This
321
+ // also proves buildList now threads `warnings` through promoteVariables.
322
+ expect(
323
+ out.warnings.some(
324
+ (w) =>
325
+ w.startsWith("Variable:") &&
326
+ w.includes("type collision for $x") &&
327
+ w.includes("String") &&
328
+ w.includes("Float"),
329
+ ),
330
+ ).toBe(true);
331
+
332
+ // First-wins: $x is declared exactly once, with the first-inferred type (String).
333
+ const declarations = out.query.match(/\$x\s*:/g) ?? [];
334
+ expect(declarations).toHaveLength(1);
335
+ expect(out.query).toMatch(/\$x\s*:\s*String/);
336
+ expect(out.query).not.toMatch(/\$x\s*:\s*Float/);
337
+
338
+ // Both references still render as the bare variable in the where arg.
339
+ expect(out.query).toMatch(/Name\s*:\s*\{\s*eq\s*:\s*\$x\s*\}/s);
340
+ expect(out.query).toMatch(/AnnualRevenue\s*:\s*\{\s*gt\s*:\s*\$x\s*\}/s);
341
+ });
342
+
343
+ // GAP 2 (W-22697670) — a `$`-prefixed string whose remainder is not a valid
344
+ // GraphQL Name (e.g. `$1var`) is not a real variable placeholder. It must NOT
345
+ // be promoted nor rendered as a bare `$1var`; it is quoted as a literal and a
346
+ // `Variable:` warning is surfaced so the typo is not silently swallowed.
347
+ it("invalid $-placeholder renders as a quoted literal and surfaces a warning (GAP 2)", async () => {
348
+ const out = await buildList(
349
+ {
350
+ org: ORG,
351
+ object: "Account",
352
+ fields: ["Id"],
353
+ filter: { Name: { eq: "$1var" } },
354
+ },
355
+ noopPrimeDeps(),
356
+ );
357
+
358
+ // The invalid placeholder warning is surfaced (proves the warnings sink is threaded).
359
+ expect(out.warnings.some((w) => w.startsWith("Variable:") && w.includes("$1var"))).toBe(true);
360
+
361
+ // Not promoted to a query variable.
362
+ expect(out.variables.find((v) => v.name === "1var")).toBeUndefined();
363
+ expect(out.query).not.toMatch(/\$1var\s*:/);
364
+
365
+ // Rendered as a quoted literal, not a bare variable reference.
366
+ expect(out.query).toMatch(/Name\s*:\s*\{\s*eq\s*:\s*"\$1var"\s*\}/s);
367
+ expect(out.query).not.toMatch(/eq\s*:\s*\$1var\b/);
368
+ });
369
+
370
+ // C2 (W-22697670): warnings from childRelationship filters must surface too —
371
+ // selectChildRelationship now threads the warnings sink through promoteVariables.
372
+ it("surfaces $-placeholder warnings from a childRelationship filter", async () => {
373
+ const out = await buildList(
374
+ {
375
+ org: ORG,
376
+ object: "Account",
377
+ fields: ["Id"],
378
+ childRelationships: [
379
+ { relationshipName: "Contacts", fields: ["Id"], filter: { Title: { eq: "$1var" } } },
380
+ ],
381
+ },
382
+ noopPrimeDeps(),
383
+ );
384
+ // The invalid placeholder warning surfaces from the CHILD filter (not dropped).
385
+ expect(out.warnings.some((w) => w.startsWith("Variable:") && w.includes("$1var"))).toBe(true);
386
+ // And the child filter renders the quoted literal, not a bare reference.
387
+ expect(out.query).toMatch(/Title\s*:\s*\{\s*eq\s*:\s*"\$1var"\s*\}/s);
388
+ });
389
+
390
+ // C1 (W-22697670): a childRelationship filter reusing the reserved $after cursor
391
+ // must keep String (first-wins) so pagination stays valid, and surface a warning.
392
+ // Regression guard: $after is now declared before the childRelationships loop.
393
+ it("childRelationship filter reusing $after keeps String (first-wins) and warns", async () => {
394
+ const out = await buildList(
395
+ {
396
+ org: ORG,
397
+ object: "Account",
398
+ fields: ["Id"],
399
+ childRelationships: [
400
+ { relationshipName: "Contacts", fields: ["Id"], filter: { Rank: { gt: "$after" } } },
401
+ ],
402
+ },
403
+ noopPrimeDeps(),
404
+ );
405
+ // Pagination's $after stays String, not overwritten to the child's Float.
406
+ expect(out.query).toMatch(/\$after\s*:\s*String/);
407
+ expect(out.query).not.toMatch(/\$after\s*:\s*Float/);
408
+ expect(
409
+ out.warnings.some(
410
+ (w) => w.startsWith("Variable:") && w.includes("type collision for $after"),
411
+ ),
412
+ ).toBe(true);
413
+ });
301
414
  });