@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,901 @@
|
|
|
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
|
+
* Barrel module — re-exports all query subcommands so existing imports
|
|
9
|
+
* like `from '../commands/query.js'` continue to work unchanged.
|
|
10
|
+
*/
|
|
11
|
+
// ── Re-exports from query-helpers ─────────────────────────────────────────────
|
|
12
|
+
export { CommandError, EXIT_CODES, getSessionSchema, walkerResultAtPath, parsePathInput, resolveAliasSegments, resolveDirectoryPathInSession, resolveArgsPath, resolveVariablesPath, getCurrentInstances, getActiveInstanceId, buildSelectionInfo, buildFieldLongInfo, formatAliasContext, detectSObjectName, printDirectory, printArgsDirectory, printVariablesDirectory, printQuery, requireFieldDirectory, dotPathToSlashPath, selectLeafInSession, pathPointsToArgs, resolveArgType, validateVariableAssignment, validateLiteralAssignment, assignViaPath, emitObjectInfoWarnings, assignInArgsContext, assignInVariablesContext, inferTypeFromArgsPath, } from "./query-helpers.js";
|
|
13
|
+
// ── Re-exports from navigate ──────────────────────────────────────────────────
|
|
14
|
+
export { queryPwd, queryLs, queryCd } from "./navigate.js";
|
|
15
|
+
// ── Re-exports from build ─────────────────────────────────────────────────────
|
|
16
|
+
export { querySelect, querySelectLs, queryRm, queryMkdir } from "./build.js";
|
|
17
|
+
// ── Re-exports from args ──────────────────────────────────────────────────────
|
|
18
|
+
export { queryAssign, queryUnassign, queryDefine } from "./args.js";
|
|
19
|
+
// ── Re-exports from review ────────────────────────────────────────────────────
|
|
20
|
+
export { queryShow, queryShowJson, queryShowQueryOnly, queryValidate, queryValidateAll, queryExecute, queryCodegen, buildSampleInputFields, validateStrictMutation, } from "./review.js";
|
|
21
|
+
// ── Re-exports from meta ──────────────────────────────────────────────────────
|
|
22
|
+
export { queryExample, printInputExamples, queryHelp } from "./meta.js";
|
|
23
|
+
// ── Re-exports from session-mgmt ──────────────────────────────────────────────
|
|
24
|
+
export { queryNew, queryClone, querySessionsList, querySessionsRm, querySessionsPrune, querySessionsClean, formatAge, parseDuration, } from "./session-mgmt.js";
|
|
25
|
+
// ── Imports used by the dispatcher ────────────────────────────────────────────
|
|
26
|
+
import { queryAssign, queryUnassign, queryDefine } from "./args.js";
|
|
27
|
+
import { querySelectLs, queryRm, queryMkdir } from "./build.js";
|
|
28
|
+
import { queryHelp } from "./meta.js";
|
|
29
|
+
import { queryPwd, queryLs, queryCd } from "./navigate.js";
|
|
30
|
+
import { printQuery, selectLeafInSession, getSessionSchema, buildSelectionInfo, buildFieldLongInfo, walkerResultAtPath, } from "./query-helpers.js";
|
|
31
|
+
import { CommandError } from "./query-helpers.js";
|
|
32
|
+
import { queryShow, queryShowJson, queryShowQueryOnly, queryValidate, queryValidateAll, queryExecute, queryCodegen, } from "./review.js";
|
|
33
|
+
import { queryClone, querySessionsList, querySessionsRm, querySessionsPrune, querySessionsClean, } from "./session-mgmt.js";
|
|
34
|
+
import { getOutputMode } from "../lib/command-registry.js";
|
|
35
|
+
import { renderQuery } from "../lib/query-builder.js";
|
|
36
|
+
import { formatHelp, isInteractiveMode } from "../lib/query-commands.js";
|
|
37
|
+
import { loadSession, saveSession, pushUndoSnapshot, popUndo, formatPath, getNavigationContext, isInArgsContext, queryNavToSchemaPath, getChildren, } from "../lib/session.js";
|
|
38
|
+
import { validateQuery } from "../lib/validator.js";
|
|
39
|
+
import { getRootFields } from "../lib/walker.js";
|
|
40
|
+
/**
|
|
41
|
+
* Parse inline key=value args with full shorthand support:
|
|
42
|
+
* - where.Field=Value → where={"Field":{"eq":"Value"}}
|
|
43
|
+
* - where.Field=$var → deferred variable binding
|
|
44
|
+
* - orderBy=Field:DESC → orderBy.Field.order=DESC
|
|
45
|
+
* - cursor → deferred $after variable definition
|
|
46
|
+
*/
|
|
47
|
+
function parseInlineSetArgs(tokens) {
|
|
48
|
+
const specs = [];
|
|
49
|
+
const deferredVarDefs = [];
|
|
50
|
+
// Normalize: split any space-containing tokens (e.g. from quoted shell args)
|
|
51
|
+
const expanded = [];
|
|
52
|
+
for (const t of tokens) {
|
|
53
|
+
if (t.includes(" ") && !t.startsWith("{") && !t.startsWith("[")) {
|
|
54
|
+
expanded.push(...t.split(/\s+/));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
expanded.push(t);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const hasCursor = expanded.includes("cursor");
|
|
61
|
+
const filteredNoCursor = hasCursor ? expanded.filter((t) => t !== "cursor") : expanded;
|
|
62
|
+
let fieldPrefix = "";
|
|
63
|
+
let startIdx = 0;
|
|
64
|
+
if (filteredNoCursor.length > 0 && !filteredNoCursor[0].includes("=")) {
|
|
65
|
+
fieldPrefix = filteredNoCursor[0];
|
|
66
|
+
startIdx = 1;
|
|
67
|
+
}
|
|
68
|
+
for (let i = startIdx; i < filteredNoCursor.length; i++) {
|
|
69
|
+
const token = filteredNoCursor[i];
|
|
70
|
+
const eqIdx = token.indexOf("=");
|
|
71
|
+
if (eqIdx > 0) {
|
|
72
|
+
let key = token.slice(0, eqIdx);
|
|
73
|
+
let val = token.slice(eqIdx + 1);
|
|
74
|
+
// Collect continuation tokens for multi-word where values (e.g. where.Status=On Hold)
|
|
75
|
+
const isWhereShorthand = /^(.*?)?where\.(\w+)(?:\.(\w+))?$/.test(key);
|
|
76
|
+
if (isWhereShorthand && !val.startsWith("$") && !val.startsWith("{")) {
|
|
77
|
+
while (i + 1 < filteredNoCursor.length) {
|
|
78
|
+
const next = filteredNoCursor[i + 1];
|
|
79
|
+
if (next.includes("=") || next === "cursor" || next.startsWith("--"))
|
|
80
|
+
break;
|
|
81
|
+
val += " " + next;
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// orderBy shorthand: orderBy=Field:DESC → orderBy.Field.order=DESC
|
|
86
|
+
if (key === "orderBy" || key.endsWith("/orderBy") || key.endsWith(".orderBy")) {
|
|
87
|
+
const orderMatch = val.match(/^(\w+):(ASC|DESC)$/i);
|
|
88
|
+
if (orderMatch) {
|
|
89
|
+
key = `${key}.${orderMatch[1]}.order`;
|
|
90
|
+
val = orderMatch[2].toUpperCase();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// where shorthand: where.Field=Value → where={"Field":{"eq":"Value"}}
|
|
94
|
+
const whereMatch = key.match(/^(.*?)?where\.(\w+)(?:\.(\w+))?$/);
|
|
95
|
+
if (whereMatch) {
|
|
96
|
+
const prefix = whereMatch[1] || "";
|
|
97
|
+
const field = whereMatch[2];
|
|
98
|
+
// Auto-detect `like` operator when value contains SQL wildcards (%) and no explicit operator
|
|
99
|
+
const op = whereMatch[3] ||
|
|
100
|
+
(val.includes("%") && !val.startsWith("$") && !val.startsWith("{") ? "like" : "eq");
|
|
101
|
+
// Warn on empty where value — likely shell expanded $var to empty string
|
|
102
|
+
if (val === "" && !isInteractiveMode()) {
|
|
103
|
+
const hint = `Warning: ${key}= has an empty value. Did the shell expand a $variable?\n Tip: use single quotes to prevent expansion: '${key}=$yourVar'`;
|
|
104
|
+
console.error(hint);
|
|
105
|
+
}
|
|
106
|
+
if (val.startsWith("$")) {
|
|
107
|
+
let resolvedPrefix = fieldPrefix;
|
|
108
|
+
if (resolvedPrefix &&
|
|
109
|
+
!resolvedPrefix.startsWith("/") &&
|
|
110
|
+
!resolvedPrefix.startsWith("query/") &&
|
|
111
|
+
!resolvedPrefix.startsWith("@args")) {
|
|
112
|
+
resolvedPrefix = `query/${resolvedPrefix}`;
|
|
113
|
+
}
|
|
114
|
+
const varPath = resolvedPrefix
|
|
115
|
+
? `${resolvedPrefix}/@args/${prefix}where/${field}/${op}`
|
|
116
|
+
: `${prefix}where/${field}/${op}`;
|
|
117
|
+
deferredVarDefs.push([val, varPath]);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
key = `${prefix}where`;
|
|
121
|
+
let parsedVal = val;
|
|
122
|
+
if (val === "null")
|
|
123
|
+
parsedVal = null;
|
|
124
|
+
else if (val === "true")
|
|
125
|
+
parsedVal = true;
|
|
126
|
+
else if (val === "false")
|
|
127
|
+
parsedVal = false;
|
|
128
|
+
else if (/^-?\d+(\.\d+)?$/.test(val))
|
|
129
|
+
parsedVal = Number(val);
|
|
130
|
+
else
|
|
131
|
+
parsedVal = val;
|
|
132
|
+
val = JSON.stringify({ [field]: { [op]: parsedVal } });
|
|
133
|
+
}
|
|
134
|
+
let resolvedPrefix = fieldPrefix;
|
|
135
|
+
if (resolvedPrefix &&
|
|
136
|
+
!resolvedPrefix.startsWith("/") &&
|
|
137
|
+
!resolvedPrefix.startsWith("query/") &&
|
|
138
|
+
!resolvedPrefix.startsWith("@args")) {
|
|
139
|
+
resolvedPrefix = `query/${resolvedPrefix}`;
|
|
140
|
+
}
|
|
141
|
+
const fullPath = resolvedPrefix ? `${resolvedPrefix}/@args/${key}` : key;
|
|
142
|
+
specs.push({ path: fullPath, value: val });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { specs, deferredVarDefs, cursorFieldPrefix: fieldPrefix, hasCursor };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Builds a JSON result object for commands that don't have their own JSON handler.
|
|
149
|
+
* Called after the command executes to capture the result state.
|
|
150
|
+
*/
|
|
151
|
+
function buildJsonResult(command, sessionId, extra = {}) {
|
|
152
|
+
try {
|
|
153
|
+
const session = loadSession(sessionId);
|
|
154
|
+
const result = { command, ...extra };
|
|
155
|
+
switch (command) {
|
|
156
|
+
case "pwd":
|
|
157
|
+
result.path = formatPath(session.navigationPath);
|
|
158
|
+
break;
|
|
159
|
+
case "cd":
|
|
160
|
+
result.path = formatPath(session.navigationPath);
|
|
161
|
+
break;
|
|
162
|
+
case "ls": {
|
|
163
|
+
result.path = formatPath(session.navigationPath);
|
|
164
|
+
const ctx = getNavigationContext(session.navigationPath);
|
|
165
|
+
if (ctx === "query" && !isInArgsContext(session.navigationPath)) {
|
|
166
|
+
const schemaPath = queryNavToSchemaPath(session.navigationPath);
|
|
167
|
+
if (schemaPath.length > 0) {
|
|
168
|
+
try {
|
|
169
|
+
const schema = getSessionSchema(session);
|
|
170
|
+
const _wr = getRootFields(schema, session.operation);
|
|
171
|
+
const walkerRes = walkerResultAtPath(session);
|
|
172
|
+
const { selectedFields } = buildSelectionInfo(session);
|
|
173
|
+
const fieldLongInfo = buildFieldLongInfo(session, walkerRes);
|
|
174
|
+
result.type = { name: walkerRes.typeName, kind: walkerRes.kind };
|
|
175
|
+
result.hasArgs = walkerRes.args.length > 0;
|
|
176
|
+
result.args = walkerRes.args.map((a) => ({
|
|
177
|
+
name: a.name,
|
|
178
|
+
type: a.typeName,
|
|
179
|
+
required: a.isNonNull,
|
|
180
|
+
}));
|
|
181
|
+
result.fields = walkerRes.fields.map((f) => {
|
|
182
|
+
const entry = {
|
|
183
|
+
name: f.name,
|
|
184
|
+
type: f.typeName,
|
|
185
|
+
kind: f.typeKind,
|
|
186
|
+
selected: selectedFields.has(f.name),
|
|
187
|
+
};
|
|
188
|
+
const longInfo = fieldLongInfo.get(f.name);
|
|
189
|
+
if (longInfo)
|
|
190
|
+
entry.metadata = longInfo;
|
|
191
|
+
return entry;
|
|
192
|
+
});
|
|
193
|
+
result.totalFields = walkerRes.fields.length;
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
/* path may not resolve */
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "select":
|
|
203
|
+
case "drop":
|
|
204
|
+
case "alias":
|
|
205
|
+
case "set":
|
|
206
|
+
case "unset":
|
|
207
|
+
case "var":
|
|
208
|
+
case "optional":
|
|
209
|
+
case "reset":
|
|
210
|
+
case "undo":
|
|
211
|
+
case "clone":
|
|
212
|
+
result.query = renderQuery(session);
|
|
213
|
+
result.path = formatPath(session.navigationPath);
|
|
214
|
+
result.selectedFields = session.nodes.filter((n) => n.kind === "field" && getChildren(session, n.id).length === 0).length;
|
|
215
|
+
result.totalNodes = session.nodes.length;
|
|
216
|
+
if (session.variables.length > 0) {
|
|
217
|
+
result.variables = session.variables.map((v) => ({
|
|
218
|
+
name: v.name,
|
|
219
|
+
type: v.type,
|
|
220
|
+
defaultValue: v.defaultValue ?? null,
|
|
221
|
+
runtimeValue: v.runtimeValue ?? null,
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
case "check": {
|
|
226
|
+
const schema = getSessionSchema(session);
|
|
227
|
+
const queryString = renderQuery(session);
|
|
228
|
+
const errors = validateQuery(schema, queryString);
|
|
229
|
+
result.valid = errors.length === 0;
|
|
230
|
+
result.query = queryString;
|
|
231
|
+
if (errors.length > 0) {
|
|
232
|
+
result.errors = errors.map((e) => ({
|
|
233
|
+
message: typeof e === "string" ? e : (e.message ?? String(e)),
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
default:
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return { command, ...extra };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// ── Shared dispatcher ─────────────────────────────────────────────────────────
|
|
248
|
+
export async function dispatchQueryCommand(sessionId, subcommand, rawRest, opts = {}) {
|
|
249
|
+
const jsonMode = rawRest.includes("--json") || getOutputMode() === "json";
|
|
250
|
+
// Extract standard display options from rawRest (needed for chain mode where
|
|
251
|
+
// opts is empty and flags like --search, -l, -a arrive as raw tokens).
|
|
252
|
+
const rest = [];
|
|
253
|
+
for (let i = 0; i < rawRest.length; i++) {
|
|
254
|
+
const tok = rawRest[i];
|
|
255
|
+
if (tok === "--json")
|
|
256
|
+
continue;
|
|
257
|
+
if (tok === "--search" && i + 1 < rawRest.length) {
|
|
258
|
+
opts.search = opts.search ?? rawRest[++i];
|
|
259
|
+
}
|
|
260
|
+
else if (tok === "--regex" && i + 1 < rawRest.length) {
|
|
261
|
+
opts.regex = opts.regex ?? rawRest[++i];
|
|
262
|
+
}
|
|
263
|
+
else if (tok === "--as" && i + 1 < rawRest.length) {
|
|
264
|
+
opts.as = opts.as ?? rawRest[++i];
|
|
265
|
+
}
|
|
266
|
+
else if (tok === "-l" || tok === "--long") {
|
|
267
|
+
opts.long = true;
|
|
268
|
+
}
|
|
269
|
+
else if (tok === "-a" || tok === "--all") {
|
|
270
|
+
opts.all = true;
|
|
271
|
+
}
|
|
272
|
+
else if (tok === "-q" || tok === "--quiet") {
|
|
273
|
+
opts.quiet = true;
|
|
274
|
+
}
|
|
275
|
+
else if (tok === "--data-cloud") {
|
|
276
|
+
opts.dataCloud = true;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
rest.push(tok);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (!subcommand) {
|
|
283
|
+
if (jsonMode) {
|
|
284
|
+
queryShowJson(sessionId);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
queryShow(sessionId);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
// Suppress human output in JSON mode by temporarily redirecting console.log
|
|
291
|
+
const origLog = console.log;
|
|
292
|
+
const origError = console.error;
|
|
293
|
+
const capturedOutput = [];
|
|
294
|
+
if (jsonMode && subcommand !== "show" && subcommand !== "run" && subcommand !== "codegen") {
|
|
295
|
+
console.log = (...args) => {
|
|
296
|
+
capturedOutput.push(args.map(String).join(" "));
|
|
297
|
+
};
|
|
298
|
+
console.error = (...args) => {
|
|
299
|
+
capturedOutput.push(args.map(String).join(" "));
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
if (!isInteractiveMode() && !opts.quiet && !jsonMode) {
|
|
304
|
+
const echoArgs = [...rest];
|
|
305
|
+
if (opts.long)
|
|
306
|
+
echoArgs.push("-l");
|
|
307
|
+
if (opts.all)
|
|
308
|
+
echoArgs.push("-a");
|
|
309
|
+
if (opts.search)
|
|
310
|
+
echoArgs.push(`--search ${opts.search}`);
|
|
311
|
+
if (opts.regex)
|
|
312
|
+
echoArgs.push(`--regex ${opts.regex}`);
|
|
313
|
+
if (opts.as)
|
|
314
|
+
echoArgs.push(`--as ${opts.as}`);
|
|
315
|
+
const echoLine = echoArgs.length > 0 ? `> ${subcommand} ${echoArgs.join(" ")}` : `> ${subcommand}`;
|
|
316
|
+
origLog(echoLine);
|
|
317
|
+
}
|
|
318
|
+
const MUTATING_COMMANDS = new Set([
|
|
319
|
+
"cd",
|
|
320
|
+
"select",
|
|
321
|
+
"drop",
|
|
322
|
+
"set",
|
|
323
|
+
"unset",
|
|
324
|
+
"alias",
|
|
325
|
+
"var",
|
|
326
|
+
"optional",
|
|
327
|
+
"reset",
|
|
328
|
+
]);
|
|
329
|
+
if (subcommand && MUTATING_COMMANDS.has(subcommand)) {
|
|
330
|
+
const session = loadSession(sessionId);
|
|
331
|
+
pushUndoSnapshot(session);
|
|
332
|
+
saveSession(session);
|
|
333
|
+
}
|
|
334
|
+
switch (subcommand) {
|
|
335
|
+
case "pwd": {
|
|
336
|
+
queryPwd(sessionId);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
case "cd": {
|
|
340
|
+
const target = rest[0] ?? "/";
|
|
341
|
+
queryCd(sessionId, target);
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case "select": {
|
|
345
|
+
if (rest[0] === "ls") {
|
|
346
|
+
querySelectLs(sessionId);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
const applyOptional = rest.includes("--optional");
|
|
350
|
+
const selectItems = [];
|
|
351
|
+
for (const rawToken of rest) {
|
|
352
|
+
if (rawToken.startsWith("--"))
|
|
353
|
+
continue;
|
|
354
|
+
const subTokens = rawToken.includes(" ")
|
|
355
|
+
? rawToken.split(/\s+/).filter(Boolean)
|
|
356
|
+
: [rawToken];
|
|
357
|
+
for (const token of subTokens) {
|
|
358
|
+
const colonIdx = token.lastIndexOf(":");
|
|
359
|
+
const afterColon = colonIdx > 0 ? token.slice(colonIdx + 1) : "";
|
|
360
|
+
if (colonIdx > 0 && afterColon.length > 0 && !/[./:]/.test(afterColon)) {
|
|
361
|
+
selectItems.push({ spec: token.slice(0, colonIdx), alias: afterColon });
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
selectItems.push({ spec: token });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (selectItems.length === 1 && !selectItems[0].alias && opts.as) {
|
|
369
|
+
selectItems[0].alias = opts.as;
|
|
370
|
+
}
|
|
371
|
+
if (selectItems.length === 0) {
|
|
372
|
+
console.error("Usage: select <path[:alias]> [<path[:alias]> ...] or select <leaf> --as <alias>");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const session = loadSession(sessionId);
|
|
376
|
+
const successMessages = [];
|
|
377
|
+
const failureMessages = [];
|
|
378
|
+
for (const item of selectItems) {
|
|
379
|
+
const snapNodes = JSON.parse(JSON.stringify(session.nodes));
|
|
380
|
+
const snapFocus = JSON.parse(JSON.stringify(session.focusByPath));
|
|
381
|
+
const nodeCountBefore = session.nodes.length;
|
|
382
|
+
try {
|
|
383
|
+
selectLeafInSession(session, item.spec, item.alias);
|
|
384
|
+
if (applyOptional) {
|
|
385
|
+
for (let ni = nodeCountBefore; ni < session.nodes.length; ni++) {
|
|
386
|
+
const node = session.nodes[ni];
|
|
387
|
+
if (!node.directives.some((d) => d.name === "optional")) {
|
|
388
|
+
node.directives.push({ name: "optional", args: {} });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const optTag = applyOptional ? " @optional" : "";
|
|
393
|
+
successMessages.push(`Selected ${item.spec}${item.alias ? ` as ${item.alias}` : ""}${optTag}.`);
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
session.nodes = snapNodes;
|
|
397
|
+
session.focusByPath = snapFocus;
|
|
398
|
+
failureMessages.push(`Failed: ${item.spec} — ${error.message}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (successMessages.length > 0) {
|
|
402
|
+
saveSession(session);
|
|
403
|
+
}
|
|
404
|
+
if (!opts.quiet) {
|
|
405
|
+
for (const msg of successMessages)
|
|
406
|
+
console.log(msg);
|
|
407
|
+
if (failureMessages.length > 0) {
|
|
408
|
+
console.log("");
|
|
409
|
+
for (const msg of failureMessages)
|
|
410
|
+
console.error(msg);
|
|
411
|
+
}
|
|
412
|
+
if (successMessages.length > 0) {
|
|
413
|
+
console.log("");
|
|
414
|
+
printQuery(session);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (failureMessages.length > 0 && successMessages.length === 0) {
|
|
418
|
+
throw new Error(failureMessages.join("\n"));
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
case "ls": {
|
|
423
|
+
const lsDirs = rest.filter((r) => !r.startsWith("-"));
|
|
424
|
+
await queryLs(sessionId, {
|
|
425
|
+
search: opts.search,
|
|
426
|
+
regex: opts.regex,
|
|
427
|
+
long: opts.long,
|
|
428
|
+
all: opts.all,
|
|
429
|
+
dataCloud: opts.dataCloud,
|
|
430
|
+
}, lsDirs.length > 0 ? lsDirs : undefined);
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
case "set": {
|
|
434
|
+
const isDefault = rest.includes("--default");
|
|
435
|
+
const filtered = rest.filter((r) => r !== "--default" && !r.startsWith("--"));
|
|
436
|
+
// cursor shorthand: `set uiapi/query/Case cursor` or mixed with other args
|
|
437
|
+
// Extract 'cursor' token and process it after the other set operations
|
|
438
|
+
const hasCursor = filtered.includes("cursor");
|
|
439
|
+
const filteredNoCursor = hasCursor ? filtered.filter((t) => t !== "cursor") : filtered;
|
|
440
|
+
const specs = [];
|
|
441
|
+
const hasKeyValue = filteredNoCursor.some((t) => t.includes("=") && !t.startsWith("$") && !t.startsWith("{") && !t.startsWith("["));
|
|
442
|
+
// Determine field prefix (first non-key=value token)
|
|
443
|
+
let cursorFieldPrefix = "";
|
|
444
|
+
// Deferred variable definitions from where.Field=$var shorthand
|
|
445
|
+
const deferredVarDefs = [];
|
|
446
|
+
if (hasKeyValue) {
|
|
447
|
+
let fieldPrefix = "";
|
|
448
|
+
let startIdx = 0;
|
|
449
|
+
if (filteredNoCursor.length > 0 && !filteredNoCursor[0].includes("=")) {
|
|
450
|
+
fieldPrefix = filteredNoCursor[0];
|
|
451
|
+
startIdx = 1;
|
|
452
|
+
}
|
|
453
|
+
cursorFieldPrefix = fieldPrefix;
|
|
454
|
+
for (let i = startIdx; i < filteredNoCursor.length; i++) {
|
|
455
|
+
const token = filteredNoCursor[i];
|
|
456
|
+
const eqIdx = token.indexOf("=");
|
|
457
|
+
if (eqIdx > 0) {
|
|
458
|
+
let key = token.slice(0, eqIdx);
|
|
459
|
+
let val = token.slice(eqIdx + 1);
|
|
460
|
+
// orderBy shorthand: orderBy=Field:DESC → orderBy.Field.order=DESC
|
|
461
|
+
if (key === "orderBy" || key.endsWith("/orderBy") || key.endsWith(".orderBy")) {
|
|
462
|
+
const orderMatch = val.match(/^(\w+):(ASC|DESC)$/i);
|
|
463
|
+
if (orderMatch) {
|
|
464
|
+
key = `${key}.${orderMatch[1]}.order`;
|
|
465
|
+
val = orderMatch[2].toUpperCase();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// where shorthand: where.Field=Value → where={"Field":{"eq":"Value"}}
|
|
469
|
+
// Also supports: where.Field.op=Value (e.g. where.Name.like=%test%)
|
|
470
|
+
// Variable binding: where.Field=$var → defines $var at where/Field/eq
|
|
471
|
+
const whereMatch = key.match(/^(.*?)?where\.(\w+)(?:\.(\w+))?$/);
|
|
472
|
+
if (whereMatch) {
|
|
473
|
+
const prefix = whereMatch[1] || "";
|
|
474
|
+
const field = whereMatch[2];
|
|
475
|
+
// Auto-detect `like` operator when value contains SQL wildcards (%) and no explicit operator
|
|
476
|
+
const op = whereMatch[3] ||
|
|
477
|
+
(val.includes("%") && !val.startsWith("$") && !val.startsWith("{")
|
|
478
|
+
? "like"
|
|
479
|
+
: "eq");
|
|
480
|
+
// If value starts with $, treat as variable binding
|
|
481
|
+
if (val.startsWith("$")) {
|
|
482
|
+
let resolvedPrefix = fieldPrefix;
|
|
483
|
+
if (resolvedPrefix &&
|
|
484
|
+
!resolvedPrefix.startsWith("/") &&
|
|
485
|
+
!resolvedPrefix.startsWith("query/") &&
|
|
486
|
+
!resolvedPrefix.startsWith("@args")) {
|
|
487
|
+
resolvedPrefix = `query/${resolvedPrefix}`;
|
|
488
|
+
}
|
|
489
|
+
const varPath = resolvedPrefix
|
|
490
|
+
? `${resolvedPrefix}/@args/${prefix}where/${field}/${op}`
|
|
491
|
+
: `${prefix}where/${field}/${op}`;
|
|
492
|
+
deferredVarDefs.push([val, varPath]);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
key = `${prefix}where`;
|
|
496
|
+
// Try to parse as number/boolean/null, otherwise use string
|
|
497
|
+
let parsedVal = val;
|
|
498
|
+
if (val === "null")
|
|
499
|
+
parsedVal = null;
|
|
500
|
+
else if (val === "true")
|
|
501
|
+
parsedVal = true;
|
|
502
|
+
else if (val === "false")
|
|
503
|
+
parsedVal = false;
|
|
504
|
+
else if (/^-?\d+(\.\d+)?$/.test(val))
|
|
505
|
+
parsedVal = Number(val);
|
|
506
|
+
else
|
|
507
|
+
parsedVal = val;
|
|
508
|
+
val = JSON.stringify({ [field]: { [op]: parsedVal } });
|
|
509
|
+
}
|
|
510
|
+
let resolvedPrefix = fieldPrefix;
|
|
511
|
+
if (resolvedPrefix &&
|
|
512
|
+
!resolvedPrefix.startsWith("/") &&
|
|
513
|
+
!resolvedPrefix.startsWith("query/") &&
|
|
514
|
+
!resolvedPrefix.startsWith("@args")) {
|
|
515
|
+
resolvedPrefix = `query/${resolvedPrefix}`;
|
|
516
|
+
}
|
|
517
|
+
const fullPath = resolvedPrefix ? `${resolvedPrefix}/@args/${key}` : key;
|
|
518
|
+
specs.push({ path: fullPath, value: val });
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
if (i + 1 < filteredNoCursor.length) {
|
|
522
|
+
let resolvedPrefix = fieldPrefix;
|
|
523
|
+
if (resolvedPrefix &&
|
|
524
|
+
!resolvedPrefix.startsWith("/") &&
|
|
525
|
+
!resolvedPrefix.startsWith("query/") &&
|
|
526
|
+
!resolvedPrefix.startsWith("@args")) {
|
|
527
|
+
resolvedPrefix = `query/${resolvedPrefix}`;
|
|
528
|
+
}
|
|
529
|
+
const path = resolvedPrefix ? `${resolvedPrefix}/@args/${token}` : token;
|
|
530
|
+
specs.push({ path, value: filteredNoCursor[i + 1] });
|
|
531
|
+
i++;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
else if (filteredNoCursor.length > 0) {
|
|
537
|
+
for (let i = 0; i < filteredNoCursor.length; i += 2) {
|
|
538
|
+
const dotPath = filteredNoCursor[i];
|
|
539
|
+
const value = filteredNoCursor[i + 1];
|
|
540
|
+
if (!dotPath || value === undefined) {
|
|
541
|
+
console.error("Usage: set [<field-path>] <key>=<value> ... | set <path> <value>");
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
specs.push({ path: dotPath, value });
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (specs.length === 0 && !hasCursor && deferredVarDefs.length === 0) {
|
|
548
|
+
console.error("Usage: set [<field-path>] <key>=<value> ... | set <path> <value>");
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (specs.length > 0) {
|
|
552
|
+
queryAssign(sessionId, specs, isDefault);
|
|
553
|
+
}
|
|
554
|
+
// Process deferred variable definitions from where.Field=$var shorthand
|
|
555
|
+
for (const [varName, varPath] of deferredVarDefs) {
|
|
556
|
+
queryDefine(sessionId, [varName, varPath]);
|
|
557
|
+
}
|
|
558
|
+
// cursor shorthand: defines $after variable at the after arg of the field prefix
|
|
559
|
+
// and auto-selects pageInfo { hasNextPage, endCursor } for pagination support
|
|
560
|
+
if (hasCursor) {
|
|
561
|
+
// Determine the field prefix for cursor — use the first non-key=value token
|
|
562
|
+
const prefix = cursorFieldPrefix || filteredNoCursor[0] || "";
|
|
563
|
+
if (!prefix) {
|
|
564
|
+
console.error("cursor shorthand requires a field path prefix, e.g. `set uiapi/query/Case cursor`");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const cursorPath = `${prefix}/@args/after`;
|
|
568
|
+
queryDefine(sessionId, ["$after", cursorPath]);
|
|
569
|
+
// Auto-select pageInfo fields needed for pagination loops
|
|
570
|
+
const session = loadSession(sessionId);
|
|
571
|
+
const pageInfoHasNext = `${prefix}/pageInfo/hasNextPage`;
|
|
572
|
+
const pageInfoEndCursor = `${prefix}/pageInfo/endCursor`;
|
|
573
|
+
try {
|
|
574
|
+
selectLeafInSession(session, pageInfoHasNext);
|
|
575
|
+
selectLeafInSession(session, pageInfoEndCursor);
|
|
576
|
+
saveSession(session);
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
// pageInfo may already be selected or path may not resolve — ignore
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
case "unset": {
|
|
585
|
+
const target = rest[0];
|
|
586
|
+
if (!target) {
|
|
587
|
+
console.error("Usage: unset <path> (e.g. unset first, unset where/Name/like)");
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
queryUnassign(sessionId, target);
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
case "alias": {
|
|
594
|
+
queryMkdir(sessionId, rest);
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
case "var": {
|
|
598
|
+
if (rest.length === 0) {
|
|
599
|
+
console.error("Usage: var $name [<path> | <Type>] [default]");
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
queryDefine(sessionId, rest);
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
case "show": {
|
|
606
|
+
if (rest.includes("--query-only") || rest.includes("--raw")) {
|
|
607
|
+
queryShowQueryOnly(sessionId);
|
|
608
|
+
}
|
|
609
|
+
else if (jsonMode) {
|
|
610
|
+
queryShowJson(sessionId);
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
queryShow(sessionId);
|
|
614
|
+
}
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
case "check": {
|
|
618
|
+
if (rest.includes("--all") || opts.all) {
|
|
619
|
+
queryValidateAll();
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
const strict = rest.includes("--strict");
|
|
623
|
+
const withCodegen = rest.includes("--codegen");
|
|
624
|
+
await queryValidate(sessionId, strict, { codegen: withCodegen });
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
case "run": {
|
|
628
|
+
const adHoc = {};
|
|
629
|
+
let dryRun = false;
|
|
630
|
+
for (let i = 0; i < rest.length; i++) {
|
|
631
|
+
if (rest[i] === "--dry-run") {
|
|
632
|
+
dryRun = true;
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (rest[i] === "--var" && rest[i + 1]) {
|
|
636
|
+
const token = rest[++i];
|
|
637
|
+
const eq = token.indexOf("=");
|
|
638
|
+
if (eq !== -1) {
|
|
639
|
+
adHoc[token.slice(0, eq).replace(/^\$/, "")] = token.slice(eq + 1);
|
|
640
|
+
}
|
|
641
|
+
else if (rest[i + 1] !== undefined && !rest[i + 1].startsWith("--")) {
|
|
642
|
+
adHoc[token.replace(/^\$/, "")] = rest[++i];
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
await queryExecute(sessionId, Object.keys(adHoc).length > 0 ? adHoc : undefined, dryRun);
|
|
647
|
+
if (!dryRun) {
|
|
648
|
+
console.log("");
|
|
649
|
+
console.log("Tip: Run `show` for a full session snapshot.");
|
|
650
|
+
}
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
case "drop": {
|
|
654
|
+
const fieldOrAlias = rest[0];
|
|
655
|
+
if (!fieldOrAlias) {
|
|
656
|
+
console.error("Usage: drop <target> (field, alias, arg, variable, or list index)");
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
queryRm(sessionId, fieldOrAlias);
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
case "help": {
|
|
663
|
+
const topic = rest[0];
|
|
664
|
+
queryHelp(sessionId, topic);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
case "sessions": {
|
|
668
|
+
const sessionsAction = rest[0];
|
|
669
|
+
if (sessionsAction === "rm" || sessionsAction === "delete" || sessionsAction === "remove") {
|
|
670
|
+
const target = rest[1];
|
|
671
|
+
if (!target) {
|
|
672
|
+
console.error("Usage: sessions rm <session-id-or-name>");
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
querySessionsRm(target);
|
|
676
|
+
}
|
|
677
|
+
else if (sessionsAction === "prune") {
|
|
678
|
+
const olderThanIdx = rest.indexOf("--older-than");
|
|
679
|
+
const duration = olderThanIdx !== -1 ? rest[olderThanIdx + 1] : undefined;
|
|
680
|
+
if (!duration) {
|
|
681
|
+
console.error("Usage: sessions prune --older-than <duration> (e.g. 1d, 12h, 30m)");
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
querySessionsPrune(duration);
|
|
685
|
+
}
|
|
686
|
+
else if (sessionsAction === "clean") {
|
|
687
|
+
querySessionsClean();
|
|
688
|
+
}
|
|
689
|
+
else if (sessionsAction) {
|
|
690
|
+
console.error(`Unknown sessions action "${sessionsAction}". Use "rm", "prune", or "clean".`);
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
querySessionsList();
|
|
694
|
+
}
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
case "reset": {
|
|
698
|
+
const session = loadSession(sessionId);
|
|
699
|
+
session.nodes = [];
|
|
700
|
+
session.variables = [];
|
|
701
|
+
session.focusByPath = {};
|
|
702
|
+
session.navigationPath = [];
|
|
703
|
+
saveSession(session);
|
|
704
|
+
console.log("Session reset. All selections, arguments, and variables cleared.");
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
case "undo": {
|
|
708
|
+
const session = loadSession(sessionId);
|
|
709
|
+
const restored = popUndo(session);
|
|
710
|
+
if (!restored) {
|
|
711
|
+
console.log("Nothing to undo.");
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
const diffs = [];
|
|
715
|
+
if (formatPath(session.navigationPath) !== formatPath(restored.navigationPath)) {
|
|
716
|
+
diffs.push(`navigation → ${formatPath(restored.navigationPath)}`);
|
|
717
|
+
}
|
|
718
|
+
const nodeDelta = session.nodes.length - restored.nodes.length;
|
|
719
|
+
if (nodeDelta > 0)
|
|
720
|
+
diffs.push(`${nodeDelta} node${nodeDelta !== 1 ? "s" : ""} removed`);
|
|
721
|
+
else if (nodeDelta < 0)
|
|
722
|
+
diffs.push(`${-nodeDelta} node${-nodeDelta !== 1 ? "s" : ""} restored`);
|
|
723
|
+
const addedVars = session.variables.filter((v) => !restored.variables.some((rv) => rv.name === v.name));
|
|
724
|
+
const removedVars = restored.variables.filter((v) => !session.variables.some((cv) => cv.name === v.name));
|
|
725
|
+
for (const v of addedVars)
|
|
726
|
+
diffs.push(`removed $${v.name}`);
|
|
727
|
+
for (const v of removedVars)
|
|
728
|
+
diffs.push(`restored $${v.name}`);
|
|
729
|
+
if (diffs.length === 0) {
|
|
730
|
+
const currArgsJson = JSON.stringify(session.nodes
|
|
731
|
+
.filter((n) => n.kind === "field")
|
|
732
|
+
.map((n) => ({ id: n.id, args: n.args })));
|
|
733
|
+
const restArgsJson = JSON.stringify(restored.nodes
|
|
734
|
+
.filter((n) => n.kind === "field")
|
|
735
|
+
.map((n) => ({ id: n.id, args: n.args })));
|
|
736
|
+
if (currArgsJson !== restArgsJson)
|
|
737
|
+
diffs.push("argument values changed");
|
|
738
|
+
}
|
|
739
|
+
saveSession(restored);
|
|
740
|
+
const desc = diffs.length > 0 ? diffs.join(", ") : "last change";
|
|
741
|
+
console.log(`Undone: ${desc}`);
|
|
742
|
+
console.log("");
|
|
743
|
+
printQuery(restored);
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
case "clone": {
|
|
747
|
+
const nameIdx = rest.indexOf("--name");
|
|
748
|
+
const cloneName = nameIdx !== -1
|
|
749
|
+
? rest[nameIdx + 1]
|
|
750
|
+
: rest[0] && !rest[0].startsWith("--")
|
|
751
|
+
? rest[0]
|
|
752
|
+
: undefined;
|
|
753
|
+
const forceClone = rest.includes("--force") || rest.includes("-f");
|
|
754
|
+
const cloneResult = queryClone(sessionId, cloneName, { force: forceClone });
|
|
755
|
+
// Helper: collect args after a flag until the next --flag
|
|
756
|
+
const collectFlagArgs = (flag) => {
|
|
757
|
+
const idx = rest.indexOf(flag);
|
|
758
|
+
if (idx === -1)
|
|
759
|
+
return [];
|
|
760
|
+
const args = [];
|
|
761
|
+
for (let j = idx + 1; j < rest.length; j++) {
|
|
762
|
+
if (rest[j].startsWith("--") || rest[j].startsWith("-s"))
|
|
763
|
+
break;
|
|
764
|
+
args.push(rest[j]);
|
|
765
|
+
}
|
|
766
|
+
return args;
|
|
767
|
+
};
|
|
768
|
+
// Apply flags in order: --unset first (remove inherited args), then --set, then --var.
|
|
769
|
+
// This ensures `clone --unset where --var $x path` works as expected:
|
|
770
|
+
// the old where is removed before the new variable binding creates a new one.
|
|
771
|
+
const clonedId = cloneResult.name ?? cloneResult.id;
|
|
772
|
+
// 1. Apply --unset to remove args from the cloned session
|
|
773
|
+
const unsetArgs = collectFlagArgs("--unset");
|
|
774
|
+
if (unsetArgs.length > 0 && clonedId) {
|
|
775
|
+
for (const path of unsetArgs) {
|
|
776
|
+
queryUnassign(clonedId, path);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
// 2. Apply --set args to the cloned session (with full shorthand support)
|
|
780
|
+
const setArgs = collectFlagArgs("--set");
|
|
781
|
+
if (setArgs.length > 0 && clonedId) {
|
|
782
|
+
const parsed = parseInlineSetArgs(setArgs);
|
|
783
|
+
if (parsed.specs.length > 0) {
|
|
784
|
+
queryAssign(clonedId, parsed.specs);
|
|
785
|
+
}
|
|
786
|
+
for (const [varName, varPath] of parsed.deferredVarDefs) {
|
|
787
|
+
queryDefine(clonedId, [varName, varPath]);
|
|
788
|
+
}
|
|
789
|
+
if (parsed.hasCursor) {
|
|
790
|
+
const prefix = parsed.cursorFieldPrefix || "";
|
|
791
|
+
if (prefix) {
|
|
792
|
+
const cursorPath = `${prefix}/@args/after`;
|
|
793
|
+
queryDefine(clonedId, ["$after", cursorPath]);
|
|
794
|
+
// Auto-select pageInfo for pagination
|
|
795
|
+
const clonedSession = loadSession(clonedId);
|
|
796
|
+
try {
|
|
797
|
+
selectLeafInSession(clonedSession, `${prefix}/pageInfo/hasNextPage`);
|
|
798
|
+
selectLeafInSession(clonedSession, `${prefix}/pageInfo/endCursor`);
|
|
799
|
+
saveSession(clonedSession);
|
|
800
|
+
}
|
|
801
|
+
catch {
|
|
802
|
+
/* ignore if already selected */
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// 3. Apply --var to bind a variable on the cloned session
|
|
808
|
+
const varArgs = collectFlagArgs("--var");
|
|
809
|
+
if (varArgs.length >= 2 && clonedId) {
|
|
810
|
+
queryDefine(clonedId, varArgs);
|
|
811
|
+
}
|
|
812
|
+
// Apply --select to add fields to the cloned session
|
|
813
|
+
const selectArgs = collectFlagArgs("--select");
|
|
814
|
+
if (selectArgs.length > 0 && clonedId) {
|
|
815
|
+
const clonedSession = loadSession(clonedId);
|
|
816
|
+
for (const rawSpec of selectArgs) {
|
|
817
|
+
const colonIdx = rawSpec.lastIndexOf(":");
|
|
818
|
+
const afterColon = colonIdx > 0 ? rawSpec.slice(colonIdx + 1) : "";
|
|
819
|
+
let spec;
|
|
820
|
+
let alias;
|
|
821
|
+
if (colonIdx > 0 && afterColon.length > 0 && !/[./:]/.test(afterColon)) {
|
|
822
|
+
spec = rawSpec.slice(0, colonIdx);
|
|
823
|
+
alias = afterColon;
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
spec = rawSpec;
|
|
827
|
+
}
|
|
828
|
+
try {
|
|
829
|
+
selectLeafInSession(clonedSession, spec, alias);
|
|
830
|
+
console.log(`Selected ${rawSpec}.`);
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
console.error(`Failed to select ${rawSpec}: ${error.message}`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
saveSession(clonedSession);
|
|
837
|
+
console.log("");
|
|
838
|
+
printQuery(clonedSession);
|
|
839
|
+
}
|
|
840
|
+
// Return cloned session info so the CLI can auto-activate it
|
|
841
|
+
return { _clonedSessionId: clonedId };
|
|
842
|
+
}
|
|
843
|
+
case "optional": {
|
|
844
|
+
if (rest.length === 0) {
|
|
845
|
+
console.error("Usage: optional <field-or-alias> [...] | optional --remove <field-or-alias> [...]");
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
const removeMode = rest.includes("--remove");
|
|
849
|
+
const targets = rest.filter((r) => !r.startsWith("--"));
|
|
850
|
+
if (targets.length === 0) {
|
|
851
|
+
console.error("Usage: optional <field-or-alias> [...] | optional --remove <field-or-alias> [...]");
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const session = loadSession(sessionId);
|
|
855
|
+
for (const target of targets) {
|
|
856
|
+
const node = session.nodes.find((n) => n.kind === "field" &&
|
|
857
|
+
(n.alias === target ||
|
|
858
|
+
n.fieldName === target));
|
|
859
|
+
if (!node) {
|
|
860
|
+
console.error(`No selected field matching "${target}". Use \`select ls\` to see selected fields.`);
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
if (removeMode) {
|
|
864
|
+
node.directives = node.directives.filter((d) => d.name !== "optional");
|
|
865
|
+
console.log(`Removed @optional from ${target}.`);
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
if (!node.directives.some((d) => d.name === "optional")) {
|
|
869
|
+
node.directives.push({ name: "optional", args: {} });
|
|
870
|
+
}
|
|
871
|
+
console.log(`Added @optional to ${target}.`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
saveSession(session);
|
|
875
|
+
console.log("");
|
|
876
|
+
printQuery(session);
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
case "codegen": {
|
|
880
|
+
await queryCodegen(sessionId, rest);
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
default:
|
|
884
|
+
throw new CommandError(`Unknown subcommand: ${subcommand}\n${formatHelp()}`);
|
|
885
|
+
}
|
|
886
|
+
// Emit JSON result for all commands when in JSON mode
|
|
887
|
+
if (jsonMode && subcommand !== "show" && subcommand !== "run" && subcommand !== "codegen") {
|
|
888
|
+
console.log = origLog;
|
|
889
|
+
console.error = origError;
|
|
890
|
+
const result = buildJsonResult(subcommand, sessionId);
|
|
891
|
+
origLog(JSON.stringify(result, null, 2));
|
|
892
|
+
return result;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
finally {
|
|
896
|
+
// Restore console in case of errors
|
|
897
|
+
console.log = origLog;
|
|
898
|
+
console.error = origError;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
//# sourceMappingURL=query.js.map
|