twenty-app-intake 0.2.0 → 0.3.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.
@@ -0,0 +1,124 @@
1
+ import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
2
+ import { CoreApiClient } from 'twenty-client-sdk/core';
3
+ import { MetadataApiClient } from 'twenty-client-sdk/metadata';
4
+ import { IDS } from '../constants/universal-identifiers';
5
+ import { createPipeline } from '../lib/pipeline-factory';
6
+ import type { PipelineContext } from '../lib/pipeline';
7
+ import { coreApi } from '../lib/rest-client';
8
+
9
+ const handler = async (params: RoutePayload) => {
10
+ const logId = params.pathParameters?.['logId'];
11
+ if (!logId) {
12
+ return { statusCode: 400, body: { error: 'Missing logId in URL' } };
13
+ }
14
+
15
+ // Fetch the existing log
16
+ const logResult = await coreApi.get<{
17
+ data: {
18
+ intakeLog: {
19
+ id: string;
20
+ status: string;
21
+ rawPayload: string | null;
22
+ intakeSourceId: string | null;
23
+ error: string | null;
24
+ };
25
+ };
26
+ }>(`/intakeLogs/${logId}`).catch(() => null);
27
+
28
+ const log = logResult?.data?.intakeLog;
29
+
30
+ if (!log) {
31
+ return { statusCode: 404, body: { error: `IntakeLog ${logId} not found` } };
32
+ }
33
+
34
+ if (log.status === 'SUCCESS') {
35
+ return {
36
+ statusCode: 400,
37
+ body: { error: 'This ingestion already succeeded — no retry needed', status: log.status },
38
+ };
39
+ }
40
+
41
+ if (!log.rawPayload) {
42
+ return {
43
+ statusCode: 400,
44
+ body: { error: 'No raw payload stored on this log — retry requires rawPayload field (logs created in v0.2.0+)' },
45
+ };
46
+ }
47
+
48
+ let rawPayload: Record<string, unknown>;
49
+ try {
50
+ rawPayload = JSON.parse(log.rawPayload);
51
+ } catch {
52
+ return { statusCode: 400, body: { error: 'rawPayload is not valid JSON' } };
53
+ }
54
+
55
+ // We need a sourceSlug to run the pipeline — get it from the source record
56
+ let sourceSlug = 'unknown';
57
+ if (log.intakeSourceId) {
58
+ const srcResult = await coreApi.get<{ data: { intakeSource: { slug: string } } }>(
59
+ `/intakeSources/${log.intakeSourceId}`,
60
+ ).catch(() => null);
61
+ sourceSlug = srcResult?.data?.intakeSource?.slug ?? 'unknown';
62
+ }
63
+
64
+ const startedAt = Date.now();
65
+ const coreClient = new CoreApiClient();
66
+ const metadataClient = new MetadataApiClient();
67
+ const pipeline = createPipeline(coreClient, metadataClient);
68
+
69
+ const ctx: PipelineContext = {
70
+ sourceSlug,
71
+ rawBody: undefined, // HMAC skipped on retry — source already verified originally
72
+ rawPayload,
73
+ headers: {},
74
+ startedAt,
75
+ skipDedup: true, // Critical — bypass idempotency so the payload re-runs
76
+ fieldsCreated: 0,
77
+ fieldsMatched: 0,
78
+ warnings: [],
79
+ events: [],
80
+ };
81
+
82
+ const result = await pipeline.run(ctx);
83
+
84
+ if (!result.ok) {
85
+ return {
86
+ statusCode: 500,
87
+ body: {
88
+ error: `Retry failed: ${result.error.message}`,
89
+ code: result.error.code,
90
+ originalLogId: logId,
91
+ },
92
+ };
93
+ }
94
+
95
+ const { value: finalCtx } = result;
96
+
97
+ return {
98
+ statusCode: 200,
99
+ body: {
100
+ success: true,
101
+ originalLogId: logId,
102
+ recordId: finalCtx.personId ?? finalCtx.companyId,
103
+ recordType: finalCtx.recordType,
104
+ personId: finalCtx.personId,
105
+ companyId: finalCtx.companyId,
106
+ fieldsCreated: finalCtx.fieldsCreated,
107
+ processingMs: Date.now() - startedAt,
108
+ warnings: finalCtx.warnings,
109
+ },
110
+ };
111
+ };
112
+
113
+ export default defineLogicFunction({
114
+ universalIdentifier: IDS.RETRY_LOGIC_FUNCTION,
115
+ name: 'intake-retry',
116
+ description: 'Retry a failed ingestion using the stored raw payload from an IntakeLog record.',
117
+ timeoutSeconds: 30,
118
+ handler,
119
+ httpRouteTriggerSettings: {
120
+ path: '/intake/logs/:logId/retry',
121
+ httpMethod: 'POST',
122
+ isAuthRequired: true,
123
+ },
124
+ });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["twenty-sdk-define-stub:__twenty-sdk-define-stub__", "../../../../src/lib/flattener.ts", "../../../../src/constants/field-map.ts", "../../../../src/lib/type-detector.ts", "../../../../src/lib/classifier.ts", "../../../../src/lib/normalizer.ts", "../../../../src/lib/idempotency.ts", "../../../../src/lib/note-formatter.ts", "../../../../src/lib/rest-client.ts", "../../../../src/logic-functions/test-ingest.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 defineLogicFunction = __defineFactoryStub;\nexport const defineNavigationMenuItem = __defineFactoryStub;\nexport const defineObject = __defineFactoryStub;\nexport const definePageLayout = __defineFactoryStub;\nexport const definePageLayoutTab = __defineFactoryStub;\nexport const definePostInstallLogicFunction = __defineFactoryStub;\nexport const definePreInstallLogicFunction = __defineFactoryStub;\nexport const defineRole = __defineFactoryStub;\nexport const defineSkill = __defineFactoryStub;\nexport const defineView = __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 ObjectRecordGroupByDateGranularity = __anyStub;\nexport const OnDeleteAction = __anyStub;\nexport const PageLayoutTabLayoutMode = __anyStub;\nexport const PermissionFlag = __anyStub;\nexport const RelationType = __anyStub;\nexport const STANDARD_OBJECT = __anyStub;\nexport const STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = __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 generateDefaultFieldUniversalIdentifier = __anyStub;\nexport const validateFields = __anyStub;\n", "type FlatRecord = Record<string, unknown>;\n\nconst MAX_DEPTH = 8;\n\n/**\n * Recursively flatten a nested object into a single-level record.\n * Arrays are kept as-is (classified as notes downstream).\n * Circular references are detected and dropped.\n *\n * e.g. { user: { phone: \"x\" } } \u2192 { userPhone: \"x\" }\n */\nexport function flatten(\n input: unknown,\n prefix = '',\n depth = 0,\n seen = new WeakSet<object>(),\n): FlatRecord {\n if (depth > MAX_DEPTH) return {};\n if (input === null || input === undefined) return {};\n if (typeof input !== 'object') {\n return prefix ? { [prefix]: input } : {};\n }\n if (Array.isArray(input)) {\n return prefix ? { [prefix]: input } : {};\n }\n\n const obj = input as Record<string, unknown>;\n\n if (seen.has(obj)) return {};\n seen.add(obj);\n\n const result: FlatRecord = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const newKey = prefix ? `${prefix}${toPascalSegment(key)}` : toCamelSegment(key);\n\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value)\n ) {\n const nested = flatten(value, newKey, depth + 1, seen);\n Object.assign(result, nested);\n } else {\n result[newKey] = value;\n }\n }\n\n return result;\n}\n\n/** Convert a snake_case or kebab-case segment to camelCase. */\nfunction toCamelSegment(s: string): string {\n return s.replace(/[-_]([a-zA-Z0-9])/g, (_, c: string) => c.toUpperCase());\n}\n\n/** Convert a snake_case or kebab-case segment to PascalCase for joining. */\nfunction toPascalSegment(s: string): string {\n const camel = toCamelSegment(s);\n return camel.charAt(0).toUpperCase() + camel.slice(1);\n}\n\n/**\n * Detect whether a top-level payload is \"structured\" (pipeline-app style\n * with nested `company` / `person` object keys) or \"flat\" (contact-form style).\n */\nexport type PayloadStructure =\n | { type: 'structured'; company: FlatRecord | null; person: FlatRecord | null; extra: FlatRecord }\n | { type: 'flat'; data: FlatRecord };\n\nexport function detectStructure(raw: unknown): PayloadStructure {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {\n return { type: 'flat', data: {} };\n }\n\n const obj = raw as Record<string, unknown>;\n const hasObjectCompany =\n 'company' in obj && typeof obj['company'] === 'object' && obj['company'] !== null && !Array.isArray(obj['company']);\n const hasObjectPerson =\n 'person' in obj && typeof obj['person'] === 'object' && obj['person'] !== null && !Array.isArray(obj['person']);\n\n if (!hasObjectCompany && !hasObjectPerson) {\n return { type: 'flat', data: flatten(obj) };\n }\n\n // Keep company and person in their original nested form \u2014 they are already\n // in Twenty's native composite format (domainName, address, emails, phones, etc.)\n const company = hasObjectCompany ? (obj['company'] as FlatRecord) : null;\n const person = hasObjectPerson ? (obj['person'] as FlatRecord) : null;\n\n const extra: FlatRecord = {};\n for (const [k, v] of Object.entries(obj)) {\n if (k === 'company' || k === 'person') continue;\n const nested = flatten({ [k]: v });\n Object.assign(extra, nested);\n }\n\n return { type: 'structured', company, person, extra };\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 { TwentyFieldType } from '../constants/field-map';\n\nconst URL_RE = /^https?:\\/\\/[^\\s/$.?#].[^\\s]*$/i;\nconst EMAIL_RE = /^[^@\\s]+@[^@\\s]+\\.[^@\\s]{2,}$/;\nconst PHONE_RE = /^[+]?[(]?[0-9]{1,4}[)]?[-\\s.]?[(]?[0-9]{1,4}[)]?[-\\s.]?[0-9]{3,9}([-\\s.][0-9]{1,9})?$/;\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})?)?)?$/;\n\nexport function detectType(value: unknown): TwentyFieldType {\n if (value === null || value === undefined) return 'TEXT';\n if (typeof value === 'boolean') return 'BOOLEAN';\n if (typeof value === 'number') return 'NUMBER';\n\n if (typeof value === 'string') {\n const s = value.trim();\n if (!s) return 'TEXT';\n if (URL_RE.test(s)) return 'LINKS';\n if (EMAIL_RE.test(s)) return 'EMAILS';\n if (PHONE_RE.test(s)) return 'PHONES';\n if (DATE_RE.test(s)) return 'DATE_TIME';\n if (!isNaN(Number(s)) && s !== '') return 'NUMBER';\n return 'TEXT';\n }\n\n if (Array.isArray(value)) return 'RAW_JSON';\n if (typeof value === 'object') return 'RAW_JSON';\n\n return 'TEXT';\n}\n", "const PROSE_KEYS = new Set([\n 'message', 'description', 'analysis', 'notes', 'comments',\n 'summary', 'inquiry', 'body', 'details', 'content', 'text',\n 'feedback', 'about', 'additional_info', 'info', 'context',\n 'requirements', 'goals', 'challenges', 'question', 'request',\n]);\n\nconst NOTE_KEY_PREFIXES = ['utm_', 'ga_', 'fb_', 'gclid', 'msclkid'];\n\nconst SENTENCE_RE = /[.!?]\\s+[A-Z]/g;\nconst LINE_BREAK_RE = /\\n|\\r/;\n\nexport type Classification = 'field' | 'note' | 'skip';\n\n/**\n * Decide whether a key+value pair belongs in a CRM field or the overflow note.\n *\n * Rules (in priority order):\n * 1. Arrays / objects \u2192 note (can't be a single field)\n * 2. Known note key names \u2192 note\n * 3. UTM / tracking prefix \u2192 note\n * 4. Contains line breaks \u2192 note\n * 5. Looks like prose (2+ sentences) \u2192 note\n * 6. Everything else \u2192 field\n */\nexport function classify(key: string, value: unknown): Classification {\n const normKey = key.toLowerCase().replace(/[-\\s]/g, '_');\n\n if (Array.isArray(value)) return 'note';\n if (value !== null && typeof value === 'object') return 'note';\n\n if (PROSE_KEYS.has(normKey)) return 'note';\n if (NOTE_KEY_PREFIXES.some((p) => normKey.startsWith(p))) return 'note';\n\n if (typeof value === 'string') {\n if (LINE_BREAK_RE.test(value)) return 'note';\n const matches = value.match(SENTENCE_RE);\n if (matches && matches.length >= 2) return 'note';\n }\n\n return 'field';\n}\n", "import { FIELD_MAP, normaliseKey, type FieldAction, type TwentyFieldType } from '../constants/field-map';\nimport { detectType } from './type-detector';\nimport { classify } from './classifier';\n\nexport type NormalizedField = {\n originalKey: string;\n canonicalName: string;\n twentyType: TwentyFieldType;\n action: FieldAction;\n value: unknown;\n source: 'builtin' | 'rule' | 'passthrough';\n};\n\nexport type DatabaseRule = {\n inputPattern: string;\n canonicalName: string;\n fieldType: string;\n action: FieldAction;\n priority: number;\n};\n\n/**\n * Normalize a flat record of incoming key\u2192value pairs into typed,\n * canonically-named fields ready for classification and schema sync.\n *\n * Strategy priority:\n * 1. Built-in field map (highest \u2014 covers known common variations)\n * 2. Database rules (user-configured per source or global)\n * 3. Passthrough (unknown fields get ext_ prefix + auto type detection)\n */\nexport function normalizeFields(\n flat: Record<string, unknown>,\n rules: DatabaseRule[] = [],\n): NormalizedField[] {\n const sorted = [...rules].sort((a, b) => b.priority - a.priority);\n\n return Object.entries(flat).map(([key, value]) =>\n normalizeField(key, value, sorted),\n );\n}\n\nfunction normalizeField(\n key: string,\n value: unknown,\n rules: DatabaseRule[],\n): NormalizedField {\n const lookupKey = normaliseKey(key);\n\n // 1. Built-in map\n const builtIn = FIELD_MAP[lookupKey];\n if (builtIn) {\n return {\n originalKey: key,\n canonicalName: builtIn.canonicalName,\n twentyType: builtIn.twentyType,\n action: builtIn.action,\n value,\n source: 'builtin',\n };\n }\n\n // 2. Database rules (exact match or regex)\n for (const rule of rules) {\n let matches = false;\n try {\n matches =\n rule.inputPattern === key ||\n rule.inputPattern === lookupKey ||\n new RegExp(rule.inputPattern, 'i').test(key);\n } catch {\n matches = rule.inputPattern === key;\n }\n\n if (matches) {\n return {\n originalKey: key,\n canonicalName: rule.canonicalName,\n twentyType: rule.fieldType as TwentyFieldType,\n action: rule.action,\n value,\n source: 'rule',\n };\n }\n }\n\n // 3. Passthrough \u2014 auto-detect type, classify, ext_ prefix\n const detectedType = detectType(value);\n const classification = classify(key, value);\n const canonicalName =\n classification === 'field'\n ? toExtFieldName(key)\n : lookupKey;\n\n return {\n originalKey: key,\n canonicalName,\n twentyType: detectedType,\n action: classification === 'field' ? 'field' : classification,\n value,\n source: 'passthrough',\n };\n}\n\n/** Convert any key to a valid Twenty custom field name: extPascalCase */\nfunction toExtFieldName(key: string): string {\n const cleaned = key\n .replace(/[-_\\s]+([a-zA-Z0-9])/g, (_, c: string) => c.toUpperCase())\n .replace(/[^a-zA-Z0-9]/g, '');\n return 'ext' + cleaned.charAt(0).toUpperCase() + cleaned.slice(1);\n}\n\n/**\n * Split normalised fields into those that become CRM fields\n * and those that go into the overflow note.\n */\nexport function partition(fields: NormalizedField[]): {\n crmFields: NormalizedField[];\n noteFields: NormalizedField[];\n skipped: NormalizedField[];\n} {\n const crmFields: NormalizedField[] = [];\n const noteFields: NormalizedField[] = [];\n const skipped: NormalizedField[] = [];\n\n for (const f of fields) {\n if (f.action === 'skip') skipped.push(f);\n else if (f.action === 'note') noteFields.push(f);\n else crmFields.push(f);\n }\n\n return { crmFields, noteFields, skipped };\n}\n\n/**\n * Assemble individual canonical field names into Twenty's composite\n * field structures (FULL_NAME, PHONES, EMAILS, ADDRESS, LINKS).\n */\nexport function assembleComposites(crmFields: NormalizedField[]): {\n assembled: Record<string, unknown>;\n remainder: NormalizedField[];\n} {\n const assembled: Record<string, unknown> = {};\n const consumed = new Set<string>();\n\n // \u2500\u2500 FULL_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\n const firstName = crmFields.find((f) => f.canonicalName === 'firstName');\n const lastName = crmFields.find((f) => f.canonicalName === 'lastName');\n const fullName = crmFields.find((f) => f.canonicalName === 'fullName');\n\n if (fullName && !firstName && !lastName) {\n const parts = splitFullName(String(fullName.value ?? ''));\n assembled['name'] = { firstName: parts[0], lastName: parts[1] };\n consumed.add('fullName');\n } else if (firstName || lastName) {\n assembled['name'] = {\n firstName: String(firstName?.value ?? ''),\n lastName: String(lastName?.value ?? ''),\n };\n if (firstName) consumed.add('firstName');\n if (lastName) consumed.add('lastName');\n }\n\n // \u2500\u2500 EMAILS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 const email = crmFields.find((f) => f.canonicalName === 'email');\n if (email) {\n assembled['emails'] = { primaryEmail: String(email.value ?? '') };\n consumed.add('email');\n }\n\n // \u2500\u2500 PHONES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 const phone = crmFields.find((f) => f.canonicalName === 'phone');\n if (phone) {\n const raw = String(phone.value ?? '');\n assembled['phones'] = {\n primaryPhoneNumber: raw.replace(/\\D/g, '').slice(-10),\n primaryPhoneCountryCode: 'US',\n primaryPhoneCallingCode: '+1',\n };\n consumed.add('phone');\n }\n\n // \u2500\u2500 LINKS (domainName / website) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 const domain = crmFields.find((f) => f.canonicalName === 'domainName');\n if (domain) {\n assembled['domainName'] = {\n primaryLinkUrl: String(domain.value ?? ''),\n primaryLinkLabel: '',\n };\n consumed.add('domainName');\n }\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\n const addressFields: Array<[string, string]> = [\n ['addressStreet1', 'addressStreet1'],\n ['addressStreet2', 'addressStreet2'],\n ['addressCity', 'addressCity'],\n ['addressState', 'addressState'],\n ['addressPostcode', 'addressPostcode'],\n ['addressCountry', 'addressCountry'],\n ];\n const addressParts: Record<string, unknown> = {};\n for (const [canonical, outKey] of addressFields) {\n const f = crmFields.find((x) => x.canonicalName === canonical);\n if (f) {\n addressParts[outKey] = f.value;\n consumed.add(canonical);\n }\n }\n if (Object.keys(addressParts).length > 0) {\n assembled['address'] = addressParts;\n }\n\n const remainder = crmFields.filter((f) => !consumed.has(f.canonicalName));\n return { assembled, remainder };\n}\n\nfunction splitFullName(fullName: string): [string, string] {\n const parts = fullName.trim().split(/\\s+/);\n if (parts.length === 0) return ['', ''];\n if (parts.length === 1) return [parts[0] ?? '', ''];\n const lastName = parts.pop() ?? '';\n return [parts.join(' '), lastName];\n}\n", "import { createHash } from 'node:crypto';\n\n/**\n * Compute a stable SHA-256 hash of a payload.\n * Keys are sorted before hashing so that { a:1, b:2 } and { b:2, a:1 }\n * produce the same hash \u2014 safe to retry from any client.\n */\nexport function computePayloadHash(payload: unknown): string {\n const stable = stableStringify(payload);\n return createHash('sha256').update(stable).digest('hex');\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null || value === undefined) return String(value);\n if (typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) {\n return '[' + value.map(stableStringify).join(',') + ']';\n }\n const obj = value as Record<string, unknown>;\n const sorted = Object.keys(obj)\n .sort()\n .map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)\n .join(',');\n return '{' + sorted + '}';\n}\n\n/** Derive a human-readable idempotency window label. */\nexport function withinWindow(processedAt: Date, windowMs = 5 * 60 * 1000): boolean {\n return Date.now() - processedAt.getTime() < windowMs;\n}\n", "type NoteField = { label: string; value: unknown };\n\n/**\n * Format the overflow data as a readable markdown note body.\n *\n * Output example:\n * **Source:** contact-form\n * **Message:** Need a new website, SEO help\n * **UTM Source:** google\n * **Marketing Score:** 72\n */\nexport function formatNote(\n sourceName: string,\n fields: Record<string, unknown>,\n): string {\n const lines: string[] = [];\n\n lines.push(`**Source:** ${sourceName}`);\n\n const entries = buildEntries(fields);\n\n for (const { label, value } of entries) {\n const formatted = formatValue(value);\n if (formatted !== null) {\n lines.push(`**${label}:** ${formatted}`);\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction buildEntries(fields: Record<string, unknown>): NoteField[] {\n return Object.entries(fields).map(([key, value]) => ({\n label: toLabel(key),\n value,\n }));\n}\n\nfunction toLabel(key: string): string {\n return key\n .replace(/^ext_/, '')\n .replace(/_/g, ' ')\n .replace(/([A-Z])/g, ' $1')\n .split(' ')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())\n .join(' ')\n .trim();\n}\n\nfunction formatValue(value: unknown): string | null {\n if (value === null || value === undefined || value === '') return null;\n if (Array.isArray(value)) return value.join(', ');\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\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", "import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';\nimport { detectStructure, flatten } from '../lib/flattener';\nimport { normalizeFields, partition, assembleComposites } from '../lib/normalizer';\nimport { computePayloadHash } from '../lib/idempotency';\nimport { formatNote } from '../lib/note-formatter';\nimport { coreApi } from '../lib/rest-client';\n\n/**\n * Dry-run endpoint \u2014 validates a payload and shows exactly what WOULD be created\n * without writing anything to the CRM.\n */\nconst handler = async (params: RoutePayload) => {\n const sourceSlug = params.pathParameters?.['slug'];\n if (!sourceSlug) {\n return { statusCode: 400, body: { error: 'Missing source slug' } };\n }\n\n let rawPayload: Record<string, unknown> = {};\n if (params.body && typeof params.body === 'object' && !Array.isArray(params.body)) {\n rawPayload = params.body as Record<string, unknown>;\n }\n\n // Load source config\n const srcResult = await coreApi.get<{ data: { intakeSources: { edges: Array<{ node: { id: string; name: string; targetObject: string } }> } } }>(\n `/intakeSources?filter=slug[eq]:${encodeURIComponent(sourceSlug)}&first=1`,\n ).catch(() => null);\n const source = srcResult?.data?.intakeSources?.edges?.[0]?.node;\n\n // Run normalization pipeline (read-only)\n const structure = detectStructure(rawPayload);\n const flat = structure.type === 'flat' ? structure.data\n : flatten(structure.type === 'structured' ? (structure.extra ?? {}) : rawPayload);\n\n const normalized = normalizeFields(flat);\n const { crmFields, noteFields, skipped } = partition(normalized);\n const { assembled, remainder } = assembleComposites(crmFields);\n for (const f of remainder) assembled[f.canonicalName] = f.value;\n\n const extFields = Object.keys(assembled).filter(k => /^ext[A-Z]/.test(k));\n const standardFields = Object.keys(assembled).filter(k => !/^ext[A-Z]/.test(k));\n\n const noteOverflow: Record<string, unknown> = {};\n for (const f of noteFields) noteOverflow[f.canonicalName] = f.value;\n\n return {\n statusCode: 200,\n body: {\n dryRun: true,\n source: source ? { id: source.id, name: source.name } : null,\n payloadHash: computePayloadHash(rawPayload),\n payloadStructure: structure.type,\n wouldCreate: {\n standardFields,\n customFieldsToCreate: extFields,\n noteFields: noteFields.map(f => f.canonicalName),\n skipped: skipped.map(f => f.originalKey),\n },\n notePreview: noteFields.length > 0 ? formatNote(source?.name ?? sourceSlug, noteOverflow) : null,\n rawFlat: flat,\n },\n };\n};\n\nexport default defineLogicFunction({\n universalIdentifier: '3f7a9c21-8d4b-4e6f-a1c2-5b8d0f2e7a4c',\n name: 'intake-test',\n description: 'Dry-run: validates a payload and shows what would be created without writing to the CRM.',\n timeoutSeconds: 15,\n handler,\n httpRouteTriggerSettings: {\n path: '/intake/:slug/test',\n httpMethod: 'POST',\n isAuthRequired: false,\n },\n});\n"],
5
- "mappings": "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",
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", "type FlatRecord = Record<string, unknown>;\n\nconst MAX_DEPTH = 8;\n\n/**\n * Recursively flatten a nested object into a single-level record.\n * Arrays are kept as-is (classified as notes downstream).\n * Circular references are detected and dropped.\n *\n * e.g. { user: { phone: \"x\" } } \u2192 { userPhone: \"x\" }\n */\nexport function flatten(\n input: unknown,\n prefix = '',\n depth = 0,\n seen = new WeakSet<object>(),\n): FlatRecord {\n if (depth > MAX_DEPTH) return {};\n if (input === null || input === undefined) return {};\n if (typeof input !== 'object') {\n return prefix ? { [prefix]: input } : {};\n }\n if (Array.isArray(input)) {\n return prefix ? { [prefix]: input } : {};\n }\n\n const obj = input as Record<string, unknown>;\n\n if (seen.has(obj)) return {};\n seen.add(obj);\n\n const result: FlatRecord = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const newKey = prefix ? `${prefix}${toPascalSegment(key)}` : toCamelSegment(key);\n\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value)\n ) {\n const nested = flatten(value, newKey, depth + 1, seen);\n Object.assign(result, nested);\n } else {\n result[newKey] = value;\n }\n }\n\n return result;\n}\n\n/** Convert a snake_case or kebab-case segment to camelCase. */\nfunction toCamelSegment(s: string): string {\n return s.replace(/[-_]([a-zA-Z0-9])/g, (_, c: string) => c.toUpperCase());\n}\n\n/** Convert a snake_case or kebab-case segment to PascalCase for joining. */\nfunction toPascalSegment(s: string): string {\n const camel = toCamelSegment(s);\n return camel.charAt(0).toUpperCase() + camel.slice(1);\n}\n\n/**\n * Detect whether a top-level payload is \"structured\" (pipeline-app style\n * with nested `company` / `person` object keys) or \"flat\" (contact-form style).\n */\nexport type PayloadStructure =\n | { type: 'structured'; company: FlatRecord | null; person: FlatRecord | null; extra: FlatRecord }\n | { type: 'flat'; data: FlatRecord };\n\nexport function detectStructure(raw: unknown): PayloadStructure {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {\n return { type: 'flat', data: {} };\n }\n\n const obj = raw as Record<string, unknown>;\n const hasObjectCompany =\n 'company' in obj && typeof obj['company'] === 'object' && obj['company'] !== null && !Array.isArray(obj['company']);\n const hasObjectPerson =\n 'person' in obj && typeof obj['person'] === 'object' && obj['person'] !== null && !Array.isArray(obj['person']);\n\n if (!hasObjectCompany && !hasObjectPerson) {\n return { type: 'flat', data: flatten(obj) };\n }\n\n // Keep company and person in their original nested form \u2014 they are already\n // in Twenty's native composite format (domainName, address, emails, phones, etc.)\n const company = hasObjectCompany ? (obj['company'] as FlatRecord) : null;\n const person = hasObjectPerson ? (obj['person'] as FlatRecord) : null;\n\n const extra: FlatRecord = {};\n for (const [k, v] of Object.entries(obj)) {\n if (k === 'company' || k === 'person') continue;\n const nested = flatten({ [k]: v });\n Object.assign(extra, nested);\n }\n\n return { type: 'structured', company, person, extra };\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 { TwentyFieldType } from '../constants/field-map';\n\nconst URL_RE = /^https?:\\/\\/[^\\s/$.?#].[^\\s]*$/i;\nconst EMAIL_RE = /^[^@\\s]+@[^@\\s]+\\.[^@\\s]{2,}$/;\nconst PHONE_RE = /^[+]?[(]?[0-9]{1,4}[)]?[-\\s.]?[(]?[0-9]{1,4}[)]?[-\\s.]?[0-9]{3,9}([-\\s.][0-9]{1,9})?$/;\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})?)?)?$/;\n\nexport function detectType(value: unknown): TwentyFieldType {\n if (value === null || value === undefined) return 'TEXT';\n if (typeof value === 'boolean') return 'BOOLEAN';\n if (typeof value === 'number') return 'NUMBER';\n\n if (typeof value === 'string') {\n const s = value.trim();\n if (!s) return 'TEXT';\n if (URL_RE.test(s)) return 'LINKS';\n if (EMAIL_RE.test(s)) return 'EMAILS';\n if (PHONE_RE.test(s)) return 'PHONES';\n if (DATE_RE.test(s)) return 'DATE_TIME';\n if (!isNaN(Number(s)) && s !== '') return 'NUMBER';\n return 'TEXT';\n }\n\n if (Array.isArray(value)) return 'RAW_JSON';\n if (typeof value === 'object') return 'RAW_JSON';\n\n return 'TEXT';\n}\n", "const PROSE_KEYS = new Set([\n 'message', 'description', 'analysis', 'notes', 'comments',\n 'summary', 'inquiry', 'body', 'details', 'content', 'text',\n 'feedback', 'about', 'additional_info', 'info', 'context',\n 'requirements', 'goals', 'challenges', 'question', 'request',\n]);\n\nconst NOTE_KEY_PREFIXES = ['utm_', 'ga_', 'fb_', 'gclid', 'msclkid'];\n\nconst SENTENCE_RE = /[.!?]\\s+[A-Z]/g;\nconst LINE_BREAK_RE = /\\n|\\r/;\n\nexport type Classification = 'field' | 'note' | 'skip';\n\n/**\n * Decide whether a key+value pair belongs in a CRM field or the overflow note.\n *\n * Rules (in priority order):\n * 1. Arrays / objects \u2192 note (can't be a single field)\n * 2. Known note key names \u2192 note\n * 3. UTM / tracking prefix \u2192 note\n * 4. Contains line breaks \u2192 note\n * 5. Looks like prose (2+ sentences) \u2192 note\n * 6. Everything else \u2192 field\n */\nexport function classify(key: string, value: unknown): Classification {\n const normKey = key.toLowerCase().replace(/[-\\s]/g, '_');\n\n if (Array.isArray(value)) return 'note';\n if (value !== null && typeof value === 'object') return 'note';\n\n if (PROSE_KEYS.has(normKey)) return 'note';\n if (NOTE_KEY_PREFIXES.some((p) => normKey.startsWith(p))) return 'note';\n\n if (typeof value === 'string') {\n if (LINE_BREAK_RE.test(value)) return 'note';\n const matches = value.match(SENTENCE_RE);\n if (matches && matches.length >= 2) return 'note';\n }\n\n return 'field';\n}\n", "import { FIELD_MAP, normaliseKey, type FieldAction, type TwentyFieldType } from '../constants/field-map';\nimport { detectType } from './type-detector';\nimport { classify } from './classifier';\n\nexport type NormalizedField = {\n originalKey: string;\n canonicalName: string;\n twentyType: TwentyFieldType;\n action: FieldAction;\n value: unknown;\n source: 'builtin' | 'rule' | 'passthrough';\n};\n\nexport type DatabaseRule = {\n inputPattern: string;\n canonicalName: string;\n fieldType: string;\n action: FieldAction;\n priority: number;\n};\n\n/**\n * Normalize a flat record of incoming key\u2192value pairs into typed,\n * canonically-named fields ready for classification and schema sync.\n *\n * Strategy priority:\n * 1. Built-in field map (highest \u2014 covers known common variations)\n * 2. Database rules (user-configured per source or global)\n * 3. Passthrough (unknown fields get ext_ prefix + auto type detection)\n */\nexport function normalizeFields(\n flat: Record<string, unknown>,\n rules: DatabaseRule[] = [],\n): NormalizedField[] {\n const sorted = [...rules].sort((a, b) => b.priority - a.priority);\n\n return Object.entries(flat).map(([key, value]) =>\n normalizeField(key, value, sorted),\n );\n}\n\nfunction normalizeField(\n key: string,\n value: unknown,\n rules: DatabaseRule[],\n): NormalizedField {\n const lookupKey = normaliseKey(key);\n\n // 1. Built-in map\n const builtIn = FIELD_MAP[lookupKey];\n if (builtIn) {\n return {\n originalKey: key,\n canonicalName: builtIn.canonicalName,\n twentyType: builtIn.twentyType,\n action: builtIn.action,\n value,\n source: 'builtin',\n };\n }\n\n // 2. Database rules (exact match or regex)\n for (const rule of rules) {\n let matches = false;\n try {\n matches =\n rule.inputPattern === key ||\n rule.inputPattern === lookupKey ||\n new RegExp(rule.inputPattern, 'i').test(key);\n } catch {\n matches = rule.inputPattern === key;\n }\n\n if (matches) {\n return {\n originalKey: key,\n canonicalName: rule.canonicalName,\n twentyType: rule.fieldType as TwentyFieldType,\n action: rule.action,\n value,\n source: 'rule',\n };\n }\n }\n\n // 3. Passthrough \u2014 auto-detect type, classify, ext_ prefix\n const detectedType = detectType(value);\n const classification = classify(key, value);\n const canonicalName =\n classification === 'field'\n ? toExtFieldName(key)\n : lookupKey;\n\n return {\n originalKey: key,\n canonicalName,\n twentyType: detectedType,\n action: classification === 'field' ? 'field' : classification,\n value,\n source: 'passthrough',\n };\n}\n\n/** Convert any key to a valid Twenty custom field name: extPascalCase */\nfunction toExtFieldName(key: string): string {\n const cleaned = key\n .replace(/[-_\\s]+([a-zA-Z0-9])/g, (_, c: string) => c.toUpperCase())\n .replace(/[^a-zA-Z0-9]/g, '');\n return 'ext' + cleaned.charAt(0).toUpperCase() + cleaned.slice(1);\n}\n\n/**\n * Split normalised fields into those that become CRM fields\n * and those that go into the overflow note.\n */\nexport function partition(fields: NormalizedField[]): {\n crmFields: NormalizedField[];\n noteFields: NormalizedField[];\n skipped: NormalizedField[];\n} {\n const crmFields: NormalizedField[] = [];\n const noteFields: NormalizedField[] = [];\n const skipped: NormalizedField[] = [];\n\n for (const f of fields) {\n if (f.action === 'skip') skipped.push(f);\n else if (f.action === 'note') noteFields.push(f);\n else crmFields.push(f);\n }\n\n return { crmFields, noteFields, skipped };\n}\n\n/**\n * Assemble individual canonical field names into Twenty's composite\n * field structures (FULL_NAME, PHONES, EMAILS, ADDRESS, LINKS).\n */\nexport function assembleComposites(crmFields: NormalizedField[]): {\n assembled: Record<string, unknown>;\n remainder: NormalizedField[];\n} {\n const assembled: Record<string, unknown> = {};\n const consumed = new Set<string>();\n\n // \u2500\u2500 FULL_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\n const firstName = crmFields.find((f) => f.canonicalName === 'firstName');\n const lastName = crmFields.find((f) => f.canonicalName === 'lastName');\n const fullName = crmFields.find((f) => f.canonicalName === 'fullName');\n\n if (fullName && !firstName && !lastName) {\n const parts = splitFullName(String(fullName.value ?? ''));\n assembled['name'] = { firstName: parts[0], lastName: parts[1] };\n consumed.add('fullName');\n } else if (firstName || lastName) {\n assembled['name'] = {\n firstName: String(firstName?.value ?? ''),\n lastName: String(lastName?.value ?? ''),\n };\n if (firstName) consumed.add('firstName');\n if (lastName) consumed.add('lastName');\n }\n\n // \u2500\u2500 EMAILS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 const email = crmFields.find((f) => f.canonicalName === 'email');\n if (email) {\n assembled['emails'] = { primaryEmail: String(email.value ?? '') };\n consumed.add('email');\n }\n\n // \u2500\u2500 PHONES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 const phone = crmFields.find((f) => f.canonicalName === 'phone');\n if (phone) {\n const raw = String(phone.value ?? '');\n assembled['phones'] = {\n primaryPhoneNumber: raw.replace(/\\D/g, '').slice(-10),\n primaryPhoneCountryCode: 'US',\n primaryPhoneCallingCode: '+1',\n };\n consumed.add('phone');\n }\n\n // \u2500\u2500 LINKS (domainName / website) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 const domain = crmFields.find((f) => f.canonicalName === 'domainName');\n if (domain) {\n assembled['domainName'] = {\n primaryLinkUrl: String(domain.value ?? ''),\n primaryLinkLabel: '',\n };\n consumed.add('domainName');\n }\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\n const addressFields: Array<[string, string]> = [\n ['addressStreet1', 'addressStreet1'],\n ['addressStreet2', 'addressStreet2'],\n ['addressCity', 'addressCity'],\n ['addressState', 'addressState'],\n ['addressPostcode', 'addressPostcode'],\n ['addressCountry', 'addressCountry'],\n ];\n const addressParts: Record<string, unknown> = {};\n for (const [canonical, outKey] of addressFields) {\n const f = crmFields.find((x) => x.canonicalName === canonical);\n if (f) {\n addressParts[outKey] = f.value;\n consumed.add(canonical);\n }\n }\n if (Object.keys(addressParts).length > 0) {\n assembled['address'] = addressParts;\n }\n\n const remainder = crmFields.filter((f) => !consumed.has(f.canonicalName));\n return { assembled, remainder };\n}\n\nfunction splitFullName(fullName: string): [string, string] {\n const parts = fullName.trim().split(/\\s+/);\n if (parts.length === 0) return ['', ''];\n if (parts.length === 1) return [parts[0] ?? '', ''];\n const lastName = parts.pop() ?? '';\n return [parts.join(' '), lastName];\n}\n", "import { createHash } from 'node:crypto';\n\n/**\n * Compute a stable SHA-256 hash of a payload.\n * Keys are sorted before hashing so that { a:1, b:2 } and { b:2, a:1 }\n * produce the same hash \u2014 safe to retry from any client.\n */\nexport function computePayloadHash(payload: unknown): string {\n const stable = stableStringify(payload);\n return createHash('sha256').update(stable).digest('hex');\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null || value === undefined) return String(value);\n if (typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) {\n return '[' + value.map(stableStringify).join(',') + ']';\n }\n const obj = value as Record<string, unknown>;\n const sorted = Object.keys(obj)\n .sort()\n .map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)\n .join(',');\n return '{' + sorted + '}';\n}\n\n/** Derive a human-readable idempotency window label. */\nexport function withinWindow(processedAt: Date, windowMs = 5 * 60 * 1000): boolean {\n return Date.now() - processedAt.getTime() < windowMs;\n}\n", "type NoteField = { label: string; value: unknown };\n\n/**\n * Format the overflow data as a readable markdown note body.\n *\n * Output example:\n * **Source:** contact-form\n * **Message:** Need a new website, SEO help\n * **UTM Source:** google\n * **Marketing Score:** 72\n */\nexport function formatNote(\n sourceName: string,\n fields: Record<string, unknown>,\n): string {\n const lines: string[] = [];\n\n lines.push(`**Source:** ${sourceName}`);\n\n const entries = buildEntries(fields);\n\n for (const { label, value } of entries) {\n const formatted = formatValue(value);\n if (formatted !== null) {\n lines.push(`**${label}:** ${formatted}`);\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction buildEntries(fields: Record<string, unknown>): NoteField[] {\n return Object.entries(fields).map(([key, value]) => ({\n label: toLabel(key),\n value,\n }));\n}\n\nfunction toLabel(key: string): string {\n return key\n .replace(/^ext_/, '')\n .replace(/_/g, ' ')\n .replace(/([A-Z])/g, ' $1')\n .split(' ')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())\n .join(' ')\n .trim();\n}\n\nfunction formatValue(value: unknown): string | null {\n if (value === null || value === undefined || value === '') return null;\n if (Array.isArray(value)) return value.join(', ');\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\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", "import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';\nimport { detectStructure, flatten } from '../lib/flattener';\nimport { normalizeFields, partition, assembleComposites } from '../lib/normalizer';\nimport { computePayloadHash } from '../lib/idempotency';\nimport { formatNote } from '../lib/note-formatter';\nimport { coreApi } from '../lib/rest-client';\n\n/**\n * Dry-run endpoint \u2014 validates a payload and shows exactly what WOULD be created\n * without writing anything to the CRM.\n */\nconst handler = async (params: RoutePayload) => {\n const sourceSlug = params.pathParameters?.['slug'];\n if (!sourceSlug) {\n return { statusCode: 400, body: { error: 'Missing source slug' } };\n }\n\n let rawPayload: Record<string, unknown> = {};\n if (params.body && typeof params.body === 'object' && !Array.isArray(params.body)) {\n rawPayload = params.body as Record<string, unknown>;\n }\n\n // Load source config\n const srcResult = await coreApi.get<{ data: { intakeSources: { edges: Array<{ node: { id: string; name: string; targetObject: string } }> } } }>(\n `/intakeSources?filter=slug[eq]:${encodeURIComponent(sourceSlug)}&first=1`,\n ).catch(() => null);\n const source = srcResult?.data?.intakeSources?.edges?.[0]?.node;\n\n // Run normalization pipeline (read-only)\n const structure = detectStructure(rawPayload);\n const flat = structure.type === 'flat' ? structure.data\n : flatten(structure.type === 'structured' ? (structure.extra ?? {}) : rawPayload);\n\n const normalized = normalizeFields(flat);\n const { crmFields, noteFields, skipped } = partition(normalized);\n const { assembled, remainder } = assembleComposites(crmFields);\n for (const f of remainder) assembled[f.canonicalName] = f.value;\n\n const extFields = Object.keys(assembled).filter(k => /^ext[A-Z]/.test(k));\n const standardFields = Object.keys(assembled).filter(k => !/^ext[A-Z]/.test(k));\n\n const noteOverflow: Record<string, unknown> = {};\n for (const f of noteFields) noteOverflow[f.canonicalName] = f.value;\n\n return {\n statusCode: 200,\n body: {\n dryRun: true,\n source: source ? { id: source.id, name: source.name } : null,\n payloadHash: computePayloadHash(rawPayload),\n payloadStructure: structure.type,\n wouldCreate: {\n standardFields,\n customFieldsToCreate: extFields,\n noteFields: noteFields.map(f => f.canonicalName),\n skipped: skipped.map(f => f.originalKey),\n },\n notePreview: noteFields.length > 0 ? formatNote(source?.name ?? sourceSlug, noteOverflow) : null,\n rawFlat: flat,\n },\n };\n};\n\nexport default defineLogicFunction({\n universalIdentifier: '3f7a9c21-8d4b-4e6f-a1c2-5b8d0f2e7a4c',\n name: 'intake-test',\n description: 'Dry-run: validates a payload and shows what would be created without writing to the CRM.',\n timeoutSeconds: 15,\n handler,\n httpRouteTriggerSettings: {\n path: '/intake/:slug/test',\n httpMethod: 'POST',\n isAuthRequired: false,\n },\n});\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,75 @@
1
+ import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
2
+ import { detectStructure, flatten } from '../lib/flattener';
3
+ import { normalizeFields, partition, assembleComposites } from '../lib/normalizer';
4
+ import { computePayloadHash } from '../lib/idempotency';
5
+ import { formatNote } from '../lib/note-formatter';
6
+ import { coreApi } from '../lib/rest-client';
7
+
8
+ /**
9
+ * Dry-run endpoint — validates a payload and shows exactly what WOULD be created
10
+ * without writing anything to the CRM.
11
+ */
12
+ const handler = async (params: RoutePayload) => {
13
+ const sourceSlug = params.pathParameters?.['slug'];
14
+ if (!sourceSlug) {
15
+ return { statusCode: 400, body: { error: 'Missing source slug' } };
16
+ }
17
+
18
+ let rawPayload: Record<string, unknown> = {};
19
+ if (params.body && typeof params.body === 'object' && !Array.isArray(params.body)) {
20
+ rawPayload = params.body as Record<string, unknown>;
21
+ }
22
+
23
+ // Load source config
24
+ const srcResult = await coreApi.get<{ data: { intakeSources: { edges: Array<{ node: { id: string; name: string; targetObject: string } }> } } }>(
25
+ `/intakeSources?filter=slug[eq]:${encodeURIComponent(sourceSlug)}&first=1`,
26
+ ).catch(() => null);
27
+ const source = srcResult?.data?.intakeSources?.edges?.[0]?.node;
28
+
29
+ // Run normalization pipeline (read-only)
30
+ const structure = detectStructure(rawPayload);
31
+ const flat = structure.type === 'flat' ? structure.data
32
+ : flatten(structure.type === 'structured' ? (structure.extra ?? {}) : rawPayload);
33
+
34
+ const normalized = normalizeFields(flat);
35
+ const { crmFields, noteFields, skipped } = partition(normalized);
36
+ const { assembled, remainder } = assembleComposites(crmFields);
37
+ for (const f of remainder) assembled[f.canonicalName] = f.value;
38
+
39
+ const extFields = Object.keys(assembled).filter(k => /^ext[A-Z]/.test(k));
40
+ const standardFields = Object.keys(assembled).filter(k => !/^ext[A-Z]/.test(k));
41
+
42
+ const noteOverflow: Record<string, unknown> = {};
43
+ for (const f of noteFields) noteOverflow[f.canonicalName] = f.value;
44
+
45
+ return {
46
+ statusCode: 200,
47
+ body: {
48
+ dryRun: true,
49
+ source: source ? { id: source.id, name: source.name } : null,
50
+ payloadHash: computePayloadHash(rawPayload),
51
+ payloadStructure: structure.type,
52
+ wouldCreate: {
53
+ standardFields,
54
+ customFieldsToCreate: extFields,
55
+ noteFields: noteFields.map(f => f.canonicalName),
56
+ skipped: skipped.map(f => f.originalKey),
57
+ },
58
+ notePreview: noteFields.length > 0 ? formatNote(source?.name ?? sourceSlug, noteOverflow) : null,
59
+ rawFlat: flat,
60
+ },
61
+ };
62
+ };
63
+
64
+ export default defineLogicFunction({
65
+ universalIdentifier: '3f7a9c21-8d4b-4e6f-a1c2-5b8d0f2e7a4c',
66
+ name: 'intake-test',
67
+ description: 'Dry-run: validates a payload and shows what would be created without writing to the CRM.',
68
+ timeoutSeconds: 15,
69
+ handler,
70
+ httpRouteTriggerSettings: {
71
+ path: '/intake/:slug/test',
72
+ httpMethod: 'POST',
73
+ isAuthRequired: false,
74
+ },
75
+ });
@@ -136,6 +136,7 @@ var LoadSourceStage = class {
136
136
  constructor(client) {
137
137
  this.client = client;
138
138
  }
139
+ client;
139
140
  name = "load-source";
140
141
  async execute(ctx) {
141
142
  const result = await this.client.query({
@@ -232,6 +233,7 @@ var IdempotencyStage = class {
232
233
  constructor(client) {
233
234
  this.client = client;
234
235
  }
236
+ client;
235
237
  name = "idempotency";
236
238
  async execute(ctx) {
237
239
  const hash = computePayloadHash(ctx.rawPayload);
@@ -681,6 +683,7 @@ var NormalizeStage = class {
681
683
  constructor(client) {
682
684
  this.client = client;
683
685
  }
686
+ client;
684
687
  name = "normalize";
685
688
  async execute(ctx) {
686
689
  if (!ctx.flat) {