twenty-app-intake 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +292 -7
  2. package/manifest.json +389 -15
  3. package/package.json +1 -1
  4. package/src/front-components/settings-panel.mjs +7 -7
  5. package/src/front-components/settings-panel.mjs.map +2 -2
  6. package/src/front-components/settings-panel.tsx +49 -0
  7. package/src/logic-functions/check-silence.mjs +263 -0
  8. package/src/logic-functions/check-silence.mjs.map +7 -0
  9. package/src/logic-functions/check-silence.ts +52 -0
  10. package/src/logic-functions/contract.mjs +507 -0
  11. package/src/logic-functions/contract.mjs.map +7 -0
  12. package/src/logic-functions/contract.ts +172 -0
  13. package/src/logic-functions/health.mjs +125 -5
  14. package/src/logic-functions/health.mjs.map +3 -3
  15. package/src/logic-functions/health.ts +41 -5
  16. package/src/logic-functions/quarantine-discard.mjs +380 -0
  17. package/src/logic-functions/quarantine-discard.mjs.map +7 -0
  18. package/src/logic-functions/quarantine-discard.ts +65 -0
  19. package/src/logic-functions/quarantine-list.mjs +211 -0
  20. package/src/logic-functions/quarantine-list.mjs.map +7 -0
  21. package/src/logic-functions/quarantine-list.ts +93 -0
  22. package/src/logic-functions/quarantine-release.mjs +1974 -0
  23. package/src/logic-functions/quarantine-release.mjs.map +7 -0
  24. package/src/logic-functions/quarantine-release.ts +94 -0
  25. package/src/logic-functions/register.mjs +34 -1
  26. package/src/logic-functions/register.mjs.map +2 -2
  27. package/src/logic-functions/replay-bulk.mjs +2040 -0
  28. package/src/logic-functions/replay-bulk.mjs.map +7 -0
  29. package/src/logic-functions/replay-bulk.ts +83 -0
  30. package/src/logic-functions/replay-log.mjs +1987 -0
  31. package/src/logic-functions/replay-log.mjs.map +7 -0
  32. package/src/logic-functions/replay-log.ts +54 -0
  33. package/src/logic-functions/retry.mjs +1001 -248
  34. package/src/logic-functions/retry.mjs.map +4 -4
  35. package/src/logic-functions/retry.ts +45 -68
  36. package/src/logic-functions/test-ingest.mjs +616 -28
  37. package/src/logic-functions/test-ingest.mjs.map +4 -4
  38. package/src/logic-functions/test-ingest.ts +132 -28
  39. package/src/logic-functions/webhook.mjs +965 -209
  40. package/src/logic-functions/webhook.mjs.map +4 -4
  41. package/src/logic-functions/webhook.ts +23 -48
  42. package/src/post-install.mjs +34 -1
  43. package/src/post-install.mjs.map +2 -2
