@salesforce/webapp-template-feature-react-global-search-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.
Files changed (33) hide show
  1. package/README.md +169 -322
  2. package/dist/CHANGELOG.md +16 -0
  3. package/dist/force-app/main/default/webapplications/feature-react-global-search/package.json +3 -3
  4. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectDetailService.ts +3 -26
  5. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoGraphQLService.ts +108 -165
  6. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoService.ts +9 -113
  7. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/components/detail/UiApiDetailForm.tsx +2 -2
  8. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectInfoBatch.ts +1 -1
  9. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectSearchData.ts +7 -228
  10. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useRecordDetailLayout.ts +1 -20
  11. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/types/filters/picklist.ts +5 -31
  12. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/types/objectInfo/objectInfo.ts +46 -163
  13. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/types/schema.d.ts +200 -0
  14. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/apiUtils.ts +3 -69
  15. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +37 -279
  16. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/recordUtils.ts +4 -4
  17. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/index.ts +120 -0
  18. package/dist/package.json +1 -1
  19. package/package.json +3 -3
  20. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectDetailService.ts +3 -26
  21. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoGraphQLService.ts +108 -165
  22. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoService.ts +9 -113
  23. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/components/detail/UiApiDetailForm.tsx +2 -2
  24. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectInfoBatch.ts +1 -1
  25. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectSearchData.ts +7 -228
  26. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useRecordDetailLayout.ts +1 -20
  27. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/types/filters/picklist.ts +5 -31
  28. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/types/objectInfo/objectInfo.ts +46 -163
  29. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/types/schema.d.ts +200 -0
  30. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/apiUtils.ts +3 -69
  31. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +37 -279
  32. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/recordUtils.ts +4 -4
  33. package/src/force-app/main/default/webapplications/feature-react-global-search/src/index.ts +120 -0
