@memberjunction/connector-impexium 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.
- package/dist/ImpexiumConnector.d.ts +226 -0
- package/dist/ImpexiumConnector.js +874 -0
- package/dist/ImpexiumConnector.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 +43 -0
|
@@ -0,0 +1,226 @@
|
|
|
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 CreateRecordContext, type UpdateRecordContext, type DeleteRecordContext, type CRUDResult, type SourceSchemaInfo } from '@memberjunction/integration-engine';
|
|
4
|
+
/**
|
|
5
|
+
* re:Members AMS (Impexium) connector — extends BaseRESTIntegrationConnector (REST/JSON over HTTP).
|
|
6
|
+
* See the design note above for the full protocol surface. Registered under the TS class symbol so the
|
|
7
|
+
* sandbox verification ladder / T1 three-way check stay green; the OpenAppPublish step rewrites the key.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ImpexiumConnector extends BaseRESTIntegrationConnector {
|
|
10
|
+
/** Cached auth context (with token expiry) for the lifetime of a sync run; re-minted on expiry. */
|
|
11
|
+
private cachedAuth;
|
|
12
|
+
/** Watermark value in effect for the current FetchChanges call — emitted as the incremental param. */
|
|
13
|
+
private currentWatermark;
|
|
14
|
+
/** Verbatim MJ: Integrations.Name. The T1 three-way name check statically parses this literal. */
|
|
15
|
+
get IntegrationName(): string;
|
|
16
|
+
get SupportsCreate(): boolean;
|
|
17
|
+
get SupportsUpdate(): boolean;
|
|
18
|
+
get SupportsDelete(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Discovery is NON-authoritative: DiscoverObjects / IntrospectSchema are cache-driven (they re-read
|
|
21
|
+
* persisted ACTIVE Declared metadata, NOT a live full-gamut enumeration). Impexium exposes no
|
|
22
|
+
* describe-all / complete-gamut endpoint (GET Setup/customfields only enumerates the tenant's custom
|
|
23
|
+
* FIELD catalog, not a whole object/field gamut). Absence in a refresh proves nothing → never
|
|
24
|
+
* deactivate. Matches Configuration.DiscoveryIsAuthoritative=false in the frozen contract.
|
|
25
|
+
*/
|
|
26
|
+
get DiscoveryIsAuthoritative(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Sample-union field enrichment (the connector standard). Declared metadata is authoritative but
|
|
29
|
+
* incomplete — it misses a tenant's CUSTOM / undeclared columns (Impexium's per-tenant custom fields).
|
|
30
|
+
* Wire the required enrichment: take the base cache-driven schema, then per object UNION the declared
|
|
31
|
+
* field set with what a live read actually returns (`mergeDeclaredWithSampledFields` — never-shrink,
|
|
32
|
+
* declared-wins, capacities widened). Best-effort + parallel: a per-object sample failure leaves that
|
|
33
|
+
* object's declared fields untouched. Overrides IntrospectSchema (NEVER DiscoverFields — that would
|
|
34
|
+
* recurse into DiscoverFieldsViaFetch's fallback).
|
|
35
|
+
*/
|
|
36
|
+
IntrospectSchema(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<SourceSchemaInfo>;
|
|
37
|
+
/** True when any cached IntegrationObject satisfies the predicate. Fail-safe read-only ([]→false). */
|
|
38
|
+
private anyObjectDeclares;
|
|
39
|
+
/**
|
|
40
|
+
* Authenticate against the RAW tenant host. Candidate A (default) runs the app + user handshake and
|
|
41
|
+
* caches the Access-Token + App-Token; candidate B ('apikey') resolves a single x-api-key. The cached
|
|
42
|
+
* context is re-minted when its tokens near expiry. NEVER inline crypto — the handshake is a plain
|
|
43
|
+
* credential POST returning tokens.
|
|
44
|
+
*/
|
|
45
|
+
protected Authenticate(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<RESTAuthContext>;
|
|
46
|
+
/**
|
|
47
|
+
* Attaches the auth headers per model: candidate A sends BOTH tokens (Access-Token + App-Token,
|
|
48
|
+
* header names Configuration-overridable); candidate B sends the single x-api-key.
|
|
49
|
+
*/
|
|
50
|
+
protected BuildHeaders(auth: RESTAuthContext): Record<string, string>;
|
|
51
|
+
/** Per-tenant host resolved in Authenticate — ZERO host baked in code. */
|
|
52
|
+
protected GetBaseURL(_companyIntegration: MJCompanyIntegrationEntity, auth: RESTAuthContext): string;
|
|
53
|
+
/** HTTP transport (fetch). Owns the wire boundary; test subclasses override this to capture requests. */
|
|
54
|
+
protected MakeHTTPRequest(auth: RESTAuthContext, url: string, method: string, headers: Record<string, string>, body?: unknown): Promise<RESTResponse>;
|
|
55
|
+
/**
|
|
56
|
+
* Strips Impexium's list envelope `{ pageNumber, dataList: T[] }` to the dataList array
|
|
57
|
+
* (ResponseDataKey='dataList'). Falls back to a bare array, the universal dataList key, or a single
|
|
58
|
+
* detail object. The FULL source record passes through (no field filtering).
|
|
59
|
+
*/
|
|
60
|
+
protected NormalizeResponse(rawBody: unknown, responseDataKey: string | null): Record<string, unknown>[];
|
|
61
|
+
/**
|
|
62
|
+
* Page-in-path pagination: the envelope carries NO hasMore/totalCount field, so termination is
|
|
63
|
+
* empty-dataList. HasMore is true iff the current page returned records; the loop advances the page
|
|
64
|
+
* number (rendered into the path by BuildPaginatedURL) and stops when a page comes back empty.
|
|
65
|
+
*/
|
|
66
|
+
protected ExtractPaginationInfo(rawBody: unknown, paginationType: PaginationType, currentPage: number, _currentOffset: number, _pageSize: number): PaginationState;
|
|
67
|
+
/**
|
|
68
|
+
* Impexium puts the page number IN THE PATH (a {Page Number}/{pageNumber}/{Page} segment), NEVER as a
|
|
69
|
+
* `?page=` query param. Substitute the page into that path segment and return — no query pagination.
|
|
70
|
+
*/
|
|
71
|
+
protected BuildPaginatedURL(basePath: string, obj: MJIntegrationObjectEntity, page: number, _offset: number, _cursor?: string, _effectivePageSize?: number): string;
|
|
72
|
+
/**
|
|
73
|
+
* Injects the incremental watermark filter when a sync is active. The param NAME is fully
|
|
74
|
+
* METADATA-DRIVEN: it comes from the IO's IncrementalWatermarkField (changedSince / purchasedSince /
|
|
75
|
+
* registeredSince / cancelledSince) and is emitted only when SupportsIncrementalSync=true AND a
|
|
76
|
+
* watermark value is in context. Runs on top of the base's DefaultQueryParams handling.
|
|
77
|
+
*/
|
|
78
|
+
protected AppendDefaultQueryParams(url: string, obj: MJIntegrationObjectEntity): string;
|
|
79
|
+
/**
|
|
80
|
+
* Fetch a batch. Flat top-level objects delegate to the base GET loop (which uses the BuildPaginatedURL
|
|
81
|
+
* / ExtractPaginationInfo / AppendDefaultQueryParams overrides above for page-in-path + incremental).
|
|
82
|
+
* Nested objects (whose frozen paths carry space-containing parent placeholders the base's `\w+`
|
|
83
|
+
* template detector can't resolve) are walked here: resolve the parent sibling IO from the preceding
|
|
84
|
+
* path RESOURCE segment, iterate synced parent records (bounded + keyset-resumable), substitute, then
|
|
85
|
+
* page the leaf. The watermark advances (partial-failure-safe) only on a fully-drained final batch.
|
|
86
|
+
*/
|
|
87
|
+
FetchChanges(ctx: FetchContext): Promise<FetchBatchResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Walks a nested object's access path: resolve the PRIMARY parent token → sibling IO (by the path
|
|
90
|
+
* segment preceding it), load the synced parent record IDs, process a bounded, keyset-resumable batch
|
|
91
|
+
* of parents, substitute + page each leaf. Surfaces a FetchWarning (never a silent empty) when the
|
|
92
|
+
* parent can't be resolved (Courses / tasks Users are not emitted IOs), when no parents are synced yet,
|
|
93
|
+
* or when a secondary parent token remains unresolved.
|
|
94
|
+
*/
|
|
95
|
+
private fetchWithParents;
|
|
96
|
+
/**
|
|
97
|
+
* Drains ALL pages of a single resolved (parent-substituted) leaf path via page-in-path pagination,
|
|
98
|
+
* or a single request when the object is non-paginated. Stops on an empty dataList; skips gracefully
|
|
99
|
+
* on 403/404 (permission-gated / absent sub-resource). Injects the incremental watermark param.
|
|
100
|
+
*/
|
|
101
|
+
private fetchLeafPages;
|
|
102
|
+
/**
|
|
103
|
+
* Create. OVERRIDDEN because Impexium's create paths carry PARENT template vars ({code}/{id}/
|
|
104
|
+
* {Record Number}) filled from the record's own attributes — the generic base path leaves them literal.
|
|
105
|
+
* The body shape + loud-on-empty-id BuildCreatedResult are preserved. Top-level creates with no path
|
|
106
|
+
* vars (Individuals, Organizations, Tasks, Requests) behave exactly as the generic path would.
|
|
107
|
+
*/
|
|
108
|
+
CreateRecord(ctx: CreateRecordContext): Promise<CRUDResult>;
|
|
109
|
+
/**
|
|
110
|
+
* Update. OVERRIDDEN so parent path vars are filled from attributes and the path id ({id}/{Task Number}/
|
|
111
|
+
* {memberRecordNumber}/…) is filled from the ExternalID when UpdateIDLocation='path'. When
|
|
112
|
+
* UpdateIDLocation='body', the id is not path-substituted (the record body carries it).
|
|
113
|
+
*/
|
|
114
|
+
UpdateRecord(ctx: UpdateRecordContext): Promise<CRUDResult>;
|
|
115
|
+
/**
|
|
116
|
+
* Delete. OVERRIDDEN so the path id ({Category Code}) is filled from the ExternalID when
|
|
117
|
+
* DeleteIDLocation='path'. NOTE (documented limitation, flagged for T10 live verification): the only
|
|
118
|
+
* two delete-capable IOs (Categories, Links) are narrow sub-resources whose path ALSO carries a parent
|
|
119
|
+
* segment ({Record Number}) that the framework's DeleteRecordContext does not surface — a single-part
|
|
120
|
+
* ExternalID fills only the target id, so a live delete of these sub-resources needs the parent encoded
|
|
121
|
+
* in a composite ExternalID. Links additionally identifies the target via the request BODY
|
|
122
|
+
* (DeleteIDLocation='body'), which the delete context does not carry.
|
|
123
|
+
*/
|
|
124
|
+
DeleteRecord(ctx: DeleteRecordContext): Promise<CRUDResult>;
|
|
125
|
+
/**
|
|
126
|
+
* Reads the created record's id from the Impexium create response. The universal envelope returns the
|
|
127
|
+
* new record under `id`; some sub-resource creates return the record with no server id
|
|
128
|
+
* (CreateIDLocation='n/a') — those route to BuildCreatedResult, which fails loudly on an empty id.
|
|
129
|
+
*/
|
|
130
|
+
protected ExtractIDFromResponse(response: RESTResponse, idLocation: string | null): string | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Tests connectivity by authenticating and reading one page of Individuals. A 2xx confirms the auth
|
|
133
|
+
* flow + tenant host are valid; 401/403 → auth failure; anything else → error. Never mutates.
|
|
134
|
+
*/
|
|
135
|
+
TestConnection(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<ConnectionTestResult>;
|
|
136
|
+
/**
|
|
137
|
+
* Runs the candidate-A app + user handshake: POST {AppId, AppKey, AppName, Email, Password} to the auth
|
|
138
|
+
* path and read the Access-Token + App-Token (+ optional expiry) from the response. UNVERIFIED at plan
|
|
139
|
+
* time — the endpoint path, request field names, response field names and TTL are all overridable, so a
|
|
140
|
+
* live/sandbox probe can settle them without a code change. Plain credential POST; no crypto.
|
|
141
|
+
*/
|
|
142
|
+
private mintTokens;
|
|
143
|
+
/** Derives the token expiry from the handshake response, falling back to the configured/default TTL. */
|
|
144
|
+
private resolveTokenExpiry;
|
|
145
|
+
/** Parses the connection config, preferring the linked Credential entity over the Configuration JSON. */
|
|
146
|
+
private parseConfig;
|
|
147
|
+
/** Loads a credential row and parses its Values JSON to a plain map. */
|
|
148
|
+
private loadCredentialValues;
|
|
149
|
+
/** Builds the typed config from a raw case-insensitive map, applying defaults + validating HostUrl. */
|
|
150
|
+
private buildConfig;
|
|
151
|
+
/** Resolves the per-tenant host (no trailing slash) — never a hardcoded host. */
|
|
152
|
+
private resolveHost;
|
|
153
|
+
/** All `{...}` placeholders in a path (space-tolerant), unwrapped. */
|
|
154
|
+
private allTokens;
|
|
155
|
+
/** True for the page placeholder (`{Page Number}` / `{pageNumber}` / `{Page}`) — space/case tolerant. */
|
|
156
|
+
private isPageToken;
|
|
157
|
+
/** Parent (non-page) placeholders in a path. */
|
|
158
|
+
private parentTokens;
|
|
159
|
+
/** True when the path still carries a page placeholder. */
|
|
160
|
+
private hasPageToken;
|
|
161
|
+
/** Substitutes the page placeholder (space/case-tolerant) with the page number. */
|
|
162
|
+
private substitutePagePlaceholder;
|
|
163
|
+
/**
|
|
164
|
+
* Resolves the parent object name for a template var from the path segment IMMEDIATELY preceding the
|
|
165
|
+
* `{token}` (Impexium's REST convention: the parent resource name precedes its id), matched against a
|
|
166
|
+
* sibling IntegrationObject by name. Returns null when no emitted sibling matches (e.g. Courses, Users).
|
|
167
|
+
* Protected so tests can resolve a parent without a populated engine cache.
|
|
168
|
+
*/
|
|
169
|
+
protected resolveParentObjectName(apiPath: string, token: string, integrationID: string): string | null;
|
|
170
|
+
/**
|
|
171
|
+
* Loads the synced parent record IDs from the local MJ database (via the object's EntityMap → the target
|
|
172
|
+
* entity's PK column). Protected so tests can inject fixed parent IDs without a live RunView.
|
|
173
|
+
*/
|
|
174
|
+
protected loadParentRecordIDs(parentObjectName: string, integrationID: string, contextUser: UserInfo): Promise<string[]>;
|
|
175
|
+
/** Fills a CreateAPIPath's parent template vars from the record's own attributes (case/space-insensitive). */
|
|
176
|
+
private substitutePathVarsFromAttributes;
|
|
177
|
+
/**
|
|
178
|
+
* Fills a write (update/delete) path: parent vars from attributes first, then the remaining path id from
|
|
179
|
+
* the (possibly composite) ExternalID when idLocation is 'path'. When idLocation is 'body'/'header', the
|
|
180
|
+
* id is NOT path-substituted (the record body / a header carries it).
|
|
181
|
+
*/
|
|
182
|
+
private substituteWritePath;
|
|
183
|
+
/** Looks up an attribute by name, matching case- and space-insensitively (`{Record Number}` → recordNumber). */
|
|
184
|
+
private lookupAttr;
|
|
185
|
+
/** Joins a base URL and a path (mirrors the base's private BuildFullURL). */
|
|
186
|
+
private joinURL;
|
|
187
|
+
/** Appends the metadata-driven incremental watermark query param when active. */
|
|
188
|
+
private appendIncrementalParam;
|
|
189
|
+
/** 403/404 are skippable (permission-gated object / absent sub-resource) — degrade to zero rows. */
|
|
190
|
+
private isSkippableStatus;
|
|
191
|
+
/** Throws a descriptive error on a non-2xx (non-skippable) status. */
|
|
192
|
+
private ensureOk;
|
|
193
|
+
/** PK field names in Sequence order; empty when the object declares no PK (→ content-hash identity). */
|
|
194
|
+
private pkFieldNames;
|
|
195
|
+
/**
|
|
196
|
+
* Builds an ExternalRecord with the FULL source record in Fields (full-record pass-through — the
|
|
197
|
+
* framework's custom-column capture diffs keys(Fields) against the field maps). ExternalID is the
|
|
198
|
+
* composite PK when every component is present + non-empty, else a deterministic content hash (which is
|
|
199
|
+
* also stamped into a single empty PK column so PK-less rows remain storable + idempotent). Mirrors the
|
|
200
|
+
* base's ToExternalRecord (which is private) for the parent-iterated path.
|
|
201
|
+
*/
|
|
202
|
+
private buildExternalRecord;
|
|
203
|
+
/** Builds a warning-only fetch result (surfaces a silent-empty as a structured SyncWarning). */
|
|
204
|
+
private warnResult;
|
|
205
|
+
/**
|
|
206
|
+
* The next watermark for a changed-since object is the time this sync STARTED (captured before any
|
|
207
|
+
* fetch), so the next incremental picks up everything modified since — a slight overlap is idempotent.
|
|
208
|
+
* Impexium's list responses carry no reliable single modified-date field to derive it from, so wall-clock
|
|
209
|
+
* start is the safe, monotonic choice. Only ever advances forward.
|
|
210
|
+
*/
|
|
211
|
+
private computeNewWatermark;
|
|
212
|
+
/** Formats a date as the Impexium changed-since format `yyyy-MM-ddTHH:mm:ss` (UTC). */
|
|
213
|
+
private formatWatermark;
|
|
214
|
+
/** Returns the first present, non-empty string value among the given keys. */
|
|
215
|
+
private firstString;
|
|
216
|
+
/** Returns the first present finite number among the given keys. */
|
|
217
|
+
private firstNumber;
|
|
218
|
+
/** Reads an `id`-like value as a string; undefined when absent/empty. */
|
|
219
|
+
private readId;
|
|
220
|
+
/** Stable total order over ID strings — numeric when ALL are integer-like, else lexical. */
|
|
221
|
+
private sortIdsStable;
|
|
222
|
+
/** Total-order comparator matching sortIdsStable, used for keyset resume (id > AfterKeyValue). */
|
|
223
|
+
private compareIds;
|
|
224
|
+
}
|
|
225
|
+
/** Tree-shaking prevention — import and call from the package entry point. */
|
|
226
|
+
export declare function LoadImpexiumConnector(): void;
|
|
@@ -0,0 +1,874 @@
|
|
|
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, RunView } from '@memberjunction/core';
|
|
9
|
+
import { IntegrationEngineBase } from '@memberjunction/integration-engine-base';
|
|
10
|
+
import { BaseIntegrationConnector, BaseRESTIntegrationConnector, computeContentHash, serializeKeyValue, } from '@memberjunction/integration-engine';
|
|
11
|
+
import { mergeDeclaredWithSampledFields } from '@memberjunction/connector-schema-merge';
|
|
12
|
+
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
13
|
+
/** Verbatim MJ: Integrations.Name — part of the T1 three-way name invariant. */
|
|
14
|
+
const INTEGRATION_NAME = 're:Members AMS';
|
|
15
|
+
/**
|
|
16
|
+
* Auth handshake defaults (candidate A). UNVERIFIED at plan time — every one is Configuration-overridable
|
|
17
|
+
* so a live/sandbox probe can correct the exact path / header / field names WITHOUT touching this code.
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_AUTH_PATH = '/api/v1/authenticate';
|
|
20
|
+
const DEFAULT_ACCESS_TOKEN_HEADER = 'Access-Token';
|
|
21
|
+
const DEFAULT_APP_TOKEN_HEADER = 'App-Token';
|
|
22
|
+
const DEFAULT_API_KEY_HEADER = 'x-api-key';
|
|
23
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
24
|
+
/** Fallback token lifetime when the handshake response carries no explicit expiry (conservative). */
|
|
25
|
+
const DEFAULT_TOKEN_TTL_MS = 20 * 60 * 1_000;
|
|
26
|
+
/** Re-mint skew window: re-mint when within this many ms of expiry. */
|
|
27
|
+
const TOKEN_REFRESH_BUFFER_MS = 60_000;
|
|
28
|
+
/** The universal list envelope key. Consistent across all 55 list-shaped responses per the frozen contract. */
|
|
29
|
+
const DATA_LIST_KEY = 'dataList';
|
|
30
|
+
/** Per-call cap on how many PARENTS a nested object iterates before yielding a keyset-resumable batch. */
|
|
31
|
+
const PARENT_BATCH_SIZE = 10;
|
|
32
|
+
/** Hard safety cap on page-loop iterations (termination is normally empty-page). */
|
|
33
|
+
const MAX_LEAF_PAGES = 10_000;
|
|
34
|
+
// ─── ImpexiumConnector ────────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* re:Members AMS (Impexium) connector — extends BaseRESTIntegrationConnector (REST/JSON over HTTP).
|
|
37
|
+
* See the design note above for the full protocol surface. Registered under the TS class symbol so the
|
|
38
|
+
* sandbox verification ladder / T1 three-way check stay green; the OpenAppPublish step rewrites the key.
|
|
39
|
+
*/
|
|
40
|
+
let ImpexiumConnector = class ImpexiumConnector extends BaseRESTIntegrationConnector {
|
|
41
|
+
constructor() {
|
|
42
|
+
super(...arguments);
|
|
43
|
+
/** Cached auth context (with token expiry) for the lifetime of a sync run; re-minted on expiry. */
|
|
44
|
+
this.cachedAuth = null;
|
|
45
|
+
}
|
|
46
|
+
// ── Identity (T1 three-way invariant) ────────────────────────────────────
|
|
47
|
+
/** Verbatim MJ: Integrations.Name. The T1 three-way name check statically parses this literal. */
|
|
48
|
+
get IntegrationName() {
|
|
49
|
+
return 're:Members AMS';
|
|
50
|
+
}
|
|
51
|
+
// ── Capability getters (kept in lockstep with the per-operation metadata columns) ──
|
|
52
|
+
//
|
|
53
|
+
// Write capability FOLLOWS the per-operation CRUD columns on the cached IntegrationObjects (Declared
|
|
54
|
+
// metadata): an object is create-capable iff it declares both CreateAPIPath + CreateMethod, etc. No
|
|
55
|
+
// hardcoded `true`; no 501 stubs. With no write metadata the surface is read-only.
|
|
56
|
+
get SupportsCreate() {
|
|
57
|
+
return this.anyObjectDeclares(o => !!o.CreateAPIPath && !!o.CreateMethod);
|
|
58
|
+
}
|
|
59
|
+
get SupportsUpdate() {
|
|
60
|
+
return this.anyObjectDeclares(o => !!o.UpdateAPIPath && !!o.UpdateMethod);
|
|
61
|
+
}
|
|
62
|
+
get SupportsDelete() {
|
|
63
|
+
return this.anyObjectDeclares(o => !!o.DeleteAPIPath && !!o.DeleteMethod);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Discovery is NON-authoritative: DiscoverObjects / IntrospectSchema are cache-driven (they re-read
|
|
67
|
+
* persisted ACTIVE Declared metadata, NOT a live full-gamut enumeration). Impexium exposes no
|
|
68
|
+
* describe-all / complete-gamut endpoint (GET Setup/customfields only enumerates the tenant's custom
|
|
69
|
+
* FIELD catalog, not a whole object/field gamut). Absence in a refresh proves nothing → never
|
|
70
|
+
* deactivate. Matches Configuration.DiscoveryIsAuthoritative=false in the frozen contract.
|
|
71
|
+
*/
|
|
72
|
+
get DiscoveryIsAuthoritative() {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Sample-union field enrichment (the connector standard). Declared metadata is authoritative but
|
|
77
|
+
* incomplete — it misses a tenant's CUSTOM / undeclared columns (Impexium's per-tenant custom fields).
|
|
78
|
+
* Wire the required enrichment: take the base cache-driven schema, then per object UNION the declared
|
|
79
|
+
* field set with what a live read actually returns (`mergeDeclaredWithSampledFields` — never-shrink,
|
|
80
|
+
* declared-wins, capacities widened). Best-effort + parallel: a per-object sample failure leaves that
|
|
81
|
+
* object's declared fields untouched. Overrides IntrospectSchema (NEVER DiscoverFields — that would
|
|
82
|
+
* recurse into DiscoverFieldsViaFetch's fallback).
|
|
83
|
+
*/
|
|
84
|
+
async IntrospectSchema(companyIntegration, contextUser) {
|
|
85
|
+
const info = await super.IntrospectSchema(companyIntegration, contextUser);
|
|
86
|
+
await Promise.all(info.Objects.map(async (obj) => {
|
|
87
|
+
try {
|
|
88
|
+
const sampled = await this.DiscoverFieldsViaFetch(companyIntegration, obj.ExternalName, contextUser);
|
|
89
|
+
obj.Fields = mergeDeclaredWithSampledFields(obj.Fields, sampled);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* best-effort — a sample failure leaves the declared fields as-is */
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
return info;
|
|
96
|
+
}
|
|
97
|
+
/** True when any cached IntegrationObject satisfies the predicate. Fail-safe read-only ([]→false). */
|
|
98
|
+
anyObjectDeclares(pred) {
|
|
99
|
+
try {
|
|
100
|
+
const integration = IntegrationEngineBase.Instance.GetIntegrationByName(INTEGRATION_NAME);
|
|
101
|
+
if (!integration)
|
|
102
|
+
return false;
|
|
103
|
+
return IntegrationEngineBase.Instance.GetActiveIntegrationObjects(integration.ID).some(pred);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ── Abstract REST hooks ──────────────────────────────────────────────────
|
|
110
|
+
/**
|
|
111
|
+
* Authenticate against the RAW tenant host. Candidate A (default) runs the app + user handshake and
|
|
112
|
+
* caches the Access-Token + App-Token; candidate B ('apikey') resolves a single x-api-key. The cached
|
|
113
|
+
* context is re-minted when its tokens near expiry. NEVER inline crypto — the handshake is a plain
|
|
114
|
+
* credential POST returning tokens.
|
|
115
|
+
*/
|
|
116
|
+
async Authenticate(companyIntegration, contextUser) {
|
|
117
|
+
if (this.cachedAuth && this.cachedAuth.ExpiresAtMs > Date.now() + TOKEN_REFRESH_BUFFER_MS) {
|
|
118
|
+
return this.cachedAuth;
|
|
119
|
+
}
|
|
120
|
+
const config = await this.parseConfig(companyIntegration, contextUser);
|
|
121
|
+
const host = this.resolveHost(config);
|
|
122
|
+
if (config.AuthModel === 'apikey') {
|
|
123
|
+
// Candidate B — single x-api-key. No handshake round-trip. Recorded as the cheaply swappable
|
|
124
|
+
// fallback; NOT silently assumed correct for the raw host (see design note).
|
|
125
|
+
this.cachedAuth = {
|
|
126
|
+
HostUrl: host,
|
|
127
|
+
AuthModel: 'apikey',
|
|
128
|
+
ApiKey: config.ApiKey,
|
|
129
|
+
AccessTokenHeader: config.AccessTokenHeader ?? DEFAULT_ACCESS_TOKEN_HEADER,
|
|
130
|
+
AppTokenHeader: config.AppTokenHeader ?? DEFAULT_APP_TOKEN_HEADER,
|
|
131
|
+
ApiKeyHeader: config.ApiKeyHeader ?? DEFAULT_API_KEY_HEADER,
|
|
132
|
+
ExpiresAtMs: Number.MAX_SAFE_INTEGER,
|
|
133
|
+
};
|
|
134
|
+
return this.cachedAuth;
|
|
135
|
+
}
|
|
136
|
+
// Candidate A — app + user handshake (UNVERIFIED; see design note). Tokens cached with expiry.
|
|
137
|
+
const tokens = await this.mintTokens(config, host);
|
|
138
|
+
this.cachedAuth = {
|
|
139
|
+
HostUrl: host,
|
|
140
|
+
AuthModel: 'app-user',
|
|
141
|
+
AccessToken: tokens.accessToken,
|
|
142
|
+
AppToken: tokens.appToken,
|
|
143
|
+
AccessTokenHeader: config.AccessTokenHeader ?? DEFAULT_ACCESS_TOKEN_HEADER,
|
|
144
|
+
AppTokenHeader: config.AppTokenHeader ?? DEFAULT_APP_TOKEN_HEADER,
|
|
145
|
+
ApiKeyHeader: config.ApiKeyHeader ?? DEFAULT_API_KEY_HEADER,
|
|
146
|
+
ExpiresAtMs: tokens.expiresAtMs,
|
|
147
|
+
};
|
|
148
|
+
return this.cachedAuth;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Attaches the auth headers per model: candidate A sends BOTH tokens (Access-Token + App-Token,
|
|
152
|
+
* header names Configuration-overridable); candidate B sends the single x-api-key.
|
|
153
|
+
*/
|
|
154
|
+
BuildHeaders(auth) {
|
|
155
|
+
const ctx = auth;
|
|
156
|
+
const headers = {
|
|
157
|
+
'Content-Type': 'application/json',
|
|
158
|
+
'Accept': 'application/json',
|
|
159
|
+
};
|
|
160
|
+
if (ctx.AuthModel === 'apikey') {
|
|
161
|
+
if (ctx.ApiKey)
|
|
162
|
+
headers[ctx.ApiKeyHeader] = ctx.ApiKey;
|
|
163
|
+
return headers;
|
|
164
|
+
}
|
|
165
|
+
if (ctx.AccessToken)
|
|
166
|
+
headers[ctx.AccessTokenHeader] = ctx.AccessToken;
|
|
167
|
+
if (ctx.AppToken)
|
|
168
|
+
headers[ctx.AppTokenHeader] = ctx.AppToken;
|
|
169
|
+
return headers;
|
|
170
|
+
}
|
|
171
|
+
/** Per-tenant host resolved in Authenticate — ZERO host baked in code. */
|
|
172
|
+
GetBaseURL(_companyIntegration, auth) {
|
|
173
|
+
return auth.HostUrl;
|
|
174
|
+
}
|
|
175
|
+
/** HTTP transport (fetch). Owns the wire boundary; test subclasses override this to capture requests. */
|
|
176
|
+
async MakeHTTPRequest(auth, url, method, headers, body) {
|
|
177
|
+
const timeoutMs = auth?.TimeoutMs;
|
|
178
|
+
const init = {
|
|
179
|
+
method,
|
|
180
|
+
headers,
|
|
181
|
+
signal: AbortSignal.timeout(typeof timeoutMs === 'number' ? timeoutMs : DEFAULT_REQUEST_TIMEOUT_MS),
|
|
182
|
+
};
|
|
183
|
+
if (body !== undefined && method !== 'GET') {
|
|
184
|
+
init.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
185
|
+
}
|
|
186
|
+
const response = await fetch(url, init);
|
|
187
|
+
const respHeaders = {};
|
|
188
|
+
response.headers.forEach((v, k) => { respHeaders[k.toLowerCase()] = v; });
|
|
189
|
+
const text = await response.text();
|
|
190
|
+
let parsed = null;
|
|
191
|
+
if (text.length > 0) {
|
|
192
|
+
try {
|
|
193
|
+
parsed = JSON.parse(text);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
parsed = text;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { Status: response.status, Body: parsed, Headers: respHeaders };
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Strips Impexium's list envelope `{ pageNumber, dataList: T[] }` to the dataList array
|
|
203
|
+
* (ResponseDataKey='dataList'). Falls back to a bare array, the universal dataList key, or a single
|
|
204
|
+
* detail object. The FULL source record passes through (no field filtering).
|
|
205
|
+
*/
|
|
206
|
+
NormalizeResponse(rawBody, responseDataKey) {
|
|
207
|
+
if (rawBody == null)
|
|
208
|
+
return [];
|
|
209
|
+
if (Array.isArray(rawBody))
|
|
210
|
+
return rawBody;
|
|
211
|
+
if (typeof rawBody !== 'object')
|
|
212
|
+
return [];
|
|
213
|
+
const body = rawBody;
|
|
214
|
+
const key = responseDataKey ?? DATA_LIST_KEY;
|
|
215
|
+
const arr = body[key];
|
|
216
|
+
if (Array.isArray(arr))
|
|
217
|
+
return arr;
|
|
218
|
+
// Universal-envelope fallback (some IOs declare a null ResponseDataKey but still list under dataList).
|
|
219
|
+
if (Array.isArray(body[DATA_LIST_KEY]))
|
|
220
|
+
return body[DATA_LIST_KEY];
|
|
221
|
+
// Single-object detail response (get-one).
|
|
222
|
+
return [body];
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Page-in-path pagination: the envelope carries NO hasMore/totalCount field, so termination is
|
|
226
|
+
* empty-dataList. HasMore is true iff the current page returned records; the loop advances the page
|
|
227
|
+
* number (rendered into the path by BuildPaginatedURL) and stops when a page comes back empty.
|
|
228
|
+
*/
|
|
229
|
+
ExtractPaginationInfo(rawBody, paginationType, currentPage, _currentOffset, _pageSize) {
|
|
230
|
+
if (paginationType !== 'PageNumber')
|
|
231
|
+
return { HasMore: false };
|
|
232
|
+
const records = this.NormalizeResponse(rawBody, DATA_LIST_KEY);
|
|
233
|
+
return { HasMore: records.length > 0, NextPage: currentPage + 1 };
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Impexium puts the page number IN THE PATH (a {Page Number}/{pageNumber}/{Page} segment), NEVER as a
|
|
237
|
+
* `?page=` query param. Substitute the page into that path segment and return — no query pagination.
|
|
238
|
+
*/
|
|
239
|
+
BuildPaginatedURL(basePath, obj, page, _offset, _cursor, _effectivePageSize) {
|
|
240
|
+
if (obj.PaginationType !== 'PageNumber')
|
|
241
|
+
return basePath;
|
|
242
|
+
return this.substitutePagePlaceholder(basePath, page);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Injects the incremental watermark filter when a sync is active. The param NAME is fully
|
|
246
|
+
* METADATA-DRIVEN: it comes from the IO's IncrementalWatermarkField (changedSince / purchasedSince /
|
|
247
|
+
* registeredSince / cancelledSince) and is emitted only when SupportsIncrementalSync=true AND a
|
|
248
|
+
* watermark value is in context. Runs on top of the base's DefaultQueryParams handling.
|
|
249
|
+
*/
|
|
250
|
+
AppendDefaultQueryParams(url, obj) {
|
|
251
|
+
return this.appendIncrementalParam(super.AppendDefaultQueryParams(url, obj), obj);
|
|
252
|
+
}
|
|
253
|
+
// ── FetchChanges override (page-in-path + nested parent access-path walk + watermark) ──
|
|
254
|
+
/**
|
|
255
|
+
* Fetch a batch. Flat top-level objects delegate to the base GET loop (which uses the BuildPaginatedURL
|
|
256
|
+
* / ExtractPaginationInfo / AppendDefaultQueryParams overrides above for page-in-path + incremental).
|
|
257
|
+
* Nested objects (whose frozen paths carry space-containing parent placeholders the base's `\w+`
|
|
258
|
+
* template detector can't resolve) are walked here: resolve the parent sibling IO from the preceding
|
|
259
|
+
* path RESOURCE segment, iterate synced parent records (bounded + keyset-resumable), substitute, then
|
|
260
|
+
* page the leaf. The watermark advances (partial-failure-safe) only on a fully-drained final batch.
|
|
261
|
+
*/
|
|
262
|
+
async FetchChanges(ctx) {
|
|
263
|
+
const obj = this.GetCachedObject(ctx.CompanyIntegration.IntegrationID, ctx.ObjectName);
|
|
264
|
+
// NOTE: Impexium sub-resources (/Individuals/{id}/Addresses, /Emails, /Notes, /CustomFields, an
|
|
265
|
+
// Organization's /Services, /tasks, …) are RESTful read+write collections — GET lists them, POST
|
|
266
|
+
// creates. Their deployed CreateAPIPath legitimately EQUALS the read APIPath (same URL, different
|
|
267
|
+
// verb), so an "APIPath == write-path" test does NOT identify a write-only object and must NOT be
|
|
268
|
+
// used to skip the read. These objects sync via the normal (parent-chained) read path below.
|
|
269
|
+
this.currentWatermark = ctx.WatermarkValue ?? undefined;
|
|
270
|
+
// Capture the sync-start time BEFORE any fetch — a changed-since watermark advances to this on a
|
|
271
|
+
// clean full drain, so the next incremental picks up everything modified since this run began.
|
|
272
|
+
const watermarkStart = this.formatWatermark(new Date());
|
|
273
|
+
try {
|
|
274
|
+
const parentTokens = this.parentTokens(obj.APIPath);
|
|
275
|
+
const result = parentTokens.length === 0
|
|
276
|
+
? await super.FetchChanges(ctx)
|
|
277
|
+
: await this.fetchWithParents(obj, parentTokens, ctx);
|
|
278
|
+
if (obj.SupportsIncrementalSync && obj.IncrementalWatermarkField && !result.HasMore) {
|
|
279
|
+
result.NewWatermarkValue = this.computeNewWatermark(ctx.WatermarkValue, watermarkStart);
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
this.currentWatermark = undefined;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Walks a nested object's access path: resolve the PRIMARY parent token → sibling IO (by the path
|
|
289
|
+
* segment preceding it), load the synced parent record IDs, process a bounded, keyset-resumable batch
|
|
290
|
+
* of parents, substitute + page each leaf. Surfaces a FetchWarning (never a silent empty) when the
|
|
291
|
+
* parent can't be resolved (Courses / tasks Users are not emitted IOs), when no parents are synced yet,
|
|
292
|
+
* or when a secondary parent token remains unresolved.
|
|
293
|
+
*/
|
|
294
|
+
async fetchWithParents(obj, parentTokens, ctx) {
|
|
295
|
+
const fields = this.GetCachedFields(obj.ID);
|
|
296
|
+
const pkFieldNames = this.pkFieldNames(fields);
|
|
297
|
+
const auth = await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser);
|
|
298
|
+
const baseURL = this.GetBaseURL(ctx.CompanyIntegration, auth);
|
|
299
|
+
const integrationID = ctx.CompanyIntegration.IntegrationID;
|
|
300
|
+
const primaryToken = parentTokens[0];
|
|
301
|
+
const parentObjectName = this.resolveParentObjectName(obj.APIPath, primaryToken, integrationID);
|
|
302
|
+
if (!parentObjectName) {
|
|
303
|
+
return this.warnResult(obj, 'PARENT_UNRESOLVED', `"${obj.Name}": the parent for {${primaryToken}} resolves to no emitted IntegrationObject ` +
|
|
304
|
+
`(the preceding path resource segment is not a synced object) — cannot walk this access path.`);
|
|
305
|
+
}
|
|
306
|
+
const allParentIDs = this.sortIdsStable(await this.loadParentRecordIDs(parentObjectName, integrationID, ctx.ContextUser));
|
|
307
|
+
if (allParentIDs.length === 0) {
|
|
308
|
+
return this.warnResult(obj, 'ZERO_PARENTS', `"${obj.Name}": no "${parentObjectName}" parent records available to iterate {${primaryToken}} ` +
|
|
309
|
+
`over — sync the parent first (check DAG order/priority).`);
|
|
310
|
+
}
|
|
311
|
+
const after = ctx.AfterKeyValue ?? null;
|
|
312
|
+
const remaining = after != null ? allParentIDs.filter(id => this.compareIds(id, after) > 0) : allParentIDs;
|
|
313
|
+
const batch = remaining.slice(0, PARENT_BATCH_SIZE);
|
|
314
|
+
const out = [];
|
|
315
|
+
const warnings = [];
|
|
316
|
+
for (const parentID of batch) {
|
|
317
|
+
const resolvedPath = obj.APIPath.replace(`{${primaryToken}}`, encodeURIComponent(parentID));
|
|
318
|
+
const leftover = this.parentTokens(resolvedPath);
|
|
319
|
+
if (leftover.length > 0) {
|
|
320
|
+
warnings.push({
|
|
321
|
+
Code: 'PARENT_UNRESOLVED',
|
|
322
|
+
Message: `"${obj.Name}": secondary parent token {${leftover[0]}} could not be resolved — skipping this parent.`,
|
|
323
|
+
Data: { object: obj.Name, templateVar: leftover[0] },
|
|
324
|
+
});
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const leafRecords = await this.fetchLeafPages(auth, baseURL, obj, resolvedPath);
|
|
328
|
+
for (const raw of leafRecords) {
|
|
329
|
+
// Emit the PURE source record — do NOT inject the parent id. `ExternalRecord.Fields` is the
|
|
330
|
+
// full-record pass-through of what the VENDOR returned; a connector-synthesized provenance tag
|
|
331
|
+
// is not source content. Critically, that tag flows into the content-hash basis (it is an
|
|
332
|
+
// unmapped key → overflow → change-detection), so tagging the same leaf record with the parent
|
|
333
|
+
// it was fetched under makes its hash depend on the TRAVERSAL PATH, not its content. When a
|
|
334
|
+
// leaf is reachable from multiple parents (a many-to-many child, or any source that returns
|
|
335
|
+
// the same child under several parents), the record is written once per parent with a
|
|
336
|
+
// different hash each time and re-writes every sync — breaking idempotency (content-hash skip
|
|
337
|
+
// never engages). Change-detection must be over the record's own content; the parent
|
|
338
|
+
// association, if needed, belongs in a mapped FK from real source data, not this tag.
|
|
339
|
+
out.push(this.buildExternalRecord(this.applyTransformPreservingKeys(raw, obj, fields), obj.Name, pkFieldNames));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const hasMore = remaining.length > batch.length;
|
|
343
|
+
return {
|
|
344
|
+
Records: out,
|
|
345
|
+
HasMore: hasMore,
|
|
346
|
+
NextAfterKeyValue: hasMore ? batch[batch.length - 1] : undefined,
|
|
347
|
+
Warnings: warnings.length > 0 ? warnings : undefined,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Drains ALL pages of a single resolved (parent-substituted) leaf path via page-in-path pagination,
|
|
352
|
+
* or a single request when the object is non-paginated. Stops on an empty dataList; skips gracefully
|
|
353
|
+
* on 403/404 (permission-gated / absent sub-resource). Injects the incremental watermark param.
|
|
354
|
+
*/
|
|
355
|
+
async fetchLeafPages(auth, baseURL, obj, resolvedPath) {
|
|
356
|
+
const headers = this.BuildHeaders(auth);
|
|
357
|
+
const paginated = obj.SupportsPagination && obj.PaginationType !== 'None' && this.hasPageToken(resolvedPath);
|
|
358
|
+
if (!paginated) {
|
|
359
|
+
const url = this.appendIncrementalParam(this.joinURL(baseURL, resolvedPath), obj);
|
|
360
|
+
const response = await this.MakeHTTPRequest(auth, url, 'GET', headers);
|
|
361
|
+
if (this.isSkippableStatus(response.Status))
|
|
362
|
+
return [];
|
|
363
|
+
this.ensureOk(response, url);
|
|
364
|
+
return this.NormalizeResponse(response.Body, obj.ResponseDataKey);
|
|
365
|
+
}
|
|
366
|
+
const out = [];
|
|
367
|
+
let page = 1;
|
|
368
|
+
for (let i = 0; i < MAX_LEAF_PAGES; i++) {
|
|
369
|
+
const pathWithPage = this.substitutePagePlaceholder(resolvedPath, page);
|
|
370
|
+
const url = this.appendIncrementalParam(this.joinURL(baseURL, pathWithPage), obj);
|
|
371
|
+
const response = await this.MakeHTTPRequest(auth, url, 'GET', headers);
|
|
372
|
+
if (this.isSkippableStatus(response.Status))
|
|
373
|
+
break;
|
|
374
|
+
this.ensureOk(response, url);
|
|
375
|
+
const records = this.NormalizeResponse(response.Body, obj.ResponseDataKey);
|
|
376
|
+
if (records.length === 0)
|
|
377
|
+
break;
|
|
378
|
+
out.push(...records);
|
|
379
|
+
page++;
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
// ── CRUD (parent-scoped write paths — the base generic path can't substitute create-path parents) ──
|
|
384
|
+
/**
|
|
385
|
+
* Create. OVERRIDDEN because Impexium's create paths carry PARENT template vars ({code}/{id}/
|
|
386
|
+
* {Record Number}) filled from the record's own attributes — the generic base path leaves them literal.
|
|
387
|
+
* The body shape + loud-on-empty-id BuildCreatedResult are preserved. Top-level creates with no path
|
|
388
|
+
* vars (Individuals, Organizations, Tasks, Requests) behave exactly as the generic path would.
|
|
389
|
+
*/
|
|
390
|
+
async CreateRecord(ctx) {
|
|
391
|
+
const ci = ctx.CompanyIntegration;
|
|
392
|
+
const contextUser = ctx.ContextUser;
|
|
393
|
+
const obj = this.GetCachedObject(ci.IntegrationID, ctx.ObjectName);
|
|
394
|
+
if (!obj.CreateAPIPath || !obj.CreateMethod)
|
|
395
|
+
return super.CreateRecord(ctx);
|
|
396
|
+
const auth = await this.Authenticate(ci, contextUser);
|
|
397
|
+
const baseURL = this.GetBaseURL(ci, auth);
|
|
398
|
+
const headers = this.BuildHeaders(auth);
|
|
399
|
+
const path = this.substitutePathVarsFromAttributes(obj.CreateAPIPath, ctx.Attributes);
|
|
400
|
+
const url = this.joinURL(baseURL, path);
|
|
401
|
+
const body = this.BuildOperationBody(ctx.Attributes, obj.CreateBodyShape, obj.CreateBodyKey);
|
|
402
|
+
const response = await this.MakeHTTPRequest(auth, url, obj.CreateMethod, headers, body);
|
|
403
|
+
if (response.Status >= 200 && response.Status < 300) {
|
|
404
|
+
const externalID = this.ExtractIDFromResponse(response, obj.CreateIDLocation);
|
|
405
|
+
return this.BuildCreatedResult(externalID, response.Status, ctx.ObjectName);
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
Success: false,
|
|
409
|
+
StatusCode: response.Status,
|
|
410
|
+
ErrorMessage: this.ExtractErrorMessage(response) ?? `HTTP ${response.Status} on create`,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Update. OVERRIDDEN so parent path vars are filled from attributes and the path id ({id}/{Task Number}/
|
|
415
|
+
* {memberRecordNumber}/…) is filled from the ExternalID when UpdateIDLocation='path'. When
|
|
416
|
+
* UpdateIDLocation='body', the id is not path-substituted (the record body carries it).
|
|
417
|
+
*/
|
|
418
|
+
async UpdateRecord(ctx) {
|
|
419
|
+
const ci = ctx.CompanyIntegration;
|
|
420
|
+
const contextUser = ctx.ContextUser;
|
|
421
|
+
const obj = this.GetCachedObject(ci.IntegrationID, ctx.ObjectName);
|
|
422
|
+
if (!obj.UpdateAPIPath || !obj.UpdateMethod)
|
|
423
|
+
return super.UpdateRecord(ctx);
|
|
424
|
+
const auth = await this.Authenticate(ci, contextUser);
|
|
425
|
+
const baseURL = this.GetBaseURL(ci, auth);
|
|
426
|
+
const headers = this.BuildHeaders(auth);
|
|
427
|
+
const path = this.substituteWritePath(obj.UpdateAPIPath, ctx.Attributes, ctx.ExternalID, obj.UpdateIDLocation);
|
|
428
|
+
const url = this.joinURL(baseURL, path);
|
|
429
|
+
const body = this.BuildOperationBody(ctx.Attributes, obj.UpdateBodyShape, obj.UpdateBodyKey);
|
|
430
|
+
const response = await this.MakeHTTPRequest(auth, url, obj.UpdateMethod, headers, body);
|
|
431
|
+
if (response.Status >= 200 && response.Status < 300) {
|
|
432
|
+
return { Success: true, StatusCode: response.Status, ExternalID: ctx.ExternalID };
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
Success: false,
|
|
436
|
+
StatusCode: response.Status,
|
|
437
|
+
ErrorMessage: this.ExtractErrorMessage(response) ?? `HTTP ${response.Status} on update`,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Delete. OVERRIDDEN so the path id ({Category Code}) is filled from the ExternalID when
|
|
442
|
+
* DeleteIDLocation='path'. NOTE (documented limitation, flagged for T10 live verification): the only
|
|
443
|
+
* two delete-capable IOs (Categories, Links) are narrow sub-resources whose path ALSO carries a parent
|
|
444
|
+
* segment ({Record Number}) that the framework's DeleteRecordContext does not surface — a single-part
|
|
445
|
+
* ExternalID fills only the target id, so a live delete of these sub-resources needs the parent encoded
|
|
446
|
+
* in a composite ExternalID. Links additionally identifies the target via the request BODY
|
|
447
|
+
* (DeleteIDLocation='body'), which the delete context does not carry.
|
|
448
|
+
*/
|
|
449
|
+
async DeleteRecord(ctx) {
|
|
450
|
+
const ci = ctx.CompanyIntegration;
|
|
451
|
+
const contextUser = ctx.ContextUser;
|
|
452
|
+
const obj = this.GetCachedObject(ci.IntegrationID, ctx.ObjectName);
|
|
453
|
+
if (!obj.DeleteAPIPath || !obj.DeleteMethod)
|
|
454
|
+
return super.DeleteRecord(ctx);
|
|
455
|
+
const auth = await this.Authenticate(ci, contextUser);
|
|
456
|
+
const baseURL = this.GetBaseURL(ci, auth);
|
|
457
|
+
const headers = this.BuildHeaders(auth);
|
|
458
|
+
const path = this.substituteWritePath(obj.DeleteAPIPath, {}, ctx.ExternalID, obj.DeleteIDLocation);
|
|
459
|
+
const url = this.joinURL(baseURL, path);
|
|
460
|
+
const response = await this.MakeHTTPRequest(auth, url, obj.DeleteMethod, headers);
|
|
461
|
+
if (response.Status >= 200 && response.Status < 300) {
|
|
462
|
+
return { Success: true, StatusCode: response.Status, ExternalID: ctx.ExternalID };
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
Success: false,
|
|
466
|
+
StatusCode: response.Status,
|
|
467
|
+
ErrorMessage: this.ExtractErrorMessage(response) ?? `HTTP ${response.Status} on delete`,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Reads the created record's id from the Impexium create response. The universal envelope returns the
|
|
472
|
+
* new record under `id`; some sub-resource creates return the record with no server id
|
|
473
|
+
* (CreateIDLocation='n/a') — those route to BuildCreatedResult, which fails loudly on an empty id.
|
|
474
|
+
*/
|
|
475
|
+
ExtractIDFromResponse(response, idLocation) {
|
|
476
|
+
if (idLocation === 'header')
|
|
477
|
+
return super.ExtractIDFromResponse(response, idLocation);
|
|
478
|
+
const body = response.Body;
|
|
479
|
+
if (!body || typeof body !== 'object')
|
|
480
|
+
return undefined;
|
|
481
|
+
const b = body;
|
|
482
|
+
const rootId = this.readId(b);
|
|
483
|
+
if (rootId)
|
|
484
|
+
return rootId;
|
|
485
|
+
// Some responses wrap the created record under the universal envelope or a single resource object.
|
|
486
|
+
const wrapped = b[DATA_LIST_KEY] ?? b['data'] ?? b['result'];
|
|
487
|
+
if (wrapped && typeof wrapped === 'object' && !Array.isArray(wrapped)) {
|
|
488
|
+
const id = this.readId(wrapped);
|
|
489
|
+
if (id)
|
|
490
|
+
return id;
|
|
491
|
+
}
|
|
492
|
+
return undefined;
|
|
493
|
+
}
|
|
494
|
+
// ── Connection test ──────────────────────────────────────────────────────
|
|
495
|
+
/**
|
|
496
|
+
* Tests connectivity by authenticating and reading one page of Individuals. A 2xx confirms the auth
|
|
497
|
+
* flow + tenant host are valid; 401/403 → auth failure; anything else → error. Never mutates.
|
|
498
|
+
*/
|
|
499
|
+
async TestConnection(companyIntegration, contextUser) {
|
|
500
|
+
try {
|
|
501
|
+
const auth = await this.Authenticate(companyIntegration, contextUser);
|
|
502
|
+
const baseURL = this.GetBaseURL(companyIntegration, auth);
|
|
503
|
+
const headers = this.BuildHeaders(auth);
|
|
504
|
+
const url = this.joinURL(baseURL, '/api/v1/Individuals/1');
|
|
505
|
+
const response = await this.MakeHTTPRequest(auth, url, 'GET', headers);
|
|
506
|
+
if (response.Status >= 200 && response.Status < 300) {
|
|
507
|
+
return { Success: true, Message: `Connected to re:Members AMS at ${baseURL}` };
|
|
508
|
+
}
|
|
509
|
+
if (response.Status === 401 || response.Status === 403) {
|
|
510
|
+
return {
|
|
511
|
+
Success: false,
|
|
512
|
+
Message: `re:Members AMS authentication failed (HTTP ${response.Status}). ` +
|
|
513
|
+
`Verify the AppId/AppKey/AppName + user login (or the auth model) and the tenant HostUrl.`,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
return { Success: false, Message: `re:Members AMS connection test returned HTTP ${response.Status}.` };
|
|
517
|
+
}
|
|
518
|
+
catch (err) {
|
|
519
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
520
|
+
return { Success: false, Message: `re:Members AMS connection test error: ${msg}` };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
524
|
+
// Private helpers
|
|
525
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
526
|
+
// ── Auth handshake ───────────────────────────────────────────────────────
|
|
527
|
+
/**
|
|
528
|
+
* Runs the candidate-A app + user handshake: POST {AppId, AppKey, AppName, Email, Password} to the auth
|
|
529
|
+
* path and read the Access-Token + App-Token (+ optional expiry) from the response. UNVERIFIED at plan
|
|
530
|
+
* time — the endpoint path, request field names, response field names and TTL are all overridable, so a
|
|
531
|
+
* live/sandbox probe can settle them without a code change. Plain credential POST; no crypto.
|
|
532
|
+
*/
|
|
533
|
+
async mintTokens(config, host) {
|
|
534
|
+
const authPath = config.AuthPath ?? DEFAULT_AUTH_PATH;
|
|
535
|
+
const url = this.joinURL(host, authPath);
|
|
536
|
+
const requestHeaders = { 'Content-Type': 'application/json', 'Accept': 'application/json' };
|
|
537
|
+
const requestBody = {
|
|
538
|
+
AppId: config.AppId,
|
|
539
|
+
AppKey: config.AppKey,
|
|
540
|
+
AppName: config.AppName,
|
|
541
|
+
Email: config.Email,
|
|
542
|
+
Password: config.Password,
|
|
543
|
+
};
|
|
544
|
+
// A bare auth context (no tokens) — the mint sends credentials in the body, not headers.
|
|
545
|
+
const bareAuth = { HostUrl: host, AuthModel: 'app-user' };
|
|
546
|
+
const response = await this.MakeHTTPRequest(bareAuth, url, 'POST', requestHeaders, requestBody);
|
|
547
|
+
if (response.Status < 200 || response.Status >= 300) {
|
|
548
|
+
throw new Error(`re:Members AMS app+user handshake failed at ${url}: HTTP ${response.Status}. ` +
|
|
549
|
+
`(Auth flow is UNVERIFIED — confirm the endpoint/field names against a live tenant.)`);
|
|
550
|
+
}
|
|
551
|
+
const body = (response.Body && typeof response.Body === 'object' ? response.Body : {});
|
|
552
|
+
const accessToken = this.firstString(body, ['AccessToken', 'accessToken', 'access_token', 'Token', 'token']);
|
|
553
|
+
if (!accessToken) {
|
|
554
|
+
throw new Error(`re:Members AMS handshake returned HTTP ${response.Status} but no access token was found in the response. ` +
|
|
555
|
+
`(Auth flow is UNVERIFIED — confirm the token field name.)`);
|
|
556
|
+
}
|
|
557
|
+
const appToken = this.firstString(body, ['AppToken', 'appToken', 'app_token', 'ApplicationToken', 'applicationToken']);
|
|
558
|
+
const expiresAtMs = this.resolveTokenExpiry(body, config);
|
|
559
|
+
return { accessToken, appToken, expiresAtMs };
|
|
560
|
+
}
|
|
561
|
+
/** Derives the token expiry from the handshake response, falling back to the configured/default TTL. */
|
|
562
|
+
resolveTokenExpiry(body, config) {
|
|
563
|
+
const ttl = config.TokenTTLMs ?? DEFAULT_TOKEN_TTL_MS;
|
|
564
|
+
// Absolute expiry (ISO string) — common vendor field names.
|
|
565
|
+
const absolute = this.firstString(body, ['AccessTokenExpiration', 'accessTokenExpiration', 'expiresAt', 'ExpiresAt']);
|
|
566
|
+
if (absolute) {
|
|
567
|
+
const t = Date.parse(absolute);
|
|
568
|
+
if (Number.isFinite(t) && t > Date.now())
|
|
569
|
+
return t;
|
|
570
|
+
}
|
|
571
|
+
// Relative expiry (seconds).
|
|
572
|
+
for (const key of ['ExpiresIn', 'expiresIn', 'expires_in']) {
|
|
573
|
+
const v = body[key];
|
|
574
|
+
if (typeof v === 'number' && Number.isFinite(v) && v > 0)
|
|
575
|
+
return Date.now() + v * 1_000;
|
|
576
|
+
}
|
|
577
|
+
return Date.now() + ttl;
|
|
578
|
+
}
|
|
579
|
+
// ── Config parsing ───────────────────────────────────────────────────────
|
|
580
|
+
/** Parses the connection config, preferring the linked Credential entity over the Configuration JSON. */
|
|
581
|
+
async parseConfig(companyIntegration, contextUser) {
|
|
582
|
+
let fromCredential = null;
|
|
583
|
+
if (companyIntegration.CredentialID) {
|
|
584
|
+
fromCredential = await this.loadCredentialValues(companyIntegration.CredentialID, contextUser);
|
|
585
|
+
}
|
|
586
|
+
let fromConfiguration = null;
|
|
587
|
+
if (companyIntegration.Configuration) {
|
|
588
|
+
try {
|
|
589
|
+
fromConfiguration = JSON.parse(companyIntegration.Configuration);
|
|
590
|
+
}
|
|
591
|
+
catch {
|
|
592
|
+
fromConfiguration = null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (!fromCredential && !fromConfiguration) {
|
|
596
|
+
throw new Error('re:Members AMS connector requires either a linked Credential or Configuration JSON.');
|
|
597
|
+
}
|
|
598
|
+
// Merge: Configuration provides non-secret keys (HostUrl, AuthModel, header overrides); the
|
|
599
|
+
// Credential provides secrets (AppId/AppKey/Email/Password/ApiKey). Credential wins on overlap.
|
|
600
|
+
const merged = { ...(fromConfiguration ?? {}), ...(fromCredential ?? {}) };
|
|
601
|
+
return this.buildConfig(merged);
|
|
602
|
+
}
|
|
603
|
+
/** Loads a credential row and parses its Values JSON to a plain map. */
|
|
604
|
+
async loadCredentialValues(credentialID, contextUser, provider) {
|
|
605
|
+
const md = provider ?? new Metadata();
|
|
606
|
+
const cred = await md.GetEntityObject('MJ: Credentials', contextUser);
|
|
607
|
+
const loaded = await cred.Load(credentialID);
|
|
608
|
+
if (!loaded || !cred.Values)
|
|
609
|
+
return null;
|
|
610
|
+
try {
|
|
611
|
+
return JSON.parse(cred.Values);
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/** Builds the typed config from a raw case-insensitive map, applying defaults + validating HostUrl. */
|
|
618
|
+
buildConfig(raw) {
|
|
619
|
+
const hostUrl = this.firstString(raw, ['HostUrl', 'hostUrl', 'hosturl', 'BaseURL', 'baseURL', 'host']);
|
|
620
|
+
if (!hostUrl) {
|
|
621
|
+
throw new Error('re:Members AMS configuration missing required "HostUrl" (the per-tenant https://{tenant}.mpxapi.com host).');
|
|
622
|
+
}
|
|
623
|
+
const authModelRaw = (this.firstString(raw, ['AuthModel', 'authModel']) ?? 'app-user').toLowerCase();
|
|
624
|
+
const authModel = authModelRaw === 'apikey' || authModelRaw === 'api-key' ? 'apikey' : 'app-user';
|
|
625
|
+
const timeout = this.firstNumber(raw, ['RequestTimeoutMs', 'requestTimeoutMs']);
|
|
626
|
+
const ttl = this.firstNumber(raw, ['TokenTTLMs', 'tokenTTLMs']);
|
|
627
|
+
return {
|
|
628
|
+
HostUrl: hostUrl,
|
|
629
|
+
AuthModel: authModel,
|
|
630
|
+
AppId: this.firstString(raw, ['AppId', 'appId', 'appid']),
|
|
631
|
+
AppKey: this.firstString(raw, ['AppKey', 'appKey', 'appkey']),
|
|
632
|
+
AppName: this.firstString(raw, ['AppName', 'appName', 'appname']),
|
|
633
|
+
Email: this.firstString(raw, ['Email', 'email', 'Username', 'username', 'UserEmail', 'userEmail']),
|
|
634
|
+
Password: this.firstString(raw, ['Password', 'password']),
|
|
635
|
+
AuthPath: this.firstString(raw, ['AuthPath', 'authPath']),
|
|
636
|
+
AccessTokenHeader: this.firstString(raw, ['AccessTokenHeader', 'accessTokenHeader']),
|
|
637
|
+
AppTokenHeader: this.firstString(raw, ['AppTokenHeader', 'appTokenHeader']),
|
|
638
|
+
ApiKey: this.firstString(raw, ['ApiKey', 'apiKey', 'apikey', 'x-api-key']),
|
|
639
|
+
ApiKeyHeader: this.firstString(raw, ['ApiKeyHeader', 'apiKeyHeader']),
|
|
640
|
+
RequestTimeoutMs: timeout,
|
|
641
|
+
TokenTTLMs: ttl,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
/** Resolves the per-tenant host (no trailing slash) — never a hardcoded host. */
|
|
645
|
+
resolveHost(config) {
|
|
646
|
+
return config.HostUrl.trim().replace(/\/+$/, '');
|
|
647
|
+
}
|
|
648
|
+
// ── Path / token helpers ─────────────────────────────────────────────────
|
|
649
|
+
/** All `{...}` placeholders in a path (space-tolerant), unwrapped. */
|
|
650
|
+
allTokens(path) {
|
|
651
|
+
return (path.match(/\{[^}]+\}/g) ?? []).map(s => s.slice(1, -1));
|
|
652
|
+
}
|
|
653
|
+
/** True for the page placeholder (`{Page Number}` / `{pageNumber}` / `{Page}`) — space/case tolerant. */
|
|
654
|
+
isPageToken(name) {
|
|
655
|
+
const n = name.toLowerCase().replace(/\s+/g, '');
|
|
656
|
+
return n === 'pagenumber' || n === 'page';
|
|
657
|
+
}
|
|
658
|
+
/** Parent (non-page) placeholders in a path. */
|
|
659
|
+
parentTokens(path) {
|
|
660
|
+
return this.allTokens(path).filter(t => !this.isPageToken(t));
|
|
661
|
+
}
|
|
662
|
+
/** True when the path still carries a page placeholder. */
|
|
663
|
+
hasPageToken(path) {
|
|
664
|
+
return this.allTokens(path).some(t => this.isPageToken(t));
|
|
665
|
+
}
|
|
666
|
+
/** Substitutes the page placeholder (space/case-tolerant) with the page number. */
|
|
667
|
+
substitutePagePlaceholder(path, page) {
|
|
668
|
+
return path.replace(/\{[^}]+\}/g, (m) => this.isPageToken(m.slice(1, -1)) ? String(page) : m);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Resolves the parent object name for a template var from the path segment IMMEDIATELY preceding the
|
|
672
|
+
* `{token}` (Impexium's REST convention: the parent resource name precedes its id), matched against a
|
|
673
|
+
* sibling IntegrationObject by name. Returns null when no emitted sibling matches (e.g. Courses, Users).
|
|
674
|
+
* Protected so tests can resolve a parent without a populated engine cache.
|
|
675
|
+
*/
|
|
676
|
+
resolveParentObjectName(apiPath, token, integrationID) {
|
|
677
|
+
const idx = apiPath.indexOf(`{${token}}`);
|
|
678
|
+
if (idx < 0)
|
|
679
|
+
return null;
|
|
680
|
+
const segment = apiPath.slice(0, idx).replace(/\/+$/, '').split('/').pop() ?? '';
|
|
681
|
+
if (!segment)
|
|
682
|
+
return null;
|
|
683
|
+
const siblings = IntegrationEngineBase.Instance.GetActiveIntegrationObjects(integrationID);
|
|
684
|
+
const match = siblings.find(s => s.Name.toLowerCase() === segment.toLowerCase());
|
|
685
|
+
return match ? match.Name : null;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Loads the synced parent record IDs from the local MJ database (via the object's EntityMap → the target
|
|
689
|
+
* entity's PK column). Protected so tests can inject fixed parent IDs without a live RunView.
|
|
690
|
+
*/
|
|
691
|
+
async loadParentRecordIDs(parentObjectName, integrationID, contextUser) {
|
|
692
|
+
const parentObj = IntegrationEngineBase.Instance.GetIntegrationObject(integrationID, parentObjectName);
|
|
693
|
+
if (!parentObj)
|
|
694
|
+
return [];
|
|
695
|
+
const pkField = this.GetCachedFields(parentObj.ID).find(f => f.IsPrimaryKey);
|
|
696
|
+
const pkFieldName = pkField ? pkField.Name : 'id';
|
|
697
|
+
const rv = new RunView();
|
|
698
|
+
const mapResult = await rv.RunView({
|
|
699
|
+
EntityName: 'MJ: Company Integration Entity Maps',
|
|
700
|
+
ExtraFilter: `ExternalObjectName='${parentObjectName.replace(/'/g, "''")}' AND SyncEnabled=1`,
|
|
701
|
+
Fields: ['Entity'],
|
|
702
|
+
MaxRows: 1,
|
|
703
|
+
ResultType: 'simple',
|
|
704
|
+
}, contextUser);
|
|
705
|
+
if (!mapResult.Success || mapResult.Results.length === 0)
|
|
706
|
+
return [];
|
|
707
|
+
const idResult = await rv.RunView({
|
|
708
|
+
EntityName: mapResult.Results[0].Entity,
|
|
709
|
+
Fields: [pkFieldName],
|
|
710
|
+
ResultType: 'simple',
|
|
711
|
+
}, contextUser);
|
|
712
|
+
if (!idResult.Success)
|
|
713
|
+
return [];
|
|
714
|
+
return idResult.Results.map(r => String(r[pkFieldName]));
|
|
715
|
+
}
|
|
716
|
+
/** Fills a CreateAPIPath's parent template vars from the record's own attributes (case/space-insensitive). */
|
|
717
|
+
substitutePathVarsFromAttributes(path, attributes) {
|
|
718
|
+
return path.replace(/\{([^}]+)\}/g, (match, name) => {
|
|
719
|
+
const v = this.lookupAttr(attributes, name);
|
|
720
|
+
return v != null && String(v).length > 0 ? encodeURIComponent(String(v)) : match;
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Fills a write (update/delete) path: parent vars from attributes first, then the remaining path id from
|
|
725
|
+
* the (possibly composite) ExternalID when idLocation is 'path'. When idLocation is 'body'/'header', the
|
|
726
|
+
* id is NOT path-substituted (the record body / a header carries it).
|
|
727
|
+
*/
|
|
728
|
+
substituteWritePath(path, attributes, externalID, idLocation) {
|
|
729
|
+
let out = this.substitutePathVarsFromAttributes(path, attributes);
|
|
730
|
+
if (externalID && (idLocation == null || idLocation === 'path')) {
|
|
731
|
+
const remaining = this.allTokens(out);
|
|
732
|
+
if (remaining.length > 0) {
|
|
733
|
+
const parts = externalID.split('|');
|
|
734
|
+
remaining.forEach((t, i) => {
|
|
735
|
+
const value = parts[i] ?? parts[parts.length - 1] ?? externalID;
|
|
736
|
+
out = out.replace(`{${t}}`, encodeURIComponent(value));
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return out;
|
|
741
|
+
}
|
|
742
|
+
/** Looks up an attribute by name, matching case- and space-insensitively (`{Record Number}` → recordNumber). */
|
|
743
|
+
lookupAttr(attributes, name) {
|
|
744
|
+
const norm = (s) => s.toLowerCase().replace(/\s+/g, '');
|
|
745
|
+
const target = norm(name);
|
|
746
|
+
for (const [k, v] of Object.entries(attributes)) {
|
|
747
|
+
if (norm(k) === target)
|
|
748
|
+
return v;
|
|
749
|
+
}
|
|
750
|
+
return undefined;
|
|
751
|
+
}
|
|
752
|
+
// ── URL + response helpers ───────────────────────────────────────────────
|
|
753
|
+
/** Joins a base URL and a path (mirrors the base's private BuildFullURL). */
|
|
754
|
+
joinURL(baseURL, apiPath) {
|
|
755
|
+
const base = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
|
|
756
|
+
const path = apiPath.startsWith('/') ? apiPath : `/${apiPath}`;
|
|
757
|
+
return `${base}${path}`;
|
|
758
|
+
}
|
|
759
|
+
/** Appends the metadata-driven incremental watermark query param when active. */
|
|
760
|
+
appendIncrementalParam(url, obj) {
|
|
761
|
+
const field = obj.IncrementalWatermarkField;
|
|
762
|
+
if (obj.SupportsIncrementalSync && field && this.currentWatermark) {
|
|
763
|
+
const sep = url.includes('?') ? '&' : '?';
|
|
764
|
+
return `${url}${sep}${encodeURIComponent(field)}=${encodeURIComponent(this.currentWatermark)}`;
|
|
765
|
+
}
|
|
766
|
+
return url;
|
|
767
|
+
}
|
|
768
|
+
/** 403/404 are skippable (permission-gated object / absent sub-resource) — degrade to zero rows. */
|
|
769
|
+
isSkippableStatus(status) {
|
|
770
|
+
return status === 403 || status === 404;
|
|
771
|
+
}
|
|
772
|
+
/** Throws a descriptive error on a non-2xx (non-skippable) status. */
|
|
773
|
+
ensureOk(response, url) {
|
|
774
|
+
if (response.Status < 200 || response.Status >= 300) {
|
|
775
|
+
const preview = typeof response.Body === 'string'
|
|
776
|
+
? response.Body.slice(0, 300)
|
|
777
|
+
: JSON.stringify(response.Body ?? '').slice(0, 300);
|
|
778
|
+
throw new Error(`[re:Members AMS] HTTP ${response.Status} from ${url}: ${preview}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
// ── Record + identity helpers ────────────────────────────────────────────
|
|
782
|
+
/** PK field names in Sequence order; empty when the object declares no PK (→ content-hash identity). */
|
|
783
|
+
pkFieldNames(fields) {
|
|
784
|
+
return fields.filter(f => f.IsPrimaryKey).sort((a, b) => a.Sequence - b.Sequence).map(f => f.Name);
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Builds an ExternalRecord with the FULL source record in Fields (full-record pass-through — the
|
|
788
|
+
* framework's custom-column capture diffs keys(Fields) against the field maps). ExternalID is the
|
|
789
|
+
* composite PK when every component is present + non-empty, else a deterministic content hash (which is
|
|
790
|
+
* also stamped into a single empty PK column so PK-less rows remain storable + idempotent). Mirrors the
|
|
791
|
+
* base's ToExternalRecord (which is private) for the parent-iterated path.
|
|
792
|
+
*/
|
|
793
|
+
buildExternalRecord(raw, objectType, pkFieldNames) {
|
|
794
|
+
const allPkPresent = pkFieldNames.length > 0
|
|
795
|
+
&& pkFieldNames.every(n => raw[n] != null && serializeKeyValue(raw[n]).length > 0);
|
|
796
|
+
const externalID = pkFieldNames.map(n => serializeKeyValue(raw[n])).join('|');
|
|
797
|
+
const resolvedID = allPkPresent ? externalID : computeContentHash(raw);
|
|
798
|
+
let fields = raw;
|
|
799
|
+
if (!allPkPresent && pkFieldNames.length === 1
|
|
800
|
+
&& (raw[pkFieldNames[0]] == null || serializeKeyValue(raw[pkFieldNames[0]]).length === 0)) {
|
|
801
|
+
fields = { ...raw, [pkFieldNames[0]]: resolvedID };
|
|
802
|
+
}
|
|
803
|
+
return { ExternalID: resolvedID, ObjectType: objectType, Fields: fields };
|
|
804
|
+
}
|
|
805
|
+
/** Builds a warning-only fetch result (surfaces a silent-empty as a structured SyncWarning). */
|
|
806
|
+
warnResult(obj, code, message) {
|
|
807
|
+
return { Records: [], HasMore: false, Warnings: [{ Code: code, Message: message, Data: { object: obj.Name } }] };
|
|
808
|
+
}
|
|
809
|
+
// ── Watermark helpers ────────────────────────────────────────────────────
|
|
810
|
+
/**
|
|
811
|
+
* The next watermark for a changed-since object is the time this sync STARTED (captured before any
|
|
812
|
+
* fetch), so the next incremental picks up everything modified since — a slight overlap is idempotent.
|
|
813
|
+
* Impexium's list responses carry no reliable single modified-date field to derive it from, so wall-clock
|
|
814
|
+
* start is the safe, monotonic choice. Only ever advances forward.
|
|
815
|
+
*/
|
|
816
|
+
computeNewWatermark(current, startTs) {
|
|
817
|
+
if (current && startTs <= current)
|
|
818
|
+
return current;
|
|
819
|
+
return startTs;
|
|
820
|
+
}
|
|
821
|
+
/** Formats a date as the Impexium changed-since format `yyyy-MM-ddTHH:mm:ss` (UTC). */
|
|
822
|
+
formatWatermark(date) {
|
|
823
|
+
return date.toISOString().replace(/\.\d+Z$/, '');
|
|
824
|
+
}
|
|
825
|
+
// ── Small utilities ──────────────────────────────────────────────────────
|
|
826
|
+
/** Returns the first present, non-empty string value among the given keys. */
|
|
827
|
+
firstString(obj, keys) {
|
|
828
|
+
for (const k of keys) {
|
|
829
|
+
const v = obj[k];
|
|
830
|
+
if (typeof v === 'string' && v.length > 0)
|
|
831
|
+
return v;
|
|
832
|
+
}
|
|
833
|
+
return undefined;
|
|
834
|
+
}
|
|
835
|
+
/** Returns the first present finite number among the given keys. */
|
|
836
|
+
firstNumber(obj, keys) {
|
|
837
|
+
for (const k of keys) {
|
|
838
|
+
const v = obj[k];
|
|
839
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
840
|
+
return v;
|
|
841
|
+
}
|
|
842
|
+
return undefined;
|
|
843
|
+
}
|
|
844
|
+
/** Reads an `id`-like value as a string; undefined when absent/empty. */
|
|
845
|
+
readId(o) {
|
|
846
|
+
for (const k of ['id', 'ID', 'Id']) {
|
|
847
|
+
const v = o[k];
|
|
848
|
+
if (typeof v === 'string' && v.length > 0)
|
|
849
|
+
return v;
|
|
850
|
+
if (typeof v === 'number')
|
|
851
|
+
return String(v);
|
|
852
|
+
}
|
|
853
|
+
return undefined;
|
|
854
|
+
}
|
|
855
|
+
/** Stable total order over ID strings — numeric when ALL are integer-like, else lexical. */
|
|
856
|
+
sortIdsStable(ids) {
|
|
857
|
+
return [...ids].sort((a, b) => this.compareIds(a, b));
|
|
858
|
+
}
|
|
859
|
+
/** Total-order comparator matching sortIdsStable, used for keyset resume (id > AfterKeyValue). */
|
|
860
|
+
compareIds(a, b) {
|
|
861
|
+
if (/^\d+$/.test(a) && /^\d+$/.test(b)) {
|
|
862
|
+
const na = Number(a), nb = Number(b);
|
|
863
|
+
return na === nb ? 0 : (na < nb ? -1 : 1);
|
|
864
|
+
}
|
|
865
|
+
return a < b ? -1 : (a > b ? 1 : 0);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
ImpexiumConnector = __decorate([
|
|
869
|
+
RegisterClass(BaseIntegrationConnector, 'ImpexiumConnector')
|
|
870
|
+
], ImpexiumConnector);
|
|
871
|
+
export { ImpexiumConnector };
|
|
872
|
+
/** Tree-shaking prevention — import and call from the package entry point. */
|
|
873
|
+
export function LoadImpexiumConnector() { }
|
|
874
|
+
//# sourceMappingURL=ImpexiumConnector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImpexiumConnector.js","sourceRoot":"","sources":["../src/ImpexiumConnector.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * from './ImpexiumConnector.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 './ImpexiumConnector.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,wBAAwB,CAAC;AAEvC;oGACoG;AACpG,MAAM,UAAU,iBAAiB,KAAiD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memberjunction/connector-impexium",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MemberJunction Impexium 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"
|
|
14
|
+
},
|
|
15
|
+
"author": "MemberJunction.com",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@memberjunction/core": ">=5.43.0 <6.0.0",
|
|
19
|
+
"@memberjunction/core-entities": ">=5.43.0 <6.0.0",
|
|
20
|
+
"@memberjunction/global": ">=5.43.0 <6.0.0",
|
|
21
|
+
"@memberjunction/integration-engine": ">=5.43.0 <6.0.0",
|
|
22
|
+
"@memberjunction/integration-engine-base": ">=5.43.0 <6.0.0"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@memberjunction/connector-schema-merge": "^1.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@memberjunction/connector-schema-merge": "^1.0.0",
|
|
29
|
+
"@memberjunction/core": "^5.43.0",
|
|
30
|
+
"@memberjunction/core-entities": "^5.43.0",
|
|
31
|
+
"@memberjunction/global": "^5.43.0",
|
|
32
|
+
"@memberjunction/integration-engine": "^5.43.0",
|
|
33
|
+
"@memberjunction/integration-engine-base": "^5.43.0",
|
|
34
|
+
"@types/node": "24.10.11",
|
|
35
|
+
"tsc-alias": "^1.8.16",
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"vitest": "^4.0.18"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/MemberJunction/Integrations"
|
|
42
|
+
}
|
|
43
|
+
}
|