@memberjunction/integration-connectors 5.51.0 → 6.1.0-edge.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +109 -277
  3. package/dist/index.d.ts +0 -36
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +7 -38
  6. package/dist/index.js.map +1 -1
  7. package/package.json +14 -19
  8. package/dist/AptifyConnector.d.ts +0 -95
  9. package/dist/AptifyConnector.d.ts.map +0 -1
  10. package/dist/AptifyConnector.js +0 -760
  11. package/dist/AptifyConnector.js.map +0 -1
  12. package/dist/BlackbaudConnector.d.ts +0 -64
  13. package/dist/BlackbaudConnector.d.ts.map +0 -1
  14. package/dist/BlackbaudConnector.js +0 -916
  15. package/dist/BlackbaudConnector.js.map +0 -1
  16. package/dist/ConstantContactConnector.d.ts +0 -137
  17. package/dist/ConstantContactConnector.d.ts.map +0 -1
  18. package/dist/ConstantContactConnector.js +0 -663
  19. package/dist/ConstantContactConnector.js.map +0 -1
  20. package/dist/CventConnector.d.ts +0 -146
  21. package/dist/CventConnector.d.ts.map +0 -1
  22. package/dist/CventConnector.js +0 -540
  23. package/dist/CventConnector.js.map +0 -1
  24. package/dist/DynamicsDataverseConnector.d.ts +0 -296
  25. package/dist/DynamicsDataverseConnector.d.ts.map +0 -1
  26. package/dist/DynamicsDataverseConnector.js +0 -790
  27. package/dist/DynamicsDataverseConnector.js.map +0 -1
  28. package/dist/FileFeedConnector.d.ts +0 -64
  29. package/dist/FileFeedConnector.d.ts.map +0 -1
  30. package/dist/FileFeedConnector.js +0 -190
  31. package/dist/FileFeedConnector.js.map +0 -1
  32. package/dist/FontevaConnector.d.ts +0 -153
  33. package/dist/FontevaConnector.d.ts.map +0 -1
  34. package/dist/FontevaConnector.js +0 -421
  35. package/dist/FontevaConnector.js.map +0 -1
  36. package/dist/GrowthZoneConnector.d.ts +0 -183
  37. package/dist/GrowthZoneConnector.d.ts.map +0 -1
  38. package/dist/GrowthZoneConnector.js +0 -702
  39. package/dist/GrowthZoneConnector.js.map +0 -1
  40. package/dist/HivebriteConnector.d.ts +0 -151
  41. package/dist/HivebriteConnector.d.ts.map +0 -1
  42. package/dist/HivebriteConnector.js +0 -533
  43. package/dist/HivebriteConnector.js.map +0 -1
  44. package/dist/HubSpotConnector.d.ts +0 -523
  45. package/dist/HubSpotConnector.d.ts.map +0 -1
  46. package/dist/HubSpotConnector.js +0 -3110
  47. package/dist/HubSpotConnector.js.map +0 -1
  48. package/dist/IMISConnector.d.ts +0 -171
  49. package/dist/IMISConnector.d.ts.map +0 -1
  50. package/dist/IMISConnector.js +0 -695
  51. package/dist/IMISConnector.js.map +0 -1
  52. package/dist/MJToMJConnector.d.ts +0 -279
  53. package/dist/MJToMJConnector.d.ts.map +0 -1
  54. package/dist/MJToMJConnector.js +0 -893
  55. package/dist/MJToMJConnector.js.map +0 -1
  56. package/dist/MagnetMailConnector.d.ts +0 -163
  57. package/dist/MagnetMailConnector.d.ts.map +0 -1
  58. package/dist/MagnetMailConnector.js +0 -823
  59. package/dist/MagnetMailConnector.js.map +0 -1
  60. package/dist/MailchimpConnector.d.ts +0 -197
  61. package/dist/MailchimpConnector.d.ts.map +0 -1
  62. package/dist/MailchimpConnector.js +0 -791
  63. package/dist/MailchimpConnector.js.map +0 -1
  64. package/dist/MemberSuiteConnector.d.ts +0 -185
  65. package/dist/MemberSuiteConnector.d.ts.map +0 -1
  66. package/dist/MemberSuiteConnector.js +0 -762
  67. package/dist/MemberSuiteConnector.js.map +0 -1
  68. package/dist/NeonCRMConnector.d.ts +0 -273
  69. package/dist/NeonCRMConnector.d.ts.map +0 -1
  70. package/dist/NeonCRMConnector.js +0 -1039
  71. package/dist/NeonCRMConnector.js.map +0 -1
  72. package/dist/NetForumConnector.d.ts +0 -170
  73. package/dist/NetForumConnector.d.ts.map +0 -1
  74. package/dist/NetForumConnector.js +0 -778
  75. package/dist/NetForumConnector.js.map +0 -1
  76. package/dist/NetSuiteConnector.d.ts +0 -136
  77. package/dist/NetSuiteConnector.d.ts.map +0 -1
  78. package/dist/NetSuiteConnector.js +0 -703
  79. package/dist/NetSuiteConnector.js.map +0 -1
  80. package/dist/NimbleAMSConnector.d.ts +0 -142
  81. package/dist/NimbleAMSConnector.d.ts.map +0 -1
  82. package/dist/NimbleAMSConnector.js +0 -703
  83. package/dist/NimbleAMSConnector.js.map +0 -1
  84. package/dist/NoviConnector.d.ts +0 -178
  85. package/dist/NoviConnector.d.ts.map +0 -1
  86. package/dist/NoviConnector.js +0 -566
  87. package/dist/NoviConnector.js.map +0 -1
  88. package/dist/ORCIDConnector.d.ts +0 -207
  89. package/dist/ORCIDConnector.d.ts.map +0 -1
  90. package/dist/ORCIDConnector.js +0 -708
  91. package/dist/ORCIDConnector.js.map +0 -1
  92. package/dist/OpenWaterConnector.d.ts +0 -173
  93. package/dist/OpenWaterConnector.d.ts.map +0 -1
  94. package/dist/OpenWaterConnector.js +0 -893
  95. package/dist/OpenWaterConnector.js.map +0 -1
  96. package/dist/PathLMSConnector.d.ts +0 -395
  97. package/dist/PathLMSConnector.d.ts.map +0 -1
  98. package/dist/PathLMSConnector.js +0 -1281
  99. package/dist/PathLMSConnector.js.map +0 -1
  100. package/dist/PheedLoopConnector.d.ts +0 -119
  101. package/dist/PheedLoopConnector.d.ts.map +0 -1
  102. package/dist/PheedLoopConnector.js +0 -429
  103. package/dist/PheedLoopConnector.js.map +0 -1
  104. package/dist/PropFuelConnector.d.ts +0 -153
  105. package/dist/PropFuelConnector.d.ts.map +0 -1
  106. package/dist/PropFuelConnector.js +0 -450
  107. package/dist/PropFuelConnector.js.map +0 -1
  108. package/dist/QuickBooksConnector.d.ts +0 -92
  109. package/dist/QuickBooksConnector.d.ts.map +0 -1
  110. package/dist/QuickBooksConnector.js +0 -713
  111. package/dist/QuickBooksConnector.js.map +0 -1
  112. package/dist/RasaConnector.d.ts +0 -152
  113. package/dist/RasaConnector.d.ts.map +0 -1
  114. package/dist/RasaConnector.js +0 -929
  115. package/dist/RasaConnector.js.map +0 -1
  116. package/dist/Reach360Connector.d.ts +0 -175
  117. package/dist/Reach360Connector.d.ts.map +0 -1
  118. package/dist/Reach360Connector.js +0 -796
  119. package/dist/Reach360Connector.js.map +0 -1
  120. package/dist/RelationalDBConnector.d.ts +0 -108
  121. package/dist/RelationalDBConnector.d.ts.map +0 -1
  122. package/dist/RelationalDBConnector.js +0 -228
  123. package/dist/RelationalDBConnector.js.map +0 -1
  124. package/dist/RhythmConnector.d.ts +0 -188
  125. package/dist/RhythmConnector.d.ts.map +0 -1
  126. package/dist/RhythmConnector.js +0 -784
  127. package/dist/RhythmConnector.js.map +0 -1
  128. package/dist/SageIntacctConnector.d.ts +0 -337
  129. package/dist/SageIntacctConnector.d.ts.map +0 -1
  130. package/dist/SageIntacctConnector.js +0 -2081
  131. package/dist/SageIntacctConnector.js.map +0 -1
  132. package/dist/SalesforceConnector.d.ts +0 -461
  133. package/dist/SalesforceConnector.d.ts.map +0 -1
  134. package/dist/SalesforceConnector.js +0 -1862
  135. package/dist/SalesforceConnector.js.map +0 -1
  136. package/dist/SharePointConnector.d.ts +0 -188
  137. package/dist/SharePointConnector.d.ts.map +0 -1
  138. package/dist/SharePointConnector.js +0 -716
  139. package/dist/SharePointConnector.js.map +0 -1
  140. package/dist/WicketConnector.d.ts +0 -252
  141. package/dist/WicketConnector.d.ts.map +0 -1
  142. package/dist/WicketConnector.js +0 -938
  143. package/dist/WicketConnector.js.map +0 -1
  144. package/dist/WildApricotConnector.d.ts +0 -201
  145. package/dist/WildApricotConnector.d.ts.map +0 -1
  146. package/dist/WildApricotConnector.js +0 -1129
  147. package/dist/WildApricotConnector.js.map +0 -1
  148. package/dist/YourMembershipConnector.d.ts +0 -349
  149. package/dist/YourMembershipConnector.d.ts.map +0 -1
  150. package/dist/YourMembershipConnector.js +0 -4062
  151. package/dist/YourMembershipConnector.js.map +0 -1
  152. package/dist/generate-integration-actions.d.ts +0 -23
  153. package/dist/generate-integration-actions.d.ts.map +0 -1
  154. package/dist/generate-integration-actions.js +0 -201
  155. package/dist/generate-integration-actions.js.map +0 -1
