@memberjunction/connector-membersuite 1.0.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,184 @@
1
+ import { type UserInfo } from '@memberjunction/core';
2
+ import type { MJCompanyIntegrationEntity } from '@memberjunction/core-entities';
3
+ import { BaseRESTIntegrationConnector, type RESTAuthContext, type RESTResponse, type PaginationState, type PaginationType, type ConnectionTestResult, type FetchContext, type FetchBatchResult, type CreateRecordContext, type UpdateRecordContext, type DeleteRecordContext, type CRUDResult, type RateLimitPolicy, type ExternalObjectSchema, type ExternalFieldSchema } from '@memberjunction/integration-engine';
4
+ /**
5
+ * Connection configuration parsed from the Credential entity / CompanyIntegration.Configuration JSON.
6
+ * Every field is per-connection (tenant-scoped) — NONE are baked constants.
7
+ */
8
+ export interface MemberSuiteConnectionConfig {
9
+ /** Tenant / association id — the `{tenantId}` path segment on the auth + list endpoints. */
10
+ TenantID: string;
11
+ /** 6-part signed-request credential set. */
12
+ AccessKeyID: string;
13
+ AssociationID: string;
14
+ AssociationKey: string;
15
+ SecretAccessKey: string;
16
+ /** Signing certificate (PEM/base64) — a POSTed credential VALUE, not a client-side signing key. */
17
+ SigningCertificate: string;
18
+ SigningCertificateID: string;
19
+ /** Optional pre-provisioned access token (test/broker injection) — bypasses the token exchange. */
20
+ AccessToken?: string;
21
+ RefreshToken?: string;
22
+ /** Override for the shared host (defaults to https://rest.membersuite.com). */
23
+ BaseURL?: string;
24
+ }
25
+ export declare class MemberSuiteConnector extends BaseRESTIntegrationConnector {
26
+ private tokenCache;
27
+ private lastRequestTime;
28
+ get IntegrationName(): string;
29
+ get SupportsCreate(): boolean;
30
+ get SupportsUpdate(): boolean;
31
+ get SupportsDelete(): boolean;
32
+ /**
33
+ * NOT authoritative for deactivation (matches the contract). The Declared baseline is seeded from
34
+ * six independent swagger specs (no single enumerate-all endpoint), and the runtime custom-field /
35
+ * saved-search surface is per-tenant + partial — absence in any one refresh proves nothing about
36
+ * the canonical object set, so it must NEVER trigger deactivation of Declared metadata.
37
+ */
38
+ get DiscoveryIsAuthoritative(): boolean;
39
+ /**
40
+ * MSQL incremental orders by lastModifiedDate and re-fetches `> watermark`, so the watermark is
41
+ * monotonic per object — the engine may narrow the next incremental window.
42
+ */
43
+ get MonotonicWatermark(): boolean;
44
+ /** Every resource exposes `id`; for no-watermark resume the engine keyset-orders by it. */
45
+ StableOrderingKey(_objectName: string): string | null;
46
+ /**
47
+ * MemberSuite publishes no documented tokens/sec rate limit in the swagger specs. A conservative
48
+ * sustained rate keeps the connector polite against a shared host; the engine's AIMD bucket adapts
49
+ * downward on any throttle signal. (Provable-only: this is a safe default, not a documented limit.)
50
+ */
51
+ get RateLimitPolicy(): RateLimitPolicy | null;
52
+ /** Honor a standard `Retry-After` header (seconds) if the vendor sends one on a 429. */
53
+ ExtractRetryAfterMs(error: unknown): number | undefined;
54
+ /**
55
+ * Returns the Declared baseline (the swagger universe seeded into the engine cache by the base
56
+ * implementation) PLUS — additively, when a credential is present — tenant-specific custom objects
57
+ * surfaced by the runtime saved-search MECHANISM. The baseline ALWAYS comes back credential-free
58
+ * (so a credential-free DocStructureSelfCheck re-yields the standard universe); a live credential is
59
+ * additive only. No catalog of customs is baked here.
60
+ */
61
+ DiscoverObjects(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<ExternalObjectSchema[]>;
62
+ /**
63
+ * Returns the Declared fields (from the engine cache) PLUS — additively, with a credential — the
64
+ * tenant's custom fields for the object via the runtime custom-field MECHANISM. The Declared set is
65
+ * always present; customs are an extension, never a replacement, and are never baked.
66
+ */
67
+ DiscoverFields(companyIntegration: MJCompanyIntegrationEntity, objectName: string, contextUser: UserInfo): Promise<ExternalFieldSchema[]>;
68
+ /**
69
+ * Runtime MECHANISM: fetch a tenant's custom-field definitions for an object and map them to
70
+ * ExternalFieldSchema. Custom fields are tenant-defined extensions — never declared, never NOT NULL
71
+ * (provable-only: a custom field's required-ness is not asserted by this endpoint's shape unless
72
+ * the vendor flags it). This is the discovery mechanism; the actual fields come from the live call.
73
+ */
74
+ private DiscoverCustomFields;
75
+ /** Maps a raw custom-field definition to a field schema. Provable-only on required/type. */
76
+ private CustomFieldToSchema;
77
+ private MapCustomFieldType;
78
+ /**
79
+ * Runtime MECHANISM: surface a tenant's saved searches as discoverable objects. Saved searches are a
80
+ * per-tenant query surface (each tenant defines its own) — never a standard object family, so they
81
+ * are additive-only and the connector stays non-authoritative.
82
+ */
83
+ private DiscoverSavedSearchObjects;
84
+ protected Authenticate(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<RESTAuthContext>;
85
+ private BuildAuthContext;
86
+ /**
87
+ * Step 1 of the two-step flow: POST the 6-part signed credential set to msc_authdata → accessToken.
88
+ *
89
+ * NO inline crypto. The swagger documents the signing certificate + its id as VALUES posted in the
90
+ * auth body (the server verifies them); it documents NO client-side HMAC/signature computation. If a
91
+ * future tenant deployment requires request-signing beyond posting the credential set, that is a
92
+ * NOTED auth-helpers extension request (RemainingGaps), not a fabricated routine here.
93
+ */
94
+ private ObtainToken;
95
+ /** Step 2 refresh: POST RefreshTokenRequest {idToken, refreshToken} → new accessToken. Best-effort. */
96
+ private RefreshToken;
97
+ private ParseConfig;
98
+ private ParseConfigJSON;
99
+ private BuildConfig;
100
+ /** True when a credential or Configuration-carried credential set is present (gates live discovery). */
101
+ private HasCredential;
102
+ TestConnection(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<ConnectionTestResult>;
103
+ protected GetBaseURL(_ci: MJCompanyIntegrationEntity, auth: RESTAuthContext): string;
104
+ /** Substitute the `{tenantId}` segment into a path template (tenant-scoped, never a constant). */
105
+ private ResolveTenantPath;
106
+ /**
107
+ * Strip the response envelope to expose individual records. MemberSuite list responses return either
108
+ * a bare array, or an envelope with the records under a `results`/`items`/`data` key (the swagger
109
+ * uses `results` for the paged list shape); a get-one returns a single object.
110
+ */
111
+ protected NormalizeResponse(rawBody: unknown, responseDataKey: string | null): Record<string, unknown>[];
112
+ /**
113
+ * PageNumber pagination (1-indexed). A page that returns a full page-size implies more may remain;
114
+ * a short/empty page is the last. The total may be reported under `totalCount`/`count` — when present
115
+ * we use it for an exact HasMore, otherwise fall back to the page-fill heuristic.
116
+ */
117
+ protected ExtractPaginationInfo(rawBody: unknown, _pt: PaginationType, currentPage: number, _co: number, pageSize: number): PaginationState;
118
+ /**
119
+ * Reads a MemberSuite object via the list door:
120
+ * GET /{service}/v1/{resource}/{tenantId}?msql=<select … where lastModifiedDate > wm>&page=&pageSize=
121
+ *
122
+ * The IO's `APIPath` (`/{service}/v1/{resource}`) is the base; we append the `{tenantId}` path segment
123
+ * (the access path's `entryQuery` carries `/{service}/v1/{resource}/{tenantId}`), the MSQL filter
124
+ * (watermark-driven incremental), and the 1-indexed page params. This is genuinely idiosyncratic
125
+ * (tenantId path segment + MSQL query language), so we override the base flat-fetch rather than the
126
+ * base appending only `page`/`pageSize` to a tenantId-less path.
127
+ */
128
+ FetchChanges(ctx: FetchContext): Promise<FetchBatchResult>;
129
+ /** The list path: `/{service}/v1/{resource}/{tenantId}` (from the access path's entryQuery / APIPath). */
130
+ private BuildListPath;
131
+ /** The MSQL `from` target — the vendor object/definition name (resource segment of the API path). */
132
+ private ResourceNameForMSQL;
133
+ /**
134
+ * Build the MSQL query. Incremental uses `> watermark` on lastModifiedDate; a first sync selects all.
135
+ * `select *` returns the full record so full-record pass-through is preserved.
136
+ */
137
+ private BuildMSQL;
138
+ /** MSQL datetime literals are single-quoted ISO-8601 strings. */
139
+ private FormatMSQLDateTime;
140
+ /** Append `msql`, `page` (1-indexed), and `pageSize` to the tenant-scoped list URL. */
141
+ private BuildMSQLPageURL;
142
+ /** Parse the per-IO access Configuration (service / version / listPath / accessPath). */
143
+ private ReadAccessConfig;
144
+ /** PK field names from the IO's fields (id by convention), sorted by sequence; falls back to `id`. */
145
+ private FindPKFieldNames;
146
+ /**
147
+ * Build an ExternalRecord from a raw vendor record. The FULL source record flows into `Fields`
148
+ * (custom-column pass-through contract M1–M4) — the only transformation is the transform-preserving
149
+ * hook (default identity), so no source key is silently dropped. The PK field(s) supply the
150
+ * ExternalID (joined with '|' for composite keys); lastModifiedDate supplies ModifiedAt.
151
+ */
152
+ private RawToExternalRecord;
153
+ CreateRecord(ctx: CreateRecordContext): Promise<CRUDResult>;
154
+ UpdateRecord(ctx: UpdateRecordContext): Promise<CRUDResult>;
155
+ DeleteRecord(ctx: DeleteRecordContext): Promise<CRUDResult>;
156
+ /**
157
+ * Returns a refusal CRUDResult when the object is OUTSIDE the writeback allowlist; null when the
158
+ * write is allowed (Activity / Certification). This enforces the operator-scoped "only Activity +
159
+ * Certification writebacks" decision in code — not a capability fabrication.
160
+ */
161
+ private GuardWriteback;
162
+ /**
163
+ * Extract the created-record id from a create response. MemberSuite returns the new record (id in
164
+ * body) per the contract's CreateIDLocation='body'. We also honor a configured idempotency reference
165
+ * (e.g. external_activity_id) when the vendor echoes it — so a duplicate writeback re-resolves to the
166
+ * same record id rather than minting a duplicate.
167
+ */
168
+ protected ExtractIDFromResponse(response: RESTResponse, idLocation: string | null): string | undefined;
169
+ /**
170
+ * Authorization header carries the RAW accessToken value (NO "Bearer " prefix — the swagger
171
+ * documents the Authorization parameter as the bare access-token string).
172
+ */
173
+ protected BuildHeaders(auth: RESTAuthContext): Record<string, string>;
174
+ /** Unauthenticated POST used by the token-exchange + refresh steps (no Authorization header). */
175
+ private RawPost;
176
+ protected MakeHTTPRequest(_auth: RESTAuthContext, url: string, method: string, headers: Record<string, string>, body?: unknown): Promise<RESTResponse>;
177
+ private FetchWithTimeout;
178
+ private ParseBody;
179
+ private ToRESTResponse;
180
+ private SafeBody;
181
+ private Throttle;
182
+ private Sleep;
183
+ }
184
+ export declare function LoadMemberSuiteConnector(): void;
@@ -0,0 +1,762 @@
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
+ /**
8
+ * MemberSuiteConnector — Integration connector for MemberSuite (Association Management System).
9
+ *
10
+ * MemberSuite is a multi-service AMS exposed over a REST API v2 (JSON). Member data is reached
11
+ * across six in-scope services — CRM, Membership, Events, Orders, Certifications, Fundraising —
12
+ * each versioned independently (`/{service}/v1/{resource}`; the Platform auth service is v2). The
13
+ * shared host is `https://rest.membersuite.com`; tenancy is resolved by a `{tenantId}` PATH segment
14
+ * on every list endpoint plus the auth-token context — there is NO per-account hostname variation.
15
+ *
16
+ * READS — list/get via `GET /{service}/v1/{resource}/{tenantId}?msql=&page=&pageSize=`.
17
+ * PageNumber pagination (1-indexed). Filtering + incremental sync use MSQL
18
+ * (MemberSuite Query Language) on the uniform `lastModifiedDate` watermark field:
19
+ * `msql=select * from {object} where lastModifiedDate > {watermark}`.
20
+ * WRITES — SCOPED to Activity + Certification writebacks ONLY (the operator-approved write-back
21
+ * use cases per the MemberSuiteContext). The API documents full CRUD on most objects, but
22
+ * writing arbitrary member/membership/order records against a live AMS is high-risk, so
23
+ * the connector REFUSES writes outside the writeback allowlist even though the per-IO
24
+ * write columns exist in metadata. Allowed writes route through the generic per-operation
25
+ * BaseRESTIntegrationConnector CRUD path (create POST flat / id-in-body → BuildCreatedResult;
26
+ * update PUT path-keyed; delete uses the non-standard infix `/delete/{id}`).
27
+ *
28
+ * AUTH (two-step signed request):
29
+ * 1. POST the 6-part signed credential set (accessKeyId + associationId + associationKey +
30
+ * secretAccessKey + signingCertificate + signingCertificateId) to
31
+ * `/platform/v2/msc_authdata/{tenantId}` → receive AuthenticationData {accessToken, idToken,
32
+ * refreshToken}.
33
+ * 2. Pass the raw `accessToken` as the `Authorization` header value (NO "Bearer " prefix — the
34
+ * swagger documents the Authorization parameter as the bare access-token string) on all
35
+ * subsequent data requests. The `/platform/v2/refreshtoken` endpoint refreshes an expired token.
36
+ *
37
+ * The 6-part credential set + tenantId are ALL per-connection Configuration/credential values —
38
+ * never baked constants (tenant-agnostic code). The "signing certificate" and its id are POSTed as
39
+ * credential VALUES in the auth body per the OpenAPI spec; the public swagger documents NO
40
+ * client-side HMAC/signature computation, so this connector performs NO inline crypto. See the
41
+ * RemainingGaps note in CODE_REPORT.md if a tenant's deployment requires request-signing beyond
42
+ * posting the credential set — that is a live-verification gap, not a fabricated routine here.
43
+ *
44
+ * DISCOVERY: DiscoverObjects/DiscoverFields return the Declared baseline (the credential-free swagger
45
+ * universe, seeded into the engine cache) AND encode the runtime MECHANISM for tenant-specific
46
+ * custom-field (`/platform/v2/customfields/{tenantId}`) + saved-search (`/platform/v2/savedsearches/
47
+ * {tenantId}`) discovery — never a baked catalog of a customer's customs/saved searches.
48
+ * DiscoveryIsAuthoritative stays FALSE: the baseline is multi-swagger (not a single enumerate-all
49
+ * endpoint) and the custom/saved-search surface is per-tenant + partial, so absence in any one
50
+ * tenant's refresh must NEVER deactivate the Declared metadata.
51
+ *
52
+ * API Documentation (swagger, credential-free):
53
+ * - Platform: https://rest.membersuite.com/platform/swagger/docs/v2
54
+ * - CRM: https://rest.membersuite.com/crm/swagger/docs/v1
55
+ * - (Membership / Events / Orders / Certifications / Fundraising mirror the CRM shape)
56
+ *
57
+ * NO BAKED CATALOG. The object/field universe is Declared (swagger → metadata file → engine cache) +
58
+ * Discovered (runtime custom-field + saved-search MECHANISM). This file is pure mechanism: auth, HTTP,
59
+ * discover, fetch, normalize, transform, write.
60
+ */
61
+ import { RegisterClass } from '@memberjunction/global';
62
+ import { Metadata } from '@memberjunction/core';
63
+ import { BaseIntegrationConnector, BaseRESTIntegrationConnector, } from '@memberjunction/integration-engine';
64
+ // ─── Constants ──────────────────────────────────────────────────────────────
65
+ const DEFAULT_BASE_URL = 'https://rest.membersuite.com';
66
+ const AUTH_PATH_TEMPLATE = '/platform/v2/msc_authdata/{tenantId}';
67
+ const REFRESH_PATH = '/platform/v2/refreshtoken';
68
+ /** Runtime-discovery MECHANISM endpoints (per-tenant, auth-gated, partial — never the baseline). */
69
+ const CUSTOM_FIELDS_PATH_TEMPLATE = '/platform/v2/customfields/{tenantId}';
70
+ const SAVED_SEARCHES_PATH_TEMPLATE = '/platform/v2/savedsearches/{tenantId}';
71
+ /** Uniform vendor watermark field across all six in-scope services. */
72
+ const DEFAULT_WATERMARK_FIELD = 'lastModifiedDate';
73
+ /** Vendor-wide PK convention (id on every resource definition). */
74
+ const PK_FIELD = 'id';
75
+ /** PageNumber pagination is 1-indexed (first page = 1). */
76
+ const FIRST_PAGE = 1;
77
+ const DEFAULT_PAGE_SIZE = 50;
78
+ /**
79
+ * The ONLY objects this connector will write to — the operator-approved writeback allowlist
80
+ * (Activity + Certification). Lower-cased IO names. Per-connection, not a vendor constant of the API
81
+ * surface: it encodes the SCOPE decision, not a catalog. Writes to any other object are refused even
82
+ * though the API + metadata declare CRUD columns for them.
83
+ */
84
+ const WRITEBACK_ALLOWLIST = new Set(['activities', 'certifications']);
85
+ const TOKEN_REFRESH_BUFFER_MS = 60_000;
86
+ const TOKEN_LIFETIME_MS = 3_600_000;
87
+ const MAX_RETRIES = 3;
88
+ const REQUEST_TIMEOUT_MS = 30_000;
89
+ const MIN_REQUEST_INTERVAL_MS = 50;
90
+ // ─── Connector ──────────────────────────────────────────────────────────────
91
+ let MemberSuiteConnector = class MemberSuiteConnector extends BaseRESTIntegrationConnector {
92
+ constructor() {
93
+ super(...arguments);
94
+ this.tokenCache = null;
95
+ this.lastRequestTime = 0;
96
+ }
97
+ get IntegrationName() { return 'MemberSuite'; }
98
+ // Capability getters: the connector CAN write (the writeback path exists), but the per-object
99
+ // allowlist guard in CreateRecord/UpdateRecord/DeleteRecord enforces that only Activity +
100
+ // Certification writebacks actually fire. Declaring the capability here lets the engine route
101
+ // writes to the connector; the guard decides per object.
102
+ get SupportsCreate() { return true; }
103
+ get SupportsUpdate() { return true; }
104
+ get SupportsDelete() { return true; }
105
+ /**
106
+ * NOT authoritative for deactivation (matches the contract). The Declared baseline is seeded from
107
+ * six independent swagger specs (no single enumerate-all endpoint), and the runtime custom-field /
108
+ * saved-search surface is per-tenant + partial — absence in any one refresh proves nothing about
109
+ * the canonical object set, so it must NEVER trigger deactivation of Declared metadata.
110
+ */
111
+ get DiscoveryIsAuthoritative() { return false; }
112
+ // ── §7 sync-efficiency hooks ─────────────────────────────────────────────
113
+ /**
114
+ * MSQL incremental orders by lastModifiedDate and re-fetches `> watermark`, so the watermark is
115
+ * monotonic per object — the engine may narrow the next incremental window.
116
+ */
117
+ get MonotonicWatermark() { return true; }
118
+ /** Every resource exposes `id`; for no-watermark resume the engine keyset-orders by it. */
119
+ StableOrderingKey(_objectName) { return PK_FIELD; }
120
+ /**
121
+ * MemberSuite publishes no documented tokens/sec rate limit in the swagger specs. A conservative
122
+ * sustained rate keeps the connector polite against a shared host; the engine's AIMD bucket adapts
123
+ * downward on any throttle signal. (Provable-only: this is a safe default, not a documented limit.)
124
+ */
125
+ get RateLimitPolicy() {
126
+ return { TokensPerSec: 10, Burst: 15, ThrottleBackoffFactor: 0.5 };
127
+ }
128
+ /** Honor a standard `Retry-After` header (seconds) if the vendor sends one on a 429. */
129
+ ExtractRetryAfterMs(error) {
130
+ if (!error || typeof error !== 'object')
131
+ return undefined;
132
+ const headers = error.Headers;
133
+ const ra = headers?.['retry-after'];
134
+ if (ra) {
135
+ const secs = Number(ra);
136
+ if (!isNaN(secs))
137
+ return secs * 1000;
138
+ }
139
+ return undefined;
140
+ }
141
+ // ── Discovery (Declared baseline + runtime custom-field / saved-search MECHANISM) ─
142
+ /**
143
+ * Returns the Declared baseline (the swagger universe seeded into the engine cache by the base
144
+ * implementation) PLUS — additively, when a credential is present — tenant-specific custom objects
145
+ * surfaced by the runtime saved-search MECHANISM. The baseline ALWAYS comes back credential-free
146
+ * (so a credential-free DocStructureSelfCheck re-yields the standard universe); a live credential is
147
+ * additive only. No catalog of customs is baked here.
148
+ */
149
+ async DiscoverObjects(companyIntegration, contextUser) {
150
+ // 1) Declared baseline — credential-free, from the engine cache. ALWAYS the standard universe.
151
+ const baseline = await super.DiscoverObjects(companyIntegration, contextUser);
152
+ // 2) ADDITIVE runtime discovery (mechanism, never a baked answer): a tenant's saved searches are
153
+ // a per-tenant custom query surface. Surface them as extra discoverable objects ONLY when a
154
+ // credential is configured — absence never removes baseline objects (DiscoveryIsAuthoritative=false).
155
+ if (!this.HasCredential(companyIntegration))
156
+ return baseline;
157
+ try {
158
+ const extras = await this.DiscoverSavedSearchObjects(companyIntegration, contextUser);
159
+ const known = new Set(baseline.map(o => o.Name.toLowerCase()));
160
+ for (const e of extras)
161
+ if (!known.has(e.Name.toLowerCase()))
162
+ baseline.push(e);
163
+ }
164
+ catch (err) {
165
+ // Runtime discovery is best-effort + additive — a failure must not break the standard universe.
166
+ console.warn(`[${this.IntegrationName}] saved-search discovery skipped: ${err instanceof Error ? err.message : String(err)}`);
167
+ }
168
+ return baseline;
169
+ }
170
+ /**
171
+ * Returns the Declared fields (from the engine cache) PLUS — additively, with a credential — the
172
+ * tenant's custom fields for the object via the runtime custom-field MECHANISM. The Declared set is
173
+ * always present; customs are an extension, never a replacement, and are never baked.
174
+ */
175
+ async DiscoverFields(companyIntegration, objectName, contextUser) {
176
+ const declared = await super.DiscoverFields(companyIntegration, objectName, contextUser);
177
+ if (!this.HasCredential(companyIntegration))
178
+ return declared;
179
+ try {
180
+ const customs = await this.DiscoverCustomFields(companyIntegration, objectName, contextUser);
181
+ const known = new Set(declared.map(f => f.Name.toLowerCase()));
182
+ for (const c of customs)
183
+ if (!known.has(c.Name.toLowerCase()))
184
+ declared.push(c);
185
+ }
186
+ catch (err) {
187
+ console.warn(`[${this.IntegrationName}] custom-field discovery skipped for "${objectName}": ${err instanceof Error ? err.message : String(err)}`);
188
+ }
189
+ return declared;
190
+ }
191
+ /**
192
+ * Runtime MECHANISM: fetch a tenant's custom-field definitions for an object and map them to
193
+ * ExternalFieldSchema. Custom fields are tenant-defined extensions — never declared, never NOT NULL
194
+ * (provable-only: a custom field's required-ness is not asserted by this endpoint's shape unless
195
+ * the vendor flags it). This is the discovery mechanism; the actual fields come from the live call.
196
+ */
197
+ async DiscoverCustomFields(companyIntegration, objectName, contextUser) {
198
+ const auth = await this.Authenticate(companyIntegration, contextUser);
199
+ const path = this.ResolveTenantPath(CUSTOM_FIELDS_PATH_TEMPLATE, auth.Config.TenantID);
200
+ const url = `${this.GetBaseURL(companyIntegration, auth)}${path}?objectType=${encodeURIComponent(objectName)}`;
201
+ const response = await this.MakeHTTPRequest(auth, url, 'GET', this.BuildHeaders(auth));
202
+ if (response.Status < 200 || response.Status >= 300)
203
+ return [];
204
+ const raw = this.NormalizeResponse(response.Body, null);
205
+ return raw.map(cf => this.CustomFieldToSchema(cf));
206
+ }
207
+ /** Maps a raw custom-field definition to a field schema. Provable-only on required/type. */
208
+ CustomFieldToSchema(cf) {
209
+ const name = String(cf['name'] ?? cf['fieldName'] ?? cf['id'] ?? '');
210
+ const dataType = typeof cf['dataType'] === 'string' ? this.MapCustomFieldType(cf['dataType']) : 'string';
211
+ const schema = {
212
+ Name: name,
213
+ Label: typeof cf['label'] === 'string' ? cf['label'] : name,
214
+ Description: typeof cf['description'] === 'string' ? cf['description'] : undefined,
215
+ DataType: dataType,
216
+ // Provable-only: only mark required when the vendor explicitly flags it.
217
+ IsRequired: cf['isRequired'] === true,
218
+ IsUniqueKey: false,
219
+ IsReadOnly: cf['isReadOnly'] === true,
220
+ };
221
+ return schema;
222
+ }
223
+ MapCustomFieldType(t) {
224
+ const map = {
225
+ string: 'string', text: 'string', textarea: 'string', email: 'string', url: 'string', phone: 'string',
226
+ boolean: 'boolean', bool: 'boolean', integer: 'number', int: 'number', number: 'number',
227
+ decimal: 'decimal', currency: 'decimal', money: 'decimal', date: 'datetime', datetime: 'datetime',
228
+ picklist: 'string', lookup: 'string',
229
+ };
230
+ return map[t.toLowerCase()] ?? 'string';
231
+ }
232
+ /**
233
+ * Runtime MECHANISM: surface a tenant's saved searches as discoverable objects. Saved searches are a
234
+ * per-tenant query surface (each tenant defines its own) — never a standard object family, so they
235
+ * are additive-only and the connector stays non-authoritative.
236
+ */
237
+ async DiscoverSavedSearchObjects(companyIntegration, contextUser) {
238
+ const auth = await this.Authenticate(companyIntegration, contextUser);
239
+ const path = this.ResolveTenantPath(SAVED_SEARCHES_PATH_TEMPLATE, auth.Config.TenantID);
240
+ const url = `${this.GetBaseURL(companyIntegration, auth)}${path}`;
241
+ const response = await this.MakeHTTPRequest(auth, url, 'GET', this.BuildHeaders(auth));
242
+ if (response.Status < 200 || response.Status >= 300)
243
+ return [];
244
+ const raw = this.NormalizeResponse(response.Body, null);
245
+ return raw.map(ss => ({
246
+ Name: `savedsearch:${String(ss['name'] ?? ss['id'] ?? '')}`,
247
+ Label: typeof ss['name'] === 'string' ? ss['name'] : 'Saved Search',
248
+ Description: 'Tenant-defined saved search (runtime-discovered query surface).',
249
+ SupportsIncrementalSync: false,
250
+ SupportsWrite: false,
251
+ }));
252
+ }
253
+ // ── Auth (two-step signed request → accessToken header) ───────────────────
254
+ async Authenticate(companyIntegration, contextUser) {
255
+ const config = await this.ParseConfig(companyIntegration, contextUser);
256
+ if (this.tokenCache && this.tokenCache.ExpiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS) {
257
+ return this.BuildAuthContext(config, this.tokenCache.AccessToken);
258
+ }
259
+ const token = await this.ObtainToken(config);
260
+ this.tokenCache = token;
261
+ return this.BuildAuthContext(config, token.AccessToken);
262
+ }
263
+ BuildAuthContext(config, accessToken) {
264
+ return {
265
+ Token: accessToken,
266
+ AccessToken: accessToken,
267
+ Config: config,
268
+ BaseURL: config.BaseURL ?? DEFAULT_BASE_URL,
269
+ };
270
+ }
271
+ /**
272
+ * Step 1 of the two-step flow: POST the 6-part signed credential set to msc_authdata → accessToken.
273
+ *
274
+ * NO inline crypto. The swagger documents the signing certificate + its id as VALUES posted in the
275
+ * auth body (the server verifies them); it documents NO client-side HMAC/signature computation. If a
276
+ * future tenant deployment requires request-signing beyond posting the credential set, that is a
277
+ * NOTED auth-helpers extension request (RemainingGaps), not a fabricated routine here.
278
+ */
279
+ async ObtainToken(config) {
280
+ // Pre-provisioned token (test/broker injection) — use as-is, no exchange.
281
+ if (config.AccessToken) {
282
+ return { AccessToken: config.AccessToken, RefreshToken: config.RefreshToken, ExpiresAt: Date.now() + TOKEN_LIFETIME_MS };
283
+ }
284
+ const baseURL = config.BaseURL ?? DEFAULT_BASE_URL;
285
+ // Refresh path: a cached refresh token can re-mint an access token without re-signing.
286
+ if (this.tokenCache?.RefreshToken) {
287
+ const refreshed = await this.RefreshToken(baseURL, this.tokenCache.RefreshToken, config);
288
+ if (refreshed)
289
+ return refreshed;
290
+ }
291
+ const authPath = this.ResolveTenantPath(AUTH_PATH_TEMPLATE, config.TenantID);
292
+ const body = {
293
+ accessKeyId: config.AccessKeyID,
294
+ associationId: config.AssociationID,
295
+ associationKey: config.AssociationKey,
296
+ secretAccessKey: config.SecretAccessKey,
297
+ signingCertificate: config.SigningCertificate,
298
+ signingCertificateId: config.SigningCertificateID,
299
+ };
300
+ const response = await this.RawPost(`${baseURL}${authPath}`, body);
301
+ if (response.Status < 200 || response.Status >= 300) {
302
+ throw new Error(`MemberSuite auth failed: HTTP ${response.Status} at ${authPath} — ${this.SafeBody(response)}`);
303
+ }
304
+ const data = response.Body;
305
+ if (!data || typeof data.accessToken !== 'string' || data.accessToken.length === 0) {
306
+ throw new Error('MemberSuite auth response carried no accessToken.');
307
+ }
308
+ return { AccessToken: data.accessToken, RefreshToken: data.refreshToken, ExpiresAt: Date.now() + TOKEN_LIFETIME_MS };
309
+ }
310
+ /** Step 2 refresh: POST RefreshTokenRequest {idToken, refreshToken} → new accessToken. Best-effort. */
311
+ async RefreshToken(baseURL, refreshToken, _config) {
312
+ try {
313
+ const response = await this.RawPost(`${baseURL}${REFRESH_PATH}`, { refreshToken });
314
+ if (response.Status < 200 || response.Status >= 300)
315
+ return null;
316
+ const data = response.Body;
317
+ if (typeof data?.accessToken !== 'string' || data.accessToken.length === 0)
318
+ return null;
319
+ return { AccessToken: data.accessToken, RefreshToken: data.refreshToken ?? refreshToken, ExpiresAt: Date.now() + TOKEN_LIFETIME_MS };
320
+ }
321
+ catch {
322
+ return null; // fall back to a full re-auth
323
+ }
324
+ }
325
+ async ParseConfig(companyIntegration, contextUser, provider) {
326
+ const fromConfigJSON = this.ParseConfigJSON(companyIntegration.Configuration);
327
+ const credentialID = companyIntegration.CredentialID;
328
+ if (credentialID) {
329
+ const md = provider ?? new Metadata();
330
+ const credential = await md.GetEntityObject('MJ: Credentials', contextUser);
331
+ const loaded = await credential.Load(credentialID);
332
+ if (loaded && credential.Values) {
333
+ const parsed = JSON.parse(credential.Values);
334
+ return this.BuildConfig(parsed, fromConfigJSON);
335
+ }
336
+ }
337
+ if (fromConfigJSON)
338
+ return this.BuildConfig(fromConfigJSON, null);
339
+ throw new Error('No MemberSuite credentials found — set the Credential entity or Configuration JSON.');
340
+ }
341
+ ParseConfigJSON(json) {
342
+ if (!json)
343
+ return null;
344
+ try {
345
+ return JSON.parse(json);
346
+ }
347
+ catch {
348
+ return null;
349
+ }
350
+ }
351
+ BuildConfig(creds, configJSON) {
352
+ const pick = (...keys) => {
353
+ for (const k of keys) {
354
+ if (creds[k])
355
+ return creds[k];
356
+ if (configJSON && configJSON[k])
357
+ return configJSON[k];
358
+ }
359
+ return undefined;
360
+ };
361
+ return {
362
+ TenantID: pick('TenantID', 'tenantId', 'tenant_id', 'AssociationID', 'associationId') ?? '',
363
+ AccessKeyID: pick('AccessKeyID', 'accessKeyId', 'access_key_id') ?? '',
364
+ AssociationID: pick('AssociationID', 'associationId', 'association_id') ?? '',
365
+ AssociationKey: pick('AssociationKey', 'associationKey', 'association_key') ?? '',
366
+ SecretAccessKey: pick('SecretAccessKey', 'secretAccessKey', 'secret_access_key') ?? '',
367
+ SigningCertificate: pick('SigningCertificate', 'signingCertificate', 'signing_certificate') ?? '',
368
+ SigningCertificateID: pick('SigningCertificateID', 'signingCertificateId', 'signing_certificate_id') ?? '',
369
+ AccessToken: pick('AccessToken', 'accessToken', 'access_token'),
370
+ RefreshToken: pick('RefreshToken', 'refreshToken', 'refresh_token'),
371
+ BaseURL: pick('BaseURL', 'baseUrl', 'base_url'),
372
+ };
373
+ }
374
+ /** True when a credential or Configuration-carried credential set is present (gates live discovery). */
375
+ HasCredential(companyIntegration) {
376
+ if (companyIntegration.CredentialID)
377
+ return true;
378
+ const cfg = this.ParseConfigJSON(companyIntegration.Configuration);
379
+ return !!cfg && (!!cfg['AccessToken'] || !!cfg['accessToken'] || !!cfg['SecretAccessKey'] || !!cfg['secretAccessKey']);
380
+ }
381
+ // ── TestConnection ───────────────────────────────────────────────────────
382
+ async TestConnection(companyIntegration, contextUser) {
383
+ try {
384
+ const auth = await this.Authenticate(companyIntegration, contextUser);
385
+ if (!auth.AccessToken)
386
+ return { Success: false, Message: 'MemberSuite authentication returned no access token.' };
387
+ // A successful token exchange is the connection proof; confirm the host is reachable for a read.
388
+ const probePath = this.ResolveTenantPath(CUSTOM_FIELDS_PATH_TEMPLATE, auth.Config.TenantID);
389
+ const response = await this.MakeHTTPRequest(auth, `${this.GetBaseURL(companyIntegration, auth)}${probePath}`, 'GET', this.BuildHeaders(auth));
390
+ if (response.Status >= 200 && response.Status < 300)
391
+ return { Success: true, Message: 'Connected to MemberSuite REST API v2.' };
392
+ if (response.Status === 401 || response.Status === 403)
393
+ return { Success: false, Message: `MemberSuite authorization denied: HTTP ${response.Status}.` };
394
+ // The token exchange already succeeded; a non-2xx on the probe (e.g. 404 for a tenant with no
395
+ // custom fields) still means we authenticated successfully.
396
+ return { Success: true, Message: `Authenticated to MemberSuite (probe returned HTTP ${response.Status}).` };
397
+ }
398
+ catch (err) {
399
+ return { Success: false, Message: err instanceof Error ? err.message : String(err) };
400
+ }
401
+ }
402
+ // ── URL / Response / Pagination ──────────────────────────────────────────
403
+ GetBaseURL(_ci, auth) {
404
+ return auth.BaseURL;
405
+ }
406
+ /** Substitute the `{tenantId}` segment into a path template (tenant-scoped, never a constant). */
407
+ ResolveTenantPath(template, tenantID) {
408
+ return template.replace(/\{tenantId\}/g, encodeURIComponent(tenantID));
409
+ }
410
+ /**
411
+ * Strip the response envelope to expose individual records. MemberSuite list responses return either
412
+ * a bare array, or an envelope with the records under a `results`/`items`/`data` key (the swagger
413
+ * uses `results` for the paged list shape); a get-one returns a single object.
414
+ */
415
+ NormalizeResponse(rawBody, responseDataKey) {
416
+ if (Array.isArray(rawBody))
417
+ return rawBody;
418
+ if (!rawBody || typeof rawBody !== 'object')
419
+ return [];
420
+ const body = rawBody;
421
+ if (responseDataKey && Array.isArray(body[responseDataKey]))
422
+ return body[responseDataKey];
423
+ for (const k of ['results', 'items', 'data', 'records', 'Results', 'Items']) {
424
+ if (Array.isArray(body[k]))
425
+ return body[k];
426
+ }
427
+ // A single non-enveloped record (e.g. get-one) — pass through as one record.
428
+ return [body];
429
+ }
430
+ /**
431
+ * PageNumber pagination (1-indexed). A page that returns a full page-size implies more may remain;
432
+ * a short/empty page is the last. The total may be reported under `totalCount`/`count` — when present
433
+ * we use it for an exact HasMore, otherwise fall back to the page-fill heuristic.
434
+ */
435
+ ExtractPaginationInfo(rawBody, _pt, currentPage, _co, pageSize) {
436
+ const records = this.NormalizeResponse(rawBody, null);
437
+ let total;
438
+ if (rawBody && typeof rawBody === 'object' && !Array.isArray(rawBody)) {
439
+ const body = rawBody;
440
+ for (const k of ['totalCount', 'count', 'total', 'TotalCount', 'totalRecords']) {
441
+ if (typeof body[k] === 'number') {
442
+ total = body[k];
443
+ break;
444
+ }
445
+ }
446
+ }
447
+ if (typeof total === 'number') {
448
+ const seen = currentPage * pageSize;
449
+ return { HasMore: seen < total, NextPage: currentPage + 1, TotalRecords: total };
450
+ }
451
+ const hasMore = records.length >= pageSize && records.length > 0;
452
+ return { HasMore: hasMore, NextPage: currentPage + 1 };
453
+ }
454
+ // ── FetchChanges (MSQL list with tenantId path + watermark filter + page paging) ─
455
+ /**
456
+ * Reads a MemberSuite object via the list door:
457
+ * GET /{service}/v1/{resource}/{tenantId}?msql=<select … where lastModifiedDate > wm>&page=&pageSize=
458
+ *
459
+ * The IO's `APIPath` (`/{service}/v1/{resource}`) is the base; we append the `{tenantId}` path segment
460
+ * (the access path's `entryQuery` carries `/{service}/v1/{resource}/{tenantId}`), the MSQL filter
461
+ * (watermark-driven incremental), and the 1-indexed page params. This is genuinely idiosyncratic
462
+ * (tenantId path segment + MSQL query language), so we override the base flat-fetch rather than the
463
+ * base appending only `page`/`pageSize` to a tenantId-less path.
464
+ */
465
+ async FetchChanges(ctx) {
466
+ const obj = this.GetCachedObject(ctx.CompanyIntegration.IntegrationID, ctx.ObjectName);
467
+ // Saved-search runtime objects are a query surface, not a standard list door — nothing to pull here.
468
+ if (ctx.ObjectName.startsWith('savedsearch:')) {
469
+ return { Records: [], HasMore: false, Warnings: [{ Code: 'NON_STANDARD_OBJECT', Message: `"${ctx.ObjectName}" is a tenant saved search (runtime query surface); it is not synced via the standard list door.` }] };
470
+ }
471
+ const fields = this.GetCachedFields(obj.ID);
472
+ const auth = await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser);
473
+ const headers = this.BuildHeaders(auth);
474
+ const watermarkField = obj.IncrementalWatermarkField ?? DEFAULT_WATERMARK_FIELD;
475
+ const cfg = this.ReadAccessConfig(obj);
476
+ const resourceName = this.ResourceNameForMSQL(obj, cfg);
477
+ const pageSize = obj.DefaultPageSize ?? DEFAULT_PAGE_SIZE;
478
+ const listBase = `${this.GetBaseURL(ctx.CompanyIntegration, auth)}${this.BuildListPath(obj, cfg, auth.Config.TenantID)}`;
479
+ const msql = this.BuildMSQL(resourceName, watermarkField, ctx.WatermarkValue ?? null);
480
+ const allRaw = [];
481
+ let page = ctx.CurrentPage ?? FIRST_PAGE;
482
+ const batchLimit = ctx.BatchSize ?? Number.MAX_SAFE_INTEGER;
483
+ let hasMore = true;
484
+ let lastPageReached = false;
485
+ while (hasMore && allRaw.length < batchLimit) {
486
+ const url = this.BuildMSQLPageURL(listBase, msql, page, pageSize);
487
+ const response = await this.MakeHTTPRequest(auth, url, 'GET', headers);
488
+ if (response.Status === 403) {
489
+ return { Records: [], HasMore: false, Warnings: [{ Code: 'FORBIDDEN', Message: `"${ctx.ObjectName}" requires additional API permissions (HTTP 403); skipping.` }] };
490
+ }
491
+ if (response.Status < 200 || response.Status >= 300) {
492
+ throw new Error(`MemberSuite list error for "${ctx.ObjectName}": HTTP ${response.Status} — ${this.SafeBody(response)}`);
493
+ }
494
+ const pageRecords = this.NormalizeResponse(response.Body, obj.ResponseDataKey);
495
+ if (pageRecords.length === 0) {
496
+ hasMore = false;
497
+ lastPageReached = true;
498
+ break;
499
+ }
500
+ allRaw.push(...pageRecords);
501
+ const pg = this.ExtractPaginationInfo(response.Body, obj.PaginationType, page, 0, pageSize);
502
+ hasMore = pg.HasMore;
503
+ page = pg.NextPage ?? page + 1;
504
+ if (!hasMore)
505
+ lastPageReached = true;
506
+ }
507
+ const pkFieldNames = this.FindPKFieldNames(fields);
508
+ const records = allRaw.map(r => this.RawToExternalRecord(r, obj, ctx.ObjectName, watermarkField, pkFieldNames));
509
+ // Watermark advances ONLY when the full batch drained (last page reached, full-batch success),
510
+ // and only to the max lastModifiedDate seen — partial-failure / mid-batch leaves it unchanged.
511
+ let newWatermark;
512
+ if (lastPageReached) {
513
+ for (const r of allRaw) {
514
+ const mod = r[watermarkField];
515
+ if (typeof mod === 'string' && (!newWatermark || mod > newWatermark))
516
+ newWatermark = mod;
517
+ }
518
+ }
519
+ return {
520
+ Records: records,
521
+ HasMore: !lastPageReached && hasMore,
522
+ NextPage: !lastPageReached ? page : undefined,
523
+ NewWatermarkValue: lastPageReached ? newWatermark : undefined,
524
+ };
525
+ }
526
+ /** The list path: `/{service}/v1/{resource}/{tenantId}` (from the access path's entryQuery / APIPath). */
527
+ BuildListPath(obj, cfg, tenantID) {
528
+ const entry = cfg.accessPath?.entryQuery ?? cfg.listPath ?? `${obj.APIPath}/{tenantId}`;
529
+ return entry.replace(/\{tenantId\}/g, encodeURIComponent(tenantID));
530
+ }
531
+ /** The MSQL `from` target — the vendor object/definition name (resource segment of the API path). */
532
+ ResourceNameForMSQL(obj, _cfg) {
533
+ // The MSQL `from` uses the resource name; the API path's last segment is the resource collection.
534
+ const segments = obj.APIPath.split('/').filter(Boolean);
535
+ return segments[segments.length - 1] ?? obj.Name;
536
+ }
537
+ /**
538
+ * Build the MSQL query. Incremental uses `> watermark` on lastModifiedDate; a first sync selects all.
539
+ * `select *` returns the full record so full-record pass-through is preserved.
540
+ */
541
+ BuildMSQL(resourceName, watermarkField, watermarkValue) {
542
+ let q = `select * from ${resourceName}`;
543
+ if (watermarkValue)
544
+ q += ` where ${watermarkField} > ${this.FormatMSQLDateTime(watermarkValue)}`;
545
+ return q;
546
+ }
547
+ /** MSQL datetime literals are single-quoted ISO-8601 strings. */
548
+ FormatMSQLDateTime(value) {
549
+ if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
550
+ const norm = value.endsWith('Z') || /[+-]\d{2}:\d{2}$/.test(value) ? value : `${value}Z`;
551
+ return `'${norm}'`;
552
+ }
553
+ const d = new Date(value);
554
+ return `'${isNaN(d.getTime()) ? value : d.toISOString()}'`;
555
+ }
556
+ /** Append `msql`, `page` (1-indexed), and `pageSize` to the tenant-scoped list URL. */
557
+ BuildMSQLPageURL(listBase, msql, page, pageSize) {
558
+ const sep = listBase.includes('?') ? '&' : '?';
559
+ return `${listBase}${sep}msql=${encodeURIComponent(msql)}&page=${page}&pageSize=${pageSize}`;
560
+ }
561
+ /** Parse the per-IO access Configuration (service / version / listPath / accessPath). */
562
+ ReadAccessConfig(obj) {
563
+ const raw = obj.Configuration;
564
+ if (!raw)
565
+ return {};
566
+ try {
567
+ return JSON.parse(raw);
568
+ }
569
+ catch {
570
+ return {};
571
+ }
572
+ }
573
+ /** PK field names from the IO's fields (id by convention), sorted by sequence; falls back to `id`. */
574
+ FindPKFieldNames(fields) {
575
+ const pks = fields.filter(f => f.IsPrimaryKey).sort((a, b) => a.Sequence - b.Sequence).map(f => f.Name);
576
+ return pks.length > 0 ? pks : [PK_FIELD];
577
+ }
578
+ /**
579
+ * Build an ExternalRecord from a raw vendor record. The FULL source record flows into `Fields`
580
+ * (custom-column pass-through contract M1–M4) — the only transformation is the transform-preserving
581
+ * hook (default identity), so no source key is silently dropped. The PK field(s) supply the
582
+ * ExternalID (joined with '|' for composite keys); lastModifiedDate supplies ModifiedAt.
583
+ */
584
+ RawToExternalRecord(raw, obj, objectType, watermarkField, pkFieldNames) {
585
+ const fields = this.applyTransformPreservingKeys(raw, obj, this.GetCachedFields(obj.ID));
586
+ const allPkPresent = pkFieldNames.length > 0 && pkFieldNames.every(n => fields[n] != null && String(fields[n]).length > 0);
587
+ const externalID = allPkPresent ? pkFieldNames.map(n => String(fields[n])).join('|') : '';
588
+ const modRaw = raw[watermarkField];
589
+ return {
590
+ ExternalID: externalID,
591
+ ObjectType: objectType,
592
+ Fields: fields,
593
+ ModifiedAt: typeof modRaw === 'string' ? new Date(modRaw) : undefined,
594
+ IsDeleted: raw['isDeleted'] === true,
595
+ };
596
+ }
597
+ // ── CRUD (writeback-scoped: Activity + Certification only) ────────────────
598
+ //
599
+ // The generic BaseRESTIntegrationConnector CRUD handles the wire shape (POST flat / id-in-body →
600
+ // BuildCreatedResult; PUT path-keyed update; DELETE via the non-standard `/delete/{id}` infix). We
601
+ // override only to ENFORCE the operator-approved writeback allowlist: a write to any object outside
602
+ // {activities, certifications} is REFUSED, even though metadata declares CRUD columns for it.
603
+ async CreateRecord(ctx) {
604
+ const guard = this.GuardWriteback(ctx.ObjectName, 'create');
605
+ if (guard)
606
+ return guard;
607
+ return super.CreateRecord(ctx);
608
+ }
609
+ async UpdateRecord(ctx) {
610
+ const guard = this.GuardWriteback(ctx.ObjectName, 'update');
611
+ if (guard)
612
+ return guard;
613
+ return super.UpdateRecord(ctx);
614
+ }
615
+ async DeleteRecord(ctx) {
616
+ const guard = this.GuardWriteback(ctx.ObjectName, 'delete');
617
+ if (guard)
618
+ return guard;
619
+ return super.DeleteRecord(ctx);
620
+ }
621
+ /**
622
+ * Returns a refusal CRUDResult when the object is OUTSIDE the writeback allowlist; null when the
623
+ * write is allowed (Activity / Certification). This enforces the operator-scoped "only Activity +
624
+ * Certification writebacks" decision in code — not a capability fabrication.
625
+ */
626
+ GuardWriteback(objectName, verb) {
627
+ if (WRITEBACK_ALLOWLIST.has(objectName.toLowerCase()))
628
+ return null;
629
+ return {
630
+ Success: false,
631
+ StatusCode: 0,
632
+ ErrorMessage: `MemberSuite ${verb} refused for "${objectName}": writebacks are scoped to Activity + Certification only (operator-approved write-back use cases). Writing other objects against a live AMS is out of scope.`,
633
+ };
634
+ }
635
+ // ── Idempotency on create (honor the configured idempotency key) ──────────
636
+ /**
637
+ * Extract the created-record id from a create response. MemberSuite returns the new record (id in
638
+ * body) per the contract's CreateIDLocation='body'. We also honor a configured idempotency reference
639
+ * (e.g. external_activity_id) when the vendor echoes it — so a duplicate writeback re-resolves to the
640
+ * same record id rather than minting a duplicate.
641
+ */
642
+ ExtractIDFromResponse(response, idLocation) {
643
+ const base = super.ExtractIDFromResponse(response, idLocation);
644
+ if (base)
645
+ return base;
646
+ if (response.Body && typeof response.Body === 'object') {
647
+ const b = response.Body;
648
+ for (const k of [PK_FIELD, 'ID', 'Id', 'recordId', 'RecordID']) {
649
+ if (typeof b[k] === 'string' || typeof b[k] === 'number')
650
+ return String(b[k]);
651
+ }
652
+ }
653
+ return undefined;
654
+ }
655
+ // ── Headers + HTTP transport ─────────────────────────────────────────────
656
+ /**
657
+ * Authorization header carries the RAW accessToken value (NO "Bearer " prefix — the swagger
658
+ * documents the Authorization parameter as the bare access-token string).
659
+ */
660
+ BuildHeaders(auth) {
661
+ const token = auth.AccessToken ?? auth.Token ?? '';
662
+ return {
663
+ Authorization: token,
664
+ Accept: 'application/json',
665
+ 'User-Agent': 'MemberJunction-Integration/1.0',
666
+ };
667
+ }
668
+ /** Unauthenticated POST used by the token-exchange + refresh steps (no Authorization header). */
669
+ async RawPost(url, body) {
670
+ const controller = new AbortController();
671
+ const tid = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
672
+ try {
673
+ const response = await fetch(url, {
674
+ method: 'POST',
675
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
676
+ body: JSON.stringify(body),
677
+ signal: controller.signal,
678
+ });
679
+ const parsed = await this.ParseBody(response);
680
+ return this.ToRESTResponse(response, parsed);
681
+ }
682
+ catch (err) {
683
+ if (err instanceof Error && err.name === 'AbortError')
684
+ throw new Error(`MemberSuite auth request timed out: ${url}`);
685
+ throw err;
686
+ }
687
+ finally {
688
+ clearTimeout(tid);
689
+ }
690
+ }
691
+ async MakeHTTPRequest(_auth, url, method, headers, body) {
692
+ await this.Throttle();
693
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
694
+ const response = await this.FetchWithTimeout(url, method, headers, body);
695
+ this.lastRequestTime = Date.now();
696
+ if (response.status === 401 && attempt === 0) {
697
+ this.tokenCache = null;
698
+ continue;
699
+ }
700
+ if (response.status === 429 && attempt < MAX_RETRIES) {
701
+ const ra = Number(response.headers.get('retry-after'));
702
+ const wait = !isNaN(ra) && ra > 0 ? ra * 1000 : Math.min(1000 * Math.pow(2, attempt) + Math.random() * 500, 60_000);
703
+ await this.Sleep(wait);
704
+ continue;
705
+ }
706
+ if (response.status >= 500 && attempt < MAX_RETRIES) {
707
+ await this.Sleep(Math.min(1000 * Math.pow(2, attempt), 30_000));
708
+ continue;
709
+ }
710
+ const parsed = await this.ParseBody(response);
711
+ return this.ToRESTResponse(response, parsed);
712
+ }
713
+ throw new Error(`MemberSuite request failed after ${MAX_RETRIES} retries: ${url}`);
714
+ }
715
+ async FetchWithTimeout(url, method, headers, body) {
716
+ const controller = new AbortController();
717
+ const tid = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
718
+ try {
719
+ const opts = { method, headers };
720
+ opts.signal = controller.signal;
721
+ if (body !== undefined && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
722
+ opts.body = JSON.stringify(body);
723
+ opts.headers = { ...headers, 'Content-Type': 'application/json' };
724
+ }
725
+ return await fetch(url, opts);
726
+ }
727
+ catch (err) {
728
+ if (err instanceof Error && err.name === 'AbortError')
729
+ throw new Error(`Request timed out: ${url}`);
730
+ throw err;
731
+ }
732
+ finally {
733
+ clearTimeout(tid);
734
+ }
735
+ }
736
+ async ParseBody(r) {
737
+ const ct = r.headers.get('content-type') ?? '';
738
+ return ct.includes('json') ? r.json().catch(() => null) : r.text();
739
+ }
740
+ ToRESTResponse(r, body) {
741
+ const h = {};
742
+ r.headers.forEach((v, k) => { h[k.toLowerCase()] = v; });
743
+ return { Status: r.status, Body: body, Headers: h };
744
+ }
745
+ SafeBody(r) {
746
+ const s = typeof r.Body === 'string' ? r.Body : JSON.stringify(r.Body);
747
+ return (s ?? '').substring(0, 300);
748
+ }
749
+ async Throttle() {
750
+ const elapsed = Date.now() - this.lastRequestTime;
751
+ if (elapsed < MIN_REQUEST_INTERVAL_MS)
752
+ await this.Sleep(MIN_REQUEST_INTERVAL_MS - elapsed);
753
+ }
754
+ Sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
755
+ };
756
+ MemberSuiteConnector = __decorate([
757
+ RegisterClass(BaseIntegrationConnector, '@memberjunction/connector-membersuite')
758
+ ], MemberSuiteConnector);
759
+ export { MemberSuiteConnector };
760
+ // Tree-shaking prevention — REQUIRED so @RegisterClass survives bundling.
761
+ export function LoadMemberSuiteConnector() { }
762
+ //# sourceMappingURL=MemberSuiteConnector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemberSuiteConnector.js","sourceRoot":"","sources":["../src/MemberSuiteConnector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from './MemberSuiteConnector.js';
2
+ /** Open App bootstrap entry: importing this module ran the connector's @RegisterClass decorator;
3
+ * this no-op satisfies the loader's required startupExport and forces the import at MJAPI boot. */
4
+ export declare function registerConnector(): void;
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './MemberSuiteConnector.js';
2
+ /** Open App bootstrap entry: importing this module ran the connector's @RegisterClass decorator;
3
+ * this no-op satisfies the loader's required startupExport and forces the import at MJAPI boot. */
4
+ export function registerConnector() { }
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAE1C;oGACoG;AACpG,MAAM,UAAU,iBAAiB,KAAiD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@memberjunction/connector-membersuite",
3
+ "version": "1.0.0",
4
+ "description": "MemberJunction MemberSuite connector.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "/dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc && tsc-alias -f",
13
+ "test": "vitest run --passWithNoTests"
14
+ },
15
+ "author": "MemberJunction.com",
16
+ "license": "ISC",
17
+ "peerDependencies": {
18
+ "@memberjunction/core": ">=5.42.0 <6.0.0",
19
+ "@memberjunction/core-entities": ">=5.42.0 <6.0.0",
20
+ "@memberjunction/global": ">=5.42.0 <6.0.0",
21
+ "@memberjunction/integration-engine": ">=5.42.0 <6.0.0"
22
+ },
23
+ "dependencies": {},
24
+ "devDependencies": {
25
+ "@types/node": "24.10.11",
26
+ "tsc-alias": "^1.8.16",
27
+ "typescript": "^5.9.3",
28
+ "vitest": "^4.0.18",
29
+ "@memberjunction/core": "^5.42.0",
30
+ "@memberjunction/core-entities": "^5.42.0",
31
+ "@memberjunction/global": "^5.42.0",
32
+ "@memberjunction/integration-engine": "^5.42.0"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/MemberJunction/Integrations"
37
+ }
38
+ }