@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.
- package/AGENT_GUIDE.md +424 -0
- package/CHANGELOG.md +448 -0
- package/LICENSE.txt +82 -0
- package/README.md +204 -0
- package/TASK.md +249 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +683 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/args.d.ts +13 -0
- package/dist/commands/args.js +207 -0
- package/dist/commands/args.js.map +1 -0
- package/dist/commands/build.d.ts +11 -0
- package/dist/commands/build.js +209 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/connect.d.ts +8 -0
- package/dist/commands/connect.js +55 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/describe.d.ts +6 -0
- package/dist/commands/describe.js +229 -0
- package/dist/commands/describe.js.map +1 -0
- package/dist/commands/meta.d.ts +9 -0
- package/dist/commands/meta.js +140 -0
- package/dist/commands/meta.js.map +1 -0
- package/dist/commands/navigate.d.ts +14 -0
- package/dist/commands/navigate.js +105 -0
- package/dist/commands/navigate.js.map +1 -0
- package/dist/commands/query-helpers.d.ts +80 -0
- package/dist/commands/query-helpers.js +865 -0
- package/dist/commands/query-helpers.js.map +1 -0
- package/dist/commands/query.d.ts +26 -0
- package/dist/commands/query.js +901 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/review.d.ts +18 -0
- package/dist/commands/review.js +533 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/session-mgmt.d.ts +25 -0
- package/dist/commands/session-mgmt.js +206 -0
- package/dist/commands/session-mgmt.js.map +1 -0
- package/dist/commands/type.d.ts +6 -0
- package/dist/commands/type.js +342 -0
- package/dist/commands/type.js.map +1 -0
- package/dist/commands/validate-input.d.ts +6 -0
- package/dist/commands/validate-input.js +32 -0
- package/dist/commands/validate-input.js.map +1 -0
- package/dist/intent/build-aggregate.d.ts +33 -0
- package/dist/intent/build-aggregate.js +134 -0
- package/dist/intent/build-aggregate.js.map +1 -0
- package/dist/intent/build-create.d.ts +14 -0
- package/dist/intent/build-create.js +16 -0
- package/dist/intent/build-create.js.map +1 -0
- package/dist/intent/build-delete.d.ts +30 -0
- package/dist/intent/build-delete.js +53 -0
- package/dist/intent/build-delete.js.map +1 -0
- package/dist/intent/build-detail.d.ts +32 -0
- package/dist/intent/build-detail.js +80 -0
- package/dist/intent/build-detail.js.map +1 -0
- package/dist/intent/build-discover.d.ts +30 -0
- package/dist/intent/build-discover.js +149 -0
- package/dist/intent/build-discover.js.map +1 -0
- package/dist/intent/build-list.d.ts +28 -0
- package/dist/intent/build-list.js +75 -0
- package/dist/intent/build-list.js.map +1 -0
- package/dist/intent/build-mutation.d.ts +23 -0
- package/dist/intent/build-mutation.js +54 -0
- package/dist/intent/build-mutation.js.map +1 -0
- package/dist/intent/build-output.d.ts +27 -0
- package/dist/intent/build-output.js +60 -0
- package/dist/intent/build-output.js.map +1 -0
- package/dist/intent/build-raw.d.ts +23 -0
- package/dist/intent/build-raw.js +54 -0
- package/dist/intent/build-raw.js.map +1 -0
- package/dist/intent/build-update.d.ts +14 -0
- package/dist/intent/build-update.js +16 -0
- package/dist/intent/build-update.js.map +1 -0
- package/dist/intent/get-schema-with-priming.d.ts +26 -0
- package/dist/intent/get-schema-with-priming.js +32 -0
- package/dist/intent/get-schema-with-priming.js.map +1 -0
- package/dist/intent/select-child-relationship.d.ts +19 -0
- package/dist/intent/select-child-relationship.js +38 -0
- package/dist/intent/select-child-relationship.js.map +1 -0
- package/dist/intent/types.d.ts +159 -0
- package/dist/intent/types.js +21 -0
- package/dist/intent/types.js.map +1 -0
- package/dist/lib/apply-command.d.ts +15 -0
- package/dist/lib/apply-command.js +238 -0
- package/dist/lib/apply-command.js.map +1 -0
- package/dist/lib/auth.d.ts +38 -0
- package/dist/lib/auth.js +113 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/codegen.d.ts +32 -0
- package/dist/lib/codegen.js +700 -0
- package/dist/lib/codegen.js.map +1 -0
- package/dist/lib/command-registry.d.ts +59 -0
- package/dist/lib/command-registry.js +366 -0
- package/dist/lib/command-registry.js.map +1 -0
- package/dist/lib/formatter.d.ts +76 -0
- package/dist/lib/formatter.js +419 -0
- package/dist/lib/formatter.js.map +1 -0
- package/dist/lib/fs-utils.d.ts +23 -0
- package/dist/lib/fs-utils.js +46 -0
- package/dist/lib/fs-utils.js.map +1 -0
- package/dist/lib/graphql-name.d.ts +27 -0
- package/dist/lib/graphql-name.js +32 -0
- package/dist/lib/graphql-name.js.map +1 -0
- package/dist/lib/interactive.d.ts +6 -0
- package/dist/lib/interactive.js +562 -0
- package/dist/lib/interactive.js.map +1 -0
- package/dist/lib/introspect.d.ts +40 -0
- package/dist/lib/introspect.js +280 -0
- package/dist/lib/introspect.js.map +1 -0
- package/dist/lib/object-info.d.ts +79 -0
- package/dist/lib/object-info.js +313 -0
- package/dist/lib/object-info.js.map +1 -0
- package/dist/lib/path-selection.d.ts +50 -0
- package/dist/lib/path-selection.js +146 -0
- package/dist/lib/path-selection.js.map +1 -0
- package/dist/lib/prime-schema.d.ts +59 -0
- package/dist/lib/prime-schema.js +158 -0
- package/dist/lib/prime-schema.js.map +1 -0
- package/dist/lib/query-builder.d.ts +10 -0
- package/dist/lib/query-builder.js +168 -0
- package/dist/lib/query-builder.js.map +1 -0
- package/dist/lib/query-commands.d.ts +19 -0
- package/dist/lib/query-commands.js +262 -0
- package/dist/lib/query-commands.js.map +1 -0
- package/dist/lib/session.d.ts +205 -0
- package/dist/lib/session.js +1075 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/tokenize.d.ts +12 -0
- package/dist/lib/tokenize.js +48 -0
- package/dist/lib/tokenize.js.map +1 -0
- package/dist/lib/uiapi.d.ts +109 -0
- package/dist/lib/uiapi.js +159 -0
- package/dist/lib/uiapi.js.map +1 -0
- package/dist/lib/validator.d.ts +27 -0
- package/dist/lib/validator.js +100 -0
- package/dist/lib/validator.js.map +1 -0
- package/dist/lib/variable-promotion.d.ts +69 -0
- package/dist/lib/variable-promotion.js +95 -0
- package/dist/lib/variable-promotion.js.map +1 -0
- package/dist/lib/walker.d.ts +147 -0
- package/dist/lib/walker.js +723 -0
- package/dist/lib/walker.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +34 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +7 -0
- package/dist/mcp/stdio.js +25 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/tools/echo.d.ts +7 -0
- package/dist/mcp/tools/echo.js +17 -0
- package/dist/mcp/tools/echo.js.map +1 -0
- package/dist/mcp/tools/sf-gql-aggregate.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-aggregate.js +75 -0
- package/dist/mcp/tools/sf-gql-aggregate.js.map +1 -0
- package/dist/mcp/tools/sf-gql-create.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-create.js +35 -0
- package/dist/mcp/tools/sf-gql-create.js.map +1 -0
- package/dist/mcp/tools/sf-gql-delete.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-delete.js +31 -0
- package/dist/mcp/tools/sf-gql-delete.js.map +1 -0
- package/dist/mcp/tools/sf-gql-detail.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-detail.js +58 -0
- package/dist/mcp/tools/sf-gql-detail.js.map +1 -0
- package/dist/mcp/tools/sf-gql-discover.d.ts +9 -0
- package/dist/mcp/tools/sf-gql-discover.js +51 -0
- package/dist/mcp/tools/sf-gql-discover.js.map +1 -0
- package/dist/mcp/tools/sf-gql-list.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-list.js +53 -0
- package/dist/mcp/tools/sf-gql-list.js.map +1 -0
- package/dist/mcp/tools/sf-gql-raw.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-raw.js +39 -0
- package/dist/mcp/tools/sf-gql-raw.js.map +1 -0
- package/dist/mcp/tools/sf-gql-update.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-update.js +35 -0
- package/dist/mcp/tools/sf-gql-update.js.map +1 -0
- package/dist/mcp/tools/shared/zod-schemas.d.ts +49 -0
- package/dist/mcp/tools/shared/zod-schemas.js +46 -0
- package/dist/mcp/tools/shared/zod-schemas.js.map +1 -0
- package/package.json +36 -0
- package/ralph-loop.sh +120 -0
- package/scripts/smoke-orderby.sh +190 -0
- package/src/__tests__/helpers/prime-deps.ts +46 -0
- package/src/__tests__/helpers/schema.ts +73 -0
- package/src/__tests__/helpers/stdout.ts +33 -0
- package/src/__tests__/setup.ts +19 -0
- package/src/cli.ts +764 -0
- package/src/commands/__tests__/query.spec.ts +137 -0
- package/src/commands/args.ts +306 -0
- package/src/commands/build.ts +288 -0
- package/src/commands/connect.ts +60 -0
- package/src/commands/describe.ts +246 -0
- package/src/commands/meta.ts +171 -0
- package/src/commands/navigate.ts +134 -0
- package/src/commands/query-helpers.ts +1202 -0
- package/src/commands/query.ts +1085 -0
- package/src/commands/review.ts +670 -0
- package/src/commands/session-mgmt.ts +256 -0
- package/src/commands/type.ts +437 -0
- package/src/commands/validate-input.ts +38 -0
- package/src/intent/__tests__/build-aggregate.spec.ts +931 -0
- package/src/intent/__tests__/build-create-validation.spec.ts +135 -0
- package/src/intent/__tests__/build-delete.spec.ts +121 -0
- package/src/intent/__tests__/build-detail.spec.ts +333 -0
- package/src/intent/__tests__/build-discover.spec.ts +432 -0
- package/src/intent/__tests__/build-list.spec.ts +284 -0
- package/src/intent/__tests__/build-mutation.spec.ts +108 -0
- package/src/intent/__tests__/build-output.spec.ts +55 -0
- package/src/intent/__tests__/build-raw.spec.ts +153 -0
- package/src/intent/__tests__/build-update-validation.spec.ts +134 -0
- package/src/intent/build-aggregate.ts +182 -0
- package/src/intent/build-create.ts +19 -0
- package/src/intent/build-delete.ts +62 -0
- package/src/intent/build-detail.ts +95 -0
- package/src/intent/build-discover.ts +199 -0
- package/src/intent/build-list.ts +91 -0
- package/src/intent/build-mutation.ts +75 -0
- package/src/intent/build-output.ts +72 -0
- package/src/intent/build-raw.ts +61 -0
- package/src/intent/build-update.ts +19 -0
- package/src/intent/get-schema-with-priming.ts +43 -0
- package/src/intent/select-child-relationship.ts +48 -0
- package/src/intent/types.ts +181 -0
- package/src/lib/__tests__/apply-command.parity.spec.ts +97 -0
- package/src/lib/__tests__/apply-command.spec.ts +171 -0
- package/src/lib/__tests__/auth.spec.ts +292 -0
- package/src/lib/__tests__/formatter.spec.ts +86 -0
- package/src/lib/__tests__/graphql-name.spec.ts +64 -0
- package/src/lib/__tests__/introspect.spec.ts +399 -0
- package/src/lib/__tests__/object-info.spec.ts +124 -0
- package/src/lib/__tests__/path-selection.spec.ts +219 -0
- package/src/lib/__tests__/prime-schema.spec.ts +269 -0
- package/src/lib/__tests__/query-builder.spec.ts +95 -0
- package/src/lib/__tests__/query-commands.spec.ts +74 -0
- package/src/lib/__tests__/session.spec.ts +292 -0
- package/src/lib/__tests__/tokenize.spec.ts +33 -0
- package/src/lib/__tests__/uiapi.spec.ts +192 -0
- package/src/lib/__tests__/variable-promotion.spec.ts +211 -0
- package/src/lib/__tests__/walker.spec.ts +250 -0
- package/src/lib/apply-command.ts +286 -0
- package/src/lib/auth.ts +157 -0
- package/src/lib/codegen.ts +899 -0
- package/src/lib/command-registry.ts +434 -0
- package/src/lib/formatter.ts +587 -0
- package/src/lib/fs-utils.ts +46 -0
- package/src/lib/graphql-name.ts +35 -0
- package/src/lib/interactive.ts +634 -0
- package/src/lib/introspect.ts +320 -0
- package/src/lib/object-info.ts +409 -0
- package/src/lib/path-selection.ts +162 -0
- package/src/lib/prime-schema.ts +195 -0
- package/src/lib/query-builder.ts +193 -0
- package/src/lib/query-commands.ts +290 -0
- package/src/lib/session.ts +1304 -0
- package/src/lib/tokenize.ts +43 -0
- package/src/lib/uiapi.ts +176 -0
- package/src/lib/validator.ts +143 -0
- package/src/lib/variable-promotion.ts +145 -0
- package/src/lib/walker.ts +975 -0
- package/src/mcp/__tests__/server.spec.ts +155 -0
- package/src/mcp/server.ts +38 -0
- package/src/mcp/stdio.ts +29 -0
- package/src/mcp/tools/__tests__/sf-gql-aggregate.spec.ts +173 -0
- package/src/mcp/tools/__tests__/sf-gql-create.spec.ts +235 -0
- package/src/mcp/tools/__tests__/sf-gql-delete.spec.ts +194 -0
- package/src/mcp/tools/__tests__/sf-gql-detail.spec.ts +246 -0
- package/src/mcp/tools/__tests__/sf-gql-discover.spec.ts +320 -0
- package/src/mcp/tools/__tests__/sf-gql-list.spec.ts +128 -0
- package/src/mcp/tools/__tests__/sf-gql-raw.spec.ts +141 -0
- package/src/mcp/tools/__tests__/sf-gql-update.spec.ts +207 -0
- package/src/mcp/tools/echo.ts +24 -0
- package/src/mcp/tools/sf-gql-aggregate.ts +102 -0
- package/src/mcp/tools/sf-gql-create.ts +55 -0
- package/src/mcp/tools/sf-gql-delete.ts +49 -0
- package/src/mcp/tools/sf-gql-detail.ts +85 -0
- package/src/mcp/tools/sf-gql-discover.ts +67 -0
- package/src/mcp/tools/sf-gql-list.ts +73 -0
- package/src/mcp/tools/sf-gql-raw.ts +56 -0
- package/src/mcp/tools/sf-gql-update.ts +55 -0
- package/src/mcp/tools/shared/zod-schemas.ts +55 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { buildOutput } from "./build-output.js";
|
|
8
|
+
import { getSchemaWithPriming } from "./get-schema-with-priming.js";
|
|
9
|
+
import { type DeleteSpec, type ToolOutput } from "./types.js";
|
|
10
|
+
import { assertGraphqlName } from "../lib/graphql-name.js";
|
|
11
|
+
import { selectDottedFieldPath } from "../lib/path-selection.js";
|
|
12
|
+
import { type PrimeDeps } from "../lib/prime-schema.js";
|
|
13
|
+
import { addVariable, createSession, deepSetArg } from "../lib/session.js";
|
|
14
|
+
import { mutationFieldPath } from "../lib/uiapi.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build a UIAPI delete mutation against a Salesforce org. Implements
|
|
18
|
+
* `sf_gql_delete` intent for the graphiti MCP server (FR-5.7).
|
|
19
|
+
*
|
|
20
|
+
* Unlike create/update, delete is uniform across SObjects:
|
|
21
|
+
* - The input type is the schema-wide `RecordDeleteInput!`, NOT an
|
|
22
|
+
* `<Object>`-specific input. Every `<Object>Delete` mutation field
|
|
23
|
+
* accepts the same `RecordDeleteInput!` carrying the record `Id`.
|
|
24
|
+
* - The result is a `RecordDeletePayload` exposing only `Id` (a plain
|
|
25
|
+
* `ID`, not a value wrapper), so the selection is always `Id` and
|
|
26
|
+
* there is no `Record` sub-path — hence no `returnFields`.
|
|
27
|
+
*
|
|
28
|
+
* Implicit behaviors not visible in the signature:
|
|
29
|
+
* - `inputVariable` defaults to `"input"`, declared as
|
|
30
|
+
* `$<name>: RecordDeleteInput!`. A leading `$` is stripped.
|
|
31
|
+
* - Operation name defaults to `Delete<Object>` (e.g. `DeleteAccount`).
|
|
32
|
+
*
|
|
33
|
+
* Throws on invalid `object`, `inputVariable`, or `operationName` (must be
|
|
34
|
+
* valid GraphQL Names), auth-missing, or introspection failure (via
|
|
35
|
+
* `getSchemaWithPriming`); never throws on validation or codegen
|
|
36
|
+
* failure (those surface as `warnings[]`).
|
|
37
|
+
*/
|
|
38
|
+
export async function buildDelete(spec: DeleteSpec, deps?: PrimeDeps): Promise<ToolOutput> {
|
|
39
|
+
assertGraphqlName(spec.object, "buildDelete", "object");
|
|
40
|
+
|
|
41
|
+
const inputVar = (spec.inputVariable ?? "input").replace(/^\$/, "");
|
|
42
|
+
assertGraphqlName(inputVar, "buildDelete", "inputVariable");
|
|
43
|
+
|
|
44
|
+
const { schema, primingNote, instanceUrl } = await getSchemaWithPriming(spec.org, deps);
|
|
45
|
+
|
|
46
|
+
const session = createSession(spec.org, "mutation", instanceUrl);
|
|
47
|
+
session.operationName = spec.operationName ?? "Delete" + spec.object;
|
|
48
|
+
assertGraphqlName(session.operationName, "buildDelete", "operationName");
|
|
49
|
+
|
|
50
|
+
const fieldPath = mutationFieldPath(spec.object, "Delete");
|
|
51
|
+
addVariable(session, inputVar, "RecordDeleteInput!");
|
|
52
|
+
deepSetArg(session, fieldPath, "input", [], "$" + inputVar);
|
|
53
|
+
|
|
54
|
+
// Delete payloads expose only `Id` (a plain ID — never a value wrapper).
|
|
55
|
+
// The selection is a literal single segment on the mutation field, so
|
|
56
|
+
// `selectDottedFieldPath` cannot realistically throw here: there is no
|
|
57
|
+
// user-supplied dotted path (unlike `buildMutation`'s `returnFields`) and
|
|
58
|
+
// no top-level mutation-arg context that raises `MutationContextError`.
|
|
59
|
+
selectDottedFieldPath(session, schema, fieldPath, "Id");
|
|
60
|
+
|
|
61
|
+
return buildOutput(session, schema, primingNote, []);
|
|
62
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { buildOutput } from "./build-output.js";
|
|
8
|
+
import { getSchemaWithPriming } from "./get-schema-with-priming.js";
|
|
9
|
+
import { selectChildRelationship } from "./select-child-relationship.js";
|
|
10
|
+
import { type DetailSpec, type ToolOutput } from "./types.js";
|
|
11
|
+
import { assertGraphqlName } from "../lib/graphql-name.js";
|
|
12
|
+
import { selectDottedFieldPath } from "../lib/path-selection.js";
|
|
13
|
+
import { type PrimeDeps } from "../lib/prime-schema.js";
|
|
14
|
+
import { addVariable, createSession, deepSetArg } from "../lib/session.js";
|
|
15
|
+
import { connectionNodePath, connectionPath } from "../lib/uiapi.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build a UIAPI single-record detail query against a Salesforce org. Implements
|
|
19
|
+
* `sf_gql_detail` intent for the graphiti MCP server (FR-5.5).
|
|
20
|
+
*
|
|
21
|
+
* Implicit behaviors not visible in the signature:
|
|
22
|
+
* - Always declares `<idVariable>: ID!` (default name `id`) and binds it via
|
|
23
|
+
* `where: { Id: { eq: $<idVariable> } }`, with `first: 1` set on the
|
|
24
|
+
* connection. Single-record by Id is the whole point of this tool.
|
|
25
|
+
* - Unlike `sf_gql_list`: no `$after` cursor, no `pageInfo` selection, and no
|
|
26
|
+
* top-level `filter` / `orderBy` / `scope`.
|
|
27
|
+
* - Scalar fields auto-wrap with `{ value }` per UIAPI; `Id` is selected bare.
|
|
28
|
+
* - Dotted paths in `fields` / `parentFields` expand polymorphic unions into
|
|
29
|
+
* per-member inline fragments; members lacking the field are silently skipped.
|
|
30
|
+
* - `childRelationships` render as `<rel> { edges { node { ... } } }`
|
|
31
|
+
* connections with their own `first` / `filter` / `orderBy` (and `$varName`
|
|
32
|
+
* leaves promote the same way they do on `sf_gql_list`).
|
|
33
|
+
* - Operation name defaults to `<Object>Detail`.
|
|
34
|
+
*
|
|
35
|
+
* Throws on invalid `object` or `operationName` (must be valid GraphQL Names),
|
|
36
|
+
* auth-missing or introspection failure (via `getSchemaWithPriming`), on FR-5.5
|
|
37
|
+
* spec violations (empty `idVariable`, collision with a child-filter `$varName`
|
|
38
|
+
* of a different type); never throws on validation or codegen failure (those
|
|
39
|
+
* surface as `warnings[]`).
|
|
40
|
+
*/
|
|
41
|
+
export async function buildDetail(spec: DetailSpec, deps?: PrimeDeps): Promise<ToolOutput> {
|
|
42
|
+
if (spec.idVariable !== undefined && spec.idVariable.length === 0) {
|
|
43
|
+
throw new Error("buildDetail: idVariable must be a non-empty string (FR-5.5)");
|
|
44
|
+
}
|
|
45
|
+
assertGraphqlName(spec.object, "buildDetail", "object");
|
|
46
|
+
|
|
47
|
+
const { schema, primingNote, instanceUrl } = await getSchemaWithPriming(spec.org, deps);
|
|
48
|
+
|
|
49
|
+
const session = createSession(spec.org, "query", instanceUrl);
|
|
50
|
+
session.operationName = spec.operationName ?? `${spec.object}Detail`;
|
|
51
|
+
assertGraphqlName(session.operationName, "buildDetail", "operationName");
|
|
52
|
+
|
|
53
|
+
const connection = connectionPath(spec.object);
|
|
54
|
+
const node = connectionNodePath(connection);
|
|
55
|
+
|
|
56
|
+
for (const field of spec.fields) {
|
|
57
|
+
selectDottedFieldPath(session, schema, node, field);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (spec.parentFields) {
|
|
61
|
+
for (const pf of spec.parentFields) {
|
|
62
|
+
selectDottedFieldPath(session, schema, node, pf);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (spec.childRelationships) {
|
|
67
|
+
for (const child of spec.childRelationships) {
|
|
68
|
+
selectChildRelationship(session, schema, node, child);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// FR-5.5: <idVariable>: ID! + where: { Id: { eq: $<idVariable> } } + first: 1.
|
|
73
|
+
// Per-leaf deepSetArg is the cleanest form for a hardcoded shape: session.ts
|
|
74
|
+
// preserves the leading `$` raw, and query-builder.ts emits it as an unquoted
|
|
75
|
+
// variable reference. Same machinery the aggregate builder uses for groupBy.
|
|
76
|
+
const idVar = spec.idVariable ?? "id";
|
|
77
|
+
|
|
78
|
+
// Children promote variables before the ID var is added; if a child filter
|
|
79
|
+
// already declared a variable with this name AND a different type, we'd
|
|
80
|
+
// silently overwrite it to `ID!` — corrupting the user's filter binding.
|
|
81
|
+
// Detect and throw rather than mask the conflict.
|
|
82
|
+
const existing = session.variables.find((v) => v.name === idVar);
|
|
83
|
+
if (existing && existing.type !== "ID!") {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`buildDetail: idVariable "${idVar}" collides with a $${idVar} reference in childRelationships ` +
|
|
86
|
+
`(declared as ${existing.type}); choose a different idVariable.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
addVariable(session, idVar, "ID!");
|
|
91
|
+
deepSetArg(session, connection, "where", ["Id", "eq"], `$${idVar}`);
|
|
92
|
+
deepSetArg(session, connection, "first", [], "1");
|
|
93
|
+
|
|
94
|
+
return buildOutput(session, schema, primingNote);
|
|
95
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { type GraphQLSchema } from "graphql";
|
|
8
|
+
import { getSchemaWithPriming } from "./get-schema-with-priming.js";
|
|
9
|
+
import {
|
|
10
|
+
type DiscoverOutput,
|
|
11
|
+
type DiscoverSpec,
|
|
12
|
+
type FieldDescription,
|
|
13
|
+
type ObjectDescription,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
import { getOrgAuth as realGetOrgAuth, type OrgAuth } from "../lib/auth.js";
|
|
16
|
+
import {
|
|
17
|
+
type FieldMetadata,
|
|
18
|
+
getObjectInfo as realGetObjectInfo,
|
|
19
|
+
getRequiredCreateFields,
|
|
20
|
+
type ObjectInfoResult,
|
|
21
|
+
} from "../lib/object-info.js";
|
|
22
|
+
import { type PrimeDeps } from "../lib/prime-schema.js";
|
|
23
|
+
import { resolvePath } from "../lib/walker.js";
|
|
24
|
+
|
|
25
|
+
export interface DiscoverDeps {
|
|
26
|
+
primeDeps?: PrimeDeps;
|
|
27
|
+
getOrgAuth?: (orgAlias: string) => Promise<OrgAuth>;
|
|
28
|
+
getObjectInfo?: (
|
|
29
|
+
auth: OrgAuth,
|
|
30
|
+
orgAlias: string,
|
|
31
|
+
sObjectName: string,
|
|
32
|
+
) => Promise<ObjectInfoResult>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build the `sf_gql_discover` payload. Three modes:
|
|
37
|
+
*
|
|
38
|
+
* - `list_objects`: enumerate queryable SObjects from the schema's
|
|
39
|
+
* `uiapi.query` selection. Optional `search` substring-filters names.
|
|
40
|
+
* - `describe_object`: fetch ObjectInfo for `spec.object` and project it
|
|
41
|
+
* onto the spec-defined `ObjectDescription` shape.
|
|
42
|
+
* - `describe_field`: same as `describe_object`, narrowed to one field.
|
|
43
|
+
*
|
|
44
|
+
* Schema priming follows FR-13.3 — first call against an unprimed org
|
|
45
|
+
* triggers introspection and surfaces a `Note: Primed schema cache ...`
|
|
46
|
+
* warning. ObjectInfo carries its own per-object cache inside
|
|
47
|
+
* `lib/object-info.ts` (1-hour TTL), so describe_* modes do not
|
|
48
|
+
* re-introspect on every call.
|
|
49
|
+
*/
|
|
50
|
+
export async function buildDiscover(
|
|
51
|
+
spec: DiscoverSpec,
|
|
52
|
+
deps: DiscoverDeps = {},
|
|
53
|
+
): Promise<DiscoverOutput> {
|
|
54
|
+
const { schema, primingNote } = await getSchemaWithPriming(spec.org, deps.primeDeps);
|
|
55
|
+
const warnings = primingNote ? [primingNote] : [];
|
|
56
|
+
|
|
57
|
+
if (spec.mode === "list_objects") {
|
|
58
|
+
const objects = listQueryableObjects(schema, spec.search);
|
|
59
|
+
return { mode: "list_objects", objects, ...(warnings.length ? { warnings } : {}) };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!spec.object) {
|
|
63
|
+
throw new Error(`sf_gql_discover mode "${spec.mode}" requires "object".`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const getAuth = deps.getOrgAuth ?? realGetOrgAuth;
|
|
67
|
+
const fetchObjectInfo = deps.getObjectInfo ?? realGetObjectInfo;
|
|
68
|
+
const auth = await getAuth(spec.org);
|
|
69
|
+
// ObjectInfo failures (auth expired, network blip, object missing) propagate
|
|
70
|
+
// to the caller. Earlier work tried a schema-only fallback that returned
|
|
71
|
+
// field names from the cached GraphQL schema, but that response cannot
|
|
72
|
+
// satisfy ObjectDescription (no picklists, no filterable/sortable flags,
|
|
73
|
+
// no requiredOnCreate) and silently invites consumers to treat absent
|
|
74
|
+
// metadata as authoritative absence. A loud error forces the right
|
|
75
|
+
// recovery (refresh auth, prime schema, retry).
|
|
76
|
+
const info = await fetchObjectInfo(auth, spec.org, spec.object);
|
|
77
|
+
|
|
78
|
+
if (spec.mode === "describe_field") {
|
|
79
|
+
if (!spec.field) {
|
|
80
|
+
throw new Error('sf_gql_discover mode "describe_field" requires "field".');
|
|
81
|
+
}
|
|
82
|
+
const field = info.fields.find((f) => f.apiName === spec.field);
|
|
83
|
+
if (!field) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Field "${spec.field}" not found on "${spec.object}". Use mode "describe_object" to list fields.`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
mode: "describe_field",
|
|
90
|
+
field: toFieldDescription(field, info),
|
|
91
|
+
...(warnings.length ? { warnings } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (spec.mode === "describe_object") {
|
|
96
|
+
return {
|
|
97
|
+
mode: "describe_object",
|
|
98
|
+
object: toObjectDescription(spec.object, info),
|
|
99
|
+
...(warnings.length ? { warnings } : {}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Exhaustiveness guard: any new DiscoverMode added to types.ts must
|
|
104
|
+
// surface as a TS error here instead of silently falling through to
|
|
105
|
+
// describe_object.
|
|
106
|
+
throw new Error(`Unhandled discover mode: ${exhaustive(spec.mode)}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function exhaustive(value: never): never {
|
|
110
|
+
throw new Error(`Unexpected value: ${String(value)}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function listQueryableObjects(
|
|
114
|
+
schema: GraphQLSchema,
|
|
115
|
+
search?: string,
|
|
116
|
+
): { name: string; label?: string }[] {
|
|
117
|
+
let result: { name: string; label?: string }[];
|
|
118
|
+
try {
|
|
119
|
+
const walker = resolvePath(schema, "query", ["uiapi", "query"]);
|
|
120
|
+
// uiapi.query exposes per-SObject Connection fields (Account, Contact, etc.)
|
|
121
|
+
// alongside non-SObject helpers (search, aggregate). Filter to fields whose
|
|
122
|
+
// return type follows the *Connection convention so list_objects matches
|
|
123
|
+
// the spec contract — "queryable SObjects" — instead of leaking helpers.
|
|
124
|
+
result = walker.fields
|
|
125
|
+
.filter((f) => /Connection$/.test(f.typeName.replace(/[![\]]/g, "")))
|
|
126
|
+
.map((f) => ({
|
|
127
|
+
name: f.name,
|
|
128
|
+
...(f.description ? { label: f.description } : {}),
|
|
129
|
+
}));
|
|
130
|
+
} catch {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (search && search.trim().length > 0) {
|
|
135
|
+
const needle = search.toLowerCase();
|
|
136
|
+
result = result.filter((o) => o.name.toLowerCase().includes(needle));
|
|
137
|
+
}
|
|
138
|
+
result.sort((a, b) => a.name.localeCompare(b.name));
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function toFieldDescription(field: FieldMetadata, info: ObjectInfoResult): FieldDescription {
|
|
143
|
+
const picklist = info.picklists.find((p) => p.apiName === field.apiName);
|
|
144
|
+
// `parseObjectInfoResponse` already drops null-valued entries, but the
|
|
145
|
+
// PicklistValue type still allows null — filter narrows to string[].
|
|
146
|
+
const picklistValues =
|
|
147
|
+
picklist?.values.map((v) => v.value).filter((v): v is string => v !== null) ?? [];
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
name: field.apiName,
|
|
151
|
+
label: field.label ?? field.apiName,
|
|
152
|
+
type: field.dataType ?? "UNKNOWN",
|
|
153
|
+
filterable: field.filterable,
|
|
154
|
+
sortable: field.sortable,
|
|
155
|
+
nameField: field.nameField,
|
|
156
|
+
compound: field.compound,
|
|
157
|
+
defaultedOnCreate: field.defaultedOnCreate,
|
|
158
|
+
...(picklistValues.length > 0 ? { picklistValues } : {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function toObjectDescription(name: string, info: ObjectInfoResult): ObjectDescription {
|
|
163
|
+
const fields = info.fields.map((f) => toFieldDescription(f, info));
|
|
164
|
+
|
|
165
|
+
const childRelationships = info.childRelationships
|
|
166
|
+
.filter((cr) => cr.relationshipName !== null)
|
|
167
|
+
.map((cr) => ({
|
|
168
|
+
relationshipName: cr.relationshipName as string,
|
|
169
|
+
childObject: cr.childObjectApiName,
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
const parentReferences = info.fields
|
|
173
|
+
.filter((f) => f.reference && f.referenceToInfos.length > 0)
|
|
174
|
+
.map((f) => ({
|
|
175
|
+
field: f.apiName,
|
|
176
|
+
targetObjects: f.referenceToInfos.map((r) => r.apiName),
|
|
177
|
+
}));
|
|
178
|
+
|
|
179
|
+
const filterableFields = info.fields
|
|
180
|
+
.filter((f) => f.filterable && !f.compound)
|
|
181
|
+
.map((f) => f.apiName);
|
|
182
|
+
|
|
183
|
+
const sortable = info.fields.find((f) => f.sortable && !f.compound);
|
|
184
|
+
const orderByExample: Record<string, unknown> = sortable
|
|
185
|
+
? { [sortable.apiName]: { order: "DESC" } }
|
|
186
|
+
: {};
|
|
187
|
+
|
|
188
|
+
const requiredOnCreate = getRequiredCreateFields(info).map((f) => f.apiName);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
name,
|
|
192
|
+
fields,
|
|
193
|
+
childRelationships,
|
|
194
|
+
parentReferences,
|
|
195
|
+
filterableFields,
|
|
196
|
+
orderByExample,
|
|
197
|
+
requiredOnCreate,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { buildOutput } from "./build-output.js";
|
|
8
|
+
import { getSchemaWithPriming } from "./get-schema-with-priming.js";
|
|
9
|
+
import { selectChildRelationship } from "./select-child-relationship.js";
|
|
10
|
+
import { type ListSpec, type ToolOutput } from "./types.js";
|
|
11
|
+
import { assertGraphqlName } from "../lib/graphql-name.js";
|
|
12
|
+
import { selectDottedFieldPath } from "../lib/path-selection.js";
|
|
13
|
+
import { type PrimeDeps } from "../lib/prime-schema.js";
|
|
14
|
+
import { addVariable, createSession, deepSetArg, selectLeaf } from "../lib/session.js";
|
|
15
|
+
import { normalizeOrderBy, promoteVariables } from "../lib/variable-promotion.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build a UIAPI list query against a Salesforce org. Implements `sf_gql_list`
|
|
19
|
+
* intent for the graphiti MCP server.
|
|
20
|
+
*
|
|
21
|
+
* Implicit behaviors not visible in the signature:
|
|
22
|
+
* - Scalar fields are auto-wrapped with `{ value }` per UIAPI; `Id` is selected bare.
|
|
23
|
+
* - Dotted paths in `fields` / `parentFields` expand polymorphic unions into
|
|
24
|
+
* per-member inline fragments; members lacking the field are silently skipped.
|
|
25
|
+
* - `childRelationships` render as `<rel> { edges { node { ... } } }` connections.
|
|
26
|
+
* - `$varName` leaves anywhere in `filter` / `orderBy` / `scope` promote to typed
|
|
27
|
+
* query variables (inferred from the schema; nullable; `String` on inference miss).
|
|
28
|
+
* - `orderBy` arrays are collapsed to the first element (UIAPI expects a singleton).
|
|
29
|
+
* - Cursor pagination is always on: declares `$after: String`, selects
|
|
30
|
+
* `pageInfo { hasNextPage endCursor }`, defaults `first: 10`.
|
|
31
|
+
* - Operation name defaults to `<Object>List`.
|
|
32
|
+
*
|
|
33
|
+
* Throws on invalid `object` or `operationName` (must be valid GraphQL Names),
|
|
34
|
+
* auth-missing, or introspection failure (via `getSchemaWithPriming`); never
|
|
35
|
+
* throws on validation or codegen failure (those surface as `warnings[]`).
|
|
36
|
+
*/
|
|
37
|
+
export async function buildList(spec: ListSpec, deps?: PrimeDeps): Promise<ToolOutput> {
|
|
38
|
+
assertGraphqlName(spec.object, "buildList", "object");
|
|
39
|
+
|
|
40
|
+
const { schema, primingNote, instanceUrl } = await getSchemaWithPriming(spec.org, deps);
|
|
41
|
+
|
|
42
|
+
const session = createSession(spec.org, "query", instanceUrl);
|
|
43
|
+
session.operationName = spec.operationName ?? `${spec.object}List`;
|
|
44
|
+
assertGraphqlName(session.operationName, "buildList", "operationName");
|
|
45
|
+
|
|
46
|
+
const connectionPath = ["uiapi", "query", spec.object];
|
|
47
|
+
const nodePath = [...connectionPath, "edges", "node"];
|
|
48
|
+
|
|
49
|
+
for (const field of spec.fields) {
|
|
50
|
+
selectDottedFieldPath(session, schema, nodePath, field);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (spec.parentFields) {
|
|
54
|
+
for (const pf of spec.parentFields) {
|
|
55
|
+
selectDottedFieldPath(session, schema, nodePath, pf);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (spec.childRelationships) {
|
|
60
|
+
for (const child of spec.childRelationships) {
|
|
61
|
+
selectChildRelationship(session, schema, nodePath, child);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const first = spec.first ?? 10;
|
|
66
|
+
deepSetArg(session, connectionPath, "first", [], String(first));
|
|
67
|
+
|
|
68
|
+
addVariable(session, "after", "String");
|
|
69
|
+
deepSetArg(session, connectionPath, "after", [], "$after");
|
|
70
|
+
|
|
71
|
+
selectLeaf(session, [...connectionPath, "pageInfo", "hasNextPage"]);
|
|
72
|
+
selectLeaf(session, [...connectionPath, "pageInfo", "endCursor"]);
|
|
73
|
+
|
|
74
|
+
if (spec.filter) {
|
|
75
|
+
promoteVariables(session, schema, connectionPath, "where", spec.filter);
|
|
76
|
+
deepSetArg(session, connectionPath, "where", [], JSON.stringify(spec.filter));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const orderBy = normalizeOrderBy(spec.orderBy);
|
|
80
|
+
if (orderBy) {
|
|
81
|
+
promoteVariables(session, schema, connectionPath, "orderBy", orderBy);
|
|
82
|
+
deepSetArg(session, connectionPath, "orderBy", [], JSON.stringify(orderBy));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (spec.scope) {
|
|
86
|
+
promoteVariables(session, schema, connectionPath, "scope", spec.scope);
|
|
87
|
+
deepSetArg(session, connectionPath, "scope", [], spec.scope);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return buildOutput(session, schema, primingNote);
|
|
91
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { buildOutput } from "./build-output.js";
|
|
8
|
+
import { getSchemaWithPriming } from "./get-schema-with-priming.js";
|
|
9
|
+
import { type CreateSpec, type ToolOutput, type UpdateSpec } from "./types.js";
|
|
10
|
+
import { assertGraphqlName } from "../lib/graphql-name.js";
|
|
11
|
+
import { selectDottedFieldPath } from "../lib/path-selection.js";
|
|
12
|
+
import { type PrimeDeps } from "../lib/prime-schema.js";
|
|
13
|
+
import { addVariable, createSession, deepSetArg } from "../lib/session.js";
|
|
14
|
+
import {
|
|
15
|
+
createInputTypeName,
|
|
16
|
+
mutationFieldPath,
|
|
17
|
+
mutationRecordPath,
|
|
18
|
+
updateInputTypeName,
|
|
19
|
+
} from "../lib/uiapi.js";
|
|
20
|
+
|
|
21
|
+
export type MutationOp = "Create" | "Update";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build a UIAPI Create/Update mutation against a Salesforce org. Shared
|
|
25
|
+
* implementation for `sf_gql_create` and `sf_gql_update` (FR-5.6).
|
|
26
|
+
*
|
|
27
|
+
* Implicit behaviors:
|
|
28
|
+
* - `returnFields` defaults to `["Id"]`.
|
|
29
|
+
* - `inputVariable` defaults to `"input"`; a leading `$` is stripped.
|
|
30
|
+
* - Operation name defaults to `<Op><Object>` (e.g. `CreateAccount`, `UpdateAccount`).
|
|
31
|
+
* - The `input` argument on the mutation field is bound to the declared variable.
|
|
32
|
+
*
|
|
33
|
+
* Throws on invalid `object`/`inputVariable`/`operationName` GraphQL Names, empty
|
|
34
|
+
* `returnFields`, auth-missing, or introspection failure. Never throws on
|
|
35
|
+
* validation/codegen failure (those surface as `warnings[]`).
|
|
36
|
+
*/
|
|
37
|
+
export async function buildMutation(
|
|
38
|
+
spec: CreateSpec | UpdateSpec,
|
|
39
|
+
op: MutationOp,
|
|
40
|
+
deps?: PrimeDeps,
|
|
41
|
+
): Promise<ToolOutput> {
|
|
42
|
+
assertGraphqlName(spec.object, "buildMutation", "object");
|
|
43
|
+
|
|
44
|
+
const inputVar = (spec.inputVariable ?? "input").replace(/^\$/, "");
|
|
45
|
+
assertGraphqlName(inputVar, "buildMutation", "inputVariable");
|
|
46
|
+
|
|
47
|
+
if (spec.returnFields !== undefined && spec.returnFields.length === 0) {
|
|
48
|
+
throw new Error("buildMutation: returnFields must contain at least one field");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { schema, primingNote, instanceUrl } = await getSchemaWithPriming(spec.org, deps);
|
|
52
|
+
|
|
53
|
+
const session = createSession(spec.org, "mutation", instanceUrl);
|
|
54
|
+
session.operationName = spec.operationName ?? op + spec.object;
|
|
55
|
+
assertGraphqlName(session.operationName, "buildMutation", "operationName");
|
|
56
|
+
|
|
57
|
+
const fieldPath = mutationFieldPath(spec.object, op);
|
|
58
|
+
const recordPath = mutationRecordPath(spec.object, op);
|
|
59
|
+
const inputTypeName =
|
|
60
|
+
op === "Create" ? createInputTypeName(spec.object) : updateInputTypeName(spec.object);
|
|
61
|
+
addVariable(session, inputVar, inputTypeName + "!");
|
|
62
|
+
deepSetArg(session, fieldPath, "input", [], "$" + inputVar);
|
|
63
|
+
|
|
64
|
+
const extraWarnings: string[] = [];
|
|
65
|
+
for (const field of spec.returnFields ?? ["Id"]) {
|
|
66
|
+
try {
|
|
67
|
+
selectDottedFieldPath(session, schema, recordPath, field);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
70
|
+
extraWarnings.push(`returnFields: ${msg}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return buildOutput(session, schema, primingNote, extraWarnings);
|
|
75
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { type GraphQLSchema } from "graphql";
|
|
8
|
+
import { type ToolOutput, type VariableInfo } from "./types.js";
|
|
9
|
+
import { generateTypes } from "../lib/codegen.js";
|
|
10
|
+
import { renderQuery } from "../lib/query-builder.js";
|
|
11
|
+
import { type QuerySession } from "../lib/session.js";
|
|
12
|
+
import { validateQuery } from "../lib/validator.js";
|
|
13
|
+
|
|
14
|
+
const SCHEMA_LEVEL_ERROR_MARKERS = ["must define one or more fields"];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Render → validate → codegen → assemble. Shared finalizer for every typed
|
|
18
|
+
* intent function (`buildList`, `buildDetail`, …).
|
|
19
|
+
*
|
|
20
|
+
* Never throws. Failure modes surface as entries in `warnings[]`:
|
|
21
|
+
* - `Validation: <msg>` — non-schema-level errors from `graphql-js validate()`.
|
|
22
|
+
* - `Validation: schema check skipped (<msg>)` — `validate()` itself crashed.
|
|
23
|
+
* - `Codegen: <msg>` — `generateTypes()` threw; `types` becomes `// Type generation failed: <msg>`.
|
|
24
|
+
*
|
|
25
|
+
* Schema-level errors (e.g. "Input Object type X must define one or more fields"
|
|
26
|
+
* raised by malformed UIAPI schemas, not by the user's query) are filtered out
|
|
27
|
+
* per FR-9.2.
|
|
28
|
+
*
|
|
29
|
+
* `primingNote` (if provided) is prepended to `warnings[]` so callers can surface
|
|
30
|
+
* the FR-13.3 lazy-prime notification without a separate channel. `extraWarnings`
|
|
31
|
+
* are appended after schema-validation warnings — used by intent builders to
|
|
32
|
+
* surface non-validator findings (e.g. malformed `$var` placeholders).
|
|
33
|
+
*/
|
|
34
|
+
export function buildOutput(
|
|
35
|
+
session: QuerySession,
|
|
36
|
+
schema: GraphQLSchema,
|
|
37
|
+
primingNote?: string,
|
|
38
|
+
extraWarnings: string[] = [],
|
|
39
|
+
): ToolOutput {
|
|
40
|
+
const query = renderQuery(session);
|
|
41
|
+
const warnings: string[] = primingNote ? [primingNote] : [];
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const errors = validateQuery(schema, query);
|
|
45
|
+
for (const err of errors) {
|
|
46
|
+
if (SCHEMA_LEVEL_ERROR_MARKERS.some((m) => err.message.includes(m))) continue;
|
|
47
|
+
warnings.push(`Validation: ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
+
warnings.push(`Validation: schema check skipped (${message})`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let types: string;
|
|
55
|
+
try {
|
|
56
|
+
types = generateTypes(session, schema);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
59
|
+
types = `// Type generation failed: ${message}`;
|
|
60
|
+
warnings.push(`Codegen: ${message}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const variables: VariableInfo[] = session.variables.map((v) => ({
|
|
64
|
+
name: v.name,
|
|
65
|
+
type: v.type,
|
|
66
|
+
required: v.type.endsWith("!"),
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
warnings.push(...extraWarnings);
|
|
70
|
+
|
|
71
|
+
return { query, variables, types, warnings };
|
|
72
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { buildOutput } from "./build-output.js";
|
|
8
|
+
import { getSchemaWithPriming } from "./get-schema-with-priming.js";
|
|
9
|
+
import { type RawSpec, type RawOperation, type ToolOutput } from "./types.js";
|
|
10
|
+
import { applyCommand } from "../lib/apply-command.js";
|
|
11
|
+
import { assertGraphqlName } from "../lib/graphql-name.js";
|
|
12
|
+
import { type PrimeDeps } from "../lib/prime-schema.js";
|
|
13
|
+
import { createSession } from "../lib/session.js";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_OP_NAME: Record<RawOperation, string> = {
|
|
16
|
+
query: "RawQuery",
|
|
17
|
+
mutation: "RawMutation",
|
|
18
|
+
aggregate: "RawAggregate",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build an arbitrary UIAPI operation from CLI-style commands. Implements
|
|
23
|
+
* `sf_gql_raw` — the low-level escape hatch (FR-12) for operations the typed
|
|
24
|
+
* tools do not model (cross-union selections, custom mutations).
|
|
25
|
+
*
|
|
26
|
+
* `operation` selects the session root (query/mutation/aggregate); the agent
|
|
27
|
+
* hand-drives every selection. No typed-tool sugar (no aggregations[] handling,
|
|
28
|
+
* no auto-pagination, no Id defaulting).
|
|
29
|
+
*
|
|
30
|
+
* Fails fast: the first command that cannot be applied throws
|
|
31
|
+
* `command <i> (<cmd>): <why>`. Throws on an invalid `typeName` (must be a valid
|
|
32
|
+
* GraphQL Name), auth-missing / introspection failure (via getSchemaWithPriming)
|
|
33
|
+
* and on empty `commands`. Never throws on validation or codegen failure (those
|
|
34
|
+
* surface as warnings[]).
|
|
35
|
+
*/
|
|
36
|
+
export async function buildRaw(spec: RawSpec, deps?: PrimeDeps): Promise<ToolOutput> {
|
|
37
|
+
if (!spec.commands || spec.commands.length === 0) {
|
|
38
|
+
throw new Error("buildRaw: commands must contain at least one command");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const operation: RawOperation = spec.operation ?? "query";
|
|
42
|
+
const { schema, primingNote, instanceUrl } = await getSchemaWithPriming(spec.org, deps);
|
|
43
|
+
|
|
44
|
+
const session = createSession(spec.org, operation, instanceUrl);
|
|
45
|
+
// `typeName` lands only in the rendered operation-name position (buildOutput
|
|
46
|
+
// runs codegen without a typeName option), so it carries the GraphQL Name
|
|
47
|
+
// constraint just like the typed builders' `operationName`.
|
|
48
|
+
session.operationName = spec.typeName ?? DEFAULT_OP_NAME[operation];
|
|
49
|
+
assertGraphqlName(session.operationName, "buildRaw", "typeName");
|
|
50
|
+
|
|
51
|
+
spec.commands.forEach((cmd, i) => {
|
|
52
|
+
try {
|
|
53
|
+
applyCommand(session, schema, cmd);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
56
|
+
throw new Error(`command ${i} (${cmd}): ${msg}`);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return buildOutput(session, schema, primingNote);
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { buildMutation } from "./build-mutation.js";
|
|
8
|
+
import { type ToolOutput, type UpdateSpec } from "./types.js";
|
|
9
|
+
import { type PrimeDeps } from "../lib/prime-schema.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build a UIAPI update mutation against a Salesforce org. Implements
|
|
13
|
+
* `sf_gql_update` intent for the graphiti MCP server (FR-5.6). Thin
|
|
14
|
+
* wrapper around `buildMutation` with `op = "Update"`; see that helper
|
|
15
|
+
* for behavior details.
|
|
16
|
+
*/
|
|
17
|
+
export async function buildUpdate(spec: UpdateSpec, deps?: PrimeDeps): Promise<ToolOutput> {
|
|
18
|
+
return buildMutation(spec, "Update", deps);
|
|
19
|
+
}
|