@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.
- package/dist/BlackbaudConnector.d.ts +173 -0
- package/dist/BlackbaudConnector.js +508 -0
- package/dist/BlackbaudConnector.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|