@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.
- package/.claude/settings.local.json +7 -0
- package/.nvmrc +1 -0
- package/.oxlintrc.json +58 -0
- package/.yarnrc.yml +1 -0
- package/README.md +16 -0
- package/package.json +37 -0
- package/public/gallery/cover.png +0 -0
- package/public/people-data-labs-icon.png +0 -0
- package/src/__tests__/global-setup.ts +87 -0
- package/src/__tests__/people-data-labs.integration-test.ts +25 -0
- package/src/application-config.ts +18 -0
- package/src/command-menu-items/enrich-companies.ts +14 -0
- package/src/command-menu-items/enrich-people.ts +14 -0
- package/src/constants/__tests__/select-option-constants.spec.ts +142 -0
- package/src/constants/billing-margin-multiplier.ts +1 -0
- package/src/constants/company-match-cost-dollars.ts +1 -0
- package/src/constants/company-type-options.ts +46 -0
- package/src/constants/enrichment-status-options.ts +19 -0
- package/src/constants/enrichment-workflow-seeds.ts +27 -0
- package/src/constants/funding-stage-options.ts +195 -0
- package/src/constants/industry-options.ts +1033 -0
- package/src/constants/inferred-salary-options.ts +81 -0
- package/src/constants/job-role-options.ts +172 -0
- package/src/constants/job-title-class-options.ts +39 -0
- package/src/constants/job-title-sub-role-options.ts +746 -0
- package/src/constants/location-continent-options.ts +53 -0
- package/src/constants/metro-options.ts +2692 -0
- package/src/constants/mic-exchange-options.ts +494 -0
- package/src/constants/micro-credits-per-dollar.ts +1 -0
- package/src/constants/pdl-base-url.ts +1 -0
- package/src/constants/person-match-cost-dollars.ts +1 -0
- package/src/constants/seniority-options.ts +74 -0
- package/src/constants/sex-options.ts +18 -0
- package/src/constants/size-options.ts +60 -0
- package/src/constants/universal-identifiers.ts +1516 -0
- package/src/constants/update-fields-option-values.ts +5 -0
- package/src/constants/update-fields-options.ts +7 -0
- package/src/fields/company/pdl-affiliated-profiles.field.ts +20 -0
- package/src/fields/company/pdl-alternative-domains.field.ts +20 -0
- package/src/fields/company/pdl-alternative-names.field.ts +20 -0
- package/src/fields/company/pdl-company-type.field.ts +28 -0
- package/src/fields/company/pdl-employee-count-by-country.field.ts +21 -0
- package/src/fields/company/pdl-employee-count.field.ts +19 -0
- package/src/fields/company/pdl-enrichment-status.field.ts +29 -0
- package/src/fields/company/pdl-facebook-url.field.ts +19 -0
- package/src/fields/company/pdl-founded-year.field.ts +19 -0
- package/src/fields/company/pdl-funding-stages.field.ts +28 -0
- package/src/fields/company/pdl-headline.field.ts +19 -0
- package/src/fields/company/pdl-id.field.ts +19 -0
- package/src/fields/company/pdl-industry-detail.field.ts +20 -0
- package/src/fields/company/pdl-industry.field.ts +28 -0
- package/src/fields/company/pdl-last-enriched-at.field.ts +20 -0
- package/src/fields/company/pdl-last-funding-date.field.ts +20 -0
- package/src/fields/company/pdl-latest-funding-stage.field.ts +29 -0
- package/src/fields/company/pdl-legal-name.field.ts +19 -0
- package/src/fields/company/pdl-linkedin-id.field.ts +19 -0
- package/src/fields/company/pdl-location-continent.field.ts +29 -0
- package/src/fields/company/pdl-location-metro.field.ts +28 -0
- package/src/fields/company/pdl-mic-exchange.field.ts +28 -0
- package/src/fields/company/pdl-naics.field.ts +19 -0
- package/src/fields/company/pdl-number-funding-rounds.field.ts +20 -0
- package/src/fields/company/pdl-raw-payload.field.ts +19 -0
- package/src/fields/company/pdl-sic.field.ts +19 -0
- package/src/fields/company/pdl-size-range.field.ts +28 -0
- package/src/fields/company/pdl-summary.field.ts +19 -0
- package/src/fields/company/pdl-tags.field.ts +19 -0
- package/src/fields/company/pdl-ticker.field.ts +19 -0
- package/src/fields/company/pdl-total-funding.field.ts +19 -0
- package/src/fields/company/pdl-twitter-url.field.ts +19 -0
- package/src/fields/person/pdl-birth-date.field.ts +19 -0
- package/src/fields/person/pdl-birth-year.field.ts +19 -0
- package/src/fields/person/pdl-certifications.field.ts +19 -0
- package/src/fields/person/pdl-education.field.ts +19 -0
- package/src/fields/person/pdl-enrichment-status.field.ts +29 -0
- package/src/fields/person/pdl-experience.field.ts +19 -0
- package/src/fields/person/pdl-facebook-url.field.ts +19 -0
- package/src/fields/person/pdl-github-url.field.ts +19 -0
- package/src/fields/person/pdl-headline.field.ts +19 -0
- package/src/fields/person/pdl-id.field.ts +19 -0
- package/src/fields/person/pdl-industry.field.ts +28 -0
- package/src/fields/person/pdl-inferred-salary.field.ts +28 -0
- package/src/fields/person/pdl-interests.field.ts +19 -0
- package/src/fields/person/pdl-job-onet-code.field.ts +19 -0
- package/src/fields/person/pdl-job-role.field.ts +28 -0
- package/src/fields/person/pdl-job-start-date.field.ts +19 -0
- package/src/fields/person/pdl-job-summary.field.ts +19 -0
- package/src/fields/person/pdl-job-title-class.field.ts +28 -0
- package/src/fields/person/pdl-job-title-sub-role.field.ts +29 -0
- package/src/fields/person/pdl-languages.field.ts +19 -0
- package/src/fields/person/pdl-last-enriched-at.field.ts +19 -0
- package/src/fields/person/pdl-likelihood.field.ts +19 -0
- package/src/fields/person/pdl-linkedin-connections.field.ts +20 -0
- package/src/fields/person/pdl-linkedin-username.field.ts +20 -0
- package/src/fields/person/pdl-location-metro.field.ts +28 -0
- package/src/fields/person/pdl-location.field.ts +19 -0
- package/src/fields/person/pdl-name-aliases.field.ts +19 -0
- package/src/fields/person/pdl-profiles.field.ts +19 -0
- package/src/fields/person/pdl-raw-payload.field.ts +19 -0
- package/src/fields/person/pdl-seniority.field.ts +28 -0
- package/src/fields/person/pdl-sex.field.ts +28 -0
- package/src/fields/person/pdl-skills.field.ts +19 -0
- package/src/fields/person/pdl-summary.field.ts +19 -0
- package/src/fields/person/pdl-twitter-url.field.ts +19 -0
- package/src/fields/person/pdl-years-experience.field.ts +21 -0
- package/src/front-components/enrich-companies-component-effect.tsx +21 -0
- package/src/front-components/enrich-people-component-effect.tsx +21 -0
- package/src/front-components/utils/call-enrich-logic-function.utils.ts +21 -0
- package/src/logic-functions/__mocks__/company-node.mock.ts +11 -0
- package/src/logic-functions/__mocks__/create-core-api-client-mock.ts +35 -0
- package/src/logic-functions/__mocks__/pdl-company-data.mock.ts +27 -0
- package/src/logic-functions/__mocks__/pdl-person-data.mock.ts +23 -0
- package/src/logic-functions/__mocks__/person-node.mock.ts +12 -0
- package/src/logic-functions/enrich-companies.function.ts +76 -0
- package/src/logic-functions/enrich-company.function.ts +73 -0
- package/src/logic-functions/enrich-people.function.ts +76 -0
- package/src/logic-functions/enrich-person.function.ts +73 -0
- package/src/logic-functions/errors/__tests__/pdl-config-error.spec.ts +16 -0
- package/src/logic-functions/errors/__tests__/pdl-invalid-input-error.spec.ts +16 -0
- package/src/logic-functions/errors/__tests__/pdl-operation-error.spec.ts +16 -0
- package/src/logic-functions/errors/__tests__/pdl-record-not-found-error.spec.ts +21 -0
- package/src/logic-functions/errors/pdl-config-error.ts +8 -0
- package/src/logic-functions/errors/pdl-error-code.ts +8 -0
- package/src/logic-functions/errors/pdl-error.ts +12 -0
- package/src/logic-functions/errors/pdl-invalid-input-error.ts +8 -0
- package/src/logic-functions/errors/pdl-operation-error.ts +8 -0
- package/src/logic-functions/errors/pdl-record-not-found-error.ts +22 -0
- package/src/logic-functions/handlers/__tests__/enrich-companies.spec.ts +159 -0
- package/src/logic-functions/handlers/__tests__/enrich-company.spec.ts +147 -0
- package/src/logic-functions/handlers/__tests__/enrich-people.spec.ts +313 -0
- package/src/logic-functions/handlers/__tests__/enrich-person.spec.ts +198 -0
- package/src/logic-functions/handlers/__tests__/post-install.spec.ts +153 -0
- package/src/logic-functions/handlers/company-enrichment-adapter.ts +42 -0
- package/src/logic-functions/handlers/company-single-enrichment-adapter.ts +15 -0
- package/src/logic-functions/handlers/enrich-companies.ts +15 -0
- package/src/logic-functions/handlers/enrich-company.ts +19 -0
- package/src/logic-functions/handlers/enrich-people.ts +15 -0
- package/src/logic-functions/handlers/enrich-person.ts +19 -0
- package/src/logic-functions/handlers/person-enrichment-adapter.ts +29 -0
- package/src/logic-functions/handlers/person-single-enrichment-adapter.ts +15 -0
- package/src/logic-functions/handlers/post-install.ts +69 -0
- package/src/logic-functions/post-install.function.ts +17 -0
- package/src/logic-functions/utils/__tests__/aggregate-bulk-enrich-result.spec.ts +52 -0
- package/src/logic-functions/utils/__tests__/build-address.spec.ts +30 -0
- package/src/logic-functions/utils/__tests__/build-allowed-values.spec.ts +16 -0
- package/src/logic-functions/utils/__tests__/build-bulk-records-trigger.spec.ts +26 -0
- package/src/logic-functions/utils/__tests__/build-company-create-data.spec.ts +37 -0
- package/src/logic-functions/utils/__tests__/build-company-match-keys.spec.ts +35 -0
- package/src/logic-functions/utils/__tests__/build-company-matched-data.spec.ts +56 -0
- package/src/logic-functions/utils/__tests__/build-currency-from-usd.spec.ts +17 -0
- package/src/logic-functions/utils/__tests__/build-emails.spec.ts +23 -0
- package/src/logic-functions/utils/__tests__/build-error-result.spec.ts +16 -0
- package/src/logic-functions/utils/__tests__/build-full-name.spec.ts +64 -0
- package/src/logic-functions/utils/__tests__/build-links.spec.ts +25 -0
- package/src/logic-functions/utils/__tests__/build-logic-function-step.spec.ts +33 -0
- package/src/logic-functions/utils/__tests__/build-matched-result.spec.ts +33 -0
- package/src/logic-functions/utils/__tests__/build-not-found-result.spec.ts +15 -0
- package/src/logic-functions/utils/__tests__/build-person-matched-data.spec.ts +117 -0
- package/src/logic-functions/utils/__tests__/build-person-name-param.spec.ts +17 -0
- package/src/logic-functions/utils/__tests__/build-phones.spec.ts +32 -0
- package/src/logic-functions/utils/__tests__/build-skipped-result.spec.ts +17 -0
- package/src/logic-functions/utils/__tests__/capitalize-name.spec.ts +26 -0
- package/src/logic-functions/utils/__tests__/capitalize-name.test.ts +17 -0
- package/src/logic-functions/utils/__tests__/charge-matched-enrichments.spec.ts +56 -0
- package/src/logic-functions/utils/__tests__/chunk.spec.ts +25 -0
- package/src/logic-functions/utils/__tests__/enrich-companies.spec.ts +26 -0
- package/src/logic-functions/utils/__tests__/enrich-company.spec.ts +54 -0
- package/src/logic-functions/utils/__tests__/enrich-people.spec.ts +32 -0
- package/src/logic-functions/utils/__tests__/enrich-person.spec.ts +62 -0
- package/src/logic-functions/utils/__tests__/extract-company-match-params.spec.ts +86 -0
- package/src/logic-functions/utils/__tests__/extract-pdl-error-message.spec.ts +17 -0
- package/src/logic-functions/utils/__tests__/extract-person-match-params.spec.ts +75 -0
- package/src/logic-functions/utils/__tests__/extract-record-ids.spec.ts +32 -0
- package/src/logic-functions/utils/__tests__/find-company-id-by-filter.spec.ts +42 -0
- package/src/logic-functions/utils/__tests__/find-company-id.spec.ts +118 -0
- package/src/logic-functions/utils/__tests__/find-existing-workflow-id.spec.ts +26 -0
- package/src/logic-functions/utils/__tests__/find-or-create-current-company.spec.ts +203 -0
- package/src/logic-functions/utils/__tests__/get-pdl-api-key.spec.ts +38 -0
- package/src/logic-functions/utils/__tests__/is-empty-address.spec.ts +20 -0
- package/src/logic-functions/utils/__tests__/is-empty-emails.spec.ts +14 -0
- package/src/logic-functions/utils/__tests__/is-empty-full-name.spec.ts +14 -0
- package/src/logic-functions/utils/__tests__/is-empty-links.spec.ts +14 -0
- package/src/logic-functions/utils/__tests__/is-empty-phones.spec.ts +14 -0
- package/src/logic-functions/utils/__tests__/is-empty-text.spec.ts +15 -0
- package/src/logic-functions/utils/__tests__/map-company.spec.ts +48 -0
- package/src/logic-functions/utils/__tests__/map-person.spec.ts +81 -0
- package/src/logic-functions/utils/__tests__/normalize-domain.spec.ts +19 -0
- package/src/logic-functions/utils/__tests__/normalize-linkedin-url.spec.ts +22 -0
- package/src/logic-functions/utils/__tests__/now-iso.spec.ts +9 -0
- package/src/logic-functions/utils/__tests__/parse-geo.spec.ts +21 -0
- package/src/logic-functions/utils/__tests__/parse-partial-date.spec.ts +29 -0
- package/src/logic-functions/utils/__tests__/parse-pdl-item.spec.ts +88 -0
- package/src/logic-functions/utils/__tests__/pick-multi-select.spec.ts +28 -0
- package/src/logic-functions/utils/__tests__/pick-select.spec.ts +38 -0
- package/src/logic-functions/utils/__tests__/pick-writable-standard.spec.ts +37 -0
- package/src/logic-functions/utils/__tests__/post-pdl-enrich.spec.ts +192 -0
- package/src/logic-functions/utils/__tests__/post-pdl-single-enrich.spec.ts +172 -0
- package/src/logic-functions/utils/__tests__/read-companies.spec.ts +39 -0
- package/src/logic-functions/utils/__tests__/read-people.spec.ts +40 -0
- package/src/logic-functions/utils/__tests__/resolve-logic-function-id.spec.ts +25 -0
- package/src/logic-functions/utils/__tests__/resolve-update-fields-mode.spec.ts +34 -0
- package/src/logic-functions/utils/__tests__/run-batch-enrichment.spec.ts +524 -0
- package/src/logic-functions/utils/__tests__/salary-transform.spec.ts +15 -0
- package/src/logic-functions/utils/__tests__/seed-enrichment-workflow.spec.ts +136 -0
- package/src/logic-functions/utils/__tests__/size-transform.spec.ts +18 -0
- package/src/logic-functions/utils/__tests__/to-json-array.spec.ts +16 -0
- package/src/logic-functions/utils/__tests__/to-json-object.spec.ts +18 -0
- package/src/logic-functions/utils/__tests__/to-number.spec.ts +17 -0
- package/src/logic-functions/utils/__tests__/to-string-array.spec.ts +17 -0
- package/src/logic-functions/utils/__tests__/to-text.spec.ts +20 -0
- package/src/logic-functions/utils/__tests__/update-companies-status.spec.ts +32 -0
- package/src/logic-functions/utils/__tests__/update-company-record.spec.ts +25 -0
- package/src/logic-functions/utils/__tests__/update-people-status.spec.ts +32 -0
- package/src/logic-functions/utils/__tests__/update-person-record.spec.ts +25 -0
- package/src/logic-functions/utils/aggregate-bulk-enrich-result.ts +21 -0
- package/src/logic-functions/utils/build-address.ts +40 -0
- package/src/logic-functions/utils/build-allowed-values.ts +5 -0
- package/src/logic-functions/utils/build-bulk-records-trigger.ts +25 -0
- package/src/logic-functions/utils/build-company-create-data.ts +35 -0
- package/src/logic-functions/utils/build-company-match-keys.ts +16 -0
- package/src/logic-functions/utils/build-company-matched-data.ts +56 -0
- package/src/logic-functions/utils/build-currency-from-usd.ts +16 -0
- package/src/logic-functions/utils/build-emails.ts +34 -0
- package/src/logic-functions/utils/build-error-result.ts +18 -0
- package/src/logic-functions/utils/build-full-name.ts +38 -0
- package/src/logic-functions/utils/build-links.ts +23 -0
- package/src/logic-functions/utils/build-logic-function-step.ts +30 -0
- package/src/logic-functions/utils/build-matched-result.ts +21 -0
- package/src/logic-functions/utils/build-not-found-result.ts +9 -0
- package/src/logic-functions/utils/build-person-matched-data.ts +78 -0
- package/src/logic-functions/utils/build-person-name-param.ts +19 -0
- package/src/logic-functions/utils/build-phones.ts +40 -0
- package/src/logic-functions/utils/build-skipped-result.ts +15 -0
- package/src/logic-functions/utils/capitalize-name.ts +8 -0
- package/src/logic-functions/utils/charge-matched-enrichments.ts +29 -0
- package/src/logic-functions/utils/chunk.ts +18 -0
- package/src/logic-functions/utils/enrich-chunk.ts +361 -0
- package/src/logic-functions/utils/enrich-companies.ts +21 -0
- package/src/logic-functions/utils/enrich-company.ts +23 -0
- package/src/logic-functions/utils/enrich-people.ts +22 -0
- package/src/logic-functions/utils/enrich-person.ts +24 -0
- package/src/logic-functions/utils/extract-company-match-params.ts +52 -0
- package/src/logic-functions/utils/extract-pdl-error-message.ts +47 -0
- package/src/logic-functions/utils/extract-person-match-params.ts +59 -0
- package/src/logic-functions/utils/extract-record-ids.ts +18 -0
- package/src/logic-functions/utils/find-company-id-by-filter.ts +28 -0
- package/src/logic-functions/utils/find-company-id.ts +59 -0
- package/src/logic-functions/utils/find-existing-workflow-id.ts +18 -0
- package/src/logic-functions/utils/find-or-create-current-company.ts +108 -0
- package/src/logic-functions/utils/get-pdl-api-key.ts +15 -0
- package/src/logic-functions/utils/internal-field-names.ts +6 -0
- package/src/logic-functions/utils/is-empty-address.ts +20 -0
- package/src/logic-functions/utils/is-empty-emails.ts +13 -0
- package/src/logic-functions/utils/is-empty-full-name.ts +13 -0
- package/src/logic-functions/utils/is-empty-links.ts +13 -0
- package/src/logic-functions/utils/is-empty-phones.ts +13 -0
- package/src/logic-functions/utils/is-empty-text.ts +4 -0
- package/src/logic-functions/utils/map-company.ts +115 -0
- package/src/logic-functions/utils/map-person.ts +136 -0
- package/src/logic-functions/utils/normalize-domain.ts +22 -0
- package/src/logic-functions/utils/normalize-linkedin-url.ts +23 -0
- package/src/logic-functions/utils/now-iso.ts +1 -0
- package/src/logic-functions/utils/parse-geo.ts +33 -0
- package/src/logic-functions/utils/parse-partial-date.ts +33 -0
- package/src/logic-functions/utils/parse-pdl-item.ts +82 -0
- package/src/logic-functions/utils/pick-multi-select.ts +32 -0
- package/src/logic-functions/utils/pick-select.ts +30 -0
- package/src/logic-functions/utils/pick-writable-standard.ts +27 -0
- package/src/logic-functions/utils/post-pdl-enrich.ts +89 -0
- package/src/logic-functions/utils/post-pdl-single-enrich.ts +59 -0
- package/src/logic-functions/utils/read-companies.ts +53 -0
- package/src/logic-functions/utils/read-people.ts +48 -0
- package/src/logic-functions/utils/resolve-logic-function-id.ts +11 -0
- package/src/logic-functions/utils/resolve-min-likelihood.ts +12 -0
- package/src/logic-functions/utils/resolve-update-fields-mode.ts +16 -0
- package/src/logic-functions/utils/run-batch-enrichment.ts +50 -0
- package/src/logic-functions/utils/run-single-enrichment.ts +44 -0
- package/src/logic-functions/utils/salary-transform.ts +18 -0
- package/src/logic-functions/utils/seed-enrichment-workflow.ts +131 -0
- package/src/logic-functions/utils/size-transform.ts +15 -0
- package/src/logic-functions/utils/to-bulk-enrich-input.ts +14 -0
- package/src/logic-functions/utils/to-json-array.ts +4 -0
- package/src/logic-functions/utils/to-json-object.ts +22 -0
- package/src/logic-functions/utils/to-number.ts +4 -0
- package/src/logic-functions/utils/to-string-array.ts +28 -0
- package/src/logic-functions/utils/to-text.ts +11 -0
- package/src/logic-functions/utils/update-companies-status.ts +18 -0
- package/src/logic-functions/utils/update-company-record.ts +15 -0
- package/src/logic-functions/utils/update-people-status.ts +18 -0
- package/src/logic-functions/utils/update-person-record.ts +15 -0
- package/src/roles/default-function.role.ts +41 -0
- package/src/types/address-parts.ts +9 -0
- package/src/types/address-value.ts +10 -0
- package/src/types/batch-enrichment-adapter.ts +43 -0
- package/src/types/bulk-enrich-input.ts +9 -0
- package/src/types/bulk-enrich-result.ts +11 -0
- package/src/types/company-id-by-match-key-cache.ts +1 -0
- package/src/types/company-match-keys.ts +6 -0
- package/src/types/company-node.ts +16 -0
- package/src/types/currency-value.ts +4 -0
- package/src/types/emails-value.ts +4 -0
- package/src/types/enrich-result.ts +11 -0
- package/src/types/enrich-status.ts +1 -0
- package/src/types/enrichment-workflow-seed.ts +9 -0
- package/src/types/full-name-value.ts +4 -0
- package/src/types/links-value.ts +5 -0
- package/src/types/mapped-record.ts +4 -0
- package/src/types/pdl-company-data.ts +50 -0
- package/src/types/pdl-company-enrich-params.ts +7 -0
- package/src/types/pdl-enrich-result.ts +4 -0
- package/src/types/pdl-person-data.ts +65 -0
- package/src/types/pdl-person-enrich-params.ts +8 -0
- package/src/types/person-node.ts +11 -0
- package/src/types/phones-value.ts +12 -0
- package/src/types/post-install-result.ts +5 -0
- package/src/types/seed-enrichment-workflow-result.ts +7 -0
- package/src/types/select-option-meta.ts +9 -0
- package/src/types/single-enrich-input.ts +7 -0
- package/src/types/tag-color.ts +26 -0
- package/src/types/update-fields-option.ts +4 -0
- package/src/types/workflow-bulk-records-trigger.ts +15 -0
- package/src/types/workflow-logic-function-step.ts +18 -0
- package/src/utils/__tests__/build-select-options.spec.ts +20 -0
- package/src/utils/__tests__/is-defined.spec.ts +16 -0
- package/src/utils/__tests__/is-unique-violation-error.spec.ts +71 -0
- package/src/utils/__tests__/normalize-enum-value.spec.ts +28 -0
- package/src/utils/__tests__/prune-undefined.spec.ts +22 -0
- package/src/utils/__tests__/strip-separators.spec.ts +13 -0
- package/src/utils/__tests__/to-error-message.spec.ts +13 -0
- package/src/utils/build-select-options.ts +16 -0
- package/src/utils/is-defined.ts +5 -0
- package/src/utils/is-unique-violation-error.ts +74 -0
- package/src/utils/normalize-enum-value.ts +15 -0
- package/src/utils/prune-undefined.ts +15 -0
- package/src/utils/strip-separators.ts +4 -0
- package/src/utils/to-error-message.ts +2 -0
- package/src/views/enriched-companies.view.ts +69 -0
- package/src/views/enriched-people.view.ts +74 -0
- package/tsconfig.json +42 -0
- package/tsconfig.spec.json +9 -0
- package/vitest.config.ts +31 -0
- package/vitest.unit.config.ts +14 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { isArray, isString } from '@sniptt/guards';
|
|
2
|
+
|
|
3
|
+
import { INDUSTRY_OPTIONS } from 'src/constants/industry-options';
|
|
4
|
+
import { INFERRED_SALARY_OPTIONS } from 'src/constants/inferred-salary-options';
|
|
5
|
+
import { JOB_ROLE_OPTIONS } from 'src/constants/job-role-options';
|
|
6
|
+
import { JOB_TITLE_CLASS_OPTIONS } from 'src/constants/job-title-class-options';
|
|
7
|
+
import { JOB_TITLE_SUB_ROLE_OPTIONS } from 'src/constants/job-title-sub-role-options';
|
|
8
|
+
import { METRO_OPTIONS } from 'src/constants/metro-options';
|
|
9
|
+
import { SENIORITY_OPTIONS } from 'src/constants/seniority-options';
|
|
10
|
+
import { SEX_OPTIONS } from 'src/constants/sex-options';
|
|
11
|
+
import { toJsonArray } from 'src/logic-functions/utils/to-json-array';
|
|
12
|
+
import { toNumber } from 'src/logic-functions/utils/to-number';
|
|
13
|
+
import { toStringArray } from 'src/logic-functions/utils/to-string-array';
|
|
14
|
+
import { toText } from 'src/logic-functions/utils/to-text';
|
|
15
|
+
import { buildAddress } from 'src/logic-functions/utils/build-address';
|
|
16
|
+
import { buildEmails } from 'src/logic-functions/utils/build-emails';
|
|
17
|
+
import { buildFullName } from 'src/logic-functions/utils/build-full-name';
|
|
18
|
+
import { buildLinks } from 'src/logic-functions/utils/build-links';
|
|
19
|
+
import { buildPhones } from 'src/logic-functions/utils/build-phones';
|
|
20
|
+
import { parsePartialDate } from 'src/logic-functions/utils/parse-partial-date';
|
|
21
|
+
import { buildAllowedValues } from 'src/logic-functions/utils/build-allowed-values';
|
|
22
|
+
import { pickMultiSelect } from 'src/logic-functions/utils/pick-multi-select';
|
|
23
|
+
import { pickSelect } from 'src/logic-functions/utils/pick-select';
|
|
24
|
+
import { salaryTransform } from 'src/logic-functions/utils/salary-transform';
|
|
25
|
+
import { type MappedRecord } from 'src/types/mapped-record';
|
|
26
|
+
import { type PdlPersonData } from 'src/types/pdl-person-data';
|
|
27
|
+
import { pruneUndefined } from 'src/utils/prune-undefined';
|
|
28
|
+
|
|
29
|
+
const SENIORITY_VALUES = buildAllowedValues(SENIORITY_OPTIONS);
|
|
30
|
+
const JOB_ROLE_VALUES = buildAllowedValues(JOB_ROLE_OPTIONS);
|
|
31
|
+
const JOB_TITLE_CLASS_VALUES = buildAllowedValues(JOB_TITLE_CLASS_OPTIONS);
|
|
32
|
+
const JOB_TITLE_SUB_ROLE_VALUES = buildAllowedValues(JOB_TITLE_SUB_ROLE_OPTIONS);
|
|
33
|
+
const INDUSTRY_VALUES = buildAllowedValues(INDUSTRY_OPTIONS);
|
|
34
|
+
const INFERRED_SALARY_VALUES = buildAllowedValues(INFERRED_SALARY_OPTIONS);
|
|
35
|
+
const SEX_VALUES = buildAllowedValues(SEX_OPTIONS);
|
|
36
|
+
const METRO_VALUES = buildAllowedValues(METRO_OPTIONS);
|
|
37
|
+
|
|
38
|
+
export const mapPerson = (personData: PdlPersonData): MappedRecord => {
|
|
39
|
+
const emailEntries = (isArray(personData.emails) ? personData.emails : []).map(
|
|
40
|
+
(rawEmail) => (isString(rawEmail) ? rawEmail : rawEmail.address),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const standard = pruneUndefined({
|
|
44
|
+
name: buildFullName({
|
|
45
|
+
firstName: personData.first_name,
|
|
46
|
+
lastName: personData.last_name,
|
|
47
|
+
fullName: personData.full_name,
|
|
48
|
+
}),
|
|
49
|
+
emails: buildEmails([
|
|
50
|
+
personData.work_email,
|
|
51
|
+
personData.recommended_personal_email,
|
|
52
|
+
...(isArray(personData.personal_emails) ? personData.personal_emails : []),
|
|
53
|
+
...emailEntries,
|
|
54
|
+
]),
|
|
55
|
+
phones: buildPhones([
|
|
56
|
+
personData.mobile_phone,
|
|
57
|
+
...(isArray(personData.phone_numbers) ? personData.phone_numbers : []),
|
|
58
|
+
]),
|
|
59
|
+
jobTitle: toText(personData.job_title),
|
|
60
|
+
linkedinLink: buildLinks({ url: personData.linkedin_url }),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const pdl = pruneUndefined({
|
|
64
|
+
pdlId: toText(personData.id),
|
|
65
|
+
|
|
66
|
+
pdlSeniority: pickMultiSelect({
|
|
67
|
+
rawValues: personData.job_title_levels,
|
|
68
|
+
allowedValues: SENIORITY_VALUES,
|
|
69
|
+
}),
|
|
70
|
+
pdlJobRole: pickSelect({
|
|
71
|
+
raw: personData.job_title_role,
|
|
72
|
+
allowedValues: JOB_ROLE_VALUES,
|
|
73
|
+
}),
|
|
74
|
+
pdlJobTitleClass: pickSelect({
|
|
75
|
+
raw: personData.job_title_class,
|
|
76
|
+
allowedValues: JOB_TITLE_CLASS_VALUES,
|
|
77
|
+
}),
|
|
78
|
+
pdlJobTitleSubRole: pickSelect({
|
|
79
|
+
raw: personData.job_title_sub_role,
|
|
80
|
+
allowedValues: JOB_TITLE_SUB_ROLE_VALUES,
|
|
81
|
+
}),
|
|
82
|
+
pdlIndustry: pickSelect({
|
|
83
|
+
raw: personData.industry,
|
|
84
|
+
allowedValues: INDUSTRY_VALUES,
|
|
85
|
+
}),
|
|
86
|
+
pdlInferredSalary: pickSelect({
|
|
87
|
+
raw: personData.inferred_salary,
|
|
88
|
+
allowedValues: INFERRED_SALARY_VALUES,
|
|
89
|
+
transform: salaryTransform,
|
|
90
|
+
}),
|
|
91
|
+
pdlSex: pickSelect({ raw: personData.sex, allowedValues: SEX_VALUES }),
|
|
92
|
+
pdlLocationMetro: pickSelect({
|
|
93
|
+
raw: personData.location_metro,
|
|
94
|
+
allowedValues: METRO_VALUES,
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
pdlHeadline: toText(personData.headline),
|
|
98
|
+
pdlSummary: toText(personData.summary),
|
|
99
|
+
pdlJobSummary: toText(personData.job_summary),
|
|
100
|
+
pdlJobOnetCode: toText(personData.job_onet_code),
|
|
101
|
+
pdlLinkedinUsername: toText(personData.linkedin_username),
|
|
102
|
+
|
|
103
|
+
pdlYearsExperience: toNumber(personData.inferred_years_experience),
|
|
104
|
+
pdlLinkedinConnections: toNumber(personData.linkedin_connections),
|
|
105
|
+
pdlBirthYear: toNumber(personData.birth_year),
|
|
106
|
+
|
|
107
|
+
pdlBirthDate: parsePartialDate(personData.birth_date),
|
|
108
|
+
pdlJobStartDate: parsePartialDate(personData.job_start_date),
|
|
109
|
+
|
|
110
|
+
pdlGithubUrl: buildLinks({ url: personData.github_url }),
|
|
111
|
+
pdlTwitterUrl: buildLinks({ url: personData.twitter_url }),
|
|
112
|
+
pdlFacebookUrl: buildLinks({ url: personData.facebook_url }),
|
|
113
|
+
|
|
114
|
+
pdlSkills: toStringArray(personData.skills),
|
|
115
|
+
pdlInterests: toStringArray(personData.interests),
|
|
116
|
+
pdlNameAliases: toStringArray(personData.name_aliases),
|
|
117
|
+
|
|
118
|
+
pdlExperience: toJsonArray(personData.experience),
|
|
119
|
+
pdlEducation: toJsonArray(personData.education),
|
|
120
|
+
pdlCertifications: toJsonArray(personData.certifications),
|
|
121
|
+
pdlProfiles: toJsonArray(personData.profiles),
|
|
122
|
+
pdlLanguages: toJsonArray(personData.languages),
|
|
123
|
+
|
|
124
|
+
pdlLocation: buildAddress({
|
|
125
|
+
street1: personData.location_street_address,
|
|
126
|
+
street2: personData.location_address_line_2,
|
|
127
|
+
city: personData.location_locality,
|
|
128
|
+
postcode: personData.location_postal_code,
|
|
129
|
+
state: personData.location_region,
|
|
130
|
+
country: personData.location_country,
|
|
131
|
+
geo: personData.location_geo,
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return { standard, pdl };
|
|
136
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { toText } from 'src/logic-functions/utils/to-text';
|
|
2
|
+
import { isDefined } from 'src/utils/is-defined';
|
|
3
|
+
|
|
4
|
+
const SCHEME_REGEX = /^[a-z][a-z0-9+.-]*:\/\//;
|
|
5
|
+
const LEADING_WWW_REGEX = /^www\./;
|
|
6
|
+
|
|
7
|
+
export const normalizeDomain = (
|
|
8
|
+
rawDomainValue: unknown,
|
|
9
|
+
): string | undefined => {
|
|
10
|
+
const domainText = toText(rawDomainValue);
|
|
11
|
+
if (!isDefined(domainText)) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const bareHost = domainText
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(SCHEME_REGEX, '')
|
|
18
|
+
.split('/')[0]
|
|
19
|
+
.replace(LEADING_WWW_REGEX, '');
|
|
20
|
+
|
|
21
|
+
return bareHost === '' ? undefined : bareHost;
|
|
22
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { toText } from 'src/logic-functions/utils/to-text';
|
|
2
|
+
import { isDefined } from 'src/utils/is-defined';
|
|
3
|
+
|
|
4
|
+
const SCHEME_REGEX = /^[a-z][a-z0-9+.-]*:\/\//;
|
|
5
|
+
const LEADING_WWW_REGEX = /^www\./;
|
|
6
|
+
const TRAILING_SLASHES_REGEX = /\/+$/;
|
|
7
|
+
|
|
8
|
+
export const normalizeLinkedinUrl = (
|
|
9
|
+
rawLinkedinUrl: unknown,
|
|
10
|
+
): string | undefined => {
|
|
11
|
+
const linkedinUrlText = toText(rawLinkedinUrl);
|
|
12
|
+
if (!isDefined(linkedinUrlText)) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const canonicalUrlWithPath = linkedinUrlText
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.replace(SCHEME_REGEX, '')
|
|
19
|
+
.replace(LEADING_WWW_REGEX, '')
|
|
20
|
+
.replace(TRAILING_SLASHES_REGEX, '');
|
|
21
|
+
|
|
22
|
+
return canonicalUrlWithPath === '' ? undefined : canonicalUrlWithPath;
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const nowIso = (): string => new Date().toISOString();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { toText } from 'src/logic-functions/utils/to-text';
|
|
2
|
+
import { isDefined } from 'src/utils/is-defined';
|
|
3
|
+
|
|
4
|
+
const NULL_COORDINATES = { lat: null, lng: null } as const;
|
|
5
|
+
|
|
6
|
+
export const parseGeo = (
|
|
7
|
+
geo: unknown,
|
|
8
|
+
): { lat: number | null; lng: number | null } => {
|
|
9
|
+
const geoText = toText(geo);
|
|
10
|
+
|
|
11
|
+
if (!isDefined(geoText)) {
|
|
12
|
+
return { ...NULL_COORDINATES };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const latitudeAndLongitudeParts = geoText.split(',');
|
|
16
|
+
if (latitudeAndLongitudeParts.length !== 2) {
|
|
17
|
+
return { ...NULL_COORDINATES };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const latitude = Number.parseFloat(latitudeAndLongitudeParts[0]);
|
|
21
|
+
const longitude = Number.parseFloat(latitudeAndLongitudeParts[1]);
|
|
22
|
+
|
|
23
|
+
const isLatitudeInRange =
|
|
24
|
+
Number.isFinite(latitude) && latitude >= -90 && latitude <= 90;
|
|
25
|
+
const isLongitudeInRange =
|
|
26
|
+
Number.isFinite(longitude) && longitude >= -180 && longitude <= 180;
|
|
27
|
+
|
|
28
|
+
if (!isLatitudeInRange || !isLongitudeInRange) {
|
|
29
|
+
return { ...NULL_COORDINATES };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { lat: latitude, lng: longitude };
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { toText } from 'src/logic-functions/utils/to-text';
|
|
2
|
+
import { isDefined } from 'src/utils/is-defined';
|
|
3
|
+
|
|
4
|
+
const PARTIAL_DATE_REGEX = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/;
|
|
5
|
+
|
|
6
|
+
export const parsePartialDate = (raw: unknown): string | undefined => {
|
|
7
|
+
const dateText = toText(raw);
|
|
8
|
+
|
|
9
|
+
if (!isDefined(dateText)) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const partialDateMatch = dateText.match(PARTIAL_DATE_REGEX);
|
|
14
|
+
if (!isDefined(partialDateMatch)) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const [, year, month = '01', day = '01'] = partialDateMatch;
|
|
19
|
+
const monthNumber = Number(month);
|
|
20
|
+
const dayNumber = Number(day);
|
|
21
|
+
|
|
22
|
+
const candidateDate = new Date(`${year}-${month}-${day}T00:00:00Z`);
|
|
23
|
+
const isRealCalendarDate =
|
|
24
|
+
!Number.isNaN(candidateDate.getTime()) &&
|
|
25
|
+
candidateDate.getUTCMonth() + 1 === monthNumber &&
|
|
26
|
+
candidateDate.getUTCDate() === dayNumber;
|
|
27
|
+
|
|
28
|
+
if (!isRealCalendarDate) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `${year}-${month}-${day}`;
|
|
33
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { isNumber, isObject } from '@sniptt/guards';
|
|
2
|
+
|
|
3
|
+
import { extractPdlErrorMessage } from 'src/logic-functions/utils/extract-pdl-error-message';
|
|
4
|
+
import { type PdlEnrichResult } from 'src/types/pdl-enrich-result';
|
|
5
|
+
import { isDefined } from 'src/utils/is-defined';
|
|
6
|
+
|
|
7
|
+
const ASSUMED_SUCCESS_STATUS_WHEN_MISSING = 200;
|
|
8
|
+
|
|
9
|
+
const ENVELOPE_FIELD_NAMES = new Set(['status', 'likelihood']);
|
|
10
|
+
|
|
11
|
+
const extractMatchedData = (
|
|
12
|
+
responseItem: Record<string, unknown>,
|
|
13
|
+
): Record<string, unknown> => {
|
|
14
|
+
if (isObject(responseItem.data)) {
|
|
15
|
+
return responseItem.data as Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return Object.fromEntries(
|
|
19
|
+
Object.entries(responseItem).filter(
|
|
20
|
+
([fieldName]) => !ENVELOPE_FIELD_NAMES.has(fieldName),
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const parsePdlItem = <TData>({
|
|
26
|
+
item,
|
|
27
|
+
requestedMinLikelihood,
|
|
28
|
+
}: {
|
|
29
|
+
item: unknown;
|
|
30
|
+
requestedMinLikelihood?: number;
|
|
31
|
+
}): PdlEnrichResult<TData> => {
|
|
32
|
+
if (!isObject(item)) {
|
|
33
|
+
return {
|
|
34
|
+
outcome: 'error',
|
|
35
|
+
httpStatus: 0,
|
|
36
|
+
message: 'People Data Labs returned a malformed response item.',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const responseItem = item as Record<string, unknown>;
|
|
41
|
+
const httpStatus = isNumber(responseItem.status)
|
|
42
|
+
? responseItem.status
|
|
43
|
+
: ASSUMED_SUCCESS_STATUS_WHEN_MISSING;
|
|
44
|
+
|
|
45
|
+
if (httpStatus === 404) {
|
|
46
|
+
return { outcome: 'not_found', httpStatus: 404 };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (httpStatus < 200 || httpStatus >= 300) {
|
|
50
|
+
return {
|
|
51
|
+
outcome: 'error',
|
|
52
|
+
httpStatus,
|
|
53
|
+
message: extractPdlErrorMessage({ json: responseItem, httpStatus }),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const matchedData = extractMatchedData(responseItem);
|
|
58
|
+
|
|
59
|
+
if (Object.keys(matchedData).length === 0) {
|
|
60
|
+
return { outcome: 'not_found', httpStatus };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const matchLikelihood = isNumber(responseItem.likelihood)
|
|
64
|
+
? responseItem.likelihood
|
|
65
|
+
: undefined;
|
|
66
|
+
|
|
67
|
+
const isMatchBelowRequestedThreshold =
|
|
68
|
+
isDefined(requestedMinLikelihood) &&
|
|
69
|
+
isDefined(matchLikelihood) &&
|
|
70
|
+
matchLikelihood < requestedMinLikelihood;
|
|
71
|
+
|
|
72
|
+
if (isMatchBelowRequestedThreshold) {
|
|
73
|
+
return { outcome: 'not_found', httpStatus };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
outcome: 'matched',
|
|
78
|
+
httpStatus,
|
|
79
|
+
likelihood: matchLikelihood,
|
|
80
|
+
data: matchedData as TData,
|
|
81
|
+
};
|
|
82
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isArray, isNonEmptyArray } from '@sniptt/guards';
|
|
2
|
+
|
|
3
|
+
import { pickSelect } from 'src/logic-functions/utils/pick-select';
|
|
4
|
+
import { isDefined } from 'src/utils/is-defined';
|
|
5
|
+
import { normalizeEnumValue } from 'src/utils/normalize-enum-value';
|
|
6
|
+
|
|
7
|
+
export const pickMultiSelect = ({
|
|
8
|
+
rawValues,
|
|
9
|
+
allowedValues,
|
|
10
|
+
transform = normalizeEnumValue,
|
|
11
|
+
}: {
|
|
12
|
+
rawValues: unknown;
|
|
13
|
+
allowedValues: Set<string>;
|
|
14
|
+
transform?: (value: string) => string | undefined;
|
|
15
|
+
}): string[] | undefined => {
|
|
16
|
+
if (!isArray(rawValues)) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const values: string[] = [];
|
|
21
|
+
const seenValues = new Set<string>();
|
|
22
|
+
|
|
23
|
+
for (const raw of rawValues) {
|
|
24
|
+
const value = pickSelect({ raw, allowedValues, transform });
|
|
25
|
+
if (isDefined(value) && !seenValues.has(value)) {
|
|
26
|
+
seenValues.add(value);
|
|
27
|
+
values.push(value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return isNonEmptyArray(values) ? values : undefined;
|
|
32
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isNonEmptyString, isString } from '@sniptt/guards';
|
|
2
|
+
|
|
3
|
+
import { isDefined } from 'src/utils/is-defined';
|
|
4
|
+
import { normalizeEnumValue } from 'src/utils/normalize-enum-value';
|
|
5
|
+
|
|
6
|
+
export const pickSelect = ({
|
|
7
|
+
raw,
|
|
8
|
+
allowedValues,
|
|
9
|
+
transform = normalizeEnumValue,
|
|
10
|
+
}: {
|
|
11
|
+
raw: unknown;
|
|
12
|
+
allowedValues: Set<string>;
|
|
13
|
+
transform?: (value: string) => string | undefined;
|
|
14
|
+
}): string | undefined => {
|
|
15
|
+
if (!isString(raw)) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const trimmed = raw.trim();
|
|
20
|
+
if (!isNonEmptyString(trimmed)) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const candidate = transform(trimmed);
|
|
25
|
+
if (!isDefined(candidate) || !allowedValues.has(candidate)) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return candidate;
|
|
30
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isDefined } from 'src/utils/is-defined';
|
|
2
|
+
|
|
3
|
+
export const pickWritableStandard = ({
|
|
4
|
+
standard,
|
|
5
|
+
current,
|
|
6
|
+
emptyChecks,
|
|
7
|
+
overrideExistingValues,
|
|
8
|
+
}: {
|
|
9
|
+
standard: Record<string, unknown>;
|
|
10
|
+
current: Record<string, unknown>;
|
|
11
|
+
emptyChecks: Record<string, (current: unknown) => boolean>;
|
|
12
|
+
overrideExistingValues?: boolean;
|
|
13
|
+
}): Record<string, unknown> => {
|
|
14
|
+
const writableFields: Record<string, unknown> = {};
|
|
15
|
+
|
|
16
|
+
for (const [fieldName, fieldValue] of Object.entries(standard)) {
|
|
17
|
+
const isEmptyCheck = emptyChecks[fieldName];
|
|
18
|
+
if (
|
|
19
|
+
isDefined(isEmptyCheck) &&
|
|
20
|
+
(overrideExistingValues === true || isEmptyCheck(current[fieldName]))
|
|
21
|
+
) {
|
|
22
|
+
writableFields[fieldName] = fieldValue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return writableFields;
|
|
27
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { isNumber, isObject } from '@sniptt/guards';
|
|
2
|
+
|
|
3
|
+
import { PDL_BASE_URL } from 'src/constants/pdl-base-url';
|
|
4
|
+
import { extractPdlErrorMessage } from 'src/logic-functions/utils/extract-pdl-error-message';
|
|
5
|
+
import { getPdlApiKey } from 'src/logic-functions/utils/get-pdl-api-key';
|
|
6
|
+
import { parsePdlItem } from 'src/logic-functions/utils/parse-pdl-item';
|
|
7
|
+
import { type PdlEnrichResult } from 'src/types/pdl-enrich-result';
|
|
8
|
+
|
|
9
|
+
export const postPdlBulkEnrich = async <TData>({
|
|
10
|
+
path,
|
|
11
|
+
requests,
|
|
12
|
+
}: {
|
|
13
|
+
path: string;
|
|
14
|
+
requests: Record<string, unknown>[];
|
|
15
|
+
}): Promise<PdlEnrichResult<TData>[]> => {
|
|
16
|
+
if (requests.length === 0) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const apiKey = getPdlApiKey();
|
|
21
|
+
|
|
22
|
+
const failAll = ({
|
|
23
|
+
message,
|
|
24
|
+
httpStatus,
|
|
25
|
+
}: {
|
|
26
|
+
message: string;
|
|
27
|
+
httpStatus: number;
|
|
28
|
+
}): PdlEnrichResult<TData>[] =>
|
|
29
|
+
requests.map(() => ({ outcome: 'error', httpStatus, message }));
|
|
30
|
+
|
|
31
|
+
let response: Response;
|
|
32
|
+
try {
|
|
33
|
+
response = await fetch(`${PDL_BASE_URL}${path}`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
'X-Api-Key': apiKey,
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({ requests: requests.map((params) => ({ params })) }),
|
|
40
|
+
});
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
43
|
+
|
|
44
|
+
return failAll({ message: `PDL request failed: ${message}`, httpStatus: 0 });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let json: unknown;
|
|
48
|
+
try {
|
|
49
|
+
json = await response.json();
|
|
50
|
+
} catch {
|
|
51
|
+
return failAll({
|
|
52
|
+
message: `PDL returned a non-JSON response (HTTP ${response.status}).`,
|
|
53
|
+
httpStatus: response.status,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
const message = isObject(json)
|
|
59
|
+
? extractPdlErrorMessage({
|
|
60
|
+
json: json as Record<string, unknown>,
|
|
61
|
+
httpStatus: response.status,
|
|
62
|
+
})
|
|
63
|
+
: `PDL request failed (HTTP ${response.status}).`;
|
|
64
|
+
|
|
65
|
+
return failAll({ message, httpStatus: response.status });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const responseItems = Array.isArray(json)
|
|
69
|
+
? json
|
|
70
|
+
: isObject(json) && Array.isArray((json as Record<string, unknown>).responses)
|
|
71
|
+
? ((json as Record<string, unknown>).responses as unknown[])
|
|
72
|
+
: [];
|
|
73
|
+
|
|
74
|
+
if (responseItems.length !== requests.length) {
|
|
75
|
+
return failAll({
|
|
76
|
+
message: `People Data Labs returned ${responseItems.length} results for ${requests.length} requests (HTTP ${response.status}).`,
|
|
77
|
+
httpStatus: response.status,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return requests.map((requestParams, index) =>
|
|
82
|
+
parsePdlItem<TData>({
|
|
83
|
+
item: responseItems[index],
|
|
84
|
+
requestedMinLikelihood: isNumber(requestParams.min_likelihood)
|
|
85
|
+
? requestParams.min_likelihood
|
|
86
|
+
: undefined,
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isNumber, isObject } from '@sniptt/guards';
|
|
2
|
+
|
|
3
|
+
import { PDL_BASE_URL } from 'src/constants/pdl-base-url';
|
|
4
|
+
import { getPdlApiKey } from 'src/logic-functions/utils/get-pdl-api-key';
|
|
5
|
+
import { parsePdlItem } from 'src/logic-functions/utils/parse-pdl-item';
|
|
6
|
+
import { type PdlEnrichResult } from 'src/types/pdl-enrich-result';
|
|
7
|
+
|
|
8
|
+
export const postPdlSingleEnrich = async <TData>({
|
|
9
|
+
path,
|
|
10
|
+
params,
|
|
11
|
+
}: {
|
|
12
|
+
path: string;
|
|
13
|
+
params: Record<string, unknown>;
|
|
14
|
+
}): Promise<PdlEnrichResult<TData>> => {
|
|
15
|
+
const apiKey = getPdlApiKey();
|
|
16
|
+
|
|
17
|
+
let response: Response;
|
|
18
|
+
try {
|
|
19
|
+
response = await fetch(`${PDL_BASE_URL}${path}`, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
'X-Api-Key': apiKey,
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify(params),
|
|
26
|
+
});
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
outcome: 'error',
|
|
32
|
+
httpStatus: 0,
|
|
33
|
+
message: `PDL request failed: ${message}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let json: unknown;
|
|
38
|
+
try {
|
|
39
|
+
json = await response.json();
|
|
40
|
+
} catch {
|
|
41
|
+
return {
|
|
42
|
+
outcome: 'error',
|
|
43
|
+
httpStatus: response.status,
|
|
44
|
+
message: `PDL returned a non-JSON response (HTTP ${response.status}).`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const responseItem = isObject(json) ? (json as Record<string, unknown>) : {};
|
|
49
|
+
const responseItemWithStatus = isNumber(responseItem.status)
|
|
50
|
+
? responseItem
|
|
51
|
+
: { ...responseItem, status: response.status };
|
|
52
|
+
|
|
53
|
+
return parsePdlItem<TData>({
|
|
54
|
+
item: responseItemWithStatus,
|
|
55
|
+
requestedMinLikelihood: isNumber(params.min_likelihood)
|
|
56
|
+
? params.min_likelihood
|
|
57
|
+
: undefined,
|
|
58
|
+
});
|
|
59
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isNonEmptyString, isObject } from '@sniptt/guards';
|
|
2
|
+
import { type CoreApiClient } from 'twenty-client-sdk/core';
|
|
3
|
+
|
|
4
|
+
import { type CompanyNode } from 'src/types/company-node';
|
|
5
|
+
|
|
6
|
+
export const readCompanies = async ({
|
|
7
|
+
client,
|
|
8
|
+
recordIds,
|
|
9
|
+
}: {
|
|
10
|
+
client: CoreApiClient;
|
|
11
|
+
recordIds: string[];
|
|
12
|
+
}): Promise<CompanyNode[]> => {
|
|
13
|
+
if (recordIds.length === 0) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = (await client.query({
|
|
18
|
+
companies: {
|
|
19
|
+
__args: { filter: { id: { in: recordIds } }, first: recordIds.length },
|
|
20
|
+
edges: {
|
|
21
|
+
node: {
|
|
22
|
+
id: true,
|
|
23
|
+
name: true,
|
|
24
|
+
domainName: { primaryLinkUrl: true },
|
|
25
|
+
linkedinLink: { primaryLinkUrl: true },
|
|
26
|
+
address: {
|
|
27
|
+
addressStreet1: true,
|
|
28
|
+
addressStreet2: true,
|
|
29
|
+
addressCity: true,
|
|
30
|
+
addressPostcode: true,
|
|
31
|
+
addressState: true,
|
|
32
|
+
addressCountry: true,
|
|
33
|
+
},
|
|
34
|
+
pdlId: true,
|
|
35
|
+
pdlLastEnrichedAt: true,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
})) as { companies?: { edges?: { node: CompanyNode }[] } };
|
|
40
|
+
|
|
41
|
+
const edges = result.companies?.edges;
|
|
42
|
+
if (!Array.isArray(edges)) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return edges
|
|
47
|
+
.map((edge) => edge?.node)
|
|
48
|
+
.filter(
|
|
49
|
+
(companyNode): companyNode is CompanyNode =>
|
|
50
|
+
isObject(companyNode) &&
|
|
51
|
+
isNonEmptyString((companyNode as { id?: unknown }).id),
|
|
52
|
+
);
|
|
53
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isNonEmptyString, isObject } from '@sniptt/guards';
|
|
2
|
+
import { type CoreApiClient } from 'twenty-client-sdk/core';
|
|
3
|
+
|
|
4
|
+
import { type PersonNode } from 'src/types/person-node';
|
|
5
|
+
|
|
6
|
+
export const readPeople = async ({
|
|
7
|
+
client,
|
|
8
|
+
recordIds,
|
|
9
|
+
}: {
|
|
10
|
+
client: CoreApiClient;
|
|
11
|
+
recordIds: string[];
|
|
12
|
+
}): Promise<PersonNode[]> => {
|
|
13
|
+
if (recordIds.length === 0) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = (await client.query({
|
|
18
|
+
people: {
|
|
19
|
+
__args: { filter: { id: { in: recordIds } }, first: recordIds.length },
|
|
20
|
+
edges: {
|
|
21
|
+
node: {
|
|
22
|
+
id: true,
|
|
23
|
+
name: { firstName: true, lastName: true },
|
|
24
|
+
emails: { primaryEmail: true },
|
|
25
|
+
phones: { primaryPhoneNumber: true },
|
|
26
|
+
jobTitle: true,
|
|
27
|
+
linkedinLink: { primaryLinkUrl: true },
|
|
28
|
+
company: { id: true, name: true },
|
|
29
|
+
pdlId: true,
|
|
30
|
+
pdlLastEnrichedAt: true,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
})) as { people?: { edges?: { node: PersonNode }[] } };
|
|
35
|
+
|
|
36
|
+
const edges = result.people?.edges;
|
|
37
|
+
if (!Array.isArray(edges)) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return edges
|
|
42
|
+
.map((edge) => edge?.node)
|
|
43
|
+
.filter(
|
|
44
|
+
(personNode): personNode is PersonNode =>
|
|
45
|
+
isObject(personNode) &&
|
|
46
|
+
isNonEmptyString((personNode as { id?: unknown }).id),
|
|
47
|
+
);
|
|
48
|
+
};
|