@memberjunction/integration-connectors 5.29.0 → 5.30.1
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/SageIntacctConnector.d.ts +162 -4
- package/dist/SageIntacctConnector.d.ts.map +1 -1
- package/dist/SageIntacctConnector.js +1139 -66
- package/dist/SageIntacctConnector.js.map +1 -1
- package/dist/SalesforceConnector.d.ts +141 -10
- package/dist/SalesforceConnector.d.ts.map +1 -1
- package/dist/SalesforceConnector.js +649 -48
- package/dist/SalesforceConnector.js.map +1 -1
- package/package.json +6 -5
|
@@ -67,14 +67,141 @@ export declare class SageIntacctConnector extends BaseIntegrationConnector {
|
|
|
67
67
|
*/
|
|
68
68
|
DiscoverObjects(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<ExternalObjectSchema[]>;
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
70
|
+
* Probes a list of candidate object names against SI's per-object
|
|
71
|
+
* `inspect <name>` endpoint to determine which are real API objects vs
|
|
72
|
+
* display-name labels with no API equivalent. Runs in parallel with a
|
|
73
|
+
* bounded worker pool so the 600+ probe round-trips don't serialize.
|
|
74
|
+
*
|
|
75
|
+
* Returns only the names that responded successfully — failures (including
|
|
76
|
+
* "Object type X not found") are silently dropped. This is intentional:
|
|
77
|
+
* the goal is to populate the picker with names the user can actually
|
|
78
|
+
* sync, not surface every SI-side error.
|
|
79
|
+
*/
|
|
80
|
+
private ProbeObjectNames;
|
|
81
|
+
/**
|
|
82
|
+
* Discovers fields on a specific object by combining the `inspect` API
|
|
83
|
+
* call (standard fields) with a `lookup` call (which exposes custom fields
|
|
84
|
+
* via the ISCUSTOM flag). Custom fields are returned with IsCustom=true so
|
|
85
|
+
* the sync engine can flag them in IntegrationObjectField metadata.
|
|
71
86
|
*/
|
|
72
87
|
DiscoverFields(companyIntegration: MJCompanyIntegrationEntity, objectName: string, contextUser: UserInfo): Promise<ExternalFieldSchema[]>;
|
|
88
|
+
private BuildLookupRequest;
|
|
89
|
+
/**
|
|
90
|
+
* Full parser for SI's `lookup <object>` response. Returns the same shape
|
|
91
|
+
* as ParseInspectFieldsResponse so callers can use lookup as a drop-in
|
|
92
|
+
* fallback when inspect comes back empty. SI's lookup endpoint frequently
|
|
93
|
+
* returns a populated `<Fields>` block for objects whose `inspect` returns
|
|
94
|
+
* an empty `<Fields/>` — same metadata, different API surface, same
|
|
95
|
+
* deterministic source-of-truth from SI.
|
|
96
|
+
*
|
|
97
|
+
* Lookup uses uppercase tags (`<NAME>`, `<DATATYPE>`, `<ISCUSTOM>`) where
|
|
98
|
+
* inspect uses mixed case (`<Name>`, `<DataType>`). Tag extraction tries
|
|
99
|
+
* both casings to be robust against per-tenant API quirks.
|
|
100
|
+
*/
|
|
101
|
+
private ParseLookupFieldsResponse;
|
|
73
102
|
/**
|
|
74
|
-
* Fetches records
|
|
75
|
-
*
|
|
103
|
+
* Fetches records from a Sage Intacct object. Uses one of two strategies
|
|
104
|
+
* based on the object's primary key shape:
|
|
105
|
+
*
|
|
106
|
+
* - Numeric PK (RECORDNO): walks RECORDNO in fixed numeric ranges with
|
|
107
|
+
* recursive halving on dense chunks. Bulletproof — no dependence on
|
|
108
|
+
* SI's scan order, pagination signals, or page-size limits.
|
|
109
|
+
*
|
|
110
|
+
* - String PK (CUSTOMERID, VENDORID, etc.): single readByQuery + readMore
|
|
111
|
+
* loop using SI's server-side resultId. Hard fails (instead of silently
|
|
112
|
+
* dropping records) if SI returns a full page without a resultId.
|
|
76
113
|
*/
|
|
77
114
|
FetchChanges(ctx: FetchContext): Promise<FetchBatchResult>;
|
|
115
|
+
/**
|
|
116
|
+
* Range-chunked walk over numeric RECORDNO. Each FetchChanges call
|
|
117
|
+
* processes ONE chunk (one HTTP request to SI). The engine drives the
|
|
118
|
+
* walk by passing the returned NextCursor back on the next call. Cursor
|
|
119
|
+
* encodes (lo, chunkSize, upperBound, maxWatermarkSeen).
|
|
120
|
+
*
|
|
121
|
+
* Termination: walk completes when `lo >= upperBound`. The upperBound is
|
|
122
|
+
* discovered ONCE at the start of the walk via exponential probe of SI's
|
|
123
|
+
* actual MAX RECORDNO — no empty-chunk heuristic, no "what if records
|
|
124
|
+
* exist past N" doubt. The walk covers exactly [0, MAX_RECORDNO + safety
|
|
125
|
+
* margin), every RECORDNO accounted for.
|
|
126
|
+
*
|
|
127
|
+
* Density adaptation: full-page response → halve chunk, re-query same lo.
|
|
128
|
+
* Watermark: tracked in cursor; emitted only on final batch.
|
|
129
|
+
*/
|
|
130
|
+
private FetchChangesByRange;
|
|
131
|
+
/** Sub-range verification: query both halves of [lo, hi) independently
|
|
132
|
+
* and confirm their counts sum to `mainCount`. Returns 'verified' when
|
|
133
|
+
* sums match, 'sub-range-dense' when either half is at-or-above
|
|
134
|
+
* pageSize (signalling that further drill-down is needed). Throws on
|
|
135
|
+
* count mismatch — that's an SI inconsistency we refuse to paper over. */
|
|
136
|
+
private verifyChunk;
|
|
137
|
+
/** Exponential probe of SI for the highest RECORDNO that exists on this
|
|
138
|
+
* object. Returns an upper bound (always strictly greater than the true
|
|
139
|
+
* max). Costs O(log_RANGE_DISCOVERY_GROWTH(maxRecordno)) HTTP calls —
|
|
140
|
+
* typically 1-5 calls for any plausible tenant.
|
|
141
|
+
*
|
|
142
|
+
* Uses pageSize=1 because we only care whether ANY record exists at or
|
|
143
|
+
* above the probe point; the records themselves are discarded. Does NOT
|
|
144
|
+
* apply WHENMODIFIED filter — we want the absolute upper bound on
|
|
145
|
+
* RECORDNO regardless of when it was modified, so that incremental
|
|
146
|
+
* syncs still cover the full RECORDNO space (each chunk inside the
|
|
147
|
+
* walk applies its own WHENMODIFIED filter).
|
|
148
|
+
*
|
|
149
|
+
* Each probe is retried up to RANGE_DISCOVERY_MAX_ATTEMPTS times on
|
|
150
|
+
* network/transport failure before throwing. A retried-but-still-failed
|
|
151
|
+
* probe THROWS rather than silently treating the failure as "no records
|
|
152
|
+
* exist past this point" — fail-stop matches the user requirement that
|
|
153
|
+
* partial sync is worse than no sync. */
|
|
154
|
+
private discoverUpperBound;
|
|
155
|
+
/** Single discovery probe with retry on TRANSPORT errors only. SI-side
|
|
156
|
+
* errors (permission denied, syntax invalid, schema rejected) are
|
|
157
|
+
* deterministic and don't benefit from retry — they're propagated
|
|
158
|
+
* immediately so the caller (sync engine) can record the object as
|
|
159
|
+
* failed and move on. Throws after RANGE_DISCOVERY_MAX_ATTEMPTS
|
|
160
|
+
* exhausted on transport — does NOT silently treat persistent failure
|
|
161
|
+
* as "empty result." */
|
|
162
|
+
private probeRecordnoExistsRetried;
|
|
163
|
+
/** Returns true when the error originated from SI's API response (a
|
|
164
|
+
* thrown structured error from CheckForErrors), false for transport /
|
|
165
|
+
* network / parser failures. SI-side errors are not worth retrying. */
|
|
166
|
+
private isSageIntacctApiError;
|
|
167
|
+
/** Halve the current chunk and re-query its lower half on the next call.
|
|
168
|
+
* Throws if already at RANGE_MIN_CHUNK — that signals the impossible
|
|
169
|
+
* case where SI has more records in 100 RECORDNOs than fit in our
|
|
170
|
+
* configured page size, and we cannot guarantee complete sync. */
|
|
171
|
+
private handleDenseChunk;
|
|
172
|
+
/** Chunk completed safely. Advance to next chunk or terminate the walk
|
|
173
|
+
* when we cross the discovered upperBound. */
|
|
174
|
+
private advanceRangeWalk;
|
|
175
|
+
/** Single-page fetch + readMore loop for objects with string PKs (CUSTOMER,
|
|
176
|
+
* VENDOR, GLACCOUNT, etc.). These are typically small lookup tables; if
|
|
177
|
+
* one ever exceeds a single page, we trust SI's resultId. If SI returns
|
|
178
|
+
* a full page without a resultId, we HARD FAIL rather than silently
|
|
179
|
+
* drop records via PK-cursor pagination. */
|
|
180
|
+
private FetchChangesSinglePull;
|
|
181
|
+
private parseRangeCursor;
|
|
182
|
+
private buildRangeCursor;
|
|
183
|
+
private buildRangeFilter;
|
|
184
|
+
/** Converts a watermark value to SI's expected datetime literal format
|
|
185
|
+
* (`MM/DD/YYYY HH:mm:ss`). The integration engine may pass watermarks
|
|
186
|
+
* in ISO 8601 (e.g. `2026-04-26T01:00:25.170Z`) when no prior SI-format
|
|
187
|
+
* watermark exists for an object — substituting that ISO string straight
|
|
188
|
+
* into a `WHENMODIFIED >= '...'` filter triggers SI's generic
|
|
189
|
+
* DL02000001 "There was an error processing the request" rejection.
|
|
190
|
+
* This converts to SI's accepted format using UTC components for
|
|
191
|
+
* deterministic results regardless of the host server's timezone. */
|
|
192
|
+
private NormalizeSageIntacctTimestamp;
|
|
193
|
+
private toExternalRecord;
|
|
194
|
+
/** Returns the lex-greater of two ISO/SI watermark strings, or undefined
|
|
195
|
+
* if both are absent. SI's WHENMODIFIED format (`MM/DD/YYYY HH:mm:ss`)
|
|
196
|
+
* doesn't sort lex-correctly across years/months in pure string compare,
|
|
197
|
+
* so we parse to Date for comparison. */
|
|
198
|
+
private maxWatermarkString;
|
|
199
|
+
/**
|
|
200
|
+
* Returns true when the IntegrationObject's discovered field set includes
|
|
201
|
+
* WHENMODIFIED. Used to skip the watermark filter for objects that don't
|
|
202
|
+
* support it (most SI platform/lookup objects).
|
|
203
|
+
*/
|
|
204
|
+
private ObjectHasWhenModified;
|
|
78
205
|
CreateRecord(ctx: CreateRecordContext): Promise<CRUDResult>;
|
|
79
206
|
UpdateRecord(ctx: UpdateRecordContext): Promise<CRUDResult>;
|
|
80
207
|
DeleteRecord(ctx: DeleteRecordContext): Promise<CRUDResult>;
|
|
@@ -107,10 +234,29 @@ export declare class SageIntacctConnector extends BaseIntegrationConnector {
|
|
|
107
234
|
*/
|
|
108
235
|
private WrapInSessionRequest;
|
|
109
236
|
private ParseSessionResponse;
|
|
237
|
+
/** Lower-level parser: returns just the raw records + SI's signals
|
|
238
|
+
* without applying any pagination logic. Used by the range-chunked and
|
|
239
|
+
* single-pull paths which decide pagination for themselves. */
|
|
240
|
+
private ParseReadByQueryResponseRaw;
|
|
110
241
|
private ParseReadByQueryResponse;
|
|
111
242
|
private ParseSingleRecord;
|
|
243
|
+
/**
|
|
244
|
+
* Returns the maximum PK value across the records, picking the right
|
|
245
|
+
* comparator (numeric vs lexicographic) based on whether all values
|
|
246
|
+
* look like integers. Returns undefined when records is empty or no
|
|
247
|
+
* record has a usable PK value.
|
|
248
|
+
*/
|
|
249
|
+
private MaxPkValue;
|
|
112
250
|
private ParseInspectObjectsResponse;
|
|
113
251
|
private ParseInspectFieldsResponse;
|
|
252
|
+
/**
|
|
253
|
+
* Pulls a tag value out of a fragment and parses it as a number. Returns
|
|
254
|
+
* null when the tag is absent, empty, or doesn't parse. Used for fields
|
|
255
|
+
* like MaxLength / Precision / Scale that SI reports inconsistently
|
|
256
|
+
* across object families (some return numbers, some omit, some return
|
|
257
|
+
* empty strings).
|
|
258
|
+
*/
|
|
259
|
+
private ParseNumericTagFromFragment;
|
|
114
260
|
/**
|
|
115
261
|
* Sends an XML request to the Sage Intacct API using a session.
|
|
116
262
|
* Handles throttling and retry logic.
|
|
@@ -152,9 +298,21 @@ export declare class SageIntacctConnector extends BaseIntegrationConnector {
|
|
|
152
298
|
*/
|
|
153
299
|
private EscapeXmlValue;
|
|
154
300
|
/**
|
|
155
|
-
* Gets the primary key field name for a Sage Intacct object
|
|
301
|
+
* Gets the primary key field name for a Sage Intacct object, preferring
|
|
302
|
+
* the IntegrationObject metadata (DefaultQueryParams.pk_field or the
|
|
303
|
+
* IntegrationObjectField marked IsPrimaryKey) over the built-in fallback
|
|
304
|
+
* map. Returns 'RECORDNO' when nothing else is available.
|
|
305
|
+
*
|
|
306
|
+
* This is called frequently — the lookup is cheap (pure engine cache) and
|
|
307
|
+
* avoids hardcoded switch statements on object names.
|
|
156
308
|
*/
|
|
157
309
|
private GetPrimaryKeyField;
|
|
310
|
+
/**
|
|
311
|
+
* Resolves the PK for an object from the engine cache.
|
|
312
|
+
* Checks (in order): DefaultQueryParams.pk_field, then the field record
|
|
313
|
+
* with IsPrimaryKey=true. Returns null when nothing is found.
|
|
314
|
+
*/
|
|
315
|
+
private resolvePkFromMetadata;
|
|
158
316
|
/**
|
|
159
317
|
* Formats an API object name into a human-readable label.
|
|
160
318
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SageIntacctConnector.d.ts","sourceRoot":"","sources":["../src/SageIntacctConnector.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,0BAA0B,EAAsB,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"SageIntacctConnector.d.ts","sourceRoot":"","sources":["../src/SageIntacctConnector.ts"],"names":[],"mappings":"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"}
|