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.
- package/README.md +292 -7
- package/manifest.json +389 -15
- package/package.json +1 -1
- package/src/front-components/settings-panel.mjs +7 -7
- package/src/front-components/settings-panel.mjs.map +2 -2
- package/src/front-components/settings-panel.tsx +49 -0
- package/src/logic-functions/check-silence.mjs +263 -0
- package/src/logic-functions/check-silence.mjs.map +7 -0
- package/src/logic-functions/check-silence.ts +52 -0
- package/src/logic-functions/contract.mjs +507 -0
- package/src/logic-functions/contract.mjs.map +7 -0
- package/src/logic-functions/contract.ts +172 -0
- package/src/logic-functions/health.mjs +125 -5
- package/src/logic-functions/health.mjs.map +3 -3
- package/src/logic-functions/health.ts +41 -5
- package/src/logic-functions/quarantine-discard.mjs +380 -0
- package/src/logic-functions/quarantine-discard.mjs.map +7 -0
- package/src/logic-functions/quarantine-discard.ts +65 -0
- package/src/logic-functions/quarantine-list.mjs +211 -0
- package/src/logic-functions/quarantine-list.mjs.map +7 -0
- package/src/logic-functions/quarantine-list.ts +93 -0
- package/src/logic-functions/quarantine-release.mjs +1974 -0
- package/src/logic-functions/quarantine-release.mjs.map +7 -0
- package/src/logic-functions/quarantine-release.ts +94 -0
- package/src/logic-functions/register.mjs +34 -1
- package/src/logic-functions/register.mjs.map +2 -2
- package/src/logic-functions/replay-bulk.mjs +2040 -0
- package/src/logic-functions/replay-bulk.mjs.map +7 -0
- package/src/logic-functions/replay-bulk.ts +83 -0
- package/src/logic-functions/replay-log.mjs +1987 -0
- package/src/logic-functions/replay-log.mjs.map +7 -0
- package/src/logic-functions/replay-log.ts +54 -0
- package/src/logic-functions/retry.mjs +1001 -248
- package/src/logic-functions/retry.mjs.map +4 -4
- package/src/logic-functions/retry.ts +45 -68
- package/src/logic-functions/test-ingest.mjs +616 -28
- package/src/logic-functions/test-ingest.mjs.map +4 -4
- package/src/logic-functions/test-ingest.ts +132 -28
- package/src/logic-functions/webhook.mjs +965 -209
- package/src/logic-functions/webhook.mjs.map +4 -4
- package/src/logic-functions/webhook.ts +23 -48
- package/src/post-install.mjs +34 -1
- package/src/post-install.mjs.map +2 -2
|
@@ -1,12 +1,48 @@
|
|
|
1
|
-
import { defineLogicFunction } from 'twenty-sdk/define';
|
|
1
|
+
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
|
|
2
2
|
import { CoreApiClient } from 'twenty-client-sdk/core';
|
|
3
3
|
import { IDS } from '../constants/universal-identifiers';
|
|
4
|
+
import { APP_VERSION } from '../constants/version';
|
|
5
|
+
import { loadSources, assess } from '../lib/silence';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Health, plus the sources that have gone quiet.
|
|
9
|
+
*
|
|
10
|
+
* The existing `status` and `timestamp` keys and the 200/503 behaviour are
|
|
11
|
+
* untouched, because monitors are already pointed at this and an upgrade must
|
|
12
|
+
* not start paging anyone. The silence summary is added alongside, and only
|
|
13
|
+
* changes the status code when the caller asks for it with `?strict=true` —
|
|
14
|
+
* which is the form to point a monitor at if you want a dead form to page you.
|
|
15
|
+
*/
|
|
16
|
+
const handler = async (params: RoutePayload) => {
|
|
17
|
+
const strict = (params.queryParameters ?? {})['strict'] === 'true';
|
|
4
18
|
|
|
5
|
-
const handler = async () => {
|
|
6
19
|
try {
|
|
7
20
|
const client = new CoreApiClient();
|
|
8
21
|
await client.query({ intakeSources: { edges: { node: { id: true } } } });
|
|
9
|
-
|
|
22
|
+
|
|
23
|
+
const verdicts = (await loadSources()).map((s) => assess(s));
|
|
24
|
+
const silent = verdicts.filter((v) => v.health === 'SILENT' || v.health === 'NEVER_RECEIVED');
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
statusCode: strict && silent.length > 0 ? 503 : 200,
|
|
28
|
+
body: {
|
|
29
|
+
status: strict && silent.length > 0 ? 'degraded' : 'ok',
|
|
30
|
+
timestamp: new Date().toISOString(),
|
|
31
|
+
version: APP_VERSION,
|
|
32
|
+
sources: {
|
|
33
|
+
total: verdicts.length,
|
|
34
|
+
monitored: verdicts.filter((v) => v.health !== 'NOT_MONITORED').length,
|
|
35
|
+
silent: silent.length,
|
|
36
|
+
silentSources: silent.map((v) => ({
|
|
37
|
+
slug: v.slug,
|
|
38
|
+
name: v.name,
|
|
39
|
+
health: v.health,
|
|
40
|
+
hoursSinceLastIngest: v.hoursSinceLastIngest,
|
|
41
|
+
expectedCadenceHours: v.expectedCadenceHours,
|
|
42
|
+
})),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
10
46
|
} catch (e) {
|
|
11
47
|
return { statusCode: 503, body: { status: 'error', message: String(e) } };
|
|
12
48
|
}
|
|
@@ -15,8 +51,8 @@ const handler = async () => {
|
|
|
15
51
|
export default defineLogicFunction({
|
|
16
52
|
universalIdentifier: IDS.HEALTH_LOGIC_FUNCTION,
|
|
17
53
|
name: 'intake-health',
|
|
18
|
-
description: 'Health check for the Intake app.',
|
|
19
|
-
timeoutSeconds:
|
|
54
|
+
description: 'Health check for the Intake app, including sources that have fallen silent.',
|
|
55
|
+
timeoutSeconds: 15,
|
|
20
56
|
handler,
|
|
21
57
|
httpRouteTriggerSettings: {
|
|
22
58
|
path: '/intake/health',
|
|
@@ -0,0 +1,380 @@
|
|
|
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/lib/rest-client.ts
|
|
124
|
+
var apiUrl = () => process.env["TWENTY_API_URL"] ?? "";
|
|
125
|
+
var token = () => process.env["TWENTY_APP_ACCESS_TOKEN"] ?? "";
|
|
126
|
+
async function request(base, method, path, body) {
|
|
127
|
+
const res = await fetch(`${base}${path}`, {
|
|
128
|
+
method,
|
|
129
|
+
headers: {
|
|
130
|
+
Authorization: `Bearer ${token()}`,
|
|
131
|
+
"Content-Type": "application/json"
|
|
132
|
+
},
|
|
133
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
134
|
+
});
|
|
135
|
+
const text = await res.text();
|
|
136
|
+
if (!res.ok) {
|
|
137
|
+
throw new Error(`${method} ${path} \u2192 ${res.status}: ${text}`);
|
|
138
|
+
}
|
|
139
|
+
return JSON.parse(text);
|
|
140
|
+
}
|
|
141
|
+
var coreApi = {
|
|
142
|
+
get: (path) => request(`${apiUrl()}/rest`, "GET", path),
|
|
143
|
+
post: (path, body) => request(`${apiUrl()}/rest`, "POST", path, body),
|
|
144
|
+
patch: (path, body) => request(`${apiUrl()}/rest`, "PATCH", path, body)
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/lib/ingest.ts
|
|
148
|
+
import { CoreApiClient } from "twenty-client-sdk/core";
|
|
149
|
+
import { MetadataApiClient } from "twenty-client-sdk/metadata";
|
|
150
|
+
|
|
151
|
+
// src/lib/stages/load-source.stage.ts
|
|
152
|
+
var BASE_FIELDS = {
|
|
153
|
+
id: true,
|
|
154
|
+
name: true,
|
|
155
|
+
slug: true,
|
|
156
|
+
targetObject: true,
|
|
157
|
+
webhookSecret: true,
|
|
158
|
+
status: true,
|
|
159
|
+
createOpportunity: true,
|
|
160
|
+
opportunityNameTemplate: true
|
|
161
|
+
};
|
|
162
|
+
var V4_FIELDS = {
|
|
163
|
+
...BASE_FIELDS,
|
|
164
|
+
honeypotField: true,
|
|
165
|
+
mergePolicy: true
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/constants/field-map.ts
|
|
169
|
+
var FIELD_MAP = {
|
|
170
|
+
// ── Phone ─────────────────────────────────────────────────────────────────
|
|
171
|
+
phone: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
172
|
+
phone_number: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
173
|
+
tel: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
174
|
+
telephone: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
175
|
+
mobile: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
176
|
+
mobile_number: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
177
|
+
cell: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
178
|
+
cell_phone: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
179
|
+
contact_phone: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
180
|
+
phone1: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
181
|
+
primary_phone: { canonicalName: "phone", twentyType: "PHONES", action: "field" },
|
|
182
|
+
// ── Email ─────────────────────────────────────────────────────────────────
|
|
183
|
+
email: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
|
|
184
|
+
email_address: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
|
|
185
|
+
e_mail: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
|
|
186
|
+
contact_email: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
|
|
187
|
+
email1: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
|
|
188
|
+
user_email: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
|
|
189
|
+
primary_email: { canonicalName: "email", twentyType: "EMAILS", action: "field" },
|
|
190
|
+
// ── First name ────────────────────────────────────────────────────────────
|
|
191
|
+
first_name: { canonicalName: "firstName", twentyType: "TEXT", action: "field" },
|
|
192
|
+
fname: { canonicalName: "firstName", twentyType: "TEXT", action: "field" },
|
|
193
|
+
given_name: { canonicalName: "firstName", twentyType: "TEXT", action: "field" },
|
|
194
|
+
forename: { canonicalName: "firstName", twentyType: "TEXT", action: "field" },
|
|
195
|
+
// ── Last name ─────────────────────────────────────────────────────────────
|
|
196
|
+
last_name: { canonicalName: "lastName", twentyType: "TEXT", action: "field" },
|
|
197
|
+
lname: { canonicalName: "lastName", twentyType: "TEXT", action: "field" },
|
|
198
|
+
surname: { canonicalName: "lastName", twentyType: "TEXT", action: "field" },
|
|
199
|
+
family_name: { canonicalName: "lastName", twentyType: "TEXT", action: "field" },
|
|
200
|
+
// ── Full name (will be split on ingest) ───────────────────────────────────
|
|
201
|
+
name: { canonicalName: "fullName", twentyType: "FULL_NAME", action: "field" },
|
|
202
|
+
full_name: { canonicalName: "fullName", twentyType: "FULL_NAME", action: "field" },
|
|
203
|
+
contact_name: { canonicalName: "fullName", twentyType: "FULL_NAME", action: "field" },
|
|
204
|
+
your_name: { canonicalName: "fullName", twentyType: "FULL_NAME", action: "field" },
|
|
205
|
+
// ── Company ───────────────────────────────────────────────────────────────
|
|
206
|
+
company: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
|
|
207
|
+
company_name: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
|
|
208
|
+
business: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
|
|
209
|
+
business_name: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
|
|
210
|
+
organization: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
|
|
211
|
+
organisation: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
|
|
212
|
+
employer: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
|
|
213
|
+
firm: { canonicalName: "companyName", twentyType: "TEXT", action: "field" },
|
|
214
|
+
// ── Website / Domain ──────────────────────────────────────────────────────
|
|
215
|
+
website: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
|
|
216
|
+
website_url: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
|
|
217
|
+
url: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
|
|
218
|
+
domain: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
|
|
219
|
+
homepage: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
|
|
220
|
+
web: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
|
|
221
|
+
site: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
|
|
222
|
+
site_url: { canonicalName: "domainName", twentyType: "LINKS", action: "field" },
|
|
223
|
+
// ── Address ───────────────────────────────────────────────────────────────
|
|
224
|
+
street: { canonicalName: "addressStreet1", twentyType: "TEXT", action: "field" },
|
|
225
|
+
street_address: { canonicalName: "addressStreet1", twentyType: "TEXT", action: "field" },
|
|
226
|
+
address_line_1: { canonicalName: "addressStreet1", twentyType: "TEXT", action: "field" },
|
|
227
|
+
address1: { canonicalName: "addressStreet1", twentyType: "TEXT", action: "field" },
|
|
228
|
+
address_line_2: { canonicalName: "addressStreet2", twentyType: "TEXT", action: "field" },
|
|
229
|
+
address2: { canonicalName: "addressStreet2", twentyType: "TEXT", action: "field" },
|
|
230
|
+
city: { canonicalName: "addressCity", twentyType: "TEXT", action: "field" },
|
|
231
|
+
address_city: { canonicalName: "addressCity", twentyType: "TEXT", action: "field" },
|
|
232
|
+
town: { canonicalName: "addressCity", twentyType: "TEXT", action: "field" },
|
|
233
|
+
state: { canonicalName: "addressState", twentyType: "TEXT", action: "field" },
|
|
234
|
+
province: { canonicalName: "addressState", twentyType: "TEXT", action: "field" },
|
|
235
|
+
region: { canonicalName: "addressState", twentyType: "TEXT", action: "field" },
|
|
236
|
+
address_state: { canonicalName: "addressState", twentyType: "TEXT", action: "field" },
|
|
237
|
+
zip: { canonicalName: "addressPostcode", twentyType: "TEXT", action: "field" },
|
|
238
|
+
zip_code: { canonicalName: "addressPostcode", twentyType: "TEXT", action: "field" },
|
|
239
|
+
postal_code: { canonicalName: "addressPostcode", twentyType: "TEXT", action: "field" },
|
|
240
|
+
postcode: { canonicalName: "addressPostcode", twentyType: "TEXT", action: "field" },
|
|
241
|
+
country: { canonicalName: "addressCountry", twentyType: "TEXT", action: "field" },
|
|
242
|
+
address_country: { canonicalName: "addressCountry", twentyType: "TEXT", action: "field" },
|
|
243
|
+
// ── Social ────────────────────────────────────────────────────────────────
|
|
244
|
+
linkedin: { canonicalName: "linkedInLink", twentyType: "LINKS", action: "field" },
|
|
245
|
+
linkedin_url: { canonicalName: "linkedInLink", twentyType: "LINKS", action: "field" },
|
|
246
|
+
linkedin_profile: { canonicalName: "linkedInLink", twentyType: "LINKS", action: "field" },
|
|
247
|
+
twitter: { canonicalName: "xLink", twentyType: "LINKS", action: "field" },
|
|
248
|
+
twitter_url: { canonicalName: "xLink", twentyType: "LINKS", action: "field" },
|
|
249
|
+
x_profile: { canonicalName: "xLink", twentyType: "LINKS", action: "field" },
|
|
250
|
+
// ── Pipeline-specific (Google / business intel) ───────────────────────────
|
|
251
|
+
google_places_url: { canonicalName: "extGooglePlacesUrl", twentyType: "LINKS", action: "field" },
|
|
252
|
+
places_url: { canonicalName: "extGooglePlacesUrl", twentyType: "LINKS", action: "field" },
|
|
253
|
+
google_maps_url: { canonicalName: "extGooglePlacesUrl", twentyType: "LINKS", action: "field" },
|
|
254
|
+
maps_url: { canonicalName: "extGooglePlacesUrl", twentyType: "LINKS", action: "field" },
|
|
255
|
+
rating: { canonicalName: "extGoogleRating", twentyType: "NUMBER", action: "field" },
|
|
256
|
+
google_rating: { canonicalName: "extGoogleRating", twentyType: "NUMBER", action: "field" },
|
|
257
|
+
stars: { canonicalName: "extGoogleRating", twentyType: "NUMBER", action: "field" },
|
|
258
|
+
review_count: { canonicalName: "extReviewCount", twentyType: "NUMBER", action: "field" },
|
|
259
|
+
reviews_count: { canonicalName: "extReviewCount", twentyType: "NUMBER", action: "field" },
|
|
260
|
+
total_reviews: { canonicalName: "extReviewCount", twentyType: "NUMBER", action: "field" },
|
|
261
|
+
num_reviews: { canonicalName: "extReviewCount", twentyType: "NUMBER", action: "field" },
|
|
262
|
+
marketing_score: { canonicalName: "extMarketingScore", twentyType: "NUMBER", action: "field" },
|
|
263
|
+
lead_score: { canonicalName: "extLeadScore", twentyType: "NUMBER", action: "field" },
|
|
264
|
+
analyzed_url: { canonicalName: "extAnalyzedUrl", twentyType: "LINKS", action: "field" },
|
|
265
|
+
// ── UTM — always captured in note ────────────────────────────────────────
|
|
266
|
+
utm_source: { canonicalName: "utm_source", twentyType: "TEXT", action: "note" },
|
|
267
|
+
utm_medium: { canonicalName: "utm_medium", twentyType: "TEXT", action: "note" },
|
|
268
|
+
utm_campaign: { canonicalName: "utm_campaign", twentyType: "TEXT", action: "note" },
|
|
269
|
+
utm_content: { canonicalName: "utm_content", twentyType: "TEXT", action: "note" },
|
|
270
|
+
utm_term: { canonicalName: "utm_term", twentyType: "TEXT", action: "note" },
|
|
271
|
+
ref: { canonicalName: "ref", twentyType: "TEXT", action: "note" },
|
|
272
|
+
referrer: { canonicalName: "referrer", twentyType: "TEXT", action: "note" },
|
|
273
|
+
source_page: { canonicalName: "source_page", twentyType: "TEXT", action: "note" },
|
|
274
|
+
landing_page: { canonicalName: "landing_page", twentyType: "TEXT", action: "note" },
|
|
275
|
+
// ── Known note fields — always prose ─────────────────────────────────────
|
|
276
|
+
message: { canonicalName: "message", twentyType: "RICH_TEXT", action: "note" },
|
|
277
|
+
description: { canonicalName: "description", twentyType: "RICH_TEXT", action: "note" },
|
|
278
|
+
analysis: { canonicalName: "analysis", twentyType: "RICH_TEXT", action: "note" },
|
|
279
|
+
notes: { canonicalName: "notes", twentyType: "RICH_TEXT", action: "note" },
|
|
280
|
+
comments: { canonicalName: "comments", twentyType: "RICH_TEXT", action: "note" },
|
|
281
|
+
summary: { canonicalName: "summary", twentyType: "RICH_TEXT", action: "note" },
|
|
282
|
+
inquiry: { canonicalName: "inquiry", twentyType: "RICH_TEXT", action: "note" },
|
|
283
|
+
body: { canonicalName: "body", twentyType: "RICH_TEXT", action: "note" },
|
|
284
|
+
details: { canonicalName: "details", twentyType: "RICH_TEXT", action: "note" },
|
|
285
|
+
content: { canonicalName: "content", twentyType: "RICH_TEXT", action: "note" },
|
|
286
|
+
text: { canonicalName: "text", twentyType: "RICH_TEXT", action: "note" },
|
|
287
|
+
feedback: { canonicalName: "feedback", twentyType: "RICH_TEXT", action: "note" },
|
|
288
|
+
about: { canonicalName: "about", twentyType: "RICH_TEXT", action: "note" },
|
|
289
|
+
additional_info: { canonicalName: "additional_info", twentyType: "RICH_TEXT", action: "note" },
|
|
290
|
+
// ── System / internal — skip entirely ────────────────────────────────────
|
|
291
|
+
_id: { canonicalName: "_id", twentyType: "TEXT", action: "skip" },
|
|
292
|
+
__v: { canonicalName: "__v", twentyType: "TEXT", action: "skip" },
|
|
293
|
+
created_at: { canonicalName: "createdAt", twentyType: "DATE_TIME", action: "skip" },
|
|
294
|
+
updated_at: { canonicalName: "updatedAt", twentyType: "DATE_TIME", action: "skip" },
|
|
295
|
+
form_id: { canonicalName: "form_id", twentyType: "TEXT", action: "skip" },
|
|
296
|
+
submission_id: { canonicalName: "submission_id", twentyType: "TEXT", action: "skip" },
|
|
297
|
+
token: { canonicalName: "token", twentyType: "TEXT", action: "skip" },
|
|
298
|
+
password: { canonicalName: "password", twentyType: "TEXT", action: "skip" },
|
|
299
|
+
secret: { canonicalName: "secret", twentyType: "TEXT", action: "skip" }
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/lib/stages/sync-schema.stage.ts
|
|
303
|
+
var STANDARD_FIELDS = /* @__PURE__ */ new Set([
|
|
304
|
+
"name",
|
|
305
|
+
"emails",
|
|
306
|
+
"phones",
|
|
307
|
+
"domainName",
|
|
308
|
+
"address",
|
|
309
|
+
"linkedInLink",
|
|
310
|
+
"xLink",
|
|
311
|
+
"companyName",
|
|
312
|
+
"firstName",
|
|
313
|
+
"lastName",
|
|
314
|
+
"fullName",
|
|
315
|
+
"jobTitle",
|
|
316
|
+
"city",
|
|
317
|
+
"employees",
|
|
318
|
+
"annualRecurringRevenue",
|
|
319
|
+
"idealCustomerProfile",
|
|
320
|
+
...Object.values(FIELD_MAP).map((e) => e.canonicalName)
|
|
321
|
+
]);
|
|
322
|
+
|
|
323
|
+
// src/lib/replay.ts
|
|
324
|
+
async function loadLog(logId) {
|
|
325
|
+
try {
|
|
326
|
+
const res = await coreApi.get(`/intakeLogs/${logId}`);
|
|
327
|
+
return res.data?.intakeLog ?? null;
|
|
328
|
+
} catch {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/logic-functions/quarantine-discard.ts
|
|
334
|
+
var handler = async (params) => {
|
|
335
|
+
const logId = params.pathParameters?.["logId"];
|
|
336
|
+
if (!logId) return { statusCode: 400, body: { error: "Missing logId in URL" } };
|
|
337
|
+
const body = params.body ?? {};
|
|
338
|
+
const purgePayload = body["purgePayload"] === true;
|
|
339
|
+
const log = await loadLog(logId);
|
|
340
|
+
if (!log) return { statusCode: 404, body: { error: `IntakeLog ${logId} not found` } };
|
|
341
|
+
if (log.status !== "QUARANTINED") {
|
|
342
|
+
return {
|
|
343
|
+
statusCode: 400,
|
|
344
|
+
body: { error: `Log ${logId} is ${log.status}, not QUARANTINED \u2014 nothing to discard`, status: log.status }
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
await coreApi.patch(`/intakeLogs/${logId}`, {
|
|
349
|
+
status: "DISCARDED",
|
|
350
|
+
...purgePayload ? { rawPayload: null } : {}
|
|
351
|
+
});
|
|
352
|
+
} catch (e) {
|
|
353
|
+
return { statusCode: 500, body: { error: `Could not discard log: ${String(e)}` } };
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
statusCode: 200,
|
|
357
|
+
body: {
|
|
358
|
+
success: true,
|
|
359
|
+
logId,
|
|
360
|
+
status: "DISCARDED",
|
|
361
|
+
payloadPurged: purgePayload
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
var quarantine_discard_default = defineLogicFunction({
|
|
366
|
+
universalIdentifier: IDS.QUARANTINE_DISCARD_LOGIC_FUNCTION,
|
|
367
|
+
name: "intake-quarantine-discard",
|
|
368
|
+
description: "Mark a quarantined payload as junk, optionally purging the stored body.",
|
|
369
|
+
timeoutSeconds: 15,
|
|
370
|
+
handler,
|
|
371
|
+
httpRouteTriggerSettings: {
|
|
372
|
+
path: "/intake/quarantine/:logId/discard",
|
|
373
|
+
httpMethod: "POST",
|
|
374
|
+
isAuthRequired: true
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
export {
|
|
378
|
+
quarantine_discard_default as default
|
|
379
|
+
};
|
|
380
|
+
//# sourceMappingURL=quarantine-discard.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["twenty-sdk-define-stub:__twenty-sdk-define-stub__", "../../../../src/constants/universal-identifiers.ts", "../../../../src/lib/rest-client.ts", "../../../../src/lib/ingest.ts", "../../../../src/lib/stages/load-source.stage.ts", "../../../../src/constants/field-map.ts", "../../../../src/lib/stages/sync-schema.stage.ts", "../../../../src/lib/replay.ts", "../../../../src/logic-functions/quarantine-discard.ts"],
|
|
4
|
+
"sourcesContent": ["\n// Auto-generated stub for twenty-sdk/define injected by the SDK CLI build.\n// Real implementations would pull in zod, twenty-shared and ~1MB of code; at\n// runtime only `default.config.handler` is consumed, so tiny no-ops suffice.\nconst __defineFactoryStub = (config) => ({\n success: true,\n config,\n errors: [],\n});\n\nconst __anyHandler = {\n get(_target, prop) {\n if (prop === '__esModule') return true;\n if (prop === Symbol.toPrimitive) return () => '';\n if (typeof prop === 'symbol') return undefined;\n return new Proxy(() => undefined, __anyHandler);\n },\n apply() {\n return new Proxy(() => undefined, __anyHandler);\n },\n};\nconst __anyStub = new Proxy(() => undefined, __anyHandler);\n\nexport const createValidationResult = __defineFactoryStub;\nexport const defineAgent = __defineFactoryStub;\nexport const defineApplication = __defineFactoryStub;\nexport const defineApplicationRole = __defineFactoryStub;\nexport const defineCommandMenuItem = __defineFactoryStub;\nexport const defineConnectionProvider = __defineFactoryStub;\nexport const defineField = __defineFactoryStub;\nexport const defineFrontComponent = __defineFactoryStub;\nexport const defineIndex = __defineFactoryStub;\nexport const defineLogicFunction = __defineFactoryStub;\nexport const defineNavigationMenuItem = __defineFactoryStub;\nexport const defineObject = __defineFactoryStub;\nexport const definePageLayout = __defineFactoryStub;\nexport const definePageLayoutTab = __defineFactoryStub;\nexport const definePermissionFlag = __defineFactoryStub;\nexport const definePostInstallLogicFunction = __defineFactoryStub;\nexport const definePreInstallLogicFunction = __defineFactoryStub;\nexport const defineRole = __defineFactoryStub;\nexport const defineSettingsFrontComponent = __defineFactoryStub;\nexport const defineSkill = __defineFactoryStub;\nexport const defineUninstallLogicFunction = __defineFactoryStub;\nexport const defineView = __defineFactoryStub;\nexport const defineViewField = __defineFactoryStub;\nexport const AggregateOperations = __anyStub;\nexport const DateDisplayFormat = __anyStub;\nexport const FieldMetadataSettingsOnClickAction = __anyStub;\nexport const FieldType = __anyStub;\nexport const HTTPMethod = __anyStub;\nexport const NavigationMenuItemType = __anyStub;\nexport const NumberDataType = __anyStub;\nexport const ObjectOpenRecordIn = __anyStub;\nexport const ObjectRecordGroupByDateGranularity = __anyStub;\nexport const OnDeleteAction = __anyStub;\nexport const PageLayoutTabLayoutMode = __anyStub;\nexport const PageLayoutType = __anyStub;\nexport const RelationType = __anyStub;\nexport const RowLevelPermissionPredicateGroupLogicalOperator = __anyStub;\nexport const RowLevelPermissionPredicateOperand = __anyStub;\nexport const STANDARD_OBJECT = __anyStub;\nexport const STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const STANDARD_PAGE_LAYOUT = __anyStub;\nexport const STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const SystemPermissionFlag = __anyStub;\nexport const ViewCalendarLayout = __anyStub;\nexport const ViewFilterGroupLogicalOperator = __anyStub;\nexport const ViewFilterOperand = __anyStub;\nexport const ViewKey = __anyStub;\nexport const ViewOpenRecordIn = __anyStub;\nexport const ViewSortDirection = __anyStub;\nexport const ViewType = __anyStub;\nexport const ViewVisibility = __anyStub;\nexport const canAccessFullAdminPanel = __anyStub;\nexport const canImpersonate = __anyStub;\nexport const every = __anyStub;\nexport const everyDefined = __anyStub;\nexport const everyEquals = __anyStub;\nexport const favoriteRecordIds = __anyStub;\nexport const featureFlags = __anyStub;\nexport const getFieldUniversalIdentifier = __anyStub;\nexport const getSystemRelationFieldUniversalIdentifier = __anyStub;\nexport const getSystemViewFieldUniversalIdentifier = __anyStub;\nexport const getSystemViewUniversalIdentifier = __anyStub;\nexport const hasAnySoftDeleteFilterOnView = __anyStub;\nexport const includes = __anyStub;\nexport const includesEvery = __anyStub;\nexport const isDashboardPageLayoutInEditMode = __anyStub;\nexport const isDefined = __anyStub;\nexport const isInSidePanel = __anyStub;\nexport const isLayoutCustomizationModeEnabled = __anyStub;\nexport const isNonEmptyString = __anyStub;\nexport const isSelectAll = __anyStub;\nexport const none = __anyStub;\nexport const noneDefined = __anyStub;\nexport const noneEquals = __anyStub;\nexport const numberOfSelectedRecords = __anyStub;\nexport const objectMetadataItem = __anyStub;\nexport const objectMetadataLabel = __anyStub;\nexport const objectPermissions = __anyStub;\nexport const pageType = __anyStub;\nexport const selectedRecords = __anyStub;\nexport const some = __anyStub;\nexport const someDefined = __anyStub;\nexport const someEquals = __anyStub;\nexport const someNonEmptyString = __anyStub;\nexport const targetObjectReadPermissions = __anyStub;\nexport const targetObjectWritePermissions = __anyStub;\nexport const validateFields = __anyStub;\n", "export const IDS = {\n APPLICATION: '96a389dd-384a-43a1-a376-9d3cf01b6ab7',\n DEFAULT_ROLE: '3dd2cdb0-32c7-44b4-9f7b-0a9c667ec249',\n\n // Objects\n INTAKE_SOURCE_OBJECT: '8c1c227c-db7d-4834-b328-cacbe8af0bfc',\n INTAKE_LOG_OBJECT: 'b77a2439-1503-4ef6-ab8b-8f4293bc19da',\n INTAKE_FIELD_RULE_OBJECT: 'e74df817-ff25-4bfb-bdf0-651662ca327e',\n\n // IntakeSource fields\n INTAKE_SOURCE_NAME: '0deb7e4b-bb90-4c86-b903-5208b42041e2',\n INTAKE_SOURCE_SLUG: '18f7645b-2dce-4145-b1b6-9cc3332c0578',\n INTAKE_SOURCE_TARGET_OBJECT: '65f3d7f7-a601-4375-9c67-684d0044620b',\n INTAKE_SOURCE_SECRET: '4c919402-07f5-4041-ade6-a42402f1881f',\n INTAKE_SOURCE_STATUS: '6f1fa6fe-02b4-48ab-b1ee-97c600bbf483',\n INTAKE_SOURCE_CREATE_OPP: '67ae59aa-2275-4ef2-b594-137f6bfafd99',\n INTAKE_SOURCE_OPP_TEMPLATE: '3907b346-77a6-4c79-a4d7-6e1cc20bb82b',\n INTAKE_SOURCE_TOTAL_INGESTED: '69655a93-bc70-48bd-874e-aeccc63ee1a8',\n INTAKE_SOURCE_TOTAL_FAILED: '27b3a499-183e-4cc3-8e40-938f953a986d',\n INTAKE_SOURCE_LAST_INGESTED: 'e167f628-9865-468e-894a-b4b6150cec26',\n\n // IntakeLog fields\n INTAKE_LOG_SOURCE_RELATION: 'b1c623ea-7a9a-47a5-bee4-35fdd668a637',\n INTAKE_LOG_STATUS: '8b6d26d1-f89c-486c-a617-9f9f6a9ca36d',\n INTAKE_LOG_PAYLOAD_HASH: 'c43bb1ad-e78a-4c60-bd95-02475ede7f44',\n INTAKE_LOG_RECORD_ID: '77a55116-a64b-4954-8369-341022dd5bdd',\n INTAKE_LOG_RECORD_TYPE: '02154e96-5808-48ed-8838-51083f174550',\n INTAKE_LOG_FIELDS_CREATED: '2295ca2b-b559-4b38-9771-12dd8d3d76d7',\n INTAKE_LOG_FIELDS_MATCHED: '83d07a0f-5bcd-4fb1-9759-f9b04b9381d8',\n INTAKE_LOG_OVERFLOW_COUNT: '4090acb1-f48a-4957-bb91-c642f8f434a7',\n INTAKE_LOG_PROCESSING_MS: 'a9191f27-4b40-42e9-8cce-20c75900e12c',\n INTAKE_LOG_ERROR: '8d9979f1-9774-43a8-b958-d1b37bd4ecc7',\n INTAKE_LOG_PROCESSED_AT: 'a8d99fbd-722e-4987-8afe-b830ac026f0a',\n\n // IntakeFieldRule fields\n INTAKE_FIELD_RULE_SOURCE_RELATION: '396dd1f1-3873-4efa-a6bb-7b41eda14d83',\n INTAKE_FIELD_RULE_INPUT_PATTERN: '757201a0-9f64-47b9-bf3a-a793074bf84e',\n INTAKE_FIELD_RULE_CANONICAL_NAME: 'fafebc55-a2cc-4c9f-975b-050c2676659d',\n INTAKE_FIELD_RULE_FIELD_TYPE: '72d1f6d8-d63e-419e-a244-8127d7b4705f',\n INTAKE_FIELD_RULE_PRIORITY: '911df299-940b-44f9-92f7-3fd01fde01a7',\n INTAKE_FIELD_RULE_IS_ACTIVE: '6f9cd96d-aecd-437f-a7b5-d45548782771',\n INTAKE_FIELD_RULE_TARGET_OBJECT: '7b8411a7-5e1d-40f2-a20c-683c07f67926',\n\n // Logic Functions\n WEBHOOK_LOGIC_FUNCTION: '8323c257-48fe-4bb6-b1dd-166844f575c0',\n HEALTH_LOGIC_FUNCTION: '4ece4e39-286b-4820-81f4-b7beee71d092',\n REGISTER_LOGIC_FUNCTION: '56d46e18-6cae-4d99-9893-b7d70740c9db',\n\n // IntakeSource reverse relation fields\n INTAKE_SOURCE_LOGS_REVERSE: '6ecfa369-22e0-4812-bb75-4338cea8be65',\n INTAKE_SOURCE_RULES_REVERSE: '776fefaf-0eee-4ec0-93e0-d11bd80c1e20',\n\n // v0.2.0 \u2014 rawPayload + retry + post-install + UI\n INTAKE_LOG_RAW_PAYLOAD: 'b65c9e1c-f2c1-4c52-a5b3-1e1ac3139692',\n RETRY_LOGIC_FUNCTION: '90f20396-a69a-4ae4-af20-6be65c565bb5',\n POST_INSTALL_LOGIC_FUNCTION: 'e836fdd6-fd54-4aa4-ba6d-87d5a02148ef',\n LOG_RETRY_FRONT_COMPONENT: '0cb97f6d-30e2-48c8-90e1-241b82870b4d',\n FIELD_RULES_TABLE_FRONT_COMPONENT: 'c35faad9-3468-4746-9c1c-b13d73e5e09b',\n SOURCE_PAGE_LAYOUT: '8c894ba8-5c8c-45ed-b0af-1d22ede93eb9',\n SOURCE_LAYOUT_TAB_RULES: '80f5c49a-083c-4062-8bd5-ae158abb6c33',\n SOURCE_LAYOUT_TAB_RULES_WIDGET: '26a9ebb7-1d59-4b03-a018-56968f6df224',\n LOG_PAGE_LAYOUT: 'b0972802-ede5-463c-9f59-cb05f04957db',\n LOG_LAYOUT_TAB_RETRY: '6b109695-ba3a-43de-a31a-23119ccf87ba',\n LOG_LAYOUT_TAB_RETRY_WIDGET: '11727c1a-5145-46de-9742-88d07d64c72e',\n SAMPLE_RULE_PHONE: '8440da8d-8ac4-4f4b-baa2-4d62936cde35',\n SAMPLE_RULE_EMAIL: 'd123cde4-05aa-44e1-86f6-454be1d7048b',\n SAMPLE_RULE_FULLNAME: '00e49fe8-1b48-4f5b-aaf4-414d0923c712',\n\n // Views & Navigation\n SOURCES_VIEW: '4435cd43-5c13-472f-b4f9-b686c0f46285',\n LOGS_VIEW: 'a2cd0a93-9778-4ba5-99d9-b8b89c4fb39d',\n INTAKE_NAV_ITEM: 'e81bf288-5686-4713-86ae-e2947b124cab',\n\n // \u2500\u2500 v0.4.0 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // IntakeLog fields \u2014 quarantine, idempotency, replay lineage\n INTAKE_LOG_IDEMPOTENCY_KEY: 'bf91e4c0-6477-41f2-a9bd-fb159d3091d4',\n INTAKE_LOG_SPAM_SCORE: '4e250af2-925f-4724-85f6-f353a6bb4cfd',\n INTAKE_LOG_QUARANTINE_REASON: '323b3c3e-a111-45d1-a211-cf70efa6cd9e',\n INTAKE_LOG_REPLAY_OF: 'b68876b1-0045-4eaa-a4e7-f0634e110ef1',\n\n // IntakeSource fields \u2014 silence detection, spam, merge policy\n INTAKE_SOURCE_EXPECTED_CADENCE: 'a97c3906-5555-4e60-a6d6-84155a4e00a6',\n INTAKE_SOURCE_HEALTH_STATUS: '2ad13b20-5bd7-450a-b52d-bcf208373cf5',\n INTAKE_SOURCE_SILENT_SINCE: 'd3395601-f29f-4e57-98eb-561ab011cc72',\n INTAKE_SOURCE_ALERT_WEBHOOK: '5a066d62-0426-41c1-b34c-1a7e881ce854',\n INTAKE_SOURCE_LAST_ALERT_AT: 'fc64a598-ccbe-44e3-bd42-716e32d626a2',\n INTAKE_SOURCE_HONEYPOT_FIELD: '5c145686-50de-496c-bb45-a30c8b39034c',\n INTAKE_SOURCE_MERGE_POLICY: '5a946f85-b1c2-493e-92c6-31c50e637a7e',\n\n // IntakeFieldRule fields\n INTAKE_FIELD_RULE_MERGE_STRATEGY: '40c6fdcb-527a-4691-8518-c40a3d3e34c6',\n\n // Logic Functions\n CONTRACT_LOGIC_FUNCTION: '2eea4152-1b08-4b8c-903c-f4c7ee7275d0',\n REPLAY_LOG_LOGIC_FUNCTION: '8ff98390-7358-4a91-84f3-435f758182a3',\n REPLAY_BULK_LOGIC_FUNCTION: 'b82ed4c4-07bc-4095-8ee5-611200626dc0',\n QUARANTINE_LIST_LOGIC_FUNCTION: '0203643c-2a97-4e7c-b4a5-b76d721cf23a',\n QUARANTINE_RELEASE_LOGIC_FUNCTION: '5a10d4fe-c807-45c6-9e61-c191d4e37b4e',\n QUARANTINE_DISCARD_LOGIC_FUNCTION: '1454a56e-6197-4dfd-9b41-5cc3b1436af7',\n CHECK_SILENCE_LOGIC_FUNCTION: 'e88cd5f2-ab96-4f89-ac55-78db66b32d66',\n\n // Application variables\n VAR_MERGE_POLICY: 'a84fe9fd-3d7c-41d9-b0e8-dfdc1a87f348',\n VAR_SPAM_FILTER_ENABLED: '570d3ba8-1904-4bad-a5ce-27e208e6414a',\n VAR_SPAM_SCORE_THRESHOLD: '4395574d-24b4-4e0e-afa3-1e90fcd4f470',\n VAR_HONEYPOT_FIELD: '2cf56c56-f5cb-47c5-aa43-84f301060a95',\n VAR_RAW_PAYLOAD_RETENTION: 'f3314ad6-05b9-4127-bb88-cea5f2018dc9',\n VAR_RAW_PAYLOAD_MAX_BYTES: 'b002ca76-df3d-47b4-8ac5-d3f562239c48',\n VAR_REPLAY_MAX_BATCH: 'cf55956f-177e-4afd-8d9f-7f42c614209c',\n} as const;\n", "/**\n * Minimal REST client for Twenty's Core and Metadata APIs.\n * Uses raw fetch so it works with dynamically created custom fields\n * that the typed SDK client doesn't know about at build time.\n */\n\nconst apiUrl = () => process.env['TWENTY_API_URL'] ?? '';\nconst token = () => process.env['TWENTY_APP_ACCESS_TOKEN'] ?? '';\n\nasync function request<T>(\n base: string,\n method: string,\n path: string,\n body?: unknown,\n): Promise<T> {\n const res = await fetch(`${base}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${token()}`,\n 'Content-Type': 'application/json',\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`${method} ${path} \u2192 ${res.status}: ${text}`);\n }\n return JSON.parse(text) as T;\n}\n\nexport const coreApi = {\n get: <T>(path: string) => request<T>(`${apiUrl()}/rest`, 'GET', path),\n post: <T>(path: string, body: unknown) => request<T>(`${apiUrl()}/rest`, 'POST', path, body),\n patch: <T>(path: string, body: unknown) => request<T>(`${apiUrl()}/rest`, 'PATCH', path, body),\n};\n\nexport const metaApi = {\n get: <T>(path: string) => request<T>(`${apiUrl()}/rest/metadata`, 'GET', path),\n post: <T>(path: string, body: unknown) => request<T>(`${apiUrl()}/rest/metadata`, 'POST', path, body),\n};\n\n/** Run a raw GraphQL query against the /graphql endpoint. */\nexport async function gql<T = unknown>(\n query: string,\n variables?: Record<string, unknown>,\n): Promise<T> {\n return request<T>(`${apiUrl()}`, 'POST', '/graphql', { query, variables });\n}\n", "/**\n * One way to run an ingestion, shared by every entry point.\n *\n * The webhook, a replay, a retry and a quarantine release all run the same\n * pipeline and all have to deal with the same terminal outcomes. Keeping that in\n * one place is what stops the endpoints drifting apart \u2014 before this, only the\n * happy path wrote an IntakeLog, so a rejected signature or a failed record\n * write left nothing behind at all and `totalFailed` never moved off zero.\n */\n\nimport { CoreApiClient } from 'twenty-client-sdk/core';\nimport { MetadataApiClient } from 'twenty-client-sdk/metadata';\nimport { createPipeline } from './pipeline-factory';\nimport type { PipelineContext } from './pipeline';\nimport type { AppError } from './result';\nimport { writeIntakeLog, bumpSourceCounters, type LogStatus } from './intake-log-writer';\n\nexport type IngestOptions = {\n sourceSlug: string;\n rawPayload: Record<string, unknown>;\n rawBody?: string | undefined;\n headers?: Record<string, string | undefined>;\n idempotencyKey?: string | undefined;\n skipDedup?: boolean;\n skipSpamFilter?: boolean;\n replayOfLogId?: string | undefined;\n};\n\nexport type IngestOutcome =\n | { ok: true; ctx: PipelineContext }\n | { ok: false; error: AppError; ctx: PipelineContext };\n\nexport async function runIngestion(options: IngestOptions): Promise<IngestOutcome> {\n const ctx: PipelineContext = {\n sourceSlug: options.sourceSlug,\n rawBody: options.rawBody,\n rawPayload: options.rawPayload,\n headers: options.headers ?? {},\n startedAt: Date.now(),\n fieldsCreated: 0,\n fieldsMatched: 0,\n warnings: [],\n events: [],\n ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),\n ...(options.skipDedup ? { skipDedup: true } : {}),\n ...(options.skipSpamFilter ? { skipSpamFilter: true } : {}),\n ...(options.replayOfLogId ? { replayOfLogId: options.replayOfLogId } : {}),\n };\n\n const pipeline = createPipeline(new CoreApiClient(), new MetadataApiClient());\n const result = await pipeline.run(ctx);\n\n if (result.ok) return { ok: true, ctx: result.value };\n\n await recordFailure(ctx, result.error);\n return { ok: false, error: result.error, ctx };\n}\n\n/**\n * Log a terminal outcome.\n *\n * Two cases deliberately write nothing. An unknown slug has no source to attach\n * a log to, and logging it anyway would let anyone fill the workspace with rows\n * by POSTing to invented URLs. A duplicate is already represented by the log of\n * the request that succeeded; recording the retry as well would double-count\n * every ingestion in the dashboard.\n */\nasync function recordFailure(ctx: PipelineContext, error: AppError): Promise<void> {\n if (error.code === 'SOURCE_NOT_FOUND') return;\n if (error.code === 'DUPLICATE_PAYLOAD') return;\n\n const details = (error.details ?? {}) as { spamScore?: number; reason?: string };\n const quarantined = error.code === 'QUARANTINED';\n const status: LogStatus = quarantined ? 'QUARANTINED' : 'FAILED';\n\n await writeIntakeLog({\n ctx,\n status,\n error: `${error.code}: ${error.message}`,\n spamScore: details.spamScore ?? null,\n quarantineReason: quarantined ? (details.reason ?? error.message) : null,\n });\n\n // A quarantine is the filter working, not the source breaking. Counting it as\n // a failure would make a well-defended form look like a broken one.\n if (ctx.source && !quarantined) await bumpSourceCounters(ctx.source.id, 'failed');\n}\n\n/**\n * The HTTP status for a terminal error.\n *\n * A quarantine answers 202: the request was well-formed and has been accepted\n * for review. Returning an error instead would tell a bot which of its attempts\n * were detected, and would show a real person a failure on a form that in fact\n * went through and is waiting for someone to look at it.\n */\nexport function statusCodeFor(code: string): number {\n switch (code) {\n case 'INVALID_SIGNATURE': return 401;\n case 'SOURCE_NOT_FOUND': return 404;\n case 'SOURCE_PAUSED': return 423;\n case 'DUPLICATE_PAYLOAD': return 200;\n case 'QUARANTINED': return 202;\n case 'NO_STORED_PAYLOAD': return 400;\n default: return 500;\n }\n}\n\n/** Pull a caller-supplied idempotency key off the header or the body. */\nexport function idempotencyKeyFrom(\n headers: Record<string, string | undefined> | undefined,\n body: Record<string, unknown>,\n): string | undefined {\n const header = headers?.['idempotency-key'] ?? headers?.['Idempotency-Key'];\n const fromBody = body['idempotencyKey'];\n const key = header ?? (typeof fromBody === 'string' ? fromBody : undefined);\n const trimmed = key?.trim();\n if (!trimmed) return undefined;\n return trimmed.slice(0, 255);\n}\n\n/** The success body shared by the webhook and every replay-shaped endpoint. */\nexport function successBody(ctx: PipelineContext, startedAt: number): Record<string, unknown> {\n return {\n success: true,\n recordId: ctx.personId ?? ctx.companyId,\n recordType: ctx.recordType,\n personId: ctx.personId,\n companyId: ctx.companyId,\n opportunityId: ctx.opportunityId,\n noteId: ctx.noteId,\n fieldsCreated: ctx.fieldsCreated,\n fieldsMatched: ctx.fieldsMatched,\n overflowCount: ctx.noteFields?.length ?? 0,\n spamScore: ctx.spam?.score ?? null,\n mergeDecisions: ctx.mergeDecisions ?? null,\n processingMs: Date.now() - startedAt,\n warnings: ctx.warnings,\n };\n}\n", "import type { PipelineStage, PipelineContext } from '../pipeline';\nimport type { Result } from '../result';\nimport { ok, err, ErrorCode } from '../result';\nimport type { MergePolicy } from '../settings';\nimport type { CoreApiClient } from 'twenty-client-sdk/core';\n\nconst BASE_FIELDS = {\n id: true,\n name: true,\n slug: true,\n targetObject: true,\n webhookSecret: true,\n status: true,\n createOpportunity: true,\n opportunityNameTemplate: true,\n} as const;\n\nconst V4_FIELDS = {\n ...BASE_FIELDS,\n honeypotField: true,\n mergePolicy: true,\n} as const;\n\ntype SourceNode = {\n id: string;\n name: string;\n slug: string;\n targetObject: string;\n webhookSecret: string | null;\n status: string;\n createOpportunity: boolean | null;\n opportunityNameTemplate: string | null;\n honeypotField?: string | null;\n mergePolicy?: string | null;\n};\n\nexport class LoadSourceStage implements PipelineStage {\n readonly name = 'load-source';\n\n constructor(private readonly client: CoreApiClient) {}\n\n async execute(ctx: PipelineContext): Promise<Result<PipelineContext>> {\n const node = await this.fetch(ctx.sourceSlug);\n\n if (!node) {\n return err(ErrorCode.SOURCE_NOT_FOUND, `No Intake source found with slug \"${ctx.sourceSlug}\"`);\n }\n\n if (node.status === 'PAUSED') {\n return err(ErrorCode.SOURCE_PAUSED, `Source \"${node.name}\" is paused`);\n }\n\n return ok({\n ...ctx,\n source: {\n id: node.id,\n name: node.name,\n slug: node.slug,\n targetObject: node.targetObject as 'PERSON' | 'COMPANY' | 'AUTO',\n webhookSecret: node.webhookSecret ?? null,\n status: node.status as 'ACTIVE' | 'PAUSED',\n createOpportunity: node.createOpportunity ?? true,\n opportunityNameTemplate: node.opportunityNameTemplate ?? '{{source}} \u2014 {{firstName}} {{lastName}}',\n honeypotField: node.honeypotField ?? null,\n mergePolicy: parseMergePolicy(node.mergePolicy),\n },\n });\n }\n\n /**\n * Ask for the 0.4.0 columns, and fall back to the original set if they are not\n * there yet. A workspace that upgraded the package but has not re-synced its\n * schema would otherwise fail the query and reject every request as an unknown\n * source \u2014 an upgrade that takes the webhook offline, which is not acceptable\n * for something sitting in front of someone's contact form.\n */\n private async fetch(slug: string): Promise<SourceNode | null> {\n const args = { filter: { slug: { eq: slug } } };\n\n try {\n const result = await this.client.query({\n intakeSources: { __args: args, edges: { node: V4_FIELDS } },\n });\n return result.intakeSources?.edges?.[0]?.node ?? null;\n } catch {\n try {\n const result = await this.client.query({\n intakeSources: { __args: args, edges: { node: BASE_FIELDS } },\n });\n return result.intakeSources?.edges?.[0]?.node ?? null;\n } catch {\n return null;\n }\n }\n }\n}\n\nfunction parseMergePolicy(raw: string | null | undefined): MergePolicy | null {\n switch ((raw ?? '').toUpperCase()) {\n case 'PRESERVE': return 'PRESERVE';\n case 'NEWEST_WINS': return 'NEWEST_WINS';\n default: return null;\n }\n}\n", "export type TwentyFieldType =\n | 'TEXT'\n | 'NUMBER'\n | 'BOOLEAN'\n | 'DATE'\n | 'DATE_TIME'\n | 'EMAILS'\n | 'PHONES'\n | 'LINKS'\n | 'FULL_NAME'\n | 'ADDRESS'\n | 'SELECT'\n | 'CURRENCY'\n | 'RATING'\n | 'RICH_TEXT'\n | 'RAW_JSON';\n\nexport type FieldAction = 'field' | 'note' | 'skip';\n\nexport type FieldMapEntry = {\n canonicalName: string;\n twentyType: TwentyFieldType;\n action: FieldAction;\n};\n\n/**\n * Normalise an incoming key to its lookup form:\n * camelCase \u2192 snake_case, hyphens/spaces \u2192 underscores, lowercase.\n * e.g. \"phoneNumber\" \u2192 \"phone_number\", \"E-Mail\" \u2192 \"e_mail\"\n */\nexport function normaliseKey(key: string): string {\n return key\n .replace(/([A-Z])/g, '_$1')\n .toLowerCase()\n .replace(/[-\\s.]/g, '_')\n .replace(/_{2,}/g, '_');\n}\n\nexport const FIELD_MAP: Record<string, FieldMapEntry> = {\n // \u2500\u2500 Phone \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n phone: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n phone_number: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n tel: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n telephone: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n mobile: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n mobile_number: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n cell: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n cell_phone: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n contact_phone: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n phone1: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n primary_phone: { canonicalName: 'phone', twentyType: 'PHONES', action: 'field' },\n\n // \u2500\u2500 Email \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n email: { canonicalName: 'email', twentyType: 'EMAILS', action: 'field' },\n email_address: { canonicalName: 'email', twentyType: 'EMAILS', action: 'field' },\n e_mail: { canonicalName: 'email', twentyType: 'EMAILS', action: 'field' },\n contact_email: { canonicalName: 'email', twentyType: 'EMAILS', action: 'field' },\n email1: { canonicalName: 'email', twentyType: 'EMAILS', action: 'field' },\n user_email: { canonicalName: 'email', twentyType: 'EMAILS', action: 'field' },\n primary_email: { canonicalName: 'email', twentyType: 'EMAILS', action: 'field' },\n\n // \u2500\u2500 First name \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n first_name: { canonicalName: 'firstName', twentyType: 'TEXT', action: 'field' },\n fname: { canonicalName: 'firstName', twentyType: 'TEXT', action: 'field' },\n given_name: { canonicalName: 'firstName', twentyType: 'TEXT', action: 'field' },\n forename: { canonicalName: 'firstName', twentyType: 'TEXT', action: 'field' },\n\n // \u2500\u2500 Last name \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n last_name: { canonicalName: 'lastName', twentyType: 'TEXT', action: 'field' },\n lname: { canonicalName: 'lastName', twentyType: 'TEXT', action: 'field' },\n surname: { canonicalName: 'lastName', twentyType: 'TEXT', action: 'field' },\n family_name: { canonicalName: 'lastName', twentyType: 'TEXT', action: 'field' },\n\n // \u2500\u2500 Full name (will be split on ingest) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n name: { canonicalName: 'fullName', twentyType: 'FULL_NAME', action: 'field' },\n full_name: { canonicalName: 'fullName', twentyType: 'FULL_NAME', action: 'field' },\n contact_name: { canonicalName: 'fullName', twentyType: 'FULL_NAME', action: 'field' },\n your_name: { canonicalName: 'fullName', twentyType: 'FULL_NAME', action: 'field' },\n\n // \u2500\u2500 Company \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n company: { canonicalName: 'companyName', twentyType: 'TEXT', action: 'field' },\n company_name: { canonicalName: 'companyName', twentyType: 'TEXT', action: 'field' },\n business: { canonicalName: 'companyName', twentyType: 'TEXT', action: 'field' },\n business_name: { canonicalName: 'companyName', twentyType: 'TEXT', action: 'field' },\n organization: { canonicalName: 'companyName', twentyType: 'TEXT', action: 'field' },\n organisation: { canonicalName: 'companyName', twentyType: 'TEXT', action: 'field' },\n employer: { canonicalName: 'companyName', twentyType: 'TEXT', action: 'field' },\n firm: { canonicalName: 'companyName', twentyType: 'TEXT', action: 'field' },\n\n // \u2500\u2500 Website / Domain \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n website: { canonicalName: 'domainName', twentyType: 'LINKS', action: 'field' },\n website_url: { canonicalName: 'domainName', twentyType: 'LINKS', action: 'field' },\n url: { canonicalName: 'domainName', twentyType: 'LINKS', action: 'field' },\n domain: { canonicalName: 'domainName', twentyType: 'LINKS', action: 'field' },\n homepage: { canonicalName: 'domainName', twentyType: 'LINKS', action: 'field' },\n web: { canonicalName: 'domainName', twentyType: 'LINKS', action: 'field' },\n site: { canonicalName: 'domainName', twentyType: 'LINKS', action: 'field' },\n site_url: { canonicalName: 'domainName', twentyType: 'LINKS', action: 'field' },\n\n // \u2500\u2500 Address \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n street: { canonicalName: 'addressStreet1', twentyType: 'TEXT', action: 'field' },\n street_address: { canonicalName: 'addressStreet1', twentyType: 'TEXT', action: 'field' },\n address_line_1: { canonicalName: 'addressStreet1', twentyType: 'TEXT', action: 'field' },\n address1: { canonicalName: 'addressStreet1', twentyType: 'TEXT', action: 'field' },\n address_line_2: { canonicalName: 'addressStreet2', twentyType: 'TEXT', action: 'field' },\n address2: { canonicalName: 'addressStreet2', twentyType: 'TEXT', action: 'field' },\n city: { canonicalName: 'addressCity', twentyType: 'TEXT', action: 'field' },\n address_city: { canonicalName: 'addressCity', twentyType: 'TEXT', action: 'field' },\n town: { canonicalName: 'addressCity', twentyType: 'TEXT', action: 'field' },\n state: { canonicalName: 'addressState', twentyType: 'TEXT', action: 'field' },\n province: { canonicalName: 'addressState', twentyType: 'TEXT', action: 'field' },\n region: { canonicalName: 'addressState', twentyType: 'TEXT', action: 'field' },\n address_state: { canonicalName: 'addressState', twentyType: 'TEXT', action: 'field' },\n zip: { canonicalName: 'addressPostcode', twentyType: 'TEXT', action: 'field' },\n zip_code: { canonicalName: 'addressPostcode', twentyType: 'TEXT', action: 'field' },\n postal_code: { canonicalName: 'addressPostcode', twentyType: 'TEXT', action: 'field' },\n postcode: { canonicalName: 'addressPostcode', twentyType: 'TEXT', action: 'field' },\n country: { canonicalName: 'addressCountry', twentyType: 'TEXT', action: 'field' },\n address_country: { canonicalName: 'addressCountry', twentyType: 'TEXT', action: 'field' },\n\n // \u2500\u2500 Social \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n linkedin: { canonicalName: 'linkedInLink', twentyType: 'LINKS', action: 'field' },\n linkedin_url: { canonicalName: 'linkedInLink', twentyType: 'LINKS', action: 'field' },\n linkedin_profile: { canonicalName: 'linkedInLink', twentyType: 'LINKS', action: 'field' },\n twitter: { canonicalName: 'xLink', twentyType: 'LINKS', action: 'field' },\n twitter_url: { canonicalName: 'xLink', twentyType: 'LINKS', action: 'field' },\n x_profile: { canonicalName: 'xLink', twentyType: 'LINKS', action: 'field' },\n\n // \u2500\u2500 Pipeline-specific (Google / business intel) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n google_places_url: { canonicalName: 'extGooglePlacesUrl', twentyType: 'LINKS', action: 'field' },\n places_url: { canonicalName: 'extGooglePlacesUrl', twentyType: 'LINKS', action: 'field' },\n google_maps_url: { canonicalName: 'extGooglePlacesUrl', twentyType: 'LINKS', action: 'field' },\n maps_url: { canonicalName: 'extGooglePlacesUrl', twentyType: 'LINKS', action: 'field' },\n rating: { canonicalName: 'extGoogleRating', twentyType: 'NUMBER', action: 'field' },\n google_rating: { canonicalName: 'extGoogleRating', twentyType: 'NUMBER', action: 'field' },\n stars: { canonicalName: 'extGoogleRating', twentyType: 'NUMBER', action: 'field' },\n review_count: { canonicalName: 'extReviewCount', twentyType: 'NUMBER', action: 'field' },\n reviews_count: { canonicalName: 'extReviewCount', twentyType: 'NUMBER', action: 'field' },\n total_reviews: { canonicalName: 'extReviewCount', twentyType: 'NUMBER', action: 'field' },\n num_reviews: { canonicalName: 'extReviewCount', twentyType: 'NUMBER', action: 'field' },\n marketing_score: { canonicalName: 'extMarketingScore', twentyType: 'NUMBER', action: 'field' },\n lead_score: { canonicalName: 'extLeadScore', twentyType: 'NUMBER', action: 'field' },\n analyzed_url: { canonicalName: 'extAnalyzedUrl', twentyType: 'LINKS', action: 'field' },\n\n // \u2500\u2500 UTM \u2014 always captured in note \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n utm_source: { canonicalName: 'utm_source', twentyType: 'TEXT', action: 'note' },\n utm_medium: { canonicalName: 'utm_medium', twentyType: 'TEXT', action: 'note' },\n utm_campaign: { canonicalName: 'utm_campaign', twentyType: 'TEXT', action: 'note' },\n utm_content: { canonicalName: 'utm_content', twentyType: 'TEXT', action: 'note' },\n utm_term: { canonicalName: 'utm_term', twentyType: 'TEXT', action: 'note' },\n ref: { canonicalName: 'ref', twentyType: 'TEXT', action: 'note' },\n referrer: { canonicalName: 'referrer', twentyType: 'TEXT', action: 'note' },\n source_page: { canonicalName: 'source_page', twentyType: 'TEXT', action: 'note' },\n landing_page: { canonicalName: 'landing_page', twentyType: 'TEXT', action: 'note' },\n\n // \u2500\u2500 Known note fields \u2014 always prose \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n message: { canonicalName: 'message', twentyType: 'RICH_TEXT', action: 'note' },\n description: { canonicalName: 'description', twentyType: 'RICH_TEXT', action: 'note' },\n analysis: { canonicalName: 'analysis', twentyType: 'RICH_TEXT', action: 'note' },\n notes: { canonicalName: 'notes', twentyType: 'RICH_TEXT', action: 'note' },\n comments: { canonicalName: 'comments', twentyType: 'RICH_TEXT', action: 'note' },\n summary: { canonicalName: 'summary', twentyType: 'RICH_TEXT', action: 'note' },\n inquiry: { canonicalName: 'inquiry', twentyType: 'RICH_TEXT', action: 'note' },\n body: { canonicalName: 'body', twentyType: 'RICH_TEXT', action: 'note' },\n details: { canonicalName: 'details', twentyType: 'RICH_TEXT', action: 'note' },\n content: { canonicalName: 'content', twentyType: 'RICH_TEXT', action: 'note' },\n text: { canonicalName: 'text', twentyType: 'RICH_TEXT', action: 'note' },\n feedback: { canonicalName: 'feedback', twentyType: 'RICH_TEXT', action: 'note' },\n about: { canonicalName: 'about', twentyType: 'RICH_TEXT', action: 'note' },\n additional_info: { canonicalName: 'additional_info', twentyType: 'RICH_TEXT', action: 'note' },\n\n // \u2500\u2500 System / internal \u2014 skip entirely \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n _id: { canonicalName: '_id', twentyType: 'TEXT', action: 'skip' },\n __v: { canonicalName: '__v', twentyType: 'TEXT', action: 'skip' },\n created_at: { canonicalName: 'createdAt', twentyType: 'DATE_TIME', action: 'skip' },\n updated_at: { canonicalName: 'updatedAt', twentyType: 'DATE_TIME', action: 'skip' },\n form_id: { canonicalName: 'form_id', twentyType: 'TEXT', action: 'skip' },\n submission_id: { canonicalName: 'submission_id', twentyType: 'TEXT', action: 'skip' },\n token: { canonicalName: 'token', twentyType: 'TEXT', action: 'skip' },\n password: { canonicalName: 'password', twentyType: 'TEXT', action: 'skip' },\n secret: { canonicalName: 'secret', twentyType: 'TEXT', action: 'skip' },\n} as const;\n", "import type { PipelineStage, PipelineContext } from '../pipeline';\nimport type { NormalizedField, TargetObject } from '../normalizer';\nimport type { Result } from '../result';\nimport { ok } from '../result';\nimport { metaApi } from '../rest-client';\nimport { FIELD_MAP } from '../../constants/field-map';\n\n/**\n * Names the app already knows how to place on a person or company.\n *\n * Two groups, and both matter. The literals are the Twenty field names written\n * directly. The FIELD_MAP values are the *pre-composite* canonical names \u2014\n * `email`, `phone`, `addressCity` \u2014 which never appear on the object because\n * assembleComposites folds them into `emails`, `phones` and `address` later.\n * Deriving them from the map rather than restating them is what stops a new\n * built-in mapping from being mistaken for an unknown field and diverted into\n * the note, which would quietly create people with no email address.\n */\nconst STANDARD_FIELDS = new Set<string>([\n 'name', 'emails', 'phones', 'domainName', 'address',\n 'linkedInLink', 'xLink', 'companyName', 'firstName', 'lastName', 'fullName',\n 'jobTitle', 'city', 'employees', 'annualRecurringRevenue', 'idealCustomerProfile',\n ...Object.values(FIELD_MAP).map((e) => e.canonicalName),\n]);\n\n/** Standard Opportunity fields the upsert stage writes directly. */\nconst STANDARD_OPPORTUNITY_FIELDS = new Set(['name', 'stage', 'amount', 'closeDate']);\n\nconst EXT_RE = /^ext[A-Z]/;\n\nexport class SyncSchemaStage implements PipelineStage {\n readonly name = 'sync-schema';\n\n private objectCache = new Map<string, { id: string; fields: Set<string>; at: number }>();\n private readonly TTL = 5 * 60 * 1000;\n\n async execute(ctx: PipelineContext): Promise<Result<PipelineContext>> {\n if (!ctx.crmFields || ctx.crmFields.length === 0) return ok(ctx);\n\n const primary = this.primaryTarget(ctx);\n const creationEnabled = process.env['INTAKE_FIELD_CREATION_ENABLED'] !== 'false';\n if (!creationEnabled) {\n ctx.warnings.push('Field creation disabled by INTAKE_FIELD_CREATION_ENABLED setting \u2014 ext fields skipped');\n }\n\n // Resolve every field's destination up front, so the rest of the pipeline\n // never has to re-derive it. `auto` collapses onto the primary record.\n const resolved: NormalizedField[] = ctx.crmFields.map((f) => ({\n ...f,\n targetObject: this.resolveTarget(f.targetObject, primary, ctx),\n }));\n\n let fieldsCreated = 0;\n let fieldsMatched = 0;\n const demoted: NormalizedField[] = [];\n\n for (const objectName of this.targetObjects(resolved)) {\n const fields = resolved.filter((f) => f.targetObject === objectName);\n if (fields.length === 0) continue;\n\n const standardForObject = objectName === 'opportunity'\n ? STANDARD_OPPORTUNITY_FIELDS\n : STANDARD_FIELDS;\n\n const meta = await this.getObjectMeta(objectName);\n if (!meta) {\n for (const f of fields) {\n if (standardForObject.has(f.canonicalName)) { f.writable = true; fieldsMatched++; continue; }\n ctx.warnings.push(`Could not read schema for ${objectName} \u2014 \"${f.canonicalName}\" goes to note`);\n demoted.push(f);\n }\n continue;\n }\n\n const maxExt = parseInt(process.env['INTAKE_MAX_EXT_FIELDS'] ?? '50', 10);\n let extCount = [...meta.fields].filter((f) => EXT_RE.test(f)).length;\n\n for (const field of fields) {\n const name = field.canonicalName;\n\n // Standard fields are written by the upsert stage directly.\n if (standardForObject.has(name)) { field.writable = true; fieldsMatched++; continue; }\n\n // Already on the object \u2014 whether an ext field from an earlier run or a\n // field created by hand that a rule now points at.\n if (meta.fields.has(name)) { field.writable = true; fieldsMatched++; continue; }\n\n // Not present. Only ext-prefixed names are auto-created: minting a field\n // for any name a rule happens to contain would turn a typo into a\n // permanent schema change.\n if (!EXT_RE.test(name)) {\n ctx.warnings.push(\n `Field \"${name}\" does not exist on ${objectName} and is not ext-prefixed \u2014 value routed to note. ` +\n `Create the field in Twenty first, or rename the rule's canonical name to ext${name.charAt(0).toUpperCase()}${name.slice(1)}.`,\n );\n demoted.push(field);\n continue;\n }\n\n if (!creationEnabled) { demoted.push(field); continue; }\n\n if (extCount >= maxExt) {\n ctx.warnings.push(`ext field limit (${maxExt}) reached for ${objectName} \u2014 \"${name}\" goes to note`);\n demoted.push(field);\n continue;\n }\n\n const created = await this.createField(meta.id, name, field.twentyType);\n if (created) {\n meta.fields.add(name);\n this.objectCache.get(objectName)?.fields.add(name);\n extCount++;\n fieldsCreated++;\n field.writable = true;\n } else {\n ctx.warnings.push(`Could not create field \"${name}\" on ${objectName} \u2014 will go to note`);\n demoted.push(field);\n }\n }\n }\n\n // A field we cannot write is not \"matched\" \u2014 it is note overflow. Moving it\n // here is what keeps the value from disappearing between the two stages.\n const demotedSet = new Set(demoted);\n return ok({\n ...ctx,\n crmFields: resolved.filter((f) => !demotedSet.has(f)),\n noteFields: [...(ctx.noteFields ?? []), ...demoted],\n fieldsCreated,\n fieldsMatched,\n });\n }\n\n /** Where an `auto` field lands: the person, or the company for company-only payloads. */\n private primaryTarget(ctx: PipelineContext): 'person' | 'company' {\n const t = ctx.source?.targetObject ?? 'AUTO';\n if (t === 'PERSON') return 'person';\n if (t === 'COMPANY') return 'company';\n\n // For structured payloads (pipeline app), extras are company-level data\n if (ctx.structuredCompany) return 'company';\n\n const hasPersonData = ctx.crmFields?.some((f) =>\n ['firstName', 'lastName', 'email', 'phone', 'fullName', 'emails', 'phones', 'name'].includes(f.canonicalName));\n const hasCompanyData = ctx.crmFields?.some((f) =>\n ['companyName', 'domainName'].includes(f.canonicalName));\n\n if (hasPersonData) return 'person';\n if (hasCompanyData) return 'company';\n return 'person';\n }\n\n private resolveTarget(\n target: TargetObject,\n primary: 'person' | 'company',\n ctx: PipelineContext,\n ): TargetObject {\n if (target === 'auto') return primary;\n // A field routed to the deal has nowhere to go when the source does not open\n // one, so it falls back to the primary record instead of being lost.\n if (target === 'opportunity' && !ctx.source?.createOpportunity) {\n ctx.warnings.push('Field routed to opportunity but this source does not create opportunities \u2014 written to the primary record instead');\n return primary;\n }\n return target;\n }\n\n private targetObjects(fields: NormalizedField[]): string[] {\n const seen = new Set<string>();\n for (const f of fields) if (f.targetObject !== 'auto') seen.add(f.targetObject);\n return [...seen];\n }\n\n private async getObjectMeta(objectName: string): Promise<{ id: string; fields: Set<string> } | null> {\n const cached = this.objectCache.get(objectName);\n if (cached && Date.now() - cached.at < this.TTL) return cached;\n\n try {\n // Metadata API returns all objects \u2014 filter client-side by nameSingular\n const res = await metaApi.get<Array<{ id: string; nameSingular: string; fields: Array<{ name: string }> }>>('/objects');\n const objects = Array.isArray(res) ? res : (res as { data: typeof res }).data ?? [];\n const node = (objects as Array<{ id: string; nameSingular: string; fields: Array<{ name: string }> }>)\n .find(o => o.nameSingular === objectName);\n if (!node) return null;\n const fields = new Set<string>((node.fields ?? []).map((f: { name: string }) => f.name));\n const entry = { id: node.id, fields, at: Date.now() };\n this.objectCache.set(objectName, entry);\n return entry;\n } catch {\n return null;\n }\n }\n\n private async createField(objectMetadataId: string, name: string, type: string): Promise<boolean> {\n try {\n await metaApi.post('/fields', {\n objectMetadataId,\n name,\n label: toLabel(name),\n type,\n description: 'Auto-created by Intake',\n icon: 'IconPlug',\n });\n return true;\n } catch {\n return false;\n }\n }\n}\n\nfunction toLabel(name: string): string {\n return name.replace(/^ext_/, '').replace(/_/g, ' ')\n .replace(/([A-Z])/g, ' $1').split(' ')\n .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())\n .join(' ').trim();\n}\n", "/**\n * Re-running a payload that was already received.\n *\n * The problem this solves is a mapping added too late. A field arrives for weeks\n * with no rule to place it, goes into the overflow note, and the day someone\n * writes the rule it only applies to what comes next \u2014 everything already in the\n * CRM has to be repaired by hand from note text, if it can be repaired at all.\n * Replay runs the stored payloads back through the rules as they exist now, so\n * the fix reaches the records that needed it.\n *\n * Distinct from retry, which exists for failures and refuses a log that already\n * succeeded. Replay is for logs that succeeded: the run worked, the mapping was\n * simply not there yet.\n */\n\nimport { coreApi } from './rest-client';\nimport { runIngestion } from './ingest';\nimport { replayMaxBatch } from './settings';\n\nexport type StoredLog = {\n id: string;\n status: string;\n rawPayload: string | null;\n intakeSourceId: string | null;\n processedAt: string | null;\n};\n\nexport type ReplayResult = {\n logId: string;\n ok: boolean;\n reason?: string;\n recordId?: string | undefined;\n personId?: string | undefined;\n companyId?: string | undefined;\n opportunityId?: string | undefined;\n fieldsCreated?: number;\n warnings?: string[];\n};\n\nexport async function loadLog(logId: string): Promise<StoredLog | null> {\n try {\n const res = await coreApi.get<{ data: { intakeLog: StoredLog } }>(`/intakeLogs/${logId}`);\n return res.data?.intakeLog ?? null;\n } catch {\n return null;\n }\n}\n\nexport async function slugForSource(sourceId: string | null): Promise<string | null> {\n if (!sourceId) return null;\n try {\n const res = await coreApi.get<{ data: { intakeSource: { slug: string } } }>(`/intakeSources/${sourceId}`);\n return res.data?.intakeSource?.slug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Replay one stored log.\n *\n * Deduplication is bypassed, because the payload is by definition one we have\n * seen \u2014 that is the whole point. Spam scoring is not bypassed: a payload that\n * would be quarantined today should still be quarantined when it is replayed.\n */\nexport async function replayLog(log: StoredLog): Promise<ReplayResult> {\n if (!log.rawPayload) {\n return {\n logId: log.id,\n ok: false,\n reason: 'No stored payload \u2014 retention was off, or the payload exceeded the size cap when it arrived',\n };\n }\n\n let payload: Record<string, unknown>;\n try {\n const parsed: unknown = JSON.parse(log.rawPayload);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return { logId: log.id, ok: false, reason: 'Stored payload is not a JSON object' };\n }\n payload = parsed as Record<string, unknown>;\n } catch {\n return { logId: log.id, ok: false, reason: 'Stored payload is not valid JSON' };\n }\n\n const slug = await slugForSource(log.intakeSourceId);\n if (!slug) {\n return { logId: log.id, ok: false, reason: 'The source for this log no longer exists' };\n }\n\n const outcome = await runIngestion({\n sourceSlug: slug,\n rawPayload: payload,\n // The signature was checked when the payload first arrived; there is no body\n // to re-verify against and the request is now coming from an authenticated\n // operator rather than the network.\n skipDedup: true,\n replayOfLogId: log.id,\n });\n\n if (!outcome.ok) {\n return { logId: log.id, ok: false, reason: `${outcome.error.code}: ${outcome.error.message}` };\n }\n\n return {\n logId: log.id,\n ok: true,\n recordId: outcome.ctx.personId ?? outcome.ctx.companyId,\n personId: outcome.ctx.personId,\n companyId: outcome.ctx.companyId,\n opportunityId: outcome.ctx.opportunityId,\n fieldsCreated: outcome.ctx.fieldsCreated,\n warnings: outcome.ctx.warnings,\n };\n}\n\nexport type BulkQuery = {\n sourceSlug?: string | undefined;\n status?: string | undefined;\n since?: string | undefined;\n until?: string | undefined;\n limit?: number | undefined;\n};\n\n/**\n * Select logs for a bulk replay.\n *\n * Capped by INTAKE_REPLAY_MAX_BATCH, and the cap is not negotiable from the\n * request: a replay writes to the CRM for every log it touches, and an\n * unbounded one aimed at a year of history would rewrite a workspace in a single\n * call. Callers wanting more run several batches and can see what each one did.\n */\nexport async function selectLogs(query: BulkQuery): Promise<StoredLog[]> {\n const max = replayMaxBatch();\n const limit = Math.min(query.limit ?? max, max);\n\n const filters: string[] = [];\n if (query.status) filters.push(`status[eq]:${encodeURIComponent(query.status)}`);\n if (query.since) filters.push(`processedAt[gte]:${encodeURIComponent(query.since)}`);\n if (query.until) filters.push(`processedAt[lte]:${encodeURIComponent(query.until)}`);\n\n if (query.sourceSlug) {\n const sourceId = await idForSlug(query.sourceSlug);\n if (!sourceId) return [];\n filters.push(`intakeSourceId[eq]:${sourceId}`);\n }\n\n const qs = [\n filters.length > 0 ? `filter=${filters.join(',')}` : '',\n `orderBy=processedAt[AscNullsLast]`,\n `limit=${limit}`,\n ].filter(Boolean).join('&');\n\n try {\n const res = await coreApi.get<{ data: { intakeLogs: { edges: Array<{ node: StoredLog }> } } }>(\n `/intakeLogs?${qs}`,\n );\n return (res.data?.intakeLogs?.edges ?? []).map((e) => e.node);\n } catch {\n return [];\n }\n}\n\nasync function idForSlug(slug: string): Promise<string | null> {\n try {\n const res = await coreApi.get<{ data: { intakeSources: { edges: Array<{ node: { id: string } }> } } }>(\n `/intakeSources?filter=slug[eq]:${encodeURIComponent(slug)}&first=1`,\n );\n return res.data?.intakeSources?.edges?.[0]?.node?.id ?? null;\n } catch {\n return null;\n }\n}\n", "import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';\nimport { IDS } from '../constants/universal-identifiers';\nimport { coreApi } from '../lib/rest-client';\nimport { loadLog } from '../lib/replay';\n\n/**\n * Mark a held payload as junk.\n *\n * The log is marked DISCARDED, not deleted. Keeping it is what lets the filter\n * be judged later \u2014 a run of discards is evidence the threshold is right, and a\n * discard sitting next to a release is evidence it is not. Deleting the row\n * would throw away the only record of how well the thing is working.\n *\n * `purgePayload` drops the stored body while keeping the row, for anyone who\n * would rather not retain the contents of obvious spam.\n */\nconst handler = async (params: RoutePayload) => {\n const logId = params.pathParameters?.['logId'];\n if (!logId) return { statusCode: 400, body: { error: 'Missing logId in URL' } };\n\n const body = (params.body ?? {}) as Record<string, unknown>;\n const purgePayload = body['purgePayload'] === true;\n\n const log = await loadLog(logId);\n if (!log) return { statusCode: 404, body: { error: `IntakeLog ${logId} not found` } };\n\n if (log.status !== 'QUARANTINED') {\n return {\n statusCode: 400,\n body: { error: `Log ${logId} is ${log.status}, not QUARANTINED \u2014 nothing to discard`, status: log.status },\n };\n }\n\n try {\n await coreApi.patch(`/intakeLogs/${logId}`, {\n status: 'DISCARDED',\n ...(purgePayload ? { rawPayload: null } : {}),\n });\n } catch (e) {\n return { statusCode: 500, body: { error: `Could not discard log: ${String(e)}` } };\n }\n\n return {\n statusCode: 200,\n body: {\n success: true,\n logId,\n status: 'DISCARDED',\n payloadPurged: purgePayload,\n },\n };\n};\n\nexport default defineLogicFunction({\n universalIdentifier: IDS.QUARANTINE_DISCARD_LOGIC_FUNCTION,\n name: 'intake-quarantine-discard',\n description: 'Mark a quarantined payload as junk, optionally purging the stored body.',\n timeoutSeconds: 15,\n handler,\n httpRouteTriggerSettings: {\n path: '/intake/quarantine/:logId/discard',\n httpMethod: 'POST',\n isAuthRequired: true,\n },\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|