@@ -49,13 +49,13 @@ const getFetchableFieldsFromLayoutItem = function (
49
49
 
50
50
  // add field: fieldType
51
51
  const fieldMetadata = metadata.fields[comp.apiName];
52
- fields[comp.apiName] = fieldMetadata ? fieldMetadata.dataType : "";
52
+ fields[comp.apiName] = fieldMetadata?.dataType ?? "";
53
53
 
54
54
  // add relatedField if one exists (Id field -> add relationship name so we request Owner.Name)
55
55
  if (comp.apiName in metadata.fields) {
56
56
  const relationshipName = fieldMetadata?.relationshipName;
57
57
  if (relationshipName) {
58
- fields[relationshipName] = fieldMetadata.dataType;
58
+ fields[relationshipName] = fieldMetadata.dataType ?? "";
59
59
 
60
60
  relationFieldMap[comp.apiName] = relationshipName;
61
61
  }
@@ -65,7 +65,7 @@ const getFetchableFieldsFromLayoutItem = function (
65
65
  const idField = findIdFieldForRelationship(metadata, comp.apiName);
66
66
  if (idField) {
67
67
  const idMeta = metadata.fields[idField];
68
- fields[idField] = idMeta ? idMeta.dataType : "";
68
+ fields[idField] = idMeta?.dataType ?? "";
69
69
  relationFieldMap[idField] = comp.apiName;
70
70
  }
71
71
  }
@@ -137,7 +137,7 @@ export const calculateFieldsToFetch = function (
137
137
  const fields = getFetchableFieldsFromLayout(metadata, layout, relationFieldMap);
138
138
  let fieldsToFetch = Object.keys(fields);
139
139
  if (shouldPrefixedWithEntityName) {
140
- fieldsToFetch = fieldsToFetch.map((field) => `${metadata.apiName}.${field}`);
140
+ fieldsToFetch = fieldsToFetch.map((field) => `${metadata.ApiName}.${field}`);
141
141
  }
142
142
  // populate field types for o11y logging
143
143
  const fieldTypes = Object.values(fields).filter((fieldType) => fieldType !== "");
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Public API for the Global Search feature package.
3
+ *
4
+ * Design goals:
5
+ * - Export **API services, hooks, types, schemas, and utilities** that customers can import from node_modules.
6
+ * - Do **not** export UI components or feature constants (customers build their own UI).
7
+ *
8
+ * Source implementation lives under `src/features/global-search/**`.
9
+ */
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // API layer
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export { objectInfoService } from "./features/global-search/api/objectInfoService";
16
+ export {
17
+ objectDetailService,
18
+ extractFieldsFromLayout,
19
+ } from "./features/global-search/api/objectDetailService";
20
+ export type { RecordDetailResult } from "./features/global-search/api/objectDetailService";
21
+
22
+ export {
23
+ getRecordsGraphQL,
24
+ getRecordByIdGraphQL,
25
+ buildGetRecordsQuery,
26
+ buildWhereFromCriteria,
27
+ buildOrderByFromSort,
28
+ } from "./features/global-search/api/recordListGraphQLService";
29
+ export type {
30
+ RecordListGraphQLResult,
31
+ RecordListGraphQLVariables,
32
+ RecordListGraphQLOptions,
33
+ GraphQLRecordNode,
34
+ } from "./features/global-search/api/recordListGraphQLService";
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Hooks
38
+ // ---------------------------------------------------------------------------
39
+
40
+ export { useObjectInfoBatch } from "./features/global-search/hooks/useObjectInfoBatch";
41
+ export {
42
+ useObjectListMetadata,
43
+ useObjectColumns,
44
+ useObjectFilters,
45
+ } from "./features/global-search/hooks/useObjectSearchData";
46
+ export { useRecordListGraphQL } from "./features/global-search/hooks/useRecordListGraphQL";
47
+ export { useRecordDetailLayout } from "./features/global-search/hooks/useRecordDetailLayout";
48
+
49
+ export type { ObjectListMetadata } from "./features/global-search/hooks/useObjectSearchData";
50
+
51
+ export type {
52
+ UseRecordListGraphQLOptions,
53
+ UseRecordListGraphQLReturn,
54
+ } from "./features/global-search/hooks/useRecordListGraphQL";
55
+
56
+ export type {
57
+ UseRecordDetailLayoutParams,
58
+ UseRecordDetailLayoutReturn,
59
+ } from "./features/global-search/hooks/useRecordDetailLayout";
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Types + Zod schemas (runtime validation)
63
+ // ---------------------------------------------------------------------------
64
+
65
+ export {
66
+ ColumnArraySchema,
67
+ SearchResultRecordArraySchema,
68
+ KeywordSearchResultSchema,
69
+ SearchResultsResponseSchema,
70
+ } from "./features/global-search/types/search/searchResults";
71
+ export type {
72
+ Column,
73
+ SearchResultRecord,
74
+ KeywordSearchResult,
75
+ SearchResultsResponse,
76
+ } from "./features/global-search/types/search/searchResults";
77
+
78
+ export {
79
+ FilterArraySchema,
80
+ FilterCriteriaArraySchema,
81
+ FILTER_OPERATORS,
82
+ } from "./features/global-search/types/filters/filters";
83
+ export type {
84
+ Filter,
85
+ FilterCriteria,
86
+ FilterOperator,
87
+ FiltersResponse,
88
+ } from "./features/global-search/types/filters/filters";
89
+
90
+ export type { PicklistValue } from "./features/global-search/types/filters/picklist";
91
+
92
+ export type {
93
+ ObjectInfoBatchResponse,
94
+ ObjectInfoResult,
95
+ } from "./features/global-search/types/objectInfo/objectInfo";
96
+
97
+ export { LayoutResponseSchema } from "./features/global-search/types/recordDetail/recordDetail";
98
+ export type { LayoutResponse } from "./features/global-search/types/recordDetail/recordDetail";
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Utilities
102
+ // ---------------------------------------------------------------------------
103
+
104
+ export { fetchAndValidate, safeEncodePath } from "./features/global-search/utils/apiUtils";
105
+ export { debounce } from "./features/global-search/utils/debounce";
106
+ export { createFiltersKey } from "./features/global-search/utils/cacheUtils";
107
+ export {
108
+ calculateFieldsToFetch,
109
+ getSafeKey,
110
+ isValidSalesforceId,
111
+ } from "./features/global-search/utils/recordUtils";
112
+ export { parseFilterValue } from "./features/global-search/utils/filterUtils";
113
+ export { sanitizeFilterValue } from "./features/global-search/utils/sanitizationUtils";
114
+ export {
115
+ getGraphQLNodeValue,
116
+ getDisplayValueForDetailFieldFromNode,
117
+ getDisplayValueForLayoutItemFromNode,
118
+ getGraphQLRecordDisplayName,
119
+ } from "./features/global-search/utils/graphQLNodeFieldUtils";
120
+ export { graphQLNodeToSearchResultRecordData } from "./features/global-search/utils/graphQLRecordAdapter";
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "1.76.0",
3
+ "version": "1.77.0",
4
4
  "description": "Base SFDX project template",
5
5
  "private": true,
6
6
  "files": [
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-feature-react-global-search-experimental",
3
- "version": "1.76.0",
3
+ "version": "1.77.0",
4
4
  "description": "Global search feature for Salesforce objects with filtering and pagination",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",
7
7
  "main": "src/force-app/main/default/webapplications/feature-react-global-search/src/index.ts",
8
- "types": "src/force-app/main/default/webapplications/feature-react-global-search/src/index.ts",
8
+ "types": "index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./src/force-app/main/default/webapplications/feature-react-global-search/src/index.ts",
11
+ "types": "./index.d.ts",
12
12
  "import": "./src/force-app/main/default/webapplications/feature-react-global-search/src/index.ts",
13
13
  "default": "./src/force-app/main/default/webapplications/feature-react-global-search/src/index.ts"
14
14
  }
@@ -36,17 +36,9 @@ export function extractFieldsFromLayout(
36
36
  return optionalFields;
37
37
  }
38
38
 
39
- /**
40
- * Fetches the Full/View layout for an object (REST). Used by detail view to render sections/rows/items.
41
- *
42
- * @param objectApiName - Object API name.
43
- * @param recordTypeId - Record type Id (default master).
44
- * @param signal - Optional abort signal.
45
- */
46
39
  export async function getLayout(
47
40
  objectApiName: string,
48
41
  recordTypeId: string = DEFAULT_RECORD_TYPE_ID,
49
- signal?: AbortSignal,
50
42
  ): Promise<LayoutResponse> {
51
43
  const params = new URLSearchParams({
52
44
  layoutType: "Full",
@@ -54,14 +46,10 @@ export async function getLayout(
54
46
  recordTypeId,
55
47
  });
56
48
  return fetchAndValidate(
57
- (abortSignal) =>
58
- uiApiClient.get(`/layout/${safeEncodePath(objectApiName)}?${params.toString()}`, {
59
- signal: abortSignal,
60
- }),
49
+ () => uiApiClient.get(`/layout/${safeEncodePath(objectApiName)}?${params.toString()}`),
61
50
  {
62
51
  schema: LayoutResponseSchema,
63
52
  errorContext: `layout for ${objectApiName}`,
64
- signal,
65
53
  },
66
54
  );
67
55
  }
@@ -86,24 +74,13 @@ function optionalFieldsToColumns(optionalFields: string[]): Column[] {
86
74
  }));
87
75
  }
88
76
 
89
- /**
90
- * Fetches everything needed for the detail page: layout (REST), object metadata (GraphQL), single record (GraphQL).
91
- * Layout drives which fields are requested; getRecordByIdGraphQL fetches that field set by Id.
92
- *
93
- * @param objectApiName - Object API name.
94
- * @param recordId - Record Id.
95
- * @param recordTypeId - Record type (default master).
96
- * @param signal - Optional abort signal.
97
- * @returns { layout, record, objectMetadata } for DetailForm / UiApiDetailForm.
98
- */
99
77
  export async function getRecordDetail(
100
78
  objectApiName: string,
101
79
  recordId: string,
102
80
  recordTypeId: string = DEFAULT_RECORD_TYPE_ID,
103
- signal?: AbortSignal,
104
81
  ): Promise<RecordDetailResult> {
105
- const layout = await getLayout(objectApiName, recordTypeId, signal);
106
- const objectMetadata = await objectInfoService.getObjectInfoBatch(objectApiName, signal);
82
+ const layout = await getLayout(objectApiName, recordTypeId);
83
+ const objectMetadata = await objectInfoService.getObjectInfoBatch(objectApiName);
107
84
  const firstResult = objectMetadata?.results?.[0]?.result;
108
85
  if (!firstResult) {
109
86
  throw new Error(`Object metadata not found for ${objectApiName}`);
@@ -7,188 +7,131 @@
7
7
  * @module api/objectInfoGraphQLService
8
8
  */
9
9
 
10
- import { getDataSDK } from "@salesforce/sdk-data";
11
-
12
- /** GraphQL objectInfoInputs for requesting picklist values (API v65.0+). Only apiName and fieldNames are sent; record type filtering is done from the response. */
13
- export interface ObjectInfoInput {
14
- apiName: string;
15
- fieldNames?: string[] | null;
16
- }
17
-
18
- /** Raw GraphQL response shape for uiapi.objectInfos (flexible for schema casing). */
19
- export interface ObjectInfosGraphQLResponse {
20
- uiapi?: {
21
- objectInfos?: Array<Record<string, unknown>>;
22
- };
23
- }
10
+ import { getDataSDK, gql } from "@salesforce/sdk-data";
11
+ import type {
12
+ GetObjectInfosQuery,
13
+ GetObjectInfosQueryVariables,
14
+ GetPicklistValuesQuery,
15
+ GetPicklistValuesQueryVariables,
16
+ ObjectInfoInput,
17
+ } from "../types/schema";
24
18
 
25
19
  /**
26
20
  * Builds objectInfos query (metadata only). Uses apiNames only — do not pass objectInfoInputs.
27
21
  */
28
- function buildObjectInfosQuery(): string {
29
- return `query GetObjectInfos($apiNames: [String!]!) {
30
- uiapi {
31
- objectInfos(apiNames: $apiNames) {
32
- ApiName
33
- label
34
- labelPlural
35
- nameFields
36
- defaultRecordTypeId
37
- keyPrefix
38
- layoutable
39
- queryable
40
- searchable
41
- updateable
42
- deletable
43
- createable
44
- custom
45
- mruEnabled
46
- feedEnabled
47
- fields {
48
- ApiName
49
- label
50
- dataType
51
- relationshipName
52
- reference
53
- compound
54
- compoundFieldName
55
- compoundComponentName
56
- controllingFields
57
- controllerName
58
- referenceToInfos {
59
- ApiName
60
- nameFields
61
- }
62
- }
63
- recordTypeInfos {
64
- recordTypeId
65
- name
66
- master
67
- available
68
- defaultRecordTypeMapping
69
- }
70
- themeInfo {
71
- color
72
- iconUrl
73
- }
74
- childRelationships {
75
- relationshipName
76
- fieldName
77
- childObjectApiName
78
- }
79
- dependentFields {
80
- controllingField
81
- }
82
- }
83
- }
84
- }`;
85
- }
22
+ const OBJECT_INFOS_QUERY = gql`
23
+ query GetObjectInfos($apiNames: [String!]!) {
24
+ uiapi {
25
+ objectInfos(apiNames: $apiNames) {
26
+ ApiName
27
+ label
28
+ labelPlural
29
+ nameFields
30
+ defaultRecordTypeId
31
+ keyPrefix
32
+ layoutable
33
+ queryable
34
+ searchable
35
+ updateable
36
+ deletable
37
+ createable
38
+ custom
39
+ mruEnabled
40
+ feedEnabled
41
+ fields {
42
+ ApiName
43
+ label
44
+ dataType
45
+ relationshipName
46
+ reference
47
+ compound
48
+ compoundFieldName
49
+ compoundComponentName
50
+ controllingFields
51
+ controllerName
52
+ referenceToInfos {
53
+ ApiName
54
+ nameFields
55
+ }
56
+ }
57
+ recordTypeInfos {
58
+ recordTypeId
59
+ name
60
+ master
61
+ available
62
+ defaultRecordTypeMapping
63
+ }
64
+ themeInfo {
65
+ color
66
+ iconUrl
67
+ }
68
+ childRelationships {
69
+ relationshipName
70
+ fieldName
71
+ childObjectApiName
72
+ }
73
+ dependentFields {
74
+ controllingField
75
+ }
76
+ }
77
+ }
78
+ }
79
+ `;
86
80
 
87
81
  /**
88
82
  * Builds objectInfos query with picklist values (API v65.0+).
89
83
  * Schema requires objectInfos to be called with either apiNames or objectInfoInputs, not both.
90
84
  * This query uses objectInfoInputs only.
85
+ * Optimized to only fetch fields used by extractPicklistValuesFromGraphQLObjectInfo.
91
86
  */
92
- function buildObjectInfosWithPicklistsQuery(): string {
93
- return `query GetPicklistValues($objectInfoInputs: [ObjectInfoInput!]!) {
94
- uiapi {
95
- objectInfos(objectInfoInputs: $objectInfoInputs) {
96
- ApiName
97
- label
98
- labelPlural
99
- nameFields
100
- defaultRecordTypeId
101
- keyPrefix
102
- layoutable
103
- queryable
104
- searchable
105
- updateable
106
- deletable
107
- createable
108
- custom
109
- mruEnabled
110
- feedEnabled
111
- fields {
112
- ApiName
113
- label
114
- dataType
115
- relationshipName
116
- reference
117
- compound
118
- compoundFieldName
119
- compoundComponentName
120
- controllingFields
121
- controllerName
122
- referenceToInfos {
123
- ApiName
124
- nameFields
125
- }
126
- ... on PicklistField {
127
- picklistValuesByRecordTypeIDs {
128
- recordTypeID
129
- defaultValue {
130
- value
131
- }
132
- picklistValues {
133
- label
134
- value
135
- validFor
136
- }
137
- }
138
- }
139
- }
140
- recordTypeInfos {
141
- recordTypeId
142
- name
143
- master
144
- available
145
- defaultRecordTypeMapping
146
- }
147
- themeInfo {
148
- color
149
- iconUrl
150
- }
151
- childRelationships {
152
- relationshipName
153
- fieldName
154
- childObjectApiName
155
- }
156
- dependentFields {
157
- controllingField
158
- }
159
- }
160
- }
161
- }`;
87
+ const PICKLIST_VALUES_QUERY = gql`
88
+ query GetPicklistValues($objectInfoInputs: [ObjectInfoInput!]!) {
89
+ uiapi {
90
+ objectInfos(objectInfoInputs: $objectInfoInputs) {
91
+ ApiName
92
+ fields {
93
+ ApiName
94
+ ... on PicklistField {
95
+ picklistValuesByRecordTypeIDs {
96
+ recordTypeID
97
+ defaultValue {
98
+ value
99
+ }
100
+ picklistValues {
101
+ label
102
+ value
103
+ validFor
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ `;
112
+
113
+ export async function queryForObjectInfos(apiNames: string[]): Promise<GetObjectInfosQuery> {
114
+ return runQuery<GetObjectInfosQuery, GetObjectInfosQueryVariables>(OBJECT_INFOS_QUERY, {
115
+ apiNames,
116
+ });
162
117
  }
163
118
 
164
- /**
165
- * Fetches object metadata for the given objects via GraphQL.
166
- *
167
- * @param apiNames - Object API names (e.g. ["Account", "Contact"]).
168
- * @param options.objectInfoInputs - When set, picklist values for specified fields are included (API v65.0+).
169
- * @param options.signal - Optional abort signal.
170
- * @returns Raw uiapi.objectInfos response (adapt to REST shape via graphQLObjectInfosToBatchResponse).
171
- */
172
- export async function getObjectInfosGraphQL(
173
- apiNames: string[],
174
- options?: {
175
- objectInfoInputs?: ObjectInfoInput[] | null;
176
- signal?: AbortSignal;
177
- },
178
- ): Promise<ObjectInfosGraphQLResponse> {
179
- const names = apiNames.length ? apiNames : [];
180
- const hasInputs = options?.objectInfoInputs != null && options.objectInfoInputs.length > 0;
181
- const query = hasInputs ? buildObjectInfosWithPicklistsQuery() : buildObjectInfosQuery();
182
- const variables: Record<string, unknown> = hasInputs
183
- ? { objectInfoInputs: options!.objectInfoInputs }
184
- : { apiNames: names };
119
+ export async function queryForPicklistValues(
120
+ objectInfoInputs: ObjectInfoInput[],
121
+ ): Promise<GetPicklistValuesQuery> {
122
+ return runQuery<GetPicklistValuesQuery, GetPicklistValuesQueryVariables>(PICKLIST_VALUES_QUERY, {
123
+ objectInfoInputs,
124
+ });
125
+ }
126
+
127
+ async function runQuery<Q, V>(query: string, variables: V): Promise<Q> {
185
128
  const data = await getDataSDK();
186
- const response = await data.graphql?.<ObjectInfosGraphQLResponse>(query, variables);
129
+ const response = await data.graphql?.<Q, V>(query, variables);
187
130
 
188
131
  if (response?.errors?.length) {
189
132
  const errorMessages = response.errors.map((e) => e.message).join("; ");
190
133
  throw new Error(`GraphQL Error: ${errorMessages}`);
191
134
  }
192
135
 
193
- return response?.data ?? ({} as ObjectInfosGraphQLResponse);
136
+ return response?.data ?? ({} as Q);
194
137
  }