@@ -1,938 +0,0 @@
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 { createHmac } from 'node:crypto';
8
- import { RegisterClass } from '@memberjunction/global';
9
- import { Metadata } from '@memberjunction/core';
10
- import { BaseIntegrationConnector, BaseRESTIntegrationConnector, } from '@memberjunction/integration-engine';
11
- // ─── Constants ───────────────────────────────────────────────────────
12
- /** Default JWT expiration window in seconds (5 minutes) */
13
- const JWT_EXPIRATION_SECONDS = 300;
14
- /** Maximum retries for rate-limited or transient failures */
15
- const MAX_RETRIES = 5;
16
- /** HTTP request timeout in milliseconds */
17
- const REQUEST_TIMEOUT_MS = 30_000;
18
- /** Minimum milliseconds between API requests (Wicket: 500 req/min ≈ 120ms/req) */
19
- const MIN_REQUEST_INTERVAL_MS = 125;
20
- /** Default page size for Wicket API requests */
21
- const DEFAULT_PAGE_SIZE = 100;
22
- /**
23
- * Maps integration object names to their JSON:API `type` values.
24
- * Used when constructing JSON:API request bodies for CRUD operations.
25
- */
26
- const WICKET_JSONAPI_TYPES = {
27
- people: 'people',
28
- organizations: 'organizations',
29
- connections: 'connections',
30
- groups: 'groups',
31
- group_members: 'group_members',
32
- person_memberships: 'person_memberships',
33
- organization_memberships: 'organization_memberships',
34
- memberships: 'memberships',
35
- touchpoints: 'touchpoints',
36
- emails: 'emails',
37
- phones: 'phones',
38
- addresses: 'addresses',
39
- roles: 'roles',
40
- user_identities: 'user_identities',
41
- resource_tags: 'resource_tags',
42
- people_emails: 'emails',
43
- people_phones: 'phones',
44
- people_addresses: 'addresses',
45
- org_emails: 'emails',
46
- org_phones: 'phones',
47
- org_addresses: 'addresses',
48
- };
49
- /**
50
- * Objects that support POST /query endpoints for advanced search.
51
- * These accept nested filters, OR logic, and data_fields searching.
52
- */
53
- const SEARCHABLE_OBJECTS = new Set([
54
- 'people',
55
- 'organizations',
56
- 'connections',
57
- 'groups',
58
- 'group_members',
59
- 'person_memberships',
60
- 'organization_memberships',
61
- ]);
62
- /**
63
- * Objects that are immutable (no update/delete allowed).
64
- * Touchpoints are audit trail records by design.
65
- */
66
- const IMMUTABLE_OBJECTS = new Set(['touchpoints']);
67
- // ─── JWT Helper ──────────────────────────────────────────────────────
68
- /**
69
- * Encodes a buffer or string to base64url format (RFC 7515).
70
- * Replaces `+` with `-`, `/` with `_`, and strips trailing `=` padding.
71
- */
72
- function base64url(input) {
73
- const b64 = Buffer.from(input, 'utf8').toString('base64');
74
- return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
75
- }
76
- /** Encodes a Buffer to base64url format. */
77
- function base64urlBuffer(input) {
78
- return input.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
79
- }
80
- /**
81
- * Generates a signed JWT token for Wicket API authentication.
82
- * Uses HS256 (HMAC-SHA256) algorithm as required by the Wicket API.
83
- *
84
- * @param credentials - Wicket credentials containing API secret, admin UUID, and tenant
85
- * @returns Signed JWT string
86
- */
87
- /** Resolves the Wicket API base URL from credentials (direct URL or tenant-derived). */
88
- function resolveWicketBaseURL(credentials) {
89
- if (credentials.ApiUrl) {
90
- // Strip trailing slash for consistency
91
- return credentials.ApiUrl.replace(/\/+$/, '');
92
- }
93
- if (credentials.TenantName) {
94
- return `https://${credentials.TenantName}-api.wicketcloud.com`;
95
- }
96
- throw new Error('Cannot determine Wicket API base URL. Provide apiUrl or tenantName in credentials.');
97
- }
98
- function generateWicketJWT(credentials) {
99
- const now = Math.floor(Date.now() / 1000);
100
- const apiUrl = resolveWicketBaseURL(credentials);
101
- const header = { alg: 'HS256', typ: 'JWT' };
102
- const payload = {
103
- exp: now + JWT_EXPIRATION_SECONDS,
104
- sub: credentials.AdminUserUUID,
105
- aud: apiUrl,
106
- };
107
- if (credentials.IssuerDomain) {
108
- payload['iss'] = credentials.IssuerDomain;
109
- }
110
- const encodedHeader = base64url(JSON.stringify(header));
111
- const encodedPayload = base64url(JSON.stringify(payload));
112
- const signingInput = `${encodedHeader}.${encodedPayload}`;
113
- const signature = createHmac('sha256', credentials.ApiSecret)
114
- .update(signingInput)
115
- .digest();
116
- return `${signingInput}.${base64urlBuffer(signature)}`;
117
- }
118
- // ─── Connector ───────────────────────────────────────────────────────
119
- /**
120
- * Connector for the Wicket membership management platform via its JSON:API REST API.
121
- *
122
- * Extends BaseRESTIntegrationConnector to leverage metadata-driven object/field
123
- * discovery, generic pagination handling, and template variable resolution.
124
- *
125
- * Supports:
126
- * - JWT Bearer authentication (HS256, generated from API secret)
127
- * - JSON:API response format normalization (flattens `data[].attributes`)
128
- * - Page-number pagination (`page[number]`, `page[size]`)
129
- * - Timestamp-based watermarks via `filter[updated_at_gteq]`
130
- * - Full CRUD operations (bidirectional sync)
131
- * - Advanced search via POST /query endpoints
132
- * - Rate limiting (500 req/min, auto-throttle + retry on 429)
133
- *
134
- * @see https://wicketapi.docs.apiary.io/
135
- */
136
- let WicketConnector = class WicketConnector extends BaseRESTIntegrationConnector {
137
- constructor() {
138
- super(...arguments);
139
- /** Timestamp of the last API request, used for throttling */
140
- this.lastRequestTime = 0;
141
- /** Cached auth context to avoid regenerating JWT on every request */
142
- this.cachedAuth = null;
143
- /** Expiration time of the cached auth context */
144
- this.cachedAuthExpiresAt = 0;
145
- }
146
- // ─── Identity ────────────────────────────────────────────────────
147
- // Required for the three-way invariant (metadata.Name = connector
148
- // IntegrationName = @RegisterClass tag). Base class default
149
- // (this.constructor.name) would return 'WicketConnector' which would
150
- // miss the metadata lookup for 'Wicket'.
151
- get IntegrationName() { return 'Wicket'; }
152
- // ─── Capability Getters ──────────────────────────────────────────
153
- get SupportsCreate() { return true; }
154
- get SupportsUpdate() { return true; }
155
- get SupportsDelete() { return true; }
156
- get SupportsSearch() { return true; }
157
- // ─── BaseRESTIntegrationConnector abstract implementations ───────
158
- async Authenticate(companyIntegration, contextUser) {
159
- // Return cached auth if still valid (with 30s safety margin)
160
- const now = Math.floor(Date.now() / 1000);
161
- if (this.cachedAuth && this.cachedAuthExpiresAt > now + 30) {
162
- return this.cachedAuth;
163
- }
164
- const credentials = await this.LoadCredentials(companyIntegration, contextUser);
165
- const token = generateWicketJWT(credentials);
166
- const baseURL = resolveWicketBaseURL(credentials);
167
- const auth = {
168
- Token: token,
169
- Credentials: credentials,
170
- BaseURL: baseURL,
171
- };
172
- this.cachedAuth = auth;
173
- this.cachedAuthExpiresAt = now + JWT_EXPIRATION_SECONDS;
174
- return auth;
175
- }
176
- BuildHeaders(auth) {
177
- return {
178
- 'Authorization': `Bearer ${auth.Token}`,
179
- 'Accept': 'application/json',
180
- 'Content-Type': 'application/vnd.api+json',
181
- };
182
- }
183
- async MakeHTTPRequest(_auth, url, method, headers, body) {
184
- await this.ThrottleRequest();
185
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
186
- const response = await this.FetchWithTimeout(url, method, headers, body);
187
- this.lastRequestTime = Date.now();
188
- if (response.status === 429) {
189
- const delayMs = this.CalculateRetryDelay(response, attempt);
190
- console.warn(`[Wicket] Rate limited (429), retrying in ${delayMs}ms ` +
191
- `(attempt ${attempt + 1}/${MAX_RETRIES})`);
192
- await this.Sleep(delayMs);
193
- continue;
194
- }
195
- const responseBody = await this.ParseResponseBody(response);
196
- return this.BuildRESTResponse(response, responseBody);
197
- }
198
- throw new Error(`Wicket API request failed after ${MAX_RETRIES} retries: ${url}`);
199
- }
200
- /**
201
- * Normalizes a JSON:API response into flat record objects.
202
- *
203
- * Wicket returns data in JSON:API format:
204
- * ```json
205
- * {
206
- * "data": [{ "id": "uuid", "type": "people", "attributes": {...}, "relationships": {...} }],
207
- * "included": [...],
208
- * "meta": { "page": { "number": 1, "size": 25, "total_count": 500 } }
209
- * }
210
- * ```
211
- *
212
- * This method flattens each record to: `{ uuid: "...", given_name: "...", ... }`
213
- */
214
- NormalizeResponse(rawBody, responseDataKey) {
215
- const body = rawBody;
216
- // Use responseDataKey if specified, otherwise default to "data"
217
- const dataKey = responseDataKey ?? 'data';
218
- const data = body[dataKey];
219
- if (!data)
220
- return [];
221
- // Handle single-object responses (e.g., GET /people/{id})
222
- if (!Array.isArray(data)) {
223
- return [this.FlattenJsonApiRecord(data)];
224
- }
225
- return data.map(r => this.FlattenJsonApiRecord(r));
226
- }
227
- /**
228
- * Extracts pagination state from Wicket's JSON:API meta response.
229
- *
230
- * Wicket returns pagination info in:
231
- * ```json
232
- * { "meta": { "page": { "number": 1, "size": 25, "total_count": 500 } } }
233
- * ```
234
- */
235
- ExtractPaginationInfo(rawBody, _paginationType, currentPage, _currentOffset, pageSize) {
236
- const body = rawBody;
237
- const meta = body['meta'];
238
- const pageInfo = meta?.['page'];
239
- if (!pageInfo?.total_count) {
240
- return { HasMore: false };
241
- }
242
- const totalPages = Math.ceil(pageInfo.total_count / (pageInfo.size ?? pageSize));
243
- const hasMore = currentPage < totalPages;
244
- return {
245
- HasMore: hasMore,
246
- NextPage: hasMore ? currentPage + 1 : undefined,
247
- TotalRecords: pageInfo.total_count,
248
- };
249
- }
250
- GetBaseURL(companyIntegration) {
251
- // If we have a cached auth context, use its base URL
252
- if (this.cachedAuth) {
253
- return this.cachedAuth.BaseURL;
254
- }
255
- // Fallback: parse from configuration JSON
256
- const configJson = companyIntegration.Get('Configuration');
257
- if (configJson) {
258
- try {
259
- const config = JSON.parse(configJson);
260
- const apiUrl = config['apiUrl'] ?? config['ApiUrl'];
261
- if (apiUrl)
262
- return apiUrl.replace(/\/+$/, '');
263
- const tenant = config['tenantName'] ?? config['TenantName'];
264
- if (tenant)
265
- return `https://${tenant}-api.wicketcloud.com`;
266
- }
267
- catch { /* fall through */ }
268
- }
269
- throw new Error('Cannot determine Wicket API base URL. Provide apiUrl or tenantName in credentials or configuration.');
270
- }
271
- // ─── Wicket-specific pagination ──────────────────────────────────
272
- /**
273
- * Overrides base pagination URL building to use Wicket's JSON:API parameter format.
274
- * Wicket uses `page[number]` and `page[size]` (bracket notation).
275
- */
276
- BuildPaginatedURL(basePath, obj, page, _offset, _cursor) {
277
- const separator = basePath.includes('?') ? '&' : '?';
278
- const pageSize = obj.DefaultPageSize || DEFAULT_PAGE_SIZE;
279
- return `${basePath}${separator}page[number]=${page}&page[size]=${pageSize}`;
280
- }
281
- // ─── FetchChanges override for watermark support ─────────────────
282
- /**
283
- * Overrides FetchChanges to inject Wicket's timestamp-based watermark filter.
284
- * Uses `filter[updated_at_gteq]` for incremental sync.
285
- */
286
- async FetchChanges(ctx) {
287
- // If watermark is provided, inject it as a filter parameter
288
- if (ctx.WatermarkValue) {
289
- const currentConfig = ctx.CompanyIntegration.Get('Configuration');
290
- const config = currentConfig ? JSON.parse(currentConfig) : {};
291
- // Store original and inject watermark filter into request
292
- const originalWatermarkFilter = config['_watermarkFilter'];
293
- config['_watermarkFilter'] = ctx.WatermarkValue;
294
- // We'll use this in AppendDefaultQueryParams override behavior
295
- }
296
- const result = await super.FetchChanges(ctx);
297
- // Set new watermark to current timestamp if we got records
298
- if (result.Records.length > 0) {
299
- result.NewWatermarkValue = new Date().toISOString();
300
- }
301
- return result;
302
- }
303
- // ─── DiscoverObjects ──────────────────────────────────────────────
304
- /**
305
- * Canonical Wicket top-level objects PLUS live-discovered per-tenant
306
- * additional-info schemas (Wicket's "custom tables" concept — each
307
- * org/event/etc can extend their data model via JSON-Schema-Forms).
308
- *
309
- * Standard objects are merged with what's already persisted so curated
310
- * customizations aren't lost. The additional-info schemas live at
311
- * `/additional_info_schemas` (Wicket exposes them as queryable
312
- * resources); each schema becomes a Discovered IO.
313
- */
314
- async DiscoverObjects(companyIntegration, contextUser) {
315
- const persisted = await super.DiscoverObjects(companyIntegration, contextUser);
316
- const canonical = [
317
- { Name: 'people', Label: 'People', Description: 'Member/person records. /people.', SupportsIncrementalSync: true, SupportsWrite: true },
318
- { Name: 'organizations', Label: 'Organizations', Description: 'Organization records. /organizations.', SupportsIncrementalSync: true, SupportsWrite: true },
319
- { Name: 'person_memberships', Label: 'Person Memberships', Description: 'Per-person membership assignments. /person_memberships.', SupportsIncrementalSync: true, SupportsWrite: true },
320
- { Name: 'organization_memberships', Label: 'Organization Memberships', Description: 'Per-organization membership assignments. /organization_memberships.', SupportsIncrementalSync: true, SupportsWrite: true },
321
- { Name: 'memberships', Label: 'Membership Definitions', Description: 'Tenant-defined membership types/categories. /memberships.', SupportsIncrementalSync: false, SupportsWrite: true },
322
- { Name: 'connections', Label: 'Connections', Description: 'Relationships between people / between people and organizations. /connections.', SupportsIncrementalSync: true, SupportsWrite: true },
323
- { Name: 'groups', Label: 'Groups', Description: 'Group records (committees, chapters, etc). /groups.', SupportsIncrementalSync: true, SupportsWrite: true },
324
- { Name: 'group_members', Label: 'Group Members', Description: 'Group membership entries. /group_members.', SupportsIncrementalSync: true, SupportsWrite: true },
325
- { Name: 'segments', Label: 'Segments', Description: 'Saved audience segments. /segments.', SupportsIncrementalSync: false, SupportsWrite: false },
326
- { Name: 'taxonomies', Label: 'Taxonomies', Description: 'Tenant-defined taxonomy categories. /taxonomies.', SupportsIncrementalSync: false, SupportsWrite: true },
327
- { Name: 'additional_info_schemas', Label: 'Additional Info Schemas', Description: 'Tenant-defined JSON-Schema extensions for custom attributes (per-tenant "custom tables"). /additional_info_schemas.', SupportsIncrementalSync: false, SupportsWrite: false },
328
- ];
329
- const byName = new Map();
330
- for (const o of persisted)
331
- byName.set(o.Name.toLowerCase(), o);
332
- for (const c of canonical) {
333
- if (!byName.has(c.Name.toLowerCase()))
334
- byName.set(c.Name.toLowerCase(), c);
335
- }
336
- // Live-discover per-tenant additional-info schemas — each one is a
337
- // custom table the operator has defined. Best-effort: probe failure
338
- // is non-fatal (canonical set still returned).
339
- try {
340
- const auth = await this.Authenticate(companyIntegration, contextUser);
341
- const headers = this.BuildHeaders(auth);
342
- const baseURL = this.GetBaseURL(companyIntegration);
343
- const resp = await this.MakeHTTPRequest(auth, `${baseURL}/additional_info_schemas`, 'GET', headers);
344
- if (resp.Status === 200) {
345
- const body = resp.Body;
346
- for (const sch of body.data ?? []) {
347
- const slug = sch.attributes?.slug ?? sch.attributes?.name ?? sch.id;
348
- if (!slug)
349
- continue;
350
- const ioName = `additional_info.${slug}`;
351
- if (!byName.has(ioName.toLowerCase())) {
352
- byName.set(ioName.toLowerCase(), {
353
- Name: ioName,
354
- Label: sch.attributes?.name ?? slug,
355
- Description: sch.attributes?.description ?? `Tenant-defined additional-info schema (Wicket custom table). /additional_info_schemas/${sch.id}.`,
356
- SupportsIncrementalSync: false,
357
- SupportsWrite: false,
358
- });
359
- }
360
- }
361
- }
362
- }
363
- catch (err) {
364
- console.warn(`[Wicket] additional_info_schemas live discovery failed (non-fatal, canonical set still returned): ${err instanceof Error ? err.message : err}`);
365
- }
366
- return [...byName.values()];
367
- }
368
- // ─── TestConnection ──────────────────────────────────────────────
369
- /** Tests connectivity by authenticating and fetching 1 person record. */
370
- async TestConnection(companyIntegration, contextUser) {
371
- try {
372
- const auth = await this.Authenticate(companyIntegration, contextUser);
373
- const headers = this.BuildHeaders(auth);
374
- const baseURL = this.GetBaseURL(companyIntegration);
375
- const response = await this.MakeHTTPRequest(auth, `${baseURL}/people?page[size]=1`, 'GET', headers);
376
- if (response.Status >= 200 && response.Status < 300) {
377
- return {
378
- Success: true,
379
- Message: 'Successfully connected to Wicket API',
380
- ServerVersion: 'Wicket JSON:API v1',
381
- };
382
- }
383
- const bodyPreview = this.FormatBodyPreview(response.Body);
384
- return {
385
- Success: false,
386
- Message: `Wicket API returned ${response.Status}: ${bodyPreview}`,
387
- };
388
- }
389
- catch (err) {
390
- const message = err instanceof Error ? err.message : String(err);
391
- return { Success: false, Message: `Connection failed: ${message}` };
392
- }
393
- }
394
- // ─── GetDefaultFieldMappings ─────────────────────────────────────
395
- GetDefaultFieldMappings(objectName, _entityName) {
396
- switch (objectName) {
397
- case 'people':
398
- return this.GetPeopleFieldMappings();
399
- case 'organizations':
400
- return this.GetOrganizationFieldMappings();
401
- case 'person_memberships':
402
- return this.GetPersonMembershipFieldMappings();
403
- case 'connections':
404
- return this.GetConnectionFieldMappings();
405
- default:
406
- return [];
407
- }
408
- }
409
- // ─── Default Configuration ───────────────────────────────────────
410
- GetDefaultConfiguration() {
411
- return {
412
- DefaultSchemaName: 'Wicket',
413
- DefaultObjects: [],
414
- };
415
- }
416
- // ─── CRUD Operations (bidirectional sync support) ────────────────
417
- /**
418
- * Creates a new record in the Wicket API.
419
- *
420
- * @param companyIntegration - Company integration with credentials
421
- * @param objectName - Wicket object type (e.g., "people", "organizations")
422
- * @param attributes - Field values for the new record
423
- * @param contextUser - User context for authorization
424
- * @param relationships - Optional JSON:API relationships to include
425
- * @returns CRUD result with the created record's external ID
426
- */
427
- async CreateRecord(ctx) {
428
- const companyIntegration = ctx.CompanyIntegration;
429
- const objectName = ctx.ObjectName;
430
- this.ValidateWriteAllowed(objectName);
431
- const auth = await this.Authenticate(companyIntegration, ctx.ContextUser);
432
- const headers = this.BuildHeaders(auth);
433
- const baseURL = this.GetBaseURL(companyIntegration);
434
- const apiPath = this.ResolveObjectAPIPath(objectName);
435
- const body = this.BuildJsonApiBody(objectName, ctx.Attributes, ctx.Relationships);
436
- try {
437
- const response = await this.MakeHTTPRequest(auth, `${baseURL}${apiPath}`, 'POST', headers, body);
438
- return this.ParseCRUDResponse(response, 'create', objectName);
439
- }
440
- catch (err) {
441
- return this.BuildCRUDError(err);
442
- }
443
- }
444
- /**
445
- * Updates an existing record in the Wicket API.
446
- *
447
- * @param companyIntegration - Company integration with credentials
448
- * @param objectName - Wicket object type
449
- * @param externalID - UUID of the record to update
450
- * @param attributes - Field values to update
451
- * @param contextUser - User context for authorization
452
- * @param relationships - Optional JSON:API relationships to update
453
- * @returns CRUD result indicating success or failure
454
- */
455
- async UpdateRecord(ctx) {
456
- const companyIntegration = ctx.CompanyIntegration;
457
- const objectName = ctx.ObjectName;
458
- this.ValidateWriteAllowed(objectName);
459
- const auth = await this.Authenticate(companyIntegration, ctx.ContextUser);
460
- const headers = this.BuildHeaders(auth);
461
- const baseURL = this.GetBaseURL(companyIntegration);
462
- const apiPath = this.ResolveObjectAPIPath(objectName);
463
- const body = this.BuildJsonApiBody(objectName, ctx.Attributes, ctx.Relationships, ctx.ExternalID);
464
- try {
465
- const response = await this.MakeHTTPRequest(auth, `${baseURL}${apiPath}/${ctx.ExternalID}`, 'PATCH', headers, body);
466
- return this.ParseCRUDResponse(response, 'update');
467
- }
468
- catch (err) {
469
- return this.BuildCRUDError(err);
470
- }
471
- }
472
- /**
473
- * Deletes a record from the Wicket API.
474
- *
475
- * @param companyIntegration - Company integration with credentials
476
- * @param objectName - Wicket object type
477
- * @param externalID - UUID of the record to delete
478
- * @param contextUser - User context for authorization
479
- * @returns CRUD result indicating success or failure
480
- */
481
- async DeleteRecord(ctx) {
482
- const companyIntegration = ctx.CompanyIntegration;
483
- const objectName = ctx.ObjectName;
484
- this.ValidateWriteAllowed(objectName);
485
- this.ValidateDeleteAllowed(objectName);
486
- const auth = await this.Authenticate(companyIntegration, ctx.ContextUser);
487
- const headers = this.BuildHeaders(auth);
488
- const baseURL = this.GetBaseURL(companyIntegration);
489
- const apiPath = this.ResolveObjectAPIPath(objectName);
490
- try {
491
- const response = await this.MakeHTTPRequest(auth, `${baseURL}${apiPath}/${ctx.ExternalID}`, 'DELETE', headers);
492
- return {
493
- Success: response.Status === 204 || (response.Status >= 200 && response.Status < 300),
494
- ExternalID: ctx.ExternalID,
495
- StatusCode: response.Status,
496
- };
497
- }
498
- catch (err) {
499
- return this.BuildCRUDError(err);
500
- }
501
- }
502
- /**
503
- * Retrieves a single record from the Wicket API by ID.
504
- *
505
- * @param companyIntegration - Company integration with credentials
506
- * @param objectName - Wicket object type
507
- * @param externalID - UUID of the record to retrieve
508
- * @param contextUser - User context for authorization
509
- * @returns The record as an ExternalRecord, or null if not found
510
- */
511
- async GetRecord(ctx) {
512
- const companyIntegration = ctx.CompanyIntegration;
513
- const objectName = ctx.ObjectName;
514
- const auth = await this.Authenticate(companyIntegration, ctx.ContextUser);
515
- const headers = this.BuildHeaders(auth);
516
- const baseURL = this.GetBaseURL(companyIntegration);
517
- const apiPath = this.ResolveObjectAPIPath(objectName);
518
- try {
519
- const response = await this.MakeHTTPRequest(auth, `${baseURL}${apiPath}/${ctx.ExternalID}`, 'GET', headers);
520
- if (response.Status === 404)
521
- return null;
522
- if (response.Status < 200 || response.Status >= 300)
523
- return null;
524
- const records = this.NormalizeResponse(response.Body, 'data');
525
- if (records.length === 0)
526
- return null;
527
- return {
528
- ExternalID: ctx.ExternalID,
529
- ObjectType: objectName,
530
- Fields: records[0],
531
- };
532
- }
533
- catch {
534
- return null;
535
- }
536
- }
537
- // ─── Search Operations ───────────────────────────────────────────
538
- /**
539
- * Searches for records using Wicket's POST /query endpoints.
540
- *
541
- * Supports advanced filtering with predicates:
542
- * - Equality: `given_name_eq`, `status_not_eq`
543
- * - Comparison: `created_at_gteq`, `updated_at_lt`
544
- * - Pattern: `email_cont`, `name_start`
545
- * - Null checks: `phone_present`, `address_blank`
546
- * - Array: `status_in` (comma-separated values)
547
- *
548
- * @param companyIntegration - Company integration with credentials
549
- * @param objectName - Wicket object type (must be in SEARCHABLE_OBJECTS)
550
- * @param options - Search filters, sorting, and pagination
551
- * @param contextUser - User context for authorization
552
- * @returns Search results with records, total count, and pagination info
553
- */
554
- async SearchRecords(ctx) {
555
- const companyIntegration = ctx.CompanyIntegration;
556
- const objectName = ctx.ObjectName;
557
- if (!SEARCHABLE_OBJECTS.has(objectName)) {
558
- throw new Error(`Search is not supported for "${objectName}". ` +
559
- `Searchable objects: ${[...SEARCHABLE_OBJECTS].join(', ')}`);
560
- }
561
- const auth = await this.Authenticate(companyIntegration, ctx.ContextUser);
562
- const headers = this.BuildHeaders(auth);
563
- const baseURL = this.GetBaseURL(companyIntegration);
564
- const wicketOptions = {
565
- Filters: ctx.Filters,
566
- Sort: ctx.Sort,
567
- Page: ctx.Page,
568
- PageSize: ctx.PageSize,
569
- };
570
- const queryBody = this.BuildSearchQueryBody(wicketOptions);
571
- const response = await this.MakeHTTPRequest(auth, `${baseURL}/${objectName}/query`, 'POST', headers, queryBody);
572
- return this.ParseSearchResponse(response, objectName);
573
- }
574
- // ─── Default Field Mapping Builders ──────────────────────────────
575
- GetPeopleFieldMappings() {
576
- return [
577
- { SourceFieldName: 'uuid', DestinationFieldName: 'ExternalID', IsKeyField: true },
578
- { SourceFieldName: 'given_name', DestinationFieldName: 'FirstName' },
579
- { SourceFieldName: 'family_name', DestinationFieldName: 'LastName' },
580
- { SourceFieldName: 'full_name', DestinationFieldName: 'FullName' },
581
- { SourceFieldName: 'job_title', DestinationFieldName: 'JobTitle' },
582
- { SourceFieldName: 'gender', DestinationFieldName: 'Gender' },
583
- { SourceFieldName: 'birth_date', DestinationFieldName: 'BirthDate' },
584
- { SourceFieldName: 'language', DestinationFieldName: 'Language' },
585
- { SourceFieldName: 'membership_number', DestinationFieldName: 'MembershipNumber' },
586
- ];
587
- }
588
- GetOrganizationFieldMappings() {
589
- return [
590
- { SourceFieldName: 'uuid', DestinationFieldName: 'ExternalID', IsKeyField: true },
591
- { SourceFieldName: 'legal_name', DestinationFieldName: 'Name' },
592
- { SourceFieldName: 'alternate_name', DestinationFieldName: 'AlternateName' },
593
- { SourceFieldName: 'description', DestinationFieldName: 'Description' },
594
- { SourceFieldName: 'identifying_number', DestinationFieldName: 'IdentifyingNumber' },
595
- { SourceFieldName: 'type', DestinationFieldName: 'OrganizationType' },
596
- ];
597
- }
598
- GetPersonMembershipFieldMappings() {
599
- return [
600
- { SourceFieldName: 'uuid', DestinationFieldName: 'ExternalID', IsKeyField: true },
601
- { SourceFieldName: 'starts_at', DestinationFieldName: 'StartDate' },
602
- { SourceFieldName: 'ends_at', DestinationFieldName: 'EndDate' },
603
- ];
604
- }
605
- GetConnectionFieldMappings() {
606
- return [
607
- { SourceFieldName: 'uuid', DestinationFieldName: 'ExternalID', IsKeyField: true },
608
- { SourceFieldName: 'type', DestinationFieldName: 'ConnectionType' },
609
- ];
610
- }
611
- // ─── Credential Management ───────────────────────────────────────
612
- /**
613
- * Reads Wicket credentials from the linked Credential entity,
614
- * or falls back to CompanyIntegration Configuration JSON.
615
- */
616
- async LoadCredentials(companyIntegration, contextUser) {
617
- const credentialID = companyIntegration.Get('CredentialID');
618
- if (credentialID) {
619
- const creds = await this.LoadFromCredentialEntity(credentialID, contextUser);
620
- if (creds)
621
- return creds;
622
- }
623
- const configJson = companyIntegration.Get('Configuration');
624
- if (configJson) {
625
- const creds = this.ParseCredentialJson(configJson);
626
- if (creds)
627
- return creds;
628
- }
629
- throw new Error('No Wicket credentials found. Attach a credential with apiSecret, adminUserUUID, ' +
630
- 'and either apiUrl or tenantName. Or set Configuration JSON on the CompanyIntegration.');
631
- }
632
- /** Loads credentials from a Credential entity by ID. */
633
- async LoadFromCredentialEntity(credentialID, contextUser, provider) {
634
- const md = provider ?? new Metadata();
635
- const credential = await md.GetEntityObject('MJ: Credentials', contextUser);
636
- const loaded = await credential.Load(credentialID);
637
- if (!loaded || !credential.Values)
638
- return null;
639
- return this.ParseCredentialJson(credential.Values);
640
- }
641
- /** Parses a JSON string to extract Wicket credentials. Returns null if required fields missing. */
642
- ParseCredentialJson(json) {
643
- try {
644
- const parsed = JSON.parse(json);
645
- const apiSecret = parsed['apiSecret'] ?? parsed['ApiSecret'] ?? parsed['api_secret'];
646
- const adminUUID = parsed['adminUserUUID'] ?? parsed['AdminUserUUID'] ?? parsed['admin_user_uuid'];
647
- const tenant = parsed['tenantName'] ?? parsed['TenantName'] ?? parsed['tenant_name'] ?? null;
648
- const apiUrl = parsed['apiUrl'] ?? parsed['ApiUrl'] ?? parsed['api_url'] ?? null;
649
- if (!apiSecret || !adminUUID)
650
- return null;
651
- // Need either a direct API URL or a tenant name to derive the URL
652
- if (!apiUrl && !tenant)
653
- return null;
654
- return {
655
- ApiSecret: apiSecret,
656
- AdminUserUUID: adminUUID,
657
- TenantName: tenant,
658
- ApiUrl: apiUrl,
659
- IssuerDomain: parsed['issuerDomain'] ?? parsed['IssuerDomain'] ?? null,
660
- };
661
- }
662
- catch {
663
- return null;
664
- }
665
- }
666
- // ─── JSON:API Body Builders ──────────────────────────────────────
667
- /**
668
- * Builds a JSON:API-compliant request body for create/update operations.
669
- *
670
- * Output format:
671
- * ```json
672
- * {
673
- * "data": {
674
- * "type": "people",
675
- * "attributes": { "given_name": "Jane", ... },
676
- * "relationships": { ... }
677
- * }
678
- * }
679
- * ```
680
- */
681
- BuildJsonApiBody(objectName, attributes, relationships, externalID) {
682
- const jsonApiType = WICKET_JSONAPI_TYPES[objectName] ?? objectName;
683
- const data = {
684
- type: jsonApiType,
685
- attributes: this.StripSystemFields(attributes),
686
- };
687
- if (externalID) {
688
- data['id'] = externalID;
689
- }
690
- if (relationships && Object.keys(relationships).length > 0) {
691
- data['relationships'] = relationships;
692
- }
693
- return { data };
694
- }
695
- /** Removes system-managed fields from attributes before sending to Wicket. */
696
- StripSystemFields(attributes) {
697
- const systemFields = new Set(['uuid', 'id', 'created_at', 'updated_at', 'slug']);
698
- const cleaned = {};
699
- for (const [key, value] of Object.entries(attributes)) {
700
- if (!systemFields.has(key)) {
701
- cleaned[key] = value;
702
- }
703
- }
704
- return cleaned;
705
- }
706
- /** Builds the POST body for a Wicket /query search endpoint. */
707
- BuildSearchQueryBody(options) {
708
- const body = {
709
- filter: options.Filters,
710
- page: {
711
- number: options.Page ?? 1,
712
- size: options.PageSize ?? DEFAULT_PAGE_SIZE,
713
- },
714
- };
715
- if (options.Sort) {
716
- body['sort'] = options.Sort;
717
- }
718
- return body;
719
- }
720
- // ─── Response Parsers ────────────────────────────────────────────
721
- /**
722
- * Flattens a JSON:API record from the nested format:
723
- * ```json
724
- * { "id": "uuid", "type": "people", "attributes": { "given_name": "John", ... }, "relationships": {...} }
725
- * ```
726
- * into a flat object with all attributes at the top level plus `uuid` and `type`.
727
- */
728
- FlattenJsonApiRecord(record) {
729
- const attributes = record['attributes'];
730
- const result = {};
731
- // Add flattened attributes first
732
- if (attributes) {
733
- for (const [key, value] of Object.entries(attributes)) {
734
- result[key] = value;
735
- }
736
- }
737
- // Add system fields
738
- result['uuid'] = record['id'];
739
- result['type'] = record['type'];
740
- // Extract relationship IDs for easy access
741
- this.FlattenRelationships(record, result);
742
- return result;
743
- }
744
- /**
745
- * Extracts relationship IDs from JSON:API relationships and adds them as flat fields.
746
- * E.g., `relationships.person.data.id` becomes `person_id` on the flat record.
747
- */
748
- FlattenRelationships(record, target) {
749
- const relationships = record['relationships'];
750
- if (!relationships)
751
- return;
752
- for (const [relName, relData] of Object.entries(relationships)) {
753
- const rel = relData;
754
- if (!rel?.['data'])
755
- continue;
756
- const data = rel['data'];
757
- if (Array.isArray(data)) {
758
- // Has-many: store as array of IDs
759
- target[`${relName}_ids`] = data.map(d => d['id']);
760
- }
761
- else {
762
- // Belongs-to: store as single ID
763
- const belongsTo = data;
764
- target[`${relName}_id`] = belongsTo['id'];
765
- }
766
- }
767
- }
768
- /** Parses a CRUD operation response into a WicketCRUDResult. */
769
- ParseCRUDResponse(response, operation, objectName) {
770
- const success = response.Status >= 200 && response.Status < 300;
771
- if (!success) {
772
- const bodyPreview = this.FormatBodyPreview(response.Body);
773
- return {
774
- Success: false,
775
- ErrorMessage: `Wicket ${operation} failed with status ${response.Status}: ${bodyPreview}`,
776
- StatusCode: response.Status,
777
- };
778
- }
779
- // Extract the created/updated record ID from the response
780
- const body = response.Body;
781
- const data = body['data'];
782
- const externalID = data?.['id'];
783
- // CREATE-ONLY: a 2xx create with no record ID is a silent record-loss bug (duplicate
784
- // creates on the next sync). Fail loudly via the base helper. Update/Delete are unchanged —
785
- // they legitimately may not echo an ID in the body.
786
- if (operation === 'create') {
787
- return this.BuildCreatedResult(externalID, response.Status, objectName ?? 'record');
788
- }
789
- return {
790
- Success: true,
791
- ExternalID: externalID,
792
- StatusCode: response.Status,
793
- };
794
- }
795
- /** Parses a search response into a WicketSearchResult. */
796
- ParseSearchResponse(response, objectName) {
797
- if (response.Status < 200 || response.Status >= 300) {
798
- const bodyPreview = this.FormatBodyPreview(response.Body);
799
- throw new Error(`Wicket search failed with status ${response.Status}: ${bodyPreview}`);
800
- }
801
- const body = response.Body;
802
- const records = this.NormalizeResponse(body, 'data');
803
- const meta = body['meta'];
804
- const pageInfo = meta?.['page'];
805
- const totalCount = pageInfo?.total_count ?? records.length;
806
- const currentPage = pageInfo?.number ?? 1;
807
- const pageSize = pageInfo?.size ?? DEFAULT_PAGE_SIZE;
808
- const totalPages = Math.ceil(totalCount / pageSize);
809
- return {
810
- Records: records.map(r => ({
811
- ExternalID: String(r['uuid'] ?? ''),
812
- ObjectType: objectName,
813
- Fields: r,
814
- })),
815
- TotalCount: totalCount,
816
- HasMore: currentPage < totalPages,
817
- };
818
- }
819
- // ─── Validation ──────────────────────────────────────────────────
820
- /** Throws if the object does not support write operations. */
821
- ValidateWriteAllowed(objectName) {
822
- if (IMMUTABLE_OBJECTS.has(objectName)) {
823
- throw new Error(`Write operations are not allowed on "${objectName}". ` +
824
- `Touchpoints are immutable audit trail records.`);
825
- }
826
- }
827
- /** Throws if the object does not support delete operations. */
828
- ValidateDeleteAllowed(objectName) {
829
- // Touchpoints and resource_tags typically don't support delete
830
- const nonDeletable = new Set(['touchpoints', 'resource_tags']);
831
- if (nonDeletable.has(objectName)) {
832
- throw new Error(`Delete operations are not supported for "${objectName}".`);
833
- }
834
- }
835
- // ─── URL Helpers ─────────────────────────────────────────────────
836
- /**
837
- * Resolves the API path for a given Wicket object name.
838
- * Handles top-level and nested (contact info) objects.
839
- */
840
- ResolveObjectAPIPath(objectName) {
841
- // Contact info objects nested under people
842
- if (objectName === 'people_emails')
843
- return '/people';
844
- if (objectName === 'people_phones')
845
- return '/people';
846
- if (objectName === 'people_addresses')
847
- return '/people';
848
- if (objectName === 'org_emails')
849
- return '/organizations';
850
- if (objectName === 'org_phones')
851
- return '/organizations';
852
- if (objectName === 'org_addresses')
853
- return '/organizations';
854
- // For direct CRUD, emails/phones/addresses use their direct endpoints
855
- if (objectName === 'emails' || objectName === 'phones' || objectName === 'addresses') {
856
- return `/${objectName}`;
857
- }
858
- // Top-level objects
859
- return `/${objectName}`;
860
- }
861
- // ─── HTTP Helpers ────────────────────────────────────────────────
862
- /** Ensures minimum interval between API requests to avoid rate limiting. */
863
- async ThrottleRequest() {
864
- const elapsed = Date.now() - this.lastRequestTime;
865
- if (elapsed < MIN_REQUEST_INTERVAL_MS) {
866
- await this.Sleep(MIN_REQUEST_INTERVAL_MS - elapsed);
867
- }
868
- }
869
- /** Executes an HTTP request with a timeout and optional body. */
870
- async FetchWithTimeout(url, method, headers, body) {
871
- const controller = new AbortController();
872
- const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
873
- const fetchOptions = {
874
- method,
875
- headers,
876
- signal: controller.signal,
877
- };
878
- if (body && (method === 'POST' || method === 'PATCH' || method === 'PUT')) {
879
- fetchOptions.body = JSON.stringify(body);
880
- }
881
- try {
882
- return await fetch(url, fetchOptions);
883
- }
884
- catch (err) {
885
- if (err instanceof Error && err.name === 'AbortError') {
886
- throw new Error(`Wicket API request timed out after ${REQUEST_TIMEOUT_MS / 1000}s: ${url}`);
887
- }
888
- throw err;
889
- }
890
- finally {
891
- clearTimeout(timeoutId);
892
- }
893
- }
894
- /** Safely parses a response body as JSON, falling back to text. */
895
- async ParseResponseBody(response) {
896
- const contentType = response.headers.get('content-type') ?? '';
897
- if (contentType.includes('json')) {
898
- return response.json();
899
- }
900
- return response.text();
901
- }
902
- /** Calculates retry delay from Retry-After header or exponential backoff. */
903
- CalculateRetryDelay(response, attempt) {
904
- const retryAfter = response.headers.get('retry-after');
905
- if (retryAfter) {
906
- const parsed = parseInt(retryAfter, 10);
907
- if (!isNaN(parsed) && parsed > 0)
908
- return parsed * 1000;
909
- }
910
- return Math.min(1000 * Math.pow(2, attempt), 30000);
911
- }
912
- /** Converts a fetch Response + parsed body into a RESTResponse. */
913
- BuildRESTResponse(response, body) {
914
- const headers = {};
915
- response.headers.forEach((v, k) => { headers[k.toLowerCase()] = v; });
916
- return { Status: response.status, Body: body, Headers: headers };
917
- }
918
- /** Formats a response body as a truncated string for error messages. */
919
- FormatBodyPreview(body) {
920
- if (typeof body === 'string')
921
- return body.slice(0, 500);
922
- return JSON.stringify(body).slice(0, 500);
923
- }
924
- /** Builds a WicketCRUDResult from a caught error. */
925
- BuildCRUDError(err) {
926
- const message = err instanceof Error ? err.message : String(err);
927
- return { Success: false, ErrorMessage: message, StatusCode: 0 };
928
- }
929
- /** Returns a promise that resolves after the specified number of milliseconds. */
930
- Sleep(ms) {
931
- return new Promise(resolve => setTimeout(resolve, ms));
932
- }
933
- };
934
- WicketConnector = __decorate([
935
- RegisterClass(BaseIntegrationConnector, 'WicketConnector')
936
- ], WicketConnector);
937
- export { WicketConnector };
938
- //# sourceMappingURL=WicketConnector.js.map