@@ -0,0 +1,507 @@
1
+ import { createRequire as __createRequire } from 'module';
2
+ const require = __createRequire(import.meta.url);
3
+
4
+ // twenty-sdk-define-stub:__twenty-sdk-define-stub__
5
+ var __defineFactoryStub = (config) => ({
6
+ success: true,
7
+ config,
8
+ errors: []
9
+ });
10
+ var __anyHandler = {
11
+ get(_target, prop) {
12
+ if (prop === "__esModule") return true;
13
+ if (prop === Symbol.toPrimitive) return () => "";
14
+ if (typeof prop === "symbol") return void 0;
15
+ return new Proxy(() => void 0, __anyHandler);
16
+ },
17
+ apply() {
18
+ return new Proxy(() => void 0, __anyHandler);
19
+ }
20
+ };
21
+ var __anyStub = new Proxy(() => void 0, __anyHandler);
22
+ var defineLogicFunction = __defineFactoryStub;
23
+
24
+ // src/constants/universal-identifiers.ts
25
+ var IDS = {
26
+ APPLICATION: "96a389dd-384a-43a1-a376-9d3cf01b6ab7",
27
+ DEFAULT_ROLE: "3dd2cdb0-32c7-44b4-9f7b-0a9c667ec249",
28
+ // Objects
29
+ INTAKE_SOURCE_OBJECT: "8c1c227c-db7d-4834-b328-cacbe8af0bfc",
30
+ INTAKE_LOG_OBJECT: "b77a2439-1503-4ef6-ab8b-8f4293bc19da",
31
+ INTAKE_FIELD_RULE_OBJECT: "e74df817-ff25-4bfb-bdf0-651662ca327e",
32
+ // IntakeSource fields
33
+ INTAKE_SOURCE_NAME: "0deb7e4b-bb90-4c86-b903-5208b42041e2",
34
+ INTAKE_SOURCE_SLUG: "18f7645b-2dce-4145-b1b6-9cc3332c0578",
35
+ INTAKE_SOURCE_TARGET_OBJECT: "65f3d7f7-a601-4375-9c67-684d0044620b",
36
+ INTAKE_SOURCE_SECRET: "4c919402-07f5-4041-ade6-a42402f1881f",
37
+ INTAKE_SOURCE_STATUS: "6f1fa6fe-02b4-48ab-b1ee-97c600bbf483",
38
+ INTAKE_SOURCE_CREATE_OPP: "67ae59aa-2275-4ef2-b594-137f6bfafd99",
39
+ INTAKE_SOURCE_OPP_TEMPLATE: "3907b346-77a6-4c79-a4d7-6e1cc20bb82b",
40
+ INTAKE_SOURCE_TOTAL_INGESTED: "69655a93-bc70-48bd-874e-aeccc63ee1a8",
41
+ INTAKE_SOURCE_TOTAL_FAILED: "27b3a499-183e-4cc3-8e40-938f953a986d",
42
+ INTAKE_SOURCE_LAST_INGESTED: "e167f628-9865-468e-894a-b4b6150cec26",
43
+ // IntakeLog fields
44
+ INTAKE_LOG_SOURCE_RELATION: "b1c623ea-7a9a-47a5-bee4-35fdd668a637",
45
+ INTAKE_LOG_STATUS: "8b6d26d1-f89c-486c-a617-9f9f6a9ca36d",
46
+ INTAKE_LOG_PAYLOAD_HASH: "c43bb1ad-e78a-4c60-bd95-02475ede7f44",
47
+ INTAKE_LOG_RECORD_ID: "77a55116-a64b-4954-8369-341022dd5bdd",
48
+ INTAKE_LOG_RECORD_TYPE: "02154e96-5808-48ed-8838-51083f174550",
49
+ INTAKE_LOG_FIELDS_CREATED: "2295ca2b-b559-4b38-9771-12dd8d3d76d7",
50
+ INTAKE_LOG_FIELDS_MATCHED: "83d07a0f-5bcd-4fb1-9759-f9b04b9381d8",
51
+ INTAKE_LOG_OVERFLOW_COUNT: "4090acb1-f48a-4957-bb91-c642f8f434a7",
52
+ INTAKE_LOG_PROCESSING_MS: "a9191f27-4b40-42e9-8cce-20c75900e12c",
53
+ INTAKE_LOG_ERROR: "8d9979f1-9774-43a8-b958-d1b37bd4ecc7",
54
+ INTAKE_LOG_PROCESSED_AT: "a8d99fbd-722e-4987-8afe-b830ac026f0a",
55
+ // IntakeFieldRule fields
56
+ INTAKE_FIELD_RULE_SOURCE_RELATION: "396dd1f1-3873-4efa-a6bb-7b41eda14d83",
57
+ INTAKE_FIELD_RULE_INPUT_PATTERN: "757201a0-9f64-47b9-bf3a-a793074bf84e",
58
+ INTAKE_FIELD_RULE_CANONICAL_NAME: "fafebc55-a2cc-4c9f-975b-050c2676659d",
59
+ INTAKE_FIELD_RULE_FIELD_TYPE: "72d1f6d8-d63e-419e-a244-8127d7b4705f",
60
+ INTAKE_FIELD_RULE_PRIORITY: "911df299-940b-44f9-92f7-3fd01fde01a7",
61
+ INTAKE_FIELD_RULE_IS_ACTIVE: "6f9cd96d-aecd-437f-a7b5-d45548782771",
62
+ INTAKE_FIELD_RULE_TARGET_OBJECT: "7b8411a7-5e1d-40f2-a20c-683c07f67926",
63
+ // Logic Functions
64
+ WEBHOOK_LOGIC_FUNCTION: "8323c257-48fe-4bb6-b1dd-166844f575c0",
65
+ HEALTH_LOGIC_FUNCTION: "4ece4e39-286b-4820-81f4-b7beee71d092",
66
+ REGISTER_LOGIC_FUNCTION: "56d46e18-6cae-4d99-9893-b7d70740c9db",
67
+ // IntakeSource reverse relation fields
68
+ INTAKE_SOURCE_LOGS_REVERSE: "6ecfa369-22e0-4812-bb75-4338cea8be65",
69
+ INTAKE_SOURCE_RULES_REVERSE: "776fefaf-0eee-4ec0-93e0-d11bd80c1e20",
70
+ // v0.2.0 — rawPayload + retry + post-install + UI
71
+ INTAKE_LOG_RAW_PAYLOAD: "b65c9e1c-f2c1-4c52-a5b3-1e1ac3139692",
72
+ RETRY_LOGIC_FUNCTION: "90f20396-a69a-4ae4-af20-6be65c565bb5",
73
+ POST_INSTALL_LOGIC_FUNCTION: "e836fdd6-fd54-4aa4-ba6d-87d5a02148ef",
74
+ LOG_RETRY_FRONT_COMPONENT: "0cb97f6d-30e2-48c8-90e1-241b82870b4d",
75
+ FIELD_RULES_TABLE_FRONT_COMPONENT: "c35faad9-3468-4746-9c1c-b13d73e5e09b",
76
+ SOURCE_PAGE_LAYOUT: "8c894ba8-5c8c-45ed-b0af-1d22ede93eb9",
77
+ SOURCE_LAYOUT_TAB_RULES: "80f5c49a-083c-4062-8bd5-ae158abb6c33",
78
+ SOURCE_LAYOUT_TAB_RULES_WIDGET: "26a9ebb7-1d59-4b03-a018-56968f6df224",
79
+ LOG_PAGE_LAYOUT: "b0972802-ede5-463c-9f59-cb05f04957db",
80
+ LOG_LAYOUT_TAB_RETRY: "6b109695-ba3a-43de-a31a-23119ccf87ba",
81
+ LOG_LAYOUT_TAB_RETRY_WIDGET: "11727c1a-5145-46de-9742-88d07d64c72e",
82
+ SAMPLE_RULE_PHONE: "8440da8d-8ac4-4f4b-baa2-4d62936cde35",
83
+ SAMPLE_RULE_EMAIL: "d123cde4-05aa-44e1-86f6-454be1d7048b",
84
+ SAMPLE_RULE_FULLNAME: "00e49fe8-1b48-4f5b-aaf4-414d0923c712",
85
+ // Views & Navigation
86
+ SOURCES_VIEW: "4435cd43-5c13-472f-b4f9-b686c0f46285",
87
+ LOGS_VIEW: "a2cd0a93-9778-4ba5-99d9-b8b89c4fb39d",
88
+ INTAKE_NAV_ITEM: "e81bf288-5686-4713-86ae-e2947b124cab",
89
+ // ── v0.4.0 ────────────────────────────────────────────────────────────────
90
+ // IntakeLog fields — quarantine, idempotency, replay lineage
91
+ INTAKE_LOG_IDEMPOTENCY_KEY: "bf91e4c0-6477-41f2-a9bd-fb159d3091d4",
92
+ INTAKE_LOG_SPAM_SCORE: "4e250af2-925f-4724-85f6-f353a6bb4cfd",
93
+ INTAKE_LOG_QUARANTINE_REASON: "323b3c3e-a111-45d1-a211-cf70efa6cd9e",
94
+ INTAKE_LOG_REPLAY_OF: "b68876b1-0045-4eaa-a4e7-f0634e110ef1",
95
+ // IntakeSource fields — silence detection, spam, merge policy
96
+ INTAKE_SOURCE_EXPECTED_CADENCE: "a97c3906-5555-4e60-a6d6-84155a4e00a6",
97
+ INTAKE_SOURCE_HEALTH_STATUS: "2ad13b20-5bd7-450a-b52d-bcf208373cf5",
98
+ INTAKE_SOURCE_SILENT_SINCE: "d3395601-f29f-4e57-98eb-561ab011cc72",
99
+ INTAKE_SOURCE_ALERT_WEBHOOK: "5a066d62-0426-41c1-b34c-1a7e881ce854",
100
+ INTAKE_SOURCE_LAST_ALERT_AT: "fc64a598-ccbe-44e3-bd42-716e32d626a2",
101
+ INTAKE_SOURCE_HONEYPOT_FIELD: "5c145686-50de-496c-bb45-a30c8b39034c",
102
+ INTAKE_SOURCE_MERGE_POLICY: "5a946f85-b1c2-493e-92c6-31c50e637a7e",
103
+ // IntakeFieldRule fields
104
+ INTAKE_FIELD_RULE_MERGE_STRATEGY: "40c6fdcb-527a-4691-8518-c40a3d3e34c6",
105
+ // Logic Functions
106
+ CONTRACT_LOGIC_FUNCTION: "2eea4152-1b08-4b8c-903c-f4c7ee7275d0",
107
+ REPLAY_LOG_LOGIC_FUNCTION: "8ff98390-7358-4a91-84f3-435f758182a3",
108
+ REPLAY_BULK_LOGIC_FUNCTION: "b82ed4c4-07bc-4095-8ee5-611200626dc0",
109
+ QUARANTINE_LIST_LOGIC_FUNCTION: "0203643c-2a97-4e7c-b4a5-b76d721cf23a",
110
+ QUARANTINE_RELEASE_LOGIC_FUNCTION: "5a10d4fe-c807-45c6-9e61-c191d4e37b4e",
111
+ QUARANTINE_DISCARD_LOGIC_FUNCTION: "1454a56e-6197-4dfd-9b41-5cc3b1436af7",
112
+ CHECK_SILENCE_LOGIC_FUNCTION: "e88cd5f2-ab96-4f89-ac55-78db66b32d66",
113
+ // Application variables
114
+ VAR_MERGE_POLICY: "a84fe9fd-3d7c-41d9-b0e8-dfdc1a87f348",
115
+ VAR_SPAM_FILTER_ENABLED: "570d3ba8-1904-4bad-a5ce-27e208e6414a",
116
+ VAR_SPAM_SCORE_THRESHOLD: "4395574d-24b4-4e0e-afa3-1e90fcd4f470",
117
+ VAR_HONEYPOT_FIELD: "2cf56c56-f5cb-47c5-aa43-84f301060a95",
118
+ VAR_RAW_PAYLOAD_RETENTION: "f3314ad6-05b9-4127-bb88-cea5f2018dc9",
119
+ VAR_RAW_PAYLOAD_MAX_BYTES: "b002ca76-df3d-47b4-8ac5-d3f562239c48",
120
+ VAR_REPLAY_MAX_BATCH: "cf55956f-177e-4afd-8d9f-7f42c614209c"
121
+ };
122
+
123
+ // src/constants/version.ts
124
+ var APP_VERSION = "0.4.0";
125
+
126
+ // src/constants/field-map.ts
127
+ var FIELD_MAP = {
128
+ // ── Phone ─────────────────────────────────────────────────────────────────
129
+ phone: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
130
+ phone_number: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
131
+ tel: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
132
+ telephone: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
133
+ mobile: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
134
+ mobile_number: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
135
+ cell: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
136
+ cell_phone: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
137
+ contact_phone: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
138
+ phone1: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
139
+ primary_phone: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
140
+ // ── Email ─────────────────────────────────────────────────────────────────
141
+ email: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
142
+ email_address: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
143
+ e_mail: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
144
+ contact_email: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
145
+ email1: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
146
+ user_email: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
147
+ primary_email: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
148
+ // ── First name ────────────────────────────────────────────────────────────
149
+ first_name: { canonicalName: "firstName", twentyType: "TEXT", action: "field" },
150
+ fname: { canonicalName: "firstName", twentyType: "TEXT", action: "field" },
151
+ given_name: { canonicalName: "firstName", twentyType: "TEXT", action: "field" },
152
+ forename: { canonicalName: "firstName", twentyType: "TEXT", action: "field" },
153
+ // ── Last name ─────────────────────────────────────────────────────────────
154
+ last_name: { canonicalName: "lastName", twentyType: "TEXT", action: "field" },
155
+ lname: { canonicalName: "lastName", twentyType: "TEXT", action: "field" },
156
+ surname: { canonicalName: "lastName", twentyType: "TEXT", action: "field" },
157
+ family_name: { canonicalName: "lastName", twentyType: "TEXT", action: "field" },
158
+ // ── Full name (will be split on ingest) ───────────────────────────────────
159
+ name: { canonicalName: "fullName", twentyType: "FULL_NAME", action: "field" },
160
+ full_name: { canonicalName: "fullName", twentyType: "FULL_NAME", action: "field" },
161
+ contact_name: { canonicalName: "fullName", twentyType: "FULL_NAME", action: "field" },
162
+ your_name: { canonicalName: "fullName", twentyType: "FULL_NAME", action: "field" },
163
+ // ── Company ───────────────────────────────────────────────────────────────
164
+ company: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
165
+ company_name: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
166
+ business: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
167
+ business_name: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
168
+ organization: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
169
+ organisation: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
170
+ employer: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
171
+ firm: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
172
+ // ── Website / Domain ──────────────────────────────────────────────────────
173
+ website: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
174
+ website_url: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
175
+ url: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
176
+ domain: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
177
+ homepage: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
178
+ web: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
179
+ site: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
180
+ site_url: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
181
+ // ── Address ───────────────────────────────────────────────────────────────
182
+ street: { canonicalName: "addressStreet1", twentyType: "TEXT", action: "field" },
183
+ street_address: { canonicalName: "addressStreet1", twentyType: "TEXT", action: "field" },
184
+ address_line_1: { canonicalName: "addressStreet1", twentyType: "TEXT", action: "field" },
185
+ address1: { canonicalName: "addressStreet1", twentyType: "TEXT", action: "field" },
186
+ address_line_2: { canonicalName: "addressStreet2", twentyType: "TEXT", action: "field" },
187
+ address2: { canonicalName: "addressStreet2", twentyType: "TEXT", action: "field" },
188
+ city: { canonicalName: "addressCity", twentyType: "TEXT", action: "field" },
189
+ address_city: { canonicalName: "addressCity", twentyType: "TEXT", action: "field" },
190
+ town: { canonicalName: "addressCity", twentyType: "TEXT", action: "field" },
191
+ state: { canonicalName: "addressState", twentyType: "TEXT", action: "field" },
192
+ province: { canonicalName: "addressState", twentyType: "TEXT", action: "field" },
193
+ region: { canonicalName: "addressState", twentyType: "TEXT", action: "field" },
194
+ address_state: { canonicalName: "addressState", twentyType: "TEXT", action: "field" },
195
+ zip: { canonicalName: "addressPostcode", twentyType: "TEXT", action: "field" },
196
+ zip_code: { canonicalName: "addressPostcode", twentyType: "TEXT", action: "field" },
197
+ postal_code: { canonicalName: "addressPostcode", twentyType: "TEXT", action: "field" },
198
+ postcode: { canonicalName: "addressPostcode", twentyType: "TEXT", action: "field" },
199
+ country: { canonicalName: "addressCountry", twentyType: "TEXT", action: "field" },
200
+ address_country: { canonicalName: "addressCountry", twentyType: "TEXT", action: "field" },
201
+ // ── Social ────────────────────────────────────────────────────────────────
202
+ linkedin: { canonicalName: "linkedInLink", twentyType: "LINKS", action: "field" },
203
+ linkedin_url: { canonicalName: "linkedInLink", twentyType: "LINKS", action: "field" },
204
+ linkedin_profile: { canonicalName: "linkedInLink", twentyType: "LINKS", action: "field" },
205
+ twitter: { canonicalName: "xLink", twentyType: "LINKS", action: "field" },
206
+ twitter_url: { canonicalName: "xLink", twentyType: "LINKS", action: "field" },
207
+ x_profile: { canonicalName: "xLink", twentyType: "LINKS", action: "field" },
208
+ // ── Pipeline-specific (Google / business intel) ───────────────────────────
209
+ google_places_url: { canonicalName: "extGooglePlacesUrl", twentyType: "LINKS", action: "field" },
210
+ places_url: { canonicalName: "extGooglePlacesUrl", twentyType: "LINKS", action: "field" },
211
+ google_maps_url: { canonicalName: "extGooglePlacesUrl", twentyType: "LINKS", action: "field" },
212
+ maps_url: { canonicalName: "extGooglePlacesUrl", twentyType: "LINKS", action: "field" },
213
+ rating: { canonicalName: "extGoogleRating", twentyType: "NUMBER", action: "field" },
214
+ google_rating: { canonicalName: "extGoogleRating", twentyType: "NUMBER", action: "field" },
215
+ stars: { canonicalName: "extGoogleRating", twentyType: "NUMBER", action: "field" },
216
+ review_count: { canonicalName: "extReviewCount", twentyType: "NUMBER", action: "field" },
217
+ reviews_count: { canonicalName: "extReviewCount", twentyType: "NUMBER", action: "field" },
218
+ total_reviews: { canonicalName: "extReviewCount", twentyType: "NUMBER", action: "field" },
219
+ num_reviews: { canonicalName: "extReviewCount", twentyType: "NUMBER", action: "field" },
220
+ marketing_score: { canonicalName: "extMarketingScore", twentyType: "NUMBER", action: "field" },
221
+ lead_score: { canonicalName: "extLeadScore", twentyType: "NUMBER", action: "field" },
222
+ analyzed_url: { canonicalName: "extAnalyzedUrl", twentyType: "LINKS", action: "field" },
223
+ // ── UTM — always captured in note ────────────────────────────────────────
224
+ utm_source: { canonicalName: "utm_source", twentyType: "TEXT", action: "note" },
225
+ utm_medium: { canonicalName: "utm_medium", twentyType: "TEXT", action: "note" },
226
+ utm_campaign: { canonicalName: "utm_campaign", twentyType: "TEXT", action: "note" },
227
+ utm_content: { canonicalName: "utm_content", twentyType: "TEXT", action: "note" },
228
+ utm_term: { canonicalName: "utm_term", twentyType: "TEXT", action: "note" },
229
+ ref: { canonicalName: "ref", twentyType: "TEXT", action: "note" },
230
+ referrer: { canonicalName: "referrer", twentyType: "TEXT", action: "note" },
231
+ source_page: { canonicalName: "source_page", twentyType: "TEXT", action: "note" },
232
+ landing_page: { canonicalName: "landing_page", twentyType: "TEXT", action: "note" },
233
+ // ── Known note fields — always prose ─────────────────────────────────────
234
+ message: { canonicalName: "message", twentyType: "RICH_TEXT", action: "note" },
235
+ description: { canonicalName: "description", twentyType: "RICH_TEXT", action: "note" },
236
+ analysis: { canonicalName: "analysis", twentyType: "RICH_TEXT", action: "note" },
237
+ notes: { canonicalName: "notes", twentyType: "RICH_TEXT", action: "note" },
238
+ comments: { canonicalName: "comments", twentyType: "RICH_TEXT", action: "note" },
239
+ summary: { canonicalName: "summary", twentyType: "RICH_TEXT", action: "note" },
240
+ inquiry: { canonicalName: "inquiry", twentyType: "RICH_TEXT", action: "note" },
241
+ body: { canonicalName: "body", twentyType: "RICH_TEXT", action: "note" },
242
+ details: { canonicalName: "details", twentyType: "RICH_TEXT", action: "note" },
243
+ content: { canonicalName: "content", twentyType: "RICH_TEXT", action: "note" },
244
+ text: { canonicalName: "text", twentyType: "RICH_TEXT", action: "note" },
245
+ feedback: { canonicalName: "feedback", twentyType: "RICH_TEXT", action: "note" },
246
+ about: { canonicalName: "about", twentyType: "RICH_TEXT", action: "note" },
247
+ additional_info: { canonicalName: "additional_info", twentyType: "RICH_TEXT", action: "note" },
248
+ // ── System / internal — skip entirely ────────────────────────────────────
249
+ _id: { canonicalName: "_id", twentyType: "TEXT", action: "skip" },
250
+ __v: { canonicalName: "__v", twentyType: "TEXT", action: "skip" },
251
+ created_at: { canonicalName: "createdAt", twentyType: "DATE_TIME", action: "skip" },
252
+ updated_at: { canonicalName: "updatedAt", twentyType: "DATE_TIME", action: "skip" },
253
+ form_id: { canonicalName: "form_id", twentyType: "TEXT", action: "skip" },
254
+ submission_id: { canonicalName: "submission_id", twentyType: "TEXT", action: "skip" },
255
+ token: { canonicalName: "token", twentyType: "TEXT", action: "skip" },
256
+ password: { canonicalName: "password", twentyType: "TEXT", action: "skip" },
257
+ secret: { canonicalName: "secret", twentyType: "TEXT", action: "skip" }
258
+ };
259
+
260
+ // src/lib/rest-client.ts
261
+ var apiUrl = () => process.env["TWENTY_API_URL"] ?? "";
262
+ var token = () => process.env["TWENTY_APP_ACCESS_TOKEN"] ?? "";
263
+ async function request(base, method, path, body) {
264
+ const res = await fetch(`${base}${path}`, {
265
+ method,
266
+ headers: {
267
+ Authorization: `Bearer ${token()}`,
268
+ "Content-Type": "application/json"
269
+ },
270
+ body: body !== void 0 ? JSON.stringify(body) : void 0
271
+ });
272
+ const text = await res.text();
273
+ if (!res.ok) {
274
+ throw new Error(`${method} ${path} \u2192 ${res.status}: ${text}`);
275
+ }
276
+ return JSON.parse(text);
277
+ }
278
+ var coreApi = {
279
+ get: (path) => request(`${apiUrl()}/rest`, "GET", path),
280
+ post: (path, body) => request(`${apiUrl()}/rest`, "POST", path, body),
281
+ patch: (path, body) => request(`${apiUrl()}/rest`, "PATCH", path, body)
282
+ };
283
+ var metaApi = {
284
+ get: (path) => request(`${apiUrl()}/rest/metadata`, "GET", path),
285
+ post: (path, body) => request(`${apiUrl()}/rest/metadata`, "POST", path, body)
286
+ };
287
+
288
+ // src/lib/diff.ts
289
+ async function objectFieldNames() {
290
+ try {
291
+ const res = await metaApi.get("/objects");
292
+ const list = Array.isArray(res) ? res : res.data ?? [];
293
+ const out = {};
294
+ for (const node of list) {
295
+ out[node.nameSingular] = new Set((node.fields ?? []).map((f) => f.name));
296
+ }
297
+ return out;
298
+ } catch {
299
+ return {};
300
+ }
301
+ }
302
+
303
+ // src/lib/settings.ts
304
+ function str(key, fallback) {
305
+ const v = process.env[key];
306
+ return v === void 0 || v === "" ? fallback : v;
307
+ }
308
+ function int(key, fallback) {
309
+ const parsed = parseInt(process.env[key] ?? "", 10);
310
+ return Number.isFinite(parsed) ? parsed : fallback;
311
+ }
312
+ function bool(key, fallback) {
313
+ const v = process.env[key];
314
+ if (v === void 0 || v === "") return fallback;
315
+ return v.toLowerCase() === "true";
316
+ }
317
+ function appLabel() {
318
+ return str("INTAKE_APP_LABEL", "Intake");
319
+ }
320
+ function defaultOpportunityStage() {
321
+ return str("INTAKE_DEFAULT_OPP_STAGE", "NEW");
322
+ }
323
+ function fieldCreationEnabled() {
324
+ return process.env["INTAKE_FIELD_CREATION_ENABLED"] !== "false";
325
+ }
326
+ function maxExtFields() {
327
+ return int("INTAKE_MAX_EXT_FIELDS", 50);
328
+ }
329
+ function dedupWindowMinutes() {
330
+ return int("INTAKE_DEDUP_WINDOW_MINUTES", 5);
331
+ }
332
+ function requireHmac() {
333
+ return bool("INTAKE_REQUIRE_HMAC", false);
334
+ }
335
+ function mergePolicy() {
336
+ return str("INTAKE_MERGE_POLICY", "PRESERVE").toUpperCase() === "NEWEST_WINS" ? "NEWEST_WINS" : "PRESERVE";
337
+ }
338
+ function spamFilterEnabled() {
339
+ return bool("INTAKE_SPAM_FILTER_ENABLED", false);
340
+ }
341
+ function spamScoreThreshold() {
342
+ return int("INTAKE_SPAM_SCORE_THRESHOLD", 5);
343
+ }
344
+ function honeypotField() {
345
+ return str("INTAKE_HONEYPOT_FIELD", "");
346
+ }
347
+ function rawPayloadRetention() {
348
+ return str("INTAKE_RAW_PAYLOAD_RETENTION", "FULL").toUpperCase() === "NONE" ? "NONE" : "FULL";
349
+ }
350
+ function rawPayloadMaxBytes() {
351
+ return int("INTAKE_RAW_PAYLOAD_MAX_BYTES", 65e3);
352
+ }
353
+ function replayMaxBatch() {
354
+ return Math.min(int("INTAKE_REPLAY_MAX_BATCH", 50), 500);
355
+ }
356
+ function snapshot() {
357
+ return {
358
+ INTAKE_APP_LABEL: appLabel(),
359
+ INTAKE_DEFAULT_OPP_STAGE: defaultOpportunityStage(),
360
+ INTAKE_FIELD_CREATION_ENABLED: fieldCreationEnabled(),
361
+ INTAKE_MAX_EXT_FIELDS: maxExtFields(),
362
+ INTAKE_DEDUP_WINDOW_MINUTES: dedupWindowMinutes(),
363
+ INTAKE_REQUIRE_HMAC: requireHmac(),
364
+ INTAKE_MERGE_POLICY: mergePolicy(),
365
+ INTAKE_SPAM_FILTER_ENABLED: spamFilterEnabled(),
366
+ INTAKE_SPAM_SCORE_THRESHOLD: spamScoreThreshold(),
367
+ INTAKE_HONEYPOT_FIELD: honeypotField(),
368
+ INTAKE_RAW_PAYLOAD_RETENTION: rawPayloadRetention(),
369
+ INTAKE_RAW_PAYLOAD_MAX_BYTES: rawPayloadMaxBytes(),
370
+ INTAKE_REPLAY_MAX_BATCH: replayMaxBatch()
371
+ };
372
+ }
373
+
374
+ // src/logic-functions/contract.ts
375
+ var handler = async () => {
376
+ const [sources, rules, objectFields] = await Promise.all([
377
+ loadSources(),
378
+ loadRules(),
379
+ objectFieldNames()
380
+ ]);
381
+ const canonical = /* @__PURE__ */ new Map();
382
+ for (const [key, entry] of Object.entries(FIELD_MAP)) {
383
+ const existing = canonical.get(entry.canonicalName);
384
+ if (existing) {
385
+ existing.accepts.push(key);
386
+ } else {
387
+ canonical.set(entry.canonicalName, {
388
+ accepts: [key],
389
+ type: entry.twentyType,
390
+ action: entry.action
391
+ });
392
+ }
393
+ }
394
+ return {
395
+ statusCode: 200,
396
+ body: {
397
+ app: {
398
+ name: "twenty-app-intake",
399
+ version: APP_VERSION,
400
+ requiredServerVersion: ">=2.26.0"
401
+ },
402
+ endpoints: ENDPOINTS,
403
+ targetObjects: ["person", "company", "opportunity", "note"],
404
+ /** How a payload key becomes a field, in the order the pipeline tries. */
405
+ resolutionOrder: [
406
+ "Built-in field map (canonicalFields below)",
407
+ "Field rules configured in the workspace (rules below)",
408
+ "Passthrough \u2014 the key is camel-cased, prefixed ext, and the type is detected from the value"
409
+ ],
410
+ canonicalFields: [...canonical.entries()].map(([name, v]) => ({
411
+ canonicalName: name,
412
+ accepts: v.accepts,
413
+ twentyType: v.type,
414
+ action: v.action
415
+ })),
416
+ /**
417
+ * Custom fields Intake has already created or can already write to. This
418
+ * is the live schema, not a guess — a caller can check whether the field
419
+ * it wants exists before sending anything.
420
+ */
421
+ extFields: {
422
+ person: extOnly(objectFields["person"]),
423
+ company: extOnly(objectFields["company"]),
424
+ opportunity: extOnly(objectFields["opportunity"])
425
+ },
426
+ rules,
427
+ sources,
428
+ settings: snapshot(),
429
+ routing: {
430
+ note: "A field reaches the Opportunity only when a rule says so or the key carries an explicit prefix. Nothing is routed to a deal by inference.",
431
+ opportunityKeyPrefixes: ["opportunity_", "opp_", "deal_"]
432
+ }
433
+ }
434
+ };
435
+ };
436
+ var ENDPOINTS = [
437
+ { method: "POST", path: "/s/intake/:slug", auth: false, description: "Ingest a payload." },
438
+ { method: "POST", path: "/s/intake/:slug/test", auth: false, description: "Dry run \u2014 returns a structured diff, writes nothing." },
439
+ { method: "GET", path: "/s/intake/health", auth: false, description: "Health check, plus a summary of silent sources." },
440
+ { method: "GET", path: "/s/intake/contract", auth: true, description: "This document." },
441
+ { method: "POST", path: "/s/intake/sources/register", auth: true, description: "Register a source and get a webhook URL and secret." },
442
+ { method: "POST", path: "/s/intake/sources/check-silence", auth: true, description: "Check every source against its expected cadence." },
443
+ { method: "POST", path: "/s/intake/logs/:logId/retry", auth: true, description: "Retry a failed ingestion from its stored payload." },
444
+ { method: "POST", path: "/s/intake/logs/:logId/replay", auth: true, description: "Re-run any stored payload through the current rules." },
445
+ { method: "POST", path: "/s/intake/replay", auth: true, description: "Bulk replay a selection of stored payloads." },
446
+ { method: "GET", path: "/s/intake/quarantine", auth: true, description: "List held payloads and why they were held." },
447
+ { method: "POST", path: "/s/intake/quarantine/:logId/release", auth: true, description: "Ingest a held payload, overruling the filter." },
448
+ { method: "POST", path: "/s/intake/quarantine/:logId/discard", auth: true, description: "Mark a held payload as junk." }
449
+ ];
450
+ function extOnly(fields) {
451
+ return [...fields ?? []].filter((f) => /^ext[A-Z]/.test(f)).sort();
452
+ }
453
+ async function loadSources() {
454
+ try {
455
+ const res = await coreApi.get("/intakeSources?limit=200");
456
+ return (res.data?.intakeSources?.edges ?? []).map(({ node }) => ({
457
+ slug: node["slug"],
458
+ name: node["name"],
459
+ status: node["status"],
460
+ targetObject: node["targetObject"],
461
+ createOpportunity: node["createOpportunity"],
462
+ // Whether requests must be signed — never the secret itself.
463
+ requiresSignature: Boolean(node["webhookSecret"]),
464
+ honeypotField: node["honeypotField"] ?? null,
465
+ mergePolicy: node["mergePolicy"] ?? null,
466
+ expectedCadenceHours: node["expectedCadenceHours"] ?? null,
467
+ healthStatus: node["healthStatus"] ?? null,
468
+ lastIngestedAt: node["lastIngestedAt"] ?? null,
469
+ totalIngested: node["totalIngested"] ?? 0
470
+ }));
471
+ } catch {
472
+ return [];
473
+ }
474
+ }
475
+ async function loadRules() {
476
+ try {
477
+ const res = await coreApi.get("/intakeFieldRules?limit=200&orderBy=priority[DescNullsLast]");
478
+ return (res.data?.intakeFieldRules?.edges ?? []).map(({ node }) => ({
479
+ inputPattern: node["inputPattern"],
480
+ canonicalName: node["canonicalName"],
481
+ fieldType: node["fieldType"],
482
+ targetObject: node["targetObject"] ?? "AUTO",
483
+ mergeStrategy: node["mergeStrategy"] ?? "INHERIT",
484
+ priority: node["priority"] ?? 0,
485
+ isActive: node["isActive"] ?? true,
486
+ scope: node["intakeSourceId"] ? "source" : "global"
487
+ }));
488
+ } catch {
489
+ return [];
490
+ }
491
+ }
492
+ var contract_default = defineLogicFunction({
493
+ universalIdentifier: IDS.CONTRACT_LOGIC_FUNCTION,
494
+ name: "intake-contract",
495
+ description: "Describes what Intake accepts and how it is currently configured, in one call.",
496
+ timeoutSeconds: 20,
497
+ handler,
498
+ httpRouteTriggerSettings: {
499
+ path: "/intake/contract",
500
+ httpMethod: "GET",
501
+ isAuthRequired: true
502
+ }
503
+ });
504
+ export {
505
+ contract_default as default
506
+ };
507
+ //# sourceMappingURL=contract.mjs.map