@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,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { type GraphQLSchema } from "graphql";
|
|
8
|
+
import { primeSchemaWithLock, type PrimeDeps } from "../lib/prime-schema.js";
|
|
9
|
+
import { getSchema } from "../lib/walker.js";
|
|
10
|
+
|
|
11
|
+
export interface SchemaWithPriming {
|
|
12
|
+
schema: GraphQLSchema;
|
|
13
|
+
instanceUrl: string;
|
|
14
|
+
primingNote?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the schema for `orgAlias`, lazily priming the on-disk cache on miss.
|
|
19
|
+
*
|
|
20
|
+
* - Cache hit → `{ schema, instanceUrl }` only.
|
|
21
|
+
* - Cache miss + auth resolves → introspects, writes cache, returns
|
|
22
|
+
* `{ schema, instanceUrl, primingNote: 'Note: Primed schema cache for "<org>" (took Nms)' }`.
|
|
23
|
+
* Callers thread the note into their `ToolOutput.warnings[]` per FR-13.3.
|
|
24
|
+
* - Auth missing or introspection failure → throws (FR-13.5 / FR-13.6).
|
|
25
|
+
*
|
|
26
|
+
* The returned schema is loaded by instance URL (auth-free fast path), so the
|
|
27
|
+
* cost of `getSchema(prime.instanceUrl)` is just a Map lookup after the first
|
|
28
|
+
* call per process.
|
|
29
|
+
*/
|
|
30
|
+
export async function getSchemaWithPriming(
|
|
31
|
+
orgAlias: string,
|
|
32
|
+
deps?: PrimeDeps,
|
|
33
|
+
): Promise<SchemaWithPriming> {
|
|
34
|
+
const prime = await primeSchemaWithLock(orgAlias, deps);
|
|
35
|
+
const schema = getSchema(prime.instanceUrl);
|
|
36
|
+
const base = { schema, instanceUrl: prime.instanceUrl };
|
|
37
|
+
return prime.cached
|
|
38
|
+
? base
|
|
39
|
+
: {
|
|
40
|
+
...base,
|
|
41
|
+
primingNote: `Note: Primed schema cache for "${orgAlias}" (took ${prime.durationMs}ms)`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 ChildRelationshipSpec } from "./types.js";
|
|
9
|
+
import { selectDottedFieldPath } from "../lib/path-selection.js";
|
|
10
|
+
import { deepSetArg, type QuerySession } from "../lib/session.js";
|
|
11
|
+
import { normalizeOrderBy, promoteVariables } from "../lib/variable-promotion.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Render a UIAPI child relationship onto an existing query session as
|
|
15
|
+
* `<rel> { edges { node { ... } } }`, with optional `first` / `filter` /
|
|
16
|
+
* `orderBy` arguments on the connection. `$varName` leaves in `filter` and
|
|
17
|
+
* `orderBy` promote to typed query variables (same machinery as the top-level
|
|
18
|
+
* connection).
|
|
19
|
+
*
|
|
20
|
+
* Shared by `buildList` and `buildDetail`. Behavior is identical between
|
|
21
|
+
* callers — the connection node path is the only thing that varies.
|
|
22
|
+
*/
|
|
23
|
+
export function selectChildRelationship(
|
|
24
|
+
session: QuerySession,
|
|
25
|
+
schema: GraphQLSchema,
|
|
26
|
+
parentNodePath: string[],
|
|
27
|
+
child: ChildRelationshipSpec,
|
|
28
|
+
): void {
|
|
29
|
+
const childConnectionPath = [...parentNodePath, child.relationshipName];
|
|
30
|
+
const childNodePath = [...childConnectionPath, "edges", "node"];
|
|
31
|
+
|
|
32
|
+
for (const field of child.fields) {
|
|
33
|
+
selectDottedFieldPath(session, schema, childNodePath, field);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (child.first !== undefined) {
|
|
37
|
+
deepSetArg(session, childConnectionPath, "first", [], String(child.first));
|
|
38
|
+
}
|
|
39
|
+
if (child.filter) {
|
|
40
|
+
promoteVariables(session, schema, childConnectionPath, "where", child.filter);
|
|
41
|
+
deepSetArg(session, childConnectionPath, "where", [], JSON.stringify(child.filter));
|
|
42
|
+
}
|
|
43
|
+
const childOrderBy = normalizeOrderBy(child.orderBy);
|
|
44
|
+
if (childOrderBy) {
|
|
45
|
+
promoteVariables(session, schema, childConnectionPath, "orderBy", childOrderBy);
|
|
46
|
+
deepSetArg(session, childConnectionPath, "orderBy", [], JSON.stringify(childOrderBy));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared envelope returned by every typed intent function. The MCP tool
|
|
9
|
+
* adapter JSON-stringifies this into the tool-call result; library
|
|
10
|
+
* consumers (CLI, eval harness) use it directly. Per FR-3 of the spec.
|
|
11
|
+
*/
|
|
12
|
+
export interface ToolOutput {
|
|
13
|
+
query: string;
|
|
14
|
+
variables: VariableInfo[];
|
|
15
|
+
types: string;
|
|
16
|
+
warnings: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface VariableInfo {
|
|
20
|
+
name: string;
|
|
21
|
+
type: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
description?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ChildRelationshipSpec {
|
|
27
|
+
relationshipName: string;
|
|
28
|
+
fields: string[];
|
|
29
|
+
first?: number;
|
|
30
|
+
filter?: Record<string, unknown>;
|
|
31
|
+
orderBy?: Record<string, unknown> | Record<string, unknown>[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ListSpec {
|
|
35
|
+
org: string;
|
|
36
|
+
object: string;
|
|
37
|
+
fields: string[];
|
|
38
|
+
parentFields?: string[];
|
|
39
|
+
childRelationships?: ChildRelationshipSpec[];
|
|
40
|
+
filter?: Record<string, unknown>;
|
|
41
|
+
orderBy?: Record<string, unknown> | Record<string, unknown>[];
|
|
42
|
+
first?: number;
|
|
43
|
+
scope?: string;
|
|
44
|
+
operationName?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DetailSpec {
|
|
48
|
+
org: string;
|
|
49
|
+
object: string;
|
|
50
|
+
fields: string[];
|
|
51
|
+
parentFields?: string[];
|
|
52
|
+
childRelationships?: ChildRelationshipSpec[];
|
|
53
|
+
/** Variable name (without leading `$`) for the record Id; default `"id"`. Declared as `<name>: ID!` per FR-5.5. */
|
|
54
|
+
idVariable?: string;
|
|
55
|
+
/** GraphQL operation name; default `<Object>Detail`. */
|
|
56
|
+
operationName?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type AggregationFunction = "count" | "countDistinct" | "sum" | "avg" | "min" | "max";
|
|
60
|
+
|
|
61
|
+
export const GROUP_BY_FUNCTIONS = [
|
|
62
|
+
"CALENDAR_MONTH",
|
|
63
|
+
"CALENDAR_QUARTER",
|
|
64
|
+
"CALENDAR_YEAR",
|
|
65
|
+
"DAY_IN_MONTH",
|
|
66
|
+
"DAY_IN_WEEK",
|
|
67
|
+
"DAY_IN_YEAR",
|
|
68
|
+
"FISCAL_MONTH",
|
|
69
|
+
"FISCAL_QUARTER",
|
|
70
|
+
"FISCAL_YEAR",
|
|
71
|
+
"HOUR_IN_DAY",
|
|
72
|
+
"CALENDAR_MONTH_IN_YEAR",
|
|
73
|
+
"FISCAL_MONTH_IN_YEAR",
|
|
74
|
+
"WEEK_IN_YEAR",
|
|
75
|
+
] as const;
|
|
76
|
+
|
|
77
|
+
export type GroupByFunction = (typeof GROUP_BY_FUNCTIONS)[number];
|
|
78
|
+
|
|
79
|
+
export interface GroupByDateElement {
|
|
80
|
+
field: string;
|
|
81
|
+
function: GroupByFunction;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type GroupByElement = string | GroupByDateElement;
|
|
85
|
+
|
|
86
|
+
export interface AggregationSpec {
|
|
87
|
+
function: AggregationFunction;
|
|
88
|
+
field?: string;
|
|
89
|
+
alias?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AggregateSpec {
|
|
93
|
+
org: string;
|
|
94
|
+
object: string;
|
|
95
|
+
groupBy?: GroupByElement[];
|
|
96
|
+
aggregations?: AggregationSpec[];
|
|
97
|
+
filter?: Record<string, unknown>;
|
|
98
|
+
orderBy?: Record<string, unknown> | Record<string, unknown>[];
|
|
99
|
+
first?: number;
|
|
100
|
+
operationName?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type DiscoverMode = "list_objects" | "describe_object" | "describe_field";
|
|
104
|
+
|
|
105
|
+
export interface DiscoverSpec {
|
|
106
|
+
org: string;
|
|
107
|
+
mode: DiscoverMode;
|
|
108
|
+
object?: string;
|
|
109
|
+
field?: string;
|
|
110
|
+
search?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface FieldDescription {
|
|
114
|
+
name: string;
|
|
115
|
+
label: string;
|
|
116
|
+
type: string;
|
|
117
|
+
filterable: boolean;
|
|
118
|
+
sortable: boolean;
|
|
119
|
+
nameField: boolean;
|
|
120
|
+
compound: boolean;
|
|
121
|
+
defaultedOnCreate: boolean;
|
|
122
|
+
picklistValues?: string[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface ObjectDescription {
|
|
126
|
+
name: string;
|
|
127
|
+
fields: FieldDescription[];
|
|
128
|
+
childRelationships: { relationshipName: string; childObject: string }[];
|
|
129
|
+
parentReferences: { field: string; targetObjects: string[] }[];
|
|
130
|
+
filterableFields: string[];
|
|
131
|
+
orderByExample: Record<string, unknown>;
|
|
132
|
+
requiredOnCreate: string[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type DiscoverOutput =
|
|
136
|
+
| {
|
|
137
|
+
mode: "list_objects";
|
|
138
|
+
objects: { name: string; label?: string }[];
|
|
139
|
+
warnings?: string[];
|
|
140
|
+
}
|
|
141
|
+
| { mode: "describe_object"; object: ObjectDescription; warnings?: string[] }
|
|
142
|
+
| { mode: "describe_field"; field: FieldDescription; warnings?: string[] };
|
|
143
|
+
|
|
144
|
+
export interface CreateSpec {
|
|
145
|
+
org: string;
|
|
146
|
+
object: string;
|
|
147
|
+
returnFields?: string[];
|
|
148
|
+
inputVariable?: string;
|
|
149
|
+
operationName?: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface UpdateSpec {
|
|
153
|
+
org: string;
|
|
154
|
+
object: string;
|
|
155
|
+
returnFields?: string[];
|
|
156
|
+
inputVariable?: string;
|
|
157
|
+
operationName?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface DeleteSpec {
|
|
161
|
+
org: string;
|
|
162
|
+
object: string;
|
|
163
|
+
inputVariable?: string;
|
|
164
|
+
operationName?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export type RawOperation = "query" | "mutation" | "aggregate";
|
|
168
|
+
|
|
169
|
+
export interface RawSpec {
|
|
170
|
+
org: string;
|
|
171
|
+
/**
|
|
172
|
+
* CLI-style commands applied in order to a transient session. v1 supports
|
|
173
|
+
* `select <path[:alias]>`, `set [<path>] <key>=<value>`, and
|
|
174
|
+
* `var $name <path> [default]`. See FR-12.
|
|
175
|
+
*/
|
|
176
|
+
commands: string[];
|
|
177
|
+
/** Operation root; default `"query"`. Sets the `createSession` operation. */
|
|
178
|
+
operation?: RawOperation;
|
|
179
|
+
/** Codegen type prefix / operation name override (FR-12.3). */
|
|
180
|
+
typeName?: string;
|
|
181
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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 { buildSchema, type GraphQLSchema } from "graphql";
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import { dispatchQueryCommand } from "../../commands/query.js";
|
|
10
|
+
import { applyCommand } from "../apply-command.js";
|
|
11
|
+
import { renderQuery } from "../query-builder.js";
|
|
12
|
+
import { createSession, saveSession, loadSession } from "../session.js";
|
|
13
|
+
import { primeSchemaCache } from "../walker.js";
|
|
14
|
+
|
|
15
|
+
const SCHEMA: GraphQLSchema = buildSchema(`
|
|
16
|
+
type Query { uiapi: UIAPI! }
|
|
17
|
+
type UIAPI { query: RecordQuery! }
|
|
18
|
+
type RecordQuery {
|
|
19
|
+
Case(first: Int, after: String, where: Case_Filter, orderBy: Case_OrderBy): CaseConnection!
|
|
20
|
+
}
|
|
21
|
+
input Case_Filter { Status: PicklistOperators, Subject: StringOperators }
|
|
22
|
+
input Case_OrderBy { CreatedDate: OrderByClause }
|
|
23
|
+
input PicklistOperators { eq: String, ne: String, in: [String!] }
|
|
24
|
+
input StringOperators { eq: String, like: String }
|
|
25
|
+
input OrderByClause { order: Order! }
|
|
26
|
+
enum Order { ASC DESC }
|
|
27
|
+
type CaseConnection { edges: [CaseEdge!]!, pageInfo: PageInfo! }
|
|
28
|
+
type CaseEdge { node: Case! }
|
|
29
|
+
type PageInfo { hasNextPage: Boolean!, endCursor: String }
|
|
30
|
+
type Case { Id: ID!, Subject: StringValue, Status: StringValue }
|
|
31
|
+
type StringValue { value: String }
|
|
32
|
+
`);
|
|
33
|
+
|
|
34
|
+
const ORG = "test-parity";
|
|
35
|
+
const ORG_URL = "https://test-parity.my.salesforce.com";
|
|
36
|
+
primeSchemaCache(ORG, SCHEMA);
|
|
37
|
+
primeSchemaCache(ORG_URL, SCHEMA);
|
|
38
|
+
|
|
39
|
+
// Each case = a sequence of commands run through BOTH engines; we compare the
|
|
40
|
+
// final rendered query. All set commands use an absolute field-path prefix
|
|
41
|
+
// (raw sessions never `cd`, so prefix-less forms are out of scope by design).
|
|
42
|
+
const CASES: { name: string; commands: string[] }[] = [
|
|
43
|
+
{ name: "select dotted leaf", commands: ["select uiapi/query/Case/edges/node/Subject/value"] },
|
|
44
|
+
{
|
|
45
|
+
name: "select with alias",
|
|
46
|
+
commands: ["select uiapi/query/Case/edges/node/Subject/value:subj"],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "select multiple leaves",
|
|
50
|
+
commands: ["select uiapi/query/Case/edges/node/Id uiapi/query/Case/edges/node/Subject/value"],
|
|
51
|
+
},
|
|
52
|
+
{ name: "set scalar arg", commands: ["set uiapi/query/Case first=5"] },
|
|
53
|
+
{ name: "set where shorthand → JSON", commands: ["set uiapi/query/Case where.Status=New"] },
|
|
54
|
+
{ name: "set where like on % wildcard", commands: ["set uiapi/query/Case where.Subject=Acme%"] },
|
|
55
|
+
{ name: "set orderBy shorthand", commands: ["set uiapi/query/Case orderBy=CreatedDate:DESC"] },
|
|
56
|
+
{
|
|
57
|
+
name: "set where=$var deferred binding",
|
|
58
|
+
commands: ["set uiapi/query/Case where.Status=$status"],
|
|
59
|
+
},
|
|
60
|
+
{ name: "set multiple specs", commands: ["set uiapi/query/Case first=5 where.Status=New"] },
|
|
61
|
+
{ name: "var declare+bind", commands: ["var $status uiapi/query/Case/@args/where/Status/eq"] },
|
|
62
|
+
{
|
|
63
|
+
name: "select + set + var sequence",
|
|
64
|
+
commands: [
|
|
65
|
+
"select uiapi/query/Case/edges/node/Subject/value",
|
|
66
|
+
"set uiapi/query/Case first=10",
|
|
67
|
+
"var $status uiapi/query/Case/@args/where/Status/eq",
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
function viaApplyCommand(commands: string[]): string {
|
|
73
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
74
|
+
for (const cmd of commands) applyCommand(session, SCHEMA, cmd);
|
|
75
|
+
return renderQuery(session);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function viaDispatcher(commands: string[]): Promise<string> {
|
|
79
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
80
|
+
saveSession(session);
|
|
81
|
+
const id = session.id;
|
|
82
|
+
for (const cmd of commands) {
|
|
83
|
+
const [verb, ...rest] = cmd.split(/\s+/);
|
|
84
|
+
await dispatchQueryCommand(id, verb, rest, {});
|
|
85
|
+
}
|
|
86
|
+
return renderQuery(loadSession(id));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe("lib/apply-command — parity with CLI dispatcher", () => {
|
|
90
|
+
for (const c of CASES) {
|
|
91
|
+
it(`renders identically: ${c.name}`, async () => {
|
|
92
|
+
const fromApply = viaApplyCommand(c.commands);
|
|
93
|
+
const fromDispatch = await viaDispatcher(c.commands);
|
|
94
|
+
expect(fromApply).toEqual(fromDispatch);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
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 { buildSchema, type GraphQLSchema } from "graphql";
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import { applyCommand } from "../apply-command.js";
|
|
10
|
+
import { renderQuery } from "../query-builder.js";
|
|
11
|
+
import { createSession } from "../session.js";
|
|
12
|
+
import { primeSchemaCache } from "../walker.js";
|
|
13
|
+
|
|
14
|
+
const SCHEMA: GraphQLSchema = buildSchema(`
|
|
15
|
+
type Query { uiapi: UIAPI! }
|
|
16
|
+
type UIAPI { query: RecordQuery! }
|
|
17
|
+
type RecordQuery {
|
|
18
|
+
Case(first: Int, after: String, where: Case_Filter, orderBy: Case_OrderBy): CaseConnection!
|
|
19
|
+
}
|
|
20
|
+
input Case_Filter { Status: PicklistOperators, Subject: StringOperators }
|
|
21
|
+
input Case_OrderBy { CreatedDate: OrderByClause }
|
|
22
|
+
input PicklistOperators { eq: String, ne: String, in: [String!] }
|
|
23
|
+
input StringOperators { eq: String, like: String }
|
|
24
|
+
input OrderByClause { order: Order! }
|
|
25
|
+
enum Order { ASC DESC }
|
|
26
|
+
type CaseConnection { edges: [CaseEdge!]!, pageInfo: PageInfo! }
|
|
27
|
+
type CaseEdge { node: Case! }
|
|
28
|
+
type PageInfo { hasNextPage: Boolean!, endCursor: String }
|
|
29
|
+
type Case { Id: ID!, Subject: StringValue, Status: StringValue }
|
|
30
|
+
type StringValue { value: String }
|
|
31
|
+
`);
|
|
32
|
+
|
|
33
|
+
const ORG = "test-apply";
|
|
34
|
+
const ORG_URL = `https://${ORG}.my.salesforce.com`;
|
|
35
|
+
primeSchemaCache(ORG_URL, SCHEMA);
|
|
36
|
+
|
|
37
|
+
describe("lib/apply-command — routing", () => {
|
|
38
|
+
it("throws on an unknown verb", () => {
|
|
39
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
40
|
+
expect(() => applyCommand(session, SCHEMA, "cd uiapi")).toThrow(/unknown command 'cd'/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("throws on an empty command", () => {
|
|
44
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
45
|
+
expect(() => applyCommand(session, SCHEMA, " ")).toThrow(/empty command/);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("lib/apply-command — select", () => {
|
|
50
|
+
it("selects a dotted leaf path with { value } wrapper", () => {
|
|
51
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
52
|
+
applyCommand(session, SCHEMA, "select uiapi/query/Case/edges/node/Subject/value");
|
|
53
|
+
const q = renderQuery(session);
|
|
54
|
+
expect(q).toMatch(/Subject\s*\{\s*value\s*\}/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("selects multiple space-separated leaves in one command", () => {
|
|
58
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
59
|
+
applyCommand(
|
|
60
|
+
session,
|
|
61
|
+
SCHEMA,
|
|
62
|
+
"select uiapi/query/Case/edges/node/Id uiapi/query/Case/edges/node/Subject/value",
|
|
63
|
+
);
|
|
64
|
+
const q = renderQuery(session);
|
|
65
|
+
expect(q).toMatch(/\bId\b/);
|
|
66
|
+
expect(q).toMatch(/Subject\s*\{\s*value\s*\}/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("applies a :alias to a selected leaf", () => {
|
|
70
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
71
|
+
applyCommand(session, SCHEMA, "select uiapi/query/Case/edges/node/Subject/value:subj");
|
|
72
|
+
const q = renderQuery(session);
|
|
73
|
+
// Alias is applied to the leaf field (value), not to the parent (Subject)
|
|
74
|
+
expect(q).toMatch(/subj:\s*value/);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("throws when no selectable item is supplied", () => {
|
|
78
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
79
|
+
expect(() => applyCommand(session, SCHEMA, "select")).toThrow();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("lib/apply-command — var", () => {
|
|
84
|
+
it("declares a variable with type inferred from the path and binds it", () => {
|
|
85
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
86
|
+
applyCommand(session, SCHEMA, "var $status uiapi/query/Case/@args/where/Status/eq");
|
|
87
|
+
const q = renderQuery(session);
|
|
88
|
+
// declared in the operation signature...
|
|
89
|
+
expect(q).toMatch(/\$status:\s*String/);
|
|
90
|
+
// ...and bound into the where arg.
|
|
91
|
+
expect(q).toMatch(/Status:\s*\{\s*eq:\s*\$status\s*\}/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("throws when no variable name is given", () => {
|
|
95
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
96
|
+
expect(() => applyCommand(session, SCHEMA, "var")).toThrow();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("lib/apply-command — set", () => {
|
|
101
|
+
it("sets a scalar arg via key=value with an absolute field path", () => {
|
|
102
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
103
|
+
applyCommand(session, SCHEMA, "set uiapi/query/Case first=5");
|
|
104
|
+
expect(renderQuery(session)).toMatch(/first:\s*5/);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("expands where.Field=Value into a filter object", () => {
|
|
108
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
109
|
+
applyCommand(session, SCHEMA, "set uiapi/query/Case where.Status=New");
|
|
110
|
+
expect(renderQuery(session)).toMatch(/Status:\s*\{\s*eq:\s*"?New"?\s*\}/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("auto-detects the like operator on a % wildcard value", () => {
|
|
114
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
115
|
+
applyCommand(session, SCHEMA, "set uiapi/query/Case where.Subject=Acme%");
|
|
116
|
+
expect(renderQuery(session)).toMatch(/Subject:\s*\{\s*like:/);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("expands orderBy=Field:DESC into an orderBy clause", () => {
|
|
120
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
121
|
+
applyCommand(session, SCHEMA, "set uiapi/query/Case orderBy=CreatedDate:DESC");
|
|
122
|
+
expect(renderQuery(session)).toMatch(/CreatedDate:\s*\{\s*order:\s*DESC\s*\}/);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("binds where.Field=$var as a deferred variable", () => {
|
|
126
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
127
|
+
applyCommand(session, SCHEMA, "set uiapi/query/Case where.Status=$status");
|
|
128
|
+
const q = renderQuery(session);
|
|
129
|
+
expect(q).toMatch(/\$status:/);
|
|
130
|
+
expect(q).toMatch(/Status:\s*\{\s*eq:\s*\$status\s*\}/);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("throws on a malformed set with no key=value", () => {
|
|
134
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
135
|
+
expect(() => applyCommand(session, SCHEMA, "set")).toThrow();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("lib/apply-command — set additional branches", () => {
|
|
140
|
+
it("accepts the legacy `set <path> <value>` pair form", () => {
|
|
141
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
142
|
+
// The pair-form branch passes the path verbatim to assignViaPath (no
|
|
143
|
+
// `query/` auto-prefix), so the path must carry the operation-root
|
|
144
|
+
// `query/` segment that resolveAliasSegments keys off — same form the
|
|
145
|
+
// CLI dispatcher's pair branch requires.
|
|
146
|
+
applyCommand(session, SCHEMA, "set query/uiapi/query/Case/@args/first 7");
|
|
147
|
+
expect(renderQuery(session)).toMatch(/first:\s*7/);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("cursor shorthand declares $after and selects pageInfo", () => {
|
|
151
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
152
|
+
// `cursor` must be mixed with a key=value so the parser routes through the
|
|
153
|
+
// hasKeyValue branch (which captures the field prefix). A bare
|
|
154
|
+
// `set <prefix> cursor` falls into the pair-loop and errors in both this
|
|
155
|
+
// port and the canonical CLI dispatcher, so the realistic form is mixed.
|
|
156
|
+
applyCommand(session, SCHEMA, "set uiapi/query/Case first=5 cursor");
|
|
157
|
+
const q = renderQuery(session);
|
|
158
|
+
expect(q).toMatch(/\$after:\s*String/);
|
|
159
|
+
expect(q).toMatch(/after:\s*\$after/);
|
|
160
|
+
expect(q).toMatch(/hasNextPage/);
|
|
161
|
+
expect(q).toMatch(/endCursor/);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("applies multiple specs in one set command", () => {
|
|
165
|
+
const session = createSession(ORG, "query", ORG_URL);
|
|
166
|
+
applyCommand(session, SCHEMA, "set uiapi/query/Case first=5 where.Status=New");
|
|
167
|
+
const q = renderQuery(session);
|
|
168
|
+
expect(q).toMatch(/first:\s*5/);
|
|
169
|
+
expect(q).toMatch(/Status:\s*\{\s*eq:\s*"?New"?\s*\}/);
|
|
170
|
+
});
|
|
171
|
+
});
|