fixes and sdk version bump
This commit is contained in:
@@ -27,8 +27,7 @@ trigger-agnostic core in `src/logic-functions/handlers/`:
|
||||
|
||||
Run locally: `yarn twenty dev:function:exec -n enrich-person -p '{"recordId":"<id>"}'`.
|
||||
|
||||
**Deferred to a later PR:** linking the `pdlCurrentCompany` relation (find-or-create a
|
||||
Company from `job_company_*`), enrichment metering/billing, and auto-enrichment triggers.
|
||||
**Deferred to a later PR:** enrichment metering/billing, and auto-enrichment triggers.
|
||||
|
||||
---
|
||||
|
||||
@@ -49,9 +48,9 @@ PDL schema v34.1**:
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------ |
|
||||
| `pdlSeniority` (`job_title_levels`, array) | MULTI_SELECT | 10 |
|
||||
| `pdlFundingStages` (`funding_stages`, array) | MULTI_SELECT | 29 |
|
||||
| `pdlIndustry` / `pdlJobCompanyIndustry` (`industry`) | SELECT | 147 |
|
||||
| `pdlIndustry` (`industry`) | SELECT | 147 |
|
||||
| `pdlJobTitleSubRole` (`job_title_sub_role`) | SELECT | 106 |
|
||||
| `pdlJobTitleClass`, `pdlInferredSalary`, `pdlSex`, `pdlCompanyType`, `pdlSizeRange`, `pdlJobCompanySize`, `pdlLatestFundingStage`, `pdlLocationContinent`, `pdlLocationMetro`, `pdlMicExchange` | SELECT | 5 / 11 / 2 / 6 / 8 / 8 / 29 / 7 / 384 / 70 |
|
||||
| `pdlJobTitleClass`, `pdlInferredSalary`, `pdlSex`, `pdlCompanyType`, `pdlSizeRange`, `pdlLatestFundingStage`, `pdlLocationContinent`, `pdlLocationMetro`, `pdlMicExchange` | SELECT | 5 / 11 / 2 / 6 / 8 / 29 / 7 / 384 / 70 |
|
||||
|
||||
- Option `value`s are normalized to **GraphQL enum names** (`united states` → `UNITED_STATES`):
|
||||
uppercase, accents stripped, non-alphanumeric → `_`, digit-leading prefixed.
|
||||
@@ -82,11 +81,19 @@ into the standard bags).
|
||||
- `pdlLocationMetro` (both) and `pdlLocationContinent` (company) stay SELECT — ADDRESS has no slot.
|
||||
_Trade-off:_ ADDRESS `country` is free text, so the country SELECT was dropped.
|
||||
|
||||
### Relation
|
||||
### Current company → standard `company`
|
||||
|
||||
Dedicated **`pdlCurrentCompany`** (Person `MANY_TO_ONE` → Company) ↔ inverse
|
||||
**`pdlCurrentEmployees`** (Company `ONE_TO_MANY` → Person). Deliberately **not** the standard
|
||||
`company` relation, so PDL's detected employer can't overwrite the user's CRM account link.
|
||||
PDL's detected current employer (`job_company_*`) is resolved to a Company record
|
||||
(**find-or-create**, matched by `pdlId` → domain → LinkedIn → name; created with
|
||||
`name` / `domainName` / `linkedinLink` + `pdlId` / `pdlIndustry` / `pdlSizeRange` when none
|
||||
matches) and linked via the **standard `company`** relation, **fill-only-if-empty** — it never
|
||||
overwrites a company the user already set, and the lookup is skipped entirely when the person
|
||||
already has one (no orphan companies).
|
||||
|
||||
Company attributes live on the **Company** record, not denormalized on the Person. The earlier
|
||||
`pdlCurrentCompany` / `pdlCurrentEmployees` relation and the six `pdlJobCompany*` scalar fields
|
||||
were **removed** as duplicates of the standard `company` relation and the linked Company's own
|
||||
fields.
|
||||
|
||||
### Enrichment metadata
|
||||
|
||||
@@ -132,7 +139,8 @@ The logic function (to be built) must:
|
||||
10. **CURRENCY**: `total_funding_raised` (USD float) → `{ amountMicros: value × 1_000_000, currencyCode: 'USD' }`.
|
||||
11. **ADDRESS**: split PDL `location.*` into the composite — Company → standard `address`,
|
||||
Person → `pdlLocation`.
|
||||
12. **Relation**: resolve `job_company_id` → find/upsert a Company record → link `pdlCurrentCompany`.
|
||||
12. **Company**: resolve `job_company_*` → find-or-create a Company record → link the standard
|
||||
`company` relation (fill-only-if-empty).
|
||||
13. **Dates**: handle partial PDL dates (`YYYY`, `YYYY-MM`) for `job_start_date`,
|
||||
`last_funding_date`, `birth_date`.
|
||||
14. Always set `pdlId`, `pdlLastEnrichedAt`, `pdlRawPayload`, `pdlLikelihood` (person).
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
"test:integration": "vitest run --passWithNoTests"
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-client-sdk": "2.4.0",
|
||||
"twenty-sdk": "2.4.0"
|
||||
"twenty-client-sdk": "2.9.1",
|
||||
"twenty-sdk": "2.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2",
|
||||
|
||||
-167
@@ -28,7 +28,6 @@ export const PDL_FIELD_UNIVERSAL_IDENTIFIERS = {
|
||||
pdlRawPayload: '91153108-0576-4da8-a58d-bf8bfc693af1',
|
||||
pdlLastEnrichedAt: '437e08e7-4a94-45af-9def-f202d54ea910',
|
||||
pdlLocation: 'e25302a6-72ed-41e9-a089-1789a1e7ff3c',
|
||||
pdlCurrentCompany: '0ed142f6-f4b9-4517-a4ad-4c09da25ef21',
|
||||
pdlLinkedinUsername: 'b897abde-d051-459e-8761-1f8cdc2a93a5',
|
||||
pdlFacebookUrl: '733a1f4e-43d7-4450-aaee-03436c6f3df8',
|
||||
pdlProfiles: '63fd9ead-07a0-42b0-b230-17fb5c1b2666',
|
||||
@@ -47,12 +46,6 @@ export const PDL_FIELD_UNIVERSAL_IDENTIFIERS = {
|
||||
pdlCertifications: '02006f53-8c32-48a0-9738-73c38ad9412d',
|
||||
pdlSummary: '243d13f9-74e7-4638-9f9a-33fe76d584dd',
|
||||
pdlJobOnetCode: '48f70f1b-cad8-44e4-a27e-5dc26edfd391',
|
||||
pdlJobCompanyName: '1426a17d-09c2-48aa-bd49-3adffeb2ec59',
|
||||
pdlJobCompanyId: '3c8cd41b-1974-4da0-975c-1b3c18c436d9',
|
||||
pdlJobCompanyWebsite: '7e44f20d-2443-4e33-999d-9c3b575112e1',
|
||||
pdlJobCompanyLinkedinUrl: '3035757a-c93a-4d1f-9fb2-e4b581ea6cfa',
|
||||
pdlJobCompanyIndustry: '4c37d4f1-3bf9-4881-afce-bbbc1e113832',
|
||||
pdlJobCompanySize: '1c7c4e5a-fafa-4c7b-b4a7-3bf0b0ca07c5',
|
||||
pdlLocationMetro: 'f5e7434d-c8f5-4eab-866b-59693c9e7111',
|
||||
pdlLikelihood: '50583b08-f422-4964-a9ff-a4e4d6f7a6f0',
|
||||
pdlEnrichmentStatus: 'f03774c1-2b8b-4c62-89af-6add78efc4df',
|
||||
@@ -73,7 +66,6 @@ export const PDL_FIELD_UNIVERSAL_IDENTIFIERS = {
|
||||
pdlFundingStages: 'ced2c9d6-93ea-40de-9636-c0e719b9dc0b',
|
||||
pdlRawPayload: 'cc5bb8e5-c60d-4791-b838-3526942d6548',
|
||||
pdlLastEnrichedAt: '4fd64027-0ba9-4d2c-85eb-26ff90b7c721',
|
||||
pdlCurrentEmployees: 'e654855f-1f10-462b-9721-cdf953c9b1bb',
|
||||
pdlLinkedinId: 'dd57bf00-e5b5-414d-a811-5480dac3b537',
|
||||
pdlFacebookUrl: '4f7fe9ad-5c4f-451c-9dc0-d368e669879f',
|
||||
pdlAlternativeNames: 'c53914e4-58b3-4fc2-b04b-94305675e130',
|
||||
@@ -1097,155 +1089,6 @@ export const PDL_SELECT_OPTION_UNIVERSAL_IDENTIFIERS = {
|
||||
wireless: '3702d3db-f99b-44a3-b7ff-4bea043050db',
|
||||
writingAndEditing: 'e25ffec5-db05-4488-85a6-4578c9ce1c9c',
|
||||
},
|
||||
personJobCompanyIndustry: {
|
||||
accounting: 'c7646bb3-309c-4f7e-bd92-1145cd8fc53e',
|
||||
airlinesAviation: 'ce4b50c2-81ce-473f-832c-d03dad5eb15c',
|
||||
alternativeDisputeResolution: 'f3adb3b8-6d55-44f9-83d2-8c314f9e1b7f',
|
||||
alternativeMedicine: 'e50695b4-e961-424c-ba35-f1552afb2972',
|
||||
animation: '3025feb7-36ef-4c7c-94f3-0e6d8c198b12',
|
||||
apparelFashion: '25f1bcec-3c54-48e7-8f27-5d1c905fc8b9',
|
||||
architecturePlanning: 'ab4dee7a-3b6c-4494-b9c1-bd6cece38372',
|
||||
artsAndCrafts: 'c61baa2c-3fba-444b-ab7d-a057b15a86d0',
|
||||
automotive: 'dbc0cb0b-4d91-4ed9-be79-51bbf07e0ad2',
|
||||
aviationAerospace: '73bf83ab-7ae0-438e-8c16-aff98d7a1bf5',
|
||||
banking: 'b8358792-323c-4d65-af43-a550fba41896',
|
||||
biotechnology: 'caa1993b-b5bc-4a38-bb35-461e9ac64cd9',
|
||||
broadcastMedia: 'ebce7aa1-6d24-48f4-afa9-1ce469496447',
|
||||
buildingMaterials: 'd0b16ab4-690a-4676-b88d-69b1879e5d6e',
|
||||
businessSuppliesAndEquipment: '98c3aa81-d777-4b09-b94d-bf3a64214917',
|
||||
capitalMarkets: 'af0ce50f-8d50-489e-962a-cf360bdb4b69',
|
||||
chemicals: '11a423de-7ddd-466e-8914-bd8cd54e8a14',
|
||||
civicSocialOrganization: 'e81d39f0-5411-49ab-90ab-d6fc1341d7f5',
|
||||
civilEngineering: '30d1ce66-d5f9-4839-af59-0beca8b77bc4',
|
||||
commercialRealEstate: '907f9a17-4090-4ff7-ad92-1fb1f2e14d7d',
|
||||
computerNetworkSecurity: '6bd9dfae-fa74-48f1-83c9-59005daaa95a',
|
||||
computerGames: '6587be1f-4d50-4155-846f-ea26da225f40',
|
||||
computerHardware: 'fff38d7e-a166-4c6a-96b7-f28209b73136',
|
||||
computerNetworking: 'f172e9f0-4217-4f59-ba31-0d6eaac5939f',
|
||||
computerSoftware: '40092912-7856-4a92-b39f-8f17c7d477a7',
|
||||
construction: '847712c3-37e8-4030-9735-5a04fccef373',
|
||||
consumerElectronics: 'b76caec2-ce78-4aec-a1a5-d8a1b7a5b337',
|
||||
consumerGoods: '3e59570f-4791-4545-aea8-1992cdc7af08',
|
||||
consumerServices: '53ddcc27-a1d1-426e-9950-172ad4ac53f5',
|
||||
cosmetics: '27740b0f-76a5-477d-a7e3-6335e5d1886f',
|
||||
dairy: 'fdccb7e6-033f-460a-a237-e1b3ff8eeeb3',
|
||||
defenseSpace: 'd87f7ba9-4f64-4b1c-a3cb-e4b26ffe9b28',
|
||||
design: 'c58ea607-1436-42c6-b49b-01bede5d65be',
|
||||
eLearning: '4dd2bbef-04f5-41a1-852c-a9066e9b56f4',
|
||||
educationManagement: '5f99d759-6789-4e32-967b-53de2d247e65',
|
||||
electricalElectronicManufacturing: '8ba94c6e-3d42-45d1-8a37-abfe0c916b09',
|
||||
entertainment: '2534f3b2-2efd-4399-9189-ae0e80dea0b8',
|
||||
environmentalServices: '4899a93c-912c-4969-a86d-481a9976cf7b',
|
||||
eventsServices: '7a3af453-5241-4dd4-9962-4355a1569d26',
|
||||
executiveOffice: 'e5563e78-6ebf-4ab4-a094-83f67b110c2a',
|
||||
facilitiesServices: '4794543b-ade7-4975-a723-85d0c3c25e1f',
|
||||
farming: '8ebd9eab-4fa9-45e4-8763-2386aa704729',
|
||||
financialServices: '352126e6-b731-4531-bb55-45ed262b8790',
|
||||
fineArt: 'e797e22f-f467-4e02-9678-6895183b12af',
|
||||
fishery: 'f20950cd-5338-48b2-a368-19e3c5f54090',
|
||||
foodBeverages: '0d25b31f-9715-4ef9-bb08-9332cd111a49',
|
||||
foodProduction: '3509b838-e3e4-4b85-a2f7-c9350197a14e',
|
||||
fundRaising: 'fd0d42aa-0b4e-46ef-b319-389bd77968d0',
|
||||
furniture: '3e4a3b44-edeb-4266-aad7-64524e3db97b',
|
||||
gamblingCasinos: '5e7bd38d-f0e1-4b8a-9085-d62bbbe5210d',
|
||||
glassCeramicsConcrete: 'c0e67757-aaf3-40b3-833b-80c76f37c51c',
|
||||
governmentAdministration: '930f7ae7-063a-429a-8130-6e4aed9b8f41',
|
||||
governmentRelations: '26fe8ba1-c71a-4ad3-8026-ec3c147effe1',
|
||||
graphicDesign: '124ba3b2-2557-4e63-9090-62ec0963f8d3',
|
||||
healthWellnessAndFitness: 'a40465be-5a7a-44e3-a823-f5b9419c5be0',
|
||||
higherEducation: 'cf1b074d-8cf5-484f-828e-2d2f9d037ad2',
|
||||
hospitalHealthCare: '059bd3c7-88e6-468b-9842-81d33556ac17',
|
||||
hospitality: '4df1f2ea-82a3-4ff4-890b-1cbe1f3203e7',
|
||||
humanResources: '123531cc-7f88-4c0b-bd98-74c7fbe4ccbb',
|
||||
importAndExport: '2053fa78-d93a-461d-8e84-695099a5e0b7',
|
||||
individualFamilyServices: '48ecdf2b-faab-4979-904a-0d082bfcc83e',
|
||||
industrialAutomation: 'be305ba6-fab3-4952-9fa0-2b4dc682b145',
|
||||
informationServices: '40ae4e28-2c3a-416d-9f66-214808ec0cbf',
|
||||
informationTechnologyAndServices: '58fc2d7c-a2e0-4284-b263-ff34f5a37d1b',
|
||||
insurance: 'f3c59c70-6ad4-446e-97e1-27c5ea251c82',
|
||||
internationalAffairs: '88b3db39-344c-4df7-a21b-9841d6d5e3c9',
|
||||
internationalTradeAndDevelopment: 'f0b3eca1-3e03-402c-8398-ffcbd8148880',
|
||||
internet: '26c060e8-e8bc-4ac6-a25a-e547cafd0050',
|
||||
investmentBanking: '5ee3fbdc-2965-46f8-9b40-e748ce9cbe17',
|
||||
investmentManagement: '76ffc3b2-6228-44fb-9cb9-9cf8cfff35ed',
|
||||
judiciary: '373db33d-eae8-4946-a6b7-377a78bfc210',
|
||||
lawEnforcement: 'cdaff85b-db82-4ef2-9493-51ab9523076b',
|
||||
lawPractice: 'b26a48d2-1fec-444a-a39c-64b476588899',
|
||||
legalServices: 'a7d8ff6a-a754-4a05-9830-d9b20e34160c',
|
||||
legislativeOffice: '3f80fd4c-d564-468e-9808-9b43cb3167d0',
|
||||
leisureTravelTourism: '3e7560d7-77af-4181-9f7e-38bb8d17a8b4',
|
||||
libraries: '988b13ae-7bb1-472b-a991-bf040d079b11',
|
||||
logisticsAndSupplyChain: 'c6cb1c2c-53d0-46f0-8004-0d9962d6106d',
|
||||
luxuryGoodsJewelry: '15ac9e52-adb5-48e3-893d-87789511a966',
|
||||
machinery: '509fbd7a-0a44-4f94-aa5c-685cce3faba4',
|
||||
managementConsulting: '3a3d9a9b-f72c-4cde-bb76-ef9458bdc516',
|
||||
maritime: '028d0304-43ba-48f6-aac9-33971bd5aac2',
|
||||
marketResearch: '32f05431-ca91-456f-a1cf-8e45f64be5d2',
|
||||
marketingAndAdvertising: '6e0c1593-b08a-4fe2-a985-186706777c13',
|
||||
mechanicalOrIndustrialEngineering: '72a8c609-7ed2-454d-ae28-5ed7c10932ab',
|
||||
mediaProduction: 'd82defb2-294b-4552-af09-0d0b3e3f87e4',
|
||||
medicalDevices: 'efe813c7-5d54-4a8a-b97e-4290f670d1da',
|
||||
medicalPractice: '58bad1fd-667f-40ca-ba18-aac0396a02b4',
|
||||
mentalHealthCare: 'f03985f0-5cc7-4850-b97c-a7ea425cbee0',
|
||||
military: '4a410e16-54ef-4312-bd3f-36c2ee36943e',
|
||||
miningMetals: 'cd00c312-130f-49c8-9942-00ea24553ba5',
|
||||
motionPicturesAndFilm: '5d935c3c-c8a3-4e51-a593-da3dbbab5783',
|
||||
museumsAndInstitutions: 'd4c2a66f-88ae-4e2a-b3fd-855951d0376c',
|
||||
music: '74cebb0e-dfa6-4fe4-9e10-2055d7265078',
|
||||
nanotechnology: '1f02fa05-1464-4e32-9090-230ad2d0557b',
|
||||
newspapers: 'b62a3423-075c-4bf9-a78c-528754df2841',
|
||||
nonProfitOrganizationManagement: '92ae1c10-12c5-423d-bb73-6f7730c5d282',
|
||||
oilEnergy: '3bea7331-2355-4a37-8604-3e83a363ef1b',
|
||||
onlineMedia: '2f893d39-9975-44cf-a607-072be812d9a4',
|
||||
outsourcingOffshoring: '29f227ec-2a83-418a-8bde-7627dd25add3',
|
||||
packageFreightDelivery: '652bfc40-a5be-4191-a9ed-10db3f5ba059',
|
||||
packagingAndContainers: '223e7263-2e3a-4a39-bef0-5a5c95759d7d',
|
||||
paperForestProducts: '083402a8-f06d-4b0f-9654-2d4e0c51d4ae',
|
||||
performingArts: 'd74ee411-6486-47ba-b0e2-56e606249a91',
|
||||
pharmaceuticals: '99a8dae3-60a3-4cd5-80e6-a83651a28146',
|
||||
philanthropy: '1744e009-9330-42e4-98ad-23da6d1cc898',
|
||||
photography: 'a93a38e3-847e-4280-bb9a-cf7269115e13',
|
||||
plastics: '8b06307f-df3b-45e2-adf6-f01b6c9a44a2',
|
||||
politicalOrganization: '1a40650c-ed34-4d3e-97b9-70bfd48b1606',
|
||||
primarySecondaryEducation: 'aeaa7034-3847-4e2b-83c6-5ad3c2c7370b',
|
||||
printing: '29c4dd98-5f87-436f-8a9a-846daf36e648',
|
||||
professionalTrainingCoaching: 'ddc493cb-208f-4c8f-b122-23cbe45c0ffe',
|
||||
programDevelopment: 'e83e628a-9f57-4727-925f-37114ce125c1',
|
||||
publicPolicy: '58c6e64d-e5a9-495d-b3ed-87987f976c4f',
|
||||
publicRelationsAndCommunications: '19bc1020-8038-4be4-a4b5-be85e54c7535',
|
||||
publicSafety: '69f06a55-17d5-4c4c-94ba-44625ec8e1f5',
|
||||
publishing: '95e47f5e-d02b-4707-8510-c1310158260b',
|
||||
railroadManufacture: '1eff24a9-db0f-463a-a9c6-2bdebd760ec6',
|
||||
ranching: 'd3f2e4ad-d933-4cc0-ae2d-65f925bee53d',
|
||||
realEstate: '9b81a975-c52e-46d2-b853-334a484f24e5',
|
||||
recreationalFacilitiesAndServices: 'a2b4ba9b-65b7-4f06-8f99-78b4b03a5a8b',
|
||||
religiousInstitutions: '9dc283bc-dfb2-4a7f-bce7-433ce0e4c22d',
|
||||
renewablesEnvironment: 'dffe3cc0-13d3-48f4-8a72-f8f5509d97e5',
|
||||
research: 'f3a47e4f-30c4-4b02-8cb0-8d493518ce57',
|
||||
restaurants: 'ec0efd93-dffe-465d-9dfe-d23e91c3aa71',
|
||||
retail: '143fdd79-0167-4701-b910-0640b69e6909',
|
||||
securityAndInvestigations: '4072f339-ee30-465a-af76-0f58d8469e25',
|
||||
semiconductors: 'c1103955-5e09-4f78-bfc2-9366c18bf22e',
|
||||
shipbuilding: '2d6481c3-d46b-4e67-9328-dd2e22eacc02',
|
||||
sportingGoods: 'ed197d67-bf55-4e50-a1ca-264c93a62961',
|
||||
sports: '5183468a-5b8d-4442-9ea3-9a45de9a7630',
|
||||
staffingAndRecruiting: '5e610095-5d70-4816-a56d-b9fc0705e0b0',
|
||||
supermarkets: '4531f90a-d45a-4f84-8066-da2987fc46e6',
|
||||
telecommunications: '94c7b837-0f22-4fca-a7a2-8951cf869c81',
|
||||
textiles: 'a09422a2-e181-4faa-b904-3816b52bb7ee',
|
||||
thinkTanks: 'c434dbe6-fa9a-4af1-8b40-9165b6a34530',
|
||||
tobacco: '16ebae0f-59fc-4b8d-932f-840d54ae8b5c',
|
||||
translationAndLocalization: 'b94949a2-bf18-4cd3-943a-c381540d088e',
|
||||
transportationTruckingRailroad: '19882453-428c-4179-8614-a5e43dd40e5a',
|
||||
utilities: '911dfaf3-d735-4fdb-92c0-d038dc993144',
|
||||
ventureCapitalPrivateEquity: '478e405c-b780-45a9-9979-0910a9d10a40',
|
||||
veterinary: '2ed603fb-0491-4386-82ca-d5e43bbcdd51',
|
||||
warehousing: 'e0758679-11f6-45cf-af21-da21e297edbc',
|
||||
wholesale: '14d5d2c2-cd70-4436-9e42-2cb913f942e8',
|
||||
wineAndSpirits: 'd470d512-1736-4fe2-8c96-af30d8f03e60',
|
||||
wireless: 'dcf165e4-c123-436f-bd52-b6ddafcb3d74',
|
||||
writingAndEditing: 'a78d7014-682e-4455-a3d1-9f0c4bb21b01',
|
||||
},
|
||||
companyIndustry: {
|
||||
accounting: '67406afe-dd7e-4a39-a86b-394e1f3b7428',
|
||||
airlinesAviation: '6e5ea79c-fc34-4fd1-aaf0-67d94585ad84',
|
||||
@@ -1523,16 +1366,6 @@ export const PDL_SELECT_OPTION_UNIVERSAL_IDENTIFIERS = {
|
||||
from150000To250000: '063ce36e-51ff-4dc2-86eb-3a4b2f823297',
|
||||
over250000: 'fc3658bf-dec7-443b-84f3-0770f3aae62f',
|
||||
},
|
||||
personJobCompanySize: {
|
||||
oneToTen: '9a958c46-b3e3-4e36-ba2f-08b5e0dd1ee7',
|
||||
elevenToFifty: 'b383e032-cf52-4082-ba2e-67b02228e283',
|
||||
fiftyOneToTwoHundred: '32fb12cb-49aa-4fbf-b683-365cea2f8d30',
|
||||
twoHundredOneToFiveHundred: '333ec4cb-e922-4b80-8830-13bf89d600a0',
|
||||
fiveHundredOneToOneThousand: '8d1612c8-5991-4d98-8901-f86effd8bd79',
|
||||
oneThousandOneToFiveThousand: '77383346-4ec3-4c45-9473-4687ee8d58af',
|
||||
fiveThousandOneToTenThousand: '9d654179-01b3-4694-ac24-f03f14023d4a',
|
||||
tenThousandOnePlus: '2ff37073-c0ca-4893-ac0c-d0f52cce1ee2',
|
||||
},
|
||||
companyLocationContinent: {
|
||||
africa: '0efd52cc-6ee3-4890-81ab-0a12a6c56d81',
|
||||
antarctica: '68bdf16c-bf40-445f-86b6-093c51ed6416',
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
import { PDL_FIELD_UNIVERSAL_IDENTIFIERS } from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier:
|
||||
PDL_FIELD_UNIVERSAL_IDENTIFIERS.company.pdlCurrentEmployees,
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
||||
type: FieldType.RELATION,
|
||||
name: 'pdlCurrentEmployees',
|
||||
label: 'PDL Current Employees',
|
||||
description:
|
||||
'People whose current employer is this company, per People Data Labs.',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
PDL_FIELD_UNIVERSAL_IDENTIFIERS.person.pdlCurrentCompany,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
icon: 'IconUsers',
|
||||
});
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
import { PDL_FIELD_UNIVERSAL_IDENTIFIERS } from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier: PDL_FIELD_UNIVERSAL_IDENTIFIERS.person.pdlCurrentCompany,
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
||||
type: FieldType.RELATION,
|
||||
name: 'pdlCurrentCompany',
|
||||
label: 'PDL Current Company',
|
||||
description: 'Current employer detected by People Data Labs.',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
PDL_FIELD_UNIVERSAL_IDENTIFIERS.company.pdlCurrentEmployees,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
joinColumnName: 'pdlCurrentCompanyId',
|
||||
},
|
||||
icon: 'IconBuilding',
|
||||
});
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
import { PDL_FIELD_UNIVERSAL_IDENTIFIERS } from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier: PDL_FIELD_UNIVERSAL_IDENTIFIERS.person.pdlJobCompanyId,
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
||||
type: FieldType.TEXT,
|
||||
name: 'pdlJobCompanyId',
|
||||
label: 'Current Company PDL ID',
|
||||
description: 'Current company PDL identifier returned by People Data Labs.',
|
||||
icon: 'IconId',
|
||||
isNullable: true,
|
||||
});
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
import { INDUSTRY_OPTIONS } from 'src/constants/industry-options';
|
||||
import {
|
||||
PDL_FIELD_UNIVERSAL_IDENTIFIERS,
|
||||
PDL_SELECT_OPTION_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'src/constants/universal-identifiers';
|
||||
import { buildSelectOptions } from 'src/utils/build-select-options';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier:
|
||||
PDL_FIELD_UNIVERSAL_IDENTIFIERS.person.pdlJobCompanyIndustry,
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
||||
type: FieldType.SELECT,
|
||||
name: 'pdlJobCompanyIndustry',
|
||||
label: 'Current Company Industry',
|
||||
description:
|
||||
'Current company canonical industry returned by People Data Labs.',
|
||||
icon: 'IconBuildingFactory2',
|
||||
isNullable: true,
|
||||
options: buildSelectOptions(
|
||||
INDUSTRY_OPTIONS,
|
||||
PDL_SELECT_OPTION_UNIVERSAL_IDENTIFIERS.personJobCompanyIndustry,
|
||||
),
|
||||
});
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
import { PDL_FIELD_UNIVERSAL_IDENTIFIERS } from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier:
|
||||
PDL_FIELD_UNIVERSAL_IDENTIFIERS.person.pdlJobCompanyLinkedinUrl,
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
||||
type: FieldType.LINKS,
|
||||
name: 'pdlJobCompanyLinkedinUrl',
|
||||
label: 'Current Company LinkedIn',
|
||||
description: 'Current company LinkedIn returned by People Data Labs.',
|
||||
icon: 'IconBrandLinkedin',
|
||||
isNullable: true,
|
||||
});
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
import { PDL_FIELD_UNIVERSAL_IDENTIFIERS } from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier: PDL_FIELD_UNIVERSAL_IDENTIFIERS.person.pdlJobCompanyName,
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
||||
type: FieldType.TEXT,
|
||||
name: 'pdlJobCompanyName',
|
||||
label: 'Current Company',
|
||||
description: 'Current company name returned by People Data Labs.',
|
||||
icon: 'IconBuilding',
|
||||
isNullable: true,
|
||||
});
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
import { SIZE_OPTIONS } from 'src/constants/size-options';
|
||||
import {
|
||||
PDL_FIELD_UNIVERSAL_IDENTIFIERS,
|
||||
PDL_SELECT_OPTION_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'src/constants/universal-identifiers';
|
||||
import { buildSelectOptions } from 'src/utils/build-select-options';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier: PDL_FIELD_UNIVERSAL_IDENTIFIERS.person.pdlJobCompanySize,
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
||||
type: FieldType.SELECT,
|
||||
name: 'pdlJobCompanySize',
|
||||
label: 'Current Company Size',
|
||||
description: 'Current company size range returned by People Data Labs.',
|
||||
icon: 'IconChartBar',
|
||||
isNullable: true,
|
||||
options: buildSelectOptions(
|
||||
SIZE_OPTIONS,
|
||||
PDL_SELECT_OPTION_UNIVERSAL_IDENTIFIERS.personJobCompanySize,
|
||||
),
|
||||
});
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
import { PDL_FIELD_UNIVERSAL_IDENTIFIERS } from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier:
|
||||
PDL_FIELD_UNIVERSAL_IDENTIFIERS.person.pdlJobCompanyWebsite,
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
||||
type: FieldType.LINKS,
|
||||
name: 'pdlJobCompanyWebsite',
|
||||
label: 'Current Company Website',
|
||||
description: 'Current company website returned by People Data Labs.',
|
||||
icon: 'IconWorld',
|
||||
isNullable: true,
|
||||
});
|
||||
+17
-3
@@ -1,20 +1,34 @@
|
||||
import { type CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
type Resolver<TResult> = TResult | ((request: unknown) => TResult);
|
||||
|
||||
type CoreApiClientMockOptions = {
|
||||
queryResult?: unknown;
|
||||
queryResult?: Resolver<unknown>;
|
||||
mutationResult?: Resolver<unknown>;
|
||||
onMutation?: (request: unknown) => void;
|
||||
};
|
||||
|
||||
const resolve = <TResult>(
|
||||
resolver: Resolver<TResult> | undefined,
|
||||
request: unknown,
|
||||
): TResult | undefined =>
|
||||
typeof resolver === 'function'
|
||||
? (resolver as (request: unknown) => TResult)(request)
|
||||
: resolver;
|
||||
|
||||
export const createCoreApiClientMock = ({
|
||||
queryResult,
|
||||
mutationResult,
|
||||
onMutation,
|
||||
}: CoreApiClientMockOptions = {}): CoreApiClient => {
|
||||
const query = vi.fn(() => Promise.resolve(queryResult));
|
||||
const query = vi.fn((request: unknown) =>
|
||||
Promise.resolve(resolve(queryResult, request)),
|
||||
);
|
||||
const mutation = vi.fn((request: unknown) => {
|
||||
onMutation?.(request);
|
||||
|
||||
return Promise.resolve({});
|
||||
return Promise.resolve(resolve(mutationResult, request) ?? {});
|
||||
});
|
||||
|
||||
return { query, mutation } as unknown as CoreApiClient;
|
||||
|
||||
+78
-3
@@ -18,12 +18,16 @@ type MutationRequest = {
|
||||
updatePerson: { __args: { data: Record<string, unknown> } };
|
||||
};
|
||||
|
||||
const captureUpdatePerson = (captured: Captured) => (request: unknown) => {
|
||||
if ('updatePerson' in (request as object)) {
|
||||
captured.data = (request as MutationRequest).updatePerson.__args.data;
|
||||
}
|
||||
};
|
||||
|
||||
const buildClient = (node: PersonNode, captured: Captured) =>
|
||||
createCoreApiClientMock({
|
||||
queryResult: { people: { edges: [{ node }] } },
|
||||
onMutation: (request) => {
|
||||
captured.data = (request as MutationRequest).updatePerson.__args.data;
|
||||
},
|
||||
onMutation: captureUpdatePerson(captured),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -62,6 +66,77 @@ describe('enrichPersonCore', () => {
|
||||
expect('linkedinLink' in (captured.data ?? {})).toBe(false);
|
||||
});
|
||||
|
||||
it('links the standard company (find-or-create) when the person has none', async () => {
|
||||
enrichPersonMock.mockResolvedValue({
|
||||
outcome: 'matched',
|
||||
httpStatus: 200,
|
||||
likelihood: 8,
|
||||
data: {
|
||||
id: 'pdl1',
|
||||
work_email: 'jane@acme.com',
|
||||
job_company_name: 'Acme',
|
||||
job_company_website: 'acme.com',
|
||||
},
|
||||
});
|
||||
const captured: Captured = {};
|
||||
const client = createCoreApiClientMock({
|
||||
queryResult: (request: unknown) =>
|
||||
'companies' in (request as object)
|
||||
? { companies: { edges: [] } }
|
||||
: { people: { edges: [{ node: PERSON_NODE_MOCK }] } },
|
||||
mutationResult: (request: unknown) =>
|
||||
'createCompany' in (request as object)
|
||||
? { createCompany: { id: 'co-new' } }
|
||||
: {},
|
||||
onMutation: captureUpdatePerson(captured),
|
||||
});
|
||||
|
||||
await enrichPersonCore({ recordId: 'p1' }, client);
|
||||
|
||||
expect(captured.data?.companyId).toBe('co-new');
|
||||
});
|
||||
|
||||
it('does not touch company when the person already has one', async () => {
|
||||
enrichPersonMock.mockResolvedValue({
|
||||
outcome: 'matched',
|
||||
httpStatus: 200,
|
||||
likelihood: 8,
|
||||
data: { id: 'pdl1', work_email: 'jane@acme.com', job_company_name: 'Acme' },
|
||||
});
|
||||
const captured: Captured = {};
|
||||
let companiesQueried = false;
|
||||
let createCompanyCalled = false;
|
||||
const client = createCoreApiClientMock({
|
||||
queryResult: (request: unknown) => {
|
||||
if ('companies' in (request as object)) {
|
||||
companiesQueried = true;
|
||||
|
||||
return { companies: { edges: [] } };
|
||||
}
|
||||
|
||||
return {
|
||||
people: {
|
||||
edges: [{ node: { ...PERSON_NODE_MOCK, company: { id: 'co-existing' } } }],
|
||||
},
|
||||
};
|
||||
},
|
||||
mutationResult: (request: unknown) => {
|
||||
if ('createCompany' in (request as object)) {
|
||||
createCompanyCalled = true;
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
onMutation: captureUpdatePerson(captured),
|
||||
});
|
||||
|
||||
await enrichPersonCore({ recordId: 'p1' }, client);
|
||||
|
||||
expect(companiesQueried).toBe(false);
|
||||
expect(createCompanyCalled).toBe(false);
|
||||
expect('companyId' in (captured.data ?? {})).toBe(false);
|
||||
});
|
||||
|
||||
it('does not overwrite a populated standard field', async () => {
|
||||
enrichPersonMock.mockResolvedValue({
|
||||
outcome: 'matched',
|
||||
|
||||
+6
@@ -5,6 +5,7 @@ import { PdlInvalidInputError } from 'src/logic-functions/errors/pdl-invalid-inp
|
||||
import { PdlRecordNotFoundError } from 'src/logic-functions/errors/pdl-record-not-found-error';
|
||||
import { buildPersonNameParam } from 'src/logic-functions/utils/build-person-name-param';
|
||||
import { buildSkippedResult } from 'src/logic-functions/utils/build-skipped-result';
|
||||
import { findOrCreateCurrentCompany } from 'src/logic-functions/utils/find-or-create-current-company';
|
||||
import { toText } from 'src/logic-functions/utils/to-text';
|
||||
import { isWithinTtl } from 'src/logic-functions/utils/is-within-ttl';
|
||||
import { mapPerson } from 'src/logic-functions/utils/map-person';
|
||||
@@ -138,9 +139,14 @@ export const enrichPersonCore = async (
|
||||
PERSON_EMPTY_CHECKS,
|
||||
);
|
||||
|
||||
const currentCompanyId = isDefined(node.company?.id)
|
||||
? undefined
|
||||
: await findOrCreateCurrentCompany(client, outcome.data as PdlPersonData);
|
||||
|
||||
const data = pruneUndefined({
|
||||
...writableStandard,
|
||||
...mapped.pdl,
|
||||
companyId: currentCompanyId,
|
||||
pdlLikelihood: outcome.likelihood,
|
||||
pdlRawPayload: outcome.data,
|
||||
pdlLastEnrichedAt: enrichedAt,
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildCompanyCreateData } from 'src/logic-functions/utils/build-company-create-data';
|
||||
import { type PdlPersonData } from 'src/types/pdl-person-data.type';
|
||||
|
||||
describe('buildCompanyCreateData', () => {
|
||||
it('maps job_company_* onto Company standard and pdl fields', () => {
|
||||
const data = buildCompanyCreateData({
|
||||
job_company_id: 'pdl-co-1',
|
||||
job_company_name: 'Acme',
|
||||
job_company_website: 'acme.com',
|
||||
job_company_linkedin_url: 'https://linkedin.com/company/acme',
|
||||
job_company_industry: 'accounting',
|
||||
job_company_size: '11-50',
|
||||
} as PdlPersonData);
|
||||
|
||||
expect(data).toMatchObject({
|
||||
name: 'Acme',
|
||||
pdlId: 'pdl-co-1',
|
||||
pdlIndustry: 'ACCOUNTING',
|
||||
pdlSizeRange: 'ELEVEN_TO_FIFTY',
|
||||
});
|
||||
expect(data.domainName).toMatchObject({ primaryLinkUrl: 'acme.com' });
|
||||
expect(data.linkedinLink).toMatchObject({
|
||||
primaryLinkUrl: 'https://linkedin.com/company/acme',
|
||||
});
|
||||
});
|
||||
|
||||
it('omits fields PDL did not return and drops unknown select values', () => {
|
||||
const data = buildCompanyCreateData({
|
||||
job_company_name: 'Acme',
|
||||
job_company_industry: 'not-a-real-industry',
|
||||
} as PdlPersonData);
|
||||
|
||||
expect(data).toEqual({ name: 'Acme' });
|
||||
});
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildCompanyMatchKeys } from 'src/logic-functions/utils/build-company-match-keys';
|
||||
import { type PdlPersonData } from 'src/types/pdl-person-data.type';
|
||||
|
||||
describe('buildCompanyMatchKeys', () => {
|
||||
it('extracts and trims the company identifiers from PDL data', () => {
|
||||
const keys = buildCompanyMatchKeys({
|
||||
job_company_id: ' pdl-co-1 ',
|
||||
job_company_website: 'acme.com',
|
||||
job_company_linkedin_url: 'https://linkedin.com/company/acme',
|
||||
job_company_name: 'Acme',
|
||||
} as PdlPersonData);
|
||||
|
||||
expect(keys).toEqual({
|
||||
pdlId: 'pdl-co-1',
|
||||
website: 'acme.com',
|
||||
linkedinUrl: 'https://linkedin.com/company/acme',
|
||||
name: 'Acme',
|
||||
});
|
||||
});
|
||||
|
||||
it('omits identifiers PDL did not return or that are blank', () => {
|
||||
expect(
|
||||
buildCompanyMatchKeys({
|
||||
job_company_name: ' ',
|
||||
job_company_website: 'acme.com',
|
||||
} as PdlPersonData),
|
||||
).toEqual({ website: 'acme.com' });
|
||||
});
|
||||
|
||||
it('returns an empty object when no company identifiers are present', () => {
|
||||
expect(buildCompanyMatchKeys({} as PdlPersonData)).toEqual({});
|
||||
});
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { type CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { findCompanyIdByFilter } from 'src/logic-functions/utils/find-company-id-by-filter';
|
||||
|
||||
describe('findCompanyIdByFilter', () => {
|
||||
it('returns the first matching company id and forwards the filter', async () => {
|
||||
const query = vi.fn(() =>
|
||||
Promise.resolve({ companies: { edges: [{ node: { id: 'co-1' } }] } }),
|
||||
);
|
||||
const client = { query } as unknown as CoreApiClient;
|
||||
const filter = { pdlId: { eq: 'pdl-co-1' } };
|
||||
|
||||
const result = await findCompanyIdByFilter(client, filter);
|
||||
|
||||
expect(result).toBe('co-1');
|
||||
expect(query).toHaveBeenCalledWith({
|
||||
companies: {
|
||||
__args: { filter, first: 1 },
|
||||
edges: { node: { id: true } },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns undefined when there are no matches', async () => {
|
||||
const query = vi.fn(() => Promise.resolve({ companies: { edges: [] } }));
|
||||
const client = { query } as unknown as CoreApiClient;
|
||||
|
||||
expect(
|
||||
await findCompanyIdByFilter(client, { name: { eq: 'Acme' } }),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined when the companies connection is absent', async () => {
|
||||
const query = vi.fn(() => Promise.resolve({}));
|
||||
const client = { query } as unknown as CoreApiClient;
|
||||
|
||||
expect(
|
||||
await findCompanyIdByFilter(client, { name: { eq: 'Acme' } }),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import { type CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { findCompanyId } from 'src/logic-functions/utils/find-company-id';
|
||||
|
||||
type CompanyQueryRequest = {
|
||||
companies: { __args: { filter: Record<string, unknown> } };
|
||||
};
|
||||
|
||||
const filterKeyOf = (request: unknown) =>
|
||||
Object.keys((request as CompanyQueryRequest).companies.__args.filter)[0];
|
||||
|
||||
const ALL_KEYS = {
|
||||
pdlId: 'pdl-co-1',
|
||||
website: 'acme.com',
|
||||
linkedinUrl: 'https://linkedin.com/company/acme',
|
||||
name: 'Acme',
|
||||
};
|
||||
|
||||
describe('findCompanyId', () => {
|
||||
it('matches by pdlId first and does not query the lower-priority keys', async () => {
|
||||
const query = vi.fn((request: unknown) =>
|
||||
Promise.resolve(
|
||||
filterKeyOf(request) === 'pdlId'
|
||||
? { companies: { edges: [{ node: { id: 'co-pdl' } }] } }
|
||||
: { companies: { edges: [] } },
|
||||
),
|
||||
);
|
||||
const client = { query } as unknown as CoreApiClient;
|
||||
|
||||
const result = await findCompanyId(client, ALL_KEYS);
|
||||
|
||||
expect(result).toBe('co-pdl');
|
||||
expect(query).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('falls through to lower-priority keys in order until one matches', async () => {
|
||||
const queriedKeys: string[] = [];
|
||||
const query = vi.fn((request: unknown) => {
|
||||
const key = filterKeyOf(request);
|
||||
queriedKeys.push(key);
|
||||
|
||||
return Promise.resolve(
|
||||
key === 'name'
|
||||
? { companies: { edges: [{ node: { id: 'co-name' } }] } }
|
||||
: { companies: { edges: [] } },
|
||||
);
|
||||
});
|
||||
const client = { query } as unknown as CoreApiClient;
|
||||
|
||||
const result = await findCompanyId(client, ALL_KEYS);
|
||||
|
||||
expect(result).toBe('co-name');
|
||||
expect(queriedKeys).toEqual([
|
||||
'pdlId',
|
||||
'domainName',
|
||||
'linkedinLink',
|
||||
'name',
|
||||
]);
|
||||
});
|
||||
|
||||
it('skips filters whose match key is absent', async () => {
|
||||
const queriedKeys: string[] = [];
|
||||
const query = vi.fn((request: unknown) => {
|
||||
queriedKeys.push(filterKeyOf(request));
|
||||
|
||||
return Promise.resolve({ companies: { edges: [] } });
|
||||
});
|
||||
const client = { query } as unknown as CoreApiClient;
|
||||
|
||||
await findCompanyId(client, { name: 'Acme' });
|
||||
|
||||
expect(queriedKeys).toEqual(['name']);
|
||||
});
|
||||
|
||||
it('returns undefined when no key matches', async () => {
|
||||
const query = vi.fn(() => Promise.resolve({ companies: { edges: [] } }));
|
||||
const client = { query } as unknown as CoreApiClient;
|
||||
|
||||
expect(await findCompanyId(client, ALL_KEYS)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined and issues no query when no keys are provided', async () => {
|
||||
const query = vi.fn();
|
||||
const client = { query } as unknown as CoreApiClient;
|
||||
|
||||
expect(await findCompanyId(client, {})).toBeUndefined();
|
||||
expect(query).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import { type CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { findOrCreateCurrentCompany } from 'src/logic-functions/utils/find-or-create-current-company';
|
||||
import { type PdlPersonData } from 'src/types/pdl-person-data.type';
|
||||
|
||||
type CompanyQueryRequest = {
|
||||
companies: { __args: { filter: Record<string, unknown> } };
|
||||
};
|
||||
type CreateCompanyRequest = {
|
||||
createCompany: { __args: { data: Record<string, unknown> } };
|
||||
};
|
||||
|
||||
const filterOf = (request: unknown) =>
|
||||
(request as CompanyQueryRequest).companies.__args.filter;
|
||||
const createDataOf = (request: unknown) =>
|
||||
(request as CreateCompanyRequest).createCompany.__args.data;
|
||||
|
||||
describe('findOrCreateCurrentCompany', () => {
|
||||
it('returns undefined and issues no calls when PDL gives no name or website', async () => {
|
||||
const query = vi.fn();
|
||||
const mutation = vi.fn();
|
||||
const client = { query, mutation } as unknown as CoreApiClient;
|
||||
|
||||
const result = await findOrCreateCurrentCompany(client, {
|
||||
job_company_id: 'pdl-co-1',
|
||||
} as PdlPersonData);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(query).not.toHaveBeenCalled();
|
||||
expect(mutation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns an existing company id without creating one', async () => {
|
||||
const query = vi.fn(() =>
|
||||
Promise.resolve({ companies: { edges: [{ node: { id: 'co-existing' } }] } }),
|
||||
);
|
||||
const mutation = vi.fn();
|
||||
const client = { query, mutation } as unknown as CoreApiClient;
|
||||
|
||||
const result = await findOrCreateCurrentCompany(client, {
|
||||
job_company_name: 'Acme',
|
||||
} as PdlPersonData);
|
||||
|
||||
expect(result).toBe('co-existing');
|
||||
expect(mutation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates a company from the PDL data when none matches', async () => {
|
||||
const query = vi.fn(() => Promise.resolve({ companies: { edges: [] } }));
|
||||
let createdData: Record<string, unknown> | undefined;
|
||||
const mutation = vi.fn((request: unknown) => {
|
||||
createdData = createDataOf(request);
|
||||
|
||||
return Promise.resolve({ createCompany: { id: 'co-new' } });
|
||||
});
|
||||
const client = { query, mutation } as unknown as CoreApiClient;
|
||||
|
||||
const result = await findOrCreateCurrentCompany(client, {
|
||||
job_company_id: 'pdl-co-2',
|
||||
job_company_name: 'Acme',
|
||||
job_company_website: 'acme.com',
|
||||
} as PdlPersonData);
|
||||
|
||||
expect(result).toBe('co-new');
|
||||
expect(createdData).toMatchObject({ name: 'Acme', pdlId: 'pdl-co-2' });
|
||||
});
|
||||
|
||||
it('re-finds the winner when a concurrent create hits a unique violation', async () => {
|
||||
let created = false;
|
||||
const query = vi.fn((request: unknown) =>
|
||||
Promise.resolve(
|
||||
created && 'pdlId' in filterOf(request)
|
||||
? { companies: { edges: [{ node: { id: 'co-race' } }] } }
|
||||
: { companies: { edges: [] } },
|
||||
),
|
||||
);
|
||||
const mutation = vi.fn(() => {
|
||||
created = true;
|
||||
|
||||
return Promise.reject(
|
||||
new Error('duplicate key value violates unique constraint'),
|
||||
);
|
||||
});
|
||||
const client = { query, mutation } as unknown as CoreApiClient;
|
||||
|
||||
const result = await findOrCreateCurrentCompany(client, {
|
||||
job_company_id: 'pdl-co-3',
|
||||
job_company_name: 'Acme',
|
||||
} as PdlPersonData);
|
||||
|
||||
expect(result).toBe('co-race');
|
||||
});
|
||||
|
||||
it('rethrows a non-unique-violation create error', async () => {
|
||||
const query = vi.fn(() => Promise.resolve({ companies: { edges: [] } }));
|
||||
const mutation = vi.fn(() => Promise.reject(new Error('network down')));
|
||||
const client = { query, mutation } as unknown as CoreApiClient;
|
||||
|
||||
await expect(
|
||||
findOrCreateCurrentCompany(client, {
|
||||
job_company_name: 'Acme',
|
||||
} as PdlPersonData),
|
||||
).rejects.toThrow('network down');
|
||||
});
|
||||
});
|
||||
+7
-1
@@ -25,7 +25,13 @@ describe('mapPerson', () => {
|
||||
expect(pdl.pdlJobRole).toBe('ENGINEERING');
|
||||
expect(pdl.pdlIndustry).toBe('ACCOUNTING');
|
||||
expect(pdl.pdlInferredSalary).toBe('FROM_45000_TO_55000');
|
||||
expect(pdl.pdlJobCompanySize).toBe('ELEVEN_TO_FIFTY');
|
||||
});
|
||||
|
||||
it('does not map company attributes onto the person', () => {
|
||||
const { pdl } = mapPerson(PDL_PERSON_DATA_MOCK);
|
||||
|
||||
expect('pdlJobCompanyName' in pdl).toBe(false);
|
||||
expect('pdlJobCompanySize' in pdl).toBe(false);
|
||||
});
|
||||
|
||||
it('drops unknown multi-select values', () => {
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { INDUSTRY_OPTIONS } from 'src/constants/industry-options';
|
||||
import { SIZE_OPTIONS } from 'src/constants/size-options';
|
||||
import { buildAllowedValues } from 'src/logic-functions/utils/build-allowed-values';
|
||||
import { buildLinks } from 'src/logic-functions/utils/build-links';
|
||||
import { pickSelect } from 'src/logic-functions/utils/pick-select';
|
||||
import { sizeTransform } from 'src/logic-functions/utils/size-transform';
|
||||
import { toText } from 'src/logic-functions/utils/to-text';
|
||||
import { type PdlPersonData } from 'src/types/pdl-person-data.type';
|
||||
import { pruneUndefined } from 'src/utils/prune-undefined';
|
||||
|
||||
const INDUSTRY_VALUES = buildAllowedValues(INDUSTRY_OPTIONS);
|
||||
const SIZE_VALUES = buildAllowedValues(SIZE_OPTIONS);
|
||||
|
||||
export const buildCompanyCreateData = (
|
||||
data: PdlPersonData,
|
||||
): Record<string, unknown> =>
|
||||
pruneUndefined<unknown>({
|
||||
name: toText(data.job_company_name),
|
||||
domainName: buildLinks(data.job_company_website),
|
||||
linkedinLink: buildLinks(data.job_company_linkedin_url),
|
||||
pdlId: toText(data.job_company_id),
|
||||
pdlIndustry: pickSelect(data.job_company_industry, INDUSTRY_VALUES),
|
||||
pdlSizeRange: pickSelect(data.job_company_size, SIZE_VALUES, sizeTransform),
|
||||
});
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { toText } from 'src/logic-functions/utils/to-text';
|
||||
import { type CompanyMatchKeys } from 'src/types/company-match-keys.type';
|
||||
import { type PdlPersonData } from 'src/types/pdl-person-data.type';
|
||||
import { pruneUndefined } from 'src/utils/prune-undefined';
|
||||
|
||||
export const buildCompanyMatchKeys = (data: PdlPersonData): CompanyMatchKeys =>
|
||||
pruneUndefined({
|
||||
pdlId: toText(data.job_company_id),
|
||||
website: toText(data.job_company_website),
|
||||
linkedinUrl: toText(data.job_company_linkedin_url),
|
||||
name: toText(data.job_company_name),
|
||||
});
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { type CoreApiClient } from 'twenty-client-sdk/core';
|
||||
|
||||
type CompaniesConnection = { edges?: { node: { id: string } }[] };
|
||||
|
||||
export const findCompanyIdByFilter = async (
|
||||
client: CoreApiClient,
|
||||
filter: Record<string, unknown>,
|
||||
): Promise<string | undefined> => {
|
||||
const result = (await client.query({
|
||||
companies: {
|
||||
__args: { filter, first: 1 },
|
||||
edges: { node: { id: true } },
|
||||
},
|
||||
})) as { companies?: CompaniesConnection };
|
||||
|
||||
return result.companies?.edges?.[0]?.node?.id;
|
||||
};
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { type CoreApiClient } from 'twenty-client-sdk/core';
|
||||
|
||||
import { findCompanyIdByFilter } from 'src/logic-functions/utils/find-company-id-by-filter';
|
||||
import { type CompanyMatchKeys } from 'src/types/company-match-keys.type';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
export const findCompanyId = async (
|
||||
client: CoreApiClient,
|
||||
{ pdlId, website, linkedinUrl, name }: CompanyMatchKeys,
|
||||
): Promise<string | undefined> => {
|
||||
if (isNonEmptyString(pdlId)) {
|
||||
const matchById = await findCompanyIdByFilter(client, {
|
||||
pdlId: { eq: pdlId },
|
||||
});
|
||||
if (isDefined(matchById)) {
|
||||
return matchById;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNonEmptyString(website)) {
|
||||
const matchByDomain = await findCompanyIdByFilter(client, {
|
||||
domainName: { primaryLinkUrl: { eq: website } },
|
||||
});
|
||||
if (isDefined(matchByDomain)) {
|
||||
return matchByDomain;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNonEmptyString(linkedinUrl)) {
|
||||
const matchByLinkedin = await findCompanyIdByFilter(client, {
|
||||
linkedinLink: { primaryLinkUrl: { eq: linkedinUrl } },
|
||||
});
|
||||
if (isDefined(matchByLinkedin)) {
|
||||
return matchByLinkedin;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNonEmptyString(name)) {
|
||||
const matchByName = await findCompanyIdByFilter(client, {
|
||||
name: { eq: name },
|
||||
});
|
||||
if (isDefined(matchByName)) {
|
||||
return matchByName;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { type CoreApiClient } from 'twenty-client-sdk/core';
|
||||
|
||||
import { buildCompanyCreateData } from 'src/logic-functions/utils/build-company-create-data';
|
||||
import { buildCompanyMatchKeys } from 'src/logic-functions/utils/build-company-match-keys';
|
||||
import { findCompanyId } from 'src/logic-functions/utils/find-company-id';
|
||||
import { type PdlPersonData } from 'src/types/pdl-person-data.type';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
import { isUniqueViolationError } from 'src/utils/is-unique-violation-error';
|
||||
|
||||
type CreateCompanyResult = { createCompany?: { id: string } };
|
||||
|
||||
export const findOrCreateCurrentCompany = async (
|
||||
client: CoreApiClient,
|
||||
data: PdlPersonData,
|
||||
): Promise<string | undefined> => {
|
||||
const matchKeys = buildCompanyMatchKeys(data);
|
||||
|
||||
if (
|
||||
!isNonEmptyString(matchKeys.name) &&
|
||||
!isNonEmptyString(matchKeys.website)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const existingId = await findCompanyId(client, matchKeys);
|
||||
if (isDefined(existingId)) {
|
||||
return existingId;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = (await client.mutation({
|
||||
createCompany: {
|
||||
__args: { data: buildCompanyCreateData(data) },
|
||||
id: true,
|
||||
},
|
||||
})) as CreateCompanyResult;
|
||||
|
||||
return result.createCompany?.id;
|
||||
} catch (createError) {
|
||||
if (!isUniqueViolationError(createError)) {
|
||||
throw createError;
|
||||
}
|
||||
|
||||
const raceWinnerId = await findCompanyId(client, matchKeys);
|
||||
if (isDefined(raceWinnerId)) {
|
||||
return raceWinnerId;
|
||||
}
|
||||
|
||||
throw createError;
|
||||
}
|
||||
};
|
||||
-14
@@ -8,7 +8,6 @@ import { JOB_TITLE_SUB_ROLE_OPTIONS } from 'src/constants/job-title-sub-role-opt
|
||||
import { METRO_OPTIONS } from 'src/constants/metro-options';
|
||||
import { SENIORITY_OPTIONS } from 'src/constants/seniority-options';
|
||||
import { SEX_OPTIONS } from 'src/constants/sex-options';
|
||||
import { SIZE_OPTIONS } from 'src/constants/size-options';
|
||||
import { toJsonArray } from 'src/logic-functions/utils/to-json-array';
|
||||
import { toNumber } from 'src/logic-functions/utils/to-number';
|
||||
import { toStringArray } from 'src/logic-functions/utils/to-string-array';
|
||||
@@ -23,7 +22,6 @@ import { buildAllowedValues } from 'src/logic-functions/utils/build-allowed-valu
|
||||
import { pickMultiSelect } from 'src/logic-functions/utils/pick-multi-select';
|
||||
import { pickSelect } from 'src/logic-functions/utils/pick-select';
|
||||
import { salaryTransform } from 'src/logic-functions/utils/salary-transform';
|
||||
import { sizeTransform } from 'src/logic-functions/utils/size-transform';
|
||||
import { type MappedRecord } from 'src/types/mapped-record.type';
|
||||
import { type PdlPersonData } from 'src/types/pdl-person-data.type';
|
||||
import { pruneUndefined } from 'src/utils/prune-undefined';
|
||||
@@ -36,7 +34,6 @@ const INDUSTRY_VALUES = buildAllowedValues(INDUSTRY_OPTIONS);
|
||||
const INFERRED_SALARY_VALUES = buildAllowedValues(INFERRED_SALARY_OPTIONS);
|
||||
const SEX_VALUES = buildAllowedValues(SEX_OPTIONS);
|
||||
const METRO_VALUES = buildAllowedValues(METRO_OPTIONS);
|
||||
const SIZE_VALUES = buildAllowedValues(SIZE_OPTIONS);
|
||||
|
||||
export const mapPerson = (data: PdlPersonData): MappedRecord => {
|
||||
const emailEntries = (data.emails ?? []).map((entry) =>
|
||||
@@ -111,17 +108,6 @@ export const mapPerson = (data: PdlPersonData): MappedRecord => {
|
||||
country: data.location_country,
|
||||
geo: data.location_geo,
|
||||
}),
|
||||
|
||||
pdlJobCompanyId: toText(data.job_company_id),
|
||||
pdlJobCompanyName: toText(data.job_company_name),
|
||||
pdlJobCompanyWebsite: buildLinks(data.job_company_website),
|
||||
pdlJobCompanyLinkedinUrl: buildLinks(data.job_company_linkedin_url),
|
||||
pdlJobCompanyIndustry: pickSelect(data.job_company_industry, INDUSTRY_VALUES),
|
||||
pdlJobCompanySize: pickSelect(
|
||||
data.job_company_size,
|
||||
SIZE_VALUES,
|
||||
sizeTransform,
|
||||
),
|
||||
});
|
||||
|
||||
return { standard, pdl };
|
||||
|
||||
+1
@@ -17,6 +17,7 @@ export const readPerson = async (
|
||||
phones: { primaryPhoneNumber: true },
|
||||
jobTitle: true,
|
||||
linkedinLink: { primaryLinkUrl: true },
|
||||
company: { id: true },
|
||||
pdlId: true,
|
||||
pdlLastEnrichedAt: true,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export type CompanyMatchKeys = {
|
||||
pdlId?: string;
|
||||
website?: string;
|
||||
linkedinUrl?: string;
|
||||
name?: string;
|
||||
};
|
||||
@@ -5,6 +5,7 @@ export type PersonNode = {
|
||||
phones?: { primaryPhoneNumber?: string | null } | null;
|
||||
jobTitle?: string | null;
|
||||
linkedinLink?: { primaryLinkUrl?: string | null } | null;
|
||||
company?: { id?: string | null } | null;
|
||||
pdlId?: string | null;
|
||||
pdlLastEnrichedAt?: string | null;
|
||||
};
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { isUniqueViolationError } from 'src/utils/is-unique-violation-error';
|
||||
|
||||
describe('isUniqueViolationError', () => {
|
||||
it('detects common unique-violation phrasings (case-insensitive)', () => {
|
||||
expect(
|
||||
isUniqueViolationError(
|
||||
new Error('duplicate key value violates unique constraint'),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(isUniqueViolationError(new Error('Record already exists'))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(isUniqueViolationError({ message: 'Uniqueness violated' })).toBe(
|
||||
true,
|
||||
);
|
||||
expect(isUniqueViolationError('VIOLATES UNIQUE index')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for unrelated errors and non-error values', () => {
|
||||
expect(isUniqueViolationError(new Error('network down'))).toBe(false);
|
||||
expect(isUniqueViolationError(null)).toBe(false);
|
||||
expect(isUniqueViolationError(undefined)).toBe(false);
|
||||
expect(isUniqueViolationError(42)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
export const isUniqueViolationError = (error: unknown): boolean => {
|
||||
const text =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: typeof error === 'object' && error !== null
|
||||
? JSON.stringify(error)
|
||||
: typeof error === 'string'
|
||||
? error
|
||||
: '';
|
||||
const lower = text.toLowerCase();
|
||||
|
||||
return (
|
||||
lower.includes('duplicate') ||
|
||||
lower.includes('unique constraint') ||
|
||||
lower.includes('uniqueness') ||
|
||||
lower.includes('already exists') ||
|
||||
lower.includes('violates unique')
|
||||
);
|
||||
};
|
||||
@@ -29,6 +29,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/aix-ppc64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/aix-ppc64@npm:0.28.0"
|
||||
conditions: os=aix & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/android-arm64@npm:0.25.12"
|
||||
@@ -43,6 +50,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/android-arm64@npm:0.28.0"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/android-arm@npm:0.25.12"
|
||||
@@ -57,6 +71,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/android-arm@npm:0.28.0"
|
||||
conditions: os=android & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-x64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/android-x64@npm:0.25.12"
|
||||
@@ -71,6 +92,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-x64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/android-x64@npm:0.28.0"
|
||||
conditions: os=android & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-arm64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/darwin-arm64@npm:0.25.12"
|
||||
@@ -85,6 +113,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-arm64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/darwin-arm64@npm:0.28.0"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-x64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/darwin-x64@npm:0.25.12"
|
||||
@@ -99,6 +134,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-x64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/darwin-x64@npm:0.28.0"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-arm64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/freebsd-arm64@npm:0.25.12"
|
||||
@@ -113,6 +155,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-arm64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/freebsd-arm64@npm:0.28.0"
|
||||
conditions: os=freebsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-x64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/freebsd-x64@npm:0.25.12"
|
||||
@@ -127,6 +176,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-x64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/freebsd-x64@npm:0.28.0"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/linux-arm64@npm:0.25.12"
|
||||
@@ -141,6 +197,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/linux-arm64@npm:0.28.0"
|
||||
conditions: os=linux & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/linux-arm@npm:0.25.12"
|
||||
@@ -155,6 +218,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/linux-arm@npm:0.28.0"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ia32@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/linux-ia32@npm:0.25.12"
|
||||
@@ -169,6 +239,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ia32@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/linux-ia32@npm:0.28.0"
|
||||
conditions: os=linux & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-loong64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/linux-loong64@npm:0.25.12"
|
||||
@@ -183,6 +260,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-loong64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/linux-loong64@npm:0.28.0"
|
||||
conditions: os=linux & cpu=loong64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-mips64el@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/linux-mips64el@npm:0.25.12"
|
||||
@@ -197,6 +281,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-mips64el@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/linux-mips64el@npm:0.28.0"
|
||||
conditions: os=linux & cpu=mips64el
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ppc64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/linux-ppc64@npm:0.25.12"
|
||||
@@ -211,6 +302,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ppc64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/linux-ppc64@npm:0.28.0"
|
||||
conditions: os=linux & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-riscv64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/linux-riscv64@npm:0.25.12"
|
||||
@@ -225,6 +323,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-riscv64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/linux-riscv64@npm:0.28.0"
|
||||
conditions: os=linux & cpu=riscv64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-s390x@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/linux-s390x@npm:0.25.12"
|
||||
@@ -239,6 +344,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-s390x@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/linux-s390x@npm:0.28.0"
|
||||
conditions: os=linux & cpu=s390x
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-x64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/linux-x64@npm:0.25.12"
|
||||
@@ -253,6 +365,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-x64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/linux-x64@npm:0.28.0"
|
||||
conditions: os=linux & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-arm64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/netbsd-arm64@npm:0.25.12"
|
||||
@@ -267,6 +386,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-arm64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/netbsd-arm64@npm:0.28.0"
|
||||
conditions: os=netbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-x64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/netbsd-x64@npm:0.25.12"
|
||||
@@ -281,6 +407,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-x64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/netbsd-x64@npm:0.28.0"
|
||||
conditions: os=netbsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-arm64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/openbsd-arm64@npm:0.25.12"
|
||||
@@ -295,6 +428,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-arm64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/openbsd-arm64@npm:0.28.0"
|
||||
conditions: os=openbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-x64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/openbsd-x64@npm:0.25.12"
|
||||
@@ -309,6 +449,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-x64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/openbsd-x64@npm:0.28.0"
|
||||
conditions: os=openbsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openharmony-arm64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/openharmony-arm64@npm:0.25.12"
|
||||
@@ -323,6 +470,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openharmony-arm64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/openharmony-arm64@npm:0.28.0"
|
||||
conditions: os=openharmony & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/sunos-x64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/sunos-x64@npm:0.25.12"
|
||||
@@ -337,6 +491,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/sunos-x64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/sunos-x64@npm:0.28.0"
|
||||
conditions: os=sunos & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-arm64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/win32-arm64@npm:0.25.12"
|
||||
@@ -351,6 +512,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-arm64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/win32-arm64@npm:0.28.0"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-ia32@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/win32-ia32@npm:0.25.12"
|
||||
@@ -365,6 +533,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-ia32@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/win32-ia32@npm:0.28.0"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-x64@npm:0.25.12":
|
||||
version: 0.25.12
|
||||
resolution: "@esbuild/win32-x64@npm:0.25.12"
|
||||
@@ -379,6 +554,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-x64@npm:0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "@esbuild/win32-x64@npm:0.28.0"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fastify/busboy@npm:^2.0.0":
|
||||
version: 2.1.1
|
||||
resolution: "@fastify/busboy@npm:2.1.1"
|
||||
@@ -1678,13 +1860,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dotenv@npm:^16.4.0":
|
||||
version: 16.6.1
|
||||
resolution: "dotenv@npm:16.6.1"
|
||||
checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dunder-proto@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "dunder-proto@npm:1.0.1"
|
||||
@@ -1965,6 +2140,95 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"esbuild@npm:^0.28.0":
|
||||
version: 0.28.0
|
||||
resolution: "esbuild@npm:0.28.0"
|
||||
dependencies:
|
||||
"@esbuild/aix-ppc64": "npm:0.28.0"
|
||||
"@esbuild/android-arm": "npm:0.28.0"
|
||||
"@esbuild/android-arm64": "npm:0.28.0"
|
||||
"@esbuild/android-x64": "npm:0.28.0"
|
||||
"@esbuild/darwin-arm64": "npm:0.28.0"
|
||||
"@esbuild/darwin-x64": "npm:0.28.0"
|
||||
"@esbuild/freebsd-arm64": "npm:0.28.0"
|
||||
"@esbuild/freebsd-x64": "npm:0.28.0"
|
||||
"@esbuild/linux-arm": "npm:0.28.0"
|
||||
"@esbuild/linux-arm64": "npm:0.28.0"
|
||||
"@esbuild/linux-ia32": "npm:0.28.0"
|
||||
"@esbuild/linux-loong64": "npm:0.28.0"
|
||||
"@esbuild/linux-mips64el": "npm:0.28.0"
|
||||
"@esbuild/linux-ppc64": "npm:0.28.0"
|
||||
"@esbuild/linux-riscv64": "npm:0.28.0"
|
||||
"@esbuild/linux-s390x": "npm:0.28.0"
|
||||
"@esbuild/linux-x64": "npm:0.28.0"
|
||||
"@esbuild/netbsd-arm64": "npm:0.28.0"
|
||||
"@esbuild/netbsd-x64": "npm:0.28.0"
|
||||
"@esbuild/openbsd-arm64": "npm:0.28.0"
|
||||
"@esbuild/openbsd-x64": "npm:0.28.0"
|
||||
"@esbuild/openharmony-arm64": "npm:0.28.0"
|
||||
"@esbuild/sunos-x64": "npm:0.28.0"
|
||||
"@esbuild/win32-arm64": "npm:0.28.0"
|
||||
"@esbuild/win32-ia32": "npm:0.28.0"
|
||||
"@esbuild/win32-x64": "npm:0.28.0"
|
||||
dependenciesMeta:
|
||||
"@esbuild/aix-ppc64":
|
||||
optional: true
|
||||
"@esbuild/android-arm":
|
||||
optional: true
|
||||
"@esbuild/android-arm64":
|
||||
optional: true
|
||||
"@esbuild/android-x64":
|
||||
optional: true
|
||||
"@esbuild/darwin-arm64":
|
||||
optional: true
|
||||
"@esbuild/darwin-x64":
|
||||
optional: true
|
||||
"@esbuild/freebsd-arm64":
|
||||
optional: true
|
||||
"@esbuild/freebsd-x64":
|
||||
optional: true
|
||||
"@esbuild/linux-arm":
|
||||
optional: true
|
||||
"@esbuild/linux-arm64":
|
||||
optional: true
|
||||
"@esbuild/linux-ia32":
|
||||
optional: true
|
||||
"@esbuild/linux-loong64":
|
||||
optional: true
|
||||
"@esbuild/linux-mips64el":
|
||||
optional: true
|
||||
"@esbuild/linux-ppc64":
|
||||
optional: true
|
||||
"@esbuild/linux-riscv64":
|
||||
optional: true
|
||||
"@esbuild/linux-s390x":
|
||||
optional: true
|
||||
"@esbuild/linux-x64":
|
||||
optional: true
|
||||
"@esbuild/netbsd-arm64":
|
||||
optional: true
|
||||
"@esbuild/netbsd-x64":
|
||||
optional: true
|
||||
"@esbuild/openbsd-arm64":
|
||||
optional: true
|
||||
"@esbuild/openbsd-x64":
|
||||
optional: true
|
||||
"@esbuild/openharmony-arm64":
|
||||
optional: true
|
||||
"@esbuild/sunos-x64":
|
||||
optional: true
|
||||
"@esbuild/win32-arm64":
|
||||
optional: true
|
||||
"@esbuild/win32-ia32":
|
||||
optional: true
|
||||
"@esbuild/win32-x64":
|
||||
optional: true
|
||||
bin:
|
||||
esbuild: bin/esbuild
|
||||
checksum: 10c0/8acd95c238ec6c4a9d16163277faf228a8994b642d187b3fe667ffbb469008e6748cde144fdc3c175bf8e78ee49e15a0ed9b9f183fdb5fcea1772f87fb1372a4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "escape-string-regexp@npm:1.0.5"
|
||||
@@ -3065,8 +3329,8 @@ __metadata:
|
||||
oxlint: "npm:^0.16.0"
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
twenty-client-sdk: "npm:2.4.0"
|
||||
twenty-sdk: "npm:2.4.0"
|
||||
twenty-client-sdk: "npm:2.9.1"
|
||||
twenty-sdk: "npm:2.9.1"
|
||||
typescript: "npm:^5.9.3"
|
||||
vite-tsconfig-paths: "npm:^4.2.1"
|
||||
vitest: "npm:^3.1.1"
|
||||
@@ -3791,30 +4055,28 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-client-sdk@npm:2.4.0":
|
||||
version: 2.4.0
|
||||
resolution: "twenty-client-sdk@npm:2.4.0"
|
||||
"twenty-client-sdk@npm:2.9.1":
|
||||
version: 2.9.1
|
||||
resolution: "twenty-client-sdk@npm:2.9.1"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@genql/runtime": "npm:^2.10.0"
|
||||
esbuild: "npm:^0.25.0"
|
||||
esbuild: "npm:^0.28.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
checksum: 10c0/cdd9e70a59b8ce16f40e84b79c16e89b984dfcbb04e4646341fea40911acee8eab5aa61872421f25212949e677a75ed51af99e61829f2902af0f0a10e6e5614d
|
||||
checksum: 10c0/fecc82f090f7f5261330a552017e31decf9f4e134e9cf54d8631666d47f303e5d1d229436f30d1ebd9983613eeba1adb8f7244741b88234e3e8cfe956f3a8ffb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-sdk@npm:2.4.0":
|
||||
version: 2.4.0
|
||||
resolution: "twenty-sdk@npm:2.4.0"
|
||||
"twenty-sdk@npm:2.9.1":
|
||||
version: 2.9.1
|
||||
resolution: "twenty-sdk@npm:2.9.1"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@genql/runtime": "npm:^2.10.0"
|
||||
"@sniptt/guards": "npm:^0.2.0"
|
||||
axios: "npm:^1.13.5"
|
||||
chalk: "npm:^5.3.0"
|
||||
chokidar: "npm:^4.0.0"
|
||||
commander: "npm:^12.0.0"
|
||||
dotenv: "npm:^16.4.0"
|
||||
esbuild: "npm:^0.25.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
graphql-sse: "npm:^2.5.4"
|
||||
@@ -3825,15 +4087,12 @@ __metadata:
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
twenty-client-sdk: "npm:2.4.0"
|
||||
typescript: "npm:^5.9.2"
|
||||
twenty-client-sdk: "npm:2.9.1"
|
||||
typescript: "npm:^5.9.3"
|
||||
uuid: "npm:^13.0.0"
|
||||
vite: "npm:^7.0.0"
|
||||
vite-tsconfig-paths: "npm:^4.2.1"
|
||||
zod: "npm:^4.1.11"
|
||||
bin:
|
||||
twenty: dist/cli.cjs
|
||||
checksum: 10c0/7266755dd0f192f0107b25b72db7340a8476f4b97f3e8d2fa539010f6702f9104463da8ddca9318b2e7df2d6be43362be5eddb95af2067d8863578907b713617
|
||||
checksum: 10c0/3bb4cd1cee344569a5a1c5f0f3af5afc1c4f84587aec983645c0d113dcb594b4d0723679b6181afbd389e83e84572e24e5fba60d6e0a9b062271dce0aeb9bbb4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3853,7 +4112,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:^5.9.2, typescript@npm:^5.9.3":
|
||||
"typescript@npm:^5.9.3":
|
||||
version: 5.9.3
|
||||
resolution: "typescript@npm:5.9.3"
|
||||
bin:
|
||||
@@ -3863,7 +4122,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@npm%3A^5.9.2#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin<compat/typescript>":
|
||||
"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin<compat/typescript>":
|
||||
version: 5.9.3
|
||||
resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"
|
||||
bin:
|
||||
@@ -3987,7 +4246,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0, vite@npm:^7.0.0":
|
||||
"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0":
|
||||
version: 7.3.3
|
||||
resolution: "vite@npm:7.3.3"
|
||||
dependencies:
|
||||
@@ -4312,10 +4571,3 @@ __metadata:
|
||||
checksum: 10c0/71cc2f2bbb537300c3f569e25693d37b3bc91f225cefce251a71c30bc6bb3e7f8e9420ca0eb57f2ac9e492b085b8dfa075fd1e8195c40b83c951dd59c6e4fbf8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^4.1.11":
|
||||
version: 4.4.3
|
||||
resolution: "zod@npm:4.4.3"
|
||||
checksum: 10c0/7ea31b558e88f9faf44f31dd185e2e1cbf51fed3081787fb96cc2534749b50c0acfc6da7f0922a7353ed092dd358c7d50c28ea96c94d04af64191bd33152eca3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
Reference in New Issue
Block a user