@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.
- package/CHANGELOG.md +11 -0
- package/dist/commands/args.js +15 -2
- package/dist/commands/args.js.map +1 -1
- package/dist/intent/build-aggregate.js +6 -2
- package/dist/intent/build-aggregate.js.map +1 -1
- package/dist/intent/build-detail.js +3 -2
- package/dist/intent/build-detail.js.map +1 -1
- package/dist/intent/build-list.js +11 -7
- package/dist/intent/build-list.js.map +1 -1
- package/dist/intent/select-child-relationship.d.ts +1 -1
- package/dist/intent/select-child-relationship.js +3 -3
- package/dist/intent/select-child-relationship.js.map +1 -1
- package/dist/lib/auth.js +3 -2
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/errors.d.ts +38 -0
- package/dist/lib/errors.js +42 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/introspect.js +4 -3
- package/dist/lib/introspect.js.map +1 -1
- package/dist/lib/prime-schema.js +13 -5
- package/dist/lib/prime-schema.js.map +1 -1
- package/dist/lib/query-builder.js +19 -6
- package/dist/lib/query-builder.js.map +1 -1
- package/dist/lib/session.d.ts +10 -1
- package/dist/lib/session.js +9 -2
- package/dist/lib/session.js.map +1 -1
- package/dist/lib/variable-promotion.js +7 -3
- package/dist/lib/variable-promotion.js.map +1 -1
- package/dist/lib/walker.js +8 -6
- package/dist/lib/walker.js.map +1 -1
- package/dist/mcp/tools/sf-gql-aggregate.js +2 -6
- package/dist/mcp/tools/sf-gql-aggregate.js.map +1 -1
- package/dist/mcp/tools/sf-gql-connect.js +3 -7
- package/dist/mcp/tools/sf-gql-connect.js.map +1 -1
- package/dist/mcp/tools/sf-gql-create.js +2 -6
- package/dist/mcp/tools/sf-gql-create.js.map +1 -1
- package/dist/mcp/tools/sf-gql-delete.js +2 -6
- package/dist/mcp/tools/sf-gql-delete.js.map +1 -1
- package/dist/mcp/tools/sf-gql-detail.js +2 -6
- package/dist/mcp/tools/sf-gql-detail.js.map +1 -1
- package/dist/mcp/tools/sf-gql-discover.js +2 -6
- package/dist/mcp/tools/sf-gql-discover.js.map +1 -1
- package/dist/mcp/tools/sf-gql-list.js +2 -6
- package/dist/mcp/tools/sf-gql-list.js.map +1 -1
- package/dist/mcp/tools/sf-gql-raw.js +2 -6
- package/dist/mcp/tools/sf-gql-raw.js.map +1 -1
- package/dist/mcp/tools/sf-gql-update.js +2 -6
- package/dist/mcp/tools/sf-gql-update.js.map +1 -1
- package/dist/schemas/tool-adapter.d.ts +56 -0
- package/dist/schemas/tool-adapter.js +129 -0
- package/dist/schemas/tool-adapter.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/args.ts +23 -2
- package/src/intent/__tests__/build-aggregate.spec.ts +64 -0
- package/src/intent/__tests__/build-list.spec.ts +115 -2
- package/src/intent/build-aggregate.ts +7 -3
- package/src/intent/build-detail.ts +4 -2
- package/src/intent/build-list.ts +13 -8
- package/src/intent/select-child-relationship.ts +3 -2
- package/src/lib/__tests__/apply-command.spec.ts +17 -1
- package/src/lib/__tests__/query-builder.spec.ts +68 -0
- package/src/lib/__tests__/session.spec.ts +44 -0
- package/src/lib/__tests__/variable-promotion.spec.ts +58 -0
- package/src/lib/auth.ts +4 -2
- package/src/lib/errors.ts +45 -0
- package/src/lib/introspect.ts +6 -3
- package/src/lib/prime-schema.ts +12 -4
- package/src/lib/query-builder.ts +19 -6
- package/src/lib/session.ts +20 -3
- package/src/lib/variable-promotion.ts +9 -3
- package/src/lib/walker.ts +8 -6
- package/src/mcp/tools/__tests__/error-surface.contract.spec.ts +261 -0
- package/src/mcp/tools/sf-gql-aggregate.ts +2 -6
- package/src/mcp/tools/sf-gql-connect.ts +3 -7
- package/src/mcp/tools/sf-gql-create.ts +2 -6
- package/src/mcp/tools/sf-gql-delete.ts +2 -6
- package/src/mcp/tools/sf-gql-detail.ts +2 -6
- package/src/mcp/tools/sf-gql-discover.ts +2 -6
- package/src/mcp/tools/sf-gql-list.ts +2 -6
- package/src/mcp/tools/sf-gql-raw.ts +2 -6
- package/src/mcp/tools/sf-gql-update.ts +2 -6
- package/src/schemas/__tests__/tool-adapter.spec.ts +299 -0
- 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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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":"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"}
|
package/package.json
CHANGED
package/src/commands/args.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
});
|