@salesforce/webapp-template-app-react-sample-b2e-experimental 1.76.0 → 1.77.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/dist/CHANGELOG.md +16 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/Maintenance_Request__c.object-meta.xml +0 -4
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/package.json +4 -4
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectDetailService.ts +3 -26
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectInfoGraphQLService.ts +108 -165
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectInfoService.ts +9 -113
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/UiApiDetailForm.tsx +2 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useObjectInfoBatch.ts +1 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useObjectSearchData.ts +7 -228
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useRecordDetailLayout.ts +1 -20
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/filters/picklist.ts +5 -31
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/objectInfo/objectInfo.ts +46 -163
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/schema.d.ts +200 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/apiUtils.ts +3 -69
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +37 -279
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/recordUtils.ts +4 -4
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/index.ts +117 -3
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceAdapter.ts +2 -23
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Maintenance.tsx +21 -4
- package/dist/package.json +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
export type Maybe<T> = T | null;
|
|
2
|
+
export type InputMaybe<T> = Maybe<T>;
|
|
3
|
+
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
|
4
|
+
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
|
5
|
+
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
|
6
|
+
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = {
|
|
7
|
+
[_ in K]?: never;
|
|
8
|
+
};
|
|
9
|
+
export type Incremental<T> =
|
|
10
|
+
| T
|
|
11
|
+
| { [P in keyof T]?: P extends " $fragmentName" | "__typename" ? T[P] : never };
|
|
12
|
+
/** All built-in and custom scalars, mapped to their actual values */
|
|
13
|
+
export type Scalars = {
|
|
14
|
+
ID: { input: string; output: string };
|
|
15
|
+
String: { input: string; output: string };
|
|
16
|
+
Boolean: { input: boolean; output: boolean };
|
|
17
|
+
Int: { input: number; output: number };
|
|
18
|
+
Float: { input: number; output: number };
|
|
19
|
+
Base64: { input: string; output: string };
|
|
20
|
+
/** An arbitrary precision signed decimal */
|
|
21
|
+
BigDecimal: { input: number | string; output: number };
|
|
22
|
+
/** An arbitrary precision signed integer */
|
|
23
|
+
BigInteger: { input: number; output: number };
|
|
24
|
+
/** An 8-bit signed integer */
|
|
25
|
+
Byte: { input: number; output: number };
|
|
26
|
+
/** A UTF-16 code unit; a character on Unicode's BMP */
|
|
27
|
+
Char: { input: number; output: number };
|
|
28
|
+
Currency: { input: number | string; output: number };
|
|
29
|
+
Date: { input: string; output: string };
|
|
30
|
+
DateTime: { input: string; output: string };
|
|
31
|
+
Double: { input: number | string; output: number };
|
|
32
|
+
Email: { input: string; output: string };
|
|
33
|
+
EncryptedString: { input: string; output: string };
|
|
34
|
+
/** Can be set to an ID or a Reference to the result of another mutation operation. */
|
|
35
|
+
IdOrRef: { input: string; output: string };
|
|
36
|
+
JSON: { input: string; output: string };
|
|
37
|
+
Latitude: { input: number | string; output: number };
|
|
38
|
+
/** A 64-bit signed integer */
|
|
39
|
+
Long: { input: number; output: number };
|
|
40
|
+
LongTextArea: { input: string; output: string };
|
|
41
|
+
Longitude: { input: number | string; output: number };
|
|
42
|
+
MultiPicklist: { input: string; output: string };
|
|
43
|
+
Percent: { input: number | string; output: number };
|
|
44
|
+
PhoneNumber: { input: string; output: string };
|
|
45
|
+
Picklist: { input: string; output: string };
|
|
46
|
+
RichTextArea: { input: string; output: string };
|
|
47
|
+
/** A 16-bit signed integer */
|
|
48
|
+
Short: { input: number; output: number };
|
|
49
|
+
TextArea: { input: string; output: string };
|
|
50
|
+
Time: { input: string; output: string };
|
|
51
|
+
Url: { input: string; output: string };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export enum DataType {
|
|
55
|
+
Address = "ADDRESS",
|
|
56
|
+
Anytype = "ANYTYPE",
|
|
57
|
+
Base64 = "BASE64",
|
|
58
|
+
Boolean = "BOOLEAN",
|
|
59
|
+
Combobox = "COMBOBOX",
|
|
60
|
+
Complexvalue = "COMPLEXVALUE",
|
|
61
|
+
Currency = "CURRENCY",
|
|
62
|
+
Date = "DATE",
|
|
63
|
+
Datetime = "DATETIME",
|
|
64
|
+
Double = "DOUBLE",
|
|
65
|
+
Email = "EMAIL",
|
|
66
|
+
Encryptedstring = "ENCRYPTEDSTRING",
|
|
67
|
+
Int = "INT",
|
|
68
|
+
Json = "JSON",
|
|
69
|
+
Junctionidlist = "JUNCTIONIDLIST",
|
|
70
|
+
Location = "LOCATION",
|
|
71
|
+
Long = "LONG",
|
|
72
|
+
Multipicklist = "MULTIPICKLIST",
|
|
73
|
+
Percent = "PERCENT",
|
|
74
|
+
Phone = "PHONE",
|
|
75
|
+
Picklist = "PICKLIST",
|
|
76
|
+
Reference = "REFERENCE",
|
|
77
|
+
String = "STRING",
|
|
78
|
+
Textarea = "TEXTAREA",
|
|
79
|
+
Time = "TIME",
|
|
80
|
+
Url = "URL",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export enum FieldExtraTypeInfo {
|
|
84
|
+
ExternalLookup = "EXTERNAL_LOOKUP",
|
|
85
|
+
ImageUrl = "IMAGE_URL",
|
|
86
|
+
IndirectLookup = "INDIRECT_LOOKUP",
|
|
87
|
+
Personname = "PERSONNAME",
|
|
88
|
+
Plaintextarea = "PLAINTEXTAREA",
|
|
89
|
+
Richtextarea = "RICHTEXTAREA",
|
|
90
|
+
SwitchablePersonname = "SWITCHABLE_PERSONNAME",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Input for ObjectInfo and PickValues */
|
|
94
|
+
export type ObjectInfoInput = {
|
|
95
|
+
apiName: Scalars["String"]["input"];
|
|
96
|
+
fieldNames?: InputMaybe<Array<Scalars["String"]["input"]>>;
|
|
97
|
+
recordTypeIDs?: InputMaybe<Array<Scalars["ID"]["input"]>>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export enum ResultOrder {
|
|
101
|
+
Asc = "ASC",
|
|
102
|
+
Desc = "DESC",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type GetObjectInfosQueryVariables = Exact<{
|
|
106
|
+
apiNames: Array<Scalars["String"]["input"]> | Scalars["String"]["input"];
|
|
107
|
+
}>;
|
|
108
|
+
|
|
109
|
+
export type GetObjectInfosQuery = {
|
|
110
|
+
uiapi: {
|
|
111
|
+
objectInfos?: Array<{
|
|
112
|
+
ApiName: string;
|
|
113
|
+
label?: string | null;
|
|
114
|
+
labelPlural?: string | null;
|
|
115
|
+
nameFields: Array<string | null>;
|
|
116
|
+
defaultRecordTypeId?: string | null;
|
|
117
|
+
keyPrefix?: string | null;
|
|
118
|
+
layoutable: boolean;
|
|
119
|
+
queryable: boolean;
|
|
120
|
+
searchable: boolean;
|
|
121
|
+
updateable: boolean;
|
|
122
|
+
deletable: boolean;
|
|
123
|
+
createable: boolean;
|
|
124
|
+
custom: boolean;
|
|
125
|
+
mruEnabled: boolean;
|
|
126
|
+
feedEnabled: boolean;
|
|
127
|
+
fields: Array<
|
|
128
|
+
| {
|
|
129
|
+
ApiName: string;
|
|
130
|
+
label?: string | null;
|
|
131
|
+
dataType?: DataType | null;
|
|
132
|
+
relationshipName?: string | null;
|
|
133
|
+
reference: boolean;
|
|
134
|
+
compound: boolean;
|
|
135
|
+
compoundFieldName?: string | null;
|
|
136
|
+
compoundComponentName?: string | null;
|
|
137
|
+
controllingFields: Array<string | null>;
|
|
138
|
+
controllerName?: string | null;
|
|
139
|
+
referenceToInfos: Array<{ ApiName: string; nameFields: Array<string | null> } | null>;
|
|
140
|
+
}
|
|
141
|
+
| {
|
|
142
|
+
ApiName: string;
|
|
143
|
+
label?: string | null;
|
|
144
|
+
dataType?: DataType | null;
|
|
145
|
+
relationshipName?: string | null;
|
|
146
|
+
reference: boolean;
|
|
147
|
+
compound: boolean;
|
|
148
|
+
compoundFieldName?: string | null;
|
|
149
|
+
compoundComponentName?: string | null;
|
|
150
|
+
controllingFields: Array<string | null>;
|
|
151
|
+
controllerName?: string | null;
|
|
152
|
+
referenceToInfos: Array<{ ApiName: string; nameFields: Array<string | null> } | null>;
|
|
153
|
+
}
|
|
154
|
+
| null
|
|
155
|
+
>;
|
|
156
|
+
recordTypeInfos: Array<{
|
|
157
|
+
recordTypeId?: string | null;
|
|
158
|
+
name?: string | null;
|
|
159
|
+
master: boolean;
|
|
160
|
+
available: boolean;
|
|
161
|
+
defaultRecordTypeMapping: boolean;
|
|
162
|
+
} | null>;
|
|
163
|
+
themeInfo?: { color?: string | null; iconUrl?: string | null } | null;
|
|
164
|
+
childRelationships: Array<{
|
|
165
|
+
relationshipName?: string | null;
|
|
166
|
+
fieldName?: string | null;
|
|
167
|
+
childObjectApiName: string;
|
|
168
|
+
} | null>;
|
|
169
|
+
dependentFields: Array<{ controllingField: string } | null>;
|
|
170
|
+
} | null> | null;
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export type GetPicklistValuesQueryVariables = Exact<{
|
|
175
|
+
objectInfoInputs: Array<ObjectInfoInput> | ObjectInfoInput;
|
|
176
|
+
}>;
|
|
177
|
+
|
|
178
|
+
export type GetPicklistValuesQuery = {
|
|
179
|
+
uiapi: {
|
|
180
|
+
objectInfos?: Array<{
|
|
181
|
+
ApiName: string;
|
|
182
|
+
fields: Array<
|
|
183
|
+
| {
|
|
184
|
+
ApiName: string;
|
|
185
|
+
picklistValuesByRecordTypeIDs?: Array<{
|
|
186
|
+
recordTypeID: string;
|
|
187
|
+
defaultValue?: { value?: string | null } | null;
|
|
188
|
+
picklistValues?: Array<{
|
|
189
|
+
label?: string | null;
|
|
190
|
+
value?: string | null;
|
|
191
|
+
validFor?: Array<number | null> | null;
|
|
192
|
+
}> | null;
|
|
193
|
+
} | null> | null;
|
|
194
|
+
}
|
|
195
|
+
| { ApiName: string }
|
|
196
|
+
| null
|
|
197
|
+
>;
|
|
198
|
+
} | null> | null;
|
|
199
|
+
};
|
|
200
|
+
};
|
|
@@ -2,77 +2,31 @@
|
|
|
2
2
|
* API Utilities
|
|
3
3
|
*
|
|
4
4
|
* Generic utility functions for API requests, validation, and URL handling.
|
|
5
|
-
* These utilities are framework-agnostic and can be reused across different API services.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import type { ZodSchema } from "zod";
|
|
9
8
|
|
|
10
|
-
/**
|
|
11
|
-
* Options for fetchAndValidate utility function
|
|
12
|
-
*/
|
|
13
9
|
export interface FetchAndValidateOptions<T> {
|
|
14
|
-
/** Zod schema for validation */
|
|
15
10
|
schema: ZodSchema<T>;
|
|
16
|
-
/** Error context for better error messages (e.g., "object info batch", "list info") */
|
|
17
11
|
errorContext: string;
|
|
18
|
-
/** Optional function to extract/transform data from response before validation */
|
|
19
12
|
extractData?: (data: unknown) => unknown;
|
|
20
|
-
/** Optional AbortSignal to cancel the request */
|
|
21
|
-
signal?: AbortSignal;
|
|
22
13
|
}
|
|
23
14
|
|
|
24
|
-
/**
|
|
25
|
-
* Generic utility function to fetch, parse, and validate API responses.
|
|
26
|
-
* Handles common patterns: fetch -> check status -> parse JSON -> validate Zod -> handle errors
|
|
27
|
-
*
|
|
28
|
-
* @param fetchFn - Function that returns a Promise<Response> (e.g., uiApiClient.get or uiApiClient.post)
|
|
29
|
-
* @param options - Configuration options including schema, error context, optional data extraction, and AbortSignal
|
|
30
|
-
* @returns Promise resolving to validated data of type T
|
|
31
|
-
*
|
|
32
|
-
* @remarks
|
|
33
|
-
* - Handles abort signals properly to prevent race conditions
|
|
34
|
-
* - Provides detailed error messages with context
|
|
35
|
-
* - Validates responses using Zod schemas for type safety
|
|
36
|
-
* - Supports data extraction/transformation before validation
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```tsx
|
|
40
|
-
* const data = await fetchAndValidate(
|
|
41
|
-
* (signal) => apiClient.get('/endpoint', { signal }),
|
|
42
|
-
* {
|
|
43
|
-
* schema: MySchema,
|
|
44
|
-
* errorContext: 'user data',
|
|
45
|
-
* extractData: (data) => data.items,
|
|
46
|
-
* signal: abortController.signal
|
|
47
|
-
* }
|
|
48
|
-
* );
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
15
|
export async function fetchAndValidate<T>(
|
|
52
|
-
fetchFn: (
|
|
16
|
+
fetchFn: () => Promise<Response>,
|
|
53
17
|
options: FetchAndValidateOptions<T>,
|
|
54
18
|
): Promise<T> {
|
|
55
|
-
const { schema, errorContext, extractData
|
|
19
|
+
const { schema, errorContext, extractData } = options;
|
|
56
20
|
|
|
57
21
|
try {
|
|
58
|
-
const response = await fetchFn(
|
|
59
|
-
|
|
60
|
-
if (signal?.aborted) {
|
|
61
|
-
throw new DOMException("The operation was aborted.", "AbortError");
|
|
62
|
-
}
|
|
22
|
+
const response = await fetchFn();
|
|
63
23
|
|
|
64
24
|
if (!response.ok) {
|
|
65
25
|
throw new Error(`Failed to fetch ${errorContext}: ${response.status} ${response.statusText}`);
|
|
66
26
|
}
|
|
67
27
|
|
|
68
28
|
const data = await response.json();
|
|
69
|
-
|
|
70
|
-
if (signal?.aborted) {
|
|
71
|
-
throw new DOMException("The operation was aborted.", "AbortError");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
29
|
const dataToValidate = extractData ? extractData(data) : data;
|
|
75
|
-
|
|
76
30
|
const validationResult = schema.safeParse(dataToValidate);
|
|
77
31
|
|
|
78
32
|
if (!validationResult.success) {
|
|
@@ -81,14 +35,6 @@ export async function fetchAndValidate<T>(
|
|
|
81
35
|
|
|
82
36
|
return validationResult.data;
|
|
83
37
|
} catch (error) {
|
|
84
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
38
|
if (error instanceof Error && error.name === "ZodError") {
|
|
93
39
|
throw new Error(`Invalid ${errorContext} response format: ${error.message}`);
|
|
94
40
|
}
|
|
@@ -108,18 +54,6 @@ export async function fetchAndValidate<T>(
|
|
|
108
54
|
}
|
|
109
55
|
}
|
|
110
56
|
|
|
111
|
-
/**
|
|
112
|
-
* Helper to safely encode path components for URLs
|
|
113
|
-
* Wraps encodeURIComponent for better semantic meaning
|
|
114
|
-
*
|
|
115
|
-
* @param segment - The path segment to encode
|
|
116
|
-
* @returns URL-encoded path segment
|
|
117
|
-
*
|
|
118
|
-
* @example
|
|
119
|
-
* ```tsx
|
|
120
|
-
* const safePath = safeEncodePath('Account Name'); // 'Account%20Name'
|
|
121
|
-
* ```
|
|
122
|
-
*/
|
|
123
57
|
export function safeEncodePath(segment: string): string {
|
|
124
58
|
return encodeURIComponent(segment);
|
|
125
59
|
}
|
|
@@ -10,259 +10,36 @@
|
|
|
10
10
|
|
|
11
11
|
import type {
|
|
12
12
|
ObjectInfoBatchResponse,
|
|
13
|
+
GetObjectInfosQueryObjectInfos,
|
|
14
|
+
GetPicklistValuesQueryObjectInfo,
|
|
15
|
+
GetPicklistValuesQueryField,
|
|
16
|
+
GetObjectInfosQueryObjectInfo,
|
|
13
17
|
ObjectInfoResult,
|
|
14
|
-
|
|
15
|
-
RecordTypeInfo,
|
|
16
|
-
ThemeInfo,
|
|
18
|
+
PicklistValue as GraphQLPicklistValue,
|
|
17
19
|
} from "../types/objectInfo/objectInfo";
|
|
18
20
|
import type { PicklistValue } from "../types/filters/picklist";
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function getNum(node: GraphQLNode, key: string): number {
|
|
28
|
-
const v = node[key] ?? node[key.charAt(0).toLowerCase() + key.slice(1)];
|
|
29
|
-
return typeof v === "number" ? v : 0;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function getBool(node: GraphQLNode, key: string): boolean {
|
|
33
|
-
const v = node[key] ?? node[key.charAt(0).toLowerCase() + key.slice(1)];
|
|
34
|
-
return typeof v === "boolean" ? v : false;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getArr<T>(node: GraphQLNode, key: string, map: (item: unknown) => T): T[] {
|
|
38
|
-
const v = node[key] ?? node[key.charAt(0).toLowerCase() + key.slice(1)];
|
|
39
|
-
if (!Array.isArray(v)) return [];
|
|
40
|
-
return v.map((item) => map(item));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function getObj(node: GraphQLNode, key: string): Record<string, unknown> {
|
|
44
|
-
const v = node[key] ?? node[key.charAt(0).toLowerCase() + key.slice(1)];
|
|
45
|
-
return v != null && typeof v === "object" && !Array.isArray(v)
|
|
46
|
-
? (v as Record<string, unknown>)
|
|
47
|
-
: {};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** GraphQL returns dependentFields as [DependentField]; REST expects Record. Normalize to object. */
|
|
51
|
-
function toDependentFieldsRecord(v: unknown): Record<string, unknown> {
|
|
52
|
-
if (v == null) return {};
|
|
53
|
-
if (Array.isArray(v)) return {};
|
|
54
|
-
return typeof v === "object" ? (v as Record<string, unknown>) : {};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Canonical dataType casing for formatters (Phone, FormattedAddress) and reference resolution (Reference). */
|
|
58
|
-
const DATA_TYPE_CANONICAL: Record<string, string> = {
|
|
59
|
-
phone: "Phone",
|
|
60
|
-
email: "Email",
|
|
61
|
-
url: "Url",
|
|
62
|
-
address: "Address",
|
|
63
|
-
reference: "Reference",
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
function normalizeDataType(raw: string): string {
|
|
67
|
-
if (!raw) return raw;
|
|
68
|
-
const lower = raw.toLowerCase();
|
|
69
|
-
return DATA_TYPE_CANONICAL[lower] ?? raw;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Normalize GraphQL Field to our Field type (REST-compatible). */
|
|
73
|
-
function mapField(fieldNode: unknown): [string, Field] {
|
|
74
|
-
const n = (fieldNode != null && typeof fieldNode === "object" ? fieldNode : {}) as GraphQLNode;
|
|
75
|
-
const apiName = getStr(n, "ApiName") || getStr(n, "apiName");
|
|
76
|
-
const label = getStr(n, "label");
|
|
77
|
-
const dataType = normalizeDataType(getStr(n, "dataType"));
|
|
78
|
-
const relationshipName = getStr(n, "relationshipName") || null;
|
|
79
|
-
const reference = getBool(n, "reference");
|
|
80
|
-
const compound = getBool(n, "compound");
|
|
81
|
-
const compoundFieldName = getStr(n, "compoundFieldName") || null;
|
|
82
|
-
const compoundComponentName = getStr(n, "compoundComponentName") || null;
|
|
83
|
-
const controllingFields = getArr(n, "controllingFields", String);
|
|
84
|
-
const controllerName = getStr(n, "controllerName") || null;
|
|
85
|
-
const referenceToInfos = getArr(n, "referenceToInfos", (item) => {
|
|
86
|
-
const r = (item != null && typeof item === "object" ? item : {}) as GraphQLNode;
|
|
87
|
-
return {
|
|
88
|
-
apiName: getStr(r, "apiName") || getStr(r, "ApiName"),
|
|
89
|
-
nameFields: getArr(r, "nameFields", String),
|
|
90
|
-
};
|
|
91
|
-
});
|
|
92
|
-
return [
|
|
93
|
-
apiName,
|
|
94
|
-
{
|
|
95
|
-
apiName,
|
|
96
|
-
label,
|
|
97
|
-
dataType,
|
|
98
|
-
relationshipName,
|
|
99
|
-
reference,
|
|
100
|
-
compound,
|
|
101
|
-
compoundFieldName,
|
|
102
|
-
compoundComponentName,
|
|
103
|
-
controllingFields,
|
|
104
|
-
controllerName,
|
|
105
|
-
referenceToInfos,
|
|
106
|
-
calculated: getBool(n, "calculated"),
|
|
107
|
-
createable: getBool(n, "createable"),
|
|
108
|
-
custom: getBool(n, "custom"),
|
|
109
|
-
defaultValue: n.defaultValue ?? null,
|
|
110
|
-
defaultedOnCreate: getBool(n, "defaultedOnCreate"),
|
|
111
|
-
digits: getNum(n, "digits"),
|
|
112
|
-
externalId: getBool(n, "externalId"),
|
|
113
|
-
extraTypeInfo: getStr(n, "extraTypeInfo") || null,
|
|
114
|
-
filterable: getBool(n, "filterable"),
|
|
115
|
-
filteredLookupInfo: null,
|
|
116
|
-
highScaleNumber: getBool(n, "highScaleNumber"),
|
|
117
|
-
htmlFormatted: getBool(n, "htmlFormatted"),
|
|
118
|
-
inlineHelpText: getStr(n, "inlineHelpText") || null,
|
|
119
|
-
length: getNum(n, "length"),
|
|
120
|
-
maskType: getStr(n, "maskType") || null,
|
|
121
|
-
nameField: getBool(n, "nameField"),
|
|
122
|
-
polymorphicForeignKey: getBool(n, "polymorphicForeignKey"),
|
|
123
|
-
precision: getNum(n, "precision"),
|
|
124
|
-
required: getBool(n, "required"),
|
|
125
|
-
scale: getNum(n, "scale"),
|
|
126
|
-
searchPrefilterable: getBool(n, "searchPrefilterable"),
|
|
127
|
-
sortable: getBool(n, "sortable"),
|
|
128
|
-
unique: getBool(n, "unique"),
|
|
129
|
-
updateable: getBool(n, "updateable"),
|
|
130
|
-
referenceTargetField: getStr(n, "referenceTargetField") || null,
|
|
131
|
-
} as Field,
|
|
132
|
-
];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** Normalize GraphQL recordTypeInfo to RecordTypeInfo. */
|
|
136
|
-
function mapRecordTypeInfo(rtNode: unknown): [string, RecordTypeInfo] {
|
|
137
|
-
const n = (rtNode != null && typeof rtNode === "object" ? rtNode : {}) as GraphQLNode;
|
|
138
|
-
const recordTypeId = getStr(n, "recordTypeId");
|
|
139
|
-
return [
|
|
140
|
-
recordTypeId,
|
|
141
|
-
{
|
|
142
|
-
recordTypeId,
|
|
143
|
-
name: getStr(n, "name"),
|
|
144
|
-
master: getBool(n, "master"),
|
|
145
|
-
available: getBool(n, "available"),
|
|
146
|
-
defaultRecordTypeMapping: getBool(n, "defaultRecordTypeMapping"),
|
|
147
|
-
} as RecordTypeInfo,
|
|
148
|
-
];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function toFieldsMap(fieldsNode: unknown): Record<string, Field> {
|
|
152
|
-
if (fieldsNode == null) return {};
|
|
153
|
-
if (Array.isArray(fieldsNode)) {
|
|
154
|
-
return Object.fromEntries((fieldsNode as unknown[]).map(mapField).filter(([k]) => k));
|
|
155
|
-
}
|
|
156
|
-
if (typeof fieldsNode === "object" && !Array.isArray(fieldsNode)) {
|
|
157
|
-
const entries = Object.entries(fieldsNode as Record<string, unknown>).map(([, v]) =>
|
|
158
|
-
mapField(v),
|
|
159
|
-
);
|
|
160
|
-
return Object.fromEntries(entries.filter(([k]) => k));
|
|
161
|
-
}
|
|
162
|
-
return {};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Ensures compound parent fields (e.g. BillingAddress, ShippingAddress) exist in the fields map
|
|
167
|
-
* with dataType "Address" so layout transform can set item.dataType for FormattedAddress.
|
|
168
|
-
* GraphQL may only return component fields (BillingStreet, etc.) with compoundFieldName.
|
|
169
|
-
*/
|
|
170
|
-
function ensureCompoundParentFields(fields: Record<string, Field>): Record<string, Field> {
|
|
171
|
-
const result = { ...fields };
|
|
172
|
-
for (const field of Object.values(fields)) {
|
|
173
|
-
const compoundName = field.compoundFieldName;
|
|
174
|
-
if (!compoundName || result[compoundName] != null) continue;
|
|
175
|
-
const dataType =
|
|
176
|
-
compoundName.endsWith("Address") || compoundName.endsWith("address") ? "Address" : "String";
|
|
177
|
-
result[compoundName] = {
|
|
178
|
-
...field,
|
|
179
|
-
apiName: compoundName,
|
|
180
|
-
label: compoundName,
|
|
181
|
-
dataType,
|
|
182
|
-
compound: true,
|
|
183
|
-
compoundFieldName: null,
|
|
184
|
-
compoundComponentName: null,
|
|
185
|
-
} as Field;
|
|
186
|
-
}
|
|
187
|
-
return result;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function toRecordTypeInfosMap(rtNode: unknown): Record<string, RecordTypeInfo> {
|
|
191
|
-
if (rtNode == null) return {};
|
|
192
|
-
if (Array.isArray(rtNode)) {
|
|
193
|
-
return Object.fromEntries((rtNode as unknown[]).map(mapRecordTypeInfo).filter(([k]) => k));
|
|
194
|
-
}
|
|
195
|
-
if (typeof rtNode === "object" && !Array.isArray(rtNode)) {
|
|
196
|
-
const entries = Object.entries(rtNode as Record<string, unknown>).map(([, v]) =>
|
|
197
|
-
mapRecordTypeInfo(v),
|
|
198
|
-
);
|
|
199
|
-
return Object.fromEntries(entries.filter(([k]) => k));
|
|
200
|
-
}
|
|
201
|
-
return {};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** Map a single GraphQL ObjectInfo node to ObjectInfoResult (REST-compatible). */
|
|
205
|
-
export function graphQLObjectInfoToObjectInfoResult(node: GraphQLNode): ObjectInfoResult {
|
|
206
|
-
const themeInfoNode = getObj(node, "themeInfo");
|
|
207
|
-
const themeInfo: ThemeInfo = {
|
|
208
|
-
color: getStr(themeInfoNode, "color"),
|
|
209
|
-
iconUrl: getStr(themeInfoNode, "iconUrl"),
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const childRelationships = getArr(node, "childRelationships", (item) => {
|
|
213
|
-
const c = (item != null && typeof item === "object" ? item : {}) as GraphQLNode;
|
|
214
|
-
return {
|
|
215
|
-
childObjectApiName: getStr(c, "childObjectApiName"),
|
|
216
|
-
fieldName: getStr(c, "fieldName"),
|
|
217
|
-
junctionIdListNames: getArr(c, "junctionIdListNames", String),
|
|
218
|
-
junctionReferenceTo: getArr(c, "junctionReferenceTo", String),
|
|
219
|
-
relationshipName: getStr(c, "relationshipName"),
|
|
220
|
-
};
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
const apiName = getStr(node, "ApiName") || getStr(node, "apiName");
|
|
224
|
-
const fieldsNode = node.fields ?? node.Fields;
|
|
225
|
-
const recordTypeInfosNode = node.recordTypeInfos ?? node.RecordTypeInfos;
|
|
226
|
-
|
|
22
|
+
export function graphQLObjectInfoToObjectInfoResult(
|
|
23
|
+
objectInfo: GetObjectInfosQueryObjectInfo,
|
|
24
|
+
): ObjectInfoResult {
|
|
25
|
+
const { fields, ...rest } = objectInfo;
|
|
26
|
+
const fieldsRecord = Object.fromEntries((fields ?? []).map((field) => [field?.ApiName, field]));
|
|
227
27
|
return {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
nameFields: getArr(node, "nameFields", String),
|
|
232
|
-
defaultRecordTypeId: getStr(node, "defaultRecordTypeId") || "012000000000000AAA",
|
|
233
|
-
keyPrefix: getStr(node, "keyPrefix"),
|
|
234
|
-
layoutable: getBool(node, "layoutable"),
|
|
235
|
-
queryable: getBool(node, "queryable"),
|
|
236
|
-
searchable: getBool(node, "searchable"),
|
|
237
|
-
updateable: getBool(node, "updateable"),
|
|
238
|
-
deletable: getBool(node, "deletable"),
|
|
239
|
-
createable: getBool(node, "createable"),
|
|
240
|
-
custom: getBool(node, "custom"),
|
|
241
|
-
mruEnabled: getBool(node, "mruEnabled"),
|
|
242
|
-
feedEnabled: getBool(node, "feedEnabled"),
|
|
243
|
-
fields: ensureCompoundParentFields(toFieldsMap(fieldsNode)),
|
|
244
|
-
recordTypeInfos: toRecordTypeInfosMap(recordTypeInfosNode),
|
|
245
|
-
themeInfo,
|
|
246
|
-
childRelationships,
|
|
247
|
-
associateEntityType: getStr(node, "associateEntityType") || null,
|
|
248
|
-
associateParentEntity: getStr(node, "associateParentEntity") || null,
|
|
249
|
-
compactLayoutable: getBool(node, "compactLayoutable"),
|
|
250
|
-
searchLayoutable: getBool(node, "searchLayoutable"),
|
|
251
|
-
dependentFields: toDependentFieldsRecord(node.dependentFields ?? node.DependentFields),
|
|
252
|
-
eTag: getStr(node, "eTag"),
|
|
253
|
-
} as ObjectInfoResult;
|
|
28
|
+
...rest,
|
|
29
|
+
fields: fieldsRecord,
|
|
30
|
+
};
|
|
254
31
|
}
|
|
255
32
|
|
|
256
33
|
/** Convert GraphQL objectInfos array to ObjectInfoBatchResponse (REST shape). */
|
|
257
34
|
export function graphQLObjectInfosToBatchResponse(
|
|
258
|
-
|
|
35
|
+
objectInfos: GetObjectInfosQueryObjectInfos,
|
|
259
36
|
_requestedApiNames: string[],
|
|
260
37
|
): ObjectInfoBatchResponse {
|
|
261
|
-
const results =
|
|
262
|
-
result: graphQLObjectInfoToObjectInfoResult(
|
|
38
|
+
const results = objectInfos.map((objectInfo) => ({
|
|
39
|
+
result: graphQLObjectInfoToObjectInfoResult(objectInfo!),
|
|
263
40
|
statusCode: 200,
|
|
264
41
|
}));
|
|
265
|
-
return { results }
|
|
42
|
+
return { results };
|
|
266
43
|
}
|
|
267
44
|
|
|
268
45
|
/**
|
|
@@ -270,50 +47,31 @@ export function graphQLObjectInfosToBatchResponse(
|
|
|
270
47
|
* Uses picklistValuesByRecordTypeIDs; prefers the given recordTypeId or first available.
|
|
271
48
|
*/
|
|
272
49
|
export function extractPicklistValuesFromGraphQLObjectInfo(
|
|
273
|
-
|
|
50
|
+
objectInfo: NonNullable<GetPicklistValuesQueryObjectInfo>,
|
|
274
51
|
fieldName: string,
|
|
275
52
|
recordTypeId?: string,
|
|
276
53
|
): PicklistValue[] {
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
const fieldMap =
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
return [k, n];
|
|
285
|
-
}),
|
|
286
|
-
)
|
|
287
|
-
: (fieldsNode as Record<string, GraphQLNode>);
|
|
54
|
+
const fields = objectInfo.fields;
|
|
55
|
+
if (fields == null) return [];
|
|
56
|
+
const fieldMap: Record<string, GetPicklistValuesQueryField> = Object.fromEntries(
|
|
57
|
+
fields.map((field) => {
|
|
58
|
+
return [field?.ApiName, field];
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
288
61
|
const field = fieldMap[fieldName];
|
|
289
|
-
if (!field
|
|
290
|
-
const picklistData =
|
|
291
|
-
|
|
62
|
+
if (!field) return [];
|
|
63
|
+
const picklistData =
|
|
64
|
+
"picklistValuesByRecordTypeIDs" in field ? field.picklistValuesByRecordTypeIDs : undefined;
|
|
65
|
+
if (!picklistData) return [];
|
|
292
66
|
const rtId = recordTypeId ?? "012000000000000AAA";
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
if (!byRecordType) {
|
|
296
|
-
const first = arr[0];
|
|
297
|
-
if (!first) return [];
|
|
298
|
-
return mapPicklistValues(first);
|
|
299
|
-
}
|
|
300
|
-
return mapPicklistValues(byRecordType);
|
|
67
|
+
const byRecordType = picklistData.find((p) => p!.recordTypeID === rtId) ?? picklistData[0];
|
|
68
|
+
return mapPicklistValues(byRecordType?.picklistValues ?? []);
|
|
301
69
|
}
|
|
302
70
|
|
|
303
|
-
function mapPicklistValues(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
label: typeof n.label === "string" ? n.label : "",
|
|
310
|
-
value: typeof n.value === "string" ? n.value : "",
|
|
311
|
-
validFor: Array.isArray(n.validFor) ? n.validFor : undefined,
|
|
312
|
-
attributes: (n.attributes != null && typeof n.attributes === "object"
|
|
313
|
-
? n.attributes
|
|
314
|
-
: undefined) as Record<string, unknown> | undefined,
|
|
315
|
-
defaultValue: typeof n.defaultValue === "boolean" ? n.defaultValue : undefined,
|
|
316
|
-
active: typeof n.active === "boolean" ? n.active : undefined,
|
|
317
|
-
} as PicklistValue;
|
|
318
|
-
});
|
|
71
|
+
function mapPicklistValues(values: GraphQLPicklistValue[]): PicklistValue[] {
|
|
72
|
+
return values.map((item) => ({
|
|
73
|
+
...item,
|
|
74
|
+
value: item.value ?? "",
|
|
75
|
+
label: item.label ?? "",
|
|
76
|
+
}));
|
|
319
77
|
}
|