@twentyhq/people-data-labs 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.nvmrc +1 -0
  3. package/.oxlintrc.json +58 -0
  4. package/.yarnrc.yml +1 -0
  5. package/README.md +16 -0
  6. package/package.json +37 -0
  7. package/public/gallery/cover.png +0 -0
  8. package/public/people-data-labs-icon.png +0 -0
  9. package/src/__tests__/global-setup.ts +87 -0
  10. package/src/__tests__/people-data-labs.integration-test.ts +25 -0
  11. package/src/application-config.ts +18 -0
  12. package/src/command-menu-items/enrich-companies.ts +14 -0
  13. package/src/command-menu-items/enrich-people.ts +14 -0
  14. package/src/constants/__tests__/select-option-constants.spec.ts +142 -0
  15. package/src/constants/billing-margin-multiplier.ts +1 -0
  16. package/src/constants/company-match-cost-dollars.ts +1 -0
  17. package/src/constants/company-type-options.ts +46 -0
  18. package/src/constants/enrichment-status-options.ts +19 -0
  19. package/src/constants/enrichment-workflow-seeds.ts +27 -0
  20. package/src/constants/funding-stage-options.ts +195 -0
  21. package/src/constants/industry-options.ts +1033 -0
  22. package/src/constants/inferred-salary-options.ts +81 -0
  23. package/src/constants/job-role-options.ts +172 -0
  24. package/src/constants/job-title-class-options.ts +39 -0
  25. package/src/constants/job-title-sub-role-options.ts +746 -0
  26. package/src/constants/location-continent-options.ts +53 -0
  27. package/src/constants/metro-options.ts +2692 -0
  28. package/src/constants/mic-exchange-options.ts +494 -0
  29. package/src/constants/micro-credits-per-dollar.ts +1 -0
  30. package/src/constants/pdl-base-url.ts +1 -0
  31. package/src/constants/person-match-cost-dollars.ts +1 -0
  32. package/src/constants/seniority-options.ts +74 -0
  33. package/src/constants/sex-options.ts +18 -0
  34. package/src/constants/size-options.ts +60 -0
  35. package/src/constants/universal-identifiers.ts +1516 -0
  36. package/src/constants/update-fields-option-values.ts +5 -0
  37. package/src/constants/update-fields-options.ts +7 -0
  38. package/src/fields/company/pdl-affiliated-profiles.field.ts +20 -0
  39. package/src/fields/company/pdl-alternative-domains.field.ts +20 -0
  40. package/src/fields/company/pdl-alternative-names.field.ts +20 -0
  41. package/src/fields/company/pdl-company-type.field.ts +28 -0
  42. package/src/fields/company/pdl-employee-count-by-country.field.ts +21 -0
  43. package/src/fields/company/pdl-employee-count.field.ts +19 -0
  44. package/src/fields/company/pdl-enrichment-status.field.ts +29 -0
  45. package/src/fields/company/pdl-facebook-url.field.ts +19 -0
  46. package/src/fields/company/pdl-founded-year.field.ts +19 -0
  47. package/src/fields/company/pdl-funding-stages.field.ts +28 -0
  48. package/src/fields/company/pdl-headline.field.ts +19 -0
  49. package/src/fields/company/pdl-id.field.ts +19 -0
  50. package/src/fields/company/pdl-industry-detail.field.ts +20 -0
  51. package/src/fields/company/pdl-industry.field.ts +28 -0
  52. package/src/fields/company/pdl-last-enriched-at.field.ts +20 -0
  53. package/src/fields/company/pdl-last-funding-date.field.ts +20 -0
  54. package/src/fields/company/pdl-latest-funding-stage.field.ts +29 -0
  55. package/src/fields/company/pdl-legal-name.field.ts +19 -0
  56. package/src/fields/company/pdl-linkedin-id.field.ts +19 -0
  57. package/src/fields/company/pdl-location-continent.field.ts +29 -0
  58. package/src/fields/company/pdl-location-metro.field.ts +28 -0
  59. package/src/fields/company/pdl-mic-exchange.field.ts +28 -0
  60. package/src/fields/company/pdl-naics.field.ts +19 -0
  61. package/src/fields/company/pdl-number-funding-rounds.field.ts +20 -0
  62. package/src/fields/company/pdl-raw-payload.field.ts +19 -0
  63. package/src/fields/company/pdl-sic.field.ts +19 -0
  64. package/src/fields/company/pdl-size-range.field.ts +28 -0
  65. package/src/fields/company/pdl-summary.field.ts +19 -0
  66. package/src/fields/company/pdl-tags.field.ts +19 -0
  67. package/src/fields/company/pdl-ticker.field.ts +19 -0
  68. package/src/fields/company/pdl-total-funding.field.ts +19 -0
  69. package/src/fields/company/pdl-twitter-url.field.ts +19 -0
  70. package/src/fields/person/pdl-birth-date.field.ts +19 -0
  71. package/src/fields/person/pdl-birth-year.field.ts +19 -0
  72. package/src/fields/person/pdl-certifications.field.ts +19 -0
  73. package/src/fields/person/pdl-education.field.ts +19 -0
  74. package/src/fields/person/pdl-enrichment-status.field.ts +29 -0
  75. package/src/fields/person/pdl-experience.field.ts +19 -0
  76. package/src/fields/person/pdl-facebook-url.field.ts +19 -0
  77. package/src/fields/person/pdl-github-url.field.ts +19 -0
  78. package/src/fields/person/pdl-headline.field.ts +19 -0
  79. package/src/fields/person/pdl-id.field.ts +19 -0
  80. package/src/fields/person/pdl-industry.field.ts +28 -0
  81. package/src/fields/person/pdl-inferred-salary.field.ts +28 -0
  82. package/src/fields/person/pdl-interests.field.ts +19 -0
  83. package/src/fields/person/pdl-job-onet-code.field.ts +19 -0
  84. package/src/fields/person/pdl-job-role.field.ts +28 -0
  85. package/src/fields/person/pdl-job-start-date.field.ts +19 -0
  86. package/src/fields/person/pdl-job-summary.field.ts +19 -0
  87. package/src/fields/person/pdl-job-title-class.field.ts +28 -0
  88. package/src/fields/person/pdl-job-title-sub-role.field.ts +29 -0
  89. package/src/fields/person/pdl-languages.field.ts +19 -0
  90. package/src/fields/person/pdl-last-enriched-at.field.ts +19 -0
  91. package/src/fields/person/pdl-likelihood.field.ts +19 -0
  92. package/src/fields/person/pdl-linkedin-connections.field.ts +20 -0
  93. package/src/fields/person/pdl-linkedin-username.field.ts +20 -0
  94. package/src/fields/person/pdl-location-metro.field.ts +28 -0
  95. package/src/fields/person/pdl-location.field.ts +19 -0
  96. package/src/fields/person/pdl-name-aliases.field.ts +19 -0
  97. package/src/fields/person/pdl-profiles.field.ts +19 -0
  98. package/src/fields/person/pdl-raw-payload.field.ts +19 -0
  99. package/src/fields/person/pdl-seniority.field.ts +28 -0
  100. package/src/fields/person/pdl-sex.field.ts +28 -0
  101. package/src/fields/person/pdl-skills.field.ts +19 -0
  102. package/src/fields/person/pdl-summary.field.ts +19 -0
  103. package/src/fields/person/pdl-twitter-url.field.ts +19 -0
  104. package/src/fields/person/pdl-years-experience.field.ts +21 -0
  105. package/src/front-components/enrich-companies-component-effect.tsx +21 -0
  106. package/src/front-components/enrich-people-component-effect.tsx +21 -0
  107. package/src/front-components/utils/call-enrich-logic-function.utils.ts +21 -0
  108. package/src/logic-functions/__mocks__/company-node.mock.ts +11 -0
  109. package/src/logic-functions/__mocks__/create-core-api-client-mock.ts +35 -0
  110. package/src/logic-functions/__mocks__/pdl-company-data.mock.ts +27 -0
  111. package/src/logic-functions/__mocks__/pdl-person-data.mock.ts +23 -0
  112. package/src/logic-functions/__mocks__/person-node.mock.ts +12 -0
  113. package/src/logic-functions/enrich-companies.function.ts +76 -0
  114. package/src/logic-functions/enrich-company.function.ts +73 -0
  115. package/src/logic-functions/enrich-people.function.ts +76 -0
  116. package/src/logic-functions/enrich-person.function.ts +73 -0
  117. package/src/logic-functions/errors/__tests__/pdl-config-error.spec.ts +16 -0
  118. package/src/logic-functions/errors/__tests__/pdl-invalid-input-error.spec.ts +16 -0
  119. package/src/logic-functions/errors/__tests__/pdl-operation-error.spec.ts +16 -0
  120. package/src/logic-functions/errors/__tests__/pdl-record-not-found-error.spec.ts +21 -0
  121. package/src/logic-functions/errors/pdl-config-error.ts +8 -0
  122. package/src/logic-functions/errors/pdl-error-code.ts +8 -0
  123. package/src/logic-functions/errors/pdl-error.ts +12 -0
  124. package/src/logic-functions/errors/pdl-invalid-input-error.ts +8 -0
  125. package/src/logic-functions/errors/pdl-operation-error.ts +8 -0
  126. package/src/logic-functions/errors/pdl-record-not-found-error.ts +22 -0
  127. package/src/logic-functions/handlers/__tests__/enrich-companies.spec.ts +159 -0
  128. package/src/logic-functions/handlers/__tests__/enrich-company.spec.ts +147 -0
  129. package/src/logic-functions/handlers/__tests__/enrich-people.spec.ts +313 -0
  130. package/src/logic-functions/handlers/__tests__/enrich-person.spec.ts +198 -0
  131. package/src/logic-functions/handlers/__tests__/post-install.spec.ts +153 -0
  132. package/src/logic-functions/handlers/company-enrichment-adapter.ts +42 -0
  133. package/src/logic-functions/handlers/company-single-enrichment-adapter.ts +15 -0
  134. package/src/logic-functions/handlers/enrich-companies.ts +15 -0
  135. package/src/logic-functions/handlers/enrich-company.ts +19 -0
  136. package/src/logic-functions/handlers/enrich-people.ts +15 -0
  137. package/src/logic-functions/handlers/enrich-person.ts +19 -0
  138. package/src/logic-functions/handlers/person-enrichment-adapter.ts +29 -0
  139. package/src/logic-functions/handlers/person-single-enrichment-adapter.ts +15 -0
  140. package/src/logic-functions/handlers/post-install.ts +69 -0
  141. package/src/logic-functions/post-install.function.ts +17 -0
  142. package/src/logic-functions/utils/__tests__/aggregate-bulk-enrich-result.spec.ts +52 -0
  143. package/src/logic-functions/utils/__tests__/build-address.spec.ts +30 -0
  144. package/src/logic-functions/utils/__tests__/build-allowed-values.spec.ts +16 -0
  145. package/src/logic-functions/utils/__tests__/build-bulk-records-trigger.spec.ts +26 -0
  146. package/src/logic-functions/utils/__tests__/build-company-create-data.spec.ts +37 -0
  147. package/src/logic-functions/utils/__tests__/build-company-match-keys.spec.ts +35 -0
  148. package/src/logic-functions/utils/__tests__/build-company-matched-data.spec.ts +56 -0
  149. package/src/logic-functions/utils/__tests__/build-currency-from-usd.spec.ts +17 -0
  150. package/src/logic-functions/utils/__tests__/build-emails.spec.ts +23 -0
  151. package/src/logic-functions/utils/__tests__/build-error-result.spec.ts +16 -0
  152. package/src/logic-functions/utils/__tests__/build-full-name.spec.ts +64 -0
  153. package/src/logic-functions/utils/__tests__/build-links.spec.ts +25 -0
  154. package/src/logic-functions/utils/__tests__/build-logic-function-step.spec.ts +33 -0
  155. package/src/logic-functions/utils/__tests__/build-matched-result.spec.ts +33 -0
  156. package/src/logic-functions/utils/__tests__/build-not-found-result.spec.ts +15 -0
  157. package/src/logic-functions/utils/__tests__/build-person-matched-data.spec.ts +117 -0
  158. package/src/logic-functions/utils/__tests__/build-person-name-param.spec.ts +17 -0
  159. package/src/logic-functions/utils/__tests__/build-phones.spec.ts +32 -0
  160. package/src/logic-functions/utils/__tests__/build-skipped-result.spec.ts +17 -0
  161. package/src/logic-functions/utils/__tests__/capitalize-name.spec.ts +26 -0
  162. package/src/logic-functions/utils/__tests__/capitalize-name.test.ts +17 -0
  163. package/src/logic-functions/utils/__tests__/charge-matched-enrichments.spec.ts +56 -0
  164. package/src/logic-functions/utils/__tests__/chunk.spec.ts +25 -0
  165. package/src/logic-functions/utils/__tests__/enrich-companies.spec.ts +26 -0
  166. package/src/logic-functions/utils/__tests__/enrich-company.spec.ts +54 -0
  167. package/src/logic-functions/utils/__tests__/enrich-people.spec.ts +32 -0
  168. package/src/logic-functions/utils/__tests__/enrich-person.spec.ts +62 -0
  169. package/src/logic-functions/utils/__tests__/extract-company-match-params.spec.ts +86 -0
  170. package/src/logic-functions/utils/__tests__/extract-pdl-error-message.spec.ts +17 -0
  171. package/src/logic-functions/utils/__tests__/extract-person-match-params.spec.ts +75 -0
  172. package/src/logic-functions/utils/__tests__/extract-record-ids.spec.ts +32 -0
  173. package/src/logic-functions/utils/__tests__/find-company-id-by-filter.spec.ts +42 -0
  174. package/src/logic-functions/utils/__tests__/find-company-id.spec.ts +118 -0
  175. package/src/logic-functions/utils/__tests__/find-existing-workflow-id.spec.ts +26 -0
  176. package/src/logic-functions/utils/__tests__/find-or-create-current-company.spec.ts +203 -0
  177. package/src/logic-functions/utils/__tests__/get-pdl-api-key.spec.ts +38 -0
  178. package/src/logic-functions/utils/__tests__/is-empty-address.spec.ts +20 -0
  179. package/src/logic-functions/utils/__tests__/is-empty-emails.spec.ts +14 -0
  180. package/src/logic-functions/utils/__tests__/is-empty-full-name.spec.ts +14 -0
  181. package/src/logic-functions/utils/__tests__/is-empty-links.spec.ts +14 -0
  182. package/src/logic-functions/utils/__tests__/is-empty-phones.spec.ts +14 -0
  183. package/src/logic-functions/utils/__tests__/is-empty-text.spec.ts +15 -0
  184. package/src/logic-functions/utils/__tests__/map-company.spec.ts +48 -0
  185. package/src/logic-functions/utils/__tests__/map-person.spec.ts +81 -0
  186. package/src/logic-functions/utils/__tests__/normalize-domain.spec.ts +19 -0
  187. package/src/logic-functions/utils/__tests__/normalize-linkedin-url.spec.ts +22 -0
  188. package/src/logic-functions/utils/__tests__/now-iso.spec.ts +9 -0
  189. package/src/logic-functions/utils/__tests__/parse-geo.spec.ts +21 -0
  190. package/src/logic-functions/utils/__tests__/parse-partial-date.spec.ts +29 -0
  191. package/src/logic-functions/utils/__tests__/parse-pdl-item.spec.ts +88 -0
  192. package/src/logic-functions/utils/__tests__/pick-multi-select.spec.ts +28 -0
  193. package/src/logic-functions/utils/__tests__/pick-select.spec.ts +38 -0
  194. package/src/logic-functions/utils/__tests__/pick-writable-standard.spec.ts +37 -0
  195. package/src/logic-functions/utils/__tests__/post-pdl-enrich.spec.ts +192 -0
  196. package/src/logic-functions/utils/__tests__/post-pdl-single-enrich.spec.ts +172 -0
  197. package/src/logic-functions/utils/__tests__/read-companies.spec.ts +39 -0
  198. package/src/logic-functions/utils/__tests__/read-people.spec.ts +40 -0
  199. package/src/logic-functions/utils/__tests__/resolve-logic-function-id.spec.ts +25 -0
  200. package/src/logic-functions/utils/__tests__/resolve-update-fields-mode.spec.ts +34 -0
  201. package/src/logic-functions/utils/__tests__/run-batch-enrichment.spec.ts +524 -0
  202. package/src/logic-functions/utils/__tests__/salary-transform.spec.ts +15 -0
  203. package/src/logic-functions/utils/__tests__/seed-enrichment-workflow.spec.ts +136 -0
  204. package/src/logic-functions/utils/__tests__/size-transform.spec.ts +18 -0
  205. package/src/logic-functions/utils/__tests__/to-json-array.spec.ts +16 -0
  206. package/src/logic-functions/utils/__tests__/to-json-object.spec.ts +18 -0
  207. package/src/logic-functions/utils/__tests__/to-number.spec.ts +17 -0
  208. package/src/logic-functions/utils/__tests__/to-string-array.spec.ts +17 -0
  209. package/src/logic-functions/utils/__tests__/to-text.spec.ts +20 -0
  210. package/src/logic-functions/utils/__tests__/update-companies-status.spec.ts +32 -0
  211. package/src/logic-functions/utils/__tests__/update-company-record.spec.ts +25 -0
  212. package/src/logic-functions/utils/__tests__/update-people-status.spec.ts +32 -0
  213. package/src/logic-functions/utils/__tests__/update-person-record.spec.ts +25 -0
  214. package/src/logic-functions/utils/aggregate-bulk-enrich-result.ts +21 -0
  215. package/src/logic-functions/utils/build-address.ts +40 -0
  216. package/src/logic-functions/utils/build-allowed-values.ts +5 -0
  217. package/src/logic-functions/utils/build-bulk-records-trigger.ts +25 -0
  218. package/src/logic-functions/utils/build-company-create-data.ts +35 -0
  219. package/src/logic-functions/utils/build-company-match-keys.ts +16 -0
  220. package/src/logic-functions/utils/build-company-matched-data.ts +56 -0
  221. package/src/logic-functions/utils/build-currency-from-usd.ts +16 -0
  222. package/src/logic-functions/utils/build-emails.ts +34 -0
  223. package/src/logic-functions/utils/build-error-result.ts +18 -0
  224. package/src/logic-functions/utils/build-full-name.ts +38 -0
  225. package/src/logic-functions/utils/build-links.ts +23 -0
  226. package/src/logic-functions/utils/build-logic-function-step.ts +30 -0
  227. package/src/logic-functions/utils/build-matched-result.ts +21 -0
  228. package/src/logic-functions/utils/build-not-found-result.ts +9 -0
  229. package/src/logic-functions/utils/build-person-matched-data.ts +78 -0
  230. package/src/logic-functions/utils/build-person-name-param.ts +19 -0
  231. package/src/logic-functions/utils/build-phones.ts +40 -0
  232. package/src/logic-functions/utils/build-skipped-result.ts +15 -0
  233. package/src/logic-functions/utils/capitalize-name.ts +8 -0
  234. package/src/logic-functions/utils/charge-matched-enrichments.ts +29 -0
  235. package/src/logic-functions/utils/chunk.ts +18 -0
  236. package/src/logic-functions/utils/enrich-chunk.ts +361 -0
  237. package/src/logic-functions/utils/enrich-companies.ts +21 -0
  238. package/src/logic-functions/utils/enrich-company.ts +23 -0
  239. package/src/logic-functions/utils/enrich-people.ts +22 -0
  240. package/src/logic-functions/utils/enrich-person.ts +24 -0
  241. package/src/logic-functions/utils/extract-company-match-params.ts +52 -0
  242. package/src/logic-functions/utils/extract-pdl-error-message.ts +47 -0
  243. package/src/logic-functions/utils/extract-person-match-params.ts +59 -0
  244. package/src/logic-functions/utils/extract-record-ids.ts +18 -0
  245. package/src/logic-functions/utils/find-company-id-by-filter.ts +28 -0
  246. package/src/logic-functions/utils/find-company-id.ts +59 -0
  247. package/src/logic-functions/utils/find-existing-workflow-id.ts +18 -0
  248. package/src/logic-functions/utils/find-or-create-current-company.ts +108 -0
  249. package/src/logic-functions/utils/get-pdl-api-key.ts +15 -0
  250. package/src/logic-functions/utils/internal-field-names.ts +6 -0
  251. package/src/logic-functions/utils/is-empty-address.ts +20 -0
  252. package/src/logic-functions/utils/is-empty-emails.ts +13 -0
  253. package/src/logic-functions/utils/is-empty-full-name.ts +13 -0
  254. package/src/logic-functions/utils/is-empty-links.ts +13 -0
  255. package/src/logic-functions/utils/is-empty-phones.ts +13 -0
  256. package/src/logic-functions/utils/is-empty-text.ts +4 -0
  257. package/src/logic-functions/utils/map-company.ts +115 -0
  258. package/src/logic-functions/utils/map-person.ts +136 -0
  259. package/src/logic-functions/utils/normalize-domain.ts +22 -0
  260. package/src/logic-functions/utils/normalize-linkedin-url.ts +23 -0
  261. package/src/logic-functions/utils/now-iso.ts +1 -0
  262. package/src/logic-functions/utils/parse-geo.ts +33 -0
  263. package/src/logic-functions/utils/parse-partial-date.ts +33 -0
  264. package/src/logic-functions/utils/parse-pdl-item.ts +82 -0
  265. package/src/logic-functions/utils/pick-multi-select.ts +32 -0
  266. package/src/logic-functions/utils/pick-select.ts +30 -0
  267. package/src/logic-functions/utils/pick-writable-standard.ts +27 -0
  268. package/src/logic-functions/utils/post-pdl-enrich.ts +89 -0
  269. package/src/logic-functions/utils/post-pdl-single-enrich.ts +59 -0
  270. package/src/logic-functions/utils/read-companies.ts +53 -0
  271. package/src/logic-functions/utils/read-people.ts +48 -0
  272. package/src/logic-functions/utils/resolve-logic-function-id.ts +11 -0
  273. package/src/logic-functions/utils/resolve-min-likelihood.ts +12 -0
  274. package/src/logic-functions/utils/resolve-update-fields-mode.ts +16 -0
  275. package/src/logic-functions/utils/run-batch-enrichment.ts +50 -0
  276. package/src/logic-functions/utils/run-single-enrichment.ts +44 -0
  277. package/src/logic-functions/utils/salary-transform.ts +18 -0
  278. package/src/logic-functions/utils/seed-enrichment-workflow.ts +131 -0
  279. package/src/logic-functions/utils/size-transform.ts +15 -0
  280. package/src/logic-functions/utils/to-bulk-enrich-input.ts +14 -0
  281. package/src/logic-functions/utils/to-json-array.ts +4 -0
  282. package/src/logic-functions/utils/to-json-object.ts +22 -0
  283. package/src/logic-functions/utils/to-number.ts +4 -0
  284. package/src/logic-functions/utils/to-string-array.ts +28 -0
  285. package/src/logic-functions/utils/to-text.ts +11 -0
  286. package/src/logic-functions/utils/update-companies-status.ts +18 -0
  287. package/src/logic-functions/utils/update-company-record.ts +15 -0
  288. package/src/logic-functions/utils/update-people-status.ts +18 -0
  289. package/src/logic-functions/utils/update-person-record.ts +15 -0
  290. package/src/roles/default-function.role.ts +41 -0
  291. package/src/types/address-parts.ts +9 -0
  292. package/src/types/address-value.ts +10 -0
  293. package/src/types/batch-enrichment-adapter.ts +43 -0
  294. package/src/types/bulk-enrich-input.ts +9 -0
  295. package/src/types/bulk-enrich-result.ts +11 -0
  296. package/src/types/company-id-by-match-key-cache.ts +1 -0
  297. package/src/types/company-match-keys.ts +6 -0
  298. package/src/types/company-node.ts +16 -0
  299. package/src/types/currency-value.ts +4 -0
  300. package/src/types/emails-value.ts +4 -0
  301. package/src/types/enrich-result.ts +11 -0
  302. package/src/types/enrich-status.ts +1 -0
  303. package/src/types/enrichment-workflow-seed.ts +9 -0
  304. package/src/types/full-name-value.ts +4 -0
  305. package/src/types/links-value.ts +5 -0
  306. package/src/types/mapped-record.ts +4 -0
  307. package/src/types/pdl-company-data.ts +50 -0
  308. package/src/types/pdl-company-enrich-params.ts +7 -0
  309. package/src/types/pdl-enrich-result.ts +4 -0
  310. package/src/types/pdl-person-data.ts +65 -0
  311. package/src/types/pdl-person-enrich-params.ts +8 -0
  312. package/src/types/person-node.ts +11 -0
  313. package/src/types/phones-value.ts +12 -0
  314. package/src/types/post-install-result.ts +5 -0
  315. package/src/types/seed-enrichment-workflow-result.ts +7 -0
  316. package/src/types/select-option-meta.ts +9 -0
  317. package/src/types/single-enrich-input.ts +7 -0
  318. package/src/types/tag-color.ts +26 -0
  319. package/src/types/update-fields-option.ts +4 -0
  320. package/src/types/workflow-bulk-records-trigger.ts +15 -0
  321. package/src/types/workflow-logic-function-step.ts +18 -0
  322. package/src/utils/__tests__/build-select-options.spec.ts +20 -0
  323. package/src/utils/__tests__/is-defined.spec.ts +16 -0
  324. package/src/utils/__tests__/is-unique-violation-error.spec.ts +71 -0
  325. package/src/utils/__tests__/normalize-enum-value.spec.ts +28 -0
  326. package/src/utils/__tests__/prune-undefined.spec.ts +22 -0
  327. package/src/utils/__tests__/strip-separators.spec.ts +13 -0
  328. package/src/utils/__tests__/to-error-message.spec.ts +13 -0
  329. package/src/utils/build-select-options.ts +16 -0
  330. package/src/utils/is-defined.ts +5 -0
  331. package/src/utils/is-unique-violation-error.ts +74 -0
  332. package/src/utils/normalize-enum-value.ts +15 -0
  333. package/src/utils/prune-undefined.ts +15 -0
  334. package/src/utils/strip-separators.ts +4 -0
  335. package/src/utils/to-error-message.ts +2 -0
  336. package/src/views/enriched-companies.view.ts +69 -0
  337. package/src/views/enriched-people.view.ts +74 -0
  338. package/tsconfig.json +42 -0
  339. package/tsconfig.spec.json +9 -0
  340. package/vitest.config.ts +31 -0
  341. package/vitest.unit.config.ts +14 -0
