@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.
Files changed (282) hide show
  1. package/AGENT_GUIDE.md +424 -0
  2. package/CHANGELOG.md +448 -0
  3. package/LICENSE.txt +82 -0
  4. package/README.md +204 -0
  5. package/TASK.md +249 -0
  6. package/dist/cli.d.ts +7 -0
  7. package/dist/cli.js +683 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/args.d.ts +13 -0
  10. package/dist/commands/args.js +207 -0
  11. package/dist/commands/args.js.map +1 -0
  12. package/dist/commands/build.d.ts +11 -0
  13. package/dist/commands/build.js +209 -0
  14. package/dist/commands/build.js.map +1 -0
  15. package/dist/commands/connect.d.ts +8 -0
  16. package/dist/commands/connect.js +55 -0
  17. package/dist/commands/connect.js.map +1 -0
  18. package/dist/commands/describe.d.ts +6 -0
  19. package/dist/commands/describe.js +229 -0
  20. package/dist/commands/describe.js.map +1 -0
  21. package/dist/commands/meta.d.ts +9 -0
  22. package/dist/commands/meta.js +140 -0
  23. package/dist/commands/meta.js.map +1 -0
  24. package/dist/commands/navigate.d.ts +14 -0
  25. package/dist/commands/navigate.js +105 -0
  26. package/dist/commands/navigate.js.map +1 -0
  27. package/dist/commands/query-helpers.d.ts +80 -0
  28. package/dist/commands/query-helpers.js +865 -0
  29. package/dist/commands/query-helpers.js.map +1 -0
  30. package/dist/commands/query.d.ts +26 -0
  31. package/dist/commands/query.js +901 -0
  32. package/dist/commands/query.js.map +1 -0
  33. package/dist/commands/review.d.ts +18 -0
  34. package/dist/commands/review.js +533 -0
  35. package/dist/commands/review.js.map +1 -0
  36. package/dist/commands/session-mgmt.d.ts +25 -0
  37. package/dist/commands/session-mgmt.js +206 -0
  38. package/dist/commands/session-mgmt.js.map +1 -0
  39. package/dist/commands/type.d.ts +6 -0
  40. package/dist/commands/type.js +342 -0
  41. package/dist/commands/type.js.map +1 -0
  42. package/dist/commands/validate-input.d.ts +6 -0
  43. package/dist/commands/validate-input.js +32 -0
  44. package/dist/commands/validate-input.js.map +1 -0
  45. package/dist/intent/build-aggregate.d.ts +33 -0
  46. package/dist/intent/build-aggregate.js +134 -0
  47. package/dist/intent/build-aggregate.js.map +1 -0
  48. package/dist/intent/build-create.d.ts +14 -0
  49. package/dist/intent/build-create.js +16 -0
  50. package/dist/intent/build-create.js.map +1 -0
  51. package/dist/intent/build-delete.d.ts +30 -0
  52. package/dist/intent/build-delete.js +53 -0
  53. package/dist/intent/build-delete.js.map +1 -0
  54. package/dist/intent/build-detail.d.ts +32 -0
  55. package/dist/intent/build-detail.js +80 -0
  56. package/dist/intent/build-detail.js.map +1 -0
  57. package/dist/intent/build-discover.d.ts +30 -0
  58. package/dist/intent/build-discover.js +149 -0
  59. package/dist/intent/build-discover.js.map +1 -0
  60. package/dist/intent/build-list.d.ts +28 -0
  61. package/dist/intent/build-list.js +75 -0
  62. package/dist/intent/build-list.js.map +1 -0
  63. package/dist/intent/build-mutation.d.ts +23 -0
  64. package/dist/intent/build-mutation.js +54 -0
  65. package/dist/intent/build-mutation.js.map +1 -0
  66. package/dist/intent/build-output.d.ts +27 -0
  67. package/dist/intent/build-output.js +60 -0
  68. package/dist/intent/build-output.js.map +1 -0
  69. package/dist/intent/build-raw.d.ts +23 -0
  70. package/dist/intent/build-raw.js +54 -0
  71. package/dist/intent/build-raw.js.map +1 -0
  72. package/dist/intent/build-update.d.ts +14 -0
  73. package/dist/intent/build-update.js +16 -0
  74. package/dist/intent/build-update.js.map +1 -0
  75. package/dist/intent/get-schema-with-priming.d.ts +26 -0
  76. package/dist/intent/get-schema-with-priming.js +32 -0
  77. package/dist/intent/get-schema-with-priming.js.map +1 -0
  78. package/dist/intent/select-child-relationship.d.ts +19 -0
  79. package/dist/intent/select-child-relationship.js +38 -0
  80. package/dist/intent/select-child-relationship.js.map +1 -0
  81. package/dist/intent/types.d.ts +159 -0
  82. package/dist/intent/types.js +21 -0
  83. package/dist/intent/types.js.map +1 -0
  84. package/dist/lib/apply-command.d.ts +15 -0
  85. package/dist/lib/apply-command.js +238 -0
  86. package/dist/lib/apply-command.js.map +1 -0
  87. package/dist/lib/auth.d.ts +38 -0
  88. package/dist/lib/auth.js +113 -0
  89. package/dist/lib/auth.js.map +1 -0
  90. package/dist/lib/codegen.d.ts +32 -0
  91. package/dist/lib/codegen.js +700 -0
  92. package/dist/lib/codegen.js.map +1 -0
  93. package/dist/lib/command-registry.d.ts +59 -0
  94. package/dist/lib/command-registry.js +366 -0
  95. package/dist/lib/command-registry.js.map +1 -0
  96. package/dist/lib/formatter.d.ts +76 -0
  97. package/dist/lib/formatter.js +419 -0
  98. package/dist/lib/formatter.js.map +1 -0
  99. package/dist/lib/fs-utils.d.ts +23 -0
  100. package/dist/lib/fs-utils.js +46 -0
  101. package/dist/lib/fs-utils.js.map +1 -0
  102. package/dist/lib/graphql-name.d.ts +27 -0
  103. package/dist/lib/graphql-name.js +32 -0
  104. package/dist/lib/graphql-name.js.map +1 -0
  105. package/dist/lib/interactive.d.ts +6 -0
  106. package/dist/lib/interactive.js +562 -0
  107. package/dist/lib/interactive.js.map +1 -0
  108. package/dist/lib/introspect.d.ts +40 -0
  109. package/dist/lib/introspect.js +280 -0
  110. package/dist/lib/introspect.js.map +1 -0
  111. package/dist/lib/object-info.d.ts +79 -0
  112. package/dist/lib/object-info.js +313 -0
  113. package/dist/lib/object-info.js.map +1 -0
  114. package/dist/lib/path-selection.d.ts +50 -0
  115. package/dist/lib/path-selection.js +146 -0
  116. package/dist/lib/path-selection.js.map +1 -0
  117. package/dist/lib/prime-schema.d.ts +59 -0
  118. package/dist/lib/prime-schema.js +158 -0
  119. package/dist/lib/prime-schema.js.map +1 -0
  120. package/dist/lib/query-builder.d.ts +10 -0
  121. package/dist/lib/query-builder.js +168 -0
  122. package/dist/lib/query-builder.js.map +1 -0
  123. package/dist/lib/query-commands.d.ts +19 -0
  124. package/dist/lib/query-commands.js +262 -0
  125. package/dist/lib/query-commands.js.map +1 -0
  126. package/dist/lib/session.d.ts +205 -0
  127. package/dist/lib/session.js +1075 -0
  128. package/dist/lib/session.js.map +1 -0
  129. package/dist/lib/tokenize.d.ts +12 -0
  130. package/dist/lib/tokenize.js +48 -0
  131. package/dist/lib/tokenize.js.map +1 -0
  132. package/dist/lib/uiapi.d.ts +109 -0
  133. package/dist/lib/uiapi.js +159 -0
  134. package/dist/lib/uiapi.js.map +1 -0
  135. package/dist/lib/validator.d.ts +27 -0
  136. package/dist/lib/validator.js +100 -0
  137. package/dist/lib/validator.js.map +1 -0
  138. package/dist/lib/variable-promotion.d.ts +69 -0
  139. package/dist/lib/variable-promotion.js +95 -0
  140. package/dist/lib/variable-promotion.js.map +1 -0
  141. package/dist/lib/walker.d.ts +147 -0
  142. package/dist/lib/walker.js +723 -0
  143. package/dist/lib/walker.js.map +1 -0
  144. package/dist/mcp/server.d.ts +7 -0
  145. package/dist/mcp/server.js +34 -0
  146. package/dist/mcp/server.js.map +1 -0
  147. package/dist/mcp/stdio.d.ts +7 -0
  148. package/dist/mcp/stdio.js +25 -0
  149. package/dist/mcp/stdio.js.map +1 -0
  150. package/dist/mcp/tools/echo.d.ts +7 -0
  151. package/dist/mcp/tools/echo.js +17 -0
  152. package/dist/mcp/tools/echo.js.map +1 -0
  153. package/dist/mcp/tools/sf-gql-aggregate.d.ts +11 -0
  154. package/dist/mcp/tools/sf-gql-aggregate.js +75 -0
  155. package/dist/mcp/tools/sf-gql-aggregate.js.map +1 -0
  156. package/dist/mcp/tools/sf-gql-create.d.ts +11 -0
  157. package/dist/mcp/tools/sf-gql-create.js +35 -0
  158. package/dist/mcp/tools/sf-gql-create.js.map +1 -0
  159. package/dist/mcp/tools/sf-gql-delete.d.ts +11 -0
  160. package/dist/mcp/tools/sf-gql-delete.js +31 -0
  161. package/dist/mcp/tools/sf-gql-delete.js.map +1 -0
  162. package/dist/mcp/tools/sf-gql-detail.d.ts +11 -0
  163. package/dist/mcp/tools/sf-gql-detail.js +58 -0
  164. package/dist/mcp/tools/sf-gql-detail.js.map +1 -0
  165. package/dist/mcp/tools/sf-gql-discover.d.ts +9 -0
  166. package/dist/mcp/tools/sf-gql-discover.js +51 -0
  167. package/dist/mcp/tools/sf-gql-discover.js.map +1 -0
  168. package/dist/mcp/tools/sf-gql-list.d.ts +11 -0
  169. package/dist/mcp/tools/sf-gql-list.js +53 -0
  170. package/dist/mcp/tools/sf-gql-list.js.map +1 -0
  171. package/dist/mcp/tools/sf-gql-raw.d.ts +11 -0
  172. package/dist/mcp/tools/sf-gql-raw.js +39 -0
  173. package/dist/mcp/tools/sf-gql-raw.js.map +1 -0
  174. package/dist/mcp/tools/sf-gql-update.d.ts +11 -0
  175. package/dist/mcp/tools/sf-gql-update.js +35 -0
  176. package/dist/mcp/tools/sf-gql-update.js.map +1 -0
  177. package/dist/mcp/tools/shared/zod-schemas.d.ts +49 -0
  178. package/dist/mcp/tools/shared/zod-schemas.js +46 -0
  179. package/dist/mcp/tools/shared/zod-schemas.js.map +1 -0
  180. package/package.json +36 -0
  181. package/ralph-loop.sh +120 -0
  182. package/scripts/smoke-orderby.sh +190 -0
  183. package/src/__tests__/helpers/prime-deps.ts +46 -0
  184. package/src/__tests__/helpers/schema.ts +73 -0
  185. package/src/__tests__/helpers/stdout.ts +33 -0
  186. package/src/__tests__/setup.ts +19 -0
  187. package/src/cli.ts +764 -0
  188. package/src/commands/__tests__/query.spec.ts +137 -0
  189. package/src/commands/args.ts +306 -0
  190. package/src/commands/build.ts +288 -0
  191. package/src/commands/connect.ts +60 -0
  192. package/src/commands/describe.ts +246 -0
  193. package/src/commands/meta.ts +171 -0
  194. package/src/commands/navigate.ts +134 -0
  195. package/src/commands/query-helpers.ts +1202 -0
  196. package/src/commands/query.ts +1085 -0
  197. package/src/commands/review.ts +670 -0
  198. package/src/commands/session-mgmt.ts +256 -0
  199. package/src/commands/type.ts +437 -0
  200. package/src/commands/validate-input.ts +38 -0
  201. package/src/intent/__tests__/build-aggregate.spec.ts +931 -0
  202. package/src/intent/__tests__/build-create-validation.spec.ts +135 -0
  203. package/src/intent/__tests__/build-delete.spec.ts +121 -0
  204. package/src/intent/__tests__/build-detail.spec.ts +333 -0
  205. package/src/intent/__tests__/build-discover.spec.ts +432 -0
  206. package/src/intent/__tests__/build-list.spec.ts +284 -0
  207. package/src/intent/__tests__/build-mutation.spec.ts +108 -0
  208. package/src/intent/__tests__/build-output.spec.ts +55 -0
  209. package/src/intent/__tests__/build-raw.spec.ts +153 -0
  210. package/src/intent/__tests__/build-update-validation.spec.ts +134 -0
  211. package/src/intent/build-aggregate.ts +182 -0
  212. package/src/intent/build-create.ts +19 -0
  213. package/src/intent/build-delete.ts +62 -0
  214. package/src/intent/build-detail.ts +95 -0
  215. package/src/intent/build-discover.ts +199 -0
  216. package/src/intent/build-list.ts +91 -0
  217. package/src/intent/build-mutation.ts +75 -0
  218. package/src/intent/build-output.ts +72 -0
  219. package/src/intent/build-raw.ts +61 -0
  220. package/src/intent/build-update.ts +19 -0
  221. package/src/intent/get-schema-with-priming.ts +43 -0
  222. package/src/intent/select-child-relationship.ts +48 -0
  223. package/src/intent/types.ts +181 -0
  224. package/src/lib/__tests__/apply-command.parity.spec.ts +97 -0
  225. package/src/lib/__tests__/apply-command.spec.ts +171 -0
  226. package/src/lib/__tests__/auth.spec.ts +292 -0
  227. package/src/lib/__tests__/formatter.spec.ts +86 -0
  228. package/src/lib/__tests__/graphql-name.spec.ts +64 -0
  229. package/src/lib/__tests__/introspect.spec.ts +399 -0
  230. package/src/lib/__tests__/object-info.spec.ts +124 -0
  231. package/src/lib/__tests__/path-selection.spec.ts +219 -0
  232. package/src/lib/__tests__/prime-schema.spec.ts +269 -0
  233. package/src/lib/__tests__/query-builder.spec.ts +95 -0
  234. package/src/lib/__tests__/query-commands.spec.ts +74 -0
  235. package/src/lib/__tests__/session.spec.ts +292 -0
  236. package/src/lib/__tests__/tokenize.spec.ts +33 -0
  237. package/src/lib/__tests__/uiapi.spec.ts +192 -0
  238. package/src/lib/__tests__/variable-promotion.spec.ts +211 -0
  239. package/src/lib/__tests__/walker.spec.ts +250 -0
  240. package/src/lib/apply-command.ts +286 -0
  241. package/src/lib/auth.ts +157 -0
  242. package/src/lib/codegen.ts +899 -0
  243. package/src/lib/command-registry.ts +434 -0
  244. package/src/lib/formatter.ts +587 -0
  245. package/src/lib/fs-utils.ts +46 -0
  246. package/src/lib/graphql-name.ts +35 -0
  247. package/src/lib/interactive.ts +634 -0
  248. package/src/lib/introspect.ts +320 -0
  249. package/src/lib/object-info.ts +409 -0
  250. package/src/lib/path-selection.ts +162 -0
  251. package/src/lib/prime-schema.ts +195 -0
  252. package/src/lib/query-builder.ts +193 -0
  253. package/src/lib/query-commands.ts +290 -0
  254. package/src/lib/session.ts +1304 -0
  255. package/src/lib/tokenize.ts +43 -0
  256. package/src/lib/uiapi.ts +176 -0
  257. package/src/lib/validator.ts +143 -0
  258. package/src/lib/variable-promotion.ts +145 -0
  259. package/src/lib/walker.ts +975 -0
  260. package/src/mcp/__tests__/server.spec.ts +155 -0
  261. package/src/mcp/server.ts +38 -0
  262. package/src/mcp/stdio.ts +29 -0
  263. package/src/mcp/tools/__tests__/sf-gql-aggregate.spec.ts +173 -0
  264. package/src/mcp/tools/__tests__/sf-gql-create.spec.ts +235 -0
  265. package/src/mcp/tools/__tests__/sf-gql-delete.spec.ts +194 -0
  266. package/src/mcp/tools/__tests__/sf-gql-detail.spec.ts +246 -0
  267. package/src/mcp/tools/__tests__/sf-gql-discover.spec.ts +320 -0
  268. package/src/mcp/tools/__tests__/sf-gql-list.spec.ts +128 -0
  269. package/src/mcp/tools/__tests__/sf-gql-raw.spec.ts +141 -0
  270. package/src/mcp/tools/__tests__/sf-gql-update.spec.ts +207 -0
  271. package/src/mcp/tools/echo.ts +24 -0
  272. package/src/mcp/tools/sf-gql-aggregate.ts +102 -0
  273. package/src/mcp/tools/sf-gql-create.ts +55 -0
  274. package/src/mcp/tools/sf-gql-delete.ts +49 -0
  275. package/src/mcp/tools/sf-gql-detail.ts +85 -0
  276. package/src/mcp/tools/sf-gql-discover.ts +67 -0
  277. package/src/mcp/tools/sf-gql-list.ts +73 -0
  278. package/src/mcp/tools/sf-gql-raw.ts +56 -0
  279. package/src/mcp/tools/sf-gql-update.ts +55 -0
  280. package/src/mcp/tools/shared/zod-schemas.ts +55 -0
  281. package/tsconfig.json +18 -0
  282. package/vitest.config.ts +14 -0
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import { CommandError, getSessionSchema, printQuery } from "./query-helpers.js";
8
+ import { getOrgAuth } from "../lib/auth.js";
9
+ import { formatNavigationPath } from "../lib/formatter.js";
10
+ import { schemaExists } from "../lib/introspect.js";
11
+ import { renderQuery } from "../lib/query-builder.js";
12
+ import {
13
+ createSession,
14
+ cloneSession,
15
+ loadSession,
16
+ saveSession,
17
+ deleteSession,
18
+ listSessions,
19
+ type QuerySession,
20
+ } from "../lib/session.js";
21
+ import { validateQuery } from "../lib/validator.js";
22
+ import { getRootFields } from "../lib/walker.js";
23
+
24
+ export interface NewSessionOpts {
25
+ mutation?: boolean;
26
+ aggregate?: boolean;
27
+ name?: string;
28
+ force?: boolean;
29
+ }
30
+
31
+ export async function queryNew(orgAlias: string, opts: NewSessionOpts): Promise<QuerySession> {
32
+ if (opts.mutation && opts.aggregate) {
33
+ throw new CommandError("Cannot use --mutation and --aggregate together. Choose one.");
34
+ }
35
+
36
+ const { instanceUrl } = await getOrgAuth(orgAlias);
37
+
38
+ if (!schemaExists(instanceUrl)) {
39
+ throw new CommandError(
40
+ `No cached schema for "${orgAlias}". Run \`graphiti connect ${orgAlias}\` first.`,
41
+ );
42
+ }
43
+
44
+ const operation = opts.mutation
45
+ ? ("mutation" as const)
46
+ : opts.aggregate
47
+ ? ("aggregate" as const)
48
+ : ("query" as const);
49
+
50
+ if (opts.name) {
51
+ const existing = listSessions().find((s) => s.name === opts.name);
52
+ if (existing) {
53
+ if (opts.force) {
54
+ deleteSession(existing.id);
55
+ } else {
56
+ throw new CommandError(
57
+ `A session named "${opts.name}" already exists (${existing.id}). ` +
58
+ `Remove it first with \`graphiti query sessions rm ${opts.name}\`, use --force to replace it, or choose a different name.`,
59
+ );
60
+ }
61
+ }
62
+ }
63
+
64
+ const session = createSession(orgAlias, operation, instanceUrl, opts.name);
65
+ saveSession(session);
66
+
67
+ const schema = getSessionSchema(session);
68
+ const rootFields = getRootFields(schema, operation);
69
+
70
+ console.log(`Session: ${session.id}${session.name ? ` (${session.name})` : ""}`);
71
+ if (session.name) {
72
+ console.log(`Use "${session.id}" or "${session.name}" in subsequent commands.`);
73
+ }
74
+ console.log(formatNavigationPath(session.navigationPath));
75
+ console.log(" query/");
76
+ console.log(" variables/");
77
+ console.log("");
78
+ console.log(`Schema root (query/): ${rootFields.length} fields`);
79
+ for (const field of rootFields.slice(0, 10)) {
80
+ console.log(
81
+ ` ${field.name}${field.typeKind === "SCALAR" || field.typeKind === "ENUM" ? "" : "/"}`,
82
+ );
83
+ }
84
+ if (rootFields.length > 10) {
85
+ console.log(` ... ${rootFields.length - 10} more (use \`cd query\` then \`ls\` to browse)`);
86
+ }
87
+ console.log("");
88
+ printQuery(session);
89
+
90
+ return session;
91
+ }
92
+
93
+ export function queryClone(
94
+ sessionId: string,
95
+ newName?: string,
96
+ opts?: { force?: boolean },
97
+ ): { id: string; name?: string } {
98
+ const source = loadSession(sessionId);
99
+ if (newName) {
100
+ const existing = listSessions().find((s) => s.name === newName);
101
+ if (existing) {
102
+ if (opts?.force) {
103
+ deleteSession(existing.id);
104
+ } else {
105
+ throw new CommandError(
106
+ `A session named "${newName}" already exists (${existing.id}). ` +
107
+ `Remove it first, use --force to replace it, or choose a different name.`,
108
+ );
109
+ }
110
+ }
111
+ }
112
+ const cloned = cloneSession(source, newName);
113
+ saveSession(cloned);
114
+ console.log(
115
+ `Cloned session ${source.id}${source.name ? ` (${source.name})` : ""} → ${cloned.id}${cloned.name ? ` (${cloned.name})` : ""}`,
116
+ );
117
+ if (cloned.name) {
118
+ console.log(`Use "${cloned.id}" or "${cloned.name}" in subsequent commands.`);
119
+ }
120
+ console.log("");
121
+ printQuery(cloned);
122
+ return { id: cloned.id, name: cloned.name };
123
+ }
124
+
125
+ export function querySessionsList(): void {
126
+ const sessions = listSessions().sort(
127
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
128
+ );
129
+
130
+ if (sessions.length === 0) {
131
+ console.log("No sessions found. Run `graphiti query new <org>` to create one.");
132
+ return;
133
+ }
134
+
135
+ console.log(`${sessions.length} session${sessions.length === 1 ? "" : "s"}:`);
136
+ console.log("");
137
+ const maxIdNameLen = Math.max(
138
+ ...sessions.map((s) => {
139
+ const nameDisplay = s.name ? ` (${s.name})` : "";
140
+ return (s.id + nameDisplay).length;
141
+ }),
142
+ );
143
+ for (const s of sessions) {
144
+ const age = formatAge(new Date(s.createdAt));
145
+ const nameDisplay = s.name ? ` (${s.name})` : "";
146
+ const idCol = (s.id + nameDisplay).padEnd(maxIdNameLen + 2);
147
+ console.log(` ${idCol}${s.operation.padEnd(10)}${s.orgAlias.padEnd(20)} ${age}`);
148
+ }
149
+ console.log("");
150
+ console.log("Resume a session (by ID or name):");
151
+ if (sessions.length > 0) {
152
+ const s = sessions[0];
153
+ const identifier = s.name ?? s.id;
154
+ console.log(` graphiti query ${identifier} ls`);
155
+ console.log(` graphiti query ${identifier} interactive`);
156
+ }
157
+ }
158
+
159
+ export function querySessionsRm(sessionIdOrName: string): void {
160
+ if (sessionIdOrName === "--all") {
161
+ const sessions = listSessions();
162
+ if (sessions.length === 0) {
163
+ console.log("No sessions to delete.");
164
+ return;
165
+ }
166
+ for (const s of sessions) {
167
+ deleteSession(s.id);
168
+ }
169
+ console.log(`Deleted ${sessions.length} session${sessions.length === 1 ? "" : "s"}.`);
170
+ return;
171
+ }
172
+
173
+ const deleted = deleteSession(sessionIdOrName);
174
+ if (!deleted) {
175
+ throw new CommandError(`Session "${sessionIdOrName}" not found.`);
176
+ }
177
+ console.log(`Deleted session "${sessionIdOrName}".`);
178
+ }
179
+
180
+ export function parseDuration(str: string): number {
181
+ const match = str.match(/^(\d+)\s*(s|m|h|d|w)$/);
182
+ if (!match)
183
+ throw new CommandError(`Invalid duration "${str}". Use format: 1d, 12h, 30m, 7w, etc.`);
184
+ const value = parseInt(match[1], 10);
185
+ const unit = match[2];
186
+ const multipliers: Record<string, number> = {
187
+ s: 1000,
188
+ m: 60_000,
189
+ h: 3_600_000,
190
+ d: 86_400_000,
191
+ w: 604_800_000,
192
+ };
193
+ return value * multipliers[unit];
194
+ }
195
+
196
+ export function querySessionsClean(): void {
197
+ const sessions = listSessions();
198
+ if (sessions.length === 0) {
199
+ console.log("No sessions found.");
200
+ return;
201
+ }
202
+
203
+ let cleaned = 0;
204
+
205
+ for (const sessionMeta of sessions) {
206
+ const identifier = sessionMeta.name ?? sessionMeta.id;
207
+ try {
208
+ const session = loadSession(sessionMeta.id);
209
+ const schema = getSessionSchema(session);
210
+ const queryString = renderQuery(session);
211
+ const errors = validateQuery(schema, queryString);
212
+ if (errors.length > 0) {
213
+ deleteSession(sessionMeta.id);
214
+ console.log(
215
+ ` Removed ${identifier} (${errors.length} error${errors.length !== 1 ? "s" : ""})`,
216
+ );
217
+ cleaned++;
218
+ }
219
+ } catch {
220
+ deleteSession(sessionMeta.id);
221
+ console.log(` Removed ${identifier} (load/schema error)`);
222
+ cleaned++;
223
+ }
224
+ }
225
+
226
+ if (cleaned === 0) {
227
+ console.log("All sessions are valid. Nothing to clean.");
228
+ } else {
229
+ console.log(`\nCleaned ${cleaned} invalid session${cleaned !== 1 ? "s" : ""}.`);
230
+ }
231
+ }
232
+
233
+ export function querySessionsPrune(olderThan: string): void {
234
+ const maxAgeMs = parseDuration(olderThan);
235
+ const sessions = listSessions();
236
+ const cutoff = Date.now() - maxAgeMs;
237
+ let count = 0;
238
+ for (const s of sessions) {
239
+ if (new Date(s.createdAt).getTime() < cutoff) {
240
+ deleteSession(s.id);
241
+ count++;
242
+ }
243
+ }
244
+ console.log(`Pruned ${count} session${count === 1 ? "" : "s"} older than ${olderThan}.`);
245
+ }
246
+
247
+ export function formatAge(date: Date): string {
248
+ const diffMs = Date.now() - date.getTime();
249
+ const diffSec = Math.floor(diffMs / 1000);
250
+ if (diffSec < 60) return `${diffSec}s ago`;
251
+ const diffMin = Math.floor(diffSec / 60);
252
+ if (diffMin < 60) return `${diffMin}m ago`;
253
+ const diffHr = Math.floor(diffMin / 60);
254
+ if (diffHr < 24) return `${diffHr}h ago`;
255
+ return `${Math.floor(diffHr / 24)}d ago`;
256
+ }
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import { type GraphQLSchema, isInputObjectType } from "graphql";
8
+ import { getOrgAuth } from "../lib/auth.js";
9
+ import { formatTypeInfo, type TypeAnnotations } from "../lib/formatter.js";
10
+ import {
11
+ getObjectInfo,
12
+ getCachedObjectInfo,
13
+ type ObjectInfoResult,
14
+ type FieldMetadata,
15
+ } from "../lib/object-info.js";
16
+ import { getSchema, inspectType, type TypeInfo } from "../lib/walker.js";
17
+
18
+ export async function typeCommand(orgAlias: string, typeName: string): Promise<void> {
19
+ const auth = await getOrgAuth(orgAlias);
20
+ const schema = getSchema(auth.instanceUrl);
21
+ const info = inspectType(schema, typeName);
22
+ const annotations = await buildAnnotations(schema, orgAlias, typeName, info);
23
+ console.log(formatTypeInfo(info, annotations));
24
+ }
25
+
26
+ // ── Annotation builders ───────────────────────────────────────────────────────
27
+
28
+ async function buildAnnotations(
29
+ schema: GraphQLSchema,
30
+ orgAlias: string,
31
+ typeName: string,
32
+ info: TypeInfo,
33
+ ): Promise<TypeAnnotations> {
34
+ const ann: TypeAnnotations = {};
35
+
36
+ switch (info.kind) {
37
+ case "OBJECT":
38
+ case "INTERFACE":
39
+ annotateObjectFields(schema, info, ann);
40
+ await annotateObjectWithObjectInfo(schema, orgAlias, typeName, info, ann);
41
+ break;
42
+
43
+ case "INPUT_OBJECT":
44
+ await annotateInputObject(schema, orgAlias, typeName, info, ann);
45
+ break;
46
+ }
47
+
48
+ return ann;
49
+ }
50
+
51
+ /**
52
+ * For OBJECT/INTERFACE types: annotate fields with wrapper-type leaf fields
53
+ * (e.g. StringValue → { value, displayValue, label }) and child relationship args.
54
+ */
55
+ function annotateObjectFields(schema: GraphQLSchema, info: TypeInfo, ann: TypeAnnotations): void {
56
+ ann.fieldComments = ann.fieldComments ?? new Map();
57
+
58
+ for (const field of info.fields) {
59
+ const comments: string[] = [];
60
+
61
+ if (field.args.length > 0) {
62
+ const argSummary = field.args
63
+ .map((a) => {
64
+ if (a.enumValues) return `${a.name}: ${a.enumValues.join(" | ")}`;
65
+ return `${a.name}: ${a.typeName}`;
66
+ })
67
+ .join(", ");
68
+ comments.push(`child: ${argSummary}`);
69
+ }
70
+
71
+ if (comments.length > 0) {
72
+ ann.fieldComments.set(field.name, comments);
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Enrich OBJECT types with ObjectInfo data: parent references, child
79
+ * relationship targets, picklist values.
80
+ */
81
+ async function annotateObjectWithObjectInfo(
82
+ schema: GraphQLSchema,
83
+ orgAlias: string,
84
+ typeName: string,
85
+ info: TypeInfo,
86
+ ann: TypeAnnotations,
87
+ ): Promise<void> {
88
+ const sObjectName = detectSObjectFromObjectType(typeName);
89
+ if (!sObjectName) return;
90
+
91
+ const objInfo = await fetchObjectInfoQuietly(orgAlias, sObjectName);
92
+ if (!objInfo) return;
93
+
94
+ ann.fieldComments = ann.fieldComments ?? new Map();
95
+ const fieldMap = new Map(objInfo.fields.map((f) => [f.apiName, f]));
96
+ const relNameMap = new Map(
97
+ objInfo.fields.filter((f) => f.relationshipName).map((f) => [f.relationshipName!, f]),
98
+ );
99
+
100
+ for (const field of info.fields) {
101
+ const existing = ann.fieldComments.get(field.name) ?? [];
102
+
103
+ const meta = fieldMap.get(field.name);
104
+ if (meta) {
105
+ if (meta.reference && meta.referenceToInfos.length > 0) {
106
+ const targets = meta.referenceToInfos.map((r) => r.apiName).join(", ");
107
+ existing.unshift(`-> ${targets}`);
108
+ }
109
+
110
+ addPicklistComment(objInfo, meta.apiName, existing);
111
+ }
112
+
113
+ const relMeta = relNameMap.get(field.name);
114
+ if (relMeta && relMeta.reference && relMeta.referenceToInfos.length > 0) {
115
+ const targets = relMeta.referenceToInfos.map((r) => r.apiName).join(", ");
116
+ if (!existing.some((c) => c.startsWith("->"))) {
117
+ existing.unshift(`-> ${targets}`);
118
+ }
119
+ }
120
+
121
+ if (existing.length > 0) {
122
+ ann.fieldComments.set(field.name, existing);
123
+ }
124
+ }
125
+
126
+ const childRelMap = new Map(
127
+ objInfo.childRelationships
128
+ .filter((cr) => cr.relationshipName)
129
+ .map((cr) => [cr.relationshipName!, cr]),
130
+ );
131
+ for (const field of info.fields) {
132
+ const cr = childRelMap.get(field.name);
133
+ if (cr) {
134
+ const existing = ann.fieldComments.get(field.name) ?? [];
135
+ if (!existing.some((c) => c.startsWith("child:"))) {
136
+ existing.unshift(`child -> ${cr.childObjectApiName}.${cr.fieldName}`);
137
+ }
138
+ ann.fieldComments.set(field.name, existing);
139
+ }
140
+ }
141
+ }
142
+
143
+ /**
144
+ * For INPUT_OBJECT types: detect whether it's a mutation input, filter, or
145
+ * orderBy and annotate accordingly.
146
+ */
147
+ async function annotateInputObject(
148
+ schema: GraphQLSchema,
149
+ orgAlias: string,
150
+ typeName: string,
151
+ info: TypeInfo,
152
+ ann: TypeAnnotations,
153
+ ): Promise<void> {
154
+ ann.fieldComments = ann.fieldComments ?? new Map();
155
+
156
+ if (isFilterType(typeName)) {
157
+ annotateFilterFields(schema, info, ann);
158
+ ann.filterExamples = buildFilterExamples(typeName, info, schema);
159
+ return;
160
+ }
161
+
162
+ if (isOrderByType(typeName)) {
163
+ annotateOrderByFields(schema, info, ann);
164
+ return;
165
+ }
166
+
167
+ const mutationMatch = typeName.match(/^(\w+?)(Create|Update)(?:Input|Representation)$/);
168
+ if (mutationMatch) {
169
+ await annotateMutationInput(
170
+ schema,
171
+ orgAlias,
172
+ typeName,
173
+ info,
174
+ ann,
175
+ mutationMatch[1],
176
+ mutationMatch[2],
177
+ );
178
+ }
179
+ }
180
+
181
+ // ── Filter annotation ─────────────────────────────────────────────────────────
182
+
183
+ function annotateFilterFields(schema: GraphQLSchema, info: TypeInfo, ann: TypeAnnotations): void {
184
+ for (const field of info.inputFields) {
185
+ const comments: string[] = [];
186
+ const namedType = schema.getType(stripWrapping(field.typeName));
187
+
188
+ if (namedType && isInputObjectType(namedType)) {
189
+ if (field.typeName.endsWith("Operators")) {
190
+ const ops = Object.keys(namedType.getFields()).join(", ");
191
+ comments.push(`${namedType.name}: ${ops}`);
192
+ } else if (field.name === "and" || field.name === "or" || field.name === "not") {
193
+ // combinators — no extra comment needed
194
+ } else {
195
+ comments.push("nested filter");
196
+ }
197
+ }
198
+
199
+ if (comments.length > 0) {
200
+ ann.fieldComments!.set(field.name, comments);
201
+ }
202
+ }
203
+ }
204
+
205
+ function buildFilterExamples(typeName: string, info: TypeInfo, schema: GraphQLSchema): string[] {
206
+ const examples: string[] = [];
207
+ const _sObject = typeName.replace(/_Filter$/, "");
208
+
209
+ const simpleField = info.inputFields.find(
210
+ (f) => f.typeName.endsWith("Operators") && !["and", "or", "not"].includes(f.name),
211
+ );
212
+ if (simpleField) {
213
+ const namedType = schema.getType(stripWrapping(simpleField.typeName));
214
+ if (namedType && isInputObjectType(namedType)) {
215
+ const ops = Object.keys(namedType.getFields());
216
+ const op = ops.includes("gt") ? "gt" : ops.includes("eq") ? "eq" : ops[0];
217
+ examples.push(`{ "${simpleField.name}": { "${op}": <value> } }`);
218
+ }
219
+ }
220
+
221
+ const twoFilterable = info.inputFields
222
+ .filter((f) => f.typeName.endsWith("Operators") && !["and", "or", "not"].includes(f.name))
223
+ .slice(0, 2);
224
+ if (twoFilterable.length === 2) {
225
+ examples.push(
226
+ `{ "and": [{ "${twoFilterable[0].name}": { "eq": <value> } }, { "${twoFilterable[1].name}": { "gt": <value> } }] }`,
227
+ );
228
+ }
229
+
230
+ return examples;
231
+ }
232
+
233
+ // ── OrderBy annotation ────────────────────────────────────────────────────────
234
+
235
+ function annotateOrderByFields(schema: GraphQLSchema, info: TypeInfo, ann: TypeAnnotations): void {
236
+ let shownClause = false;
237
+ for (const field of info.inputFields) {
238
+ const comments: string[] = [];
239
+ const namedType = schema.getType(stripWrapping(field.typeName));
240
+
241
+ if (namedType && isInputObjectType(namedType)) {
242
+ const _fieldNames = Object.keys(namedType.getFields());
243
+ if (!shownClause && field.typeName === "OrderByClause") {
244
+ comments.push(`{ order: ASC | DESC, nulls: FIRST | LAST }`);
245
+ shownClause = true;
246
+ } else if (field.typeName !== "OrderByClause") {
247
+ comments.push(`nested: ${namedType.name}`);
248
+ }
249
+ }
250
+
251
+ if (comments.length > 0) {
252
+ ann.fieldComments!.set(field.name, comments);
253
+ }
254
+ }
255
+ }
256
+
257
+ // ── Mutation input annotation ─────────────────────────────────────────────────
258
+
259
+ async function annotateMutationInput(
260
+ schema: GraphQLSchema,
261
+ orgAlias: string,
262
+ typeName: string,
263
+ info: TypeInfo,
264
+ ann: TypeAnnotations,
265
+ sObjectName: string,
266
+ _operation: string,
267
+ ): Promise<void> {
268
+ const objInfo = await fetchObjectInfoQuietly(orgAlias, sObjectName);
269
+
270
+ if (objInfo) {
271
+ const fieldMap = new Map(objInfo.fields.map((f) => [f.apiName, f]));
272
+
273
+ for (const field of info.inputFields) {
274
+ const comments: string[] = [];
275
+ const meta = fieldMap.get(field.name);
276
+ if (meta) {
277
+ addMutationFieldTags(meta, comments);
278
+ addPicklistComment(objInfo, meta.apiName, comments);
279
+ if (meta.reference && meta.referenceToInfos.length > 0) {
280
+ const targets = meta.referenceToInfos.map((r) => r.apiName).join(", ");
281
+ comments.push(`-> ${targets}`);
282
+ }
283
+ }
284
+ if (comments.length > 0) {
285
+ ann.fieldComments!.set(field.name, comments);
286
+ }
287
+ }
288
+ }
289
+
290
+ const nestedInput = info.inputFields.find((f) => {
291
+ const t = schema.getType(stripWrapping(f.typeName));
292
+ return t && isInputObjectType(t);
293
+ });
294
+ if (nestedInput) {
295
+ const nestedType = schema.getType(stripWrapping(nestedInput.typeName));
296
+ if (nestedType && isInputObjectType(nestedType)) {
297
+ const nestedInfo = inspectType(schema, nestedType.name);
298
+ ann.inlinedInputs = ann.inlinedInputs ?? [];
299
+ const nestedAnn: TypeAnnotations = { fieldComments: new Map() };
300
+
301
+ if (objInfo) {
302
+ const fieldMap = new Map(objInfo.fields.map((f) => [f.apiName, f]));
303
+ for (const field of nestedInfo.inputFields) {
304
+ const comments: string[] = [];
305
+ const meta = fieldMap.get(field.name);
306
+ if (meta) {
307
+ addMutationFieldTags(meta, comments);
308
+ addPicklistComment(objInfo, meta.apiName, comments);
309
+ if (meta.reference && meta.referenceToInfos.length > 0) {
310
+ const targets = meta.referenceToInfos.map((r) => r.apiName).join(", ");
311
+ comments.push(`-> ${targets}`);
312
+ }
313
+ }
314
+ if (comments.length > 0) {
315
+ nestedAnn.fieldComments!.set(field.name, comments);
316
+ }
317
+ }
318
+
319
+ nestedAnn.sampleInput = buildSampleInput(sObjectName, objInfo, nestedInfo);
320
+ }
321
+
322
+ ann.inlinedInputs.push({ info: nestedInfo, annotations: nestedAnn });
323
+ }
324
+ }
325
+ }
326
+
327
+ function addMutationFieldTags(meta: FieldMetadata, comments: string[]): void {
328
+ const tags: string[] = [];
329
+ if (meta.required && meta.createable && !meta.defaultedOnCreate) tags.push("required");
330
+ if (meta.defaultedOnCreate) tags.push("has default");
331
+ if (!meta.createable && !meta.updateable) tags.push("read-only");
332
+ if (tags.length > 0) comments.push(tags.join(" | "));
333
+ }
334
+
335
+ function buildSampleInput(
336
+ sObjectName: string,
337
+ objInfo: ObjectInfoResult,
338
+ nestedInfo: TypeInfo,
339
+ ): string {
340
+ const requiredFields = objInfo.fields.filter(
341
+ (f) => f.required && f.createable && !f.defaultedOnCreate,
342
+ );
343
+ const nestedFieldNames = new Set(nestedInfo.inputFields.map((f) => f.name));
344
+ const relevant = requiredFields.filter((f) => nestedFieldNames.has(f.apiName));
345
+
346
+ const entries: Record<string, string> = {};
347
+ for (const f of relevant) {
348
+ const picklist = objInfo.picklists.find((p) => p.apiName === f.apiName);
349
+ if (picklist && picklist.values.length > 0) {
350
+ entries[f.apiName] = JSON.stringify(picklist.values[0].value);
351
+ } else if (f.dataType === "STRING" || f.dataType === "TEXTAREA") {
352
+ entries[f.apiName] = `"<${f.label ?? f.apiName}>"`;
353
+ } else if (
354
+ f.dataType === "CURRENCY" ||
355
+ f.dataType === "DOUBLE" ||
356
+ f.dataType === "INT" ||
357
+ f.dataType === "PERCENT"
358
+ ) {
359
+ entries[f.apiName] = "0";
360
+ } else if (f.dataType === "DATE") {
361
+ entries[f.apiName] = `"${new Date().toISOString().split("T")[0]}"`;
362
+ } else if (f.dataType === "DATETIME") {
363
+ entries[f.apiName] = `"${new Date().toISOString()}"`;
364
+ } else if (f.dataType === "BOOLEAN") {
365
+ entries[f.apiName] = "false";
366
+ } else if (f.dataType === "REFERENCE") {
367
+ entries[f.apiName] = '"<record-id>"';
368
+ } else {
369
+ entries[f.apiName] = `"<${f.apiName}>"`;
370
+ }
371
+ }
372
+
373
+ const inner = Object.entries(entries)
374
+ .map(([k, v]) => ` ${JSON.stringify(k)}: ${v}`)
375
+ .join(",\n");
376
+ return `{ "${sObjectName}": {\n${inner}\n } }`;
377
+ }
378
+
379
+ // ── Helpers ───────────────────────────────────────────────────────────────────
380
+
381
+ function addPicklistComment(
382
+ objInfo: ObjectInfoResult,
383
+ fieldName: string,
384
+ comments: string[],
385
+ ): void {
386
+ const picklist = objInfo.picklists.find((p) => p.apiName === fieldName);
387
+ if (picklist && picklist.values.length > 0) {
388
+ const vals = picklist.values.map((v) => v.value).join(", ");
389
+ comments.push(`picklist: [${vals}]`);
390
+ }
391
+ }
392
+
393
+ function stripWrapping(typeName: string): string {
394
+ return typeName.replace(/[[\]!]/g, "");
395
+ }
396
+
397
+ function isFilterType(name: string): boolean {
398
+ return /_Filter$/.test(name);
399
+ }
400
+
401
+ function isOrderByType(name: string): boolean {
402
+ return /_OrderBy$/.test(name);
403
+ }
404
+
405
+ function detectSObjectFromObjectType(typeName: string): string | null {
406
+ if (typeName.endsWith("Connection")) {
407
+ return typeName.replace(/Connection$/, "");
408
+ }
409
+ if (typeName.endsWith("Edge")) {
410
+ return typeName.replace(/Edge$/, "");
411
+ }
412
+ const schemaOnlyTypes = new Set([
413
+ "UIAPI",
414
+ "RecordQuery",
415
+ "RecordQueryAggregate",
416
+ "PageInfo",
417
+ "UIAPIMutations",
418
+ "FieldValue",
419
+ ]);
420
+ if (schemaOnlyTypes.has(typeName)) return null;
421
+ return typeName;
422
+ }
423
+
424
+ async function fetchObjectInfoQuietly(
425
+ orgAlias: string,
426
+ sObjectName: string,
427
+ ): Promise<ObjectInfoResult | null> {
428
+ let info = getCachedObjectInfo(orgAlias, sObjectName);
429
+ if (info) return info;
430
+ try {
431
+ const auth = await getOrgAuth(orgAlias);
432
+ info = await getObjectInfo(auth, orgAlias, sObjectName);
433
+ return info;
434
+ } catch {
435
+ return null;
436
+ }
437
+ }