@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,1202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any -- graphiti traverses untyped schema/introspection JSON; see follow-up to replace with `unknown` + narrowing */
|
|
7
|
+
|
|
8
|
+
import type { GraphQLSchema } from "graphql";
|
|
9
|
+
import {
|
|
10
|
+
formatArgsDirectoryListing,
|
|
11
|
+
formatDirectoryListing,
|
|
12
|
+
formatInputDirectoryListing,
|
|
13
|
+
formatVariablesListing,
|
|
14
|
+
parseSearchTerms,
|
|
15
|
+
parseSearchRegex,
|
|
16
|
+
type AliasEntry,
|
|
17
|
+
type FieldLongInfo,
|
|
18
|
+
} from "../lib/formatter.js";
|
|
19
|
+
import { getCachedObjectInfo } from "../lib/object-info.js";
|
|
20
|
+
import { renderQuery } from "../lib/query-builder.js";
|
|
21
|
+
import {
|
|
22
|
+
deepSetArg,
|
|
23
|
+
deepSetVariableValue,
|
|
24
|
+
formatPath,
|
|
25
|
+
getArgsFieldPath,
|
|
26
|
+
getChildren,
|
|
27
|
+
getFocusedNodeAtPath,
|
|
28
|
+
getInputSubPath,
|
|
29
|
+
getNavigationContext,
|
|
30
|
+
isAliasedSegment,
|
|
31
|
+
isArgsSegment,
|
|
32
|
+
isFragmentSegment,
|
|
33
|
+
normalizeFragmentSegment,
|
|
34
|
+
isInArgsContext,
|
|
35
|
+
listInstancesAtPath,
|
|
36
|
+
parseVariablePath,
|
|
37
|
+
pathKey,
|
|
38
|
+
queryNavToSchemaPath,
|
|
39
|
+
selectLeaf,
|
|
40
|
+
setVariableDefault,
|
|
41
|
+
setVariableRuntimeValue,
|
|
42
|
+
syncFocusFromNavigationPath,
|
|
43
|
+
toSchemaPath,
|
|
44
|
+
type FieldProjectionNode,
|
|
45
|
+
type OperationType,
|
|
46
|
+
type ProjectionNode,
|
|
47
|
+
type QuerySession,
|
|
48
|
+
} from "../lib/session.js";
|
|
49
|
+
import {
|
|
50
|
+
getRootFields,
|
|
51
|
+
getSchema,
|
|
52
|
+
resolveArgByName,
|
|
53
|
+
resolveFieldOnPath,
|
|
54
|
+
resolveInputPath,
|
|
55
|
+
resolvePath,
|
|
56
|
+
type InputWalkerResult,
|
|
57
|
+
type WalkerResult,
|
|
58
|
+
} from "../lib/walker.js";
|
|
59
|
+
|
|
60
|
+
// Re-export for use by other modules and external consumers.
|
|
61
|
+
export type { WalkerResult, InputWalkerResult, QuerySession, FieldProjectionNode, ProjectionNode };
|
|
62
|
+
|
|
63
|
+
// ── CommandError ──────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
export class CommandError extends Error {
|
|
66
|
+
exitCode: number;
|
|
67
|
+
constructor(message: string, exitCode = 1) {
|
|
68
|
+
super(message);
|
|
69
|
+
this.name = "CommandError";
|
|
70
|
+
this.exitCode = exitCode;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const EXIT_CODES = {
|
|
75
|
+
SUCCESS: 0,
|
|
76
|
+
USER_ERROR: 1,
|
|
77
|
+
VALIDATION_FAILURE: 2,
|
|
78
|
+
EXECUTION_FAILURE: 3,
|
|
79
|
+
AUTH_FAILURE: 4,
|
|
80
|
+
} as const;
|
|
81
|
+
|
|
82
|
+
// ── Schema helper ─────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
export function getSessionSchema(session: QuerySession): GraphQLSchema {
|
|
85
|
+
if (!session.instanceUrl) {
|
|
86
|
+
throw new CommandError(
|
|
87
|
+
`Session ${session.id} has no instanceUrl. Recreate it with \`graphiti query new <org>\`.`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return getSchema(session.instanceUrl);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Internal helpers ──────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
export function walkerResultAtPath(
|
|
96
|
+
session: QuerySession,
|
|
97
|
+
navPath = session.navigationPath,
|
|
98
|
+
): WalkerResult {
|
|
99
|
+
const schema = getSessionSchema(session);
|
|
100
|
+
const schemaPath = queryNavToSchemaPath(navPath);
|
|
101
|
+
return resolvePath(schema, session.operation, schemaPath);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function parsePathInput(currentPath: string[], rawPath: string): string[] {
|
|
105
|
+
if (!rawPath || rawPath === ".") return [...currentPath];
|
|
106
|
+
const absolute = rawPath.startsWith("/");
|
|
107
|
+
const parts = rawPath.split("/").filter((part) => part.length > 0);
|
|
108
|
+
const base = absolute ? [] : [...currentPath];
|
|
109
|
+
|
|
110
|
+
for (const part of parts) {
|
|
111
|
+
if (part === ".") continue;
|
|
112
|
+
if (part === "..") {
|
|
113
|
+
base.pop();
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (part === "@args" && base.length > 0 && base[base.length - 1] === "@args") {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
base.push(normalizeFragmentSegment(part));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return base;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function resolveAliasSegments(session: QuerySession, navPath: string[]): string[] {
|
|
126
|
+
const result = [...navPath];
|
|
127
|
+
const queryIdx = result.indexOf("query");
|
|
128
|
+
if (queryIdx === -1) return result;
|
|
129
|
+
|
|
130
|
+
let parentId: string | null = null;
|
|
131
|
+
for (let i = queryIdx + 1; i < result.length; i++) {
|
|
132
|
+
const seg = result[i];
|
|
133
|
+
if (isArgsSegment(seg) || isFragmentSegment(seg) || isAliasedSegment(seg)) break;
|
|
134
|
+
|
|
135
|
+
const children = getChildren(session, parentId);
|
|
136
|
+
const aliasMatch = children.find(
|
|
137
|
+
(n): n is FieldProjectionNode => n.kind === "field" && n.alias === seg,
|
|
138
|
+
);
|
|
139
|
+
if (aliasMatch) {
|
|
140
|
+
result[i] = `${aliasMatch.alias}(${aliasMatch.fieldName})`;
|
|
141
|
+
const schemaPath = toSchemaPath(result.slice(queryIdx + 1, i + 1));
|
|
142
|
+
session.focusByPath[pathKey(schemaPath)] = aliasMatch.id;
|
|
143
|
+
parentId = aliasMatch.id;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const fieldName = seg;
|
|
148
|
+
const schemaPath = toSchemaPath(result.slice(queryIdx + 1, i + 1));
|
|
149
|
+
const focusedId = session.focusByPath[pathKey(schemaPath)];
|
|
150
|
+
const node =
|
|
151
|
+
children.find((n) => n.id === focusedId) ??
|
|
152
|
+
children.find((n) => n.kind === "field" && n.fieldName === fieldName);
|
|
153
|
+
parentId = node?.id ?? null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function resolveDirectoryPathInSession(session: QuerySession, rawPath: string): string[] {
|
|
160
|
+
let resolved = parsePathInput(session.navigationPath, rawPath);
|
|
161
|
+
|
|
162
|
+
if (resolved.length === 0) return resolved;
|
|
163
|
+
|
|
164
|
+
if (resolved.length > 0 && resolved[0] !== "query" && resolved[0] !== "variables") {
|
|
165
|
+
resolved = ["query", ...resolved];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const ctx = getNavigationContext(resolved);
|
|
169
|
+
|
|
170
|
+
if (ctx === "root") {
|
|
171
|
+
throw new Error("At root, navigate into `query/` or `variables/`.");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (ctx === "variables") {
|
|
175
|
+
return resolveVariablesPath(session, resolved);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
resolved = resolveAliasSegments(session, resolved);
|
|
179
|
+
|
|
180
|
+
if (isInArgsContext(resolved)) {
|
|
181
|
+
return resolveArgsPath(session, resolved);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const schemaPath = queryNavToSchemaPath(resolved);
|
|
185
|
+
if (schemaPath.length === 0) return resolved;
|
|
186
|
+
|
|
187
|
+
const schema = getSessionSchema(session);
|
|
188
|
+
const wr = resolvePath(schema, session.operation, schemaPath);
|
|
189
|
+
if (wr.isLeaf) {
|
|
190
|
+
const last = resolved[resolved.length - 1];
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Cannot cd into leaf node ${formatPath(resolved)} (${wr.typeName}). Use \`select ${last}\` instead.`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return resolved;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function resolveArgsPath(session: QuerySession, resolved: string[]): string[] {
|
|
200
|
+
const schema = getSessionSchema(session);
|
|
201
|
+
const fieldSchemaPath = getArgsFieldPath(resolved);
|
|
202
|
+
const inputSubPath = getInputSubPath(resolved);
|
|
203
|
+
|
|
204
|
+
const wr = resolvePath(schema, session.operation, fieldSchemaPath);
|
|
205
|
+
if (wr.args.length === 0) {
|
|
206
|
+
throw new Error(`Field at ${formatPath(fieldSchemaPath)} has no arguments.`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (inputSubPath.length === 0) return resolved;
|
|
210
|
+
|
|
211
|
+
const argName = inputSubPath[0];
|
|
212
|
+
const argInfo = resolveArgByName(schema, wr, argName);
|
|
213
|
+
const rawTypeName = argInfo.typeName;
|
|
214
|
+
|
|
215
|
+
const remaining = inputSubPath.slice(1);
|
|
216
|
+
if (remaining.length === 0) {
|
|
217
|
+
const inputResult = resolveInputPath(schema, rawTypeName, []);
|
|
218
|
+
if (inputResult.isLeaf && !inputResult.isList) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Cannot cd into "${argName}" — it is a scalar (${inputResult.typeName}). Use \`assign ${argName} <value>\` instead.`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return resolved;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const inputResult = resolveInputPath(schema, rawTypeName, remaining);
|
|
227
|
+
if (inputResult.isLeaf && !inputResult.isList) {
|
|
228
|
+
const last = remaining[remaining.length - 1];
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Cannot cd into "${last}" — it is a scalar (${inputResult.typeName}). Use \`assign\` to set its value.`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return resolved;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function resolveVariablesPath(session: QuerySession, resolved: string[]): string[] {
|
|
238
|
+
if (resolved.length === 1) return resolved;
|
|
239
|
+
|
|
240
|
+
const varParsed = parseVariablePath(resolved);
|
|
241
|
+
if (!varParsed) throw new Error("Invalid variables path.");
|
|
242
|
+
|
|
243
|
+
const variable = session.variables.find((v) => v.name === varParsed.varName);
|
|
244
|
+
if (!variable) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`Variable "$${varParsed.varName}" is not defined. Use \`define $${varParsed.varName} <Type>\` to create it.`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (varParsed.inputSubPath.length === 0) {
|
|
251
|
+
const schema = getSessionSchema(session);
|
|
252
|
+
const rawTypeName = variable.type.replace(/[![\]]/g, "");
|
|
253
|
+
const inputResult = resolveInputPath(schema, rawTypeName, []);
|
|
254
|
+
if (inputResult.isLeaf && !inputResult.isList) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Cannot cd into "$${varParsed.varName}" — it is a scalar type (${variable.type}). Use \`assign\` to set its value.`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return resolved;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const schema = getSessionSchema(session);
|
|
263
|
+
const rawTypeName = variable.type.replace(/[![\]]/g, "");
|
|
264
|
+
const inputResult = resolveInputPath(schema, rawTypeName, varParsed.inputSubPath);
|
|
265
|
+
if (inputResult.isLeaf && !inputResult.isList) {
|
|
266
|
+
const last = varParsed.inputSubPath[varParsed.inputSubPath.length - 1];
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Cannot cd into "${last}" — it is a scalar (${inputResult.typeName}). Use \`assign\` to set its value.`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return resolved;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function getCurrentInstances(session: QuerySession): ProjectionNode[] {
|
|
276
|
+
if (session.navigationPath.length === 0) return [];
|
|
277
|
+
return listInstancesAtPath(session, toSchemaPath(session.navigationPath));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function getActiveInstanceId(session: QuerySession): string | undefined {
|
|
281
|
+
return session.focusByPath[pathKey(toSchemaPath(session.navigationPath))];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function buildSelectionInfo(session: QuerySession): {
|
|
285
|
+
selectedFields: Set<string>;
|
|
286
|
+
optionalFields: Set<string>;
|
|
287
|
+
aliases: AliasEntry[];
|
|
288
|
+
} {
|
|
289
|
+
const activeNode = getFocusedNodeAtPath(
|
|
290
|
+
session,
|
|
291
|
+
queryNavToSchemaPath(session.navigationPath),
|
|
292
|
+
false,
|
|
293
|
+
);
|
|
294
|
+
const parentId = activeNode?.id ?? null;
|
|
295
|
+
const children = getChildren(session, parentId);
|
|
296
|
+
|
|
297
|
+
const selectedFields = new Set<string>();
|
|
298
|
+
const optionalFields = new Set<string>();
|
|
299
|
+
const aliases: AliasEntry[] = [];
|
|
300
|
+
|
|
301
|
+
for (const child of children) {
|
|
302
|
+
if (child.kind === "field") {
|
|
303
|
+
selectedFields.add(child.fieldName);
|
|
304
|
+
if (child.directives.some((d) => d.name === "optional")) {
|
|
305
|
+
optionalFields.add(child.fieldName);
|
|
306
|
+
}
|
|
307
|
+
if (child.alias) {
|
|
308
|
+
aliases.push({
|
|
309
|
+
alias: child.alias,
|
|
310
|
+
fieldName: child.fieldName,
|
|
311
|
+
argCount: Object.keys(child.args).length,
|
|
312
|
+
isActive: session.focusByPath[pathKey(child.schemaPath)] === child.id,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return { selectedFields, optionalFields, aliases };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function buildFieldLongInfo(
|
|
322
|
+
session: QuerySession,
|
|
323
|
+
wr: WalkerResult,
|
|
324
|
+
): Map<string, FieldLongInfo> {
|
|
325
|
+
const map = new Map<string, FieldLongInfo>();
|
|
326
|
+
const sObjectName = detectSObjectName(session);
|
|
327
|
+
const objInfo = sObjectName ? getCachedObjectInfo(session.orgAlias, sObjectName) : null;
|
|
328
|
+
|
|
329
|
+
if (!objInfo) return map;
|
|
330
|
+
|
|
331
|
+
const metaByApi = new Map(objInfo.fields.map((f) => [f.apiName, f]));
|
|
332
|
+
const relByName = new Map(
|
|
333
|
+
objInfo.fields.filter((f) => f.relationshipName).map((f) => [f.relationshipName!, f]),
|
|
334
|
+
);
|
|
335
|
+
const childRelByName = new Map(
|
|
336
|
+
objInfo.childRelationships
|
|
337
|
+
.filter((cr) => cr.relationshipName)
|
|
338
|
+
.map((cr) => [cr.relationshipName!, cr]),
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
for (const field of wr.fields) {
|
|
342
|
+
const info: FieldLongInfo = {
|
|
343
|
+
required: false,
|
|
344
|
+
createable: true,
|
|
345
|
+
updateable: true,
|
|
346
|
+
defaultedOnCreate: false,
|
|
347
|
+
filterable: true,
|
|
348
|
+
sortable: true,
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const meta = metaByApi.get(field.name);
|
|
352
|
+
if (meta) {
|
|
353
|
+
info.required = meta.required;
|
|
354
|
+
info.createable = meta.createable;
|
|
355
|
+
info.updateable = meta.updateable;
|
|
356
|
+
info.defaultedOnCreate = meta.defaultedOnCreate;
|
|
357
|
+
info.filterable = meta.filterable;
|
|
358
|
+
info.sortable = meta.sortable;
|
|
359
|
+
info.label = meta.label ?? undefined;
|
|
360
|
+
info.dataType = meta.dataType ?? undefined;
|
|
361
|
+
info.nameField = meta.nameField || undefined;
|
|
362
|
+
info.compound = meta.compound || undefined;
|
|
363
|
+
info.compoundFieldName = meta.compoundFieldName ?? undefined;
|
|
364
|
+
info.extraTypeInfo = meta.extraTypeInfo ?? undefined;
|
|
365
|
+
info.calculated = meta.calculated || undefined;
|
|
366
|
+
info.custom = meta.custom || undefined;
|
|
367
|
+
info.inlineHelpText = meta.inlineHelpText ?? undefined;
|
|
368
|
+
if (meta.precision > 0) info.precision = meta.precision;
|
|
369
|
+
if (meta.scale > 0) info.scale = meta.scale;
|
|
370
|
+
|
|
371
|
+
if (meta.reference && meta.referenceToInfos.length > 0) {
|
|
372
|
+
info.referenceTargets = meta.referenceToInfos.map((r) => r.apiName);
|
|
373
|
+
const nameFields = meta.referenceToInfos.flatMap((r) => r.nameFields);
|
|
374
|
+
if (nameFields.length > 0) info.referenceNameFields = nameFields;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const relMeta = relByName.get(field.name);
|
|
379
|
+
if (relMeta && relMeta.reference && relMeta.referenceToInfos.length > 0) {
|
|
380
|
+
info.referenceTargets = relMeta.referenceToInfos.map((r) => r.apiName);
|
|
381
|
+
const nameFields = relMeta.referenceToInfos.flatMap((r) => r.nameFields);
|
|
382
|
+
if (nameFields.length > 0) info.referenceNameFields = nameFields;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const cr = childRelByName.get(field.name);
|
|
386
|
+
if (cr) {
|
|
387
|
+
info.childRelTarget = `${cr.childObjectApiName}.${cr.fieldName}`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const picklist = objInfo.picklists.find((p) => p.apiName === field.name);
|
|
391
|
+
if (picklist && picklist.values.length > 0) {
|
|
392
|
+
info.picklistValues = picklist.values
|
|
393
|
+
.map((v) => v.value)
|
|
394
|
+
.filter((v): v is string => v !== null);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
map.set(field.name, info);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return map;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function formatAliasContext(session: QuerySession): string | null {
|
|
404
|
+
if (session.navigationPath.length === 0) return null;
|
|
405
|
+
const ctx = getNavigationContext(session.navigationPath);
|
|
406
|
+
if (ctx !== "query" || isInArgsContext(session.navigationPath)) return null;
|
|
407
|
+
|
|
408
|
+
const schemaPath = queryNavToSchemaPath(session.navigationPath);
|
|
409
|
+
if (schemaPath.length === 0) return null;
|
|
410
|
+
const instances = listInstancesAtPath(session, schemaPath);
|
|
411
|
+
const fieldInstances = instances.filter((n): n is FieldProjectionNode => n.kind === "field");
|
|
412
|
+
|
|
413
|
+
if (fieldInstances.length === 0) return null;
|
|
414
|
+
|
|
415
|
+
const hasAliases = fieldInstances.some((n) => n.alias);
|
|
416
|
+
if (fieldInstances.length === 1 && !hasAliases) return null;
|
|
417
|
+
|
|
418
|
+
const activeId = session.focusByPath[pathKey(schemaPath)];
|
|
419
|
+
|
|
420
|
+
if (fieldInstances.length === 1) {
|
|
421
|
+
const inst = fieldInstances[0];
|
|
422
|
+
const argCount = Object.keys(inst.args).length;
|
|
423
|
+
const argPart = argCount > 0 ? ` [${argCount} arg${argCount === 1 ? "" : "s"}]` : "";
|
|
424
|
+
return `Alias: ${inst.alias}${argPart}`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const parts = fieldInstances.map((inst) => {
|
|
428
|
+
const isActive = inst.id === activeId || (!activeId && inst === fieldInstances[0]);
|
|
429
|
+
const name = inst.alias ?? "(unnamed)";
|
|
430
|
+
const argCount = Object.keys(inst.args).length;
|
|
431
|
+
const argPart = argCount > 0 ? ` [${argCount} arg${argCount === 1 ? "" : "s"}]` : "";
|
|
432
|
+
return `${isActive ? "* " : " "}${name}${argPart}`;
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
return `Aliases: ${parts.join(" | ")}`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function detectSObjectName(session: QuerySession, overridePath?: string[]): string | null {
|
|
439
|
+
const schemaPath = overridePath ?? queryNavToSchemaPath(session.navigationPath);
|
|
440
|
+
|
|
441
|
+
if (session.operation === "mutation") {
|
|
442
|
+
const idx = schemaPath.indexOf("uiapi");
|
|
443
|
+
if (idx !== -1 && schemaPath[idx + 1]) {
|
|
444
|
+
const match = schemaPath[idx + 1].match(/^(\w+?)(?:Create|Update|Delete)$/);
|
|
445
|
+
if (match) return match[1];
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const queryIdx = schemaPath.indexOf("query");
|
|
450
|
+
if (queryIdx !== -1 && schemaPath[queryIdx + 1]) {
|
|
451
|
+
return schemaPath[queryIdx + 1];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const aggIdx = schemaPath.indexOf("aggregate");
|
|
455
|
+
if (aggIdx !== -1 && schemaPath[aggIdx + 1]) {
|
|
456
|
+
return schemaPath[aggIdx + 1];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function printDirectory(
|
|
463
|
+
session: QuerySession,
|
|
464
|
+
opts: {
|
|
465
|
+
search?: string;
|
|
466
|
+
regex?: string;
|
|
467
|
+
long?: boolean;
|
|
468
|
+
all?: boolean;
|
|
469
|
+
showFields?: boolean;
|
|
470
|
+
dataCloud?: boolean;
|
|
471
|
+
} = {},
|
|
472
|
+
): void {
|
|
473
|
+
const ctx = getNavigationContext(session.navigationPath);
|
|
474
|
+
|
|
475
|
+
if (session.navigationPath.length === 0 || ctx === "root") {
|
|
476
|
+
console.log(" query/");
|
|
477
|
+
console.log(" variables/");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (ctx === "variables") {
|
|
482
|
+
printVariablesDirectory(session, opts);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (isInArgsContext(session.navigationPath)) {
|
|
487
|
+
printArgsDirectory(session, opts);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const schemaPath = queryNavToSchemaPath(session.navigationPath);
|
|
492
|
+
if (schemaPath.length === 0) {
|
|
493
|
+
const schema = getSessionSchema(session);
|
|
494
|
+
let rootFields = getRootFields(schema, session.operation);
|
|
495
|
+
const rootMatcher = opts.regex
|
|
496
|
+
? parseSearchRegex(opts.regex)
|
|
497
|
+
: opts.search
|
|
498
|
+
? parseSearchTerms(opts.search)
|
|
499
|
+
: undefined;
|
|
500
|
+
if (rootMatcher) {
|
|
501
|
+
rootFields = rootFields.filter((f) => rootMatcher.test(f.name));
|
|
502
|
+
}
|
|
503
|
+
for (const f of rootFields) {
|
|
504
|
+
console.log(` ${f.name}/`);
|
|
505
|
+
}
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const walkerResult = walkerResultAtPath(session);
|
|
510
|
+
const { selectedFields, optionalFields, aliases } = buildSelectionInfo(session);
|
|
511
|
+
|
|
512
|
+
let fieldLongInfo: Map<string, FieldLongInfo> | undefined;
|
|
513
|
+
if (opts.long) {
|
|
514
|
+
fieldLongInfo = buildFieldLongInfo(session, walkerResult);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (opts.showFields !== false) {
|
|
518
|
+
console.log(
|
|
519
|
+
formatDirectoryListing(walkerResult, {
|
|
520
|
+
searchPattern: opts.search,
|
|
521
|
+
regexPattern: opts.regex,
|
|
522
|
+
long: opts.long,
|
|
523
|
+
all: opts.all,
|
|
524
|
+
hasArgs: walkerResult.args.length > 0,
|
|
525
|
+
aliases,
|
|
526
|
+
selectedFields,
|
|
527
|
+
optionalFields,
|
|
528
|
+
fieldLongInfo,
|
|
529
|
+
dataCloud: opts.dataCloud,
|
|
530
|
+
}),
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export function printArgsDirectory(
|
|
536
|
+
session: QuerySession,
|
|
537
|
+
opts: {
|
|
538
|
+
search?: string;
|
|
539
|
+
regex?: string;
|
|
540
|
+
long?: boolean;
|
|
541
|
+
all?: boolean;
|
|
542
|
+
showFields?: boolean;
|
|
543
|
+
} = {},
|
|
544
|
+
): void {
|
|
545
|
+
const schema = getSessionSchema(session);
|
|
546
|
+
const fieldSchemaPath = getArgsFieldPath(session.navigationPath);
|
|
547
|
+
const inputSubPath = getInputSubPath(session.navigationPath);
|
|
548
|
+
const wr = resolvePath(schema, session.operation, fieldSchemaPath);
|
|
549
|
+
|
|
550
|
+
const node = getFocusedNodeAtPath(session, fieldSchemaPath, false);
|
|
551
|
+
const currentArgs = node && node.kind === "field" ? node.args : {};
|
|
552
|
+
|
|
553
|
+
if (inputSubPath.length === 0) {
|
|
554
|
+
console.log(
|
|
555
|
+
formatArgsDirectoryListing(wr.args, !!opts.long, currentArgs, opts.search, opts.regex),
|
|
556
|
+
);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const argName = inputSubPath[0];
|
|
561
|
+
const argInfo = resolveArgByName(schema, wr, argName);
|
|
562
|
+
const rawTypeName = argInfo.typeName;
|
|
563
|
+
const remaining = inputSubPath.slice(1);
|
|
564
|
+
const inputResult = resolveInputPath(schema, rawTypeName, remaining);
|
|
565
|
+
|
|
566
|
+
let currentValues: Record<string, unknown> | undefined;
|
|
567
|
+
let listElementCount: number | undefined;
|
|
568
|
+
if (node && node.kind === "field") {
|
|
569
|
+
const raw = node.args[argName];
|
|
570
|
+
if (raw) {
|
|
571
|
+
try {
|
|
572
|
+
let parsed = JSON.parse(raw);
|
|
573
|
+
for (const seg of remaining) {
|
|
574
|
+
if (parsed === null || parsed === undefined) break;
|
|
575
|
+
if (/^\d+$/.test(seg)) {
|
|
576
|
+
parsed = Array.isArray(parsed) ? parsed[Number(seg)] : undefined;
|
|
577
|
+
} else {
|
|
578
|
+
parsed = typeof parsed === "object" ? parsed[seg] : undefined;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (inputResult.isList && Array.isArray(parsed)) {
|
|
582
|
+
listElementCount = parsed.length;
|
|
583
|
+
currentValues = {};
|
|
584
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
585
|
+
currentValues[String(i)] = parsed[i];
|
|
586
|
+
}
|
|
587
|
+
} else if (typeof parsed === "object" && parsed !== null) {
|
|
588
|
+
currentValues = parsed as Record<string, unknown>;
|
|
589
|
+
}
|
|
590
|
+
} catch {
|
|
591
|
+
/* no current values */
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
console.log(
|
|
597
|
+
formatInputDirectoryListing(
|
|
598
|
+
inputResult,
|
|
599
|
+
!!opts.long,
|
|
600
|
+
currentValues,
|
|
601
|
+
listElementCount,
|
|
602
|
+
opts.search,
|
|
603
|
+
opts.regex,
|
|
604
|
+
),
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export function printVariablesDirectory(
|
|
609
|
+
session: QuerySession,
|
|
610
|
+
opts: {
|
|
611
|
+
search?: string;
|
|
612
|
+
regex?: string;
|
|
613
|
+
long?: boolean;
|
|
614
|
+
all?: boolean;
|
|
615
|
+
showFields?: boolean;
|
|
616
|
+
} = {},
|
|
617
|
+
): void {
|
|
618
|
+
const varParsed = parseVariablePath(session.navigationPath);
|
|
619
|
+
|
|
620
|
+
if (!varParsed) {
|
|
621
|
+
console.log(formatVariablesListing(session.variables, !!opts.long));
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const variable = session.variables.find((v) => v.name === varParsed.varName);
|
|
626
|
+
if (!variable) {
|
|
627
|
+
console.log(`Variable "$${varParsed.varName}" is not defined.`);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const schema = getSessionSchema(session);
|
|
632
|
+
const rawTypeName = variable.type.replace(/[![\]]/g, "");
|
|
633
|
+
const inputResult = resolveInputPath(schema, rawTypeName, varParsed.inputSubPath);
|
|
634
|
+
|
|
635
|
+
let currentValues: Record<string, unknown> | undefined;
|
|
636
|
+
let listElementCount: number | undefined;
|
|
637
|
+
if (variable.runtimeValue) {
|
|
638
|
+
try {
|
|
639
|
+
let parsed = JSON.parse(variable.runtimeValue);
|
|
640
|
+
for (const seg of varParsed.inputSubPath) {
|
|
641
|
+
if (parsed === null || parsed === undefined) break;
|
|
642
|
+
if (/^\d+$/.test(seg)) {
|
|
643
|
+
parsed = Array.isArray(parsed) ? parsed[Number(seg)] : undefined;
|
|
644
|
+
} else {
|
|
645
|
+
parsed = typeof parsed === "object" ? parsed[seg] : undefined;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (inputResult.isList && Array.isArray(parsed)) {
|
|
649
|
+
listElementCount = parsed.length;
|
|
650
|
+
currentValues = {};
|
|
651
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
652
|
+
currentValues[String(i)] = parsed[i];
|
|
653
|
+
}
|
|
654
|
+
} else if (typeof parsed === "object" && parsed !== null) {
|
|
655
|
+
currentValues = parsed as Record<string, unknown>;
|
|
656
|
+
}
|
|
657
|
+
} catch {
|
|
658
|
+
/* no current values */
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
console.log(
|
|
663
|
+
formatInputDirectoryListing(
|
|
664
|
+
inputResult,
|
|
665
|
+
!!opts.long,
|
|
666
|
+
currentValues,
|
|
667
|
+
listElementCount,
|
|
668
|
+
opts.search,
|
|
669
|
+
opts.regex,
|
|
670
|
+
),
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export function printQuery(session: QuerySession): void {
|
|
675
|
+
console.log("Query:");
|
|
676
|
+
console.log(renderQuery(session));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export function requireFieldDirectory(session: QuerySession): string[] {
|
|
680
|
+
const ctx = getNavigationContext(session.navigationPath);
|
|
681
|
+
if (ctx !== "query") {
|
|
682
|
+
throw new Error("This command requires a query field directory. Navigate into query/ first.");
|
|
683
|
+
}
|
|
684
|
+
const schemaPath = queryNavToSchemaPath(session.navigationPath);
|
|
685
|
+
if (schemaPath.length === 0) {
|
|
686
|
+
throw new Error(
|
|
687
|
+
"You are at the root of the query tree. Navigate into a field first (e.g. `cd uiapi`).",
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
const last = session.navigationPath[session.navigationPath.length - 1];
|
|
691
|
+
if (isFragmentSegment(last)) {
|
|
692
|
+
throw new Error(
|
|
693
|
+
"The current directory is an inline fragment. Arguments and aliases apply to field directories only.",
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
return schemaPath;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export function dotPathToSlashPath(dotPath: string): string {
|
|
700
|
+
const segments: string[] = [];
|
|
701
|
+
let i = 0;
|
|
702
|
+
while (i < dotPath.length) {
|
|
703
|
+
if (dotPath[i] === ".") {
|
|
704
|
+
i++;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (dotPath[i] === "[") {
|
|
708
|
+
const close = dotPath.indexOf("]", i);
|
|
709
|
+
if (close === -1) {
|
|
710
|
+
segments.push(dotPath.slice(i));
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
segments.push(normalizeFragmentSegment(dotPath.slice(i, close + 1)));
|
|
714
|
+
i = close + 1;
|
|
715
|
+
} else {
|
|
716
|
+
let end = dotPath.indexOf(".", i);
|
|
717
|
+
if (end === -1) end = dotPath.length;
|
|
718
|
+
segments.push(normalizeFragmentSegment(dotPath.slice(i, end)));
|
|
719
|
+
i = end;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return segments.join("/");
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
export function selectLeafInSession(session: QuerySession, leafSpec: string, alias?: string): void {
|
|
726
|
+
syncFocusFromNavigationPath(session);
|
|
727
|
+
const schema = getSessionSchema(session);
|
|
728
|
+
|
|
729
|
+
let normalizedSpec: string;
|
|
730
|
+
if (leafSpec.includes(".") && !leafSpec.includes("/")) {
|
|
731
|
+
normalizedSpec = dotPathToSlashPath(leafSpec);
|
|
732
|
+
} else if (leafSpec.includes("/") && leafSpec.includes(".")) {
|
|
733
|
+
const parts = leafSpec.split("/");
|
|
734
|
+
const lastPart = parts.pop()!;
|
|
735
|
+
if (lastPart.includes(".")) {
|
|
736
|
+
const dotParts = dotPathToSlashPath(lastPart);
|
|
737
|
+
normalizedSpec = [...parts, ...dotParts.split("/")].join("/");
|
|
738
|
+
} else {
|
|
739
|
+
normalizedSpec = leafSpec;
|
|
740
|
+
}
|
|
741
|
+
} else {
|
|
742
|
+
normalizedSpec = leafSpec;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (
|
|
746
|
+
normalizedSpec.includes("/") &&
|
|
747
|
+
!normalizedSpec.startsWith("/") &&
|
|
748
|
+
!normalizedSpec.startsWith("query/") &&
|
|
749
|
+
!normalizedSpec.startsWith("variables/") &&
|
|
750
|
+
getNavigationContext(session.navigationPath) === "root"
|
|
751
|
+
) {
|
|
752
|
+
const firstSeg = normalizedSpec.split("/")[0];
|
|
753
|
+
const rootFields = getRootFields(schema, session.operation);
|
|
754
|
+
if (rootFields.some((f) => f.name === firstSeg)) {
|
|
755
|
+
normalizedSpec = `query/${normalizedSpec}`;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (normalizedSpec.includes("/")) {
|
|
760
|
+
const parts = normalizedSpec.split("/");
|
|
761
|
+
const leafName = parts.pop()!;
|
|
762
|
+
const dirPath = parts.join("/");
|
|
763
|
+
|
|
764
|
+
let resolved = parsePathInput(session.navigationPath, dirPath);
|
|
765
|
+
resolved = resolveAliasSegments(session, resolved);
|
|
766
|
+
const schemaPath =
|
|
767
|
+
getNavigationContext(resolved) === "query"
|
|
768
|
+
? queryNavToSchemaPath(resolved)
|
|
769
|
+
: toSchemaPath(resolved);
|
|
770
|
+
|
|
771
|
+
let fieldResult: WalkerResult;
|
|
772
|
+
try {
|
|
773
|
+
fieldResult = resolveFieldOnPath(schema, session.operation, schemaPath, leafName);
|
|
774
|
+
} catch (error: any) {
|
|
775
|
+
if ((leafName === "value" || leafName === "displayValue") && parts.length >= 1) {
|
|
776
|
+
try {
|
|
777
|
+
const dirResult = resolvePath(schema, session.operation, schemaPath);
|
|
778
|
+
if (dirResult.isLeaf) {
|
|
779
|
+
const scalarFieldName = schemaPath[schemaPath.length - 1];
|
|
780
|
+
const parentSchemaPath = schemaPath.slice(0, -1);
|
|
781
|
+
selectLeaf(session, [...parentSchemaPath, scalarFieldName], alias);
|
|
782
|
+
console.log(
|
|
783
|
+
` (auto-corrected: ${scalarFieldName} is a ${dirResult.typeName} scalar — selected directly without .${leafName})`,
|
|
784
|
+
);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
} catch {
|
|
788
|
+
/* fall through to original error */
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Auto-inject Record/ for mutation payloads: when a field isn't found on a Payload type,
|
|
793
|
+
// find the Payload ancestor in the schemaPath and inject Record/ after it
|
|
794
|
+
if (session.operation === "mutation" && error.message?.includes("Payload")) {
|
|
795
|
+
try {
|
|
796
|
+
// Walk the schemaPath to find where the Payload type is
|
|
797
|
+
for (let pi = 1; pi <= schemaPath.length; pi++) {
|
|
798
|
+
const candidatePath = schemaPath.slice(0, pi);
|
|
799
|
+
let parentResult: WalkerResult;
|
|
800
|
+
try {
|
|
801
|
+
parentResult = resolvePath(schema, session.operation, candidatePath);
|
|
802
|
+
} catch {
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
if (
|
|
806
|
+
parentResult.typeName.endsWith("Payload") &&
|
|
807
|
+
parentResult.fields.some((f) => f.name === "Record")
|
|
808
|
+
) {
|
|
809
|
+
// Reconstruct path with Record/ injected after the Payload
|
|
810
|
+
const afterPayload = schemaPath.slice(pi);
|
|
811
|
+
const newSchemaPath = [...candidatePath, "Record", ...afterPayload];
|
|
812
|
+
const recordResult = resolveFieldOnPath(
|
|
813
|
+
schema,
|
|
814
|
+
session.operation,
|
|
815
|
+
newSchemaPath,
|
|
816
|
+
leafName,
|
|
817
|
+
);
|
|
818
|
+
if (recordResult.isLeaf) {
|
|
819
|
+
selectLeaf(session, [...newSchemaPath, leafName], alias);
|
|
820
|
+
console.log(
|
|
821
|
+
` (auto-injected Record/: path adjusted to include Record/ inside ${parentResult.typeName})`,
|
|
822
|
+
);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (
|
|
826
|
+
recordResult.typeName.endsWith("Value") &&
|
|
827
|
+
recordResult.fields.some((f) => f.name === "value")
|
|
828
|
+
) {
|
|
829
|
+
selectLeaf(session, [...newSchemaPath, leafName, "value"], alias);
|
|
830
|
+
console.log(
|
|
831
|
+
` (auto-injected Record/.../${leafName}/value inside ${parentResult.typeName})`,
|
|
832
|
+
);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
} catch {
|
|
839
|
+
/* fall through to original error */
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (session.navigationPath.length < 4 && parts[0] === "edges") {
|
|
844
|
+
throw new Error(
|
|
845
|
+
`${error.message}\nHint: "edges" is part of a connection type. Navigate to the connection first with \`cd query/uiapi/query/<SObject>\`, then select fields.`,
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
throw error;
|
|
850
|
+
}
|
|
851
|
+
if (!fieldResult.isLeaf) {
|
|
852
|
+
const leaves = fieldResult.fields
|
|
853
|
+
.filter((f) => f.typeKind === "SCALAR" || f.typeKind === "ENUM")
|
|
854
|
+
.map((f) => f.name);
|
|
855
|
+
if (fieldResult.typeName.endsWith("Value") && leaves.includes("value")) {
|
|
856
|
+
selectLeaf(session, [...schemaPath, leafName, "value"], alias);
|
|
857
|
+
console.log(
|
|
858
|
+
` (auto-expanded: ${leafName} is a ${fieldResult.typeName} wrapper — selected ${leafName}/value)`,
|
|
859
|
+
);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const leafHint = leaves.length > 0 ? ` Available leaves: ${leaves.join(", ")}` : "";
|
|
863
|
+
throw new Error(
|
|
864
|
+
`Cannot select "${leafName}" from ${formatPath(schemaPath)} because it resolves to ${fieldResult.typeName}. Navigate into it with \`cd ${normalizedSpec.replace(/\/[^/]+$/, "")}\` and select one of its leaf children.${leafHint}`,
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
selectLeaf(session, [...schemaPath, leafName], alias);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const schemaPath = queryNavToSchemaPath(session.navigationPath);
|
|
872
|
+
const fieldResult = resolveFieldOnPath(schema, session.operation, schemaPath, normalizedSpec);
|
|
873
|
+
if (!fieldResult.isLeaf) {
|
|
874
|
+
const leaves = fieldResult.fields
|
|
875
|
+
.filter((f) => f.typeKind === "SCALAR" || f.typeKind === "ENUM")
|
|
876
|
+
.map((f) => f.name);
|
|
877
|
+
if (fieldResult.typeName.endsWith("Value") && leaves.includes("value")) {
|
|
878
|
+
selectLeaf(session, [...schemaPath, normalizedSpec, "value"], alias);
|
|
879
|
+
console.log(
|
|
880
|
+
` (auto-expanded: ${normalizedSpec} is a ${fieldResult.typeName} wrapper — selected ${normalizedSpec}/value)`,
|
|
881
|
+
);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const leafHint = leaves.length > 0 ? ` Available leaves: ${leaves.join(", ")}` : "";
|
|
885
|
+
throw new Error(
|
|
886
|
+
`Cannot select "${normalizedSpec}" from ${formatPath(schemaPath)} because it resolves to ${fieldResult.typeName}. Navigate into it with \`cd ${normalizedSpec}\` and select one of its leaf children.${leafHint}`,
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
selectLeaf(session, [...schemaPath, normalizedSpec], alias);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// ── Assignment / validation helpers ──────────────────────────────────────────
|
|
893
|
+
|
|
894
|
+
export function pathPointsToArgs(p: string): boolean {
|
|
895
|
+
return p.includes("@args") || (p.includes("/") && p.split("/").some((s) => s === "@args"));
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
export function resolveArgType(
|
|
899
|
+
session: QuerySession,
|
|
900
|
+
fieldSchemaPath: string[],
|
|
901
|
+
argName: string,
|
|
902
|
+
inputPath: string[],
|
|
903
|
+
): { expectedType: string; argTypeKind: string } {
|
|
904
|
+
const schema = getSessionSchema(session);
|
|
905
|
+
const wr = resolvePath(schema, session.operation, fieldSchemaPath);
|
|
906
|
+
const argInfo = resolveArgByName(schema, wr, argName);
|
|
907
|
+
|
|
908
|
+
if (inputPath.length > 0) {
|
|
909
|
+
const inputResult = resolveInputPath(schema, argInfo.typeName, inputPath);
|
|
910
|
+
return { expectedType: inputResult.typeName, argTypeKind: inputResult.kind };
|
|
911
|
+
}
|
|
912
|
+
return { expectedType: argInfo.typeName, argTypeKind: argInfo.typeKind };
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export function validateVariableAssignment(
|
|
916
|
+
session: QuerySession,
|
|
917
|
+
fieldSchemaPath: string[],
|
|
918
|
+
argName: string,
|
|
919
|
+
inputPath: string[],
|
|
920
|
+
varRef: string,
|
|
921
|
+
): void {
|
|
922
|
+
const cleanName = varRef.replace(/^\$/, "");
|
|
923
|
+
const variable = session.variables.find((v) => v.name === cleanName);
|
|
924
|
+
if (!variable) {
|
|
925
|
+
throw new CommandError(
|
|
926
|
+
`Variable "$${cleanName}" is not defined. Define it first with \`define $${cleanName} <path>\`.`,
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const { expectedType } = resolveArgType(session, fieldSchemaPath, argName, inputPath);
|
|
931
|
+
const varBaseType = variable.type.replace(/[![\]]/g, "");
|
|
932
|
+
|
|
933
|
+
if (varBaseType !== expectedType) {
|
|
934
|
+
throw new CommandError(
|
|
935
|
+
`Type mismatch: $${cleanName} is ${variable.type}, but ${argName}${inputPath.length > 0 ? "." + inputPath.join(".") : ""} expects ${expectedType}.`,
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
export function validateLiteralAssignment(
|
|
941
|
+
session: QuerySession,
|
|
942
|
+
fieldSchemaPath: string[],
|
|
943
|
+
argName: string,
|
|
944
|
+
inputPath: string[],
|
|
945
|
+
value: string,
|
|
946
|
+
contextHint: string,
|
|
947
|
+
): void {
|
|
948
|
+
const schema = getSessionSchema(session);
|
|
949
|
+
const wr = resolvePath(schema, session.operation, fieldSchemaPath);
|
|
950
|
+
const argInfo = resolveArgByName(schema, wr, argName);
|
|
951
|
+
const rawTypeName = argInfo.typeName;
|
|
952
|
+
|
|
953
|
+
const isJsonLiteral = /^\s*[{[]/.test(value);
|
|
954
|
+
|
|
955
|
+
if (inputPath.length > 0) {
|
|
956
|
+
const inputResult = resolveInputPath(schema, rawTypeName, inputPath);
|
|
957
|
+
if (!inputResult.isLeaf && !isJsonLiteral) {
|
|
958
|
+
throw new CommandError(
|
|
959
|
+
`Cannot assign to "${inputPath[inputPath.length - 1]}" — it is ${inputResult.typeName} (${inputResult.kind}). ` +
|
|
960
|
+
contextHint,
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
} else if (argInfo.typeKind !== "SCALAR" && argInfo.typeKind !== "ENUM" && !isJsonLiteral) {
|
|
964
|
+
throw new CommandError(
|
|
965
|
+
`Cannot assign a literal to "${argName}" — it is ${rawTypeName} (${argInfo.typeKind}). ` +
|
|
966
|
+
contextHint,
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if (isJsonLiteral) {
|
|
971
|
+
const sanitized = value.replace(/\$[a-zA-Z_]\w*/g, '"__var_placeholder__"');
|
|
972
|
+
try {
|
|
973
|
+
JSON.parse(sanitized);
|
|
974
|
+
} catch {
|
|
975
|
+
throw new CommandError(
|
|
976
|
+
`Invalid JSON for "${argName}": ${value.slice(0, 60)}${value.length > 60 ? "..." : ""}`,
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
export function assignViaPath(session: QuerySession, rawPath: string, value: string): void {
|
|
983
|
+
const normalized = rawPath.replace(/\./g, "/");
|
|
984
|
+
|
|
985
|
+
let resolved = parsePathInput(session.navigationPath, normalized);
|
|
986
|
+
resolved = resolveAliasSegments(session, resolved);
|
|
987
|
+
|
|
988
|
+
if (!isInArgsContext(resolved)) {
|
|
989
|
+
throw new CommandError(
|
|
990
|
+
`Path "${rawPath}" does not point into an @args/ directory. ` +
|
|
991
|
+
"Use a path like `@args/first` or `Account/@args/where`.",
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const fieldSchemaPath = getArgsFieldPath(resolved);
|
|
996
|
+
const inputSubPath = getInputSubPath(resolved);
|
|
997
|
+
|
|
998
|
+
if (inputSubPath.length === 0) {
|
|
999
|
+
throw new CommandError(
|
|
1000
|
+
"Path points to @args/ root. Specify a specific argument: e.g. `@args/first` or `@args/where`.",
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const argName = inputSubPath[0];
|
|
1005
|
+
const inputPath = inputSubPath.slice(1);
|
|
1006
|
+
|
|
1007
|
+
if (value.startsWith("$")) {
|
|
1008
|
+
validateVariableAssignment(session, fieldSchemaPath, argName, inputPath, value);
|
|
1009
|
+
} else {
|
|
1010
|
+
validateLiteralAssignment(
|
|
1011
|
+
session,
|
|
1012
|
+
fieldSchemaPath,
|
|
1013
|
+
argName,
|
|
1014
|
+
inputPath,
|
|
1015
|
+
value,
|
|
1016
|
+
`Assign to a deeper path (e.g. \`@args/${argName}/...\`) or assign a $variable reference.`,
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
deepSetArg(session, fieldSchemaPath, argName, inputPath, value);
|
|
1021
|
+
|
|
1022
|
+
emitObjectInfoWarnings(session, fieldSchemaPath, argName, inputPath, value);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
export function emitObjectInfoWarnings(
|
|
1026
|
+
session: QuerySession,
|
|
1027
|
+
fieldSchemaPath: string[],
|
|
1028
|
+
argName: string,
|
|
1029
|
+
inputPath: string[],
|
|
1030
|
+
value: string,
|
|
1031
|
+
): void {
|
|
1032
|
+
const sObjectName = detectSObjectName(session, fieldSchemaPath);
|
|
1033
|
+
if (!sObjectName) return;
|
|
1034
|
+
|
|
1035
|
+
const objInfo = getCachedObjectInfo(session.orgAlias, sObjectName);
|
|
1036
|
+
if (!objInfo) return;
|
|
1037
|
+
|
|
1038
|
+
const metaByApi = new Map(objInfo.fields.map((f) => [f.apiName, f]));
|
|
1039
|
+
|
|
1040
|
+
if ((argName === "where" || argName === "orderBy") && /^\s*[{[]/.test(value)) {
|
|
1041
|
+
try {
|
|
1042
|
+
const parsed = JSON.parse(value);
|
|
1043
|
+
const fieldNames = typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : [];
|
|
1044
|
+
for (const fn of fieldNames) {
|
|
1045
|
+
if (fn === "and" || fn === "or" || fn === "not") continue;
|
|
1046
|
+
const meta = metaByApi.get(fn);
|
|
1047
|
+
if (!meta) continue;
|
|
1048
|
+
if (argName === "where" && !meta.filterable) {
|
|
1049
|
+
console.log(
|
|
1050
|
+
` Warning: ${fn} is not filterable per ObjectInfo. This where clause may fail at runtime.`,
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
if (argName === "orderBy" && !meta.sortable) {
|
|
1054
|
+
console.log(
|
|
1055
|
+
` Warning: ${fn} is not sortable per ObjectInfo. This orderBy clause may fail at runtime.`,
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
} catch {
|
|
1060
|
+
/* not valid JSON — skip */
|
|
1061
|
+
}
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const fieldName = inputPath.length > 0 ? inputPath[0] : argName;
|
|
1066
|
+
const isInWhere = argName === "where" || inputPath.includes("where");
|
|
1067
|
+
const isInOrderBy = argName === "orderBy" || inputPath.includes("orderBy");
|
|
1068
|
+
const meta = metaByApi.get(fieldName);
|
|
1069
|
+
|
|
1070
|
+
if (meta) {
|
|
1071
|
+
if (isInWhere && !meta.filterable) {
|
|
1072
|
+
console.log(
|
|
1073
|
+
` Warning: ${fieldName} is not filterable per ObjectInfo. This where clause may fail at runtime.`,
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
if (isInOrderBy && !meta.sortable) {
|
|
1077
|
+
console.log(
|
|
1078
|
+
` Warning: ${fieldName} is not sortable per ObjectInfo. This orderBy clause may fail at runtime.`,
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Create-only field guard: warn when assigning a create-only field in an update mutation context
|
|
1083
|
+
const isMutationUpdate =
|
|
1084
|
+
session.operation === "mutation" && fieldSchemaPath.some((s) => /Update$/.test(s));
|
|
1085
|
+
if (isMutationUpdate && meta.createable && !meta.updateable) {
|
|
1086
|
+
console.log(
|
|
1087
|
+
` Warning: ${fieldName} is create-only (not updateable). Setting it in an update mutation will fail at runtime.`,
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (!value.startsWith("$") && !value.startsWith("{") && !value.startsWith("[")) {
|
|
1093
|
+
const picklist = objInfo.picklists.find((p) => p.apiName === fieldName);
|
|
1094
|
+
if (picklist && picklist.values.length > 0) {
|
|
1095
|
+
const cleanValue = value.replace(/^"|"$/g, "");
|
|
1096
|
+
const validValues = picklist.values
|
|
1097
|
+
.map((v) => v.value)
|
|
1098
|
+
.filter((v): v is string => v !== null);
|
|
1099
|
+
if (validValues.length > 0 && !validValues.includes(cleanValue)) {
|
|
1100
|
+
console.log(` Warning: "${cleanValue}" is not a known picklist value for ${fieldName}.`);
|
|
1101
|
+
console.log(` Valid values: ${validValues.join(", ")}`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
export function assignInArgsContext(
|
|
1108
|
+
session: QuerySession,
|
|
1109
|
+
segments: string[],
|
|
1110
|
+
value: string,
|
|
1111
|
+
): void {
|
|
1112
|
+
const fieldSchemaPath = getArgsFieldPath(session.navigationPath);
|
|
1113
|
+
const currentInputSub = getInputSubPath(session.navigationPath);
|
|
1114
|
+
|
|
1115
|
+
const fullInputPath = [...currentInputSub, ...segments];
|
|
1116
|
+
|
|
1117
|
+
if (fullInputPath.length === 0) {
|
|
1118
|
+
throw new CommandError("Usage: assign <name> <value>");
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const argName = fullInputPath[0];
|
|
1122
|
+
const inputPath = fullInputPath.slice(1);
|
|
1123
|
+
|
|
1124
|
+
if (value.startsWith("$")) {
|
|
1125
|
+
validateVariableAssignment(session, fieldSchemaPath, argName, inputPath, value);
|
|
1126
|
+
} else {
|
|
1127
|
+
validateLiteralAssignment(
|
|
1128
|
+
session,
|
|
1129
|
+
fieldSchemaPath,
|
|
1130
|
+
argName,
|
|
1131
|
+
inputPath,
|
|
1132
|
+
value,
|
|
1133
|
+
`Navigate into it with \`cd\` and assign its leaf fields, or assign a $variable reference.`,
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
deepSetArg(session, fieldSchemaPath, argName, inputPath, value);
|
|
1138
|
+
|
|
1139
|
+
emitObjectInfoWarnings(session, fieldSchemaPath, argName, inputPath, value);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
export function assignInVariablesContext(
|
|
1143
|
+
session: QuerySession,
|
|
1144
|
+
segments: string[],
|
|
1145
|
+
value: string,
|
|
1146
|
+
setDefault = false,
|
|
1147
|
+
): void {
|
|
1148
|
+
const varParsed = parseVariablePath(session.navigationPath);
|
|
1149
|
+
|
|
1150
|
+
if (!varParsed) {
|
|
1151
|
+
if (segments.length === 1 && segments[0].startsWith("$")) {
|
|
1152
|
+
const cleanName = segments[0].replace(/^\$/, "");
|
|
1153
|
+
const variable = session.variables.find((v) => v.name === cleanName);
|
|
1154
|
+
if (!variable) throw new CommandError(`Variable "$${cleanName}" is not defined.`);
|
|
1155
|
+
if (setDefault) {
|
|
1156
|
+
setVariableDefault(session, cleanName, value);
|
|
1157
|
+
console.log(`Set default for $${cleanName} = ${value}`);
|
|
1158
|
+
} else {
|
|
1159
|
+
setVariableRuntimeValue(session, cleanName, value);
|
|
1160
|
+
}
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
throw new CommandError(
|
|
1164
|
+
"Navigate into a variable first (e.g. `cd $myVar`) or specify: `assign [--default] $varName <value>`.",
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
if (setDefault) {
|
|
1169
|
+
throw new CommandError(
|
|
1170
|
+
"--default can only be used on a whole variable (e.g. `assign --default $varName <value>`), not on nested paths.",
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const fullPath = [...varParsed.inputSubPath, ...segments];
|
|
1175
|
+
deepSetVariableValue(session, varParsed.varName, fullPath, value);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
export function inferTypeFromArgsPath(
|
|
1179
|
+
schema: GraphQLSchema,
|
|
1180
|
+
operation: OperationType,
|
|
1181
|
+
fieldSchemaPath: string[],
|
|
1182
|
+
inputSubPath: string[],
|
|
1183
|
+
): { inferredType: string; argName: string; argInputPath: string[] } {
|
|
1184
|
+
const argName = inputSubPath[0];
|
|
1185
|
+
const wr = resolvePath(schema, operation, fieldSchemaPath);
|
|
1186
|
+
const argInfo = resolveArgByName(schema, wr, argName);
|
|
1187
|
+
|
|
1188
|
+
let inferredType: string;
|
|
1189
|
+
if (inputSubPath.length === 1) {
|
|
1190
|
+
inferredType = argInfo.typeName;
|
|
1191
|
+
if (argInfo.isList) inferredType = `[${inferredType}]`;
|
|
1192
|
+
if (argInfo.isNonNull) inferredType += "!";
|
|
1193
|
+
} else {
|
|
1194
|
+
const nestedPath = inputSubPath.slice(1);
|
|
1195
|
+
const inputResult = resolveInputPath(schema, argInfo.typeName, nestedPath);
|
|
1196
|
+
inferredType = inputResult.typeName;
|
|
1197
|
+
if (inputResult.isList) inferredType = `[${inferredType}]`;
|
|
1198
|
+
if (inputResult.isNonNull) inferredType += "!";
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
return { inferredType, argName, argInputPath: inputSubPath.slice(1) };
|
|
1202
|
+
}
|