@memberjunction/connector-blackbaud 1.2.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,173 @@
1
+ import { type UserInfo } from '@memberjunction/core';
2
+ import type { MJCompanyIntegrationEntity, MJIntegrationObjectEntity } from '@memberjunction/core-entities';
3
+ import { BaseRESTIntegrationConnector, type RESTAuthContext, type RESTResponse, type PaginationState, type PaginationType, type ConnectionTestResult, type FetchContext, type FetchBatchResult, type RateLimitPolicy, type CreateRecordContext, type CRUDResult, type SourceSchemaInfo } from '@memberjunction/integration-engine';
4
+ /**
5
+ * BlackbaudConnector — Blackbaud Raiser's Edge NXT (SKY API) integration connector.
6
+ *
7
+ * ── AUTH (TWO credential parts on EVERY request) ────────────────────────────
8
+ * SKY API requires BOTH parts on every call — missing either returns 401
9
+ * (Configuration.AuthFlowNote, confirmed from vendor tutorial repos + live docs):
10
+ * 1. `Authorization: Bearer <access_token>` — OAuth 2.0 authorization-code access
11
+ * token. Minted/refreshed via the shared {@link OAuth2TokenManager} (the
12
+ * `refresh_token` grant against `https://oauth2.sky.blackbaud.com/token`); SKY
13
+ * API is a CONFIDENTIAL app so the exchange carries client_id/client_secret and
14
+ * returns a rotating refresh_token. No inline crypto — the manager owns the
15
+ * token round-trip.
16
+ * 2. `bb-api-subscription-key: <subscription_key>` — the developer account's SKY API
17
+ * subscription key. Vendor docs PROSE title-cases it `Bb-Api-Subscription-Key`;
18
+ * vendor SAMPLE CODE (2 Blackbaud-owned GitHub tutorial repos) sends the literal
19
+ * lowercase `bb-api-subscription-key` — HTTP header names are case-insensitive
20
+ * (RFC 7230), and we send exactly what a working vendor client transmits.
21
+ * Both are injected by {@link BuildHeaders}.
22
+ *
23
+ * ── BASE URL / PER-FAMILY PATHS ─────────────────────────────────────────────
24
+ * SKY API versions each product family via a `/{family}/v1/` path segment
25
+ * (Configuration.APIVersioningStrategy = url-path): `/constituent/v1`, `/gift/v1`,
26
+ * `/fundraising/v1`, `/opportunity/v1`, `/commpref/v1`, `/nxt-data-integration/v1`,
27
+ * `/gift-batch/v1`. Every IO's `APIPath` already carries its family prefix, so the
28
+ * host is a SINGLE constant (`https://api.sky.blackbaud.com`) and {@link GetBaseURL}
29
+ * returns it; the base class concatenates `host + APIPath` (family prefix included).
30
+ * The host is overridable via Configuration.ApiBaseUrl for sandbox/regional tenants.
31
+ *
32
+ * ── DISCOVERY (mechanism only — NO baked catalog) ───────────────────────────
33
+ * The object/field universe is credential-free-documented (the 4 in-scope SKY API
34
+ * OpenAPI specs), so it is seeded as Declared metadata in the integration file.
35
+ * This connector carries NO object/field catalog constant: {@link DiscoverObjects} /
36
+ * {@link DiscoverFields} inherit the base cache-driven implementation that reads the
37
+ * Declared metadata (the sanctioned "case-1 → Declared metadata" mechanism).
38
+ *
39
+ * ── PAGINATION (SKY API limit/offset envelope) ──────────────────────────────
40
+ * Offset pagination via `limit`/`offset` query params; the collection response is
41
+ * `{ count, value: [...] }` with an optional `next_link` hypermedia accelerator
42
+ * (Configuration.PaginationDefaults). The base's Offset `BuildPaginatedURL` already
43
+ * emits `offset=X&limit=Y` (SKY API's exact params), so it is NOT overridden;
44
+ * {@link NormalizeResponse} unwraps `value` and {@link ExtractPaginationInfo} drives
45
+ * the loop off `next_link` + `count`.
46
+ *
47
+ * ── INCREMENTAL SYNC (request/response field-name SPLIT) ─────────────────────
48
+ * Incremental IOs (constituent, gift, fundraising_*, opportunity) filter on the
49
+ * REQUEST query param `last_modified=<watermark>` but track the new high-watermark
50
+ * from the RESPONSE record field `date_modified` — two DIFFERENT names
51
+ * (Configuration.IncrementalSyncNote). {@link FetchChanges} injects `last_modified`
52
+ * for those IOs and computes `NewWatermarkValue` from the max `date_modified` seen.
53
+ *
54
+ * ── CRUD ────────────────────────────────────────────────────────────────────
55
+ * Generic per-operation CRUD from {@link BaseRESTIntegrationConnector} (reads
56
+ * Create/Update APIPath/Method/BodyShape/IDLocation off each IO row) is used as-is;
57
+ * create fails LOUDLY on an empty response ID via `BuildCreatedResult`. Constituent
58
+ * create is the ONE genuinely-idiosyncratic write (split virtual individual/org
59
+ * endpoints, no generic create in v1) — {@link CreateRecord} overrides ONLY that
60
+ * object and delegates everything else to the generic path.
61
+ *
62
+ * ── RATE LIMIT ──────────────────────────────────────────────────────────────
63
+ * 10 req/s, 25,000 calls/day Standard tier (Configuration.RateLimitPolicy, current
64
+ * live vendor value). Surfaced via {@link RateLimitPolicy} + {@link ExtractRetryAfterMs}
65
+ * (SKY API returns a `Retry-After` header in seconds alongside a 429/403 quota body).
66
+ */
67
+ export declare class BlackbaudConnector extends BaseRESTIntegrationConnector {
68
+ /** OAuth2 token manager (one per connector instance; caches + refreshes the access token). */
69
+ private tokenManager;
70
+ /**
71
+ * The `last_modified=<watermark>` query fragment to append to the NEXT flat paginated request,
72
+ * set for the duration of a SINGLE incremental {@link FetchChanges} call. Consumed by
73
+ * {@link AppendDefaultQueryParams}. Single-threaded: the engine awaits each FetchChanges fully
74
+ * before the next, and incremental IOs are all FLAT (no concurrent parent-iteration), so this
75
+ * per-call field never races across objects. Cleared in a `finally`.
76
+ */
77
+ private pendingWatermarkFilter;
78
+ /** Verbatim three-way invariant name: IntegrationName getter === MJ: Integrations.Name ('blackbaud'). */
79
+ get IntegrationName(): string;
80
+ /**
81
+ * IntrospectSchema — pure WIRING of MJ's existing sampler into the declared catalog (the connector
82
+ * sample-union standard; see CONNECTOR_DISCOVERY_STANDARD.md). This connector adds NO discovery,
83
+ * merge, or sync logic — it only wires `DiscoverFieldsViaFetch` (MJ's sampler) into IntrospectSchema.
84
+ *
85
+ * `super.IntrospectSchema` yields the cache-driven Declared catalog (no measured widths). For each
86
+ * object we then call MJ's `DiscoverFieldsViaFetch` — MJ's own read-path sampler that measures real
87
+ * field widths and surfaces custom columns — and the shared PURE `mergeDeclaredWithSampledFields`
88
+ * unions the two by field name (adopt MJ's measured width; append MJ-discovered custom columns). MJ
89
+ * owns everything else (measurement, type/PK inference, persistence, reconcile, sync).
90
+ *
91
+ * Recursion note: `DiscoverFieldsViaFetch` falls back to the UNCHANGED `DiscoverFields` (cache-driven)
92
+ * when the read path can't run — never back into THIS method — so there is no infinite recursion.
93
+ * This connector does NOT override `DiscoverFields` to call any ViaFetch/ViaStream.
94
+ *
95
+ * Robustness: objects are sampled IN PARALLEL under a small bounded pool; any per-object failure
96
+ * keeps that object's declared fields, so a single bad sample never breaks introspection.
97
+ */
98
+ IntrospectSchema(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<SourceSchemaInfo>;
99
+ get SupportsCreate(): boolean;
100
+ get SupportsUpdate(): boolean;
101
+ /**
102
+ * Mints/refreshes the OAuth2 access token AND resolves the subscription key. Both credential
103
+ * parts ride the returned auth context so {@link BuildHeaders} can inject both on every request.
104
+ */
105
+ protected Authenticate(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<BlackbaudAuthContext>;
106
+ /**
107
+ * Builds request headers with BOTH required SKY API credential parts. `bb-api-subscription-key`
108
+ * casing matches the vendor's own sample code (case-insensitive on the wire per RFC 7230).
109
+ */
110
+ protected BuildHeaders(auth: RESTAuthContext): Record<string, string>;
111
+ protected GetBaseURL(_companyIntegration: MJCompanyIntegrationEntity, auth: RESTAuthContext): string;
112
+ TestConnection(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<ConnectionTestResult>;
113
+ protected NormalizeResponse(rawBody: unknown, responseDataKey: string | null): Record<string, unknown>[];
114
+ /**
115
+ * Drives the offset-pagination loop from the SKY API envelope: continue while a `next_link` is
116
+ * present, advancing `offset` by the count of records seen. When `next_link` is absent, the page
117
+ * is the last one. `count` is the total (used only for diagnostics).
118
+ */
119
+ protected ExtractPaginationInfo(rawBody: unknown, _paginationType: PaginationType, _currentPage: number, currentOffset: number, _pageSize: number): PaginationState;
120
+ /**
121
+ * Wraps the base fetch to (1) inject the `last_modified=<watermark>` filter for incremental IOs
122
+ * and (2) compute `NewWatermarkValue` from the max RESPONSE `date_modified` — the request/response
123
+ * field-name split the frozen contract calls out. Everything else (pagination loop, parent-chain
124
+ * walking, transform, PK assembly) is the base's; this only adds the watermark param + high-water
125
+ * tracking, so nested-graph objects and generic flat objects flow through unchanged.
126
+ */
127
+ FetchChanges(ctx: FetchContext): Promise<FetchBatchResult>;
128
+ /**
129
+ * Appends the pending incremental watermark filter (set by {@link FetchChanges}) to a flat
130
+ * paginated request, on top of whatever DefaultQueryParams the base appends. Skips injection when
131
+ * the URL already carries `last_modified` (idempotent across the pagination loop's pages).
132
+ */
133
+ protected AppendDefaultQueryParams(url: string, obj: MJIntegrationObjectEntity): string;
134
+ /** Highest RESPONSE `date_modified` (lexical ISO-8601 compare) across a batch, or undefined. */
135
+ private MaxWatermark;
136
+ /**
137
+ * Constituent create is genuinely idiosyncratic: SKY API v1 has NO generic
138
+ * `POST /constituent/v1/constituents` — creation goes through split virtual endpoints
139
+ * (`/constituent/v1/virtual/individuals` vs `/virtual/organizations`), chosen by the record's
140
+ * `type` (Configuration.createMechanism = 'split-virtual-endpoints'). All OTHER objects delegate
141
+ * to the base's generic per-operation CRUD; this override only special-cases `constituent`.
142
+ * Still routes through {@link BuildCreatedResult} so an empty-ID create fails LOUDLY (write-path invariant).
143
+ */
144
+ CreateRecord(ctx: CreateRecordContext): Promise<CRUDResult>;
145
+ /** SKY API Standard tier: 10 req/s (Configuration.RateLimitPolicy, current live vendor value). */
146
+ get RateLimitPolicy(): RateLimitPolicy;
147
+ /**
148
+ * SKY API returns a `Retry-After` header (seconds) on 429 (rate limit) and 403 (quota) responses
149
+ * (Configuration.ErrorResponseShapeNote). Parse it to ms so the engine backs off precisely.
150
+ */
151
+ ExtractRetryAfterMs(error: unknown): number | undefined;
152
+ protected MakeHTTPRequest(_auth: RESTAuthContext, url: string, method: string, headers: Record<string, string>, body?: unknown): Promise<RESTResponse>;
153
+ private ParseBody;
154
+ /**
155
+ * Resolves the two credential parts (OAuth2 client/refresh + subscription key) plus non-secret
156
+ * host config from the linked Credential entity, falling back to CompanyIntegration.Configuration.
157
+ */
158
+ private LoadCredentials;
159
+ /** Loads credential fields from a Credential entity's Values JSON. */
160
+ private LoadFromCredentialEntity;
161
+ /** Parses a JSON string into credential/config fields (tolerant of casing aliases). */
162
+ private ParseConfigJson;
163
+ /** Drops undefined/empty-string entries so a later fallback source can fill them. */
164
+ private compact;
165
+ }
166
+ /** Auth context: resolved bearer token + subscription key + optional host override. */
167
+ interface BlackbaudAuthContext extends RESTAuthContext {
168
+ Token: string;
169
+ SubscriptionKey: string;
170
+ ApiBaseUrl?: string;
171
+ }
172
+ export declare function LoadBlackbaudConnector(): void;
173
+ export {};
@@ -0,0 +1,508 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { RegisterClass } from '@memberjunction/global';
8
+ import { Metadata } from '@memberjunction/core';
9
+ import { BaseIntegrationConnector, BaseRESTIntegrationConnector, OAuth2TokenManager, } from '@memberjunction/integration-engine';
10
+ import { mergeDeclaredWithSampledFields } from '@memberjunction/connector-schema-merge';
11
+ import { z } from 'zod';
12
+ /**
13
+ * BlackbaudConnector — Blackbaud Raiser's Edge NXT (SKY API) integration connector.
14
+ *
15
+ * ── AUTH (TWO credential parts on EVERY request) ────────────────────────────
16
+ * SKY API requires BOTH parts on every call — missing either returns 401
17
+ * (Configuration.AuthFlowNote, confirmed from vendor tutorial repos + live docs):
18
+ * 1. `Authorization: Bearer <access_token>` — OAuth 2.0 authorization-code access
19
+ * token. Minted/refreshed via the shared {@link OAuth2TokenManager} (the
20
+ * `refresh_token` grant against `https://oauth2.sky.blackbaud.com/token`); SKY
21
+ * API is a CONFIDENTIAL app so the exchange carries client_id/client_secret and
22
+ * returns a rotating refresh_token. No inline crypto — the manager owns the
23
+ * token round-trip.
24
+ * 2. `bb-api-subscription-key: <subscription_key>` — the developer account's SKY API
25
+ * subscription key. Vendor docs PROSE title-cases it `Bb-Api-Subscription-Key`;
26
+ * vendor SAMPLE CODE (2 Blackbaud-owned GitHub tutorial repos) sends the literal
27
+ * lowercase `bb-api-subscription-key` — HTTP header names are case-insensitive
28
+ * (RFC 7230), and we send exactly what a working vendor client transmits.
29
+ * Both are injected by {@link BuildHeaders}.
30
+ *
31
+ * ── BASE URL / PER-FAMILY PATHS ─────────────────────────────────────────────
32
+ * SKY API versions each product family via a `/{family}/v1/` path segment
33
+ * (Configuration.APIVersioningStrategy = url-path): `/constituent/v1`, `/gift/v1`,
34
+ * `/fundraising/v1`, `/opportunity/v1`, `/commpref/v1`, `/nxt-data-integration/v1`,
35
+ * `/gift-batch/v1`. Every IO's `APIPath` already carries its family prefix, so the
36
+ * host is a SINGLE constant (`https://api.sky.blackbaud.com`) and {@link GetBaseURL}
37
+ * returns it; the base class concatenates `host + APIPath` (family prefix included).
38
+ * The host is overridable via Configuration.ApiBaseUrl for sandbox/regional tenants.
39
+ *
40
+ * ── DISCOVERY (mechanism only — NO baked catalog) ───────────────────────────
41
+ * The object/field universe is credential-free-documented (the 4 in-scope SKY API
42
+ * OpenAPI specs), so it is seeded as Declared metadata in the integration file.
43
+ * This connector carries NO object/field catalog constant: {@link DiscoverObjects} /
44
+ * {@link DiscoverFields} inherit the base cache-driven implementation that reads the
45
+ * Declared metadata (the sanctioned "case-1 → Declared metadata" mechanism).
46
+ *
47
+ * ── PAGINATION (SKY API limit/offset envelope) ──────────────────────────────
48
+ * Offset pagination via `limit`/`offset` query params; the collection response is
49
+ * `{ count, value: [...] }` with an optional `next_link` hypermedia accelerator
50
+ * (Configuration.PaginationDefaults). The base's Offset `BuildPaginatedURL` already
51
+ * emits `offset=X&limit=Y` (SKY API's exact params), so it is NOT overridden;
52
+ * {@link NormalizeResponse} unwraps `value` and {@link ExtractPaginationInfo} drives
53
+ * the loop off `next_link` + `count`.
54
+ *
55
+ * ── INCREMENTAL SYNC (request/response field-name SPLIT) ─────────────────────
56
+ * Incremental IOs (constituent, gift, fundraising_*, opportunity) filter on the
57
+ * REQUEST query param `last_modified=<watermark>` but track the new high-watermark
58
+ * from the RESPONSE record field `date_modified` — two DIFFERENT names
59
+ * (Configuration.IncrementalSyncNote). {@link FetchChanges} injects `last_modified`
60
+ * for those IOs and computes `NewWatermarkValue` from the max `date_modified` seen.
61
+ *
62
+ * ── CRUD ────────────────────────────────────────────────────────────────────
63
+ * Generic per-operation CRUD from {@link BaseRESTIntegrationConnector} (reads
64
+ * Create/Update APIPath/Method/BodyShape/IDLocation off each IO row) is used as-is;
65
+ * create fails LOUDLY on an empty response ID via `BuildCreatedResult`. Constituent
66
+ * create is the ONE genuinely-idiosyncratic write (split virtual individual/org
67
+ * endpoints, no generic create in v1) — {@link CreateRecord} overrides ONLY that
68
+ * object and delegates everything else to the generic path.
69
+ *
70
+ * ── RATE LIMIT ──────────────────────────────────────────────────────────────
71
+ * 10 req/s, 25,000 calls/day Standard tier (Configuration.RateLimitPolicy, current
72
+ * live vendor value). Surfaced via {@link RateLimitPolicy} + {@link ExtractRetryAfterMs}
73
+ * (SKY API returns a `Retry-After` header in seconds alongside a 429/403 quota body).
74
+ */
75
+ let BlackbaudConnector = class BlackbaudConnector extends BaseRESTIntegrationConnector {
76
+ constructor() {
77
+ super(...arguments);
78
+ /** OAuth2 token manager (one per connector instance; caches + refreshes the access token). */
79
+ this.tokenManager = new OAuth2TokenManager();
80
+ /**
81
+ * The `last_modified=<watermark>` query fragment to append to the NEXT flat paginated request,
82
+ * set for the duration of a SINGLE incremental {@link FetchChanges} call. Consumed by
83
+ * {@link AppendDefaultQueryParams}. Single-threaded: the engine awaits each FetchChanges fully
84
+ * before the next, and incremental IOs are all FLAT (no concurrent parent-iteration), so this
85
+ * per-call field never races across objects. Cleared in a `finally`.
86
+ */
87
+ this.pendingWatermarkFilter = null;
88
+ }
89
+ /** Verbatim three-way invariant name: IntegrationName getter === MJ: Integrations.Name ('blackbaud'). */
90
+ get IntegrationName() {
91
+ return 'blackbaud';
92
+ }
93
+ /**
94
+ * IntrospectSchema — pure WIRING of MJ's existing sampler into the declared catalog (the connector
95
+ * sample-union standard; see CONNECTOR_DISCOVERY_STANDARD.md). This connector adds NO discovery,
96
+ * merge, or sync logic — it only wires `DiscoverFieldsViaFetch` (MJ's sampler) into IntrospectSchema.
97
+ *
98
+ * `super.IntrospectSchema` yields the cache-driven Declared catalog (no measured widths). For each
99
+ * object we then call MJ's `DiscoverFieldsViaFetch` — MJ's own read-path sampler that measures real
100
+ * field widths and surfaces custom columns — and the shared PURE `mergeDeclaredWithSampledFields`
101
+ * unions the two by field name (adopt MJ's measured width; append MJ-discovered custom columns). MJ
102
+ * owns everything else (measurement, type/PK inference, persistence, reconcile, sync).
103
+ *
104
+ * Recursion note: `DiscoverFieldsViaFetch` falls back to the UNCHANGED `DiscoverFields` (cache-driven)
105
+ * when the read path can't run — never back into THIS method — so there is no infinite recursion.
106
+ * This connector does NOT override `DiscoverFields` to call any ViaFetch/ViaStream.
107
+ *
108
+ * Robustness: objects are sampled IN PARALLEL under a small bounded pool; any per-object failure
109
+ * keeps that object's declared fields, so a single bad sample never breaks introspection.
110
+ */
111
+ async IntrospectSchema(companyIntegration, contextUser) {
112
+ const schema = await super.IntrospectSchema(companyIntegration, contextUser);
113
+ await runBounded(schema.Objects, 8, async (obj) => {
114
+ try {
115
+ const sampled = await this.DiscoverFieldsViaFetch(companyIntegration, obj.ExternalName, contextUser);
116
+ obj.Fields = mergeDeclaredWithSampledFields(obj.Fields, sampled);
117
+ }
118
+ catch {
119
+ // Keep this object's declared fields — sampling is best-effort and never breaks introspection.
120
+ }
121
+ });
122
+ return schema;
123
+ }
124
+ // ─── Capability surface ──────────────────────────────────────────────────
125
+ // The Declared metadata drives per-object capability (SupportsCreate/Update on each IO row);
126
+ // these connector-level getters affirm the connector CAN do the verb so the generic per-operation
127
+ // CRUD path is reachable. Delete is NOT surfaced: the frozen contract found DELETE support sparse
128
+ // (a single constituent-sub-object endpoint), and NO IO row carries DeleteAPIPath/DeleteMethod —
129
+ // so SupportsDelete stays false (null-capability honesty; a true getter with null columns crashes).
130
+ get SupportsCreate() { return true; }
131
+ get SupportsUpdate() { return true; }
132
+ // ─── Auth ────────────────────────────────────────────────────────────────
133
+ /**
134
+ * Mints/refreshes the OAuth2 access token AND resolves the subscription key. Both credential
135
+ * parts ride the returned auth context so {@link BuildHeaders} can inject both on every request.
136
+ */
137
+ async Authenticate(companyIntegration, contextUser) {
138
+ const creds = await this.LoadCredentials(companyIntegration, contextUser);
139
+ // Prefer refreshing an access token via the shared manager (confidential app → refresh_token
140
+ // grant). When no client_id/refresh_token is available but a static access token is provided,
141
+ // fall back to sending that token directly (e.g. reference-mode / short-lived test token).
142
+ let token = creds.AccessToken;
143
+ if (creds.ClientID && creds.ClientSecret && creds.RefreshToken) {
144
+ const minted = await this.tokenManager.GetAccessToken({
145
+ TokenURL: creds.TokenURL,
146
+ ClientId: creds.ClientID,
147
+ ClientSecret: creds.ClientSecret,
148
+ RefreshToken: creds.RefreshToken,
149
+ UseBasicAuth: true, // SKY API accepts client auth as HTTP Basic on the token endpoint
150
+ }, 'refresh_token');
151
+ token = minted.AccessToken;
152
+ }
153
+ if (!token) {
154
+ throw new Error('No Blackbaud access token available — provide (ClientID + ClientSecret + RefreshToken) to refresh, ' +
155
+ 'or a static AccessToken, on the credential or Configuration JSON.');
156
+ }
157
+ if (!creds.SubscriptionKey) {
158
+ throw new Error('No Blackbaud SubscriptionKey found — the bb-api-subscription-key header is required on every SKY API call.');
159
+ }
160
+ return {
161
+ Token: token,
162
+ TokenType: 'Bearer',
163
+ SubscriptionKey: creds.SubscriptionKey,
164
+ ApiBaseUrl: creds.ApiBaseUrl,
165
+ };
166
+ }
167
+ /**
168
+ * Builds request headers with BOTH required SKY API credential parts. `bb-api-subscription-key`
169
+ * casing matches the vendor's own sample code (case-insensitive on the wire per RFC 7230).
170
+ */
171
+ BuildHeaders(auth) {
172
+ const bb = auth;
173
+ return {
174
+ 'Authorization': `Bearer ${bb.Token}`,
175
+ 'bb-api-subscription-key': bb.SubscriptionKey,
176
+ 'Accept': 'application/json',
177
+ };
178
+ }
179
+ // ─── Base URL (single host; family prefix rides each IO's APIPath) ─────────
180
+ GetBaseURL(_companyIntegration, auth) {
181
+ const bb = auth;
182
+ const host = bb.ApiBaseUrl ?? BLACKBAUD_API_HOST;
183
+ return host.replace(/\/+$/, '');
184
+ }
185
+ // ─── TestConnection ────────────────────────────────────────────────────────
186
+ async TestConnection(companyIntegration, contextUser) {
187
+ try {
188
+ const auth = await this.Authenticate(companyIntegration, contextUser);
189
+ const headers = this.BuildHeaders(auth);
190
+ const url = `${this.GetBaseURL(companyIntegration, auth)}/constituent/v1/constituents?limit=1`;
191
+ const response = await this.MakeHTTPRequest(auth, url, 'GET', headers);
192
+ if (response.Status >= 200 && response.Status < 300) {
193
+ const body = (response.Body ?? {});
194
+ const count = typeof body.count === 'number' ? body.count : undefined;
195
+ return {
196
+ Success: true,
197
+ Message: `Connected to Blackbaud SKY API${count != null ? ` — ${count} constituents visible` : ''}.`,
198
+ };
199
+ }
200
+ return {
201
+ Success: false,
202
+ Message: `Blackbaud SKY API returned HTTP ${response.Status}: ${this.ExtractErrorMessage(response) ?? 'authentication or subscription-key failure'}.`,
203
+ };
204
+ }
205
+ catch (err) {
206
+ return { Success: false, Message: err instanceof Error ? err.message : String(err) };
207
+ }
208
+ }
209
+ // ─── Response parsing (SKY API `{ count, value: [...] }` envelope) ──────────
210
+ NormalizeResponse(rawBody, responseDataKey) {
211
+ if (rawBody == null)
212
+ return [];
213
+ if (Array.isArray(rawBody))
214
+ return rawBody;
215
+ if (typeof rawBody !== 'object')
216
+ return [];
217
+ const body = rawBody;
218
+ // Vendor-declared envelope key wins when the IO names one; else SKY API's canonical `value`.
219
+ const key = responseDataKey ?? 'value';
220
+ if (Array.isArray(body[key]))
221
+ return body[key];
222
+ if (Array.isArray(body['value']))
223
+ return body['value'];
224
+ // A single-record (get-one) response has no envelope — treat the object itself as one record.
225
+ return Object.keys(body).length > 0 ? [body] : [];
226
+ }
227
+ /**
228
+ * Drives the offset-pagination loop from the SKY API envelope: continue while a `next_link` is
229
+ * present, advancing `offset` by the count of records seen. When `next_link` is absent, the page
230
+ * is the last one. `count` is the total (used only for diagnostics).
231
+ */
232
+ ExtractPaginationInfo(rawBody, _paginationType, _currentPage, currentOffset, _pageSize) {
233
+ const body = (rawBody ?? {});
234
+ const pageCount = Array.isArray(body.value) ? body.value.length : 0;
235
+ const hasMore = typeof body.next_link === 'string' && body.next_link.length > 0;
236
+ return {
237
+ HasMore: hasMore,
238
+ NextOffset: currentOffset + pageCount,
239
+ TotalRecords: typeof body.count === 'number' ? body.count : undefined,
240
+ };
241
+ }
242
+ // ─── Incremental watermark injection ────────────────────────────────────────
243
+ /**
244
+ * Wraps the base fetch to (1) inject the `last_modified=<watermark>` filter for incremental IOs
245
+ * and (2) compute `NewWatermarkValue` from the max RESPONSE `date_modified` — the request/response
246
+ * field-name split the frozen contract calls out. Everything else (pagination loop, parent-chain
247
+ * walking, transform, PK assembly) is the base's; this only adds the watermark param + high-water
248
+ * tracking, so nested-graph objects and generic flat objects flow through unchanged.
249
+ */
250
+ async FetchChanges(ctx) {
251
+ const obj = this.GetCachedObject(ctx.CompanyIntegration.IntegrationID, ctx.ObjectName);
252
+ const watermarkField = obj.IncrementalWatermarkField ?? null;
253
+ const wantsWatermark = obj.SupportsIncrementalSync && !!ctx.WatermarkValue && !!watermarkField;
254
+ this.pendingWatermarkFilter = wantsWatermark
255
+ ? `last_modified=${encodeURIComponent(String(ctx.WatermarkValue))}`
256
+ : null;
257
+ try {
258
+ const batch = await super.FetchChanges(ctx);
259
+ if (obj.SupportsIncrementalSync && watermarkField) {
260
+ const maxSeen = this.MaxWatermark(batch.Records, watermarkField);
261
+ if (maxSeen)
262
+ batch.NewWatermarkValue = maxSeen;
263
+ }
264
+ return batch;
265
+ }
266
+ finally {
267
+ this.pendingWatermarkFilter = null;
268
+ }
269
+ }
270
+ /**
271
+ * Appends the pending incremental watermark filter (set by {@link FetchChanges}) to a flat
272
+ * paginated request, on top of whatever DefaultQueryParams the base appends. Skips injection when
273
+ * the URL already carries `last_modified` (idempotent across the pagination loop's pages).
274
+ */
275
+ AppendDefaultQueryParams(url, obj) {
276
+ let out = super.AppendDefaultQueryParams(url, obj);
277
+ if (this.pendingWatermarkFilter && !/[?&]last_modified=/i.test(out)) {
278
+ out += (out.includes('?') ? '&' : '?') + this.pendingWatermarkFilter;
279
+ }
280
+ return out;
281
+ }
282
+ /** Highest RESPONSE `date_modified` (lexical ISO-8601 compare) across a batch, or undefined. */
283
+ MaxWatermark(records, watermarkField) {
284
+ let max;
285
+ for (const rec of records) {
286
+ const v = rec.Fields?.[watermarkField];
287
+ if (typeof v === 'string' && v.length > 0 && (!max || v > max))
288
+ max = v;
289
+ }
290
+ return max;
291
+ }
292
+ // ─── Idiosyncratic write: Constituent split-virtual create ──────────────────
293
+ /**
294
+ * Constituent create is genuinely idiosyncratic: SKY API v1 has NO generic
295
+ * `POST /constituent/v1/constituents` — creation goes through split virtual endpoints
296
+ * (`/constituent/v1/virtual/individuals` vs `/virtual/organizations`), chosen by the record's
297
+ * `type` (Configuration.createMechanism = 'split-virtual-endpoints'). All OTHER objects delegate
298
+ * to the base's generic per-operation CRUD; this override only special-cases `constituent`.
299
+ * Still routes through {@link BuildCreatedResult} so an empty-ID create fails LOUDLY (write-path invariant).
300
+ */
301
+ async CreateRecord(ctx) {
302
+ if (ctx.ObjectName.toLowerCase() !== 'constituent') {
303
+ return super.CreateRecord(ctx);
304
+ }
305
+ const ci = ctx.CompanyIntegration;
306
+ const contextUser = ctx.ContextUser;
307
+ const auth = await this.Authenticate(ci, contextUser);
308
+ const headers = this.BuildHeaders(auth);
309
+ const type = String(ctx.Attributes['type'] ?? '').toLowerCase();
310
+ const path = type === 'organization'
311
+ ? '/constituent/v1/virtual/organizations'
312
+ : '/constituent/v1/virtual/individuals';
313
+ const url = `${this.GetBaseURL(ci, auth)}${path}`;
314
+ const response = await this.MakeHTTPRequest(auth, url, 'POST', { ...headers, 'Content-Type': 'application/json' }, ctx.Attributes);
315
+ if (response.Status >= 200 && response.Status < 300) {
316
+ const id = this.ExtractIDFromResponse(response, 'body');
317
+ return this.BuildCreatedResult(id, response.Status, ctx.ObjectName);
318
+ }
319
+ return {
320
+ Success: false,
321
+ StatusCode: response.Status,
322
+ ErrorMessage: this.ExtractErrorMessage(response) ?? `HTTP ${response.Status} on constituent create`,
323
+ };
324
+ }
325
+ // ─── Sync-efficiency hooks (filled from the frozen contract's rate-limit facts) ──
326
+ /** SKY API Standard tier: 10 req/s (Configuration.RateLimitPolicy, current live vendor value). */
327
+ get RateLimitPolicy() {
328
+ return { TokensPerSec: 10, Burst: 10 };
329
+ }
330
+ /**
331
+ * SKY API returns a `Retry-After` header (seconds) on 429 (rate limit) and 403 (quota) responses
332
+ * (Configuration.ErrorResponseShapeNote). Parse it to ms so the engine backs off precisely.
333
+ */
334
+ ExtractRetryAfterMs(error) {
335
+ const headers = error?.Headers;
336
+ const raw = headers?.['retry-after'] ?? headers?.['Retry-After'];
337
+ if (typeof raw === 'string') {
338
+ const secs = parseInt(raw, 10);
339
+ if (Number.isFinite(secs) && secs >= 0)
340
+ return secs * 1000;
341
+ }
342
+ return undefined;
343
+ }
344
+ // ─── HTTP transport ──────────────────────────────────────────────────────
345
+ async MakeHTTPRequest(_auth, url, method, headers, body) {
346
+ const controller = new AbortController();
347
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
348
+ try {
349
+ const opts = { method, headers, signal: controller.signal };
350
+ if (body != null && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
351
+ opts.body = JSON.stringify(body);
352
+ }
353
+ const response = await fetch(url, opts);
354
+ const parsedBody = await this.ParseBody(response);
355
+ const hdrs = {};
356
+ response.headers.forEach((v, k) => { hdrs[k.toLowerCase()] = v; });
357
+ return { Status: response.status, Body: parsedBody, Headers: hdrs };
358
+ }
359
+ catch (err) {
360
+ if (err instanceof Error && err.name === 'AbortError') {
361
+ throw new Error(`Blackbaud SKY API request timed out after ${REQUEST_TIMEOUT_MS}ms: ${url}`);
362
+ }
363
+ throw err;
364
+ }
365
+ finally {
366
+ clearTimeout(timeout);
367
+ }
368
+ }
369
+ async ParseBody(response) {
370
+ const ct = response.headers.get('content-type') ?? '';
371
+ if (ct.includes('json')) {
372
+ try {
373
+ return await response.json();
374
+ }
375
+ catch {
376
+ return null;
377
+ }
378
+ }
379
+ const text = await response.text();
380
+ return text.length > 0 ? text : null;
381
+ }
382
+ // ─── Credential resolution (secret bytes never leave this scope) ────────────
383
+ /**
384
+ * Resolves the two credential parts (OAuth2 client/refresh + subscription key) plus non-secret
385
+ * host config from the linked Credential entity, falling back to CompanyIntegration.Configuration.
386
+ */
387
+ async LoadCredentials(companyIntegration, contextUser) {
388
+ let creds = {};
389
+ if (companyIntegration.CredentialID) {
390
+ const fromCred = await this.LoadFromCredentialEntity(companyIntegration.CredentialID, contextUser);
391
+ if (fromCred)
392
+ creds = { ...creds, ...this.compact(fromCred) };
393
+ }
394
+ // Non-secret host config (and, for reference mode, a static token) may also live on Configuration.
395
+ if (companyIntegration.Configuration) {
396
+ const fromConfig = this.ParseConfigJson(companyIntegration.Configuration);
397
+ if (fromConfig) {
398
+ // Credential entity wins for secrets already resolved; Configuration fills the gaps.
399
+ for (const [k, v] of Object.entries(this.compact(fromConfig))) {
400
+ if (creds[k] == null)
401
+ creds[k] = v;
402
+ }
403
+ }
404
+ }
405
+ if (!creds.SubscriptionKey && !creds.AccessToken && !creds.ClientID) {
406
+ throw new Error('No Blackbaud credentials found — set SubscriptionKey plus either (ClientID+ClientSecret+RefreshToken) ' +
407
+ 'or a static AccessToken on the credential Values or CompanyIntegration.Configuration JSON.');
408
+ }
409
+ return {
410
+ ClientID: creds.ClientID ?? '',
411
+ ClientSecret: creds.ClientSecret ?? '',
412
+ SubscriptionKey: creds.SubscriptionKey ?? '',
413
+ AccessToken: creds.AccessToken ?? '',
414
+ RefreshToken: creds.RefreshToken ?? '',
415
+ TokenURL: creds.TokenURL ?? BLACKBAUD_TOKEN_URL,
416
+ ApiBaseUrl: creds.ApiBaseUrl,
417
+ };
418
+ }
419
+ /** Loads credential fields from a Credential entity's Values JSON. */
420
+ async LoadFromCredentialEntity(credentialID, contextUser, provider) {
421
+ const md = provider ?? new Metadata();
422
+ const credential = await md.GetEntityObject('MJ: Credentials', contextUser);
423
+ const loaded = await credential.Load(credentialID);
424
+ if (!loaded || !credential.Values)
425
+ return null;
426
+ return this.ParseConfigJson(credential.Values);
427
+ }
428
+ /** Parses a JSON string into credential/config fields (tolerant of casing aliases). */
429
+ ParseConfigJson(json) {
430
+ try {
431
+ const result = BlackbaudConfigSchema.safeParse(JSON.parse(json));
432
+ if (!result.success)
433
+ return null;
434
+ const p = result.data;
435
+ return {
436
+ ClientID: p.ClientID ?? p.clientId ?? p.ClientId,
437
+ ClientSecret: p.ClientSecret ?? p.clientSecret,
438
+ SubscriptionKey: p.SubscriptionKey ?? p.subscriptionKey ?? p['bb-api-subscription-key'],
439
+ AccessToken: p.AccessToken ?? p.accessToken ?? p.Token ?? p.token,
440
+ RefreshToken: p.RefreshToken ?? p.refreshToken,
441
+ TokenURL: p.TokenURL ?? p.tokenUrl,
442
+ ApiBaseUrl: p.ApiBaseUrl ?? p.apiBaseUrl,
443
+ };
444
+ }
445
+ catch {
446
+ return null;
447
+ }
448
+ }
449
+ /** Drops undefined/empty-string entries so a later fallback source can fill them. */
450
+ compact(o) {
451
+ const out = {};
452
+ for (const [k, v] of Object.entries(o)) {
453
+ if (v != null && v !== '')
454
+ out[k] = v;
455
+ }
456
+ return out;
457
+ }
458
+ };
459
+ BlackbaudConnector = __decorate([
460
+ RegisterClass(BaseIntegrationConnector, '@memberjunction/connector-blackbaud')
461
+ ], BlackbaudConnector);
462
+ export { BlackbaudConnector };
463
+ // ─── Module-level constants (mechanism, NOT a catalog) ────────────────────────
464
+ /** SKY API host root; the `/{family}/v1/...` path rides each IO's APIPath. Overridable via Configuration.ApiBaseUrl. */
465
+ const BLACKBAUD_API_HOST = 'https://api.sky.blackbaud.com';
466
+ /** OAuth2 token endpoint (authorization-code / refresh_token grant). */
467
+ const BLACKBAUD_TOKEN_URL = 'https://oauth2.sky.blackbaud.com/token';
468
+ /** Per-request timeout. */
469
+ const REQUEST_TIMEOUT_MS = 30_000;
470
+ /** Zod schema for the credential/Configuration JSON shape (tolerant of casing aliases). */
471
+ const BlackbaudConfigSchema = z.object({
472
+ ClientID: z.string().optional(),
473
+ clientId: z.string().optional(),
474
+ ClientId: z.string().optional(),
475
+ ClientSecret: z.string().optional(),
476
+ clientSecret: z.string().optional(),
477
+ SubscriptionKey: z.string().optional(),
478
+ subscriptionKey: z.string().optional(),
479
+ 'bb-api-subscription-key': z.string().optional(),
480
+ AccessToken: z.string().optional(),
481
+ accessToken: z.string().optional(),
482
+ Token: z.string().optional(),
483
+ token: z.string().optional(),
484
+ RefreshToken: z.string().optional(),
485
+ refreshToken: z.string().optional(),
486
+ TokenURL: z.string().optional(),
487
+ tokenUrl: z.string().optional(),
488
+ ApiBaseUrl: z.string().optional(),
489
+ apiBaseUrl: z.string().optional(),
490
+ });
491
+ // Tree-shaking prevention — REQUIRED so @RegisterClass survives bundling.
492
+ export function LoadBlackbaudConnector() { }
493
+ /**
494
+ * Minimal bounded promise-pool: runs `worker` over `items` with at most `limit` in flight.
495
+ * (BaseRESTIntegrationConnector.RunBounded is private, so the sample-union override brings its own
496
+ * tiny pool — this is local plumbing, NOT a shared framework artifact.)
497
+ */
498
+ async function runBounded(items, limit, worker) {
499
+ const queue = [...items];
500
+ const size = Math.max(1, Math.min(limit, queue.length));
501
+ const runners = Array.from({ length: size }, async () => {
502
+ for (let next = queue.shift(); next !== undefined; next = queue.shift()) {
503
+ await worker(next);
504
+ }
505
+ });
506
+ await Promise.all(runners);
507
+ }
508
+ //# sourceMappingURL=BlackbaudConnector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlackbaudConnector.js","sourceRoot":"","sources":["../src/BlackbaudConnector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from './BlackbaudConnector.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 './BlackbaudConnector.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,yBAAyB,CAAC;AAExC;oGACoG;AACpG,MAAM,UAAU,iBAAiB,KAAiD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@memberjunction/connector-blackbaud",
3
+ "version": "1.2.0",
4
+ "private": false,
5
+ "description": "MemberJunction Blackbaud connector.",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "/dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc && tsc-alias -f",
14
+ "test": "vitest run --passWithNoTests"
15
+ },
16
+ "author": "MemberJunction.com",
17
+ "license": "ISC",
18
+ "peerDependencies": {
19
+ "@memberjunction/core": ">=5.42.0 <6.0.0",
20
+ "@memberjunction/core-entities": ">=5.42.0 <6.0.0",
21
+ "@memberjunction/global": ">=5.42.0 <6.0.0",
22
+ "@memberjunction/integration-engine": ">=5.42.0 <6.0.0"
23
+ },
24
+ "dependencies": {
25
+ "@memberjunction/connector-schema-merge": "^1.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "24.10.11",
29
+ "tsc-alias": "^1.8.16",
30
+ "typescript": "^5.9.3",
31
+ "vitest": "^4.0.18",
32
+ "@memberjunction/core": "^5.42.0",
33
+ "@memberjunction/core-entities": "^5.42.0",
34
+ "@memberjunction/global": "^5.42.0",
35
+ "@memberjunction/integration-engine": "^5.42.0",
36
+ "@memberjunction/connector-schema-merge": "^1.0.0"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/MemberJunction/Integrations"
41
+ }
42
+ }