@memberjunction/connector-rhythm-software 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/RhythmConnector.d.ts +193 -0
- package/dist/RhythmConnector.js +794 -0
- package/dist/RhythmConnector.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 +40 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { type UserInfo } from '@memberjunction/core';
|
|
2
|
+
import type { MJCompanyIntegrationEntity } from '@memberjunction/core-entities';
|
|
3
|
+
import { BaseRESTIntegrationConnector, type RESTAuthContext, type RESTResponse, type PaginationState, type PaginationType, type ConnectionTestResult, type ExternalObjectSchema, type ExternalFieldSchema, type ExternalRecord, type FetchContext, type FetchBatchResult, type RateLimitPolicy, type CreateRecordContext, type UpdateRecordContext, type DeleteRecordContext, type GetRecordContext, type CRUDResult } from '@memberjunction/integration-engine';
|
|
4
|
+
/**
|
|
5
|
+
* Rhythm connection configuration, parsed from the attached MJ Credential (preferred) or the
|
|
6
|
+
* CompanyIntegration.Configuration JSON. Field names are read case-insensitively. NONE of these
|
|
7
|
+
* values are read at build time — they are resolved from the bound credential at runtime.
|
|
8
|
+
*/
|
|
9
|
+
export interface RhythmConnectionConfig {
|
|
10
|
+
/** OAuth2 (Auth0 M2M) client identifier. */
|
|
11
|
+
ClientId: string;
|
|
12
|
+
/** OAuth2 (Auth0 M2M) client secret. */
|
|
13
|
+
ClientSecret: string;
|
|
14
|
+
/**
|
|
15
|
+
* Pre-provided OAuth2 bearer access token. When supplied, the connector uses it DIRECTLY and skips the
|
|
16
|
+
* Auth0 client_credentials mint (ClientId/Secret + Auth0Domain then become optional). Used by the
|
|
17
|
+
* credential-free e2e harness (AccessToken bypass) and token-passthrough deployments.
|
|
18
|
+
*/
|
|
19
|
+
AccessToken?: string;
|
|
20
|
+
/** Tenant-specific Auth0 domain (e.g. `your-tenant.us.auth0.com`) the token is minted from. */
|
|
21
|
+
Auth0Domain: string;
|
|
22
|
+
/** Auth0 API audience. Default `https://api.rhythmsoftware.com`. */
|
|
23
|
+
Audience?: string;
|
|
24
|
+
/** Rhythm tenant identifier — substituted into every endpoint's {tenantId} path param. */
|
|
25
|
+
TenantId: string;
|
|
26
|
+
/** Optional absolute token-endpoint override; defaults to `https://{Auth0Domain}/oauth/token`. */
|
|
27
|
+
TokenURL?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Optional single base-URL override. Rhythm normally routes each object to its own module
|
|
30
|
+
* subdomain (`Configuration.Subdomain`); when this is set, EVERY request is routed through this
|
|
31
|
+
* one origin instead (path preserved). Real multi-subdomain tenants leave it unset — it exists
|
|
32
|
+
* for a proxying API gateway or the credential-free e2e mock that fronts all modules on one host.
|
|
33
|
+
*/
|
|
34
|
+
BaseURL?: string;
|
|
35
|
+
/** Maximum retries for rate-limited / transient failures. Default 3. */
|
|
36
|
+
MaxRetries?: number;
|
|
37
|
+
/** HTTP request timeout in ms. Default 30000. */
|
|
38
|
+
RequestTimeoutMs?: number;
|
|
39
|
+
}
|
|
40
|
+
export declare class RhythmConnector extends BaseRESTIntegrationConnector {
|
|
41
|
+
/** Cached auth context for the current sync run. */
|
|
42
|
+
private authCache;
|
|
43
|
+
/** Shared OAuth2 token manager — owns the client-credentials round-trip + token cache. */
|
|
44
|
+
private readonly tokenManager;
|
|
45
|
+
/**
|
|
46
|
+
* Subdomain the CURRENT operation routes to (set per-call before the generic CRUD path reads
|
|
47
|
+
* GetBaseURL). Rhythm has no single base URL — each IO lives on its own module subdomain.
|
|
48
|
+
*/
|
|
49
|
+
private currentSubdomain;
|
|
50
|
+
get IntegrationName(): string;
|
|
51
|
+
get SupportsCreate(): boolean;
|
|
52
|
+
get SupportsUpdate(): boolean;
|
|
53
|
+
get SupportsDelete(): boolean;
|
|
54
|
+
/** True when any cached IntegrationObject satisfies the predicate. []→false when the engine
|
|
55
|
+
* cache is unavailable (e.g. capability probed before configuration) — fail-safe read-only. */
|
|
56
|
+
private anyObjectDeclares;
|
|
57
|
+
/**
|
|
58
|
+
* No-watermark keyset resume hint. Rhythm exposes no server-side incremental filter, but every
|
|
59
|
+
* record carries the universal `id` PK (StableOrderingKey="id" on every IO). Returning it lets
|
|
60
|
+
* the engine resume a full scan by id. Provable: 377/377 IOs declare id as PK.
|
|
61
|
+
*/
|
|
62
|
+
StableOrderingKey(_objectName: string): string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Conservative AIMD rate policy. The Rhythm M2M token carries a tenant-configured monthly request
|
|
65
|
+
* budget (no fixed per-second limit is documented in the specs); 429 means the tenant limit was
|
|
66
|
+
* exceeded. We declare a gentle sustained rate + multiplicative back-off so a sync never bursts
|
|
67
|
+
* the budget, and parse Retry-After from the 429 (see ExtractRetryAfterMs).
|
|
68
|
+
*/
|
|
69
|
+
get RateLimitPolicy(): RateLimitPolicy | null;
|
|
70
|
+
/** Parses a Retry-After header (seconds or HTTP-date) on a 429 into ms for the engine's AIMD bucket. */
|
|
71
|
+
ExtractRetryAfterMs(error: unknown): number | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* OAuth 2.0 Client Credentials (Auth0 M2M) authentication. Mints/refreshes the Bearer JWT via the
|
|
74
|
+
* shared {@link OAuth2TokenManager} — the Auth0-required `audience` rides the additive ExtraParams.
|
|
75
|
+
*/
|
|
76
|
+
protected Authenticate(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<RESTAuthContext>;
|
|
77
|
+
/** Runs the Auth0 client_credentials token round-trip through OAuth2TokenManager. */
|
|
78
|
+
private MintToken;
|
|
79
|
+
/** Sends the OAuth2 bearer token on every request. */
|
|
80
|
+
protected BuildHeaders(auth: RESTAuthContext): Record<string, string>;
|
|
81
|
+
/**
|
|
82
|
+
* Returns the base URL for the CURRENT operation = the module subdomain set by FetchChanges/CRUD
|
|
83
|
+
* before delegating. Rhythm has no single base URL, so this is per-object (the subdomain is
|
|
84
|
+
* resolved from the IO's Configuration.Subdomain). Falls back to the auth-derived host only if a
|
|
85
|
+
* subdomain was somehow not set (defensive; never expected in the normal path).
|
|
86
|
+
*/
|
|
87
|
+
protected GetBaseURL(_companyIntegration: MJCompanyIntegrationEntity, auth: RESTAuthContext): string;
|
|
88
|
+
/**
|
|
89
|
+
* Strips the Rhythm list envelope `{ Count, Items, LastEvaluatedKey }` down to the record array.
|
|
90
|
+
* Handles three shapes:
|
|
91
|
+
* 1. `{ Items: [...] }` — the standard list envelope (responseDataKey defaults to "Items").
|
|
92
|
+
* 2. raw array — defensive (an endpoint returning a bare array).
|
|
93
|
+
* 3. single object — a get-one detail record.
|
|
94
|
+
* The FULL source record passes through (no field filtering) so the framework's custom-column
|
|
95
|
+
* capture sees everything the source returned.
|
|
96
|
+
*/
|
|
97
|
+
protected NormalizeResponse(rawBody: unknown, responseDataKey: string | null): Record<string, unknown>[];
|
|
98
|
+
/**
|
|
99
|
+
* Derives Rhythm's DynamoDB-style keyset cursor state. The next page is fetched with
|
|
100
|
+
* `?exclusiveStartKey=<LastEvaluatedKey>`; an absent/null/empty LastEvaluatedKey marks the final
|
|
101
|
+
* page (Items also empty ⇒ done). The cursor may be a string or an object key — it is serialized
|
|
102
|
+
* to a string when present.
|
|
103
|
+
*/
|
|
104
|
+
protected ExtractPaginationInfo(rawBody: unknown, _paginationType: PaginationType, _currentPage: number, _currentOffset: number, _pageSize: number): PaginationState;
|
|
105
|
+
FetchChanges(ctx: FetchContext): Promise<FetchBatchResult>;
|
|
106
|
+
/**
|
|
107
|
+
* fk-child traversal — CONNECTOR-SIDE, no framework change. For an object whose only list route is
|
|
108
|
+
* a parent-FK path `/{resource}/{tenantId}/{parent}/{fkId}`, resolve the parent object, page a
|
|
109
|
+
* BOUNDED set of parent ids, and fetch children per parent (the engine's private template-var
|
|
110
|
+
* traversal can't be reused, and it treats {tenantId} as a parent — so we do it here). Parent
|
|
111
|
+
* resolution: the FK field's RelatedIntegrationObjectID when present (deployed), else the parent
|
|
112
|
+
* resource segment in ReadPath (works on the seeded/test cache where @lookup refs are scrubbed).
|
|
113
|
+
*/
|
|
114
|
+
private FetchFkChild;
|
|
115
|
+
/** Resolve the parent IntegrationObject for an fk-child's FK variable: FK metadata first, else the parent-resource path segment. */
|
|
116
|
+
private ResolveFkParent;
|
|
117
|
+
/** Fetch a bounded page of a parent object's PK ids (its own list/search read) for fk-child traversal. */
|
|
118
|
+
private FetchParentIds;
|
|
119
|
+
CreateRecord(ctx: CreateRecordContext): Promise<CRUDResult>;
|
|
120
|
+
UpdateRecord(ctx: UpdateRecordContext): Promise<CRUDResult>;
|
|
121
|
+
DeleteRecord(ctx: DeleteRecordContext): Promise<CRUDResult>;
|
|
122
|
+
GetRecord(ctx: GetRecordContext): Promise<ExternalRecord | null>;
|
|
123
|
+
/** Resolves + sets the current module subdomain for an object before a generic CRUD delegate. */
|
|
124
|
+
private RouteSubdomain;
|
|
125
|
+
/**
|
|
126
|
+
* HTTP transport with retry/back-off for 429/503 and substitution of the connection-level
|
|
127
|
+
* {tenantId} into the URL (the single chokepoint every request flows through, so tenant routing
|
|
128
|
+
* is uniform across fetch + CRUD). On a 429 the response carries the Retry-After the engine's AIMD
|
|
129
|
+
* bucket consumes via ExtractRetryAfterMs.
|
|
130
|
+
*/
|
|
131
|
+
protected MakeHTTPRequest(auth: RESTAuthContext, url: string, method: string, headers: Record<string, string>, body?: unknown): Promise<RESTResponse>;
|
|
132
|
+
/**
|
|
133
|
+
* Tests connectivity by minting an Auth0 M2M token and listing one page of the first cached IO
|
|
134
|
+
* (subdomain + {tenantId} routed). A 2xx confirms the OAuth2 credentials + tenant are valid.
|
|
135
|
+
* Falls back to asserting the token mint alone when no catalog is loaded.
|
|
136
|
+
*/
|
|
137
|
+
TestConnection(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<ConnectionTestResult>;
|
|
138
|
+
/**
|
|
139
|
+
* Discovers the full object universe from the IntegrationEngineBase cache (the Declared metadata
|
|
140
|
+
* in `.rhythm.integration.json`). Rhythm publishes its catalog credential-free (15 OpenAPI v1
|
|
141
|
+
* specs), so the baseline is Declared metadata — never hardcoded here, never sampled at build.
|
|
142
|
+
*/
|
|
143
|
+
DiscoverObjects(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<ExternalObjectSchema[]>;
|
|
144
|
+
/** Discovers fields for an object from the cached Declared metadata. */
|
|
145
|
+
DiscoverFields(companyIntegration: MJCompanyIntegrationEntity, objectName: string, contextUser: UserInfo): Promise<ExternalFieldSchema[]>;
|
|
146
|
+
/**
|
|
147
|
+
* Parses the connection config, preferring the attached MJ Credential over the raw Configuration
|
|
148
|
+
* JSON. Credential bytes are resolved at runtime — never at build.
|
|
149
|
+
*/
|
|
150
|
+
private ParseConfig;
|
|
151
|
+
/** Loads the connection config from the MJ: Credentials entity Values JSON. */
|
|
152
|
+
private ParseConfigFromCredential;
|
|
153
|
+
/** Validates the parsed config + applies defaults. Field names are read case-insensitively. */
|
|
154
|
+
private ValidateConfig;
|
|
155
|
+
/** Resolves the Auth0 token endpoint: credential TokenURL override, else `https://{domain}/oauth/token`. */
|
|
156
|
+
private ResolveTokenURL;
|
|
157
|
+
/** Parses an IO's Configuration JSON (tolerant of absent/malformed). */
|
|
158
|
+
private ParseObjectConfig;
|
|
159
|
+
/** Resolves the module subdomain for an object (required — every Rhythm IO declares one). */
|
|
160
|
+
private ResolveSubdomain;
|
|
161
|
+
/** Substitutes the connection-level {tenantId} into a path/URL. */
|
|
162
|
+
private SubstituteTenant;
|
|
163
|
+
/** Joins a base subdomain and a path into a full URL (single slash). */
|
|
164
|
+
private JoinURL;
|
|
165
|
+
/** Appends the keyset cursor query param to a URL when a cursor is present. */
|
|
166
|
+
private AppendCursor;
|
|
167
|
+
/** Returns PK field names (sorted by Sequence), falling back to ['id'] (Rhythm's universal PK). */
|
|
168
|
+
private PrimaryKeyFieldNames;
|
|
169
|
+
/** First cached IO for the integration, used as the TestConnection read probe. */
|
|
170
|
+
private FirstProbeObject;
|
|
171
|
+
/**
|
|
172
|
+
* Builds an ExternalRecord from a raw source record, mirroring the base's §4 identity logic:
|
|
173
|
+
* use the declared PK when every component is present + non-empty, else a deterministic
|
|
174
|
+
* content-hash (so PK-less/partial-key rows stay syncable + dedupable). The FULL source record is
|
|
175
|
+
* preserved in Fields (full-record pass-through); a single-PK whose value is empty is stamped with
|
|
176
|
+
* the synthetic identity so the codegen reload finds the row.
|
|
177
|
+
*/
|
|
178
|
+
private buildExternalRecord;
|
|
179
|
+
/** Validates an HTTP response and throws (with header context) on non-2xx. */
|
|
180
|
+
private ValidateResponse;
|
|
181
|
+
/** Parses a Retry-After header (seconds or HTTP-date) into ms, if present. */
|
|
182
|
+
private RetryAfterMs;
|
|
183
|
+
/** Exponential backoff delay for retry attempts (capped at 30s). */
|
|
184
|
+
private BackoffDelay;
|
|
185
|
+
/** Checks whether an error is transient (network/timeout). */
|
|
186
|
+
private IsTransientNetworkError;
|
|
187
|
+
/** Builds the normalized RESTResponse from a fetch Response. */
|
|
188
|
+
private BuildRESTResponse;
|
|
189
|
+
/** Promise-wrapped setTimeout. */
|
|
190
|
+
private Sleep;
|
|
191
|
+
}
|
|
192
|
+
/** Tree-shaking prevention — import and call from the package entry point. */
|
|
193
|
+
export declare function LoadRhythmConnector(): void;
|
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* RhythmConnector — Integration connector for the Rhythm Software (Rhythm AMS) REST API.
|
|
9
|
+
*
|
|
10
|
+
* API docs: https://docs.api.rhythmsoftware.com/ (15 per-module OpenAPI v1 specs,
|
|
11
|
+
* info.version "v1-2025-04-18"). Catalog is credential-free Declared metadata in
|
|
12
|
+
* `metadata/integrations/rhythm/.rhythm.integration.json` (377 IOs across 14 modules).
|
|
13
|
+
*
|
|
14
|
+
* ── Auth: OAuth 2.0 Client Credentials (M2M) via Auth0 ───────────────────────
|
|
15
|
+
* Mints a Bearer JWT through the shared {@link OAuth2TokenManager} (no inlined token logic):
|
|
16
|
+
* POST https://{auth0Domain}/oauth/token
|
|
17
|
+
* grant_type=client_credentials & client_id & client_secret & audience
|
|
18
|
+
* `audience` (Auth0-specific, default https://api.rhythmsoftware.com) flows through the
|
|
19
|
+
* manager's additive `ExtraParams`. The JWT is valid ~24h; the manager re-mints on expiry.
|
|
20
|
+
* Every request sends `Authorization: Bearer {accessToken}`.
|
|
21
|
+
*
|
|
22
|
+
* ── Routing: per-module subdomains + {tenantId} path param (idiosyncratic) ───
|
|
23
|
+
* Rhythm has NO single base URL — each module is its own subdomain
|
|
24
|
+
* (rolodex.api.rhythmsoftware.com, membership.api…, events.api…, …). Each IO carries its
|
|
25
|
+
* `Configuration.Subdomain`; the connector routes per-object. EVERY endpoint embeds {tenantId}
|
|
26
|
+
* as a path param (distinct from the OAuth credentials), substituted from the connection's
|
|
27
|
+
* TenantId at request time. This is why FetchChanges + GetBaseURL + MakeHTTPRequest are
|
|
28
|
+
* overridden — the generic single-base-URL/template-var model does not fit.
|
|
29
|
+
*
|
|
30
|
+
* ── Pagination: DynamoDB-style keyset cursor ────────────────────────────────
|
|
31
|
+
* List endpoints return `{ Count, Items, LastEvaluatedKey }`. The next page is requested with
|
|
32
|
+
* `?exclusiveStartKey=<LastEvaluatedKey>`; an absent/null LastEvaluatedKey marks the final page.
|
|
33
|
+
* No page-size param (server-controlled). Search endpoints (POST …/search) are out of scope here.
|
|
34
|
+
*
|
|
35
|
+
* ── Incremental: none server-side → full pull + content-hash dedup ──────────
|
|
36
|
+
* No list endpoint exposes a date filter in any of the 15 specs. Records carry the vendor-managed
|
|
37
|
+
* `sys_last_modified_at` timestamp but it is NOT a server-side filter param, so every IO is a full
|
|
38
|
+
* pull (SupportsIncrementalSync=false) and the engine's content-hash skip de-dupes unchanged rows.
|
|
39
|
+
*
|
|
40
|
+
* ── Write: full CRUD (metadata-driven generic path) ─────────────────────────
|
|
41
|
+
* 356/377 IOs are create-capable, 361 update, 352 delete — the per-operation CRUD columns are
|
|
42
|
+
* populated on every writable IO, so the base BaseRESTIntegrationConnector generic
|
|
43
|
+
* CreateRecord/UpdateRecord/DeleteRecord path executes them as-is (no per-verb override). POST
|
|
44
|
+
* creates (ID auto-generated, returned in the body); PATCH updates (RFC 6902); DELETE is a hard
|
|
45
|
+
* delete. Subdomain + {tenantId} routing is layered in via GetBaseURL/MakeHTTPRequest.
|
|
46
|
+
*/
|
|
47
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
48
|
+
import { Metadata } from '@memberjunction/core';
|
|
49
|
+
import { BaseIntegrationConnector, BaseRESTIntegrationConnector, OAuth2TokenManager, computeContentHash, serializeKeyValue, } from '@memberjunction/integration-engine';
|
|
50
|
+
import { IntegrationEngineBase } from '@memberjunction/integration-engine-base';
|
|
51
|
+
// ─── Constants ───────────────────────────────────────────────────────
|
|
52
|
+
/** The canonical MJ: Integrations.Name — part of the three-way invariant. */
|
|
53
|
+
const INTEGRATION_NAME = 'Rhythm Software';
|
|
54
|
+
/** Auth0 token endpoint path appended to `https://{Auth0Domain}` when no TokenURL override is given. */
|
|
55
|
+
const DEFAULT_TOKEN_PATH = '/oauth/token';
|
|
56
|
+
/** Default Auth0 API audience for Rhythm M2M tokens (per integration Configuration). */
|
|
57
|
+
const DEFAULT_AUDIENCE = 'https://api.rhythmsoftware.com';
|
|
58
|
+
/** DynamoDB-style keyset cursor query param (uniform across all 15 modules per the OpenAPI specs). */
|
|
59
|
+
const DEFAULT_CURSOR_PARAM = 'exclusiveStartKey';
|
|
60
|
+
/** Response envelope key carrying the record array. */
|
|
61
|
+
const DEFAULT_ITEMS_KEY = 'Items';
|
|
62
|
+
/** Response envelope key carrying the next-page cursor (absent/null ⇒ final page). */
|
|
63
|
+
const DEFAULT_CURSOR_KEY = 'LastEvaluatedKey';
|
|
64
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
65
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
66
|
+
/** Safety cap on pages per FetchChanges call so a runaway/looping cursor can never spin forever. */
|
|
67
|
+
const MAX_PAGES_PER_FETCH = 10_000;
|
|
68
|
+
// ─── Connector Implementation ────────────────────────────────────────
|
|
69
|
+
let RhythmConnector = class RhythmConnector extends BaseRESTIntegrationConnector {
|
|
70
|
+
constructor() {
|
|
71
|
+
super(...arguments);
|
|
72
|
+
/** Cached auth context for the current sync run. */
|
|
73
|
+
this.authCache = null;
|
|
74
|
+
/** Shared OAuth2 token manager — owns the client-credentials round-trip + token cache. */
|
|
75
|
+
this.tokenManager = new OAuth2TokenManager();
|
|
76
|
+
/**
|
|
77
|
+
* Subdomain the CURRENT operation routes to (set per-call before the generic CRUD path reads
|
|
78
|
+
* GetBaseURL). Rhythm has no single base URL — each IO lives on its own module subdomain.
|
|
79
|
+
*/
|
|
80
|
+
this.currentSubdomain = null;
|
|
81
|
+
}
|
|
82
|
+
// Returns the EXACT MJ: Integrations.Name string LITERAL (not the INTEGRATION_NAME const) so the
|
|
83
|
+
// T1 ThreeWayName invariant can statically parse the getter's returned value from connector source.
|
|
84
|
+
get IntegrationName() { return 'Rhythm Software'; }
|
|
85
|
+
// ── Capability getters: METADATA-DRIVEN (no hardcoded answer) ─────
|
|
86
|
+
//
|
|
87
|
+
// Write capability FOLLOWS the per-operation CRUD columns on the cached IntegrationObjects
|
|
88
|
+
// (Declared metadata). An object is create-capable when it declares both CreateAPIPath +
|
|
89
|
+
// CreateMethod; same for update/delete. The Rhythm catalog populates these on every writable
|
|
90
|
+
// IO, so the base generic CRUD path executes them — these getters never bake a hardcoded answer.
|
|
91
|
+
get SupportsCreate() {
|
|
92
|
+
return this.anyObjectDeclares(o => !!o.CreateAPIPath && !!o.CreateMethod);
|
|
93
|
+
}
|
|
94
|
+
get SupportsUpdate() {
|
|
95
|
+
return this.anyObjectDeclares(o => !!o.UpdateAPIPath && !!o.UpdateMethod);
|
|
96
|
+
}
|
|
97
|
+
get SupportsDelete() {
|
|
98
|
+
return this.anyObjectDeclares(o => !!o.DeleteAPIPath && !!o.DeleteMethod);
|
|
99
|
+
}
|
|
100
|
+
/** True when any cached IntegrationObject satisfies the predicate. []→false when the engine
|
|
101
|
+
* cache is unavailable (e.g. capability probed before configuration) — fail-safe read-only. */
|
|
102
|
+
anyObjectDeclares(pred) {
|
|
103
|
+
const integration = IntegrationEngineBase.Instance.GetIntegrationByName(INTEGRATION_NAME);
|
|
104
|
+
if (!integration)
|
|
105
|
+
return false;
|
|
106
|
+
return IntegrationEngineBase.Instance.GetActiveIntegrationObjects(integration.ID).some(pred);
|
|
107
|
+
}
|
|
108
|
+
// ── Sync-efficiency hooks (provable from the frozen contract) ─────
|
|
109
|
+
/**
|
|
110
|
+
* No-watermark keyset resume hint. Rhythm exposes no server-side incremental filter, but every
|
|
111
|
+
* record carries the universal `id` PK (StableOrderingKey="id" on every IO). Returning it lets
|
|
112
|
+
* the engine resume a full scan by id. Provable: 377/377 IOs declare id as PK.
|
|
113
|
+
*/
|
|
114
|
+
StableOrderingKey(_objectName) {
|
|
115
|
+
return 'id';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Conservative AIMD rate policy. The Rhythm M2M token carries a tenant-configured monthly request
|
|
119
|
+
* budget (no fixed per-second limit is documented in the specs); 429 means the tenant limit was
|
|
120
|
+
* exceeded. We declare a gentle sustained rate + multiplicative back-off so a sync never bursts
|
|
121
|
+
* the budget, and parse Retry-After from the 429 (see ExtractRetryAfterMs).
|
|
122
|
+
*/
|
|
123
|
+
get RateLimitPolicy() {
|
|
124
|
+
return { TokensPerSec: 5, Burst: 5, ThrottleBackoffFactor: 0.5 };
|
|
125
|
+
}
|
|
126
|
+
/** Parses a Retry-After header (seconds or HTTP-date) on a 429 into ms for the engine's AIMD bucket. */
|
|
127
|
+
ExtractRetryAfterMs(error) {
|
|
128
|
+
if (!error || typeof error !== 'object')
|
|
129
|
+
return undefined;
|
|
130
|
+
const headers = error.Headers;
|
|
131
|
+
const raw = headers?.['retry-after'];
|
|
132
|
+
if (!raw)
|
|
133
|
+
return undefined;
|
|
134
|
+
const asSeconds = Number(raw);
|
|
135
|
+
if (!isNaN(asSeconds))
|
|
136
|
+
return Math.max(0, asSeconds * 1_000);
|
|
137
|
+
const asDate = new Date(raw).getTime();
|
|
138
|
+
if (!isNaN(asDate))
|
|
139
|
+
return Math.max(0, asDate - Date.now());
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
// ─── BaseRESTIntegrationConnector abstract methods ──────────────
|
|
143
|
+
/**
|
|
144
|
+
* OAuth 2.0 Client Credentials (Auth0 M2M) authentication. Mints/refreshes the Bearer JWT via the
|
|
145
|
+
* shared {@link OAuth2TokenManager} — the Auth0-required `audience` rides the additive ExtraParams.
|
|
146
|
+
*/
|
|
147
|
+
async Authenticate(companyIntegration, contextUser) {
|
|
148
|
+
if (this.authCache)
|
|
149
|
+
return this.authCache;
|
|
150
|
+
const config = await this.ParseConfig(companyIntegration, contextUser);
|
|
151
|
+
// Honor a pre-provided AccessToken (credential-free e2e bypass + token-passthrough deployments):
|
|
152
|
+
// use it directly and skip the Auth0 client_credentials mint when a bearer is supplied.
|
|
153
|
+
const token = (config.AccessToken && config.AccessToken.length > 0)
|
|
154
|
+
? config.AccessToken
|
|
155
|
+
: await this.MintToken(config);
|
|
156
|
+
const auth = { Token: token, Config: config };
|
|
157
|
+
this.authCache = auth;
|
|
158
|
+
return auth;
|
|
159
|
+
}
|
|
160
|
+
/** Runs the Auth0 client_credentials token round-trip through OAuth2TokenManager. */
|
|
161
|
+
async MintToken(config) {
|
|
162
|
+
const req = {
|
|
163
|
+
TokenURL: this.ResolveTokenURL(config),
|
|
164
|
+
ClientId: config.ClientId,
|
|
165
|
+
ClientSecret: config.ClientSecret,
|
|
166
|
+
// Auth0 client_credentials requires `audience`; flows through the manager's ExtraParams.
|
|
167
|
+
ExtraParams: { audience: config.Audience ?? DEFAULT_AUDIENCE },
|
|
168
|
+
TimeoutMs: config.RequestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
169
|
+
};
|
|
170
|
+
const token = await this.tokenManager.GetAccessToken(req, 'client_credentials');
|
|
171
|
+
return token.AccessToken;
|
|
172
|
+
}
|
|
173
|
+
/** Sends the OAuth2 bearer token on every request. */
|
|
174
|
+
BuildHeaders(auth) {
|
|
175
|
+
const token = auth.Token ?? auth.Token ?? '';
|
|
176
|
+
return {
|
|
177
|
+
'Authorization': `Bearer ${token}`,
|
|
178
|
+
'Accept': 'application/json',
|
|
179
|
+
'Content-Type': 'application/json',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Returns the base URL for the CURRENT operation = the module subdomain set by FetchChanges/CRUD
|
|
184
|
+
* before delegating. Rhythm has no single base URL, so this is per-object (the subdomain is
|
|
185
|
+
* resolved from the IO's Configuration.Subdomain). Falls back to the auth-derived host only if a
|
|
186
|
+
* subdomain was somehow not set (defensive; never expected in the normal path).
|
|
187
|
+
*/
|
|
188
|
+
GetBaseURL(_companyIntegration, auth) {
|
|
189
|
+
// Connection-level base-URL override (a proxying gateway, or the credential-free e2e mock that
|
|
190
|
+
// fronts every module on one host): route EVERY module-subdomain request through this single
|
|
191
|
+
// origin, preserving the resource path. Real multi-subdomain tenants leave it unset.
|
|
192
|
+
const override = auth?.Config?.BaseURL;
|
|
193
|
+
if (override && override.length > 0) {
|
|
194
|
+
try {
|
|
195
|
+
return new URL(override).origin;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return override.replace(/\/+$/, '');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (this.currentSubdomain)
|
|
202
|
+
return this.currentSubdomain;
|
|
203
|
+
throw new Error('RhythmConnector: no module subdomain set for the current operation — ' +
|
|
204
|
+
'every Rhythm IO must carry Configuration.Subdomain.');
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Strips the Rhythm list envelope `{ Count, Items, LastEvaluatedKey }` down to the record array.
|
|
208
|
+
* Handles three shapes:
|
|
209
|
+
* 1. `{ Items: [...] }` — the standard list envelope (responseDataKey defaults to "Items").
|
|
210
|
+
* 2. raw array — defensive (an endpoint returning a bare array).
|
|
211
|
+
* 3. single object — a get-one detail record.
|
|
212
|
+
* The FULL source record passes through (no field filtering) so the framework's custom-column
|
|
213
|
+
* capture sees everything the source returned.
|
|
214
|
+
*/
|
|
215
|
+
NormalizeResponse(rawBody, responseDataKey) {
|
|
216
|
+
if (rawBody == null)
|
|
217
|
+
return [];
|
|
218
|
+
if (Array.isArray(rawBody)) {
|
|
219
|
+
return rawBody;
|
|
220
|
+
}
|
|
221
|
+
if (typeof rawBody === 'object') {
|
|
222
|
+
const body = rawBody;
|
|
223
|
+
const itemsKey = responseDataKey ?? DEFAULT_ITEMS_KEY;
|
|
224
|
+
if (Array.isArray(body[itemsKey])) {
|
|
225
|
+
return body[itemsKey];
|
|
226
|
+
}
|
|
227
|
+
// A single detail record (get-one) — not a list envelope.
|
|
228
|
+
return [body];
|
|
229
|
+
}
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Derives Rhythm's DynamoDB-style keyset cursor state. The next page is fetched with
|
|
234
|
+
* `?exclusiveStartKey=<LastEvaluatedKey>`; an absent/null/empty LastEvaluatedKey marks the final
|
|
235
|
+
* page (Items also empty ⇒ done). The cursor may be a string or an object key — it is serialized
|
|
236
|
+
* to a string when present.
|
|
237
|
+
*/
|
|
238
|
+
ExtractPaginationInfo(rawBody, _paginationType, _currentPage, _currentOffset, _pageSize) {
|
|
239
|
+
if (!rawBody || typeof rawBody !== 'object') {
|
|
240
|
+
return { HasMore: false };
|
|
241
|
+
}
|
|
242
|
+
const body = rawBody;
|
|
243
|
+
const cursorRaw = body[DEFAULT_CURSOR_KEY];
|
|
244
|
+
if (cursorRaw == null)
|
|
245
|
+
return { HasMore: false };
|
|
246
|
+
const cursor = typeof cursorRaw === 'string' ? cursorRaw : JSON.stringify(cursorRaw);
|
|
247
|
+
if (cursor.length === 0)
|
|
248
|
+
return { HasMore: false };
|
|
249
|
+
const total = typeof body.Count === 'number' ? body.Count : undefined;
|
|
250
|
+
return { HasMore: true, NextCursor: cursor, TotalRecords: total };
|
|
251
|
+
}
|
|
252
|
+
// ─── FetchChanges (idiosyncratic: subdomain + {tenantId} routing) ───
|
|
253
|
+
//
|
|
254
|
+
// OVERRIDE rationale: Rhythm's per-module subdomain routing + the {tenantId} path param on every
|
|
255
|
+
// endpoint do NOT fit the base's single-base-URL/template-var FetchChanges (which would treat
|
|
256
|
+
// {tenantId} as a parent-record iteration var). We resolve the subdomain + substitute {tenantId},
|
|
257
|
+
// then run the uniform DynamoDB cursor loop ourselves. All Rhythm IOs are top-level flat resources
|
|
258
|
+
// (no genuine parent-iteration), so the loop stays simple. Records are assembled exactly like the
|
|
259
|
+
// base (full-record pass-through + §4 synthetic-PK fallback) via buildExternalRecord.
|
|
260
|
+
async FetchChanges(ctx) {
|
|
261
|
+
const obj = this.GetCachedObject(ctx.CompanyIntegration.IntegrationID, ctx.ObjectName);
|
|
262
|
+
const fields = this.GetCachedFields(obj.ID);
|
|
263
|
+
const auth = (await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser));
|
|
264
|
+
const objCfg = this.ParseObjectConfig(obj);
|
|
265
|
+
this.currentSubdomain = this.ResolveSubdomain(objCfg);
|
|
266
|
+
const pkFieldNames = this.PrimaryKeyFieldNames(fields);
|
|
267
|
+
const itemsKey = objCfg.ResponseItemsKey ?? DEFAULT_ITEMS_KEY;
|
|
268
|
+
const cursorParam = objCfg.PaginationCursorParam ?? DEFAULT_CURSOR_PARAM;
|
|
269
|
+
const readStyle = objCfg.ReadStyle ?? 'list';
|
|
270
|
+
// Rhythm exposes no universal GET list-all. Objects that can only be reached on-demand by id
|
|
271
|
+
// ('by-id' — singletons/statistics) or via a parent FK path ('fk-child') have NO bulk-list
|
|
272
|
+
// endpoint: issuing a GET on their resource base would 405. Surface a structural FetchWarning
|
|
273
|
+
// (the engine reports it in the run artifact) instead of a swallowed error or a doomed request.
|
|
274
|
+
if (readStyle === 'by-id') {
|
|
275
|
+
// Genuinely non-enumerable: Rhythm exposes ONLY GET /{resource}/{tenantId}/{id} for these
|
|
276
|
+
// (no list, no /search, no by-FK path) — there is no endpoint to bulk-list them. Fetched
|
|
277
|
+
// on demand by a known id; surfaced honestly rather than issuing a doomed request.
|
|
278
|
+
return {
|
|
279
|
+
Records: [], HasMore: false,
|
|
280
|
+
Warnings: [{ Code: 'NO_ENUMERATION_ENDPOINT', Message: `"${obj.Name}" has no list/search/by-FK endpoint in the Rhythm spec (get-one by id only) — not bulk-syncable.`, Data: { ReadStyle: readStyle } }],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (readStyle === 'fk-child') {
|
|
284
|
+
// Reached only via a parent-FK path /{resource}/{tenantId}/{parent}/{fkId}. Traverse it in
|
|
285
|
+
// the connector (no framework change): resolve the parent, page its ids, fetch children per
|
|
286
|
+
// parent. See FetchFkChild.
|
|
287
|
+
return await this.FetchFkChild(obj, fields, auth, objCfg, ctx, pkFieldNames, itemsKey);
|
|
288
|
+
}
|
|
289
|
+
// 'search' → Rhythm lists via POST `/{resource}/{tenantId}/search` (no GET list-all); the
|
|
290
|
+
// keyset cursor rides in the request BODY. 'list' (default) → GET the resource base with the
|
|
291
|
+
// cursor as a query param. ReadPath is the spec-valid path for the chosen style.
|
|
292
|
+
const isSearch = readStyle === 'search';
|
|
293
|
+
const readPath = (isSearch && objCfg.ReadPath) ? objCfg.ReadPath : obj.APIPath;
|
|
294
|
+
// Route through GetBaseURL (returns currentSubdomain) rather than reading currentSubdomain
|
|
295
|
+
// directly, so the per-object subdomain stays overridable by the test harness's GetBaseURL
|
|
296
|
+
// hook — identical behaviour at runtime, but mock-routable for the offline tiers.
|
|
297
|
+
const url = this.JoinURL(this.GetBaseURL(ctx.CompanyIntegration, auth), this.SubstituteTenant(readPath, auth.Config.TenantId));
|
|
298
|
+
const records = [];
|
|
299
|
+
const batchLimit = ctx.BatchSize ?? Number.MAX_SAFE_INTEGER;
|
|
300
|
+
let cursor = ctx.CurrentCursor;
|
|
301
|
+
let hasMore = true;
|
|
302
|
+
let pages = 0;
|
|
303
|
+
while (hasMore && records.length < batchLimit && pages < MAX_PAGES_PER_FETCH) {
|
|
304
|
+
pages++;
|
|
305
|
+
let response;
|
|
306
|
+
if (isSearch) {
|
|
307
|
+
// POST /search — the cursor (exclusiveStartKey) is a body field, not a query param.
|
|
308
|
+
const body = cursor ? { [cursorParam]: cursor } : {};
|
|
309
|
+
response = await this.MakeHTTPRequest(auth, url, 'POST', this.BuildHeaders(auth), body);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const pageURL = this.AppendCursor(url, cursorParam, cursor);
|
|
313
|
+
response = await this.MakeHTTPRequest(auth, pageURL, 'GET', this.BuildHeaders(auth));
|
|
314
|
+
}
|
|
315
|
+
if (response.Status === 403) {
|
|
316
|
+
// Object requires additional API permissions/scopes — skip cleanly for this run.
|
|
317
|
+
console.warn(`[${this.IntegrationName}] HTTP 403 for "${obj.Name}" — missing permission/scope; skipping. URL: ${url}`);
|
|
318
|
+
return { Records: records, HasMore: false };
|
|
319
|
+
}
|
|
320
|
+
this.ValidateResponse(response, url);
|
|
321
|
+
const raw = this.NormalizeResponse(response.Body, itemsKey);
|
|
322
|
+
for (const r of raw) {
|
|
323
|
+
const transformed = this.applyTransformPreservingKeys(r, obj, fields);
|
|
324
|
+
records.push(this.buildExternalRecord(transformed, ctx.ObjectName, pkFieldNames));
|
|
325
|
+
}
|
|
326
|
+
const page = this.ExtractPaginationInfo(response.Body, obj.PaginationType, 1, 0, raw.length);
|
|
327
|
+
hasMore = page.HasMore;
|
|
328
|
+
cursor = page.NextCursor;
|
|
329
|
+
}
|
|
330
|
+
return { Records: records, HasMore: hasMore, NextCursor: hasMore ? cursor : undefined };
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* fk-child traversal — CONNECTOR-SIDE, no framework change. For an object whose only list route is
|
|
334
|
+
* a parent-FK path `/{resource}/{tenantId}/{parent}/{fkId}`, resolve the parent object, page a
|
|
335
|
+
* BOUNDED set of parent ids, and fetch children per parent (the engine's private template-var
|
|
336
|
+
* traversal can't be reused, and it treats {tenantId} as a parent — so we do it here). Parent
|
|
337
|
+
* resolution: the FK field's RelatedIntegrationObjectID when present (deployed), else the parent
|
|
338
|
+
* resource segment in ReadPath (works on the seeded/test cache where @lookup refs are scrubbed).
|
|
339
|
+
*/
|
|
340
|
+
async FetchFkChild(obj, fields, auth, objCfg, ctx, pkFieldNames, itemsKey) {
|
|
341
|
+
const MAX_FK_PARENTS = 200; // Goldilocks: bound parent fan-out per call
|
|
342
|
+
const readPath = objCfg.ReadPath;
|
|
343
|
+
if (!readPath)
|
|
344
|
+
return { Records: [], HasMore: false, Warnings: [{ Code: 'FK_NO_READPATH', Message: `"${obj.Name}" is fk-child but has no Configuration.ReadPath` }] };
|
|
345
|
+
const allVars = (readPath.match(/\{(\w+)\}/g) ?? []).map(s => s.slice(1, -1));
|
|
346
|
+
const fkVar = [...allVars].reverse().find(v => v.toLowerCase() !== 'tenantid');
|
|
347
|
+
if (!fkVar)
|
|
348
|
+
return { Records: [], HasMore: false, Warnings: [{ Code: 'FK_NO_VAR', Message: `"${obj.Name}" ReadPath has no parent FK variable: ${readPath}` }] };
|
|
349
|
+
const parent = this.ResolveFkParent(fields, readPath, fkVar);
|
|
350
|
+
if (!parent)
|
|
351
|
+
return { Records: [], HasMore: false, Warnings: [{ Code: 'FK_NO_PARENT', Message: `"${obj.Name}" fk-child: could not resolve a parent object for {${fkVar}} (path ${readPath})` }] };
|
|
352
|
+
const parentIds = await this.FetchParentIds(parent, auth, ctx, MAX_FK_PARENTS);
|
|
353
|
+
if (parentIds.length === 0) {
|
|
354
|
+
return { Records: [], HasMore: false, Warnings: [{ Code: 'FK_NO_PARENTS', Message: `"${obj.Name}" fk-child: parent "${parent.Name}" yielded 0 records to traverse {${fkVar}} over`, Data: { parent: parent.Name } }] };
|
|
355
|
+
}
|
|
356
|
+
const records = [];
|
|
357
|
+
const batchLimit = ctx.BatchSize ?? Number.MAX_SAFE_INTEGER;
|
|
358
|
+
const base = this.GetBaseURL(ctx.CompanyIntegration, auth);
|
|
359
|
+
let parentsTraversed = 0;
|
|
360
|
+
for (const pid of parentIds) {
|
|
361
|
+
if (records.length >= batchLimit)
|
|
362
|
+
break;
|
|
363
|
+
parentsTraversed++;
|
|
364
|
+
const path = this.SubstituteTenant(readPath, auth.Config.TenantId).split(`{${fkVar}}`).join(encodeURIComponent(pid));
|
|
365
|
+
const url = this.JoinURL(base, path);
|
|
366
|
+
const response = await this.MakeHTTPRequest(auth, url, 'GET', this.BuildHeaders(auth));
|
|
367
|
+
if (response.Status === 403 || response.Status === 404)
|
|
368
|
+
continue; // inaccessible/empty parent edge — skip
|
|
369
|
+
this.ValidateResponse(response, url);
|
|
370
|
+
const raw = this.NormalizeResponse(response.Body, itemsKey);
|
|
371
|
+
for (const r of raw)
|
|
372
|
+
records.push(this.buildExternalRecord(this.applyTransformPreservingKeys(r, obj, fields), ctx.ObjectName, pkFieldNames));
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
Records: records, HasMore: false,
|
|
376
|
+
Warnings: records.length ? [] : [{ Code: 'FK_CHILD_EMPTY', Message: `"${obj.Name}" fk-child traversed ${parentsTraversed} "${parent.Name}" parent(s); 0 children`, Data: { parent: parent.Name, parentsTraversed } }],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/** Resolve the parent IntegrationObject for an fk-child's FK variable: FK metadata first, else the parent-resource path segment. */
|
|
380
|
+
ResolveFkParent(fields, readPath, fkVar) {
|
|
381
|
+
const integration = IntegrationEngineBase.Instance.GetIntegrationByName(INTEGRATION_NAME);
|
|
382
|
+
if (!integration)
|
|
383
|
+
return null;
|
|
384
|
+
const siblings = IntegrationEngineBase.Instance.GetActiveIntegrationObjects(integration.ID);
|
|
385
|
+
const fkField = fields.find(f => f.Name?.toLowerCase() === fkVar.toLowerCase() && f.RelatedIntegrationObjectID);
|
|
386
|
+
if (fkField?.RelatedIntegrationObjectID) {
|
|
387
|
+
const byId = siblings.find(s => s.ID === fkField.RelatedIntegrationObjectID);
|
|
388
|
+
if (byId)
|
|
389
|
+
return byId;
|
|
390
|
+
}
|
|
391
|
+
const segs = readPath.split('/').filter(Boolean);
|
|
392
|
+
const idx = segs.indexOf(`{${fkVar}}`);
|
|
393
|
+
const parentSeg = idx > 0 ? segs[idx - 1] : null;
|
|
394
|
+
if (parentSeg) {
|
|
395
|
+
const norm = (s) => s.toLowerCase().replace(/s$/, '');
|
|
396
|
+
const byPath = siblings.find(s => norm((s.APIPath ?? '').split('/').filter(Boolean)[0] ?? '') === norm(parentSeg));
|
|
397
|
+
if (byPath)
|
|
398
|
+
return byPath;
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
/** Fetch a bounded page of a parent object's PK ids (its own list/search read) for fk-child traversal. */
|
|
403
|
+
async FetchParentIds(parent, auth, ctx, max) {
|
|
404
|
+
const cfg = this.ParseObjectConfig(parent);
|
|
405
|
+
const pStyle = cfg.ReadStyle ?? 'list';
|
|
406
|
+
if (pStyle === 'fk-child' || pStyle === 'by-id')
|
|
407
|
+
return []; // don't recurse into a non-bulk parent
|
|
408
|
+
const pk = this.PrimaryKeyFieldNames(this.GetCachedFields(parent.ID))[0] ?? 'id';
|
|
409
|
+
const prevSub = this.currentSubdomain;
|
|
410
|
+
try {
|
|
411
|
+
this.currentSubdomain = this.ResolveSubdomain(cfg);
|
|
412
|
+
const itemsKey = cfg.ResponseItemsKey ?? DEFAULT_ITEMS_KEY;
|
|
413
|
+
const base = this.GetBaseURL(ctx.CompanyIntegration, auth);
|
|
414
|
+
const isSearch = pStyle === 'search';
|
|
415
|
+
const readPath = (isSearch && cfg.ReadPath) ? cfg.ReadPath : parent.APIPath;
|
|
416
|
+
const url = this.JoinURL(base, this.SubstituteTenant(readPath, auth.Config.TenantId));
|
|
417
|
+
const response = isSearch
|
|
418
|
+
? await this.MakeHTTPRequest(auth, url, 'POST', this.BuildHeaders(auth), {})
|
|
419
|
+
: await this.MakeHTTPRequest(auth, url, 'GET', this.BuildHeaders(auth));
|
|
420
|
+
if (response.Status < 200 || response.Status >= 300)
|
|
421
|
+
return [];
|
|
422
|
+
const ids = [];
|
|
423
|
+
for (const r of this.NormalizeResponse(response.Body, itemsKey)) {
|
|
424
|
+
const v = r[pk];
|
|
425
|
+
if (v != null && String(v).length > 0)
|
|
426
|
+
ids.push(String(v));
|
|
427
|
+
if (ids.length >= max)
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
return ids;
|
|
431
|
+
}
|
|
432
|
+
finally {
|
|
433
|
+
this.currentSubdomain = prevSub;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// ─── CRUD: subdomain + {tenantId} routing over the generic base path ──
|
|
437
|
+
//
|
|
438
|
+
// The base's generic CreateRecord/UpdateRecord/DeleteRecord/GetRecord already read the
|
|
439
|
+
// per-operation columns (CreateAPIPath/Method/BodyShape/IDLocation, Update*, Delete*) and route
|
|
440
|
+
// through GetBaseURL + MakeHTTPRequest. We do NOT re-implement the write logic — we only set the
|
|
441
|
+
// current module subdomain before delegating (MakeHTTPRequest then substitutes {tenantId}). This
|
|
442
|
+
// keeps the metadata-driven generic path intact while layering Rhythm's two routing facts on top.
|
|
443
|
+
async CreateRecord(ctx) {
|
|
444
|
+
await this.RouteSubdomain(ctx.CompanyIntegration, ctx.ObjectName);
|
|
445
|
+
return super.CreateRecord(ctx);
|
|
446
|
+
}
|
|
447
|
+
async UpdateRecord(ctx) {
|
|
448
|
+
await this.RouteSubdomain(ctx.CompanyIntegration, ctx.ObjectName);
|
|
449
|
+
return super.UpdateRecord(ctx);
|
|
450
|
+
}
|
|
451
|
+
async DeleteRecord(ctx) {
|
|
452
|
+
await this.RouteSubdomain(ctx.CompanyIntegration, ctx.ObjectName);
|
|
453
|
+
return super.DeleteRecord(ctx);
|
|
454
|
+
}
|
|
455
|
+
async GetRecord(ctx) {
|
|
456
|
+
await this.RouteSubdomain(ctx.CompanyIntegration, ctx.ObjectName);
|
|
457
|
+
return super.GetRecord(ctx);
|
|
458
|
+
}
|
|
459
|
+
/** Resolves + sets the current module subdomain for an object before a generic CRUD delegate. */
|
|
460
|
+
async RouteSubdomain(ci, objectName) {
|
|
461
|
+
const obj = this.GetCachedObject(ci.IntegrationID, objectName);
|
|
462
|
+
this.currentSubdomain = this.ResolveSubdomain(this.ParseObjectConfig(obj));
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* HTTP transport with retry/back-off for 429/503 and substitution of the connection-level
|
|
466
|
+
* {tenantId} into the URL (the single chokepoint every request flows through, so tenant routing
|
|
467
|
+
* is uniform across fetch + CRUD). On a 429 the response carries the Retry-After the engine's AIMD
|
|
468
|
+
* bucket consumes via ExtractRetryAfterMs.
|
|
469
|
+
*/
|
|
470
|
+
async MakeHTTPRequest(auth, url, method, headers, body) {
|
|
471
|
+
const config = auth.Config;
|
|
472
|
+
const maxRetries = config.MaxRetries ?? DEFAULT_MAX_RETRIES;
|
|
473
|
+
const timeoutMs = config.RequestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
474
|
+
// Defensive: substitute any residual {tenantId} the path still carries (fetch pre-substitutes,
|
|
475
|
+
// the generic CRUD path does not — this covers both uniformly).
|
|
476
|
+
const finalURL = this.SubstituteTenant(url, config.TenantId);
|
|
477
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
478
|
+
const fetchOptions = {
|
|
479
|
+
method,
|
|
480
|
+
headers,
|
|
481
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
482
|
+
};
|
|
483
|
+
if (body !== undefined && method !== 'GET') {
|
|
484
|
+
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
485
|
+
}
|
|
486
|
+
let response;
|
|
487
|
+
try {
|
|
488
|
+
response = await fetch(finalURL, fetchOptions);
|
|
489
|
+
}
|
|
490
|
+
catch (err) {
|
|
491
|
+
if (attempt < maxRetries && this.IsTransientNetworkError(err)) {
|
|
492
|
+
await this.Sleep(this.BackoffDelay(attempt));
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
throw err;
|
|
496
|
+
}
|
|
497
|
+
if ((response.status === 429 || response.status === 503) && attempt < maxRetries) {
|
|
498
|
+
console.warn(`[${this.IntegrationName}] HTTP ${response.status} from ${finalURL} — backing off`);
|
|
499
|
+
await this.Sleep(this.RetryAfterMs(response) ?? this.BackoffDelay(attempt));
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
return this.BuildRESTResponse(response);
|
|
503
|
+
}
|
|
504
|
+
throw new Error(`Rhythm request failed after ${maxRetries + 1} attempts: ${finalURL}`);
|
|
505
|
+
}
|
|
506
|
+
// ─── TestConnection ──────────────────────────────────────────────
|
|
507
|
+
/**
|
|
508
|
+
* Tests connectivity by minting an Auth0 M2M token and listing one page of the first cached IO
|
|
509
|
+
* (subdomain + {tenantId} routed). A 2xx confirms the OAuth2 credentials + tenant are valid.
|
|
510
|
+
* Falls back to asserting the token mint alone when no catalog is loaded.
|
|
511
|
+
*/
|
|
512
|
+
async TestConnection(companyIntegration, contextUser) {
|
|
513
|
+
try {
|
|
514
|
+
const auth = (await this.Authenticate(companyIntegration, contextUser));
|
|
515
|
+
const probe = this.FirstProbeObject(companyIntegration.IntegrationID);
|
|
516
|
+
if (!probe) {
|
|
517
|
+
// Token minted but no catalog cached (e.g. credential-free self-check) — the mint
|
|
518
|
+
// succeeding is itself a valid auth confirmation.
|
|
519
|
+
return {
|
|
520
|
+
Success: true,
|
|
521
|
+
Message: 'Rhythm OAuth2 token minted (no object catalog loaded to probe a read).',
|
|
522
|
+
ServerVersion: 'Rhythm REST API v1',
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
const objCfg = this.ParseObjectConfig(probe);
|
|
526
|
+
this.currentSubdomain = this.ResolveSubdomain(objCfg);
|
|
527
|
+
const path = this.SubstituteTenant(probe.APIPath, auth.Config.TenantId);
|
|
528
|
+
const url = this.JoinURL(this.GetBaseURL(companyIntegration, auth), path);
|
|
529
|
+
const response = await this.MakeHTTPRequest(auth, url, 'GET', this.BuildHeaders(auth));
|
|
530
|
+
if (response.Status < 200 || response.Status >= 300) {
|
|
531
|
+
return { Success: false, Message: `Rhythm returned HTTP ${response.Status} from ${url}` };
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
Success: true,
|
|
535
|
+
Message: `Connected to Rhythm (tenant ${auth.Config.TenantId}) via ${probe.Name}`,
|
|
536
|
+
ServerVersion: 'Rhythm REST API v1',
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
catch (err) {
|
|
540
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
541
|
+
return { Success: false, Message: `Connection failed: ${message}` };
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
// ─── Discovery (metadata-driven, no hardcoded catalog) ───────────
|
|
545
|
+
/**
|
|
546
|
+
* Discovers the full object universe from the IntegrationEngineBase cache (the Declared metadata
|
|
547
|
+
* in `.rhythm.integration.json`). Rhythm publishes its catalog credential-free (15 OpenAPI v1
|
|
548
|
+
* specs), so the baseline is Declared metadata — never hardcoded here, never sampled at build.
|
|
549
|
+
*/
|
|
550
|
+
async DiscoverObjects(companyIntegration, contextUser) {
|
|
551
|
+
const seeded = await super.DiscoverObjects(companyIntegration, contextUser);
|
|
552
|
+
if (seeded.length > 0)
|
|
553
|
+
return seeded;
|
|
554
|
+
// No seeded Declared metadata loaded (a credential-free context with no DB-backed engine).
|
|
555
|
+
// The catalog is runtime-seeded Declared metadata (loaded from the DB) — NOT a code constant
|
|
556
|
+
// (per the no-hardcoded-catalog rule), so it cannot be reproduced from source credential-free.
|
|
557
|
+
throw new Error('Rhythm DiscoverObjects requires a loaded object catalog — the 377-object universe is ' +
|
|
558
|
+
'runtime-seeded Declared metadata (from the 15 public OpenAPI specs), not a code constant.');
|
|
559
|
+
}
|
|
560
|
+
/** Discovers fields for an object from the cached Declared metadata. */
|
|
561
|
+
async DiscoverFields(companyIntegration, objectName, contextUser) {
|
|
562
|
+
return super.DiscoverFields(companyIntegration, objectName, contextUser);
|
|
563
|
+
}
|
|
564
|
+
// ─── Config parsing ──────────────────────────────────────────────
|
|
565
|
+
/**
|
|
566
|
+
* Parses the connection config, preferring the attached MJ Credential over the raw Configuration
|
|
567
|
+
* JSON. Credential bytes are resolved at runtime — never at build.
|
|
568
|
+
*/
|
|
569
|
+
async ParseConfig(companyIntegration, contextUser) {
|
|
570
|
+
if (companyIntegration.CredentialID) {
|
|
571
|
+
return this.ParseConfigFromCredential(companyIntegration.CredentialID, contextUser);
|
|
572
|
+
}
|
|
573
|
+
if (companyIntegration.Configuration) {
|
|
574
|
+
return this.ValidateConfig(JSON.parse(companyIntegration.Configuration));
|
|
575
|
+
}
|
|
576
|
+
throw new Error('Rhythm connector requires either CredentialID or Configuration JSON');
|
|
577
|
+
}
|
|
578
|
+
/** Loads the connection config from the MJ: Credentials entity Values JSON. */
|
|
579
|
+
async ParseConfigFromCredential(credentialID, contextUser, provider) {
|
|
580
|
+
const md = provider ?? new Metadata();
|
|
581
|
+
const cred = await md.GetEntityObject('MJ: Credentials', contextUser);
|
|
582
|
+
const loaded = await cred.Load(credentialID);
|
|
583
|
+
if (!loaded || !cred.Values) {
|
|
584
|
+
throw new Error('Rhythm credential could not be loaded or has no Values JSON');
|
|
585
|
+
}
|
|
586
|
+
return this.ValidateConfig(JSON.parse(cred.Values));
|
|
587
|
+
}
|
|
588
|
+
/** Validates the parsed config + applies defaults. Field names are read case-insensitively. */
|
|
589
|
+
ValidateConfig(raw) {
|
|
590
|
+
if (!raw || typeof raw !== 'object') {
|
|
591
|
+
throw new Error('Rhythm configuration is not a valid object');
|
|
592
|
+
}
|
|
593
|
+
const obj = raw;
|
|
594
|
+
const getStr = (...keys) => {
|
|
595
|
+
for (const key of keys) {
|
|
596
|
+
const lower = key.toLowerCase();
|
|
597
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
598
|
+
if (k.toLowerCase() === lower && typeof v === 'string' && v.length > 0)
|
|
599
|
+
return v;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return undefined;
|
|
603
|
+
};
|
|
604
|
+
const getNum = (...keys) => {
|
|
605
|
+
for (const key of keys) {
|
|
606
|
+
const lower = key.toLowerCase();
|
|
607
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
608
|
+
if (k.toLowerCase() === lower && typeof v === 'number')
|
|
609
|
+
return v;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return undefined;
|
|
613
|
+
};
|
|
614
|
+
const accessToken = getStr('accesstoken', 'access_token');
|
|
615
|
+
const clientId = getStr('clientid', 'client_id');
|
|
616
|
+
const clientSecret = getStr('clientsecret', 'client_secret');
|
|
617
|
+
const auth0Domain = getStr('auth0domain', 'auth0_domain', 'authdomain', 'domain');
|
|
618
|
+
// ClientId/Secret + Auth0Domain are required ONLY to mint a token; a pre-provided AccessToken
|
|
619
|
+
// bypasses the mint (e2e harness / token-passthrough), so they're optional in that case.
|
|
620
|
+
if (!accessToken) {
|
|
621
|
+
if (!clientId || !clientSecret) {
|
|
622
|
+
throw new Error('Rhythm OAuth2 configuration missing required field: ClientId / ClientSecret');
|
|
623
|
+
}
|
|
624
|
+
if (!auth0Domain) {
|
|
625
|
+
throw new Error('Rhythm OAuth2 configuration missing required field: Auth0Domain');
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const tenantId = getStr('tenantid', 'tenant_id', 'tenant');
|
|
629
|
+
if (!tenantId) {
|
|
630
|
+
throw new Error('Rhythm configuration missing required field: TenantId');
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
AccessToken: accessToken,
|
|
634
|
+
ClientId: clientId ?? '',
|
|
635
|
+
ClientSecret: clientSecret ?? '',
|
|
636
|
+
Auth0Domain: auth0Domain ?? '',
|
|
637
|
+
Audience: getStr('audience') ?? DEFAULT_AUDIENCE,
|
|
638
|
+
TenantId: tenantId,
|
|
639
|
+
TokenURL: getStr('tokenurl', 'token_url'),
|
|
640
|
+
BaseURL: getStr('baseurl', 'base_url'),
|
|
641
|
+
MaxRetries: getNum('maxretries') ?? DEFAULT_MAX_RETRIES,
|
|
642
|
+
RequestTimeoutMs: getNum('requesttimeoutms') ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
/** Resolves the Auth0 token endpoint: credential TokenURL override, else `https://{domain}/oauth/token`. */
|
|
646
|
+
ResolveTokenURL(config) {
|
|
647
|
+
if (config.TokenURL && config.TokenURL.length > 0)
|
|
648
|
+
return config.TokenURL;
|
|
649
|
+
// Single-origin override: when a base URL fronts every Rhythm host (a proxying gateway, or the
|
|
650
|
+
// credential-free e2e mock), the OAuth2 token endpoint is fronted there too. Real tenants leave
|
|
651
|
+
// BaseURL unset and mint directly from the Auth0 domain.
|
|
652
|
+
if (config.BaseURL && config.BaseURL.length > 0) {
|
|
653
|
+
try {
|
|
654
|
+
return `${new URL(config.BaseURL).origin}${DEFAULT_TOKEN_PATH}`;
|
|
655
|
+
}
|
|
656
|
+
catch { /* fall through to Auth0 */ }
|
|
657
|
+
}
|
|
658
|
+
const host = config.Auth0Domain.replace(/^https?:\/\//, '').replace(/\/+$/, '');
|
|
659
|
+
return `https://${host}${DEFAULT_TOKEN_PATH}`;
|
|
660
|
+
}
|
|
661
|
+
// ─── Object-config + routing helpers ──────────────────────────────
|
|
662
|
+
/** Parses an IO's Configuration JSON (tolerant of absent/malformed). */
|
|
663
|
+
ParseObjectConfig(obj) {
|
|
664
|
+
const raw = obj.Configuration;
|
|
665
|
+
if (!raw || typeof raw !== 'string')
|
|
666
|
+
return {};
|
|
667
|
+
try {
|
|
668
|
+
return JSON.parse(raw);
|
|
669
|
+
}
|
|
670
|
+
catch {
|
|
671
|
+
console.warn(`[${this.IntegrationName}] Invalid Configuration JSON for object "${obj.Name}"`);
|
|
672
|
+
return {};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/** Resolves the module subdomain for an object (required — every Rhythm IO declares one). */
|
|
676
|
+
ResolveSubdomain(cfg) {
|
|
677
|
+
if (cfg.Subdomain && cfg.Subdomain.length > 0)
|
|
678
|
+
return cfg.Subdomain.replace(/\/+$/, '');
|
|
679
|
+
throw new Error('Rhythm object Configuration missing required field: Subdomain');
|
|
680
|
+
}
|
|
681
|
+
/** Substitutes the connection-level {tenantId} into a path/URL. */
|
|
682
|
+
SubstituteTenant(pathOrURL, tenantId) {
|
|
683
|
+
return pathOrURL.replace(/\{tenantId\}/g, encodeURIComponent(tenantId));
|
|
684
|
+
}
|
|
685
|
+
/** Joins a base subdomain and a path into a full URL (single slash). */
|
|
686
|
+
JoinURL(base, path) {
|
|
687
|
+
const b = base.endsWith('/') ? base.slice(0, -1) : base;
|
|
688
|
+
const p = path.startsWith('/') ? path : `/${path}`;
|
|
689
|
+
return `${b}${p}`;
|
|
690
|
+
}
|
|
691
|
+
/** Appends the keyset cursor query param to a URL when a cursor is present. */
|
|
692
|
+
AppendCursor(url, cursorParam, cursor) {
|
|
693
|
+
if (!cursor)
|
|
694
|
+
return url;
|
|
695
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
696
|
+
return `${url}${separator}${encodeURIComponent(cursorParam)}=${encodeURIComponent(cursor)}`;
|
|
697
|
+
}
|
|
698
|
+
/** Returns PK field names (sorted by Sequence), falling back to ['id'] (Rhythm's universal PK). */
|
|
699
|
+
PrimaryKeyFieldNames(fields) {
|
|
700
|
+
const pk = fields.filter(f => f.IsPrimaryKey).sort((a, b) => a.Sequence - b.Sequence);
|
|
701
|
+
return pk.length > 0 ? pk.map(f => f.Name) : ['id'];
|
|
702
|
+
}
|
|
703
|
+
/** First cached IO for the integration, used as the TestConnection read probe. */
|
|
704
|
+
FirstProbeObject(integrationID) {
|
|
705
|
+
const objs = IntegrationEngineBase.Instance.GetActiveIntegrationObjects(integrationID);
|
|
706
|
+
return objs.length > 0 ? objs[0] : undefined;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Builds an ExternalRecord from a raw source record, mirroring the base's §4 identity logic:
|
|
710
|
+
* use the declared PK when every component is present + non-empty, else a deterministic
|
|
711
|
+
* content-hash (so PK-less/partial-key rows stay syncable + dedupable). The FULL source record is
|
|
712
|
+
* preserved in Fields (full-record pass-through); a single-PK whose value is empty is stamped with
|
|
713
|
+
* the synthetic identity so the codegen reload finds the row.
|
|
714
|
+
*/
|
|
715
|
+
buildExternalRecord(raw, objectType, pkFieldNames) {
|
|
716
|
+
const allPkPresent = pkFieldNames.length > 0
|
|
717
|
+
&& pkFieldNames.every(name => raw[name] != null && serializeKeyValue(raw[name]).length > 0);
|
|
718
|
+
const externalID = pkFieldNames.map(name => serializeKeyValue(raw[name])).join('|');
|
|
719
|
+
const resolvedID = allPkPresent ? externalID : computeContentHash(raw);
|
|
720
|
+
let fields = raw;
|
|
721
|
+
if (!allPkPresent && pkFieldNames.length === 1
|
|
722
|
+
&& (raw[pkFieldNames[0]] == null || serializeKeyValue(raw[pkFieldNames[0]]).length === 0)) {
|
|
723
|
+
fields = { ...raw, [pkFieldNames[0]]: resolvedID };
|
|
724
|
+
}
|
|
725
|
+
return { ExternalID: resolvedID, ObjectType: objectType, Fields: fields };
|
|
726
|
+
}
|
|
727
|
+
// ─── HTTP helpers ────────────────────────────────────────────────
|
|
728
|
+
/** Validates an HTTP response and throws (with header context) on non-2xx. */
|
|
729
|
+
ValidateResponse(response, url) {
|
|
730
|
+
if (response.Status < 200 || response.Status >= 300) {
|
|
731
|
+
const preview = typeof response.Body === 'string'
|
|
732
|
+
? response.Body.slice(0, 500)
|
|
733
|
+
: JSON.stringify(response.Body).slice(0, 500);
|
|
734
|
+
const err = new Error(`HTTP ${response.Status} from ${url}: ${preview}`);
|
|
735
|
+
// Attach headers/status so ExtractRetryAfterMs can read Retry-After off a thrown 429.
|
|
736
|
+
err.Headers = response.Headers;
|
|
737
|
+
err.Status = response.Status;
|
|
738
|
+
throw err;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/** Parses a Retry-After header (seconds or HTTP-date) into ms, if present. */
|
|
742
|
+
RetryAfterMs(response) {
|
|
743
|
+
const header = response.headers.get('retry-after');
|
|
744
|
+
if (!header)
|
|
745
|
+
return undefined;
|
|
746
|
+
const asSeconds = Number(header);
|
|
747
|
+
if (!isNaN(asSeconds))
|
|
748
|
+
return Math.max(0, asSeconds * 1_000);
|
|
749
|
+
const asDate = new Date(header).getTime();
|
|
750
|
+
if (!isNaN(asDate))
|
|
751
|
+
return Math.max(0, asDate - Date.now());
|
|
752
|
+
return undefined;
|
|
753
|
+
}
|
|
754
|
+
/** Exponential backoff delay for retry attempts (capped at 30s). */
|
|
755
|
+
BackoffDelay(attempt) {
|
|
756
|
+
return Math.min(2_000 * Math.pow(2, attempt), 30_000);
|
|
757
|
+
}
|
|
758
|
+
/** Checks whether an error is transient (network/timeout). */
|
|
759
|
+
IsTransientNetworkError(err) {
|
|
760
|
+
if (!(err instanceof Error))
|
|
761
|
+
return false;
|
|
762
|
+
const msg = err.message.toLowerCase();
|
|
763
|
+
return msg.includes('timeout') || msg.includes('abort') ||
|
|
764
|
+
msg.includes('econnreset') || msg.includes('econnrefused') ||
|
|
765
|
+
msg.includes('fetch failed');
|
|
766
|
+
}
|
|
767
|
+
/** Builds the normalized RESTResponse from a fetch Response. */
|
|
768
|
+
async BuildRESTResponse(response) {
|
|
769
|
+
const headers = {};
|
|
770
|
+
response.headers.forEach((v, k) => { headers[k.toLowerCase()] = v; });
|
|
771
|
+
const text = await response.text();
|
|
772
|
+
let body = null;
|
|
773
|
+
if (text.length > 0) {
|
|
774
|
+
try {
|
|
775
|
+
body = JSON.parse(text);
|
|
776
|
+
}
|
|
777
|
+
catch {
|
|
778
|
+
body = text;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return { Status: response.status, Body: body, Headers: headers };
|
|
782
|
+
}
|
|
783
|
+
/** Promise-wrapped setTimeout. */
|
|
784
|
+
Sleep(ms) {
|
|
785
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
RhythmConnector = __decorate([
|
|
789
|
+
RegisterClass(BaseIntegrationConnector, '@memberjunction/connector-rhythm-software')
|
|
790
|
+
], RhythmConnector);
|
|
791
|
+
export { RhythmConnector };
|
|
792
|
+
/** Tree-shaking prevention — import and call from the package entry point. */
|
|
793
|
+
export function LoadRhythmConnector() { }
|
|
794
|
+
//# sourceMappingURL=RhythmConnector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RhythmConnector.js","sourceRoot":"","sources":["../src/RhythmConnector.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * from './RhythmConnector.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 './RhythmConnector.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,sBAAsB,CAAC;AAErC;oGACoG;AACpG,MAAM,UAAU,iBAAiB,KAAiD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memberjunction/connector-rhythm-software",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MemberJunction Rhythm connector.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"/dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc && tsc-alias -f",
|
|
13
|
+
"test": "vitest run --passWithNoTests"
|
|
14
|
+
},
|
|
15
|
+
"author": "MemberJunction.com",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@memberjunction/core": ">=5.42.0 <6.0.0",
|
|
19
|
+
"@memberjunction/core-entities": ">=5.42.0 <6.0.0",
|
|
20
|
+
"@memberjunction/global": ">=5.42.0 <6.0.0",
|
|
21
|
+
"@memberjunction/integration-engine": ">=5.42.0 <6.0.0",
|
|
22
|
+
"@memberjunction/integration-engine-base": ">=5.42.0 <6.0.0"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "24.10.11",
|
|
27
|
+
"tsc-alias": "^1.8.16",
|
|
28
|
+
"typescript": "^5.9.3",
|
|
29
|
+
"vitest": "^4.0.18",
|
|
30
|
+
"@memberjunction/core": "^5.42.0",
|
|
31
|
+
"@memberjunction/core-entities": "^5.42.0",
|
|
32
|
+
"@memberjunction/global": "^5.42.0",
|
|
33
|
+
"@memberjunction/integration-engine": "^5.42.0",
|
|
34
|
+
"@memberjunction/integration-engine-base": "^5.42.0"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/MemberJunction/Integrations"
|
|
39
|
+
}
|
|
40
|
+
}
|