@salesforce/graphiti 11.13.2 → 11.14.0
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/CHANGELOG.md +6 -0
- package/dist/intent/build-list.js +7 -6
- package/dist/intent/build-list.js.map +1 -1
- package/dist/intent/select-child-relationship.js +7 -6
- package/dist/intent/select-child-relationship.js.map +1 -1
- package/dist/intent/types.d.ts +6 -6
- package/dist/lib/variable-promotion.d.ts +26 -0
- package/dist/lib/variable-promotion.js +43 -0
- package/dist/lib/variable-promotion.js.map +1 -1
- package/dist/schemas/fields.d.ts +41 -12
- package/dist/schemas/fields.js +57 -7
- package/dist/schemas/fields.js.map +1 -1
- package/dist/schemas/input-schemas.d.ts +35 -35
- package/dist/schemas/input-schemas.js +19 -15
- package/dist/schemas/input-schemas.js.map +1 -1
- package/package.json +1 -1
- package/src/intent/__tests__/build-detail.spec.ts +54 -0
- package/src/intent/__tests__/build-list.spec.ts +59 -0
- package/src/intent/build-list.ts +21 -6
- package/src/intent/select-child-relationship.ts +27 -6
- package/src/intent/types.ts +6 -6
- package/src/lib/__tests__/variable-promotion.spec.ts +51 -1
- package/src/lib/variable-promotion.ts +62 -0
- package/src/mcp/tools/__tests__/sf-gql-detail.spec.ts +5 -4
- package/src/mcp/tools/__tests__/sf-gql-list.spec.ts +287 -1
- package/src/schemas/fields.ts +66 -7
- package/src/schemas/input-schemas.ts +37 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-schemas.js","sourceRoot":"","sources":["../../src/schemas/input-schemas.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,
|
|
1
|
+
{"version":3,"file":"input-schemas.js","sourceRoot":"","sources":["../../src/schemas/input-schemas.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -335,4 +335,58 @@ describe("intent/build-detail", () => {
|
|
|
335
335
|
await buildDetail({ org: ORG, object: "Account", fields: ["Id", "Name"] }, noopPrimeDeps());
|
|
336
336
|
expect(spy).toHaveBeenCalledWith(ORG, "query", ORG_URL);
|
|
337
337
|
});
|
|
338
|
+
|
|
339
|
+
it("promotes a whole-argument $orderBy on a child relationship (the $commentsOrderBy gap)", async () => {
|
|
340
|
+
const out = await buildDetail(
|
|
341
|
+
{
|
|
342
|
+
org: ORG,
|
|
343
|
+
object: "Account",
|
|
344
|
+
fields: ["Id"],
|
|
345
|
+
childRelationships: [
|
|
346
|
+
{ relationshipName: "Contacts", fields: ["Id"], orderBy: "$commentsOrderBy" },
|
|
347
|
+
],
|
|
348
|
+
},
|
|
349
|
+
noopPrimeDeps(),
|
|
350
|
+
);
|
|
351
|
+
const v = out.variables.find((x) => x.name === "commentsOrderBy");
|
|
352
|
+
expect(v).toBeDefined();
|
|
353
|
+
expect(v!.type).toMatch(/_OrderBy$/);
|
|
354
|
+
expect(out.query).toMatch(/orderBy\s*:\s*\$commentsOrderBy\b/);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("promotes a whole-argument $filter on a child relationship", async () => {
|
|
358
|
+
const out = await buildDetail(
|
|
359
|
+
{
|
|
360
|
+
org: ORG,
|
|
361
|
+
object: "Account",
|
|
362
|
+
fields: ["Id"],
|
|
363
|
+
childRelationships: [
|
|
364
|
+
{ relationshipName: "Contacts", fields: ["Id"], filter: "$contactFilter" },
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
noopPrimeDeps(),
|
|
368
|
+
);
|
|
369
|
+
const v = out.variables.find((x) => x.name === "contactFilter");
|
|
370
|
+
expect(v).toBeDefined();
|
|
371
|
+
expect(v!.type).toMatch(/_Filter$/);
|
|
372
|
+
expect(out.query).toMatch(/where\s*:\s*\$contactFilter\b/);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("promotes a whole-argument $first on a child relationship", async () => {
|
|
376
|
+
const out = await buildDetail(
|
|
377
|
+
{
|
|
378
|
+
org: ORG,
|
|
379
|
+
object: "Account",
|
|
380
|
+
fields: ["Id"],
|
|
381
|
+
childRelationships: [
|
|
382
|
+
{ relationshipName: "Contacts", fields: ["Id"], first: "$childFirst" },
|
|
383
|
+
],
|
|
384
|
+
},
|
|
385
|
+
noopPrimeDeps(),
|
|
386
|
+
);
|
|
387
|
+
const v = out.variables.find((x) => x.name === "childFirst");
|
|
388
|
+
expect(v).toBeDefined();
|
|
389
|
+
expect(v!.type).toBe("Int");
|
|
390
|
+
expect(out.query).toMatch(/first\s*:\s*\$childFirst\b/);
|
|
391
|
+
});
|
|
338
392
|
});
|
|
@@ -411,4 +411,63 @@ describe("intent/build-list", () => {
|
|
|
411
411
|
),
|
|
412
412
|
).toBe(true);
|
|
413
413
|
});
|
|
414
|
+
|
|
415
|
+
it("promotes a whole-argument $filter to a typed <Object>_Filter variable", async () => {
|
|
416
|
+
const out = await buildList(
|
|
417
|
+
{ org: ORG, object: "Case", fields: ["Id"], filter: "$filter" },
|
|
418
|
+
noopPrimeDeps(),
|
|
419
|
+
);
|
|
420
|
+
const filter = out.variables.find((v) => v.name === "filter");
|
|
421
|
+
expect(filter).toBeDefined();
|
|
422
|
+
expect(filter!.type).toBe("Case_Filter");
|
|
423
|
+
expect(filter!.required).toBe(false);
|
|
424
|
+
expect(out.query).toMatch(/where\s*:\s*\$filter\b/);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("promotes a whole-argument $orderBy to a typed <Object>_OrderBy variable", async () => {
|
|
428
|
+
const out = await buildList(
|
|
429
|
+
{ org: ORG, object: "Case", fields: ["Id"], orderBy: "$sort" },
|
|
430
|
+
noopPrimeDeps(),
|
|
431
|
+
);
|
|
432
|
+
const sort = out.variables.find((v) => v.name === "sort");
|
|
433
|
+
expect(sort).toBeDefined();
|
|
434
|
+
expect(sort!.type).toBe("Case_OrderBy");
|
|
435
|
+
expect(out.query).toMatch(/orderBy\s*:\s*\$sort\b/);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("leaf filter promotion still works after whole-arg support", async () => {
|
|
439
|
+
const out = await buildList(
|
|
440
|
+
{ org: ORG, object: "Case", fields: ["Id"], filter: { Status: { eq: "$status" } } },
|
|
441
|
+
noopPrimeDeps(),
|
|
442
|
+
);
|
|
443
|
+
expect(out.variables.find((v) => v.name === "status")).toBeDefined();
|
|
444
|
+
expect(out.query).toMatch(/\$status\b/);
|
|
445
|
+
expect(out.query).not.toMatch(/where\s*:\s*\$status\b/); // leaf, not whole-arg
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("promotes a whole-argument $first to a typed Int variable", async () => {
|
|
449
|
+
const out = await buildList(
|
|
450
|
+
{ org: ORG, object: "Case", fields: ["Id"], first: "$first" },
|
|
451
|
+
noopPrimeDeps(),
|
|
452
|
+
);
|
|
453
|
+
const v = out.variables.find((x) => x.name === "first");
|
|
454
|
+
expect(v).toBeDefined();
|
|
455
|
+
expect(v!.type).toBe("Int");
|
|
456
|
+
expect(v!.required).toBe(false);
|
|
457
|
+
expect(out.query).toMatch(/first\s*:\s*\$first\b/);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it("numeric first still renders as a literal", async () => {
|
|
461
|
+
const out = await buildList(
|
|
462
|
+
{ org: ORG, object: "Case", fields: ["Id"], first: 25 },
|
|
463
|
+
noopPrimeDeps(),
|
|
464
|
+
);
|
|
465
|
+
expect(out.variables.find((x) => x.name === "first")).toBeUndefined();
|
|
466
|
+
expect(out.query).toMatch(/first\s*:\s*25\b/);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("omitted first defaults to the literal 10", async () => {
|
|
470
|
+
const out = await buildList({ org: ORG, object: "Case", fields: ["Id"] }, noopPrimeDeps());
|
|
471
|
+
expect(out.query).toMatch(/first\s*:\s*10\b/);
|
|
472
|
+
});
|
|
414
473
|
});
|
package/src/intent/build-list.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { assertGraphqlName } from "../lib/graphql-name.js";
|
|
|
12
12
|
import { selectDottedFieldPath } from "../lib/path-selection.js";
|
|
13
13
|
import { type PrimeDeps } from "../lib/prime-schema.js";
|
|
14
14
|
import { addVariable, createSession, deepSetArg, selectLeaf } from "../lib/session.js";
|
|
15
|
-
import { normalizeOrderBy, promoteVariables } from "../lib/variable-promotion.js";
|
|
15
|
+
import { normalizeOrderBy, promoteArg, promoteVariables } from "../lib/variable-promotion.js";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Build a UIAPI list query against a Salesforce org. Implements `sf_gql_list`
|
|
@@ -71,20 +71,35 @@ export async function buildList(spec: ListSpec, deps?: PrimeDeps): Promise<ToolO
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const first = spec.first ?? 10;
|
|
74
|
-
|
|
74
|
+
const firstRendered = promoteArg(session, schema, connectionPath, "first", first).rendered;
|
|
75
|
+
deepSetArg(session, connectionPath, "first", [], firstRendered);
|
|
75
76
|
|
|
76
77
|
selectLeaf(session, [...connectionPath, "pageInfo", "hasNextPage"]);
|
|
77
78
|
selectLeaf(session, [...connectionPath, "pageInfo", "endCursor"]);
|
|
78
79
|
|
|
79
80
|
if (spec.filter) {
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
const { rendered } = promoteArg(
|
|
82
|
+
session,
|
|
83
|
+
schema,
|
|
84
|
+
connectionPath,
|
|
85
|
+
"where",
|
|
86
|
+
spec.filter,
|
|
87
|
+
extraWarnings,
|
|
88
|
+
);
|
|
89
|
+
deepSetArg(session, connectionPath, "where", [], rendered);
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
const orderBy = normalizeOrderBy(spec.orderBy);
|
|
85
93
|
if (orderBy) {
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
const { rendered } = promoteArg(
|
|
95
|
+
session,
|
|
96
|
+
schema,
|
|
97
|
+
connectionPath,
|
|
98
|
+
"orderBy",
|
|
99
|
+
orderBy,
|
|
100
|
+
extraWarnings,
|
|
101
|
+
);
|
|
102
|
+
deepSetArg(session, connectionPath, "orderBy", [], rendered);
|
|
88
103
|
}
|
|
89
104
|
|
|
90
105
|
if (spec.scope) {
|
|
@@ -8,7 +8,7 @@ import { type GraphQLSchema } from "graphql";
|
|
|
8
8
|
import { type ChildRelationshipSpec } from "./types.js";
|
|
9
9
|
import { selectDottedFieldPath } from "../lib/path-selection.js";
|
|
10
10
|
import { deepSetArg, type QuerySession } from "../lib/session.js";
|
|
11
|
-
import { normalizeOrderBy,
|
|
11
|
+
import { normalizeOrderBy, promoteArg } from "../lib/variable-promotion.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Render a UIAPI child relationship onto an existing query session as
|
|
@@ -35,15 +35,36 @@ export function selectChildRelationship(
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
if (child.first !== undefined) {
|
|
38
|
-
|
|
38
|
+
const firstRendered = promoteArg(
|
|
39
|
+
session,
|
|
40
|
+
schema,
|
|
41
|
+
childConnectionPath,
|
|
42
|
+
"first",
|
|
43
|
+
child.first,
|
|
44
|
+
).rendered;
|
|
45
|
+
deepSetArg(session, childConnectionPath, "first", [], firstRendered);
|
|
39
46
|
}
|
|
40
47
|
if (child.filter) {
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
const { rendered } = promoteArg(
|
|
49
|
+
session,
|
|
50
|
+
schema,
|
|
51
|
+
childConnectionPath,
|
|
52
|
+
"where",
|
|
53
|
+
child.filter,
|
|
54
|
+
warnings,
|
|
55
|
+
);
|
|
56
|
+
deepSetArg(session, childConnectionPath, "where", [], rendered);
|
|
43
57
|
}
|
|
44
58
|
const childOrderBy = normalizeOrderBy(child.orderBy);
|
|
45
59
|
if (childOrderBy) {
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
const { rendered } = promoteArg(
|
|
61
|
+
session,
|
|
62
|
+
schema,
|
|
63
|
+
childConnectionPath,
|
|
64
|
+
"orderBy",
|
|
65
|
+
childOrderBy,
|
|
66
|
+
warnings,
|
|
67
|
+
);
|
|
68
|
+
deepSetArg(session, childConnectionPath, "orderBy", [], rendered);
|
|
48
69
|
}
|
|
49
70
|
}
|
package/src/intent/types.ts
CHANGED
|
@@ -26,9 +26,9 @@ export interface VariableInfo {
|
|
|
26
26
|
export interface ChildRelationshipSpec {
|
|
27
27
|
relationshipName: string;
|
|
28
28
|
fields: string[];
|
|
29
|
-
first?: number;
|
|
30
|
-
filter?: Record<string, unknown
|
|
31
|
-
orderBy?: Record<string, unknown> | Record<string, unknown>[];
|
|
29
|
+
first?: number | string;
|
|
30
|
+
filter?: Record<string, unknown> | string;
|
|
31
|
+
orderBy?: Record<string, unknown> | Record<string, unknown>[] | string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export interface ListSpec {
|
|
@@ -37,9 +37,9 @@ export interface ListSpec {
|
|
|
37
37
|
fields: string[];
|
|
38
38
|
parentFields?: string[];
|
|
39
39
|
childRelationships?: ChildRelationshipSpec[];
|
|
40
|
-
filter?: Record<string, unknown
|
|
41
|
-
orderBy?: Record<string, unknown> | Record<string, unknown>[];
|
|
42
|
-
first?: number;
|
|
40
|
+
filter?: Record<string, unknown> | string;
|
|
41
|
+
orderBy?: Record<string, unknown> | Record<string, unknown>[] | string;
|
|
42
|
+
first?: number | string;
|
|
43
43
|
scope?: string;
|
|
44
44
|
operationName?: string;
|
|
45
45
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { buildSchema } from "graphql";
|
|
8
8
|
import { describe, expect, it } from "vitest";
|
|
9
9
|
import { createSession, type QuerySession } from "../session.js";
|
|
10
|
-
import { normalizeOrderBy, promoteVariables } from "../variable-promotion.js";
|
|
10
|
+
import { normalizeOrderBy, promoteArg, promoteVariables } from "../variable-promotion.js";
|
|
11
11
|
|
|
12
12
|
// Mimics the relevant slice of the Salesforce UIAPI schema: a connection
|
|
13
13
|
// field with `where`, `orderBy`, and `scope` args, where `where` is a
|
|
@@ -247,6 +247,56 @@ describe("variable-promotion", () => {
|
|
|
247
247
|
});
|
|
248
248
|
});
|
|
249
249
|
|
|
250
|
+
describe("promoteArg: whole-argument promotion", () => {
|
|
251
|
+
it("promotes a whole-arg $var on where to the filter input type", () => {
|
|
252
|
+
const session = makeSession();
|
|
253
|
+
const out = promoteArg(session, schema, accountFieldPath, "where", "$filter");
|
|
254
|
+
expect(out.rendered).toBe("$filter");
|
|
255
|
+
expect(session.variables).toHaveLength(1);
|
|
256
|
+
expect(session.variables[0]!.name).toBe("filter");
|
|
257
|
+
expect(session.variables[0]!.type).toBe("Account_Filter");
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("promotes a whole-arg $var on orderBy to the orderBy input type", () => {
|
|
261
|
+
const session = makeSession();
|
|
262
|
+
const out = promoteArg(session, schema, accountFieldPath, "orderBy", "$sort");
|
|
263
|
+
expect(out.rendered).toBe("$sort");
|
|
264
|
+
expect(session.variables[0]!.name).toBe("sort");
|
|
265
|
+
expect(session.variables[0]!.type).toBe("Account_OrderBy");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("delegates object values to leaf promotion and renders JSON", () => {
|
|
269
|
+
const session = makeSession();
|
|
270
|
+
const out = promoteArg(session, schema, accountFieldPath, "where", {
|
|
271
|
+
Status: { eq: "$status" },
|
|
272
|
+
});
|
|
273
|
+
expect(out.rendered).toBe(JSON.stringify({ Status: { eq: "$status" } }));
|
|
274
|
+
expect(session.variables).toHaveLength(1);
|
|
275
|
+
expect(session.variables[0]!.name).toBe("status");
|
|
276
|
+
expect(session.variables[0]!.type).toBe("Picklist");
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("renders literal object with no $vars and promotes nothing", () => {
|
|
280
|
+
const session = makeSession();
|
|
281
|
+
const out = promoteArg(session, schema, accountFieldPath, "where", {
|
|
282
|
+
Status: { eq: "Active" },
|
|
283
|
+
});
|
|
284
|
+
expect(out.rendered).toBe(JSON.stringify({ Status: { eq: "Active" } }));
|
|
285
|
+
expect(session.variables).toHaveLength(0);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("falls back to String and warns when the arg type cannot be inferred", () => {
|
|
289
|
+
const session = makeSession();
|
|
290
|
+
const warnings: string[] = [];
|
|
291
|
+
// `bogusArg` is not an argument on the Account field — inference throws.
|
|
292
|
+
const out = promoteArg(session, schema, accountFieldPath, "bogusArg", "$x", warnings);
|
|
293
|
+
expect(out.rendered).toBe("$x");
|
|
294
|
+
expect(session.variables[0]!.name).toBe("x");
|
|
295
|
+
expect(session.variables[0]!.type).toBe("String");
|
|
296
|
+
expect(warnings.some((w) => w.includes("$x") && /could not infer/i.test(w))).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
250
300
|
describe("normalizeOrderBy", () => {
|
|
251
301
|
it("collapses an array to its first element", () => {
|
|
252
302
|
const result = normalizeOrderBy([{ Name: "ASC" }, { Industry: "DESC" }]);
|
|
@@ -132,6 +132,68 @@ function walk(
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Result of promoting one whole argument. `rendered` is what the caller
|
|
137
|
+
* hands to `deepSetArg`: the raw `$var` reference when the entire argument
|
|
138
|
+
* was a single `$varName` string, or the JSON-stringified value otherwise.
|
|
139
|
+
*/
|
|
140
|
+
export interface ArgPromotion {
|
|
141
|
+
rendered: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Promote a `where` / `orderBy` argument that may be EITHER a normal
|
|
146
|
+
* object (whose `$varName` leaves promote via `promoteVariables`) OR a
|
|
147
|
+
* single top-level `$varName` string standing in for the whole argument.
|
|
148
|
+
*
|
|
149
|
+
* The whole-argument form is the idiomatic UIAPI pattern for an
|
|
150
|
+
* optionally-applied filter: a `null`-bound `where` means "no constraint →
|
|
151
|
+
* all rows", which a nullable leaf cannot express. When `value` is a
|
|
152
|
+
* whole-arg placeholder we infer the argument's own input type
|
|
153
|
+
* (e.g. `Case_Filter`, `Case_OrderBy`), declare one nullable variable, and
|
|
154
|
+
* return its `$ref` so the caller renders `where: $filter` directly.
|
|
155
|
+
*
|
|
156
|
+
* Type inference for a real `where`/`orderBy` argument effectively never
|
|
157
|
+
* fails, but if it does we still emit the variable as `String` and push a
|
|
158
|
+
* loud warning — a best-effort query the user can debug beats a hard error
|
|
159
|
+
* before they see anything.
|
|
160
|
+
*/
|
|
161
|
+
export function promoteArg(
|
|
162
|
+
session: QuerySession,
|
|
163
|
+
schema: GraphQLSchema,
|
|
164
|
+
fieldSchemaPath: string[],
|
|
165
|
+
argName: string,
|
|
166
|
+
value: unknown,
|
|
167
|
+
warnings?: string[],
|
|
168
|
+
): ArgPromotion {
|
|
169
|
+
if (typeof value === "string") {
|
|
170
|
+
const m = VAR_PLACEHOLDER_RE.exec(value);
|
|
171
|
+
if (m && m[1]) {
|
|
172
|
+
const varName = m[1];
|
|
173
|
+
try {
|
|
174
|
+
const { inferredType } = inferTypeFromArgsPath(schema, session.operation, fieldSchemaPath, [
|
|
175
|
+
argName,
|
|
176
|
+
]);
|
|
177
|
+
const declared = inferredType.endsWith("!") ? inferredType.slice(0, -1) : inferredType;
|
|
178
|
+
addVariable(session, varName, declared);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
addVariable(session, varName, "String");
|
|
181
|
+
const hint = err instanceof Error ? ` (${err.message})` : "";
|
|
182
|
+
warnings?.push(
|
|
183
|
+
`Variable: could not infer the GraphQL type for whole-argument '${value}' on '${argName}'${hint}; declared as 'String'. The rendered query may be invalid — verify the variable type.`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return { rendered: value };
|
|
187
|
+
}
|
|
188
|
+
// A `$`-prefixed string that is not a valid placeholder, or any other
|
|
189
|
+
// bare string: fall through to leaf handling, which emits the existing
|
|
190
|
+
// invalid-placeholder warning and renders the literal.
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
promoteVariables(session, schema, fieldSchemaPath, argName, value, warnings);
|
|
194
|
+
return { rendered: JSON.stringify(value) };
|
|
195
|
+
}
|
|
196
|
+
|
|
135
197
|
/**
|
|
136
198
|
* Collapses an array-shaped orderBy input to its first element, so
|
|
137
199
|
* callers can pass either shape without knowing Salesforce's
|
|
@@ -193,7 +193,7 @@ describe("mcp/tools/sf-gql-detail", () => {
|
|
|
193
193
|
}
|
|
194
194
|
});
|
|
195
195
|
|
|
196
|
-
it("tools/list advertises childRelationships.orderBy as a singleton object (FR-6.3)", async () => {
|
|
196
|
+
it("tools/list advertises childRelationships.orderBy as a singleton object or whole-arg string (FR-6.3)", async () => {
|
|
197
197
|
const { client, server } = await connect();
|
|
198
198
|
try {
|
|
199
199
|
const list = await client.listTools();
|
|
@@ -205,9 +205,10 @@ describe("mcp/tools/sf-gql-detail", () => {
|
|
|
205
205
|
};
|
|
206
206
|
const orderBy = child?.items?.properties?.orderBy;
|
|
207
207
|
expect(orderBy).toBeDefined();
|
|
208
|
-
//
|
|
209
|
-
expect(orderBy?.anyOf).
|
|
210
|
-
expect(orderBy?.
|
|
208
|
+
// After Task 2, orderBy accepts both singleton objects and whole-argument $var strings.
|
|
209
|
+
expect(orderBy?.anyOf).toBeDefined();
|
|
210
|
+
expect(Array.isArray(orderBy?.anyOf)).toBe(true);
|
|
211
|
+
expect(orderBy?.anyOf?.length).toBeGreaterThanOrEqual(2);
|
|
211
212
|
} finally {
|
|
212
213
|
await client.close();
|
|
213
214
|
await server.close();
|