@@ -0,0 +1,313 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { type CoreApiClient } from 'twenty-client-sdk/core';
3
+
4
+ import { UPDATE_FIELDS_OPTIONS } from 'src/constants/update-fields-options';
5
+ import { createCoreApiClientMock } from 'src/logic-functions/__mocks__/create-core-api-client-mock';
6
+ import { PERSON_NODE_MOCK } from 'src/logic-functions/__mocks__/person-node.mock';
7
+ import { enrichPeopleCore } from 'src/logic-functions/handlers/enrich-people';
8
+ import { enrichPeople } from 'src/logic-functions/utils/enrich-people';
9
+ import { type PersonNode } from 'src/types/person-node';
10
+
11
+ vi.mock('src/logic-functions/utils/enrich-people', () => ({
12
+ enrichPeople: vi.fn(),
13
+ }));
14
+
15
+ const enrichPeopleMock = vi.mocked(enrichPeople);
16
+
17
+ type Captured = {
18
+ updatePerson?: Record<string, unknown>;
19
+ updatePeople?: { filter: unknown; data: Record<string, unknown> };
20
+ companiesQueried?: boolean;
21
+ createCompanyCalled?: boolean;
22
+ };
23
+
24
+ type MutationRequest = {
25
+ updatePerson?: { __args: { id: string; data: Record<string, unknown> } };
26
+ updatePeople?: { __args: { filter: unknown; data: Record<string, unknown> } };
27
+ createCompany?: unknown;
28
+ };
29
+
30
+ const captureMutation = (captured: Captured) => (request: unknown) => {
31
+ const mutation = request as MutationRequest;
32
+ if (mutation.updatePerson) {
33
+ captured.updatePerson = mutation.updatePerson.__args.data;
34
+ }
35
+ if (mutation.updatePeople) {
36
+ captured.updatePeople = {
37
+ filter: mutation.updatePeople.__args.filter,
38
+ data: mutation.updatePeople.__args.data,
39
+ };
40
+ }
41
+ if ('createCompany' in mutation) {
42
+ captured.createCompanyCalled = true;
43
+ }
44
+ };
45
+
46
+ const buildClient = (options: {
47
+ people: PersonNode[];
48
+ captured: Captured;
49
+ createCompanyId?: string;
50
+ }): CoreApiClient =>
51
+ createCoreApiClientMock({
52
+ queryResult: (request: unknown) => {
53
+ if ('companies' in (request as object)) {
54
+ options.captured.companiesQueried = true;
55
+
56
+ return { companies: { edges: [] } };
57
+ }
58
+
59
+ return { people: { edges: options.people.map((node) => ({ node })) } };
60
+ },
61
+ mutationResult: (request: unknown) =>
62
+ 'createCompany' in (request as object) &&
63
+ options.createCompanyId !== undefined
64
+ ? { createCompany: { id: options.createCompanyId } }
65
+ : {},
66
+ onMutation: captureMutation(options.captured),
67
+ });
68
+
69
+ const runOne = (client: CoreApiClient, recordId = 'p1') =>
70
+ enrichPeopleCore({ input: { records: [{ id: recordId }] }, client });
71
+
72
+ beforeEach(() => {
73
+ enrichPeopleMock.mockReset();
74
+ });
75
+
76
+ describe('enrichPeopleCore', () => {
77
+ it('fills empty standard fields and writes pdl metadata via updatePerson on a match', async () => {
78
+ enrichPeopleMock.mockResolvedValue([
79
+ {
80
+ outcome: 'matched',
81
+ httpStatus: 200,
82
+ likelihood: 8,
83
+ data: {
84
+ id: 'pdl1',
85
+ first_name: 'Jane',
86
+ last_name: 'Doe',
87
+ work_email: 'jane@acme.com',
88
+ job_title: 'CEO',
89
+ linkedin_url: 'https://linkedin.com/in/new',
90
+ },
91
+ },
92
+ ]);
93
+ const captured: Captured = {};
94
+ const client = buildClient({ people: [PERSON_NODE_MOCK], captured });
95
+
96
+ const result = await runOne(client);
97
+
98
+ expect(enrichPeopleMock).toHaveBeenCalledTimes(1);
99
+ expect(result.matched).toBe(1);
100
+ expect(captured.updatePerson?.name).toEqual({
101
+ firstName: 'Jane',
102
+ lastName: 'Doe',
103
+ });
104
+ expect(captured.updatePerson?.jobTitle).toBe('CEO');
105
+ expect(captured.updatePerson?.emails).toMatchObject({
106
+ primaryEmail: 'jane@acme.com',
107
+ });
108
+ expect(captured.updatePerson?.pdlEnrichmentStatus).toBe('MATCHED');
109
+ expect(captured.updatePerson?.pdlLikelihood).toBe(8);
110
+ expect(typeof captured.updatePerson?.pdlLastEnrichedAt).toBe('string');
111
+ expect(captured.updatePerson?.pdlRawPayload).toMatchObject({ id: 'pdl1' });
112
+ expect('linkedinLink' in (captured.updatePerson ?? {})).toBe(false);
113
+ });
114
+
115
+ it('links a found-or-created company when the person has none', async () => {
116
+ enrichPeopleMock.mockResolvedValue([
117
+ {
118
+ outcome: 'matched',
119
+ httpStatus: 200,
120
+ likelihood: 8,
121
+ data: {
122
+ id: 'pdl1',
123
+ work_email: 'jane@acme.com',
124
+ job_company_name: 'Acme',
125
+ job_company_website: 'acme.com',
126
+ },
127
+ },
128
+ ]);
129
+ const captured: Captured = {};
130
+ const client = buildClient({
131
+ people: [PERSON_NODE_MOCK],
132
+ captured,
133
+ createCompanyId: 'co-new',
134
+ });
135
+
136
+ await runOne(client);
137
+
138
+ expect(captured.updatePerson?.companyId).toBe('co-new');
139
+ });
140
+
141
+ it('does not touch company when the person already has one', async () => {
142
+ enrichPeopleMock.mockResolvedValue([
143
+ {
144
+ outcome: 'matched',
145
+ httpStatus: 200,
146
+ likelihood: 8,
147
+ data: { id: 'pdl1', work_email: 'jane@acme.com', job_company_name: 'Acme' },
148
+ },
149
+ ]);
150
+ const captured: Captured = {};
151
+ const client = buildClient({
152
+ people: [{ ...PERSON_NODE_MOCK, company: { id: 'co-existing' } }],
153
+ captured,
154
+ });
155
+
156
+ await runOne(client);
157
+
158
+ expect(captured.companiesQueried).toBeUndefined();
159
+ expect(captured.createCompanyCalled).toBeUndefined();
160
+ expect('companyId' in (captured.updatePerson ?? {})).toBe(false);
161
+ });
162
+
163
+ it('does not overwrite a populated standard field', async () => {
164
+ enrichPeopleMock.mockResolvedValue([
165
+ {
166
+ outcome: 'matched',
167
+ httpStatus: 200,
168
+ likelihood: 5,
169
+ data: { id: 'pdl1', job_title: 'CEO' },
170
+ },
171
+ ]);
172
+ const captured: Captured = {};
173
+ const client = buildClient({
174
+ people: [{ ...PERSON_NODE_MOCK, jobTitle: 'Existing Title' }],
175
+ captured,
176
+ });
177
+
178
+ await runOne(client);
179
+
180
+ expect('jobTitle' in (captured.updatePerson ?? {})).toBe(false);
181
+ });
182
+
183
+ it('records NOT_FOUND and writes the status via a batched updatePeople', async () => {
184
+ enrichPeopleMock.mockResolvedValue([{ outcome: 'not_found', httpStatus: 404 }]);
185
+ const captured: Captured = {};
186
+ const client = buildClient({ people: [PERSON_NODE_MOCK], captured });
187
+
188
+ const result = await runOne(client);
189
+
190
+ expect(result.notFound).toBe(1);
191
+ expect(captured.updatePerson).toBeUndefined();
192
+ expect(Object.keys(captured.updatePeople?.data ?? {}).sort()).toEqual([
193
+ 'pdlEnrichmentStatus',
194
+ 'pdlLastEnrichedAt',
195
+ ]);
196
+ expect(captured.updatePeople?.data.pdlEnrichmentStatus).toBe('NOT_FOUND');
197
+ expect(captured.updatePeople?.filter).toEqual({ id: { in: ['p1'] } });
198
+ });
199
+
200
+ it('records ERROR and reports failure on a PDL error', async () => {
201
+ enrichPeopleMock.mockResolvedValue([
202
+ { outcome: 'error', httpStatus: 500, message: 'boom' },
203
+ ]);
204
+ const captured: Captured = {};
205
+ const client = buildClient({ people: [PERSON_NODE_MOCK], captured });
206
+
207
+ const result = await runOne(client);
208
+
209
+ expect(result.errored).toBe(1);
210
+ expect(result.success).toBe(false);
211
+ expect(result.results[0].error).toBe('boom');
212
+ expect(captured.updatePeople?.data).toEqual({
213
+ pdlEnrichmentStatus: 'ERROR',
214
+ pdlLastEnrichedAt: expect.any(String),
215
+ });
216
+ });
217
+
218
+ it('overwrites a populated standard field when updateFields is "Yes and overwrite"', async () => {
219
+ enrichPeopleMock.mockResolvedValue([
220
+ {
221
+ outcome: 'matched',
222
+ httpStatus: 200,
223
+ likelihood: 5,
224
+ data: { id: 'pdl1', job_title: 'CEO' },
225
+ },
226
+ ]);
227
+ const captured: Captured = {};
228
+ const client = buildClient({
229
+ people: [{ ...PERSON_NODE_MOCK, jobTitle: 'Existing Title' }],
230
+ captured,
231
+ });
232
+
233
+ await enrichPeopleCore({
234
+ input: {
235
+ records: [{ id: 'p1' }],
236
+ updateFields: UPDATE_FIELDS_OPTIONS.overwrite,
237
+ },
238
+ client,
239
+ });
240
+
241
+ expect(captured.updatePerson?.jobTitle).toBe('CEO');
242
+ });
243
+
244
+ it('returns enriched data without writing when updateFields is "No"', async () => {
245
+ enrichPeopleMock.mockResolvedValue([
246
+ {
247
+ outcome: 'matched',
248
+ httpStatus: 200,
249
+ likelihood: 8,
250
+ data: { id: 'pdl1', first_name: 'Jane', job_title: 'CEO' },
251
+ },
252
+ ]);
253
+ const captured: Captured = {};
254
+ const client = buildClient({ people: [PERSON_NODE_MOCK], captured });
255
+
256
+ const result = await enrichPeopleCore({
257
+ input: { records: [{ id: 'p1' }], updateFields: UPDATE_FIELDS_OPTIONS.no },
258
+ client,
259
+ });
260
+
261
+ expect(result.matched).toBe(1);
262
+ expect(result.results[0].updatedFields).toEqual([]);
263
+ expect(result.results[0].data?.jobTitle).toBe('CEO');
264
+ expect(captured.updatePerson).toBeUndefined();
265
+ expect(captured.updatePeople).toBeUndefined();
266
+ expect(captured.createCompanyCalled).toBeUndefined();
267
+ });
268
+
269
+ it('skips when there is no usable identifier', async () => {
270
+ const captured: Captured = {};
271
+ const client = buildClient({
272
+ people: [{ ...PERSON_NODE_MOCK, linkedinLink: null }],
273
+ captured,
274
+ });
275
+
276
+ const result = await runOne(client);
277
+
278
+ expect(result.skipped).toBe(1);
279
+ expect(enrichPeopleMock).not.toHaveBeenCalled();
280
+ });
281
+
282
+ it('marks a missing record as ERROR', async () => {
283
+ const captured: Captured = {};
284
+ const client = buildClient({ people: [], captured });
285
+
286
+ const result = await runOne(client, 'missing');
287
+
288
+ expect(result.errored).toBe(1);
289
+ expect(result.results[0].error).toBe('Person missing not found');
290
+ expect(enrichPeopleMock).not.toHaveBeenCalled();
291
+ });
292
+
293
+ it('batches the PDL request and the not-found status write across records', async () => {
294
+ enrichPeopleMock.mockResolvedValue([
295
+ { outcome: 'not_found', httpStatus: 404 },
296
+ { outcome: 'not_found', httpStatus: 404 },
297
+ ]);
298
+ const captured: Captured = {};
299
+ const client = buildClient({
300
+ people: [PERSON_NODE_MOCK, { ...PERSON_NODE_MOCK, id: 'p2' }],
301
+ captured,
302
+ });
303
+
304
+ const result = await enrichPeopleCore({
305
+ input: { records: [{ id: 'p1' }, { id: 'p2' }] },
306
+ client,
307
+ });
308
+
309
+ expect(enrichPeopleMock).toHaveBeenCalledTimes(1);
310
+ expect(captured.updatePeople?.filter).toEqual({ id: { in: ['p1', 'p2'] } });
311
+ expect(result.notFound).toBe(2);
312
+ });
313
+ });
@@ -0,0 +1,198 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { type CoreApiClient } from 'twenty-client-sdk/core';
3
+
4
+ import { UPDATE_FIELDS_OPTIONS } from 'src/constants/update-fields-options';
5
+ import { createCoreApiClientMock } from 'src/logic-functions/__mocks__/create-core-api-client-mock';
6
+ import { PERSON_NODE_MOCK } from 'src/logic-functions/__mocks__/person-node.mock';
7
+ import { enrichPersonCore } from 'src/logic-functions/handlers/enrich-person';
8
+ import { enrichPerson } from 'src/logic-functions/utils/enrich-person';
9
+ import { type PersonNode } from 'src/types/person-node';
10
+
11
+ vi.mock('src/logic-functions/utils/enrich-person', () => ({
12
+ enrichPerson: vi.fn(),
13
+ }));
14
+
15
+ const enrichPersonMock = vi.mocked(enrichPerson);
16
+
17
+ type Captured = {
18
+ updatePerson?: Record<string, unknown>;
19
+ updatePeople?: { filter: unknown; data: Record<string, unknown> };
20
+ };
21
+
22
+ type MutationRequest = {
23
+ updatePerson?: { __args: { id: string; data: Record<string, unknown> } };
24
+ updatePeople?: { __args: { filter: unknown; data: Record<string, unknown> } };
25
+ };
26
+
27
+ const captureMutation = (captured: Captured) => (request: unknown) => {
28
+ const mutation = request as MutationRequest;
29
+ if (mutation.updatePerson) {
30
+ captured.updatePerson = mutation.updatePerson.__args.data;
31
+ }
32
+ if (mutation.updatePeople) {
33
+ captured.updatePeople = {
34
+ filter: mutation.updatePeople.__args.filter,
35
+ data: mutation.updatePeople.__args.data,
36
+ };
37
+ }
38
+ };
39
+
40
+ const buildClient = (people: PersonNode[], captured: Captured): CoreApiClient =>
41
+ createCoreApiClientMock({
42
+ queryResult: { people: { edges: people.map((node) => ({ node })) } },
43
+ onMutation: captureMutation(captured),
44
+ });
45
+
46
+ const runOne = (client: CoreApiClient, recordId = 'p1') =>
47
+ enrichPersonCore({ input: { recordId }, client });
48
+
49
+ beforeEach(() => {
50
+ enrichPersonMock.mockReset();
51
+ });
52
+
53
+ describe('enrichPersonCore', () => {
54
+ it('fills empty standard fields and writes pdl metadata via updatePerson on a match', async () => {
55
+ enrichPersonMock.mockResolvedValue([
56
+ {
57
+ outcome: 'matched',
58
+ httpStatus: 200,
59
+ likelihood: 8,
60
+ data: {
61
+ id: 'pdl1',
62
+ first_name: 'Jane',
63
+ last_name: 'Doe',
64
+ job_title: 'CEO',
65
+ },
66
+ },
67
+ ]);
68
+ const captured: Captured = {};
69
+ const client = buildClient([PERSON_NODE_MOCK], captured);
70
+
71
+ const result = await runOne(client);
72
+
73
+ expect(enrichPersonMock).toHaveBeenCalledTimes(1);
74
+ expect(result.status).toBe('MATCHED');
75
+ expect(result.success).toBe(true);
76
+ expect(result.recordId).toBe('p1');
77
+ expect(captured.updatePerson?.name).toEqual({
78
+ firstName: 'Jane',
79
+ lastName: 'Doe',
80
+ });
81
+ expect(captured.updatePerson?.jobTitle).toBe('CEO');
82
+ expect(captured.updatePerson?.pdlEnrichmentStatus).toBe('MATCHED');
83
+ expect(captured.updatePerson?.pdlLikelihood).toBe(8);
84
+ });
85
+
86
+ it('records NOT_FOUND and writes the status via updatePeople', async () => {
87
+ enrichPersonMock.mockResolvedValue([{ outcome: 'not_found', httpStatus: 404 }]);
88
+ const captured: Captured = {};
89
+ const client = buildClient([PERSON_NODE_MOCK], captured);
90
+
91
+ const result = await runOne(client);
92
+
93
+ expect(result.status).toBe('NOT_FOUND');
94
+ expect(captured.updatePerson).toBeUndefined();
95
+ expect(captured.updatePeople?.data.pdlEnrichmentStatus).toBe('NOT_FOUND');
96
+ expect(captured.updatePeople?.filter).toEqual({ id: { in: ['p1'] } });
97
+ });
98
+
99
+ it('records ERROR and reports failure on a PDL error', async () => {
100
+ enrichPersonMock.mockResolvedValue([
101
+ { outcome: 'error', httpStatus: 500, message: 'boom' },
102
+ ]);
103
+ const captured: Captured = {};
104
+ const client = buildClient([PERSON_NODE_MOCK], captured);
105
+
106
+ const result = await runOne(client);
107
+
108
+ expect(result.status).toBe('ERROR');
109
+ expect(result.success).toBe(false);
110
+ expect(result.error).toBe('boom');
111
+ expect(captured.updatePeople?.data).toEqual({
112
+ pdlEnrichmentStatus: 'ERROR',
113
+ pdlLastEnrichedAt: expect.any(String),
114
+ });
115
+ });
116
+
117
+ it('overwrites a populated standard field when updateFields is "Yes and overwrite"', async () => {
118
+ enrichPersonMock.mockResolvedValue([
119
+ {
120
+ outcome: 'matched',
121
+ httpStatus: 200,
122
+ likelihood: 5,
123
+ data: { id: 'pdl1', job_title: 'CEO' },
124
+ },
125
+ ]);
126
+ const captured: Captured = {};
127
+ const client = buildClient(
128
+ [{ ...PERSON_NODE_MOCK, jobTitle: 'Existing Title' }],
129
+ captured,
130
+ );
131
+
132
+ await enrichPersonCore({
133
+ input: { recordId: 'p1', updateFields: UPDATE_FIELDS_OPTIONS.overwrite },
134
+ client,
135
+ });
136
+
137
+ expect(captured.updatePerson?.jobTitle).toBe('CEO');
138
+ });
139
+
140
+ it('returns enriched data without writing when updateFields is "No"', async () => {
141
+ enrichPersonMock.mockResolvedValue([
142
+ {
143
+ outcome: 'matched',
144
+ httpStatus: 200,
145
+ likelihood: 8,
146
+ data: { id: 'pdl1', first_name: 'Jane', job_title: 'CEO' },
147
+ },
148
+ ]);
149
+ const captured: Captured = {};
150
+ const client = buildClient([PERSON_NODE_MOCK], captured);
151
+
152
+ const result = await enrichPersonCore({
153
+ input: { recordId: 'p1', updateFields: UPDATE_FIELDS_OPTIONS.no },
154
+ client,
155
+ });
156
+
157
+ expect(result.status).toBe('MATCHED');
158
+ expect(result.updatedFields).toEqual([]);
159
+ expect(result.data?.jobTitle).toBe('CEO');
160
+ expect(captured.updatePerson).toBeUndefined();
161
+ expect(captured.updatePeople).toBeUndefined();
162
+ });
163
+
164
+ it('skips when there is no usable identifier', async () => {
165
+ const captured: Captured = {};
166
+ const client = buildClient(
167
+ [{ ...PERSON_NODE_MOCK, linkedinLink: null }],
168
+ captured,
169
+ );
170
+
171
+ const result = await runOne(client);
172
+
173
+ expect(result.status).toBe('SKIPPED');
174
+ expect(enrichPersonMock).not.toHaveBeenCalled();
175
+ });
176
+
177
+ it('marks a missing record as ERROR', async () => {
178
+ const captured: Captured = {};
179
+ const client = buildClient([], captured);
180
+
181
+ const result = await runOne(client, 'missing');
182
+
183
+ expect(result.status).toBe('ERROR');
184
+ expect(result.error).toBe('Person missing not found');
185
+ expect(enrichPersonMock).not.toHaveBeenCalled();
186
+ });
187
+
188
+ it('returns an ERROR without touching PDL when no record id is provided', async () => {
189
+ const captured: Captured = {};
190
+ const client = buildClient([PERSON_NODE_MOCK], captured);
191
+
192
+ const result = await enrichPersonCore({ input: {}, client });
193
+
194
+ expect(result.status).toBe('ERROR');
195
+ expect(result.error).toBe('No record id was provided to enrich.');
196
+ expect(enrichPersonMock).not.toHaveBeenCalled();
197
+ });
198
+ });
@@ -0,0 +1,153 @@
1
+ import { type MetadataApiClient } from 'twenty-client-sdk/metadata';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+
4
+ import { PDL_LOGIC_FUNCTION_CONSTANTS } from 'src/constants/universal-identifiers';
5
+ import { createCoreApiClientMock } from 'src/logic-functions/__mocks__/create-core-api-client-mock';
6
+ import { postInstallCore } from 'src/logic-functions/handlers/post-install';
7
+
8
+ type AnyRequest = Record<string, unknown>;
9
+
10
+ const buildMetadataClient = (
11
+ logicFunctions: { id: string; universalIdentifier: string }[],
12
+ ): MetadataApiClient =>
13
+ ({
14
+ query: vi.fn(() =>
15
+ Promise.resolve({ findManyLogicFunctions: logicFunctions }),
16
+ ),
17
+ }) as unknown as MetadataApiClient;
18
+
19
+ const buildCoreClient = () =>
20
+ createCoreApiClientMock({
21
+ queryResult: (request: unknown) => {
22
+ const req = request as AnyRequest;
23
+ if ('workflows' in req) {
24
+ return { workflows: { edges: [] } };
25
+ }
26
+ if ('workflowVersions' in req) {
27
+ return {
28
+ workflowVersions: {
29
+ edges: [{ node: { id: 'version-1', status: 'DRAFT' } }],
30
+ },
31
+ };
32
+ }
33
+ return {};
34
+ },
35
+ mutationResult: (request: unknown) =>
36
+ 'createWorkflow' in (request as AnyRequest)
37
+ ? { createWorkflow: { id: 'workflow-new' } }
38
+ : {},
39
+ });
40
+
41
+ describe('postInstallCore', () => {
42
+ it('seeds a workflow for each resolvable enrich function', async () => {
43
+ const metadataClient = buildMetadataClient([
44
+ {
45
+ id: 'company-fn',
46
+ universalIdentifier:
47
+ PDL_LOGIC_FUNCTION_CONSTANTS.enrichCompanies.universalIdentifier,
48
+ },
49
+ {
50
+ id: 'person-fn',
51
+ universalIdentifier:
52
+ PDL_LOGIC_FUNCTION_CONSTANTS.enrichPeople.universalIdentifier,
53
+ },
54
+ ]);
55
+
56
+ const result = await postInstallCore({
57
+ coreClient: buildCoreClient(),
58
+ metadataClient,
59
+ });
60
+
61
+ expect(result.seededWorkflows.map((workflow) => workflow.objectNameSingular)).toEqual([
62
+ 'company',
63
+ 'person',
64
+ ]);
65
+ expect(
66
+ result.seededWorkflows.every((workflow) => workflow.status === 'created'),
67
+ ).toBe(true);
68
+ });
69
+
70
+ it('skips a workflow when its logic function is not installed', async () => {
71
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
72
+
73
+ const metadataClient = buildMetadataClient([
74
+ {
75
+ id: 'company-fn',
76
+ universalIdentifier:
77
+ PDL_LOGIC_FUNCTION_CONSTANTS.enrichCompanies.universalIdentifier,
78
+ },
79
+ ]);
80
+
81
+ const result = await postInstallCore({
82
+ coreClient: buildCoreClient(),
83
+ metadataClient,
84
+ });
85
+
86
+ expect(result.seededWorkflows).toHaveLength(1);
87
+ expect(result.seededWorkflows[0]?.objectNameSingular).toBe('company');
88
+ expect(warnSpy).toHaveBeenCalledTimes(1);
89
+
90
+ warnSpy.mockRestore();
91
+ });
92
+
93
+ it('continues seeding remaining workflows when one seed fails', async () => {
94
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
95
+
96
+ const metadataClient = buildMetadataClient([
97
+ {
98
+ id: 'company-fn',
99
+ universalIdentifier:
100
+ PDL_LOGIC_FUNCTION_CONSTANTS.enrichCompanies.universalIdentifier,
101
+ },
102
+ {
103
+ id: 'person-fn',
104
+ universalIdentifier:
105
+ PDL_LOGIC_FUNCTION_CONSTANTS.enrichPeople.universalIdentifier,
106
+ },
107
+ ]);
108
+
109
+ const coreClient = createCoreApiClientMock({
110
+ queryResult: (request: unknown) => {
111
+ const req = request as AnyRequest;
112
+ if ('workflows' in req) {
113
+ return { workflows: { edges: [] } };
114
+ }
115
+ if ('workflowVersions' in req) {
116
+ return {
117
+ workflowVersions: {
118
+ edges: [{ node: { id: 'version-1', status: 'DRAFT' } }],
119
+ },
120
+ };
121
+ }
122
+ return {};
123
+ },
124
+ mutationResult: (request: unknown) => {
125
+ const req = request as AnyRequest;
126
+ if ('createWorkflow' in req) {
127
+ const args = (req.createWorkflow as AnyRequest)?.__args as AnyRequest;
128
+ const workflowName = (args?.data as AnyRequest)?.name;
129
+ return workflowName === 'Enrich companies with People Data Labs'
130
+ ? { createWorkflow: {} }
131
+ : { createWorkflow: { id: 'workflow-person' } };
132
+ }
133
+ return {};
134
+ },
135
+ });
136
+
137
+ const result = await postInstallCore({ coreClient, metadataClient });
138
+
139
+ expect(result.seededWorkflows).toHaveLength(2);
140
+ expect(result.seededWorkflows[0]).toMatchObject({
141
+ objectNameSingular: 'company',
142
+ status: 'failed',
143
+ });
144
+ expect(result.seededWorkflows[0]?.error).toBeDefined();
145
+ expect(result.seededWorkflows[1]).toMatchObject({
146
+ objectNameSingular: 'person',
147
+ status: 'created',
148
+ });
149
+ expect(warnSpy).toHaveBeenCalledTimes(1);
150
+
151
+ warnSpy.mockRestore();
152
+ });
153
+ });
@@ -0,0 +1,42 @@
1
+ import { COMPANY_MATCH_COST_DOLLARS } from 'src/constants/company-match-cost-dollars';
2
+ import { buildCompanyMatchedData } from 'src/logic-functions/utils/build-company-matched-data';
3
+ import { enrichCompanies } from 'src/logic-functions/utils/enrich-companies';
4
+ import { extractCompanyMatchParams } from 'src/logic-functions/utils/extract-company-match-params';
5
+ import { readCompanies } from 'src/logic-functions/utils/read-companies';
6
+ import { updateCompaniesStatus } from 'src/logic-functions/utils/update-companies-status';
7
+ import { updateCompanyRecord } from 'src/logic-functions/utils/update-company-record';
8
+ import { type BatchEnrichmentAdapter } from 'src/types/batch-enrichment-adapter';
9
+ import { type CompanyNode } from 'src/types/company-node';
10
+ import { type PdlCompanyData } from 'src/types/pdl-company-data';
11
+ import { type PdlCompanyEnrichParams } from 'src/types/pdl-company-enrich-params';
12
+
13
+ export const companyEnrichmentAdapter: BatchEnrichmentAdapter<
14
+ CompanyNode,
15
+ PdlCompanyData,
16
+ PdlCompanyEnrichParams
17
+ > = {
18
+ objectNameSingular: 'Company',
19
+ noIdentifierMessage:
20
+ 'No usable identifier (domain, LinkedIn, or name) to match against PDL.',
21
+ costPerMatchDollars: COMPANY_MATCH_COST_DOLLARS,
22
+ readRecords: readCompanies,
23
+ getNodeId: (node) => node.id,
24
+ extractParams: extractCompanyMatchParams,
25
+ enrichBatch: enrichCompanies,
26
+ buildMatchedData: ({
27
+ node,
28
+ outcome,
29
+ enrichedAt,
30
+ overrideExistingValues,
31
+ shouldPersist,
32
+ }) =>
33
+ buildCompanyMatchedData({
34
+ node,
35
+ outcome,
36
+ enrichedAt,
37
+ overrideExistingValues,
38
+ shouldPersist,
39
+ }),
40
+ updateOne: updateCompanyRecord,
41
+ updateManyStatus: updateCompaniesStatus,
42
+ };