@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.
Files changed (488) hide show
  1. package/LICENSE +81 -30
  2. package/README.md +168 -134
  3. package/lib/adapters/connection-facade.d.ts +458 -0
  4. package/lib/adapters/connection-facade.js +379 -0
  5. package/lib/adapters/connection-facade.js.map +1 -0
  6. package/lib/adapters/errors.d.ts +547 -0
  7. package/lib/adapters/errors.js +937 -0
  8. package/lib/adapters/errors.js.map +1 -0
  9. package/lib/adapters/lifecycle.d.ts +112 -0
  10. package/lib/adapters/lifecycle.js +94 -0
  11. package/lib/adapters/lifecycle.js.map +1 -0
  12. package/lib/adapters/rest/cache.d.ts +69 -0
  13. package/lib/adapters/rest/cache.js +133 -0
  14. package/lib/adapters/rest/cache.js.map +1 -0
  15. package/lib/adapters/rest/index.d.ts +11 -0
  16. package/lib/adapters/rest/index.js +18 -0
  17. package/lib/adapters/rest/index.js.map +1 -0
  18. package/lib/adapters/rest/profiling-rest-client.d.ts +137 -0
  19. package/lib/adapters/rest/profiling-rest-client.js +115 -0
  20. package/lib/adapters/rest/profiling-rest-client.js.map +1 -0
  21. package/lib/adapters/rest/rest-api-adapter.d.ts +393 -0
  22. package/lib/adapters/rest/rest-api-adapter.js +764 -0
  23. package/lib/adapters/rest/rest-api-adapter.js.map +1 -0
  24. package/lib/adapters/rest/types.d.ts +34 -0
  25. package/lib/adapters/rest/types.js +9 -0
  26. package/lib/adapters/rest/types.js.map +1 -0
  27. package/lib/adapters/retry.d.ts +91 -0
  28. package/lib/adapters/retry.js +215 -0
  29. package/lib/adapters/retry.js.map +1 -0
  30. package/lib/adapters/soql/cuneiform-query-builder.d.ts +418 -0
  31. package/lib/adapters/soql/cuneiform-query-builder.js +606 -0
  32. package/lib/adapters/soql/cuneiform-query-builder.js.map +1 -0
  33. package/lib/adapters/soql/soql-query-adapter.d.ts +141 -0
  34. package/lib/adapters/soql/soql-query-adapter.js +259 -0
  35. package/lib/adapters/soql/soql-query-adapter.js.map +1 -0
  36. package/lib/adapters/soql/types.d.ts +37 -0
  37. package/lib/adapters/soql/types.js +19 -0
  38. package/lib/adapters/soql/types.js.map +1 -0
  39. package/lib/adapters/testing/index.d.ts +37 -0
  40. package/lib/adapters/testing/index.js +20 -0
  41. package/lib/adapters/testing/index.js.map +1 -0
  42. package/lib/adapters/testing/mock-connection.d.ts +77 -0
  43. package/lib/adapters/testing/mock-connection.js +207 -0
  44. package/lib/adapters/testing/mock-connection.js.map +1 -0
  45. package/lib/adapters/testing/mock-logger.d.ts +29 -0
  46. package/lib/adapters/testing/mock-logger.js +57 -0
  47. package/lib/adapters/testing/mock-logger.js.map +1 -0
  48. package/lib/adapters/testing/mock-mcp-adapters.d.ts +32 -0
  49. package/lib/adapters/testing/mock-mcp-adapters.js +52 -0
  50. package/lib/adapters/testing/mock-mcp-adapters.js.map +1 -0
  51. package/lib/adapters/testing/mock-oclif-config.d.ts +22 -0
  52. package/lib/adapters/testing/mock-oclif-config.js +90 -0
  53. package/lib/adapters/testing/mock-oclif-config.js.map +1 -0
  54. package/lib/adapters/testing/mock-rest-adapter.d.ts +26 -0
  55. package/lib/adapters/testing/mock-rest-adapter.js +243 -0
  56. package/lib/adapters/testing/mock-rest-adapter.js.map +1 -0
  57. package/lib/adapters/testing/mock-salesforce-connection.d.ts +40 -0
  58. package/lib/adapters/testing/mock-salesforce-connection.js +61 -0
  59. package/lib/adapters/testing/mock-salesforce-connection.js.map +1 -0
  60. package/lib/adapters/testing/mock-soql-adapter.d.ts +30 -0
  61. package/lib/adapters/testing/mock-soql-adapter.js +120 -0
  62. package/lib/adapters/testing/mock-soql-adapter.js.map +1 -0
  63. package/lib/adapters/testing/mock-tooling-adapter.d.ts +24 -0
  64. package/lib/adapters/testing/mock-tooling-adapter.js +163 -0
  65. package/lib/adapters/testing/mock-tooling-adapter.js.map +1 -0
  66. package/lib/adapters/testing/stub-connection.d.ts +93 -0
  67. package/lib/adapters/testing/stub-connection.js +97 -0
  68. package/lib/adapters/testing/stub-connection.js.map +1 -0
  69. package/lib/adapters/testing/stub-rest-adapter.d.ts +52 -0
  70. package/lib/adapters/testing/stub-rest-adapter.js +58 -0
  71. package/lib/adapters/testing/stub-rest-adapter.js.map +1 -0
  72. package/lib/adapters/testing/stub-soql-adapter.d.ts +56 -0
  73. package/lib/adapters/testing/stub-soql-adapter.js +50 -0
  74. package/lib/adapters/testing/stub-soql-adapter.js.map +1 -0
  75. package/lib/adapters/testing/types.d.ts +71 -0
  76. package/lib/adapters/testing/types.js +9 -0
  77. package/lib/adapters/testing/types.js.map +1 -0
  78. package/lib/adapters/tooling/index.d.ts +10 -0
  79. package/lib/adapters/tooling/index.js +17 -0
  80. package/lib/adapters/tooling/index.js.map +1 -0
  81. package/lib/adapters/tooling/tooling-api-adapter.d.ts +157 -0
  82. package/lib/adapters/tooling/tooling-api-adapter.js +339 -0
  83. package/lib/adapters/tooling/tooling-api-adapter.js.map +1 -0
  84. package/lib/adapters/tooling/types.d.ts +81 -0
  85. package/lib/adapters/tooling/types.js +9 -0
  86. package/lib/adapters/tooling/types.js.map +1 -0
  87. package/lib/adapters/types.d.ts +112 -0
  88. package/lib/adapters/types.js +169 -0
  89. package/lib/adapters/types.js.map +1 -0
  90. package/lib/base/cuneiform-command.d.ts +175 -0
  91. package/lib/base/cuneiform-command.js +326 -0
  92. package/lib/base/cuneiform-command.js.map +1 -0
  93. package/lib/commands/cuneiform/compatibility/check.d.ts +43 -0
  94. package/lib/commands/cuneiform/compatibility/check.js +114 -0
  95. package/lib/commands/cuneiform/compatibility/check.js.map +1 -0
  96. package/lib/commands/cuneiform/definition/create.d.ts +120 -0
  97. package/lib/commands/cuneiform/definition/create.js +737 -0
  98. package/lib/commands/cuneiform/definition/create.js.map +1 -0
  99. package/lib/commands/cuneiform/definition/export.d.ts +57 -0
  100. package/lib/commands/cuneiform/definition/export.js +133 -0
  101. package/lib/commands/cuneiform/definition/export.js.map +1 -0
  102. package/lib/commands/cuneiform/definition/get.d.ts +86 -0
  103. package/lib/commands/cuneiform/definition/get.js +277 -0
  104. package/lib/commands/cuneiform/definition/get.js.map +1 -0
  105. package/lib/commands/cuneiform/definition/import.d.ts +54 -0
  106. package/lib/commands/cuneiform/definition/import.js +118 -0
  107. package/lib/commands/cuneiform/definition/import.js.map +1 -0
  108. package/lib/commands/cuneiform/definition/list.d.ts +110 -0
  109. package/lib/commands/cuneiform/definition/list.js +351 -0
  110. package/lib/commands/cuneiform/definition/list.js.map +1 -0
  111. package/lib/commands/cuneiform/definition/purge.d.ts +109 -0
  112. package/lib/commands/cuneiform/definition/purge.js +578 -0
  113. package/lib/commands/cuneiform/definition/purge.js.map +1 -0
  114. package/lib/commands/cuneiform/definition/update.d.ts +58 -0
  115. package/lib/commands/cuneiform/definition/update.js +209 -0
  116. package/lib/commands/cuneiform/definition/update.js.map +1 -0
  117. package/lib/commands/cuneiform/mcp/serve.d.ts +56 -0
  118. package/lib/commands/cuneiform/mcp/serve.js +109 -0
  119. package/lib/commands/cuneiform/mcp/serve.js.map +1 -0
  120. package/lib/commands/cuneiform/object/describe.d.ts +61 -0
  121. package/lib/commands/cuneiform/object/describe.js +461 -0
  122. package/lib/commands/cuneiform/object/describe.js.map +1 -0
  123. package/lib/commands/cuneiform/object/list.d.ts +123 -0
  124. package/lib/commands/cuneiform/object/list.js +264 -0
  125. package/lib/commands/cuneiform/object/list.js.map +1 -0
  126. package/lib/commands/cuneiform/org/details.d.ts +99 -0
  127. package/lib/commands/cuneiform/org/details.js +521 -0
  128. package/lib/commands/cuneiform/org/details.js.map +1 -0
  129. package/lib/commands/cuneiform/org/reset.d.ts +46 -0
  130. package/lib/commands/cuneiform/org/reset.js +135 -0
  131. package/lib/commands/cuneiform/org/reset.js.map +1 -0
  132. package/lib/commands/cuneiform/profile/request/cancel.d.ts +59 -0
  133. package/lib/commands/cuneiform/profile/request/cancel.js +202 -0
  134. package/lib/commands/cuneiform/profile/request/cancel.js.map +1 -0
  135. package/lib/commands/cuneiform/profile/request/delete.d.ts +59 -0
  136. package/lib/commands/cuneiform/profile/request/delete.js +223 -0
  137. package/lib/commands/cuneiform/profile/request/delete.js.map +1 -0
  138. package/lib/commands/cuneiform/profile/request/list.d.ts +35 -0
  139. package/lib/commands/cuneiform/profile/request/list.js +102 -0
  140. package/lib/commands/cuneiform/profile/request/list.js.map +1 -0
  141. package/lib/commands/cuneiform/profile.d.ts +93 -0
  142. package/lib/commands/cuneiform/profile.js +353 -0
  143. package/lib/commands/cuneiform/profile.js.map +1 -0
  144. package/lib/commands/cuneiform/summary/purge.d.ts +80 -0
  145. package/lib/commands/cuneiform/summary/purge.js +467 -0
  146. package/lib/commands/cuneiform/summary/purge.js.map +1 -0
  147. package/lib/commands/cuneiform/summary/reprofile.d.ts +60 -0
  148. package/lib/commands/cuneiform/summary/reprofile.js +236 -0
  149. package/lib/commands/cuneiform/summary/reprofile.js.map +1 -0
  150. package/lib/commands/cuneiform/summary/stop.d.ts +59 -0
  151. package/lib/commands/cuneiform/summary/stop.js +234 -0
  152. package/lib/commands/cuneiform/summary/stop.js.map +1 -0
  153. package/lib/commands/cuneiform/user/details.d.ts +77 -0
  154. package/lib/commands/cuneiform/user/details.js +414 -0
  155. package/lib/commands/cuneiform/user/details.js.map +1 -0
  156. package/lib/constants/namespace-constants.d.ts +102 -0
  157. package/lib/constants/namespace-constants.js +225 -0
  158. package/lib/constants/namespace-constants.js.map +1 -0
  159. package/lib/debug/command-debug-proxy.d.ts +101 -0
  160. package/lib/debug/command-debug-proxy.js +171 -0
  161. package/lib/debug/command-debug-proxy.js.map +1 -0
  162. package/lib/debug/debug-logger.d.ts +85 -0
  163. package/lib/debug/debug-logger.js +133 -0
  164. package/lib/debug/debug-logger.js.map +1 -0
  165. package/lib/debug/service-debug-proxy.d.ts +30 -0
  166. package/lib/debug/service-debug-proxy.js +102 -0
  167. package/lib/debug/service-debug-proxy.js.map +1 -0
  168. package/lib/hooks/prerun.d.ts +25 -0
  169. package/lib/hooks/prerun.js +47 -0
  170. package/lib/hooks/prerun.js.map +1 -0
  171. package/lib/mcp/config/mcp-config.d.ts +55 -0
  172. package/lib/mcp/config/mcp-config.js +51 -0
  173. package/lib/mcp/config/mcp-config.js.map +1 -0
  174. package/lib/mcp/config/pagination.d.ts +96 -0
  175. package/lib/mcp/config/pagination.js +108 -0
  176. package/lib/mcp/config/pagination.js.map +1 -0
  177. package/lib/mcp/config/system-prompts.d.ts +18 -0
  178. package/lib/mcp/config/system-prompts.js +92 -0
  179. package/lib/mcp/config/system-prompts.js.map +1 -0
  180. package/lib/mcp/errors.d.ts +23 -0
  181. package/lib/mcp/errors.js +27 -0
  182. package/lib/mcp/errors.js.map +1 -0
  183. package/lib/mcp/schemas/input-schemas.d.ts +327 -0
  184. package/lib/mcp/schemas/input-schemas.js +310 -0
  185. package/lib/mcp/schemas/input-schemas.js.map +1 -0
  186. package/lib/mcp/server.d.ts +40 -0
  187. package/lib/mcp/server.js +316 -0
  188. package/lib/mcp/server.js.map +1 -0
  189. package/lib/mcp/tools/contactpoint-tools.d.ts +14 -0
  190. package/lib/mcp/tools/contactpoint-tools.js +34 -0
  191. package/lib/mcp/tools/contactpoint-tools.js.map +1 -0
  192. package/lib/mcp/tools/definition-io-tools.d.ts +19 -0
  193. package/lib/mcp/tools/definition-io-tools.js +152 -0
  194. package/lib/mcp/tools/definition-io-tools.js.map +1 -0
  195. package/lib/mcp/tools/definition-tools.d.ts +51 -0
  196. package/lib/mcp/tools/definition-tools.js +220 -0
  197. package/lib/mcp/tools/definition-tools.js.map +1 -0
  198. package/lib/mcp/tools/index.d.ts +37 -0
  199. package/lib/mcp/tools/index.js +88 -0
  200. package/lib/mcp/tools/index.js.map +1 -0
  201. package/lib/mcp/tools/object-tools.d.ts +22 -0
  202. package/lib/mcp/tools/object-tools.js +327 -0
  203. package/lib/mcp/tools/object-tools.js.map +1 -0
  204. package/lib/mcp/tools/org-tools.d.ts +14 -0
  205. package/lib/mcp/tools/org-tools.js +177 -0
  206. package/lib/mcp/tools/org-tools.js.map +1 -0
  207. package/lib/mcp/tools/profile-tools.d.ts +59 -0
  208. package/lib/mcp/tools/profile-tools.js +213 -0
  209. package/lib/mcp/tools/profile-tools.js.map +1 -0
  210. package/lib/mcp/tools/summary-tools.d.ts +14 -0
  211. package/lib/mcp/tools/summary-tools.js +38 -0
  212. package/lib/mcp/tools/summary-tools.js.map +1 -0
  213. package/lib/mcp/tools/tool-factory.d.ts +63 -0
  214. package/lib/mcp/tools/tool-factory.js +146 -0
  215. package/lib/mcp/tools/tool-factory.js.map +1 -0
  216. package/lib/mcp/tools/user-tools.d.ts +25 -0
  217. package/lib/mcp/tools/user-tools.js +167 -0
  218. package/lib/mcp/tools/user-tools.js.map +1 -0
  219. package/lib/models/cascade-skip-accumulator.d.ts +25 -0
  220. package/lib/models/cascade-skip-accumulator.js +9 -0
  221. package/lib/models/cascade-skip-accumulator.js.map +1 -0
  222. package/lib/models/date-literal.d.ts +280 -0
  223. package/lib/models/date-literal.js +1164 -0
  224. package/lib/models/date-literal.js.map +1 -0
  225. package/lib/models/object-describe-types.d.ts +173 -0
  226. package/lib/models/object-describe-types.js +9 -0
  227. package/lib/models/object-describe-types.js.map +1 -0
  228. package/lib/models/portability-recipe.d.ts +35 -0
  229. package/lib/models/portability-recipe.js +113 -0
  230. package/lib/models/portability-recipe.js.map +1 -0
  231. package/lib/models/profile-request-types.d.ts +118 -0
  232. package/lib/models/profile-request-types.js +23 -0
  233. package/lib/models/profile-request-types.js.map +1 -0
  234. package/lib/models/profiling-execution-types.d.ts +154 -0
  235. package/lib/models/profiling-execution-types.js +14 -0
  236. package/lib/models/profiling-execution-types.js.map +1 -0
  237. package/lib/models/service-result.d.ts +114 -0
  238. package/lib/models/service-result.js +81 -0
  239. package/lib/models/service-result.js.map +1 -0
  240. package/lib/models/sfdmu-types.d.ts +49 -0
  241. package/lib/models/sfdmu-types.js +23 -0
  242. package/lib/models/sfdmu-types.js.map +1 -0
  243. package/lib/models/status-types.d.ts +38 -0
  244. package/lib/models/status-types.js +12 -0
  245. package/lib/models/status-types.js.map +1 -0
  246. package/lib/models/summary-bulk-types.d.ts +61 -0
  247. package/lib/models/summary-bulk-types.js +23 -0
  248. package/lib/models/summary-bulk-types.js.map +1 -0
  249. package/lib/models/user-details-types.d.ts +188 -0
  250. package/lib/models/user-details-types.js +9 -0
  251. package/lib/models/user-details-types.js.map +1 -0
  252. package/lib/models/year-range.d.ts +78 -0
  253. package/lib/models/year-range.js +153 -0
  254. package/lib/models/year-range.js.map +1 -0
  255. package/lib/operations/CompatibilityCheckOperation.d.ts +62 -0
  256. package/lib/operations/CompatibilityCheckOperation.js +102 -0
  257. package/lib/operations/CompatibilityCheckOperation.js.map +1 -0
  258. package/lib/operations/DefinitionCreateOperation.d.ts +427 -0
  259. package/lib/operations/DefinitionCreateOperation.js +1270 -0
  260. package/lib/operations/DefinitionCreateOperation.js.map +1 -0
  261. package/lib/operations/DefinitionExportOperation.d.ts +155 -0
  262. package/lib/operations/DefinitionExportOperation.js +281 -0
  263. package/lib/operations/DefinitionExportOperation.js.map +1 -0
  264. package/lib/operations/DefinitionImportOperation.d.ts +144 -0
  265. package/lib/operations/DefinitionImportOperation.js +357 -0
  266. package/lib/operations/DefinitionImportOperation.js.map +1 -0
  267. package/lib/operations/DefinitionListOperation.d.ts +66 -0
  268. package/lib/operations/DefinitionListOperation.js +108 -0
  269. package/lib/operations/DefinitionListOperation.js.map +1 -0
  270. package/lib/operations/DefinitionPurgeOperation.d.ts +203 -0
  271. package/lib/operations/DefinitionPurgeOperation.js +465 -0
  272. package/lib/operations/DefinitionPurgeOperation.js.map +1 -0
  273. package/lib/operations/DefinitionUpdateOperation.d.ts +78 -0
  274. package/lib/operations/DefinitionUpdateOperation.js +142 -0
  275. package/lib/operations/DefinitionUpdateOperation.js.map +1 -0
  276. package/lib/operations/OrgDetailsOperation.d.ts +253 -0
  277. package/lib/operations/OrgDetailsOperation.js +456 -0
  278. package/lib/operations/OrgDetailsOperation.js.map +1 -0
  279. package/lib/operations/OrgResetOperation.d.ts +114 -0
  280. package/lib/operations/OrgResetOperation.js +209 -0
  281. package/lib/operations/OrgResetOperation.js.map +1 -0
  282. package/lib/operations/ProfileOperation.d.ts +192 -0
  283. package/lib/operations/ProfileOperation.js +371 -0
  284. package/lib/operations/ProfileOperation.js.map +1 -0
  285. package/lib/operations/ProfileRequestCancelOperation.d.ts +59 -0
  286. package/lib/operations/ProfileRequestCancelOperation.js +137 -0
  287. package/lib/operations/ProfileRequestCancelOperation.js.map +1 -0
  288. package/lib/operations/ProfileRequestDeleteOperation.d.ts +64 -0
  289. package/lib/operations/ProfileRequestDeleteOperation.js +134 -0
  290. package/lib/operations/ProfileRequestDeleteOperation.js.map +1 -0
  291. package/lib/operations/ProfileRequestListOperation.d.ts +39 -0
  292. package/lib/operations/ProfileRequestListOperation.js +61 -0
  293. package/lib/operations/ProfileRequestListOperation.js.map +1 -0
  294. package/lib/operations/SummaryPurgeOperation.d.ts +134 -0
  295. package/lib/operations/SummaryPurgeOperation.js +257 -0
  296. package/lib/operations/SummaryPurgeOperation.js.map +1 -0
  297. package/lib/operations/SummaryReprofileOperation.d.ts +88 -0
  298. package/lib/operations/SummaryReprofileOperation.js +174 -0
  299. package/lib/operations/SummaryReprofileOperation.js.map +1 -0
  300. package/lib/operations/SummaryStopOperation.d.ts +87 -0
  301. package/lib/operations/SummaryStopOperation.js +175 -0
  302. package/lib/operations/SummaryStopOperation.js.map +1 -0
  303. package/lib/services/BulkExecutionService.d.ts +120 -0
  304. package/lib/services/BulkExecutionService.js +535 -0
  305. package/lib/services/BulkExecutionService.js.map +1 -0
  306. package/lib/services/CompatibilityService.d.ts +81 -0
  307. package/lib/services/CompatibilityService.js +118 -0
  308. package/lib/services/CompatibilityService.js.map +1 -0
  309. package/lib/services/ConfigureMode.d.ts +98 -0
  310. package/lib/services/ConfigureMode.js +413 -0
  311. package/lib/services/ConfigureMode.js.map +1 -0
  312. package/lib/services/ContactPointService.d.ts +111 -0
  313. package/lib/services/ContactPointService.js +286 -0
  314. package/lib/services/ContactPointService.js.map +1 -0
  315. package/lib/services/DataAvailabilityService.d.ts +81 -0
  316. package/lib/services/DataAvailabilityService.js +128 -0
  317. package/lib/services/DataAvailabilityService.js.map +1 -0
  318. package/lib/services/DefinitionFieldGenerationService.d.ts +357 -0
  319. package/lib/services/DefinitionFieldGenerationService.js +899 -0
  320. package/lib/services/DefinitionFieldGenerationService.js.map +1 -0
  321. package/lib/services/DefinitionQueryBuilder.d.ts +92 -0
  322. package/lib/services/DefinitionQueryBuilder.js +328 -0
  323. package/lib/services/DefinitionQueryBuilder.js.map +1 -0
  324. package/lib/services/ObjectDescribeService.d.ts +436 -0
  325. package/lib/services/ObjectDescribeService.js +881 -0
  326. package/lib/services/ObjectDescribeService.js.map +1 -0
  327. package/lib/services/ObjectFilteringService.d.ts +484 -0
  328. package/lib/services/ObjectFilteringService.js +1080 -0
  329. package/lib/services/ObjectFilteringService.js.map +1 -0
  330. package/lib/services/ObjectListCommandService.d.ts +467 -0
  331. package/lib/services/ObjectListCommandService.js +904 -0
  332. package/lib/services/ObjectListCommandService.js.map +1 -0
  333. package/lib/services/ObjectListService.d.ts +201 -0
  334. package/lib/services/ObjectListService.js +350 -0
  335. package/lib/services/ObjectListService.js.map +1 -0
  336. package/lib/services/OrgInfoService.d.ts +493 -0
  337. package/lib/services/OrgInfoService.js +1142 -0
  338. package/lib/services/OrgInfoService.js.map +1 -0
  339. package/lib/services/PollingService.d.ts +105 -0
  340. package/lib/services/PollingService.js +117 -0
  341. package/lib/services/PollingService.js.map +1 -0
  342. package/lib/services/ProfileRequestService.d.ts +186 -0
  343. package/lib/services/ProfileRequestService.js +555 -0
  344. package/lib/services/ProfileRequestService.js.map +1 -0
  345. package/lib/services/ProfilingDefinitionService.d.ts +575 -0
  346. package/lib/services/ProfilingDefinitionService.js +1029 -0
  347. package/lib/services/ProfilingDefinitionService.js.map +1 -0
  348. package/lib/services/ProfilingExecutionService.d.ts +122 -0
  349. package/lib/services/ProfilingExecutionService.js +320 -0
  350. package/lib/services/ProfilingExecutionService.js.map +1 -0
  351. package/lib/services/ProfilingSummaryService.d.ts +292 -0
  352. package/lib/services/ProfilingSummaryService.js +688 -0
  353. package/lib/services/ProfilingSummaryService.js.map +1 -0
  354. package/lib/services/RecordTypeService.d.ts +129 -0
  355. package/lib/services/RecordTypeService.js +284 -0
  356. package/lib/services/RecordTypeService.js.map +1 -0
  357. package/lib/services/SFDMUService.d.ts +146 -0
  358. package/lib/services/SFDMUService.js +323 -0
  359. package/lib/services/SFDMUService.js.map +1 -0
  360. package/lib/services/TabDetectionService.d.ts +105 -0
  361. package/lib/services/TabDetectionService.js +206 -0
  362. package/lib/services/TabDetectionService.js.map +1 -0
  363. package/lib/services/UnconfigureMode.d.ts +74 -0
  364. package/lib/services/UnconfigureMode.js +378 -0
  365. package/lib/services/UnconfigureMode.js.map +1 -0
  366. package/lib/services/UserConfigurationService.d.ts +158 -0
  367. package/lib/services/UserConfigurationService.js +574 -0
  368. package/lib/services/UserConfigurationService.js.map +1 -0
  369. package/lib/services/UserConfigurationTypes.d.ts +181 -0
  370. package/lib/services/UserConfigurationTypes.js +14 -0
  371. package/lib/services/UserConfigurationTypes.js.map +1 -0
  372. package/lib/services/UserReadinessService.d.ts +347 -0
  373. package/lib/services/UserReadinessService.js +891 -0
  374. package/lib/services/UserReadinessService.js.map +1 -0
  375. package/lib/services/constants.d.ts +54 -0
  376. package/lib/services/constants.js +71 -0
  377. package/lib/services/constants.js.map +1 -0
  378. package/lib/services/namespace-constants.d.ts +1 -0
  379. package/lib/services/namespace-constants.js +11 -0
  380. package/lib/services/namespace-constants.js.map +1 -0
  381. package/lib/services/namespace-filter.d.ts +36 -0
  382. package/lib/services/namespace-filter.js +109 -0
  383. package/lib/services/namespace-filter.js.map +1 -0
  384. package/lib/services/validation.d.ts +47 -0
  385. package/lib/services/validation.js +119 -0
  386. package/lib/services/validation.js.map +1 -0
  387. package/lib/utils/batch-processor.d.ts +13 -0
  388. package/lib/utils/batch-processor.js +39 -0
  389. package/lib/utils/batch-processor.js.map +1 -0
  390. package/lib/utils/formatting/availability-grid.d.ts +81 -0
  391. package/lib/utils/formatting/availability-grid.js +94 -0
  392. package/lib/utils/formatting/availability-grid.js.map +1 -0
  393. package/lib/utils/formatting/business-process-grid.d.ts +51 -0
  394. package/lib/utils/formatting/business-process-grid.js +58 -0
  395. package/lib/utils/formatting/business-process-grid.js.map +1 -0
  396. package/lib/utils/formatting/command-display.d.ts +154 -0
  397. package/lib/utils/formatting/command-display.js +154 -0
  398. package/lib/utils/formatting/command-display.js.map +1 -0
  399. package/lib/utils/formatting/definition-create-display.d.ts +118 -0
  400. package/lib/utils/formatting/definition-create-display.js +230 -0
  401. package/lib/utils/formatting/definition-create-display.js.map +1 -0
  402. package/lib/utils/formatting/empty-states.d.ts +35 -0
  403. package/lib/utils/formatting/empty-states.js +70 -0
  404. package/lib/utils/formatting/empty-states.js.map +1 -0
  405. package/lib/utils/formatting/errors.d.ts +33 -0
  406. package/lib/utils/formatting/errors.js +72 -0
  407. package/lib/utils/formatting/errors.js.map +1 -0
  408. package/lib/utils/formatting/field-types.d.ts +32 -0
  409. package/lib/utils/formatting/field-types.js +88 -0
  410. package/lib/utils/formatting/field-types.js.map +1 -0
  411. package/lib/utils/formatting/index.d.ts +29 -0
  412. package/lib/utils/formatting/index.js +28 -0
  413. package/lib/utils/formatting/index.js.map +1 -0
  414. package/lib/utils/formatting/indicators.d.ts +113 -0
  415. package/lib/utils/formatting/indicators.js +161 -0
  416. package/lib/utils/formatting/indicators.js.map +1 -0
  417. package/lib/utils/formatting/loading-messages.d.ts +37 -0
  418. package/lib/utils/formatting/loading-messages.js +50 -0
  419. package/lib/utils/formatting/loading-messages.js.map +1 -0
  420. package/lib/utils/formatting/namespace-display.d.ts +31 -0
  421. package/lib/utils/formatting/namespace-display.js +64 -0
  422. package/lib/utils/formatting/namespace-display.js.map +1 -0
  423. package/lib/utils/formatting/numbers.d.ts +73 -0
  424. package/lib/utils/formatting/numbers.js +187 -0
  425. package/lib/utils/formatting/numbers.js.map +1 -0
  426. package/lib/utils/formatting/object-describe-display.d.ts +117 -0
  427. package/lib/utils/formatting/object-describe-display.js +447 -0
  428. package/lib/utils/formatting/object-describe-display.js.map +1 -0
  429. package/lib/utils/formatting/object-list-display.d.ts +225 -0
  430. package/lib/utils/formatting/object-list-display.js +718 -0
  431. package/lib/utils/formatting/object-list-display.js.map +1 -0
  432. package/lib/utils/formatting/org-identity.d.ts +15 -0
  433. package/lib/utils/formatting/org-identity.js +28 -0
  434. package/lib/utils/formatting/org-identity.js.map +1 -0
  435. package/lib/utils/formatting/record-age-grid.d.ts +41 -0
  436. package/lib/utils/formatting/record-age-grid.js +56 -0
  437. package/lib/utils/formatting/record-age-grid.js.map +1 -0
  438. package/lib/utils/formatting/sections.d.ts +108 -0
  439. package/lib/utils/formatting/sections.js +150 -0
  440. package/lib/utils/formatting/sections.js.map +1 -0
  441. package/lib/utils/formatting/tables.d.ts +90 -0
  442. package/lib/utils/formatting/tables.js +113 -0
  443. package/lib/utils/formatting/tables.js.map +1 -0
  444. package/lib/utils/formatting/user-details-display.d.ts +101 -0
  445. package/lib/utils/formatting/user-details-display.js +425 -0
  446. package/lib/utils/formatting/user-details-display.js.map +1 -0
  447. package/lib/utils/pagination/keypress-reader.d.ts +20 -0
  448. package/lib/utils/pagination/keypress-reader.js +63 -0
  449. package/lib/utils/pagination/keypress-reader.js.map +1 -0
  450. package/lib/utils/pagination/paginate-output.d.ts +48 -0
  451. package/lib/utils/pagination/paginate-output.js +136 -0
  452. package/lib/utils/pagination/paginate-output.js.map +1 -0
  453. package/messages/compatibility.check.md +71 -0
  454. package/messages/cuneiform.access.md +138 -0
  455. package/messages/definition.create.md +525 -0
  456. package/messages/definition.export.md +84 -0
  457. package/messages/definition.get.md +147 -0
  458. package/messages/definition.import.md +65 -0
  459. package/messages/definition.list.md +264 -0
  460. package/messages/definition.purge.md +330 -0
  461. package/messages/definition.update.md +118 -0
  462. package/messages/mcp.serve.md +66 -0
  463. package/messages/object.describe.md +205 -0
  464. package/messages/object.list.md +463 -0
  465. package/messages/org.details.md +386 -0
  466. package/messages/org.reset.md +71 -0
  467. package/messages/profile.md +243 -0
  468. package/messages/profile.request.cancel.md +143 -0
  469. package/messages/profile.request.delete.md +139 -0
  470. package/messages/profile.request.list.md +89 -0
  471. package/messages/summary.purge.md +218 -0
  472. package/messages/summary.reprofile.md +150 -0
  473. package/messages/summary.stop.md +157 -0
  474. package/messages/user.details.md +501 -0
  475. package/oclif.lock +3267 -2148
  476. package/oclif.manifest.json +2829 -31
  477. package/package.json +104 -18
  478. package/lib/commands/cuneiform/about.d.ts +0 -13
  479. package/lib/commands/cuneiform/about.js +0 -26
  480. package/lib/commands/cuneiform/about.js.map +0 -1
  481. package/lib/commands/hello/world.d.ts +0 -14
  482. package/lib/commands/hello/world.js +0 -27
  483. package/lib/commands/hello/world.js.map +0 -1
  484. package/lib/index.d.ts +0 -2
  485. package/lib/index.js +0 -2
  486. package/lib/index.js.map +0 -1
  487. package/messages/cuneiform.about.md +0 -19
  488. 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