@peernova/cuneiform-sf 1.0.2 → 1.0.4-beta.8
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 +59 -95
- 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/index.d.ts +33 -0
- package/lib/adapters/index.js +50 -0
- package/lib/adapters/index.js.map +1 -0
- package/lib/adapters/lifecycle.d.ts +119 -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 +389 -0
- package/lib/adapters/rest/rest-api-adapter.js +747 -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 +391 -0
- package/lib/adapters/soql/cuneiform-query-builder.js +559 -0
- package/lib/adapters/soql/cuneiform-query-builder.js.map +1 -0
- package/lib/adapters/soql/index.d.ts +13 -0
- package/lib/adapters/soql/index.js +21 -0
- package/lib/adapters/soql/index.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 +152 -0
- package/lib/base/cuneiform-command.js +243 -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 +119 -0
- package/lib/commands/cuneiform/definition/create.js +693 -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 +270 -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 +344 -0
- package/lib/commands/cuneiform/definition/list.js.map +1 -0
- package/lib/commands/cuneiform/definition/purge.d.ts +105 -0
- package/lib/commands/cuneiform/definition/purge.js +533 -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 +206 -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 +111 -0
- package/lib/commands/cuneiform/object/list.js +239 -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 +90 -0
- package/lib/commands/cuneiform/profile.js +322 -0
- package/lib/commands/cuneiform/profile.js.map +1 -0
- package/lib/commands/cuneiform/summary/purge.d.ts +77 -0
- package/lib/commands/cuneiform/summary/purge.js +429 -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 +73 -0
- package/lib/commands/cuneiform/user/details.js +391 -0
- package/lib/commands/cuneiform/user/details.js.map +1 -0
- package/lib/constants/index.d.ts +8 -0
- package/lib/constants/index.js +16 -0
- package/lib/constants/index.js.map +1 -0
- package/lib/constants/namespace-constants.d.ts +91 -0
- package/lib/constants/namespace-constants.js +211 -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/index.d.ts +12 -0
- package/lib/debug/index.js +20 -0
- package/lib/debug/index.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 +302 -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 +199 -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 +306 -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/date-literal.d.ts +211 -0
- package/lib/models/date-literal.js +615 -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/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 +53 -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 +163 -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 +411 -0
- package/lib/operations/DefinitionCreateOperation.js +1121 -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 +199 -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 +187 -0
- package/lib/operations/ProfileOperation.js +373 -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 +85 -0
- package/lib/services/ConfigureMode.js +390 -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 +309 -0
- package/lib/services/DefinitionFieldGenerationService.js +795 -0
- package/lib/services/DefinitionFieldGenerationService.js.map +1 -0
- package/lib/services/DefinitionQueryBuilder.d.ts +59 -0
- package/lib/services/DefinitionQueryBuilder.js +234 -0
- package/lib/services/DefinitionQueryBuilder.js.map +1 -0
- package/lib/services/ObjectDescribeService.d.ts +436 -0
- package/lib/services/ObjectDescribeService.js +869 -0
- package/lib/services/ObjectDescribeService.js.map +1 -0
- package/lib/services/ObjectFilteringService.d.ts +400 -0
- package/lib/services/ObjectFilteringService.js +878 -0
- package/lib/services/ObjectFilteringService.js.map +1 -0
- package/lib/services/ObjectListCommandService.d.ts +429 -0
- package/lib/services/ObjectListCommandService.js +873 -0
- package/lib/services/ObjectListCommandService.js.map +1 -0
- package/lib/services/ObjectListService.d.ts +201 -0
- package/lib/services/ObjectListService.js +345 -0
- package/lib/services/ObjectListService.js.map +1 -0
- package/lib/services/OrgInfoService.d.ts +485 -0
- package/lib/services/OrgInfoService.js +1122 -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 +535 -0
- package/lib/services/ProfilingDefinitionService.js +981 -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 +685 -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 +133 -0
- package/lib/services/SFDMUService.js +295 -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 +155 -0
- package/lib/services/UserConfigurationService.js +573 -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 +330 -0
- package/lib/services/UserReadinessService.js +831 -0
- package/lib/services/UserReadinessService.js.map +1 -0
- package/lib/services/constants.d.ts +53 -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/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 +231 -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 +114 -0
- package/lib/utils/formatting/object-describe-display.js +440 -0
- package/lib/utils/formatting/object-describe-display.js.map +1 -0
- package/lib/utils/formatting/object-list-display.d.ts +213 -0
- package/lib/utils/formatting/object-list-display.js +672 -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/index.d.ts +11 -0
- package/lib/utils/pagination/index.js +18 -0
- package/lib/utils/pagination/index.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 +511 -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 +318 -0
- package/messages/definition.update.md +118 -0
- package/messages/mcp.serve.md +66 -0
- package/messages/object.describe.md +201 -0
- package/messages/object.list.md +443 -0
- package/messages/org.details.md +386 -0
- package/messages/org.reset.md +71 -0
- package/messages/profile.md +231 -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 +2887 -2149
- package/oclif.manifest.json +2813 -31
- package/package.json +94 -19
- 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,878 @@
|
|
|
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 { validateNamespace, validateNonEmptyArray } from './validation.js';
|
|
13
|
+
import { MAX_OBJECT_NAMES } from './constants.js';
|
|
14
|
+
/** Default number of objects to process concurrently in checkDataExistsInRange. */
|
|
15
|
+
const DEFAULT_CONCURRENCY_LIMIT = 5;
|
|
16
|
+
/** Valid type filter values */
|
|
17
|
+
const VALID_TYPES = new Set(['standard', 'custom', 'all']);
|
|
18
|
+
/** Valid name filter operators */
|
|
19
|
+
const VALID_OPERATORS = new Set(['startsWith', 'contains', 'equals', 'wildcard']);
|
|
20
|
+
/** Valid classification values */
|
|
21
|
+
const VALID_CLASSIFICATIONS = new Set(['customer', 'internal', 'all']);
|
|
22
|
+
/** Namespace format pattern: starts with letter, alphanumeric only */
|
|
23
|
+
const NAMESPACE_PATTERN = /^[a-zA-Z][a-zA-Z0-9]*$/;
|
|
24
|
+
/**
|
|
25
|
+
* Object API name suffixes that indicate non-data objects which are never valid
|
|
26
|
+
* profiling targets: `__mdt` (Custom Metadata Types — configuration, not data),
|
|
27
|
+
* `__History` (change-tracking auxiliary), `__Share` (sharing-rule auxiliary),
|
|
28
|
+
* `__Feed` (Chatter feed auxiliary), `__ChangeEvent` (Change Data Capture event),
|
|
29
|
+
* `__e` (Platform Events — no persistent records).
|
|
30
|
+
*
|
|
31
|
+
* Big Objects (`__b`) and External Objects (`__x`) are intentionally excluded
|
|
32
|
+
* from this list — they hold queryable records and remain valid profiling
|
|
33
|
+
* targets, gated by the layoutable/keyPrefix predicate alone.
|
|
34
|
+
*
|
|
35
|
+
* The layoutable + keyPrefix predicate alone is insufficient: in some orgs
|
|
36
|
+
* Custom Metadata Types report `IsLayoutable=true` and a non-null KeyPrefix
|
|
37
|
+
* (e.g., m00) and end up in the `customer` EntityDefinition set — see CLI-3085.
|
|
38
|
+
* The ISV REST `filter-objects` endpoint does not honor `excludeSystemObjects`
|
|
39
|
+
* for `__mdt` either, so the CLI applies this name-based safety filter on both
|
|
40
|
+
* the local SOQL path and the REST response.
|
|
41
|
+
*/
|
|
42
|
+
const NON_DATA_SUFFIX_PATTERN = /__(mdt|History|Share|Feed|ChangeEvent|e)$/;
|
|
43
|
+
/**
|
|
44
|
+
* Domain service for filtering and classifying Salesforce objects.
|
|
45
|
+
*
|
|
46
|
+
* Provides methods to filter objects by type, namespace, classification,
|
|
47
|
+
* record counts, and record types. Uses describeGlobal for metadata,
|
|
48
|
+
* EntityDefinition SOQL for customer/internal classification, and
|
|
49
|
+
* lazy-loads record type information via SOQL.
|
|
50
|
+
*
|
|
51
|
+
* @design
|
|
52
|
+
* **File Size**: This file is ~630 lines, larger than the ideal 200-400 line target.
|
|
53
|
+
* This is intentional: all filter logic, validation, and transformation code is
|
|
54
|
+
* cohesive and related. Splitting into multiple files would force readers to jump
|
|
55
|
+
* between files to understand the filter chain, hurting confidence and clarity.
|
|
56
|
+
* Per Human-Centered Code Principles, "Confidence > Maintainability" — a cohesive
|
|
57
|
+
* file beats scattered fragments.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* const service = new ObjectFilteringService({
|
|
62
|
+
* restApiAdapter,
|
|
63
|
+
* soqlAdapter,
|
|
64
|
+
* logger: console,
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* // Filter custom objects with records
|
|
68
|
+
* const result = await service.filter({
|
|
69
|
+
* type: 'custom',
|
|
70
|
+
* withRecords: true,
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* if (result.success) {
|
|
74
|
+
* for (const obj of result.data) {
|
|
75
|
+
* console.log(`${obj.name}: ${obj.recordCount} records`);
|
|
76
|
+
* }
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export class ObjectFilteringService {
|
|
81
|
+
restApiAdapter;
|
|
82
|
+
soqlAdapter;
|
|
83
|
+
concurrencyLimit;
|
|
84
|
+
logger;
|
|
85
|
+
restClient;
|
|
86
|
+
/** Lazily loaded set of object names that have active record types */
|
|
87
|
+
recordTypeCache = null;
|
|
88
|
+
/** Cache of object names that have an OwnerId field (per-session) */
|
|
89
|
+
ownerFieldCache = new Map();
|
|
90
|
+
/** Lazily loaded set of customer-facing object names from EntityDefinition (per-session) */
|
|
91
|
+
customerObjectCache = null;
|
|
92
|
+
/**
|
|
93
|
+
* Creates a new ObjectFilteringService.
|
|
94
|
+
*
|
|
95
|
+
* @param config - Service configuration with required adapters
|
|
96
|
+
*/
|
|
97
|
+
constructor(config) {
|
|
98
|
+
this.restApiAdapter = config.restApiAdapter;
|
|
99
|
+
this.soqlAdapter = config.soqlAdapter;
|
|
100
|
+
this.concurrencyLimit = config.concurrencyLimit ?? DEFAULT_CONCURRENCY_LIMIT;
|
|
101
|
+
this.logger = config.logger;
|
|
102
|
+
this.restClient = config.restClient;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Validates filter options and returns a failure result if invalid.
|
|
106
|
+
*
|
|
107
|
+
* @param options - The filter options to validate
|
|
108
|
+
* @returns A failure ServiceResult if validation fails, undefined otherwise
|
|
109
|
+
*/
|
|
110
|
+
static validateFilterOptions(options) {
|
|
111
|
+
// Validate type
|
|
112
|
+
if (options.type !== undefined && !VALID_TYPES.has(options.type)) {
|
|
113
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_TYPE, `Invalid type parameter: "${options.type}". Must be 'standard', 'custom', or 'all'.`);
|
|
114
|
+
}
|
|
115
|
+
// Validate namespace (only if it's a non-null string)
|
|
116
|
+
if (options.namespace !== undefined && options.namespace !== null) {
|
|
117
|
+
if (!NAMESPACE_PATTERN.test(options.namespace)) {
|
|
118
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_NAMESPACE, `Invalid namespace format: "${options.namespace}". Must start with a letter and contain only letters and numbers.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Validate name filter
|
|
122
|
+
if (options.nameFilter) {
|
|
123
|
+
if (!options.nameFilter.value || options.nameFilter.value.trim().length === 0) {
|
|
124
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_NAME_FILTER, 'Name filter value is required and cannot be empty.');
|
|
125
|
+
}
|
|
126
|
+
if (!VALID_OPERATORS.has(options.nameFilter.operator)) {
|
|
127
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_NAME_FILTER, `Invalid name filter operator: "${options.nameFilter.operator}". Must be 'startsWith', 'contains', 'equals', or 'wildcard'.`);
|
|
128
|
+
}
|
|
129
|
+
// For wildcard operator, validate that pattern contains at least one *
|
|
130
|
+
if (options.nameFilter.operator === 'wildcard' && !options.nameFilter.value.includes('*')) {
|
|
131
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_NAME_FILTER, `Wildcard pattern must contain at least one '*' character. Got: "${options.nameFilter.value}".`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Validate classification
|
|
135
|
+
if (options.classification !== undefined && !VALID_CLASSIFICATIONS.has(options.classification)) {
|
|
136
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_CLASSIFICATION, `Invalid classification: "${options.classification}". Must be 'customer', 'internal', or 'all'.`);
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Determines whether an object is custom.
|
|
142
|
+
* Custom object detection: name ends with __c OR custom === true.
|
|
143
|
+
*
|
|
144
|
+
* @param obj - The global describe object to check
|
|
145
|
+
* @returns True if the object is custom
|
|
146
|
+
*/
|
|
147
|
+
static isCustomObject(obj) {
|
|
148
|
+
return obj.custom || obj.name.endsWith('__c');
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Extracts namespace from an object API name.
|
|
152
|
+
* Namespaced objects follow the pattern: namespace__ObjectName__c
|
|
153
|
+
*
|
|
154
|
+
* @param name - The object API name
|
|
155
|
+
* @returns The namespace prefix, or undefined if unmanaged
|
|
156
|
+
*/
|
|
157
|
+
static extractNamespace(name) {
|
|
158
|
+
// Namespace pattern: at least two double-underscore segments
|
|
159
|
+
// e.g., ns__Object__c -> ns, ns__Object -> ns
|
|
160
|
+
// Standard objects and non-namespaced custom objects have at most one __
|
|
161
|
+
const parts = name.split('__');
|
|
162
|
+
if (parts.length >= 3) {
|
|
163
|
+
// ns__Object__c or ns__Object__suffix
|
|
164
|
+
return parts[0];
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Converts a global describe result to SObjectInfo.
|
|
170
|
+
*
|
|
171
|
+
* @param obj - The global describe object
|
|
172
|
+
* @returns SObjectInfo representation
|
|
173
|
+
*/
|
|
174
|
+
static toSObjectInfo(obj) {
|
|
175
|
+
const info = {
|
|
176
|
+
name: obj.name,
|
|
177
|
+
label: obj.label,
|
|
178
|
+
isCustom: ObjectFilteringService.isCustomObject(obj),
|
|
179
|
+
};
|
|
180
|
+
const namespace = ObjectFilteringService.extractNamespace(obj.name);
|
|
181
|
+
if (namespace) {
|
|
182
|
+
info.namespace = namespace;
|
|
183
|
+
}
|
|
184
|
+
return info;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Applies type filter to objects.
|
|
188
|
+
*/
|
|
189
|
+
static applyTypeFilter(objects, type) {
|
|
190
|
+
if (type === 'custom') {
|
|
191
|
+
return objects.filter((obj) => ObjectFilteringService.isCustomObject(obj));
|
|
192
|
+
}
|
|
193
|
+
// standard
|
|
194
|
+
return objects.filter((obj) => !ObjectFilteringService.isCustomObject(obj));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Applies namespace filter to objects.
|
|
198
|
+
*/
|
|
199
|
+
static applyNamespaceFilter(objects, namespace) {
|
|
200
|
+
if (namespace === null) {
|
|
201
|
+
// null means unmanaged -- no namespace
|
|
202
|
+
return objects.filter((obj) => !ObjectFilteringService.extractNamespace(obj.name));
|
|
203
|
+
}
|
|
204
|
+
// Match specific namespace (case-insensitive)
|
|
205
|
+
// Salesforce namespaces are case-insensitive in org references and API calls
|
|
206
|
+
const lowerNamespace = namespace.toLowerCase();
|
|
207
|
+
return objects.filter((obj) => {
|
|
208
|
+
const ns = ObjectFilteringService.extractNamespace(obj.name);
|
|
209
|
+
return ns !== undefined && ns.toLowerCase() === lowerNamespace;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Converts a wildcard pattern (with *) to a regular expression.
|
|
214
|
+
* The * character matches any sequence of characters.
|
|
215
|
+
*
|
|
216
|
+
* @param pattern - The wildcard pattern (e.g., "Account*", "*__c", "*Contact*")
|
|
217
|
+
* @returns A RegExp that matches the pattern case-insensitively
|
|
218
|
+
*/
|
|
219
|
+
static wildcardToRegex(pattern) {
|
|
220
|
+
// Escape special regex characters except *
|
|
221
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
222
|
+
// Convert * to .* (match any characters)
|
|
223
|
+
const regexPattern = escaped.replace(/\*/g, '.*');
|
|
224
|
+
// Anchor the pattern and make it case-insensitive
|
|
225
|
+
return new RegExp(`^${regexPattern}$`, 'i');
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Applies name filter to objects (case-insensitive).
|
|
229
|
+
*/
|
|
230
|
+
static applyNameFilter(objects, nameFilter) {
|
|
231
|
+
const lowerValue = nameFilter.value.toLowerCase();
|
|
232
|
+
// Pre-compile regex for wildcard operator
|
|
233
|
+
const wildcardRegex = nameFilter.operator === 'wildcard' ? ObjectFilteringService.wildcardToRegex(nameFilter.value) : null;
|
|
234
|
+
return objects.filter((obj) => {
|
|
235
|
+
const lowerName = obj.name.toLowerCase();
|
|
236
|
+
switch (nameFilter.operator) {
|
|
237
|
+
case 'startsWith':
|
|
238
|
+
return lowerName.startsWith(lowerValue);
|
|
239
|
+
case 'contains':
|
|
240
|
+
return lowerName.includes(lowerValue);
|
|
241
|
+
case 'equals':
|
|
242
|
+
return lowerName === lowerValue;
|
|
243
|
+
case 'wildcard':
|
|
244
|
+
return wildcardRegex.test(obj.name);
|
|
245
|
+
default:
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Applies system object exclusion. Excludes objects that are not valid profiling
|
|
252
|
+
* targets: layoutable === false OR keyPrefix === null (auxiliary/system objects),
|
|
253
|
+
* OR name matches NON_DATA_SUFFIX_PATTERN (CLI-3085: Salesforce reports
|
|
254
|
+
* IsLayoutable=true and a non-null KeyPrefix for Custom Metadata Types in some
|
|
255
|
+
* orgs, so the layoutable/keyPrefix predicate alone is insufficient).
|
|
256
|
+
*/
|
|
257
|
+
static applySystemObjectExclusion(objects) {
|
|
258
|
+
return objects.filter((obj) => {
|
|
259
|
+
const layoutable = obj.layoutable ?? true;
|
|
260
|
+
if (!layoutable || obj.keyPrefix === null)
|
|
261
|
+
return false;
|
|
262
|
+
if (NON_DATA_SUFFIX_PATTERN.test(obj.name))
|
|
263
|
+
return false;
|
|
264
|
+
return true;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Predicate filter: keep only objects with `recordCount > 0`.
|
|
269
|
+
*
|
|
270
|
+
* Operates on already-populated counts produced by {@link populateRecordCounts};
|
|
271
|
+
* does not perform any API calls. Objects whose count was not populated (undefined
|
|
272
|
+
* recordCount, e.g. when the count query failed) are excluded — only positively
|
|
273
|
+
* confirmed non-zero counts pass.
|
|
274
|
+
*/
|
|
275
|
+
static applyWithRecordsFilter(objects) {
|
|
276
|
+
return objects.filter((obj) => (obj.recordCount ?? 0) > 0);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Predicate filter: keep only objects with `recordCount === 0`.
|
|
280
|
+
*
|
|
281
|
+
* Operates on already-populated counts produced by {@link populateRecordCounts};
|
|
282
|
+
* does not perform any API calls. Objects whose count was not populated (undefined
|
|
283
|
+
* recordCount) are excluded — only positively confirmed zero counts pass.
|
|
284
|
+
*/
|
|
285
|
+
static applyWithoutRecordsFilter(objects) {
|
|
286
|
+
return objects.filter((obj) => obj.recordCount === 0);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Applies withRecords or withoutRecords locally after REST delegation.
|
|
290
|
+
* Called when the Apex endpoint has returned a candidate list that still needs
|
|
291
|
+
* record-count filtering — the server intentionally does not do this step.
|
|
292
|
+
*/
|
|
293
|
+
static applyPostRestRecordCountFilter(results, options) {
|
|
294
|
+
if (options.withRecords) {
|
|
295
|
+
return ObjectFilteringService.applyWithRecordsFilter(results);
|
|
296
|
+
}
|
|
297
|
+
if (options.withoutRecords) {
|
|
298
|
+
return ObjectFilteringService.applyWithoutRecordsFilter(results);
|
|
299
|
+
}
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Excludes Custom Settings (list and hierarchy) from describe results.
|
|
304
|
+
* Custom Settings are configuration objects, not business data objects.
|
|
305
|
+
* Protected Custom Settings (visibility=Protected) must never be visible to subscribers.
|
|
306
|
+
*/
|
|
307
|
+
static applyCustomSettingExclusion(objects) {
|
|
308
|
+
return objects.filter((obj) => !obj.customSetting);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Filters Salesforce objects based on combined filter criteria.
|
|
312
|
+
*
|
|
313
|
+
* Applies filters in order from cheapest to most expensive:
|
|
314
|
+
* type -> namespace -> nameFilter -> classification -> excludeSystemObjects -> customSettings -> withRecords -> hasRecordTypes
|
|
315
|
+
*
|
|
316
|
+
* @param options - Filter criteria to apply
|
|
317
|
+
* @returns ServiceResult containing filtered SObjectInfo array
|
|
318
|
+
*/
|
|
319
|
+
async filter(options) {
|
|
320
|
+
const startTime = Date.now();
|
|
321
|
+
try {
|
|
322
|
+
// Validate inputs
|
|
323
|
+
const validationError = ObjectFilteringService.validateFilterOptions(options);
|
|
324
|
+
if (validationError) {
|
|
325
|
+
return validationError;
|
|
326
|
+
}
|
|
327
|
+
// REST API path: delegate metadata filtering to the server when restClient is available.
|
|
328
|
+
// withRecords/withoutRecords are intentionally NOT delegated — the server-side path uses
|
|
329
|
+
// per-object SOQL COUNT() queries that exhaust the governor budget on large orgs, causing
|
|
330
|
+
// objects without records to slip through (CLI-3083). The CLI applies them locally via
|
|
331
|
+
// GET /limits/recordCount (single call, no SOQL cost). Falls back to the local path on
|
|
332
|
+
// failure (e.g., endpoint not deployed).
|
|
333
|
+
const restFallbackResult = await this.tryRestDelegation(options, startTime);
|
|
334
|
+
if (restFallbackResult) {
|
|
335
|
+
const restResults = ObjectFilteringService.applyPostRestRecordCountFilter(restFallbackResult.data, options);
|
|
336
|
+
return createSuccessResult(restResults, {
|
|
337
|
+
message: `Found ${restResults.length} objects matching filter criteria`,
|
|
338
|
+
metadata: { duration: Date.now() - startTime, strategyUsed: 'rest-api' },
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
// Fetch global describe (local path)
|
|
342
|
+
const globalResult = await this.restApiAdapter.describeGlobal();
|
|
343
|
+
if (!globalResult.success) {
|
|
344
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, globalResult.message ?? 'Global describe failed', {
|
|
345
|
+
metadata: { duration: Date.now() - startTime },
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
let sobjects = globalResult.data.sobjects;
|
|
349
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
350
|
+
// Filter Application Order (cheapest → most expensive)
|
|
351
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
352
|
+
// Filters are deliberately ordered to minimize work:
|
|
353
|
+
// 0. non-profileable — drop non-queryable objects (mirrors server-side
|
|
354
|
+
// applyNonProfileableFilters in pnova/ObjectFilterRestService.cls).
|
|
355
|
+
// Non-queryable objects (FeedTrackedChange, etc.) cannot be profiled,
|
|
356
|
+
// so they must never appear in any classification bucket. Without
|
|
357
|
+
// this gate, the local fallback returned a superset of the REST path
|
|
358
|
+
// for `--classification customer|internal` (CLI-3310).
|
|
359
|
+
// 1. type — in-memory flag check (instant)
|
|
360
|
+
// 2. namespace — string parsing (instant)
|
|
361
|
+
// 3. nameFilter — string comparison (instant)
|
|
362
|
+
// 4. classification — 1 SOQL query (cached after first call)
|
|
363
|
+
// 5. excludeSystem — in-memory flag check (instant)
|
|
364
|
+
// 6. customSettings — in-memory flag check (instant, uses describe already fetched)
|
|
365
|
+
// 7. populate counts — 1 bulk REST call against the bounded result set.
|
|
366
|
+
// The Records column is part of the documented output contract — counts
|
|
367
|
+
// must be queried unconditionally so that "0" never doubles as a placeholder
|
|
368
|
+
// for "we didn't ask". `withRecords` / `withoutRecords` are predicates,
|
|
369
|
+
// not population gates (CLI-3077).
|
|
370
|
+
// 8. withRecords — predicate against populated counts (no API call)
|
|
371
|
+
// 9. withoutRecords — predicate against populated counts (no API call)
|
|
372
|
+
// 10. hasRecordTypes — 1 SOQL query (cached after first call)
|
|
373
|
+
// 11. withOwner — N describe calls (1 per uncached object)
|
|
374
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
375
|
+
// 0. Non-profileable exclusion — match server's applyNonProfileableFilters.
|
|
376
|
+
// Use !== false so that objects where queryable is absent in the API
|
|
377
|
+
// response are kept rather than silently dropped.
|
|
378
|
+
sobjects = sobjects.filter((obj) => obj.queryable !== false);
|
|
379
|
+
// 1. Type filter (instant)
|
|
380
|
+
if (options.type && options.type !== 'all') {
|
|
381
|
+
sobjects = ObjectFilteringService.applyTypeFilter(sobjects, options.type);
|
|
382
|
+
}
|
|
383
|
+
// 2. Namespace filter (instant)
|
|
384
|
+
if (options.namespace !== undefined) {
|
|
385
|
+
sobjects = ObjectFilteringService.applyNamespaceFilter(sobjects, options.namespace);
|
|
386
|
+
}
|
|
387
|
+
// 3. Name filter (instant)
|
|
388
|
+
if (options.nameFilter) {
|
|
389
|
+
sobjects = ObjectFilteringService.applyNameFilter(sobjects, options.nameFilter);
|
|
390
|
+
}
|
|
391
|
+
// 4. Classification filter (1 SOQL query, cached after first call)
|
|
392
|
+
if (options.classification && options.classification !== 'all') {
|
|
393
|
+
sobjects = await this.applyClassificationFilter(sobjects, options.classification);
|
|
394
|
+
}
|
|
395
|
+
// 5. System object exclusion (instant)
|
|
396
|
+
if (options.excludeSystemObjects) {
|
|
397
|
+
sobjects = ObjectFilteringService.applySystemObjectExclusion(sobjects);
|
|
398
|
+
}
|
|
399
|
+
// 6. Custom Setting exclusion (instant — flag already present in global describe)
|
|
400
|
+
sobjects = ObjectFilteringService.applyCustomSettingExclusion(sobjects);
|
|
401
|
+
// Convert to SObjectInfo before expensive operations
|
|
402
|
+
let results = sobjects.map((obj) => ObjectFilteringService.toSObjectInfo(obj));
|
|
403
|
+
// Populate isCustomer classification if EntityDefinition cache is available
|
|
404
|
+
if (this.customerObjectCache !== null) {
|
|
405
|
+
results = results.map((obj) => ({ ...obj, isCustomer: this.customerObjectCache.has(obj.name) }));
|
|
406
|
+
}
|
|
407
|
+
// 6. Populate recordCount unconditionally (single bulk REST call). When the
|
|
408
|
+
// caller asked to filter by record count, the populate step must succeed —
|
|
409
|
+
// we can't honestly say "objects with records" if we don't know counts.
|
|
410
|
+
const countsRequired = options.withRecords === true || options.withoutRecords === true;
|
|
411
|
+
results = await this.populateRecordCounts(results, countsRequired);
|
|
412
|
+
// 7. withRecords filter (predicate against populated counts)
|
|
413
|
+
if (options.withRecords) {
|
|
414
|
+
results = ObjectFilteringService.applyWithRecordsFilter(results);
|
|
415
|
+
}
|
|
416
|
+
// 8. withoutRecords filter (predicate against populated counts)
|
|
417
|
+
if (options.withoutRecords) {
|
|
418
|
+
results = ObjectFilteringService.applyWithoutRecordsFilter(results);
|
|
419
|
+
}
|
|
420
|
+
// 9. hasRecordTypes filter (expensive — 1 SOQL query, cached after first call)
|
|
421
|
+
if (options.hasRecordTypes) {
|
|
422
|
+
results = await this.applyHasRecordTypesFilter(results);
|
|
423
|
+
}
|
|
424
|
+
// 10. withOwner filter (expensive — N API calls, 1 describe per uncached object)
|
|
425
|
+
if (options.withOwner) {
|
|
426
|
+
results = await this.applyWithOwnerFilter(results);
|
|
427
|
+
}
|
|
428
|
+
const duration = Date.now() - startTime;
|
|
429
|
+
this.logger?.log(`Filter returned ${results.length} objects in ${duration}ms`);
|
|
430
|
+
return createSuccessResult(results, {
|
|
431
|
+
message: `Found ${results.length} objects matching filter criteria`,
|
|
432
|
+
metadata: { duration },
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
437
|
+
this.logger?.log(`Filter failed: ${errorMessage}`);
|
|
438
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, errorMessage, {
|
|
439
|
+
metadata: { duration: Date.now() - startTime },
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Filters objects by type (standard or custom).
|
|
445
|
+
*
|
|
446
|
+
* @param type - The object type to filter by
|
|
447
|
+
* @returns ServiceResult containing filtered SObjectInfo array
|
|
448
|
+
*/
|
|
449
|
+
async filterByType(type) {
|
|
450
|
+
if (!VALID_TYPES.has(type)) {
|
|
451
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_TYPE, `Invalid type parameter: "${type}". Must be 'standard', 'custom', or 'all'.`);
|
|
452
|
+
}
|
|
453
|
+
return this.filter({ type });
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Filters objects by namespace.
|
|
457
|
+
*
|
|
458
|
+
* @param namespace - The namespace to filter by, or null for unmanaged objects
|
|
459
|
+
* @returns ServiceResult containing filtered SObjectInfo array
|
|
460
|
+
*/
|
|
461
|
+
async filterByNamespace(namespace) {
|
|
462
|
+
if (namespace !== null) {
|
|
463
|
+
const error = validateNamespace(namespace);
|
|
464
|
+
if (error) {
|
|
465
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_NAMESPACE, error);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return this.filter({ namespace });
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Filters objects to only those with records (record count > 0).
|
|
472
|
+
*
|
|
473
|
+
* @returns ServiceResult containing filtered SObjectInfo array
|
|
474
|
+
*/
|
|
475
|
+
async filterWithRecords() {
|
|
476
|
+
return this.filter({ withRecords: true });
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Retrieves SObjectInfo for specific object names.
|
|
480
|
+
*
|
|
481
|
+
* Returns success with data for found objects and warnings for missing ones.
|
|
482
|
+
* If none are found, returns success with empty data and warnings.
|
|
483
|
+
*
|
|
484
|
+
* @param objectNames - Array of object API names to look up
|
|
485
|
+
* @returns ServiceResult containing found SObjectInfo array with warnings for missing
|
|
486
|
+
*/
|
|
487
|
+
async getObjectsByName(objectNames) {
|
|
488
|
+
const startTime = Date.now();
|
|
489
|
+
// Validate input
|
|
490
|
+
const validationError = validateNonEmptyArray(objectNames, 'objectNames', MAX_OBJECT_NAMES);
|
|
491
|
+
if (validationError) {
|
|
492
|
+
return createFailureResult([], ServiceErrorCodes.INVALID_OBJECT_NAMES, validationError);
|
|
493
|
+
}
|
|
494
|
+
try {
|
|
495
|
+
// Fetch global describe
|
|
496
|
+
const globalResult = await this.restApiAdapter.describeGlobal();
|
|
497
|
+
if (!globalResult.success) {
|
|
498
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, globalResult.message ?? 'Global describe failed', {
|
|
499
|
+
metadata: { duration: Date.now() - startTime },
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
// Build a lookup map (case-insensitive)
|
|
503
|
+
const objectMap = new Map();
|
|
504
|
+
for (const obj of globalResult.data.sobjects) {
|
|
505
|
+
objectMap.set(obj.name.toLowerCase(), obj);
|
|
506
|
+
}
|
|
507
|
+
const found = [];
|
|
508
|
+
const warnings = [];
|
|
509
|
+
for (const name of objectNames) {
|
|
510
|
+
const obj = objectMap.get(name.toLowerCase());
|
|
511
|
+
if (obj) {
|
|
512
|
+
found.push(ObjectFilteringService.toSObjectInfo(obj));
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
warnings.push(`Object not found: ${name}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const duration = Date.now() - startTime;
|
|
519
|
+
this.logger?.log(`getObjectsByName: found ${found.length}/${objectNames.length} objects in ${duration}ms`);
|
|
520
|
+
return createSuccessResult(found, {
|
|
521
|
+
message: `Found ${found.length} of ${objectNames.length} requested objects`,
|
|
522
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
523
|
+
metadata: { duration },
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
528
|
+
this.logger?.log(`getObjectsByName failed: ${errorMessage}`);
|
|
529
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, errorMessage, {
|
|
530
|
+
metadata: { duration: Date.now() - startTime },
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Retrieves record counts for the specified objects using the Record Count API.
|
|
536
|
+
*
|
|
537
|
+
* @param objectNames - Array of object API names to get counts for
|
|
538
|
+
* @returns ServiceResult containing a map of object name to record count
|
|
539
|
+
*/
|
|
540
|
+
async getRecordCounts(objectNames) {
|
|
541
|
+
return this.restApiAdapter.getRecordCounts(objectNames);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Checks whether data exists for each probe specification using LIMIT 1 SOQL probes.
|
|
545
|
+
*
|
|
546
|
+
* Returns a flat grid of per-probe boolean results. Each probe uses a
|
|
547
|
+
* `SELECT Id FROM {Object} WHERE CreatedDate >= {yearStart} AND CreatedDate < {yearEnd} LIMIT 1`
|
|
548
|
+
* query to determine data presence efficiently. When a probe includes a `recordType`,
|
|
549
|
+
* a `RecordType.DeveloperName = '{value}'` filter is added.
|
|
550
|
+
*
|
|
551
|
+
* Probes are grouped by object and processed one object at a time. All probes for a single
|
|
552
|
+
* object (across years and record types) run concurrently, then the next object's probes fire.
|
|
553
|
+
* This keeps concurrent SOQL queries scoped to a single object's probes.
|
|
554
|
+
*
|
|
555
|
+
* Failures on individual probes are captured per-entry (hasData=false, error set) rather
|
|
556
|
+
* than failing the entire operation — the grid is informational, not blocking.
|
|
557
|
+
*
|
|
558
|
+
* @param probes - Array of probe specifications to execute
|
|
559
|
+
* @returns ServiceResult containing the availability grid
|
|
560
|
+
*/
|
|
561
|
+
async checkDataExistsInRange(probes) {
|
|
562
|
+
const startTime = Date.now();
|
|
563
|
+
const grid = [];
|
|
564
|
+
if (probes.length === 0) {
|
|
565
|
+
return createSuccessResult(grid, {
|
|
566
|
+
message: 'No probes to check',
|
|
567
|
+
metadata: { duration: Date.now() - startTime },
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
// Group probes by object for per-object concurrent execution
|
|
571
|
+
const grouped = new Map();
|
|
572
|
+
for (const probe of probes) {
|
|
573
|
+
const existing = grouped.get(probe.objectApiName);
|
|
574
|
+
if (existing) {
|
|
575
|
+
existing.push(probe);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
grouped.set(probe.objectApiName, [probe]);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Process objects in batched parallel groups; probes within each object run concurrently
|
|
582
|
+
const groupedEntries = [...grouped.entries()];
|
|
583
|
+
const batchedResults = await processInBatches(groupedEntries, this.concurrencyLimit, async ([objectName, objectProbes]) => {
|
|
584
|
+
const results = await Promise.all(objectProbes.map((spec) => this.probeDataForYear(spec)));
|
|
585
|
+
this.logger?.log(`Data probes for ${objectName}: ${objectProbes.length} probes`);
|
|
586
|
+
return results;
|
|
587
|
+
});
|
|
588
|
+
for (const results of batchedResults) {
|
|
589
|
+
grid.push(...results);
|
|
590
|
+
}
|
|
591
|
+
const duration = Date.now() - startTime;
|
|
592
|
+
const gapsCount = grid.filter((e) => !e.hasData && !e.error).length;
|
|
593
|
+
const errorsCount = grid.filter((e) => e.error !== undefined).length;
|
|
594
|
+
this.logger?.log(`Data availability check: ${grid.length} probes, ${gapsCount} gaps, ${errorsCount} errors in ${duration}ms`);
|
|
595
|
+
return createSuccessResult(grid, {
|
|
596
|
+
message: `Checked ${grid.length} probe combinations`,
|
|
597
|
+
metadata: { duration },
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Probes a single object/year/recordType combination with a LIMIT 1 SOQL query.
|
|
602
|
+
* Returns a DataAvailabilityEntry — never throws.
|
|
603
|
+
*/
|
|
604
|
+
async probeDataForYear(spec) {
|
|
605
|
+
const { objectApiName, year, recordType } = spec;
|
|
606
|
+
const yearStart = `${year}-01-01T00:00:00Z`;
|
|
607
|
+
const yearEnd = `${year + 1}-01-01T00:00:00Z`;
|
|
608
|
+
const builder = CuneiformQueryBuilder.create().select(['Id']).from(objectApiName);
|
|
609
|
+
if (recordType) {
|
|
610
|
+
builder.where('RecordType.DeveloperName', '=', recordType);
|
|
611
|
+
builder.andWhereDatetime('CreatedDate', '>=', yearStart);
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
builder.whereDatetime('CreatedDate', '>=', yearStart);
|
|
615
|
+
}
|
|
616
|
+
const soql = builder.andWhereDatetime('CreatedDate', '<', yearEnd).limit(1).toSOQL();
|
|
617
|
+
const label = recordType ? `${objectApiName}/${recordType}/${year}` : `${objectApiName}/${year}`;
|
|
618
|
+
try {
|
|
619
|
+
const result = await this.soqlAdapter.query(soql);
|
|
620
|
+
if (!result.success) {
|
|
621
|
+
this.logger?.log(`Data probe failed for ${label}: ${result.message ?? 'unknown error'}`);
|
|
622
|
+
return { objectApiName, year, hasData: false, error: result.message ?? 'Query failed', recordType };
|
|
623
|
+
}
|
|
624
|
+
return { objectApiName, year, hasData: result.data.records.length > 0, recordType };
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
628
|
+
this.logger?.log(`Data probe exception for ${label}: ${errorMessage}`);
|
|
629
|
+
return { objectApiName, year, hasData: false, error: errorMessage, recordType };
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Attempts REST delegation for object filtering.
|
|
634
|
+
* Returns the successful result, or null to signal fallback to the local SOQL path.
|
|
635
|
+
*/
|
|
636
|
+
async tryRestDelegation(options, startTime) {
|
|
637
|
+
if (!this.restClient) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
const restResult = await this.filterViaRest(options, startTime);
|
|
641
|
+
if (restResult.success) {
|
|
642
|
+
return restResult;
|
|
643
|
+
}
|
|
644
|
+
this.logger?.log(`REST filter-objects failed (${restResult.message ?? 'unknown'}), falling back to local SOQL path`);
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Lazily loads and caches the set of customer-facing object names from EntityDefinition.
|
|
649
|
+
* Uses a SOQL query against EntityDefinition to classify objects based on Salesforce metadata
|
|
650
|
+
* rather than hardcoded exclusion lists.
|
|
651
|
+
*
|
|
652
|
+
* @returns Set of customer-facing object API names
|
|
653
|
+
*/
|
|
654
|
+
/**
|
|
655
|
+
* Delegates object filtering to the ISV REST API (filter-objects endpoint).
|
|
656
|
+
* Server handles the full cost-optimized filter chain.
|
|
657
|
+
*/
|
|
658
|
+
async filterViaRest(options, startTime) {
|
|
659
|
+
this.logger?.log('Delegating object filtering to ISV REST API (filter-objects)');
|
|
660
|
+
const restResult = await this.restClient.filterObjects({
|
|
661
|
+
type: options.type,
|
|
662
|
+
namespace: options.namespace === null ? '' : options.namespace,
|
|
663
|
+
excludeSystemObjects: options.excludeSystemObjects,
|
|
664
|
+
namePattern: options.nameFilter?.value,
|
|
665
|
+
nameOperator: options.nameFilter?.operator,
|
|
666
|
+
classification: options.classification,
|
|
667
|
+
hasRecordTypes: options.hasRecordTypes,
|
|
668
|
+
withOwner: options.withOwner,
|
|
669
|
+
});
|
|
670
|
+
if (!restResult.success) {
|
|
671
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, restResult.message ?? 'REST filter-objects failed', {
|
|
672
|
+
metadata: { duration: Date.now() - startTime },
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
const response = restResult.data;
|
|
676
|
+
if (!response.success) {
|
|
677
|
+
const errorMsg = response.errors?.length > 0 ? response.errors[0] : 'Server-side filtering failed';
|
|
678
|
+
return createFailureResult([], ServiceErrorCodes.FILTER_FAILED, errorMsg, {
|
|
679
|
+
metadata: { duration: Date.now() - startTime },
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
// CLI-3085: server-side filter-objects endpoint does not honor
|
|
683
|
+
// excludeSystemObjects for __mdt (and parity for History/Share/Feed/ChangeEvent
|
|
684
|
+
// /b/e/x suffixes is not guaranteed). Apply a name-based safety filter on the
|
|
685
|
+
// REST response when the caller requested system-object exclusion so the
|
|
686
|
+
// create flow stays tight regardless of server behavior.
|
|
687
|
+
// Support both `results` (plural) and `result` (singular) — different ISV
|
|
688
|
+
// endpoint versions use different keys.
|
|
689
|
+
let results = response.results ?? response.result ?? [];
|
|
690
|
+
if (options.excludeSystemObjects) {
|
|
691
|
+
const before = results.length;
|
|
692
|
+
results = results.filter((obj) => !NON_DATA_SUFFIX_PATTERN.test(obj.name));
|
|
693
|
+
if (results.length !== before) {
|
|
694
|
+
this.logger?.log(`REST post-filter excluded ${before - results.length} non-data objects (CLI-3085)`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
// CLI-3077: populate recordCount on the server response — older server builds
|
|
698
|
+
// skip count population unless withRecords/withoutRecords is set, which would
|
|
699
|
+
// otherwise surface as the "0 records" symptom. The server-side fix on
|
|
700
|
+
// packaging/v4.12.0 also populates unconditionally; this client-side step is
|
|
701
|
+
// defense-in-depth that lets the CLI ship independently of the package release.
|
|
702
|
+
const countsRequired = options.withRecords === true || options.withoutRecords === true;
|
|
703
|
+
const populated = results.some((obj) => obj.recordCount !== undefined && obj.recordCount !== null)
|
|
704
|
+
? results
|
|
705
|
+
: await this.populateRecordCounts(results, countsRequired);
|
|
706
|
+
return createSuccessResult(populated, {
|
|
707
|
+
message: `Filtered ${String(populated.length)} objects via REST API`,
|
|
708
|
+
metadata: { duration: Date.now() - startTime, strategyUsed: 'rest-api' },
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
async getCustomerObjects() {
|
|
712
|
+
if (this.customerObjectCache !== null) {
|
|
713
|
+
return this.customerObjectCache;
|
|
714
|
+
}
|
|
715
|
+
// EntityDefinition is a Setup object backed by metadata, not records. The
|
|
716
|
+
// platform returns it in batches and does NOT support queryMore() — the
|
|
717
|
+
// query MUST resolve in a single batch. Salesforce caps the EntityDefinition
|
|
718
|
+
// batch at 200 rows; using a larger LIMIT triggers the runtime error
|
|
719
|
+
// "EntityDefinition does not support queryMore()". 200 covers all practical
|
|
720
|
+
// orgs; objects beyond 200 customer-facing entities are classified as
|
|
721
|
+
// internal (false-negative is acceptable for the long-tail).
|
|
722
|
+
//
|
|
723
|
+
// Chained .where() calls must use andWhere() for subsequent conditions —
|
|
724
|
+
// .where() replaces the first condition each time it's called.
|
|
725
|
+
const soql = CuneiformQueryBuilder.create()
|
|
726
|
+
.select(['QualifiedApiName'])
|
|
727
|
+
.from('EntityDefinition')
|
|
728
|
+
.where('IsCustomizable', '=', true)
|
|
729
|
+
.andWhere('IsLayoutable', '=', true)
|
|
730
|
+
.andWhere('IsDeprecatedAndHidden', '=', false)
|
|
731
|
+
.limit(200)
|
|
732
|
+
.toSOQL();
|
|
733
|
+
const result = await this.soqlAdapter.query(soql);
|
|
734
|
+
if (result.success) {
|
|
735
|
+
this.customerObjectCache = new Set(result.data.records.map((r) => r.QualifiedApiName));
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
const errorMessage = `EntityDefinition classification query failed: ${result.message ?? 'unknown error'}`;
|
|
739
|
+
this.logger?.log(errorMessage);
|
|
740
|
+
throw new Error(errorMessage);
|
|
741
|
+
}
|
|
742
|
+
return this.customerObjectCache;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Applies classification filter to objects using EntityDefinition metadata.
|
|
746
|
+
* Customer-facing objects are those present in the EntityDefinition customer set.
|
|
747
|
+
* Internal objects are everything else.
|
|
748
|
+
*/
|
|
749
|
+
async applyClassificationFilter(objects, classification) {
|
|
750
|
+
const customerObjects = await this.getCustomerObjects();
|
|
751
|
+
if (classification === 'customer') {
|
|
752
|
+
return objects.filter((obj) => customerObjects.has(obj.name));
|
|
753
|
+
}
|
|
754
|
+
// internal: NOT in customer set
|
|
755
|
+
return objects.filter((obj) => !customerObjects.has(obj.name));
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Populates `recordCount` on every result via a single bulk record-count REST call.
|
|
759
|
+
*
|
|
760
|
+
* Always called during {@link filter} so the Records column is populated
|
|
761
|
+
* unconditionally — see CLI-3077.
|
|
762
|
+
*
|
|
763
|
+
* Failure semantics: when `required` is true (user passed `withRecords` or
|
|
764
|
+
* `withoutRecords`), a count-query failure throws so {@link filter} returns a
|
|
765
|
+
* `FILTER_FAILED` ServiceResult — we can't honestly filter by record count if
|
|
766
|
+
* we don't know counts. When `required` is false, a failure is logged and
|
|
767
|
+
* `recordCount` is left undefined; the display renders that as `—` rather than
|
|
768
|
+
* a misleading `0`.
|
|
769
|
+
*
|
|
770
|
+
* @param objects - SObjectInfo array to enrich with record counts
|
|
771
|
+
* @param required - Whether the caller's filter chain depends on accurate counts
|
|
772
|
+
* @returns Same array with `recordCount` populated (or unchanged on soft-failure)
|
|
773
|
+
* @throws Error when `required` is true and the count query fails
|
|
774
|
+
*/
|
|
775
|
+
async populateRecordCounts(objects, required) {
|
|
776
|
+
if (objects.length === 0) {
|
|
777
|
+
return objects;
|
|
778
|
+
}
|
|
779
|
+
const objectNames = objects.map((obj) => obj.name);
|
|
780
|
+
const countsResult = await this.restApiAdapter.getRecordCounts(objectNames);
|
|
781
|
+
if (!countsResult.success) {
|
|
782
|
+
const message = `Record count query failed: ${countsResult.message ?? 'unknown error'}`;
|
|
783
|
+
this.logger?.log(message);
|
|
784
|
+
if (required) {
|
|
785
|
+
throw new Error(message);
|
|
786
|
+
}
|
|
787
|
+
return objects;
|
|
788
|
+
}
|
|
789
|
+
const counts = countsResult.data;
|
|
790
|
+
return objects.map((obj) => ({ ...obj, recordCount: counts.get(obj.name) ?? 0 }));
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Applies hasRecordTypes filter by querying active record types.
|
|
794
|
+
* Uses lazy-loaded cache for record type information.
|
|
795
|
+
*/
|
|
796
|
+
async applyHasRecordTypesFilter(objects) {
|
|
797
|
+
const recordTypeObjects = await this.getRecordTypeObjects();
|
|
798
|
+
return objects.filter((obj) => recordTypeObjects.has(obj.name)).map((obj) => ({ ...obj, hasRecordTypes: true }));
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Lazily loads and caches the set of object names that have active record types.
|
|
802
|
+
*
|
|
803
|
+
* @returns Set of object API names with active record types
|
|
804
|
+
*/
|
|
805
|
+
async getRecordTypeObjects() {
|
|
806
|
+
if (this.recordTypeCache !== null) {
|
|
807
|
+
return this.recordTypeCache;
|
|
808
|
+
}
|
|
809
|
+
const soql = CuneiformQueryBuilder.create()
|
|
810
|
+
.select(['SobjectType'])
|
|
811
|
+
.from('RecordType')
|
|
812
|
+
.where('IsActive', '=', true)
|
|
813
|
+
.groupBy(['SobjectType'])
|
|
814
|
+
.toSOQL();
|
|
815
|
+
const result = await this.soqlAdapter.query(soql);
|
|
816
|
+
if (result.success) {
|
|
817
|
+
this.recordTypeCache = new Set(result.data.records.map((r) => r.SobjectType));
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
this.logger?.log(`Record type query failed: ${result.message ?? 'unknown error'}`);
|
|
821
|
+
this.recordTypeCache = new Set();
|
|
822
|
+
}
|
|
823
|
+
return this.recordTypeCache;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Applies withOwner filter by checking if objects have an OwnerId field.
|
|
827
|
+
* Uses cached describe results for efficiency.
|
|
828
|
+
*/
|
|
829
|
+
async applyWithOwnerFilter(objects) {
|
|
830
|
+
const results = [];
|
|
831
|
+
// Check owner fields in parallel
|
|
832
|
+
const ownerPromises = objects.map(async (obj) => {
|
|
833
|
+
const hasOwner = await this.hasOwnerField(obj.name);
|
|
834
|
+
if (hasOwner) {
|
|
835
|
+
return { ...obj, hasOwner: true };
|
|
836
|
+
}
|
|
837
|
+
return null;
|
|
838
|
+
});
|
|
839
|
+
const resolved = await Promise.all(ownerPromises);
|
|
840
|
+
for (const item of resolved) {
|
|
841
|
+
if (item !== null) {
|
|
842
|
+
results.push(item);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return results;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Checks if an object has an OwnerId field.
|
|
849
|
+
* Results are cached per-session for efficiency.
|
|
850
|
+
*
|
|
851
|
+
* @param objectName - The object API name to check
|
|
852
|
+
* @returns True if the object has an OwnerId field
|
|
853
|
+
*/
|
|
854
|
+
async hasOwnerField(objectName) {
|
|
855
|
+
// Check cache first
|
|
856
|
+
if (this.ownerFieldCache.has(objectName)) {
|
|
857
|
+
return this.ownerFieldCache.get(objectName);
|
|
858
|
+
}
|
|
859
|
+
try {
|
|
860
|
+
const describeResult = await this.restApiAdapter.describeObject(objectName);
|
|
861
|
+
if (!describeResult.success) {
|
|
862
|
+
this.logger?.log(`Describe failed for ${objectName}: ${describeResult.message ?? 'unknown error'}`);
|
|
863
|
+
this.ownerFieldCache.set(objectName, false);
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
const hasOwner = describeResult.data.fields.some((field) => field.name === 'OwnerId');
|
|
867
|
+
this.ownerFieldCache.set(objectName, hasOwner);
|
|
868
|
+
return hasOwner;
|
|
869
|
+
}
|
|
870
|
+
catch (error) {
|
|
871
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
872
|
+
this.logger?.log(`Error checking owner field for ${objectName}: ${errorMessage}`);
|
|
873
|
+
this.ownerFieldCache.set(objectName, false);
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
//# sourceMappingURL=ObjectFilteringService.js.map
|