@peernova/cuneiform-sf 1.0.2 → 1.0.4-beta.10
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/LICENSE +81 -30
- package/README.md +168 -134
- package/lib/adapters/connection-facade.d.ts +458 -0
- package/lib/adapters/connection-facade.js +379 -0
- package/lib/adapters/connection-facade.js.map +1 -0
- package/lib/adapters/errors.d.ts +547 -0
- package/lib/adapters/errors.js +937 -0
- package/lib/adapters/errors.js.map +1 -0
- package/lib/adapters/lifecycle.d.ts +112 -0
- package/lib/adapters/lifecycle.js +94 -0
- package/lib/adapters/lifecycle.js.map +1 -0
- package/lib/adapters/rest/cache.d.ts +69 -0
- package/lib/adapters/rest/cache.js +133 -0
- package/lib/adapters/rest/cache.js.map +1 -0
- package/lib/adapters/rest/index.d.ts +11 -0
- package/lib/adapters/rest/index.js +18 -0
- package/lib/adapters/rest/index.js.map +1 -0
- package/lib/adapters/rest/profiling-rest-client.d.ts +137 -0
- package/lib/adapters/rest/profiling-rest-client.js +115 -0
- package/lib/adapters/rest/profiling-rest-client.js.map +1 -0
- package/lib/adapters/rest/rest-api-adapter.d.ts +393 -0
- package/lib/adapters/rest/rest-api-adapter.js +764 -0
- package/lib/adapters/rest/rest-api-adapter.js.map +1 -0
- package/lib/adapters/rest/types.d.ts +34 -0
- package/lib/adapters/rest/types.js +9 -0
- package/lib/adapters/rest/types.js.map +1 -0
- package/lib/adapters/retry.d.ts +91 -0
- package/lib/adapters/retry.js +215 -0
- package/lib/adapters/retry.js.map +1 -0
- package/lib/adapters/soql/cuneiform-query-builder.d.ts +418 -0
- package/lib/adapters/soql/cuneiform-query-builder.js +606 -0
- package/lib/adapters/soql/cuneiform-query-builder.js.map +1 -0
- package/lib/adapters/soql/soql-query-adapter.d.ts +141 -0
- package/lib/adapters/soql/soql-query-adapter.js +259 -0
- package/lib/adapters/soql/soql-query-adapter.js.map +1 -0
- package/lib/adapters/soql/types.d.ts +37 -0
- package/lib/adapters/soql/types.js +19 -0
- package/lib/adapters/soql/types.js.map +1 -0
- package/lib/adapters/testing/index.d.ts +37 -0
- package/lib/adapters/testing/index.js +20 -0
- package/lib/adapters/testing/index.js.map +1 -0
- package/lib/adapters/testing/mock-connection.d.ts +77 -0
- package/lib/adapters/testing/mock-connection.js +207 -0
- package/lib/adapters/testing/mock-connection.js.map +1 -0
- package/lib/adapters/testing/mock-logger.d.ts +29 -0
- package/lib/adapters/testing/mock-logger.js +57 -0
- package/lib/adapters/testing/mock-logger.js.map +1 -0
- package/lib/adapters/testing/mock-mcp-adapters.d.ts +32 -0
- package/lib/adapters/testing/mock-mcp-adapters.js +52 -0
- package/lib/adapters/testing/mock-mcp-adapters.js.map +1 -0
- package/lib/adapters/testing/mock-oclif-config.d.ts +22 -0
- package/lib/adapters/testing/mock-oclif-config.js +90 -0
- package/lib/adapters/testing/mock-oclif-config.js.map +1 -0
- package/lib/adapters/testing/mock-rest-adapter.d.ts +26 -0
- package/lib/adapters/testing/mock-rest-adapter.js +243 -0
- package/lib/adapters/testing/mock-rest-adapter.js.map +1 -0
- package/lib/adapters/testing/mock-salesforce-connection.d.ts +40 -0
- package/lib/adapters/testing/mock-salesforce-connection.js +61 -0
- package/lib/adapters/testing/mock-salesforce-connection.js.map +1 -0
- package/lib/adapters/testing/mock-soql-adapter.d.ts +30 -0
- package/lib/adapters/testing/mock-soql-adapter.js +120 -0
- package/lib/adapters/testing/mock-soql-adapter.js.map +1 -0
- package/lib/adapters/testing/mock-tooling-adapter.d.ts +24 -0
- package/lib/adapters/testing/mock-tooling-adapter.js +163 -0
- package/lib/adapters/testing/mock-tooling-adapter.js.map +1 -0
- package/lib/adapters/testing/stub-connection.d.ts +93 -0
- package/lib/adapters/testing/stub-connection.js +97 -0
- package/lib/adapters/testing/stub-connection.js.map +1 -0
- package/lib/adapters/testing/stub-rest-adapter.d.ts +52 -0
- package/lib/adapters/testing/stub-rest-adapter.js +58 -0
- package/lib/adapters/testing/stub-rest-adapter.js.map +1 -0
- package/lib/adapters/testing/stub-soql-adapter.d.ts +56 -0
- package/lib/adapters/testing/stub-soql-adapter.js +50 -0
- package/lib/adapters/testing/stub-soql-adapter.js.map +1 -0
- package/lib/adapters/testing/types.d.ts +71 -0
- package/lib/adapters/testing/types.js +9 -0
- package/lib/adapters/testing/types.js.map +1 -0
- package/lib/adapters/tooling/index.d.ts +10 -0
- package/lib/adapters/tooling/index.js +17 -0
- package/lib/adapters/tooling/index.js.map +1 -0
- package/lib/adapters/tooling/tooling-api-adapter.d.ts +157 -0
- package/lib/adapters/tooling/tooling-api-adapter.js +339 -0
- package/lib/adapters/tooling/tooling-api-adapter.js.map +1 -0
- package/lib/adapters/tooling/types.d.ts +81 -0
- package/lib/adapters/tooling/types.js +9 -0
- package/lib/adapters/tooling/types.js.map +1 -0
- package/lib/adapters/types.d.ts +112 -0
- package/lib/adapters/types.js +169 -0
- package/lib/adapters/types.js.map +1 -0
- package/lib/base/cuneiform-command.d.ts +175 -0
- package/lib/base/cuneiform-command.js +326 -0
- package/lib/base/cuneiform-command.js.map +1 -0
- package/lib/commands/cuneiform/compatibility/check.d.ts +43 -0
- package/lib/commands/cuneiform/compatibility/check.js +114 -0
- package/lib/commands/cuneiform/compatibility/check.js.map +1 -0
- package/lib/commands/cuneiform/definition/create.d.ts +120 -0
- package/lib/commands/cuneiform/definition/create.js +737 -0
- package/lib/commands/cuneiform/definition/create.js.map +1 -0
- package/lib/commands/cuneiform/definition/export.d.ts +57 -0
- package/lib/commands/cuneiform/definition/export.js +133 -0
- package/lib/commands/cuneiform/definition/export.js.map +1 -0
- package/lib/commands/cuneiform/definition/get.d.ts +86 -0
- package/lib/commands/cuneiform/definition/get.js +277 -0
- package/lib/commands/cuneiform/definition/get.js.map +1 -0
- package/lib/commands/cuneiform/definition/import.d.ts +54 -0
- package/lib/commands/cuneiform/definition/import.js +118 -0
- package/lib/commands/cuneiform/definition/import.js.map +1 -0
- package/lib/commands/cuneiform/definition/list.d.ts +110 -0
- package/lib/commands/cuneiform/definition/list.js +351 -0
- package/lib/commands/cuneiform/definition/list.js.map +1 -0
- package/lib/commands/cuneiform/definition/purge.d.ts +109 -0
- package/lib/commands/cuneiform/definition/purge.js +578 -0
- package/lib/commands/cuneiform/definition/purge.js.map +1 -0
- package/lib/commands/cuneiform/definition/update.d.ts +58 -0
- package/lib/commands/cuneiform/definition/update.js +209 -0
- package/lib/commands/cuneiform/definition/update.js.map +1 -0
- package/lib/commands/cuneiform/mcp/serve.d.ts +56 -0
- package/lib/commands/cuneiform/mcp/serve.js +109 -0
- package/lib/commands/cuneiform/mcp/serve.js.map +1 -0
- package/lib/commands/cuneiform/object/describe.d.ts +61 -0
- package/lib/commands/cuneiform/object/describe.js +461 -0
- package/lib/commands/cuneiform/object/describe.js.map +1 -0
- package/lib/commands/cuneiform/object/list.d.ts +123 -0
- package/lib/commands/cuneiform/object/list.js +264 -0
- package/lib/commands/cuneiform/object/list.js.map +1 -0
- package/lib/commands/cuneiform/org/details.d.ts +99 -0
- package/lib/commands/cuneiform/org/details.js +521 -0
- package/lib/commands/cuneiform/org/details.js.map +1 -0
- package/lib/commands/cuneiform/org/reset.d.ts +46 -0
- package/lib/commands/cuneiform/org/reset.js +135 -0
- package/lib/commands/cuneiform/org/reset.js.map +1 -0
- package/lib/commands/cuneiform/profile/request/cancel.d.ts +59 -0
- package/lib/commands/cuneiform/profile/request/cancel.js +202 -0
- package/lib/commands/cuneiform/profile/request/cancel.js.map +1 -0
- package/lib/commands/cuneiform/profile/request/delete.d.ts +59 -0
- package/lib/commands/cuneiform/profile/request/delete.js +223 -0
- package/lib/commands/cuneiform/profile/request/delete.js.map +1 -0
- package/lib/commands/cuneiform/profile/request/list.d.ts +35 -0
- package/lib/commands/cuneiform/profile/request/list.js +102 -0
- package/lib/commands/cuneiform/profile/request/list.js.map +1 -0
- package/lib/commands/cuneiform/profile.d.ts +93 -0
- package/lib/commands/cuneiform/profile.js +353 -0
- package/lib/commands/cuneiform/profile.js.map +1 -0
- package/lib/commands/cuneiform/summary/purge.d.ts +80 -0
- package/lib/commands/cuneiform/summary/purge.js +467 -0
- package/lib/commands/cuneiform/summary/purge.js.map +1 -0
- package/lib/commands/cuneiform/summary/reprofile.d.ts +60 -0
- package/lib/commands/cuneiform/summary/reprofile.js +236 -0
- package/lib/commands/cuneiform/summary/reprofile.js.map +1 -0
- package/lib/commands/cuneiform/summary/stop.d.ts +59 -0
- package/lib/commands/cuneiform/summary/stop.js +234 -0
- package/lib/commands/cuneiform/summary/stop.js.map +1 -0
- package/lib/commands/cuneiform/user/details.d.ts +77 -0
- package/lib/commands/cuneiform/user/details.js +414 -0
- package/lib/commands/cuneiform/user/details.js.map +1 -0
- package/lib/constants/namespace-constants.d.ts +102 -0
- package/lib/constants/namespace-constants.js +225 -0
- package/lib/constants/namespace-constants.js.map +1 -0
- package/lib/debug/command-debug-proxy.d.ts +101 -0
- package/lib/debug/command-debug-proxy.js +171 -0
- package/lib/debug/command-debug-proxy.js.map +1 -0
- package/lib/debug/debug-logger.d.ts +85 -0
- package/lib/debug/debug-logger.js +133 -0
- package/lib/debug/debug-logger.js.map +1 -0
- package/lib/debug/service-debug-proxy.d.ts +30 -0
- package/lib/debug/service-debug-proxy.js +102 -0
- package/lib/debug/service-debug-proxy.js.map +1 -0
- package/lib/hooks/prerun.d.ts +25 -0
- package/lib/hooks/prerun.js +47 -0
- package/lib/hooks/prerun.js.map +1 -0
- package/lib/mcp/config/mcp-config.d.ts +55 -0
- package/lib/mcp/config/mcp-config.js +51 -0
- package/lib/mcp/config/mcp-config.js.map +1 -0
- package/lib/mcp/config/pagination.d.ts +96 -0
- package/lib/mcp/config/pagination.js +108 -0
- package/lib/mcp/config/pagination.js.map +1 -0
- package/lib/mcp/config/system-prompts.d.ts +18 -0
- package/lib/mcp/config/system-prompts.js +92 -0
- package/lib/mcp/config/system-prompts.js.map +1 -0
- package/lib/mcp/errors.d.ts +23 -0
- package/lib/mcp/errors.js +27 -0
- package/lib/mcp/errors.js.map +1 -0
- package/lib/mcp/schemas/input-schemas.d.ts +327 -0
- package/lib/mcp/schemas/input-schemas.js +310 -0
- package/lib/mcp/schemas/input-schemas.js.map +1 -0
- package/lib/mcp/server.d.ts +40 -0
- package/lib/mcp/server.js +316 -0
- package/lib/mcp/server.js.map +1 -0
- package/lib/mcp/tools/contactpoint-tools.d.ts +14 -0
- package/lib/mcp/tools/contactpoint-tools.js +34 -0
- package/lib/mcp/tools/contactpoint-tools.js.map +1 -0
- package/lib/mcp/tools/definition-io-tools.d.ts +19 -0
- package/lib/mcp/tools/definition-io-tools.js +152 -0
- package/lib/mcp/tools/definition-io-tools.js.map +1 -0
- package/lib/mcp/tools/definition-tools.d.ts +51 -0
- package/lib/mcp/tools/definition-tools.js +220 -0
- package/lib/mcp/tools/definition-tools.js.map +1 -0
- package/lib/mcp/tools/index.d.ts +37 -0
- package/lib/mcp/tools/index.js +88 -0
- package/lib/mcp/tools/index.js.map +1 -0
- package/lib/mcp/tools/object-tools.d.ts +22 -0
- package/lib/mcp/tools/object-tools.js +327 -0
- package/lib/mcp/tools/object-tools.js.map +1 -0
- package/lib/mcp/tools/org-tools.d.ts +14 -0
- package/lib/mcp/tools/org-tools.js +177 -0
- package/lib/mcp/tools/org-tools.js.map +1 -0
- package/lib/mcp/tools/profile-tools.d.ts +59 -0
- package/lib/mcp/tools/profile-tools.js +213 -0
- package/lib/mcp/tools/profile-tools.js.map +1 -0
- package/lib/mcp/tools/summary-tools.d.ts +14 -0
- package/lib/mcp/tools/summary-tools.js +38 -0
- package/lib/mcp/tools/summary-tools.js.map +1 -0
- package/lib/mcp/tools/tool-factory.d.ts +63 -0
- package/lib/mcp/tools/tool-factory.js +146 -0
- package/lib/mcp/tools/tool-factory.js.map +1 -0
- package/lib/mcp/tools/user-tools.d.ts +25 -0
- package/lib/mcp/tools/user-tools.js +167 -0
- package/lib/mcp/tools/user-tools.js.map +1 -0
- package/lib/models/cascade-skip-accumulator.d.ts +25 -0
- package/lib/models/cascade-skip-accumulator.js +9 -0
- package/lib/models/cascade-skip-accumulator.js.map +1 -0
- package/lib/models/date-literal.d.ts +280 -0
- package/lib/models/date-literal.js +1164 -0
- package/lib/models/date-literal.js.map +1 -0
- package/lib/models/object-describe-types.d.ts +173 -0
- package/lib/models/object-describe-types.js +9 -0
- package/lib/models/object-describe-types.js.map +1 -0
- package/lib/models/portability-recipe.d.ts +35 -0
- package/lib/models/portability-recipe.js +113 -0
- package/lib/models/portability-recipe.js.map +1 -0
- package/lib/models/profile-request-types.d.ts +118 -0
- package/lib/models/profile-request-types.js +23 -0
- package/lib/models/profile-request-types.js.map +1 -0
- package/lib/models/profiling-execution-types.d.ts +154 -0
- package/lib/models/profiling-execution-types.js +14 -0
- package/lib/models/profiling-execution-types.js.map +1 -0
- package/lib/models/service-result.d.ts +114 -0
- package/lib/models/service-result.js +81 -0
- package/lib/models/service-result.js.map +1 -0
- package/lib/models/sfdmu-types.d.ts +49 -0
- package/lib/models/sfdmu-types.js +23 -0
- package/lib/models/sfdmu-types.js.map +1 -0
- package/lib/models/status-types.d.ts +38 -0
- package/lib/models/status-types.js +12 -0
- package/lib/models/status-types.js.map +1 -0
- package/lib/models/summary-bulk-types.d.ts +61 -0
- package/lib/models/summary-bulk-types.js +23 -0
- package/lib/models/summary-bulk-types.js.map +1 -0
- package/lib/models/user-details-types.d.ts +188 -0
- package/lib/models/user-details-types.js +9 -0
- package/lib/models/user-details-types.js.map +1 -0
- package/lib/models/year-range.d.ts +78 -0
- package/lib/models/year-range.js +153 -0
- package/lib/models/year-range.js.map +1 -0
- package/lib/operations/CompatibilityCheckOperation.d.ts +62 -0
- package/lib/operations/CompatibilityCheckOperation.js +102 -0
- package/lib/operations/CompatibilityCheckOperation.js.map +1 -0
- package/lib/operations/DefinitionCreateOperation.d.ts +427 -0
- package/lib/operations/DefinitionCreateOperation.js +1270 -0
- package/lib/operations/DefinitionCreateOperation.js.map +1 -0
- package/lib/operations/DefinitionExportOperation.d.ts +155 -0
- package/lib/operations/DefinitionExportOperation.js +281 -0
- package/lib/operations/DefinitionExportOperation.js.map +1 -0
- package/lib/operations/DefinitionImportOperation.d.ts +144 -0
- package/lib/operations/DefinitionImportOperation.js +357 -0
- package/lib/operations/DefinitionImportOperation.js.map +1 -0
- package/lib/operations/DefinitionListOperation.d.ts +66 -0
- package/lib/operations/DefinitionListOperation.js +108 -0
- package/lib/operations/DefinitionListOperation.js.map +1 -0
- package/lib/operations/DefinitionPurgeOperation.d.ts +203 -0
- package/lib/operations/DefinitionPurgeOperation.js +465 -0
- package/lib/operations/DefinitionPurgeOperation.js.map +1 -0
- package/lib/operations/DefinitionUpdateOperation.d.ts +78 -0
- package/lib/operations/DefinitionUpdateOperation.js +142 -0
- package/lib/operations/DefinitionUpdateOperation.js.map +1 -0
- package/lib/operations/OrgDetailsOperation.d.ts +253 -0
- package/lib/operations/OrgDetailsOperation.js +456 -0
- package/lib/operations/OrgDetailsOperation.js.map +1 -0
- package/lib/operations/OrgResetOperation.d.ts +114 -0
- package/lib/operations/OrgResetOperation.js +209 -0
- package/lib/operations/OrgResetOperation.js.map +1 -0
- package/lib/operations/ProfileOperation.d.ts +192 -0
- package/lib/operations/ProfileOperation.js +371 -0
- package/lib/operations/ProfileOperation.js.map +1 -0
- package/lib/operations/ProfileRequestCancelOperation.d.ts +59 -0
- package/lib/operations/ProfileRequestCancelOperation.js +137 -0
- package/lib/operations/ProfileRequestCancelOperation.js.map +1 -0
- package/lib/operations/ProfileRequestDeleteOperation.d.ts +64 -0
- package/lib/operations/ProfileRequestDeleteOperation.js +134 -0
- package/lib/operations/ProfileRequestDeleteOperation.js.map +1 -0
- package/lib/operations/ProfileRequestListOperation.d.ts +39 -0
- package/lib/operations/ProfileRequestListOperation.js +61 -0
- package/lib/operations/ProfileRequestListOperation.js.map +1 -0
- package/lib/operations/SummaryPurgeOperation.d.ts +134 -0
- package/lib/operations/SummaryPurgeOperation.js +257 -0
- package/lib/operations/SummaryPurgeOperation.js.map +1 -0
- package/lib/operations/SummaryReprofileOperation.d.ts +88 -0
- package/lib/operations/SummaryReprofileOperation.js +174 -0
- package/lib/operations/SummaryReprofileOperation.js.map +1 -0
- package/lib/operations/SummaryStopOperation.d.ts +87 -0
- package/lib/operations/SummaryStopOperation.js +175 -0
- package/lib/operations/SummaryStopOperation.js.map +1 -0
- package/lib/services/BulkExecutionService.d.ts +120 -0
- package/lib/services/BulkExecutionService.js +535 -0
- package/lib/services/BulkExecutionService.js.map +1 -0
- package/lib/services/CompatibilityService.d.ts +81 -0
- package/lib/services/CompatibilityService.js +118 -0
- package/lib/services/CompatibilityService.js.map +1 -0
- package/lib/services/ConfigureMode.d.ts +98 -0
- package/lib/services/ConfigureMode.js +413 -0
- package/lib/services/ConfigureMode.js.map +1 -0
- package/lib/services/ContactPointService.d.ts +111 -0
- package/lib/services/ContactPointService.js +286 -0
- package/lib/services/ContactPointService.js.map +1 -0
- package/lib/services/DataAvailabilityService.d.ts +81 -0
- package/lib/services/DataAvailabilityService.js +128 -0
- package/lib/services/DataAvailabilityService.js.map +1 -0
- package/lib/services/DefinitionFieldGenerationService.d.ts +357 -0
- package/lib/services/DefinitionFieldGenerationService.js +899 -0
- package/lib/services/DefinitionFieldGenerationService.js.map +1 -0
- package/lib/services/DefinitionQueryBuilder.d.ts +92 -0
- package/lib/services/DefinitionQueryBuilder.js +328 -0
- package/lib/services/DefinitionQueryBuilder.js.map +1 -0
- package/lib/services/ObjectDescribeService.d.ts +436 -0
- package/lib/services/ObjectDescribeService.js +881 -0
- package/lib/services/ObjectDescribeService.js.map +1 -0
- package/lib/services/ObjectFilteringService.d.ts +484 -0
- package/lib/services/ObjectFilteringService.js +1080 -0
- package/lib/services/ObjectFilteringService.js.map +1 -0
- package/lib/services/ObjectListCommandService.d.ts +467 -0
- package/lib/services/ObjectListCommandService.js +904 -0
- package/lib/services/ObjectListCommandService.js.map +1 -0
- package/lib/services/ObjectListService.d.ts +201 -0
- package/lib/services/ObjectListService.js +350 -0
- package/lib/services/ObjectListService.js.map +1 -0
- package/lib/services/OrgInfoService.d.ts +493 -0
- package/lib/services/OrgInfoService.js +1142 -0
- package/lib/services/OrgInfoService.js.map +1 -0
- package/lib/services/PollingService.d.ts +105 -0
- package/lib/services/PollingService.js +117 -0
- package/lib/services/PollingService.js.map +1 -0
- package/lib/services/ProfileRequestService.d.ts +186 -0
- package/lib/services/ProfileRequestService.js +555 -0
- package/lib/services/ProfileRequestService.js.map +1 -0
- package/lib/services/ProfilingDefinitionService.d.ts +575 -0
- package/lib/services/ProfilingDefinitionService.js +1029 -0
- package/lib/services/ProfilingDefinitionService.js.map +1 -0
- package/lib/services/ProfilingExecutionService.d.ts +122 -0
- package/lib/services/ProfilingExecutionService.js +320 -0
- package/lib/services/ProfilingExecutionService.js.map +1 -0
- package/lib/services/ProfilingSummaryService.d.ts +292 -0
- package/lib/services/ProfilingSummaryService.js +688 -0
- package/lib/services/ProfilingSummaryService.js.map +1 -0
- package/lib/services/RecordTypeService.d.ts +129 -0
- package/lib/services/RecordTypeService.js +284 -0
- package/lib/services/RecordTypeService.js.map +1 -0
- package/lib/services/SFDMUService.d.ts +146 -0
- package/lib/services/SFDMUService.js +323 -0
- package/lib/services/SFDMUService.js.map +1 -0
- package/lib/services/TabDetectionService.d.ts +105 -0
- package/lib/services/TabDetectionService.js +206 -0
- package/lib/services/TabDetectionService.js.map +1 -0
- package/lib/services/UnconfigureMode.d.ts +74 -0
- package/lib/services/UnconfigureMode.js +378 -0
- package/lib/services/UnconfigureMode.js.map +1 -0
- package/lib/services/UserConfigurationService.d.ts +158 -0
- package/lib/services/UserConfigurationService.js +574 -0
- package/lib/services/UserConfigurationService.js.map +1 -0
- package/lib/services/UserConfigurationTypes.d.ts +181 -0
- package/lib/services/UserConfigurationTypes.js +14 -0
- package/lib/services/UserConfigurationTypes.js.map +1 -0
- package/lib/services/UserReadinessService.d.ts +347 -0
- package/lib/services/UserReadinessService.js +891 -0
- package/lib/services/UserReadinessService.js.map +1 -0
- package/lib/services/constants.d.ts +54 -0
- package/lib/services/constants.js +71 -0
- package/lib/services/constants.js.map +1 -0
- package/lib/services/namespace-constants.d.ts +1 -0
- package/lib/services/namespace-constants.js +11 -0
- package/lib/services/namespace-constants.js.map +1 -0
- package/lib/services/namespace-filter.d.ts +36 -0
- package/lib/services/namespace-filter.js +109 -0
- package/lib/services/namespace-filter.js.map +1 -0
- package/lib/services/validation.d.ts +47 -0
- package/lib/services/validation.js +119 -0
- package/lib/services/validation.js.map +1 -0
- package/lib/utils/batch-processor.d.ts +13 -0
- package/lib/utils/batch-processor.js +39 -0
- package/lib/utils/batch-processor.js.map +1 -0
- package/lib/utils/formatting/availability-grid.d.ts +81 -0
- package/lib/utils/formatting/availability-grid.js +94 -0
- package/lib/utils/formatting/availability-grid.js.map +1 -0
- package/lib/utils/formatting/business-process-grid.d.ts +51 -0
- package/lib/utils/formatting/business-process-grid.js +58 -0
- package/lib/utils/formatting/business-process-grid.js.map +1 -0
- package/lib/utils/formatting/command-display.d.ts +154 -0
- package/lib/utils/formatting/command-display.js +154 -0
- package/lib/utils/formatting/command-display.js.map +1 -0
- package/lib/utils/formatting/definition-create-display.d.ts +118 -0
- package/lib/utils/formatting/definition-create-display.js +230 -0
- package/lib/utils/formatting/definition-create-display.js.map +1 -0
- package/lib/utils/formatting/empty-states.d.ts +35 -0
- package/lib/utils/formatting/empty-states.js +70 -0
- package/lib/utils/formatting/empty-states.js.map +1 -0
- package/lib/utils/formatting/errors.d.ts +33 -0
- package/lib/utils/formatting/errors.js +72 -0
- package/lib/utils/formatting/errors.js.map +1 -0
- package/lib/utils/formatting/field-types.d.ts +32 -0
- package/lib/utils/formatting/field-types.js +88 -0
- package/lib/utils/formatting/field-types.js.map +1 -0
- package/lib/utils/formatting/index.d.ts +29 -0
- package/lib/utils/formatting/index.js +28 -0
- package/lib/utils/formatting/index.js.map +1 -0
- package/lib/utils/formatting/indicators.d.ts +113 -0
- package/lib/utils/formatting/indicators.js +161 -0
- package/lib/utils/formatting/indicators.js.map +1 -0
- package/lib/utils/formatting/loading-messages.d.ts +37 -0
- package/lib/utils/formatting/loading-messages.js +50 -0
- package/lib/utils/formatting/loading-messages.js.map +1 -0
- package/lib/utils/formatting/namespace-display.d.ts +31 -0
- package/lib/utils/formatting/namespace-display.js +64 -0
- package/lib/utils/formatting/namespace-display.js.map +1 -0
- package/lib/utils/formatting/numbers.d.ts +73 -0
- package/lib/utils/formatting/numbers.js +187 -0
- package/lib/utils/formatting/numbers.js.map +1 -0
- package/lib/utils/formatting/object-describe-display.d.ts +117 -0
- package/lib/utils/formatting/object-describe-display.js +447 -0
- package/lib/utils/formatting/object-describe-display.js.map +1 -0
- package/lib/utils/formatting/object-list-display.d.ts +225 -0
- package/lib/utils/formatting/object-list-display.js +718 -0
- package/lib/utils/formatting/object-list-display.js.map +1 -0
- package/lib/utils/formatting/org-identity.d.ts +15 -0
- package/lib/utils/formatting/org-identity.js +28 -0
- package/lib/utils/formatting/org-identity.js.map +1 -0
- package/lib/utils/formatting/record-age-grid.d.ts +41 -0
- package/lib/utils/formatting/record-age-grid.js +56 -0
- package/lib/utils/formatting/record-age-grid.js.map +1 -0
- package/lib/utils/formatting/sections.d.ts +108 -0
- package/lib/utils/formatting/sections.js +150 -0
- package/lib/utils/formatting/sections.js.map +1 -0
- package/lib/utils/formatting/tables.d.ts +90 -0
- package/lib/utils/formatting/tables.js +113 -0
- package/lib/utils/formatting/tables.js.map +1 -0
- package/lib/utils/formatting/user-details-display.d.ts +101 -0
- package/lib/utils/formatting/user-details-display.js +425 -0
- package/lib/utils/formatting/user-details-display.js.map +1 -0
- package/lib/utils/pagination/keypress-reader.d.ts +20 -0
- package/lib/utils/pagination/keypress-reader.js +63 -0
- package/lib/utils/pagination/keypress-reader.js.map +1 -0
- package/lib/utils/pagination/paginate-output.d.ts +48 -0
- package/lib/utils/pagination/paginate-output.js +136 -0
- package/lib/utils/pagination/paginate-output.js.map +1 -0
- package/messages/compatibility.check.md +71 -0
- package/messages/cuneiform.access.md +138 -0
- package/messages/definition.create.md +525 -0
- package/messages/definition.export.md +84 -0
- package/messages/definition.get.md +147 -0
- package/messages/definition.import.md +65 -0
- package/messages/definition.list.md +264 -0
- package/messages/definition.purge.md +330 -0
- package/messages/definition.update.md +118 -0
- package/messages/mcp.serve.md +66 -0
- package/messages/object.describe.md +205 -0
- package/messages/object.list.md +463 -0
- package/messages/org.details.md +386 -0
- package/messages/org.reset.md +71 -0
- package/messages/profile.md +243 -0
- package/messages/profile.request.cancel.md +143 -0
- package/messages/profile.request.delete.md +139 -0
- package/messages/profile.request.list.md +89 -0
- package/messages/summary.purge.md +218 -0
- package/messages/summary.reprofile.md +150 -0
- package/messages/summary.stop.md +157 -0
- package/messages/user.details.md +501 -0
- package/oclif.lock +3267 -2148
- package/oclif.manifest.json +2829 -31
- package/package.json +104 -18
- package/lib/commands/cuneiform/about.d.ts +0 -13
- package/lib/commands/cuneiform/about.js +0 -26
- package/lib/commands/cuneiform/about.js.map +0 -1
- package/lib/commands/hello/world.d.ts +0 -14
- package/lib/commands/hello/world.js +0 -27
- package/lib/commands/hello/world.js.map +0 -1
- package/lib/index.d.ts +0 -2
- package/lib/index.js +0 -2
- package/lib/index.js.map +0 -1
- package/messages/cuneiform.about.md +0 -19
- package/messages/hello.world.md +0 -29
|
@@ -0,0 +1,1080 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026, PeerNova, Inc. All Rights Reserved.
|
|
3
|
+
* PROPRIETARY AND CONFIDENTIAL. Unauthorized copying, modification,
|
|
4
|
+
* or distribution is strictly prohibited. Use is governed by the
|
|
5
|
+
* Master Subscription Agreement (MSA) between PeerNova, Inc. and the
|
|
6
|
+
* licensee. See LICENSE file in the repo root.
|
|
7
|
+
*/
|
|
8
|
+
import { CuneiformQueryBuilder } from '../adapters/soql/cuneiform-query-builder.js';
|
|
9
|
+
import { createSuccessResult, createFailureResult } from '../models/service-result.js';
|
|
10
|
+
import { ServiceErrorCodes } from '../adapters/errors.js';
|
|
11
|
+
import { processInBatches } from '../utils/batch-processor.js';
|
|
12
|
+
import { validateNonEmptyArray } from './validation.js';
|
|
13
|
+
import { FEATURE_GATED_STANDARD_OBJECTS, MAX_OBJECT_NAMES } from './constants.js';
|
|
14
|
+
/** Default number of objects to process concurrently in checkDataExistsInRange. */
|
|
15
|
+
const DEFAULT_CONCURRENCY_LIMIT = 5;
|
|
16
|
+
/**
|
|
17
|
+
* Upper bound on suspect-set reconciliation (CLI-4214). The `/limits/recordCount` hint
|
|
18
|
+
* omits empty and not-yet-counted objects; the service reconciles those "suspects" with a
|
|
19
|
+
* LIMIT-1 probe + targeted COUNT(). This cap stops the reconciliation from degenerating into
|
|
20
|
+
* a whole-org COUNT() sweep (the CLI-3083 governor blowup). Aligned with {@link MAX_OBJECT_NAMES}.
|
|
21
|
+
* When the suspect set exceeds the cap, the first 200 are reconciled and the remainder are
|
|
22
|
+
* surfaced as a truncation warning with `recordCount` left undefined — never silently zeroed.
|
|
23
|
+
*/
|
|
24
|
+
const RECONCILIATION_CAP = 200;
|
|
25
|
+
/** Valid type filter values */
|
|
26
|
+
const VALID_TYPES = new Set(['standard', 'custom', 'all']);
|
|
27
|
+
/** Valid name filter operators */
|
|
28
|
+
const VALID_OPERATORS = new Set(['startsWith', 'contains', 'equals', 'wildcard']);
|
|
29
|
+
/** Valid classification values */
|
|
30
|
+
const VALID_CLASSIFICATIONS = new Set(['customer', 'internal', 'all']);
|
|
31
|
+
/**
|
|
32
|
+
* Object API name suffixes that indicate non-data objects which are never valid
|
|
33
|
+
* profiling targets: `__mdt` (Custom Metadata Types — configuration, not data),
|
|
34
|
+
* `__History` (change-tracking auxiliary), `__Share` (sharing-rule auxiliary),
|
|
35
|
+
* `__Feed` (Chatter feed auxiliary), `__ChangeEvent` (Change Data Capture event),
|
|
36
|
+
* `__e` (Platform Events — no persistent records).
|
|
37
|
+
*
|
|
38
|
+
* Big Objects (`__b`) and External Objects (`__x`) are intentionally excluded
|
|
39
|
+
* from this list — they hold queryable records and remain valid profiling
|
|
40
|
+
* targets, gated by the layoutable/keyPrefix predicate alone.
|
|
41
|
+
*
|
|
42
|
+
* The layoutable + keyPrefix predicate alone is insufficient: in some orgs
|
|
43
|
+
* Custom Metadata Types report `IsLayoutable=true` and a non-null KeyPrefix
|
|
44
|
+
* (e.g., m00) and end up in the `customer` EntityDefinition set — see CLI-3085.
|
|
45
|
+
* The ISV REST `filter-objects` endpoint does not honor `excludeSystemObjects`
|
|
46
|
+
* for `__mdt` either, so the CLI applies this name-based safety filter on both
|
|
47
|
+
* the local SOQL path and the REST response.
|
|
48
|
+
*/
|
|
49
|
+
const NON_DATA_SUFFIX_PATTERN = /__(mdt|History|Share|Feed|ChangeEvent|e)$/;
|
|
50
|
+
/**
|
|
51
|
+
* Domain service for filtering and classifying Salesforce objects.
|
|
52
|
+
*
|
|
53
|
+
* Provides methods to filter objects by type, namespace, classification,
|
|
54
|
+
* record counts, and record types. Uses describeGlobal for metadata,
|
|
55
|
+
* EntityDefinition SOQL for customer/internal classification, and
|
|
56
|
+
* lazy-loads record type information via SOQL.
|
|
57
|
+
*
|
|
58
|
+
* @design
|
|
59
|
+
* **File Size**: This file is ~630 lines, larger than the ideal 200-400 line target.
|
|
60
|
+
* This is intentional: all filter logic, validation, and transformation code is
|
|
61
|
+
* cohesive and related. Splitting into multiple files would force readers to jump
|
|
62
|
+
* between files to understand the filter chain, hurting confidence and clarity.
|
|
63
|
+
* Per Human-Centered Code Principles, "Confidence > Maintainability" — a cohesive
|
|
64
|
+
* file beats scattered fragments.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const service = new ObjectFilteringService({
|
|
69
|
+
* restApiAdapter,
|
|
70
|
+
* soqlAdapter,
|
|
71
|
+
* logger: console,
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* // Filter custom objects with records
|
|
75
|
+
* const result = await service.filter({
|
|
76
|
+
* type: 'custom',
|
|
77
|
+
* withRecords: true,
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* if (result.success) {
|
|
81
|
+
* for (const obj of result.data) {
|
|
82
|
+
* console.log(`${obj.name}: ${obj.recordCount} records`);
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export class ObjectFilteringService {
|
|
88
|
+
restApiAdapter;
|
|
89
|
+
soqlAdapter;
|
|
90
|
+
concurrencyLimit;
|
|
91
|
+
logger;
|
|
92
|
+
restClient;
|
|
93
|
+
/** Lazily loaded set of object names that have active record types */
|
|
94
|
+
recordTypeCache = null;
|
|
95
|
+
/** Cache of object names that have an OwnerId field (per-session) */
|
|
96
|
+
ownerFieldCache = new Map();
|
|
97
|
+
/** Lazily loaded set of customer-facing object names from EntityDefinition (per-session) */
|
|
98
|
+
customerObjectCache = null;
|
|
99
|
+
/**
|
|
100
|
+
* Creates a new ObjectFilteringService.
|
|
101
|
+
*
|
|
102
|
+
* @param config - Service configuration with required adapters
|
|
103
|
+
*/
|
|
104
|
+
constructor(config) {
|
|
105
|
+
this.restApiAdapter = config.restApiAdapter;
|
|
106
|
+
this.soqlAdapter = config.soqlAdapter;
|
|
107
|
+
this.concurrencyLimit = config.concurrencyLimit ?? DEFAULT_CONCURRENCY_LIMIT;
|
|
108
|
+
this.logger = config.logger;
|
|
109
|
+
this.restClient = config.restClient;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Validates filter options and returns a failure result if invalid.
|
|
113
|
+
*
|
|
114
|
+
* @param options - The filter options to validate
|
|
115
|
+
* @returns A failure ServiceResult if validation fails, undefined otherwise
|
|
116
|
+
*/
|
|
117
|
+
static validateFilterOptions(options) {
|
|
118
|
+
// Validate type
|
|
119
|
+
if (options.type !== undefined && !VALID_TYPES.has(options.type)) {
|
|
120
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_TYPE, `Invalid type parameter: "${options.type}". Must be 'standard', 'custom', or 'all'.`);
|
|
121
|
+
}
|
|
122
|
+
// Namespace validation is now performed at the parser boundary
|
|
123
|
+
// (`parseNamespaceFilter` in services/namespace-filter.ts). The typed
|
|
124
|
+
// NamespaceFilter discriminated union arriving here is already validated.
|
|
125
|
+
// Validate name filter
|
|
126
|
+
if (options.nameFilter) {
|
|
127
|
+
if (!options.nameFilter.value || options.nameFilter.value.trim().length === 0) {
|
|
128
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_NAME_FILTER, 'Name filter value is required and cannot be empty.');
|
|
129
|
+
}
|
|
130
|
+
if (!VALID_OPERATORS.has(options.nameFilter.operator)) {
|
|
131
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_NAME_FILTER, `Invalid name filter operator: "${options.nameFilter.operator}". Must be 'startsWith', 'contains', 'equals', or 'wildcard'.`);
|
|
132
|
+
}
|
|
133
|
+
// For wildcard operator, validate that pattern contains at least one *
|
|
134
|
+
if (options.nameFilter.operator === 'wildcard' && !options.nameFilter.value.includes('*')) {
|
|
135
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_NAME_FILTER, `Wildcard pattern must contain at least one '*' character. Got: "${options.nameFilter.value}".`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Validate classification
|
|
139
|
+
if (options.classification !== undefined && !VALID_CLASSIFICATIONS.has(options.classification)) {
|
|
140
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_CLASSIFICATION, `Invalid classification: "${options.classification}". Must be 'customer', 'internal', or 'all'.`);
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Determines whether an object is custom.
|
|
146
|
+
* Custom object detection: name ends with __c OR custom === true.
|
|
147
|
+
*
|
|
148
|
+
* @param obj - The global describe object to check
|
|
149
|
+
* @returns True if the object is custom
|
|
150
|
+
*/
|
|
151
|
+
static isCustomObject(obj) {
|
|
152
|
+
return obj.custom || obj.name.endsWith('__c');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Extracts namespace from an object API name.
|
|
156
|
+
* Namespaced objects follow the pattern: namespace__ObjectName__c
|
|
157
|
+
*
|
|
158
|
+
* @param name - The object API name
|
|
159
|
+
* @returns The namespace prefix, or undefined if unmanaged
|
|
160
|
+
*/
|
|
161
|
+
static extractNamespace(name) {
|
|
162
|
+
// Namespace pattern: at least two double-underscore segments
|
|
163
|
+
// e.g., ns__Object__c -> ns, ns__Object -> ns
|
|
164
|
+
// Standard objects and non-namespaced custom objects have at most one __
|
|
165
|
+
const parts = name.split('__');
|
|
166
|
+
if (parts.length >= 3) {
|
|
167
|
+
// ns__Object__c or ns__Object__suffix
|
|
168
|
+
return parts[0];
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Converts a global describe result to SObjectInfo.
|
|
174
|
+
*
|
|
175
|
+
* @param obj - The global describe object
|
|
176
|
+
* @returns SObjectInfo representation
|
|
177
|
+
*/
|
|
178
|
+
static toSObjectInfo(obj) {
|
|
179
|
+
const info = {
|
|
180
|
+
name: obj.name,
|
|
181
|
+
label: obj.label,
|
|
182
|
+
isCustom: ObjectFilteringService.isCustomObject(obj),
|
|
183
|
+
};
|
|
184
|
+
const namespace = ObjectFilteringService.extractNamespace(obj.name);
|
|
185
|
+
if (namespace) {
|
|
186
|
+
info.namespace = namespace;
|
|
187
|
+
}
|
|
188
|
+
return info;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Applies type filter to objects.
|
|
192
|
+
*/
|
|
193
|
+
static applyTypeFilter(objects, type) {
|
|
194
|
+
if (type === 'custom') {
|
|
195
|
+
return objects.filter((obj) => ObjectFilteringService.isCustomObject(obj));
|
|
196
|
+
}
|
|
197
|
+
// standard
|
|
198
|
+
return objects.filter((obj) => !ObjectFilteringService.isCustomObject(obj));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Applies namespace filter to objects.
|
|
202
|
+
*
|
|
203
|
+
* Accepts a typed `NamespaceFilter` (or `undefined` for "no filter"). CLI-3801
|
|
204
|
+
* replaced the previous `string | null` parameter with the discriminated union
|
|
205
|
+
* parsed by `parseNamespaceFilter` at the command/MCP boundary.
|
|
206
|
+
*
|
|
207
|
+
* - `undefined` → return objects unchanged
|
|
208
|
+
* - `kind: 'all'` → return objects unchanged
|
|
209
|
+
* - `kind: 'unmanaged'` → only objects without a namespace prefix
|
|
210
|
+
* - `kind: 'managed'` → objects matching any of the listed namespaces (case-insensitive)
|
|
211
|
+
*/
|
|
212
|
+
static applyNamespaceFilter(objects, filter) {
|
|
213
|
+
if (filter === undefined || filter.kind === 'all') {
|
|
214
|
+
return objects;
|
|
215
|
+
}
|
|
216
|
+
if (filter.kind === 'unmanaged') {
|
|
217
|
+
return objects.filter((obj) => !ObjectFilteringService.extractNamespace(obj.name));
|
|
218
|
+
}
|
|
219
|
+
// kind === 'managed' — match any namespace in the list (case-insensitive).
|
|
220
|
+
// Salesforce namespaces are case-insensitive in org references and API calls.
|
|
221
|
+
const lowerSet = new Set(filter.namespaces.map((n) => n.toLowerCase()));
|
|
222
|
+
return objects.filter((obj) => {
|
|
223
|
+
const ns = ObjectFilteringService.extractNamespace(obj.name);
|
|
224
|
+
return ns !== undefined && lowerSet.has(ns.toLowerCase());
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Converts a NamespaceFilter to the legacy string shape accepted by the ISV
|
|
229
|
+
* REST `filter-objects` endpoint. Used only on the REST delegation path —
|
|
230
|
+
* SOQL path consumes the typed filter directly via {@link applyNamespaceFilter}.
|
|
231
|
+
*
|
|
232
|
+
* Mapping:
|
|
233
|
+
* - `undefined` → `undefined` (no filter)
|
|
234
|
+
* - `kind: 'all'` → `undefined` (no filter — every namespace)
|
|
235
|
+
* - `kind: 'unmanaged'` → `''` (REST sentinel for unmanaged)
|
|
236
|
+
* - `kind: 'managed'` → first namespace in the list (multi-namespace OR is SOQL-only; additional namespaces are dropped on this path)
|
|
237
|
+
*/
|
|
238
|
+
static namespaceFilterToRestString(filter) {
|
|
239
|
+
if (filter === undefined || filter.kind === 'all')
|
|
240
|
+
return undefined;
|
|
241
|
+
if (filter.kind === 'unmanaged')
|
|
242
|
+
return '';
|
|
243
|
+
// kind === 'managed' — the REST endpoint accepts a single namespace string.
|
|
244
|
+
return filter.namespaces[0];
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Returns true when the filter is a managed filter with 2+ namespaces — the
|
|
248
|
+
* REST endpoint cannot represent multi-namespace OR-union, so callers route
|
|
249
|
+
* around REST delegation and use the local SOQL fallback instead.
|
|
250
|
+
*/
|
|
251
|
+
static isMultiNamespaceFilter(filter) {
|
|
252
|
+
return filter !== undefined && filter.kind === 'managed' && filter.namespaces.length >= 2;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Converts a wildcard pattern (with *) to a regular expression.
|
|
256
|
+
* The * character matches any sequence of characters.
|
|
257
|
+
*
|
|
258
|
+
* @param pattern - The wildcard pattern (e.g., "Account*", "*__c", "*Contact*")
|
|
259
|
+
* @returns A RegExp that matches the pattern case-insensitively
|
|
260
|
+
*/
|
|
261
|
+
static wildcardToRegex(pattern) {
|
|
262
|
+
// Escape special regex characters except *
|
|
263
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
264
|
+
// Convert * to .* (match any characters)
|
|
265
|
+
const regexPattern = escaped.replace(/\*/g, '.*');
|
|
266
|
+
// Anchor the pattern and make it case-insensitive
|
|
267
|
+
return new RegExp(`^${regexPattern}$`, 'i');
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Applies name filter to objects (case-insensitive).
|
|
271
|
+
*/
|
|
272
|
+
static applyNameFilter(objects, nameFilter) {
|
|
273
|
+
const lowerValue = nameFilter.value.toLowerCase();
|
|
274
|
+
// Pre-compile regex for wildcard operator
|
|
275
|
+
const wildcardRegex = nameFilter.operator === 'wildcard' ? ObjectFilteringService.wildcardToRegex(nameFilter.value) : null;
|
|
276
|
+
return objects.filter((obj) => {
|
|
277
|
+
const lowerName = obj.name.toLowerCase();
|
|
278
|
+
switch (nameFilter.operator) {
|
|
279
|
+
case 'startsWith':
|
|
280
|
+
return lowerName.startsWith(lowerValue);
|
|
281
|
+
case 'contains':
|
|
282
|
+
return lowerName.includes(lowerValue);
|
|
283
|
+
case 'equals':
|
|
284
|
+
return lowerName === lowerValue;
|
|
285
|
+
case 'wildcard':
|
|
286
|
+
return wildcardRegex.test(obj.name);
|
|
287
|
+
default:
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Applies system object exclusion. Excludes objects that are not valid profiling
|
|
294
|
+
* targets: layoutable === false OR keyPrefix === null (auxiliary/system objects),
|
|
295
|
+
* OR name matches NON_DATA_SUFFIX_PATTERN (CLI-3085: Salesforce reports
|
|
296
|
+
* IsLayoutable=true and a non-null KeyPrefix for Custom Metadata Types in some
|
|
297
|
+
* orgs, so the layoutable/keyPrefix predicate alone is insufficient),
|
|
298
|
+
* OR name is a feature-gated standard object that fails ISV schema introspection
|
|
299
|
+
* (CLI-3783: objects visible in describeGlobal but inaccessible via Apex when
|
|
300
|
+
* the underlying feature/license is not active).
|
|
301
|
+
*/
|
|
302
|
+
static applySystemObjectExclusion(objects) {
|
|
303
|
+
return objects.filter((obj) => {
|
|
304
|
+
const layoutable = obj.layoutable ?? true;
|
|
305
|
+
if (!layoutable || obj.keyPrefix === null)
|
|
306
|
+
return false;
|
|
307
|
+
if (NON_DATA_SUFFIX_PATTERN.test(obj.name))
|
|
308
|
+
return false;
|
|
309
|
+
if (FEATURE_GATED_STANDARD_OBJECTS.has(obj.name))
|
|
310
|
+
return false;
|
|
311
|
+
return true;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Predicate filter: keep only objects with `recordCount > 0`.
|
|
316
|
+
*
|
|
317
|
+
* Operates on already-populated counts produced by {@link populateRecordCounts}; does not
|
|
318
|
+
* perform any API calls. The qualifying set is `limits-present-with-count>0 ∪ probe-positive`
|
|
319
|
+
* (CLI-4214). Objects whose count is genuinely unknown (undefined recordCount — bulk call
|
|
320
|
+
* failed or reconciliation was capped) are excluded — only positively confirmed non-zero
|
|
321
|
+
* counts pass.
|
|
322
|
+
*/
|
|
323
|
+
static applyWithRecordsFilter(objects) {
|
|
324
|
+
return objects.filter((obj) => (obj.recordCount ?? 0) > 0);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Predicate filter: keep only objects with `recordCount === 0`.
|
|
328
|
+
*
|
|
329
|
+
* Operates on already-populated counts produced by {@link populateRecordCounts}; does not
|
|
330
|
+
* perform any API calls. Only AUTHORITATIVE zeros pass — a probe-negative suspect or a SOQL
|
|
331
|
+
* fallback 0 (CLI-4214). An object whose count is genuinely unknown (undefined recordCount,
|
|
332
|
+
* e.g. reconciliation failed or was capped) is excluded — it must NOT be silently classified
|
|
333
|
+
* as empty.
|
|
334
|
+
*/
|
|
335
|
+
static applyWithoutRecordsFilter(objects) {
|
|
336
|
+
return objects.filter((obj) => obj.recordCount === 0);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Applies withRecords or withoutRecords locally after REST delegation.
|
|
340
|
+
* Called when the Apex endpoint has returned a candidate list that still needs
|
|
341
|
+
* record-count filtering — the server intentionally does not do this step.
|
|
342
|
+
*/
|
|
343
|
+
static applyPostRestRecordCountFilter(results, options) {
|
|
344
|
+
if (options.withRecords) {
|
|
345
|
+
return ObjectFilteringService.applyWithRecordsFilter(results);
|
|
346
|
+
}
|
|
347
|
+
if (options.withoutRecords) {
|
|
348
|
+
return ObjectFilteringService.applyWithoutRecordsFilter(results);
|
|
349
|
+
}
|
|
350
|
+
return results;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Excludes Custom Settings (list and hierarchy) from describe results.
|
|
354
|
+
* Custom Settings are configuration objects, not business data objects.
|
|
355
|
+
* Protected Custom Settings (visibility=Protected) must never be visible to subscribers.
|
|
356
|
+
*/
|
|
357
|
+
static applyCustomSettingExclusion(objects) {
|
|
358
|
+
return objects.filter((obj) => !obj.customSetting);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Filters Salesforce objects based on combined filter criteria.
|
|
362
|
+
*
|
|
363
|
+
* Applies filters in order from cheapest to most expensive:
|
|
364
|
+
* type -> namespace -> nameFilter -> classification -> excludeSystemObjects -> customSettings -> withRecords -> hasRecordTypes
|
|
365
|
+
*
|
|
366
|
+
* @param options - Filter criteria to apply
|
|
367
|
+
* @returns ServiceResult containing filtered SObjectInfo array
|
|
368
|
+
*/
|
|
369
|
+
async filter(options) {
|
|
370
|
+
const startTime = Date.now();
|
|
371
|
+
try {
|
|
372
|
+
// Validate inputs
|
|
373
|
+
const validationError = ObjectFilteringService.validateFilterOptions(options);
|
|
374
|
+
if (validationError) {
|
|
375
|
+
return validationError;
|
|
376
|
+
}
|
|
377
|
+
// REST API path: delegate metadata filtering to the server when restClient is available.
|
|
378
|
+
// withRecords/withoutRecords are intentionally NOT delegated — the server-side path uses
|
|
379
|
+
// per-object SOQL COUNT() queries that exhaust the governor budget on large orgs, causing
|
|
380
|
+
// objects without records to slip through (CLI-3083). The CLI applies them locally via
|
|
381
|
+
// GET /limits/recordCount (single call, no SOQL cost). Falls back to the local path on
|
|
382
|
+
// failure (e.g., endpoint not deployed).
|
|
383
|
+
const restFallbackResult = await this.tryRestDelegation(options, startTime);
|
|
384
|
+
if (restFallbackResult) {
|
|
385
|
+
const restResults = ObjectFilteringService.applyPostRestRecordCountFilter(restFallbackResult.data, options);
|
|
386
|
+
return createSuccessResult(restResults, {
|
|
387
|
+
message: `Found ${restResults.length} objects matching filter criteria`,
|
|
388
|
+
// CLI-4214: carry forward reconciliation disclosures (e.g. truncation) produced
|
|
389
|
+
// by filterViaRest → populateRecordCounts; the rebuilt result must not drop them.
|
|
390
|
+
warnings: restFallbackResult.warnings,
|
|
391
|
+
metadata: { duration: Date.now() - startTime, strategyUsed: 'rest-api' },
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
// Fetch global describe (local path)
|
|
395
|
+
const globalResult = await this.restApiAdapter.describeGlobal();
|
|
396
|
+
if (!globalResult.success) {
|
|
397
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, globalResult.message ?? 'Global describe failed', {
|
|
398
|
+
metadata: { duration: Date.now() - startTime },
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
let sobjects = globalResult.data.sobjects;
|
|
402
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
403
|
+
// Filter Application Order (cheapest → most expensive)
|
|
404
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
405
|
+
// Filters are deliberately ordered to minimize work:
|
|
406
|
+
// 0. non-profileable — drop non-queryable objects (mirrors server-side
|
|
407
|
+
// applyNonProfileableFilters in pnova/ObjectFilterRestService.cls).
|
|
408
|
+
// Non-queryable objects (FeedTrackedChange, etc.) cannot be profiled,
|
|
409
|
+
// so they must never appear in any classification bucket. Without
|
|
410
|
+
// this gate, the local fallback returned a superset of the REST path
|
|
411
|
+
// for `--classification customer|internal` (CLI-3310).
|
|
412
|
+
// 1. type — in-memory flag check (instant)
|
|
413
|
+
// 2. namespace — string parsing (instant)
|
|
414
|
+
// 3. nameFilter — string comparison (instant)
|
|
415
|
+
// 4. classification — 1 SOQL query (cached after first call)
|
|
416
|
+
// 5. excludeSystem — in-memory flag check (instant)
|
|
417
|
+
// 6. customSettings — in-memory flag check (instant, uses describe already fetched)
|
|
418
|
+
// 7. populate counts — 1 bulk REST call against the bounded result set.
|
|
419
|
+
// The Records column is part of the documented output contract — counts
|
|
420
|
+
// must be queried unconditionally so that "0" never doubles as a placeholder
|
|
421
|
+
// for "we didn't ask". `withRecords` / `withoutRecords` are predicates,
|
|
422
|
+
// not population gates (CLI-3077).
|
|
423
|
+
// 8. withRecords — predicate against populated counts (no API call)
|
|
424
|
+
// 9. withoutRecords — predicate against populated counts (no API call)
|
|
425
|
+
// 10. hasRecordTypes — 1 SOQL query (cached after first call)
|
|
426
|
+
// 11. withOwner — N describe calls (1 per uncached object)
|
|
427
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
428
|
+
// 0. Non-profileable exclusion — match server's applyNonProfileableFilters.
|
|
429
|
+
// Use !== false so that objects where queryable is absent in the API
|
|
430
|
+
// response are kept rather than silently dropped.
|
|
431
|
+
sobjects = sobjects.filter((obj) => obj.queryable !== false);
|
|
432
|
+
// 1. Type filter (instant)
|
|
433
|
+
if (options.type && options.type !== 'all') {
|
|
434
|
+
sobjects = ObjectFilteringService.applyTypeFilter(sobjects, options.type);
|
|
435
|
+
}
|
|
436
|
+
// 2. Namespace filter (instant)
|
|
437
|
+
if (options.namespace !== undefined) {
|
|
438
|
+
sobjects = ObjectFilteringService.applyNamespaceFilter(sobjects, options.namespace);
|
|
439
|
+
}
|
|
440
|
+
// 3. Name filter (instant)
|
|
441
|
+
if (options.nameFilter) {
|
|
442
|
+
sobjects = ObjectFilteringService.applyNameFilter(sobjects, options.nameFilter);
|
|
443
|
+
}
|
|
444
|
+
// 4. Classification filter (1 SOQL query, cached after first call)
|
|
445
|
+
if (options.classification && options.classification !== 'all') {
|
|
446
|
+
sobjects = await this.applyClassificationFilter(sobjects, options.classification);
|
|
447
|
+
}
|
|
448
|
+
// 5. System object exclusion (instant)
|
|
449
|
+
if (options.excludeSystemObjects) {
|
|
450
|
+
sobjects = ObjectFilteringService.applySystemObjectExclusion(sobjects);
|
|
451
|
+
}
|
|
452
|
+
// 6. Custom Setting exclusion (instant — flag already present in global describe)
|
|
453
|
+
sobjects = ObjectFilteringService.applyCustomSettingExclusion(sobjects);
|
|
454
|
+
// Convert to SObjectInfo before expensive operations
|
|
455
|
+
let results = sobjects.map((obj) => ObjectFilteringService.toSObjectInfo(obj));
|
|
456
|
+
// Populate isCustomer classification if EntityDefinition cache is available
|
|
457
|
+
if (this.customerObjectCache !== null) {
|
|
458
|
+
results = results.map((obj) => ({ ...obj, isCustomer: this.customerObjectCache.has(obj.name) }));
|
|
459
|
+
}
|
|
460
|
+
// 6. Populate recordCount unconditionally (single bulk REST call). When the
|
|
461
|
+
// caller asked to filter by record count, the populate step must succeed —
|
|
462
|
+
// we can't honestly say "objects with records" if we don't know counts.
|
|
463
|
+
// CLI-4214: reconciliation may surface a truncation warning (suspect set > cap).
|
|
464
|
+
const countsRequired = options.withRecords === true || options.withoutRecords === true;
|
|
465
|
+
const populateResult = await this.populateRecordCounts(results, countsRequired);
|
|
466
|
+
results = populateResult.objects;
|
|
467
|
+
const populateWarnings = populateResult.warnings;
|
|
468
|
+
// 7-8. withRecords / withoutRecords predicate against populated counts. Shared with the
|
|
469
|
+
// REST path via applyPostRestRecordCountFilter (single branch keeps complexity bounded).
|
|
470
|
+
results = ObjectFilteringService.applyPostRestRecordCountFilter(results, options);
|
|
471
|
+
// 9. hasRecordTypes filter (expensive — 1 SOQL query, cached after first call)
|
|
472
|
+
if (options.hasRecordTypes) {
|
|
473
|
+
results = await this.applyHasRecordTypesFilter(results);
|
|
474
|
+
}
|
|
475
|
+
// 10. withOwner filter (expensive — N API calls, 1 describe per uncached object)
|
|
476
|
+
if (options.withOwner) {
|
|
477
|
+
results = await this.applyWithOwnerFilter(results);
|
|
478
|
+
}
|
|
479
|
+
const duration = Date.now() - startTime;
|
|
480
|
+
this.logger?.log(`Filter returned ${results.length} objects in ${duration}ms`);
|
|
481
|
+
return createSuccessResult(results, {
|
|
482
|
+
message: `Found ${results.length} objects matching filter criteria`,
|
|
483
|
+
warnings: populateWarnings.length > 0 ? populateWarnings : undefined,
|
|
484
|
+
metadata: { duration },
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
489
|
+
this.logger?.log(`Filter failed: ${errorMessage}`);
|
|
490
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, errorMessage, {
|
|
491
|
+
metadata: { duration: Date.now() - startTime },
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Filters objects by type (standard or custom).
|
|
497
|
+
*
|
|
498
|
+
* @param type - The object type to filter by
|
|
499
|
+
* @returns ServiceResult containing filtered SObjectInfo array
|
|
500
|
+
*/
|
|
501
|
+
async filterByType(type) {
|
|
502
|
+
if (!VALID_TYPES.has(type)) {
|
|
503
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_TYPE, `Invalid type parameter: "${type}". Must be 'standard', 'custom', or 'all'.`);
|
|
504
|
+
}
|
|
505
|
+
return this.filter({ type });
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Filters objects by namespace.
|
|
509
|
+
*
|
|
510
|
+
* @param filter - The typed NamespaceFilter to apply (undefined returns all objects)
|
|
511
|
+
* @returns ServiceResult containing filtered SObjectInfo array
|
|
512
|
+
*/
|
|
513
|
+
async filterByNamespace(filter) {
|
|
514
|
+
// Validation is performed at the parser boundary (parseNamespaceFilter); the
|
|
515
|
+
// discriminated union arriving here is already well-formed.
|
|
516
|
+
return this.filter({ namespace: filter });
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Filters objects to only those with records (record count > 0).
|
|
520
|
+
*
|
|
521
|
+
* @returns ServiceResult containing filtered SObjectInfo array
|
|
522
|
+
*/
|
|
523
|
+
async filterWithRecords() {
|
|
524
|
+
return this.filter({ withRecords: true });
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Retrieves SObjectInfo for specific object names.
|
|
528
|
+
*
|
|
529
|
+
* Returns success with data for found objects and warnings for missing ones.
|
|
530
|
+
* If none are found, returns success with empty data and warnings.
|
|
531
|
+
*
|
|
532
|
+
* @param objectNames - Array of object API names to look up
|
|
533
|
+
* @returns ServiceResult containing found SObjectInfo array with warnings for missing
|
|
534
|
+
*/
|
|
535
|
+
async getObjectsByName(objectNames) {
|
|
536
|
+
const startTime = Date.now();
|
|
537
|
+
// Validate input
|
|
538
|
+
const validationError = validateNonEmptyArray(objectNames, 'objectNames', MAX_OBJECT_NAMES);
|
|
539
|
+
if (validationError) {
|
|
540
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_OBJECT_NAMES, validationError);
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
// Fetch global describe
|
|
544
|
+
const globalResult = await this.restApiAdapter.describeGlobal();
|
|
545
|
+
if (!globalResult.success) {
|
|
546
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, globalResult.message ?? 'Global describe failed', {
|
|
547
|
+
metadata: { duration: Date.now() - startTime },
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
// Build a lookup map (case-insensitive)
|
|
551
|
+
const objectMap = new Map();
|
|
552
|
+
for (const obj of globalResult.data.sobjects) {
|
|
553
|
+
objectMap.set(obj.name.toLowerCase(), obj);
|
|
554
|
+
}
|
|
555
|
+
const found = [];
|
|
556
|
+
const warnings = [];
|
|
557
|
+
for (const name of objectNames) {
|
|
558
|
+
const obj = objectMap.get(name.toLowerCase());
|
|
559
|
+
if (obj) {
|
|
560
|
+
found.push(ObjectFilteringService.toSObjectInfo(obj));
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
warnings.push(`Object not found: ${name}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const duration = Date.now() - startTime;
|
|
567
|
+
this.logger?.log(`getObjectsByName: found ${found.length}/${objectNames.length} objects in ${duration}ms`);
|
|
568
|
+
return createSuccessResult(found, {
|
|
569
|
+
message: `Found ${found.length} of ${objectNames.length} requested objects`,
|
|
570
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
571
|
+
metadata: { duration },
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
576
|
+
this.logger?.log(`getObjectsByName failed: ${errorMessage}`);
|
|
577
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, errorMessage, {
|
|
578
|
+
metadata: { duration: Date.now() - startTime },
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Retrieves record counts for the specified objects using the Record Count API.
|
|
584
|
+
*
|
|
585
|
+
* @param objectNames - Array of object API names to get counts for
|
|
586
|
+
* @returns ServiceResult containing a map of object name to record count
|
|
587
|
+
*/
|
|
588
|
+
async getRecordCounts(objectNames) {
|
|
589
|
+
return this.restApiAdapter.getRecordCounts(objectNames);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Checks whether data exists for each probe specification using LIMIT 1 SOQL probes.
|
|
593
|
+
*
|
|
594
|
+
* Returns a flat grid of per-probe boolean results. Each probe uses a
|
|
595
|
+
* `SELECT Id FROM {Object} WHERE CreatedDate >= {yearStart} AND CreatedDate < {yearEnd} LIMIT 1`
|
|
596
|
+
* query to determine data presence efficiently. When a probe includes a `recordType`,
|
|
597
|
+
* a `RecordType.DeveloperName = '{value}'` filter is added.
|
|
598
|
+
*
|
|
599
|
+
* Probes are grouped by object and processed one object at a time. All probes for a single
|
|
600
|
+
* object (across years and record types) run concurrently, then the next object's probes fire.
|
|
601
|
+
* This keeps concurrent SOQL queries scoped to a single object's probes.
|
|
602
|
+
*
|
|
603
|
+
* Failures on individual probes are captured per-entry (hasData=false, error set) rather
|
|
604
|
+
* than failing the entire operation — the grid is informational, not blocking.
|
|
605
|
+
*
|
|
606
|
+
* @param probes - Array of probe specifications to execute
|
|
607
|
+
* @returns ServiceResult containing the availability grid
|
|
608
|
+
*/
|
|
609
|
+
async checkDataExistsInRange(probes) {
|
|
610
|
+
const startTime = Date.now();
|
|
611
|
+
const grid = [];
|
|
612
|
+
if (probes.length === 0) {
|
|
613
|
+
return createSuccessResult(grid, {
|
|
614
|
+
message: 'No probes to check',
|
|
615
|
+
metadata: { duration: Date.now() - startTime },
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
// Group probes by object for per-object concurrent execution
|
|
619
|
+
const grouped = new Map();
|
|
620
|
+
for (const probe of probes) {
|
|
621
|
+
const existing = grouped.get(probe.objectApiName);
|
|
622
|
+
if (existing) {
|
|
623
|
+
existing.push(probe);
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
grouped.set(probe.objectApiName, [probe]);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// Process objects in batched parallel groups; probes within each object run concurrently
|
|
630
|
+
const groupedEntries = [...grouped.entries()];
|
|
631
|
+
const batchedResults = await processInBatches(groupedEntries, this.concurrencyLimit, async ([objectName, objectProbes]) => {
|
|
632
|
+
const results = await Promise.all(objectProbes.map((spec) => this.probeDataForYear(spec)));
|
|
633
|
+
this.logger?.log(`Data probes for ${objectName}: ${objectProbes.length} probes`);
|
|
634
|
+
return results;
|
|
635
|
+
});
|
|
636
|
+
for (const results of batchedResults) {
|
|
637
|
+
grid.push(...results);
|
|
638
|
+
}
|
|
639
|
+
const duration = Date.now() - startTime;
|
|
640
|
+
const gapsCount = grid.filter((e) => !e.hasData && !e.error).length;
|
|
641
|
+
const errorsCount = grid.filter((e) => e.error !== undefined).length;
|
|
642
|
+
this.logger?.log(`Data availability check: ${grid.length} probes, ${gapsCount} gaps, ${errorsCount} errors in ${duration}ms`);
|
|
643
|
+
return createSuccessResult(grid, {
|
|
644
|
+
message: `Checked ${grid.length} probe combinations`,
|
|
645
|
+
metadata: { duration },
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Probes a single object/year/recordType combination with a LIMIT 1 SOQL query.
|
|
650
|
+
* Returns a DataAvailabilityEntry — never throws.
|
|
651
|
+
*/
|
|
652
|
+
async probeDataForYear(spec) {
|
|
653
|
+
const { objectApiName, year, recordType } = spec;
|
|
654
|
+
const yearStart = `${year}-01-01T00:00:00Z`;
|
|
655
|
+
const yearEnd = `${year + 1}-01-01T00:00:00Z`;
|
|
656
|
+
const builder = CuneiformQueryBuilder.create().select(['Id']).from(objectApiName);
|
|
657
|
+
if (recordType) {
|
|
658
|
+
builder.where('RecordType.DeveloperName', '=', recordType);
|
|
659
|
+
builder.andWhereDatetime('CreatedDate', '>=', yearStart);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
builder.whereDatetime('CreatedDate', '>=', yearStart);
|
|
663
|
+
}
|
|
664
|
+
const soql = builder.andWhereDatetime('CreatedDate', '<', yearEnd).limit(1).toSOQL();
|
|
665
|
+
const label = recordType ? `${objectApiName}/${recordType}/${year}` : `${objectApiName}/${year}`;
|
|
666
|
+
try {
|
|
667
|
+
const result = await this.soqlAdapter.query(soql);
|
|
668
|
+
if (!result.success) {
|
|
669
|
+
this.logger?.log(`Data probe failed for ${label}: ${result.message ?? 'unknown error'}`);
|
|
670
|
+
return { objectApiName, year, hasData: false, error: result.message ?? 'Query failed', recordType };
|
|
671
|
+
}
|
|
672
|
+
return { objectApiName, year, hasData: result.data.records.length > 0, recordType };
|
|
673
|
+
}
|
|
674
|
+
catch (error) {
|
|
675
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
676
|
+
this.logger?.log(`Data probe exception for ${label}: ${errorMessage}`);
|
|
677
|
+
return { objectApiName, year, hasData: false, error: errorMessage, recordType };
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Attempts REST delegation for object filtering.
|
|
682
|
+
* Returns the successful result, or null to signal fallback to the local SOQL path.
|
|
683
|
+
*
|
|
684
|
+
* Multi-namespace filters (`kind: 'managed'` with 2+ namespaces) bypass REST
|
|
685
|
+
* delegation because the ISV REST `filter-objects` endpoint accepts only a
|
|
686
|
+
* single namespace string. Sending only the first namespace would silently
|
|
687
|
+
* drop the rest — a correctness gap. The local SOQL path consumes the full
|
|
688
|
+
* NamespaceFilter via {@link applyNamespaceFilter} and matches correctly.
|
|
689
|
+
*/
|
|
690
|
+
async tryRestDelegation(options, startTime) {
|
|
691
|
+
if (!this.restClient) {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
if (ObjectFilteringService.isMultiNamespaceFilter(options.namespace)) {
|
|
695
|
+
this.logger?.log('Multi-namespace filter requested — bypassing REST delegation, using local SOQL path for OR-union');
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
const restResult = await this.filterViaRest(options, startTime);
|
|
699
|
+
if (restResult.success) {
|
|
700
|
+
return restResult;
|
|
701
|
+
}
|
|
702
|
+
this.logger?.log(`REST filter-objects failed (${restResult.message ?? 'unknown'}), falling back to local SOQL path`);
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Lazily loads and caches the set of customer-facing object names from EntityDefinition.
|
|
707
|
+
* Uses a SOQL query against EntityDefinition to classify objects based on Salesforce metadata
|
|
708
|
+
* rather than hardcoded exclusion lists.
|
|
709
|
+
*
|
|
710
|
+
* @returns Set of customer-facing object API names
|
|
711
|
+
*/
|
|
712
|
+
/**
|
|
713
|
+
* Delegates object filtering to the ISV REST API (filter-objects endpoint).
|
|
714
|
+
* Server handles the full cost-optimized filter chain.
|
|
715
|
+
*/
|
|
716
|
+
async filterViaRest(options, startTime) {
|
|
717
|
+
this.logger?.log('Delegating object filtering to ISV REST API (filter-objects)');
|
|
718
|
+
const restResult = await this.restClient.filterObjects({
|
|
719
|
+
type: options.type,
|
|
720
|
+
// CLI-3801: NamespaceFilter is converted to the legacy string shape the REST
|
|
721
|
+
// endpoint accepts. The endpoint accepts a single namespace string or empty
|
|
722
|
+
// string (== unmanaged); see ObjectFilterRestService.cls. Multi-namespace
|
|
723
|
+
// OR support is SOQL-only at this layer — the REST path collapses to the
|
|
724
|
+
// first namespace as the most honest fallback (a downstream client-side
|
|
725
|
+
// filter pass on the response is not run because the server already filtered;
|
|
726
|
+
// additional namespaces are lost when REST delegation is used).
|
|
727
|
+
namespace: ObjectFilteringService.namespaceFilterToRestString(options.namespace),
|
|
728
|
+
excludeSystemObjects: options.excludeSystemObjects,
|
|
729
|
+
namePattern: options.nameFilter?.value,
|
|
730
|
+
nameOperator: options.nameFilter?.operator,
|
|
731
|
+
classification: options.classification,
|
|
732
|
+
hasRecordTypes: options.hasRecordTypes,
|
|
733
|
+
withOwner: options.withOwner,
|
|
734
|
+
});
|
|
735
|
+
if (!restResult.success) {
|
|
736
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, restResult.message ?? 'REST filter-objects failed', {
|
|
737
|
+
metadata: { duration: Date.now() - startTime },
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
const response = restResult.data;
|
|
741
|
+
if (!response.success) {
|
|
742
|
+
const errorMsg = response.errors?.length > 0 ? response.errors[0] : 'Server-side filtering failed';
|
|
743
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, errorMsg, {
|
|
744
|
+
metadata: { duration: Date.now() - startTime },
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
// CLI-3085: server-side filter-objects endpoint does not honor
|
|
748
|
+
// excludeSystemObjects for __mdt (and parity for History/Share/Feed/ChangeEvent
|
|
749
|
+
// /b/e/x suffixes is not guaranteed). Apply a name-based safety filter on the
|
|
750
|
+
// REST response when the caller requested system-object exclusion so the
|
|
751
|
+
// create flow stays tight regardless of server behavior.
|
|
752
|
+
// CLI-3783: same posture for feature-gated standard objects — the server may
|
|
753
|
+
// emit them in describeGlobal-derived results, but the ISV definition-create
|
|
754
|
+
// call fails with E4402 on Apex SObjectBase.getSObjectSchema(). Strip them
|
|
755
|
+
// post-fetch on the same `excludeSystemObjects` switch.
|
|
756
|
+
// Support both `results` (plural) and `result` (singular) — different ISV
|
|
757
|
+
// endpoint versions use different keys.
|
|
758
|
+
let results = response.results ?? response.result ?? [];
|
|
759
|
+
if (options.excludeSystemObjects) {
|
|
760
|
+
const before = results.length;
|
|
761
|
+
results = results.filter((obj) => !NON_DATA_SUFFIX_PATTERN.test(obj.name) && !FEATURE_GATED_STANDARD_OBJECTS.has(obj.name));
|
|
762
|
+
if (results.length !== before) {
|
|
763
|
+
this.logger?.log(`REST post-filter excluded ${before - results.length} non-data or feature-gated objects (CLI-3085, CLI-3783)`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
// CLI-3077: populate recordCount on the server response — older server builds
|
|
767
|
+
// skip count population unless withRecords/withoutRecords is set, which would
|
|
768
|
+
// otherwise surface as the "0 records" symptom. The server-side fix on
|
|
769
|
+
// packaging/v4.12.0 also populates unconditionally; this client-side step is
|
|
770
|
+
// defense-in-depth that lets the CLI ship independently of the package release.
|
|
771
|
+
const countsRequired = options.withRecords === true || options.withoutRecords === true;
|
|
772
|
+
const alreadyPopulated = results.some((obj) => obj.recordCount !== undefined && obj.recordCount !== null);
|
|
773
|
+
// CLI-4214: when the server did not pre-populate counts, run the same bulk + bounded
|
|
774
|
+
// reconciliation path as the local filter so truncation disclosures propagate.
|
|
775
|
+
const { objects: populated, warnings: populateWarnings } = alreadyPopulated
|
|
776
|
+
? { objects: results, warnings: [] }
|
|
777
|
+
: await this.populateRecordCounts(results, countsRequired);
|
|
778
|
+
return createSuccessResult(populated, {
|
|
779
|
+
message: `Filtered ${String(populated.length)} objects via REST API`,
|
|
780
|
+
warnings: populateWarnings.length > 0 ? populateWarnings : undefined,
|
|
781
|
+
metadata: { duration: Date.now() - startTime, strategyUsed: 'rest-api' },
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
async getCustomerObjects() {
|
|
785
|
+
if (this.customerObjectCache !== null) {
|
|
786
|
+
return this.customerObjectCache;
|
|
787
|
+
}
|
|
788
|
+
// EntityDefinition is a Setup object backed by metadata, not records. The
|
|
789
|
+
// platform returns it in batches and does NOT support queryMore() — the
|
|
790
|
+
// query MUST resolve in a single batch. Salesforce caps the EntityDefinition
|
|
791
|
+
// batch at 200 rows; using a larger LIMIT triggers the runtime error
|
|
792
|
+
// "EntityDefinition does not support queryMore()". 200 covers all practical
|
|
793
|
+
// orgs; objects beyond 200 customer-facing entities are classified as
|
|
794
|
+
// internal (false-negative is acceptable for the long-tail).
|
|
795
|
+
//
|
|
796
|
+
// Chained .where() calls must use andWhere() for subsequent conditions —
|
|
797
|
+
// .where() replaces the first condition each time it's called.
|
|
798
|
+
const soql = CuneiformQueryBuilder.create()
|
|
799
|
+
.select(['QualifiedApiName'])
|
|
800
|
+
.from('EntityDefinition')
|
|
801
|
+
.where('IsCustomizable', '=', true)
|
|
802
|
+
.andWhere('IsLayoutable', '=', true)
|
|
803
|
+
.andWhere('IsDeprecatedAndHidden', '=', false)
|
|
804
|
+
.limit(200)
|
|
805
|
+
.toSOQL();
|
|
806
|
+
const result = await this.soqlAdapter.query(soql);
|
|
807
|
+
if (result.success) {
|
|
808
|
+
this.customerObjectCache = new Set(result.data.records.map((r) => r.QualifiedApiName));
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
const errorMessage = `EntityDefinition classification query failed: ${result.message ?? 'unknown error'}`;
|
|
812
|
+
this.logger?.log(errorMessage);
|
|
813
|
+
throw new Error(errorMessage);
|
|
814
|
+
}
|
|
815
|
+
return this.customerObjectCache;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Applies classification filter to objects using EntityDefinition metadata.
|
|
819
|
+
* Customer-facing objects are those present in the EntityDefinition customer set.
|
|
820
|
+
* Internal objects are everything else.
|
|
821
|
+
*/
|
|
822
|
+
async applyClassificationFilter(objects, classification) {
|
|
823
|
+
const customerObjects = await this.getCustomerObjects();
|
|
824
|
+
if (classification === 'customer') {
|
|
825
|
+
return objects.filter((obj) => customerObjects.has(obj.name));
|
|
826
|
+
}
|
|
827
|
+
// internal: NOT in customer set
|
|
828
|
+
return objects.filter((obj) => !customerObjects.has(obj.name));
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Populates `recordCount` on every result via a single bulk record-count REST call.
|
|
832
|
+
*
|
|
833
|
+
* Always called during {@link filter} so the Records column is populated
|
|
834
|
+
* unconditionally — see CLI-3077.
|
|
835
|
+
*
|
|
836
|
+
* Failure semantics: when `required` is true (user passed `withRecords` or
|
|
837
|
+
* `withoutRecords`), a count-query failure throws so {@link filter} returns a
|
|
838
|
+
* `FILTER_FAILED` ServiceResult — we can't honestly filter by record count if
|
|
839
|
+
* we don't know counts. When `required` is false, a failure is logged and
|
|
840
|
+
* `recordCount` is left undefined; the display renders that as `—` rather than
|
|
841
|
+
* a misleading `0`.
|
|
842
|
+
*
|
|
843
|
+
* CLI-4214 — `/limits/recordCount` is a HINT (returns only objects with count > 0), so objects it omits arrive ABSENT from the bulk map. Those form the "suspect set": their count is NOT known authoritatively. Suspects are reconciled in two bounded stages.
|
|
844
|
+
* Stage 1: LIMIT-1 presence probe (`SELECT Id FROM {obj} LIMIT 1`), batched at `concurrencyLimit` via {@link processInBatches} — mirrors {@link checkDataExistsInRange} / {@link probeDataForYear}.
|
|
845
|
+
* Stage 2: targeted SOQL `COUNT()` over probe-POSITIVE suspects → authoritative count (provenance `'queried'`). Probe-negative suspects are authoritative empties (recordCount 0, provenance `'queried'`).
|
|
846
|
+
* The suspect reconciliation is capped at {@link RECONCILIATION_CAP} to avoid a whole-org COUNT() sweep (CLI-3083). When the suspect set exceeds the cap, the first 200 are reconciled and the remainder are surfaced via an explicit truncation warning with `recordCount` left undefined (genuinely unknown — never silently zeroed).
|
|
847
|
+
*
|
|
848
|
+
* @param objects - SObjectInfo array to enrich with record counts
|
|
849
|
+
* @param required - Whether the caller's filter chain depends on accurate counts
|
|
850
|
+
* @returns Object holding the enriched array and any disclosure warnings (e.g. truncation)
|
|
851
|
+
* @throws Error when `required` is true and the bulk count query fails entirely
|
|
852
|
+
*/
|
|
853
|
+
async populateRecordCounts(objects, required) {
|
|
854
|
+
if (objects.length === 0) {
|
|
855
|
+
return { objects, warnings: [] };
|
|
856
|
+
}
|
|
857
|
+
const objectNames = objects.map((obj) => obj.name);
|
|
858
|
+
const countsResult = await this.restApiAdapter.getRecordCounts(objectNames);
|
|
859
|
+
if (!countsResult.success) {
|
|
860
|
+
// Bulk call failed entirely. Preserve the existing soft-fail semantics: required=true
|
|
861
|
+
// throws (FILTER_FAILED upstream); required=false leaves recordCount undefined (— in display).
|
|
862
|
+
const message = `Record count query failed: ${countsResult.message ?? 'unknown error'}`;
|
|
863
|
+
this.logger?.log(message);
|
|
864
|
+
if (required) {
|
|
865
|
+
throw new Error(message);
|
|
866
|
+
}
|
|
867
|
+
return { objects, warnings: [] };
|
|
868
|
+
}
|
|
869
|
+
const limitsCounts = countsResult.data;
|
|
870
|
+
// A `/limits/recordCount` value > 0 is trusted as an approximate hint (provenance 'limits').
|
|
871
|
+
// A value that is ABSENT *or* exactly 0 is a SUSPECT — the endpoint is async-maintained and
|
|
872
|
+
// empirically returns BOTH omitted objects AND stale `0`s for objects that were last counted
|
|
873
|
+
// empty but now hold records (CLI-4214 covers both facets: the omitted "Opportunity" case and
|
|
874
|
+
// the stale-zero / undercount "Account reported 1 vs 526" case). A present-0 is therefore not
|
|
875
|
+
// authoritative until a SOQL probe confirms it; reconcile it like an omission.
|
|
876
|
+
const suspects = objects
|
|
877
|
+
.filter((obj) => {
|
|
878
|
+
const c = limitsCounts.get(obj.name);
|
|
879
|
+
return c === undefined || c === 0;
|
|
880
|
+
})
|
|
881
|
+
.map((obj) => obj.name);
|
|
882
|
+
const reconciliation = await this.reconcileSuspectCounts(suspects);
|
|
883
|
+
const enriched = objects.map((obj) => {
|
|
884
|
+
const limitsCount = limitsCounts.get(obj.name);
|
|
885
|
+
// Trust the hint only when it positively reports records (> 0). Absent or 0 → use the
|
|
886
|
+
// authoritative reconciliation result instead of the untrustworthy hint value.
|
|
887
|
+
if (limitsCount !== undefined && limitsCount > 0) {
|
|
888
|
+
return { ...obj, recordCount: limitsCount, recordCountProvenance: 'limits' };
|
|
889
|
+
}
|
|
890
|
+
const reconciled = reconciliation.counts.get(obj.name);
|
|
891
|
+
if (reconciled !== undefined) {
|
|
892
|
+
return { ...obj, recordCount: reconciled, recordCountProvenance: 'queried' };
|
|
893
|
+
}
|
|
894
|
+
// Genuinely unknown (beyond the reconciliation cap, or a reconciliation probe failed):
|
|
895
|
+
// leave recordCount undefined so it is never read as an authoritative 0.
|
|
896
|
+
return { ...obj, recordCount: undefined, recordCountProvenance: undefined };
|
|
897
|
+
});
|
|
898
|
+
return { objects: enriched, warnings: reconciliation.warnings };
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Two-stage bounded reconciliation of the suspect set (CLI-4214).
|
|
902
|
+
*
|
|
903
|
+
* @param suspectNames - Object API names absent from the bulk `/limits/recordCount` map
|
|
904
|
+
* @returns Authoritative counts for the reconciled subset plus any truncation warnings; objects beyond {@link RECONCILIATION_CAP} are intentionally absent from `counts`.
|
|
905
|
+
*/
|
|
906
|
+
async reconcileSuspectCounts(suspectNames) {
|
|
907
|
+
const counts = new Map();
|
|
908
|
+
const warnings = [];
|
|
909
|
+
if (suspectNames.length === 0) {
|
|
910
|
+
return { counts, warnings };
|
|
911
|
+
}
|
|
912
|
+
// BOUND: cap the reconciliation so we never run COUNT() over the whole org (CLI-3083).
|
|
913
|
+
const toReconcile = suspectNames.slice(0, RECONCILIATION_CAP);
|
|
914
|
+
if (suspectNames.length > RECONCILIATION_CAP) {
|
|
915
|
+
const unreconciled = suspectNames.length - RECONCILIATION_CAP;
|
|
916
|
+
warnings.push(`Record-count reconciliation capped at ${RECONCILIATION_CAP} of ${suspectNames.length} objects; ` +
|
|
917
|
+
`${unreconciled} objects could not be authoritatively counted.`);
|
|
918
|
+
}
|
|
919
|
+
// Stage 1 (cheap): LIMIT-1 presence probe, batched at concurrencyLimit.
|
|
920
|
+
const probeResults = await processInBatches(toReconcile, this.concurrencyLimit, async (name) => ({
|
|
921
|
+
name,
|
|
922
|
+
present: await this.probeObjectHasRecords(name),
|
|
923
|
+
}));
|
|
924
|
+
const positives = [];
|
|
925
|
+
for (const { name, present } of probeResults) {
|
|
926
|
+
if (present === true) {
|
|
927
|
+
positives.push(name);
|
|
928
|
+
}
|
|
929
|
+
else if (present === false) {
|
|
930
|
+
// Probe-negative suspect → AUTHORITATIVE empty (confirmed zero).
|
|
931
|
+
counts.set(name, 0);
|
|
932
|
+
}
|
|
933
|
+
// present === undefined → the probe itself FAILED. We cannot confirm presence or
|
|
934
|
+
// absence, so the count stays unknown (absent from `counts`) — never a silent 0.
|
|
935
|
+
}
|
|
936
|
+
// Stage 2 (small): authoritative COUNT() ONLY over probe-positive suspects.
|
|
937
|
+
const countResults = await processInBatches(positives, this.concurrencyLimit, async (name) => ({
|
|
938
|
+
name,
|
|
939
|
+
count: await this.countObjectRecords(name),
|
|
940
|
+
}));
|
|
941
|
+
for (const { name, count } of countResults) {
|
|
942
|
+
if (count !== undefined) {
|
|
943
|
+
counts.set(name, count);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return { counts, warnings };
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Stage-1 LIMIT-1 presence probe for a single suspect object (CLI-4214).
|
|
950
|
+
* Mirrors {@link probeDataForYear} — same SOQL adapter, same query builder. Never throws.
|
|
951
|
+
*
|
|
952
|
+
* @param objectApiName - The object API name to probe
|
|
953
|
+
* @returns `true` when at least one record exists, `false` when authoritatively empty, `undefined` when the probe itself FAILED (presence unknown — must not be read as empty)
|
|
954
|
+
*/
|
|
955
|
+
async probeObjectHasRecords(objectApiName) {
|
|
956
|
+
const soql = CuneiformQueryBuilder.create().select(['Id']).from(objectApiName).limit(1).toSOQL();
|
|
957
|
+
try {
|
|
958
|
+
const result = await this.soqlAdapter.query(soql);
|
|
959
|
+
if (!result.success) {
|
|
960
|
+
this.logger?.log(`Record presence probe failed for ${objectApiName}: ${result.message ?? 'unknown error'}`);
|
|
961
|
+
return undefined;
|
|
962
|
+
}
|
|
963
|
+
return result.data.records.length > 0;
|
|
964
|
+
}
|
|
965
|
+
catch (error) {
|
|
966
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
967
|
+
this.logger?.log(`Record presence probe exception for ${objectApiName}: ${errorMessage}`);
|
|
968
|
+
return undefined;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Stage-2 authoritative `SELECT COUNT()` for a probe-positive suspect (CLI-4214).
|
|
973
|
+
* Uses the query builder `.count()` so the total arrives in `totalSize`. Never throws.
|
|
974
|
+
*
|
|
975
|
+
* @param objectApiName - The object API name to count
|
|
976
|
+
* @returns The authoritative record count, or undefined when the count query failed
|
|
977
|
+
*/
|
|
978
|
+
async countObjectRecords(objectApiName) {
|
|
979
|
+
const soql = CuneiformQueryBuilder.create().count().from(objectApiName).toSOQL();
|
|
980
|
+
try {
|
|
981
|
+
const result = await this.soqlAdapter.query(soql);
|
|
982
|
+
if (!result.success) {
|
|
983
|
+
this.logger?.log(`Record count query failed for ${objectApiName}: ${result.message ?? 'unknown error'}`);
|
|
984
|
+
return undefined;
|
|
985
|
+
}
|
|
986
|
+
return result.data.totalSize;
|
|
987
|
+
}
|
|
988
|
+
catch (error) {
|
|
989
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
990
|
+
this.logger?.log(`Record count exception for ${objectApiName}: ${errorMessage}`);
|
|
991
|
+
return undefined;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Applies hasRecordTypes filter by querying active record types.
|
|
996
|
+
* Uses lazy-loaded cache for record type information.
|
|
997
|
+
*/
|
|
998
|
+
async applyHasRecordTypesFilter(objects) {
|
|
999
|
+
const recordTypeObjects = await this.getRecordTypeObjects();
|
|
1000
|
+
return objects.filter((obj) => recordTypeObjects.has(obj.name)).map((obj) => ({ ...obj, hasRecordTypes: true }));
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Lazily loads and caches the set of object names that have active record types.
|
|
1004
|
+
*
|
|
1005
|
+
* @returns Set of object API names with active record types
|
|
1006
|
+
*/
|
|
1007
|
+
async getRecordTypeObjects() {
|
|
1008
|
+
if (this.recordTypeCache !== null) {
|
|
1009
|
+
return this.recordTypeCache;
|
|
1010
|
+
}
|
|
1011
|
+
const soql = CuneiformQueryBuilder.create()
|
|
1012
|
+
.select(['SobjectType'])
|
|
1013
|
+
.from('RecordType')
|
|
1014
|
+
.where('IsActive', '=', true)
|
|
1015
|
+
.groupBy(['SobjectType'])
|
|
1016
|
+
.toSOQL();
|
|
1017
|
+
const result = await this.soqlAdapter.query(soql);
|
|
1018
|
+
if (result.success) {
|
|
1019
|
+
this.recordTypeCache = new Set(result.data.records.map((r) => r.SobjectType));
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
this.logger?.log(`Record type query failed: ${result.message ?? 'unknown error'}`);
|
|
1023
|
+
this.recordTypeCache = new Set();
|
|
1024
|
+
}
|
|
1025
|
+
return this.recordTypeCache;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Applies withOwner filter by checking if objects have an OwnerId field.
|
|
1029
|
+
* Uses cached describe results for efficiency.
|
|
1030
|
+
*/
|
|
1031
|
+
async applyWithOwnerFilter(objects) {
|
|
1032
|
+
const results = [];
|
|
1033
|
+
// Check owner fields in parallel
|
|
1034
|
+
const ownerPromises = objects.map(async (obj) => {
|
|
1035
|
+
const hasOwner = await this.hasOwnerField(obj.name);
|
|
1036
|
+
if (hasOwner) {
|
|
1037
|
+
return { ...obj, hasOwner: true };
|
|
1038
|
+
}
|
|
1039
|
+
return null;
|
|
1040
|
+
});
|
|
1041
|
+
const resolved = await Promise.all(ownerPromises);
|
|
1042
|
+
for (const item of resolved) {
|
|
1043
|
+
if (item !== null) {
|
|
1044
|
+
results.push(item);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return results;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Checks if an object has an OwnerId field.
|
|
1051
|
+
* Results are cached per-session for efficiency.
|
|
1052
|
+
*
|
|
1053
|
+
* @param objectName - The object API name to check
|
|
1054
|
+
* @returns True if the object has an OwnerId field
|
|
1055
|
+
*/
|
|
1056
|
+
async hasOwnerField(objectName) {
|
|
1057
|
+
// Check cache first
|
|
1058
|
+
if (this.ownerFieldCache.has(objectName)) {
|
|
1059
|
+
return this.ownerFieldCache.get(objectName);
|
|
1060
|
+
}
|
|
1061
|
+
try {
|
|
1062
|
+
const describeResult = await this.restApiAdapter.describeObject(objectName);
|
|
1063
|
+
if (!describeResult.success) {
|
|
1064
|
+
this.logger?.log(`Describe failed for ${objectName}: ${describeResult.message ?? 'unknown error'}`);
|
|
1065
|
+
this.ownerFieldCache.set(objectName, false);
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
const hasOwner = describeResult.data.fields.some((field) => field.name === 'OwnerId');
|
|
1069
|
+
this.ownerFieldCache.set(objectName, hasOwner);
|
|
1070
|
+
return hasOwner;
|
|
1071
|
+
}
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1074
|
+
this.logger?.log(`Error checking owner field for ${objectName}: ${errorMessage}`);
|
|
1075
|
+
this.ownerFieldCache.set(objectName, false);
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
//# sourceMappingURL=ObjectFilteringService.js.map
|