@memberjunction/integration-connectors 0.0.1 → 5.9.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 CHANGED
@@ -1,45 +1,124 @@
1
1
  # @memberjunction/integration-connectors
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Concrete integration connectors for the MemberJunction Integration Engine. This package provides four connectors that implement `BaseIntegrationConnector` from `@memberjunction/integration-engine`.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## Connectors
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ ### RelationalDBConnector (Base Class)
8
8
 
9
- ## Purpose
9
+ Abstract base class for connectors that read from SQL Server databases via the `mssql` package. Provides shared logic for connection management, object/field discovery, and batched record fetching with watermark support.
10
10
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/integration-connectors`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
11
+ **Configuration JSON:**
12
+ ```json
13
+ {
14
+ "server": "your-sql-server",
15
+ "database": "YourDatabase",
16
+ "user": "username",
17
+ "password": "password"
18
+ }
19
+ ```
15
20
 
16
- ## What is OIDC Trusted Publishing?
21
+ **Shared capabilities:**
22
+ - `TestConnection` — runs `SELECT @@VERSION` to verify connectivity
23
+ - `DiscoverObjects` — queries `INFORMATION_SCHEMA.TABLES` for all base tables
24
+ - `DiscoverFields` — queries `INFORMATION_SCHEMA.COLUMNS` for a given table
25
+ - `FetchChangesFromTable` — parameterized incremental fetch with watermark filtering and batch limiting
26
+ - Connection pool caching per server+database pair
17
27
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
28
+ ### HubSpotConnector
19
29
 
20
- ## Setup Instructions
30
+ Reads from MockHubSpot database tables: `hs_Contacts`, `hs_Companies`, `hs_Deals`, `hs_Owners`.
21
31
 
22
- To properly configure OIDC trusted publishing for this package:
32
+ **Registration:** `@RegisterClass(BaseIntegrationConnector, 'HubSpotConnector')`
23
33
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
34
+ **Default field mappings:**
35
+ | Source Object | Target Entity | Mappings |
36
+ |---|---|---|
37
+ | `hs_Contacts` | Contacts | email→Email (key), firstname→FirstName, lastname→LastName, phone→Phone, company→CompanyName, lifecyclestage→Status |
38
+ | `hs_Companies` | Companies | name→Name (key), domain→Website, industry→Industry, city→City, state→State |
39
+ | `hs_Deals` | — | No default mappings |
28
40
 
29
- ## DO NOT USE THIS PACKAGE
41
+ **To swap for real HubSpot API:** Replace `RelationalDBConnector` with direct HTTP calls to the HubSpot V3 API. The `FetchChanges` method would use the HubSpot Search API with `lastmodifieddate` filters, and `Configuration` would store an API key or OAuth tokens instead of database credentials.
30
42
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
43
+ ### SalesforceConnector
36
44
 
37
- ## More Information
45
+ Reads from MockSalesforce database tables: `sf_Contact`, `sf_Account`, `sf_Opportunity`, `sf_User`.
38
46
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
47
+ **Registration:** `@RegisterClass(BaseIntegrationConnector, 'SalesforceConnector')`
42
48
 
43
- ---
49
+ **Default field mappings:**
50
+ | Source Object | Target Entity | Mappings |
51
+ |---|---|---|
52
+ | `sf_Contact` | Contacts | Email→Email (key), FirstName→FirstName, LastName→LastName, Phone→Phone, Title→Title, Department→Department |
53
+ | `sf_Account` | Companies | Name→Name (key), Industry→Industry, BillingCity→City, BillingState→State, Phone→Phone |
44
54
 
45
- **Maintained for OIDC setup purposes only**
55
+ **To swap for real Salesforce API:** Replace `RelationalDBConnector` with `jsforce` or direct REST calls to the Salesforce SOQL API. Use `SystemModstamp` for watermarks and the Salesforce Bulk API for large datasets.
56
+
57
+ ### YourMembershipConnector
58
+
59
+ Reads from MockYourMembership database tables: `ym_Members`, `ym_Events`, `ym_EventRegistrations`, `ym_Chapters`, `ym_MembershipLevels`.
60
+
61
+ **Registration:** `@RegisterClass(BaseIntegrationConnector, 'YourMembershipConnector')`
62
+
63
+ **Default field mappings:**
64
+ | Source Object | Target Entity | Mappings |
65
+ |---|---|---|
66
+ | `ym_Members` | Contacts | email→Email (key), first_name→FirstName, last_name→LastName, phone→Phone, status→Status |
67
+
68
+ ### FileFeedConnector
69
+
70
+ Reads data from local CSV files. Always performs a full load (no incremental sync support).
71
+
72
+ **Registration:** `@RegisterClass(BaseIntegrationConnector, 'FileFeedConnector')`
73
+
74
+ **Configuration JSON:**
75
+ ```json
76
+ {
77
+ "storagePath": "/absolute/path/to/file.csv",
78
+ "fileType": "csv"
79
+ }
80
+ ```
81
+
82
+ **Capabilities:**
83
+ - `TestConnection` — checks that the file exists on disk
84
+ - `DiscoverObjects` — returns the file name as the single available object
85
+ - `DiscoverFields` — parses the CSV header row
86
+ - `FetchChanges` — parses all data rows into `ExternalRecord[]` (watermark is ignored)
87
+
88
+ **CSV parsing rules:**
89
+ - First row is treated as the header
90
+ - Supports quoted fields with commas inside
91
+ - Supports escaped quotes (`""`) inside quoted fields
92
+ - Empty values are mapped to `null`
93
+
94
+ ## Building
95
+
96
+ ```bash
97
+ cd packages/Integration/connectors
98
+ npm run build
99
+ ```
100
+
101
+ ## Testing
102
+
103
+ Tests are integration tests that connect to the mock SQL Server databases.
104
+
105
+ ```bash
106
+ cd packages/Integration/connectors
107
+ npm run test # single run
108
+ npm run test:watch # watch mode
109
+ ```
110
+
111
+ ## Architecture
112
+
113
+ ```
114
+ src/
115
+ index.ts # Package exports
116
+ RelationalDBConnector.ts # Base class for SQL-based connectors
117
+ HubSpotConnector.ts # HubSpot mock connector
118
+ SalesforceConnector.ts # Salesforce mock connector
119
+ YourMembershipConnector.ts # YourMembership mock connector
120
+ FileFeedConnector.ts # CSV file connector
121
+ __tests__/ # Integration tests
122
+ test-fixtures/
123
+ sample-contacts.csv # 100-row sample for FileFeed testing
124
+ ```
@@ -0,0 +1,64 @@
1
+ import type { UserInfo } from '@memberjunction/core';
2
+ import type { MJCompanyIntegrationEntity } from '@memberjunction/core-entities';
3
+ import { BaseIntegrationConnector, type ConnectionTestResult, type ExternalObjectSchema, type ExternalFieldSchema, type FetchContext, type FetchBatchResult } from '@memberjunction/integration-engine';
4
+ /**
5
+ * Connector for file-based data feeds (CSV).
6
+ * Reads data from local CSV files. Always performs a full load (no incremental sync).
7
+ * Supports quoted fields with commas inside and empty values mapped to null.
8
+ */
9
+ export declare class FileFeedConnector extends BaseIntegrationConnector {
10
+ /**
11
+ * Tests connectivity by checking that the configured file exists on disk.
12
+ * @param companyIntegration - The entity with Configuration JSON containing storagePath
13
+ * @param _contextUser - User context for authorization
14
+ * @returns Success if the file exists, failure otherwise
15
+ */
16
+ TestConnection(companyIntegration: MJCompanyIntegrationEntity, _contextUser: UserInfo): Promise<ConnectionTestResult>;
17
+ /**
18
+ * Discovers available objects by returning the file name as the single object.
19
+ * @param companyIntegration - The entity with Configuration JSON
20
+ * @param _contextUser - User context for authorization
21
+ * @returns Array with one entry for the configured file
22
+ */
23
+ DiscoverObjects(companyIntegration: MJCompanyIntegrationEntity, _contextUser: UserInfo): Promise<ExternalObjectSchema[]>;
24
+ /**
25
+ * Discovers fields by parsing the CSV header row.
26
+ * @param companyIntegration - The entity with Configuration JSON
27
+ * @param _objectName - Object name (file name), not used since we only have one file
28
+ * @param _contextUser - User context for authorization
29
+ * @returns Array of field schemas from the CSV header
30
+ */
31
+ DiscoverFields(companyIntegration: MJCompanyIntegrationEntity, _objectName: string, _contextUser: UserInfo): Promise<ExternalFieldSchema[]>;
32
+ /**
33
+ * Fetches all records from the CSV file as a full load.
34
+ * CSV files do not support incremental sync — watermark is ignored.
35
+ * @param ctx - Fetch context (watermark is always ignored for file feeds)
36
+ * @returns All records from the CSV file in a single batch
37
+ */
38
+ FetchChanges(ctx: FetchContext): Promise<FetchBatchResult>;
39
+ /**
40
+ * Parses the Configuration JSON to extract file feed settings.
41
+ * @param companyIntegration - The entity containing Configuration JSON
42
+ * @returns Parsed file feed configuration
43
+ */
44
+ private parseConfig;
45
+ /**
46
+ * Parses an array of CSV data lines into ExternalRecord objects.
47
+ * @param lines - CSV data lines (excluding header)
48
+ * @param headers - Column header names
49
+ * @param objectName - Object type name for the records
50
+ * @returns Array of ExternalRecord objects
51
+ */
52
+ private parseDataLines;
53
+ }
54
+ /**
55
+ * Parses a single CSV line handling quoted fields with commas.
56
+ * Supports:
57
+ * - Unquoted fields separated by commas
58
+ * - Quoted fields that may contain commas
59
+ * - Escaped quotes ("") inside quoted fields
60
+ * @param line - A single CSV line to parse
61
+ * @returns Array of field values
62
+ */
63
+ export declare function parseCsvLine(line: string): string[];
64
+ //# sourceMappingURL=FileFeedConnector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileFeedConnector.d.ts","sourceRoot":"","sources":["../src/FileFeedConnector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EACH,wBAAwB,EACxB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EAExB,MAAM,oCAAoC,CAAC;AAY5C;;;;GAIG;AACH,qBACa,iBAAkB,SAAQ,wBAAwB;IAC3D;;;;;OAKG;IACU,cAAc,CACvB,kBAAkB,EAAE,0BAA0B,EAC9C,YAAY,EAAE,QAAQ,GACvB,OAAO,CAAC,oBAAoB,CAAC;IAahC;;;;;OAKG;IACU,eAAe,CACxB,kBAAkB,EAAE,0BAA0B,EAC9C,YAAY,EAAE,QAAQ,GACvB,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAalC;;;;;;OAMG;IACU,cAAc,CACvB,kBAAkB,EAAE,0BAA0B,EAC9C,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,QAAQ,GACvB,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAmBjC;;;;;OAKG;IACU,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAcvE;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAiBnB;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;CAsBzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAqCnD"}
@@ -0,0 +1,190 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { RegisterClass } from '@memberjunction/global';
8
+ import { BaseIntegrationConnector, } from '@memberjunction/integration-engine';
9
+ import * as fs from 'node:fs';
10
+ import * as path from 'node:path';
11
+ /**
12
+ * Connector for file-based data feeds (CSV).
13
+ * Reads data from local CSV files. Always performs a full load (no incremental sync).
14
+ * Supports quoted fields with commas inside and empty values mapped to null.
15
+ */
16
+ let FileFeedConnector = class FileFeedConnector extends BaseIntegrationConnector {
17
+ /**
18
+ * Tests connectivity by checking that the configured file exists on disk.
19
+ * @param companyIntegration - The entity with Configuration JSON containing storagePath
20
+ * @param _contextUser - User context for authorization
21
+ * @returns Success if the file exists, failure otherwise
22
+ */
23
+ async TestConnection(companyIntegration, _contextUser) {
24
+ try {
25
+ const config = this.parseConfig(companyIntegration);
26
+ if (!fs.existsSync(config.StoragePath)) {
27
+ return { Success: false, Message: `File not found: ${config.StoragePath}` };
28
+ }
29
+ return { Success: true, Message: `File exists: ${config.StoragePath}` };
30
+ }
31
+ catch (err) {
32
+ const message = err instanceof Error ? err.message : String(err);
33
+ return { Success: false, Message: `Configuration error: ${message}` };
34
+ }
35
+ }
36
+ /**
37
+ * Discovers available objects by returning the file name as the single object.
38
+ * @param companyIntegration - The entity with Configuration JSON
39
+ * @param _contextUser - User context for authorization
40
+ * @returns Array with one entry for the configured file
41
+ */
42
+ async DiscoverObjects(companyIntegration, _contextUser) {
43
+ const config = this.parseConfig(companyIntegration);
44
+ const fileName = path.basename(config.StoragePath);
45
+ return [
46
+ {
47
+ Name: fileName,
48
+ Label: fileName,
49
+ SupportsIncrementalSync: false,
50
+ SupportsWrite: false,
51
+ },
52
+ ];
53
+ }
54
+ /**
55
+ * Discovers fields by parsing the CSV header row.
56
+ * @param companyIntegration - The entity with Configuration JSON
57
+ * @param _objectName - Object name (file name), not used since we only have one file
58
+ * @param _contextUser - User context for authorization
59
+ * @returns Array of field schemas from the CSV header
60
+ */
61
+ async DiscoverFields(companyIntegration, _objectName, _contextUser) {
62
+ const config = this.parseConfig(companyIntegration);
63
+ const content = fs.readFileSync(config.StoragePath, 'utf-8');
64
+ const firstLine = content.split('\n')[0]?.trim();
65
+ if (!firstLine) {
66
+ return [];
67
+ }
68
+ const headers = parseCsvLine(firstLine);
69
+ return headers.map((header) => ({
70
+ Name: header,
71
+ Label: header,
72
+ DataType: 'string',
73
+ IsRequired: false,
74
+ IsUniqueKey: false,
75
+ IsReadOnly: true,
76
+ }));
77
+ }
78
+ /**
79
+ * Fetches all records from the CSV file as a full load.
80
+ * CSV files do not support incremental sync — watermark is ignored.
81
+ * @param ctx - Fetch context (watermark is always ignored for file feeds)
82
+ * @returns All records from the CSV file in a single batch
83
+ */
84
+ async FetchChanges(ctx) {
85
+ const config = this.parseConfig(ctx.CompanyIntegration);
86
+ const content = fs.readFileSync(config.StoragePath, 'utf-8');
87
+ const lines = content.split('\n').filter((line) => line.trim().length > 0);
88
+ if (lines.length < 2) {
89
+ return { Records: [], HasMore: false };
90
+ }
91
+ const headers = parseCsvLine(lines[0]);
92
+ const records = this.parseDataLines(lines.slice(1), headers, ctx.ObjectName);
93
+ return { Records: records, HasMore: false };
94
+ }
95
+ /**
96
+ * Parses the Configuration JSON to extract file feed settings.
97
+ * @param companyIntegration - The entity containing Configuration JSON
98
+ * @returns Parsed file feed configuration
99
+ */
100
+ parseConfig(companyIntegration) {
101
+ const configJson = companyIntegration.Get('Configuration');
102
+ if (!configJson) {
103
+ throw new Error('CompanyIntegration.Configuration is null or empty');
104
+ }
105
+ const parsed = JSON.parse(configJson);
106
+ const storagePath = parsed['storagePath'];
107
+ const fileType = parsed['fileType'] ?? 'csv';
108
+ if (!storagePath) {
109
+ throw new Error('Configuration JSON must contain a storagePath field');
110
+ }
111
+ return { StoragePath: storagePath, FileType: fileType };
112
+ }
113
+ /**
114
+ * Parses an array of CSV data lines into ExternalRecord objects.
115
+ * @param lines - CSV data lines (excluding header)
116
+ * @param headers - Column header names
117
+ * @param objectName - Object type name for the records
118
+ * @returns Array of ExternalRecord objects
119
+ */
120
+ parseDataLines(lines, headers, objectName) {
121
+ return lines.map((line, index) => {
122
+ const values = parseCsvLine(line);
123
+ const fields = {};
124
+ for (let i = 0; i < headers.length; i++) {
125
+ const rawValue = i < values.length ? values[i] : '';
126
+ fields[headers[i]] = rawValue === '' ? null : rawValue;
127
+ }
128
+ return {
129
+ ExternalID: String(index + 1),
130
+ ObjectType: objectName,
131
+ Fields: fields,
132
+ IsDeleted: false,
133
+ };
134
+ });
135
+ }
136
+ };
137
+ FileFeedConnector = __decorate([
138
+ RegisterClass(BaseIntegrationConnector, 'FileFeedConnector')
139
+ ], FileFeedConnector);
140
+ export { FileFeedConnector };
141
+ /**
142
+ * Parses a single CSV line handling quoted fields with commas.
143
+ * Supports:
144
+ * - Unquoted fields separated by commas
145
+ * - Quoted fields that may contain commas
146
+ * - Escaped quotes ("") inside quoted fields
147
+ * @param line - A single CSV line to parse
148
+ * @returns Array of field values
149
+ */
150
+ export function parseCsvLine(line) {
151
+ const fields = [];
152
+ let current = '';
153
+ let inQuotes = false;
154
+ let i = 0;
155
+ while (i < line.length) {
156
+ const char = line[i];
157
+ if (inQuotes) {
158
+ if (char === '"' && i + 1 < line.length && line[i + 1] === '"') {
159
+ current += '"';
160
+ i += 2;
161
+ }
162
+ else if (char === '"') {
163
+ inQuotes = false;
164
+ i++;
165
+ }
166
+ else {
167
+ current += char;
168
+ i++;
169
+ }
170
+ }
171
+ else {
172
+ if (char === '"') {
173
+ inQuotes = true;
174
+ i++;
175
+ }
176
+ else if (char === ',') {
177
+ fields.push(current.trim());
178
+ current = '';
179
+ i++;
180
+ }
181
+ else {
182
+ current += char;
183
+ i++;
184
+ }
185
+ }
186
+ }
187
+ fields.push(current.trim());
188
+ return fields;
189
+ }
190
+ //# sourceMappingURL=FileFeedConnector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileFeedConnector.js","sourceRoot":"","sources":["../src/FileFeedConnector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import { type UserInfo } from '@memberjunction/core';
2
+ import type { MJCompanyIntegrationEntity, MJIntegrationObjectEntity } from '@memberjunction/core-entities';
3
+ import { BaseRESTIntegrationConnector, type RESTAuthContext, type RESTResponse, type PaginationState, type PaginationType, type ConnectionTestResult, type DefaultFieldMapping, type DefaultIntegrationConfig } from '@memberjunction/integration-engine';
4
+ /** HubSpot property definition (from /crm/v3/properties/{objectType}) */
5
+ interface HubSpotPropertyDef {
6
+ name: string;
7
+ label: string;
8
+ type: string;
9
+ fieldType: string;
10
+ groupName: string;
11
+ description: string;
12
+ hasUniqueValue: boolean;
13
+ calculated: boolean;
14
+ externalOptions: boolean;
15
+ modificationMetadata?: {
16
+ readOnlyValue: boolean;
17
+ };
18
+ }
19
+ /**
20
+ * Connector for HubSpot CRM via the HubSpot REST API v3.
21
+ *
22
+ * Extends BaseRESTIntegrationConnector to leverage metadata-driven object/field
23
+ * discovery from IntegrationEngineBase cache and generic pagination handling.
24
+ *
25
+ * Uses Bearer token authentication (private app key or OAuth access token).
26
+ * Supports cursor-based pagination and automatic response flattening.
27
+ *
28
+ * DATA SAFETY: This connector is READ-ONLY. It never writes data back to HubSpot.
29
+ */
30
+ export declare class HubSpotConnector extends BaseRESTIntegrationConnector {
31
+ /** Timestamp of the last API request, used for throttling */
32
+ private lastRequestTime;
33
+ protected Authenticate(companyIntegration: MJCompanyIntegrationEntity): Promise<RESTAuthContext>;
34
+ protected BuildHeaders(auth: RESTAuthContext): Record<string, string>;
35
+ protected MakeHTTPRequest(_auth: RESTAuthContext, url: string, method: string, headers: Record<string, string>, _body?: unknown): Promise<RESTResponse>;
36
+ protected NormalizeResponse(rawBody: unknown, responseDataKey: string | null): Record<string, unknown>[];
37
+ protected ExtractPaginationInfo(rawBody: unknown, _paginationType: PaginationType, _currentPage: number, _currentOffset: number, _pageSize: number): PaginationState;
38
+ protected GetBaseURL(_companyIntegration: MJCompanyIntegrationEntity): string;
39
+ /**
40
+ * Overrides base pagination URL building to use HubSpot's parameter names.
41
+ * HubSpot uses `after` for cursor pagination (not `cursor`), and needs
42
+ * `limit` instead of `pageSize`. Also appends `properties` query param.
43
+ */
44
+ protected BuildPaginatedURL(basePath: string, obj: MJIntegrationObjectEntity, _page: number, _offset: number, cursor?: string): string;
45
+ /** Tests connectivity by authenticating and fetching 1 contact. */
46
+ TestConnection(companyIntegration: MJCompanyIntegrationEntity, _contextUser: UserInfo): Promise<ConnectionTestResult>;
47
+ GetDefaultFieldMappings(objectName: string, _entityName: string): DefaultFieldMapping[];
48
+ GetDefaultConfiguration(): DefaultIntegrationConfig;
49
+ /** Converts a HubSpot property definition to ExternalFieldSchema format */
50
+ MapPropertyToField(prop: HubSpotPropertyDef): {
51
+ Name: string;
52
+ Label: string;
53
+ DataType: string;
54
+ IsRequired: boolean;
55
+ IsUniqueKey: boolean;
56
+ IsReadOnly: boolean;
57
+ };
58
+ /** Maps HubSpot type + fieldType to a simplified data type string */
59
+ MapHubSpotType(type: string, fieldType: string): string;
60
+ /**
61
+ * Reads credentials from CompanyIntegration.CredentialID -> Credential.Values JSON,
62
+ * or falls back to CompanyIntegration Configuration JSON for backwards compat.
63
+ */
64
+ private LoadCredentials;
65
+ /** Loads credentials from a Credential entity by ID. */
66
+ private LoadFromCredentialEntity;
67
+ /** Parses a JSON string to extract HubSpot credentials. Returns null if no token found. */
68
+ private ParseCredentialJson;
69
+ /**
70
+ * Flattens a HubSpot CRM record from the nested format:
71
+ * { id, properties: { field1, field2 }, createdAt, updatedAt, archived }
72
+ * into a flat record with all properties at the top level,
73
+ * plus system fields (hs_object_id, createdAt, updatedAt, archived).
74
+ */
75
+ private FlattenHubSpotRecord;
76
+ /**
77
+ * Extracts the HubSpot object name from an API path.
78
+ * E.g., "/crm/v3/objects/contacts" -> "contacts"
79
+ */
80
+ private ExtractObjectNameFromPath;
81
+ /**
82
+ * Builds the `properties` query parameter for a HubSpot object type.
83
+ * Returns empty string if no properties are configured for the object.
84
+ */
85
+ private BuildPropertiesParam;
86
+ /** Executes an HTTP request with a timeout. */
87
+ private FetchWithTimeout;
88
+ /** Calculates retry delay from Retry-After header or exponential backoff. */
89
+ private CalculateRetryDelay;
90
+ /** Converts a fetch Response + parsed body into a RESTResponse. */
91
+ private BuildRESTResponse;
92
+ /** Returns a promise that resolves after the specified number of milliseconds. */
93
+ private Sleep;
94
+ }
95
+ export {};
96
+ //# sourceMappingURL=HubSpotConnector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HubSpotConnector.d.ts","sourceRoot":"","sources":["../src/HubSpotConnector.ts"],"names":[],"mappings":"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"}