@memberjunction/integration-engine 5.49.0 → 5.50.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/IntegrationEngine.d.ts +77 -0
- package/dist/IntegrationEngine.d.ts.map +1 -1
- package/dist/IntegrationEngine.js +247 -51
- package/dist/IntegrationEngine.js.map +1 -1
- package/dist/MatchEngine.d.ts +156 -14
- package/dist/MatchEngine.d.ts.map +1 -1
- package/dist/MatchEngine.js +438 -68
- package/dist/MatchEngine.js.map +1 -1
- package/dist/RecordMapBatch.d.ts +162 -0
- package/dist/RecordMapBatch.d.ts.map +1 -0
- package/dist/RecordMapBatch.js +283 -0
- package/dist/RecordMapBatch.js.map +1 -0
- package/dist/prefetchFilter.d.ts +18 -0
- package/dist/prefetchFilter.d.ts.map +1 -1
- package/dist/prefetchFilter.js +15 -10
- package/dist/prefetchFilter.js.map +1 -1
- package/package.json +7 -7
package/dist/MatchEngine.d.ts
CHANGED
|
@@ -21,6 +21,52 @@ export declare class MatchEngine {
|
|
|
21
21
|
* @returns Updated mapped records with resolved ChangeType and MatchedMJRecordID
|
|
22
22
|
*/
|
|
23
23
|
Resolve(records: MappedRecord[], entityMap: ICompanyIntegrationEntityMap, fieldMaps: ICompanyIntegrationFieldMap[], contextUser: UserInfo, provider?: IMetadataProvider): Promise<MappedRecord[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Issues every lookup this batch needs as ONE batched read, and builds both indexes from it.
|
|
26
|
+
*
|
|
27
|
+
* The legs are independent — the record-map read and each criteria-shape group's read share
|
|
28
|
+
* no data dependency — so they go out together via {@link RunView.RunViews} rather than as
|
|
29
|
+
* separate awaits. Result order matches param order, which is how each leg is attributed back.
|
|
30
|
+
*
|
|
31
|
+
* A `null` index means "the read failed, fall back to the per-record query" and is NOT the
|
|
32
|
+
* same as an empty index, which means "asked, and there is nothing". Conflating them would
|
|
33
|
+
* treat every record as unmapped and turn an incremental sync into a batch of duplicate
|
|
34
|
+
* creates, so the two are kept distinct all the way down.
|
|
35
|
+
*/
|
|
36
|
+
private PrefetchBatchLookups;
|
|
37
|
+
/**
|
|
38
|
+
* The read that resolves an entire batch of external IDs against the record map, or null when
|
|
39
|
+
* the batch carries no usable external ID (nothing to ask, so no query is issued).
|
|
40
|
+
*/
|
|
41
|
+
private buildRecordMapViewParams;
|
|
42
|
+
/**
|
|
43
|
+
* Builds the record-map index from its leg of the batched read.
|
|
44
|
+
*
|
|
45
|
+
* Returns an index containing only the IDs that HAVE a mapping, so a miss is represented by
|
|
46
|
+
* absence — exactly what the per-record lookup needed to distinguish. Returns null if the read
|
|
47
|
+
* failed, which makes callers fall back to their original per-record query rather than
|
|
48
|
+
* silently treating every record as unmapped.
|
|
49
|
+
*
|
|
50
|
+
* The index carries a second, normalized view of the same rows. The `IN (…)` read is evaluated
|
|
51
|
+
* by the database under its own collation, so a row stored as `abc` comes back for a requested
|
|
52
|
+
* `ABC`; pairing the result up in JavaScript with `===` would then miss it. See
|
|
53
|
+
* {@link normalizeExternalID}.
|
|
54
|
+
*/
|
|
55
|
+
private buildRecordMapIndexFromResult;
|
|
56
|
+
/** Builds the exact + normalized views of a record-map read, separating out ambiguous folds. */
|
|
57
|
+
private buildRecordMapIndex;
|
|
58
|
+
/**
|
|
59
|
+
* True when the platform's `=` on string columns is case- and trailing-blank-insensitive, so
|
|
60
|
+
* the JS-side fold in {@link normalizeExternalID} reproduces what the database already did.
|
|
61
|
+
*
|
|
62
|
+
* SQL Server, under the default `SQL_Latin1_General_CP1_CI_AS` collation, is. PostgreSQL is
|
|
63
|
+
* not: `text`/`varchar` equality there is exact on both counts. Folding on PG would make the
|
|
64
|
+
* batch index return a mapping the per-record query would never return — the record would be
|
|
65
|
+
* UPDATED onto someone else's row instead of created, which is the one error direction this
|
|
66
|
+
* whole index is built to avoid. A provider with no dialect (client-side) gets `false`, the
|
|
67
|
+
* conservative answer.
|
|
68
|
+
*/
|
|
69
|
+
private get platformFoldsStringEquality();
|
|
24
70
|
/**
|
|
25
71
|
* Resolves a single record by checking for an existing MJ match.
|
|
26
72
|
*/
|
|
@@ -34,30 +80,126 @@ export declare class MatchEngine {
|
|
|
34
80
|
*/
|
|
35
81
|
private ResolveExistingRecord;
|
|
36
82
|
/**
|
|
37
|
-
* Attempts to find an existing MJ record by
|
|
38
|
-
* to the CompanyIntegrationRecordMap.
|
|
83
|
+
* Attempts to find an existing MJ record by identity (PK) or, failing that, by the
|
|
84
|
+
* configured key fields — then falls back to the CompanyIntegrationRecordMap.
|
|
39
85
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
86
|
+
* The direct lookup is attempted whenever it CAN resolve something:
|
|
87
|
+
* - the mapped record carries a complete PK (single OR composite) — the identity case,
|
|
88
|
+
* and the same key the apply path will address the row by; or
|
|
89
|
+
* - key fields are configured — the fallback case, for mapping external records onto
|
|
90
|
+
* pre-existing MJ rows whose PK the external system does not know.
|
|
91
|
+
*
|
|
92
|
+
* Previously a single-PK entity with no configured key fields skipped this entirely and
|
|
93
|
+
* relied on the record map alone — so a record whose map row was missing (or truncated;
|
|
94
|
+
* see `LoadAllRecordMaps`) re-CREATED a row whose PK already existed. Integration shadow
|
|
95
|
+
* tables are exactly this shape: a single soft PK holding the external ID.
|
|
44
96
|
*/
|
|
45
97
|
private FindExistingRecord;
|
|
46
98
|
/**
|
|
47
|
-
*
|
|
99
|
+
* Answers the identity/key lookup from the batch prefetch when it can, and only issues the
|
|
100
|
+
* per-record query when the prefetch neither found the record nor proved it absent.
|
|
101
|
+
*/
|
|
102
|
+
private LookupByKeyFields;
|
|
103
|
+
/**
|
|
104
|
+
* Returns the entity's primary-key fields (soft PKs included — integration shadow tables
|
|
105
|
+
* carry no physical key, so `PrimaryKeys` is populated from `additionalSchemaInfo` and is
|
|
106
|
+
* still the natural identity).
|
|
107
|
+
*/
|
|
108
|
+
private primaryKeyFieldsFor;
|
|
109
|
+
/** True when the mapped data carries a value for EVERY PK field — i.e. it asserts an identity. */
|
|
110
|
+
private hasCompleteMappedPrimaryKey;
|
|
111
|
+
/**
|
|
112
|
+
* Searches for an existing MJ record, using ONE definition of identity.
|
|
113
|
+
*
|
|
114
|
+
* **The unified rule (tasks.md PR 2 item 3).** The entity's primary key — including a
|
|
115
|
+
* soft PK, which is what integration shadow tables carry — is the single definition of
|
|
116
|
+
* record identity, for matching AND for saving. Configured key fields (`IsKeyField`) are
|
|
117
|
+
* a *fallback lookup* for records whose PK the mapped data does not carry, never a
|
|
118
|
+
* competing identity.
|
|
48
119
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
120
|
+
* Concretely: when the mapped record carries a COMPLETE PK, match on the PK alone. The
|
|
121
|
+
* apply path already addresses the record by its PK (`CreateRecord.extractMappedPrimaryKey`
|
|
122
|
+
* → `InnerLoad`), so matching on anything else is exactly how a record could match one row
|
|
123
|
+
* and then be written to another — or match a row whose PK then collides on save. When the
|
|
124
|
+
* PK is absent or partial (e.g. mapping external records onto pre-existing MJ rows keyed by
|
|
125
|
+
* a server-assigned UUID), fall back to the configured key fields, plus whatever PK parts
|
|
126
|
+
* ARE present to narrow the result.
|
|
52
127
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
128
|
+
* The returned ID is always the PK values in `PrimaryKeys` order, '|'-joined — the same
|
|
129
|
+
* format `ExternalID`/`EntityRecordID` use — so the caller can load the row directly.
|
|
55
130
|
*/
|
|
56
131
|
private FindByKeyFields;
|
|
57
132
|
/**
|
|
58
|
-
* Builds
|
|
133
|
+
* Builds the field/value criteria that identify a record, applying the item-3 identity rule:
|
|
134
|
+
* a COMPLETE primary key matches on the PK alone; otherwise the configured key fields plus
|
|
135
|
+
* whatever PK parts are present. Returns null when nothing can be matched on.
|
|
136
|
+
*
|
|
137
|
+
* Field/value pairs (rather than pre-rendered SQL) so the same criteria can be rendered as a
|
|
138
|
+
* filter clause AND used as a local lookup key when a whole batch is resolved in one query.
|
|
139
|
+
*/
|
|
140
|
+
private BuildMatchCriteria;
|
|
141
|
+
/**
|
|
142
|
+
* Quotes a value as a SQL string literal for a `RunView` `ExtraFilter`.
|
|
143
|
+
*
|
|
144
|
+
* `ExtraFilter` is SQL text, not a parameter list, so literals must be escaped here rather
|
|
145
|
+
* than bound. Where the provider is a database provider this defers to its dialect — the same
|
|
146
|
+
* rule `RecordMapBatch`'s read path uses, so the two cannot disagree about a value.
|
|
147
|
+
* `IMetadataProvider` in general carries no dialect (a client-side provider has none), so the
|
|
148
|
+
* fallback is ANSI quote-doubling, which is what both supported platforms do.
|
|
149
|
+
*
|
|
150
|
+
* Every value quoted here — external IDs and key-field values alike — is compared against an
|
|
151
|
+
* nvarchar column, so it goes through {@link quoteTextLiteral} rather than the dialect's bare
|
|
152
|
+
* `QuoteStringLiteral`: on SQL Server the bare form is a *varchar* literal and silently drops
|
|
153
|
+
* any character outside the database's collation codepage before the comparison.
|
|
154
|
+
*/
|
|
155
|
+
private quoteLiteral;
|
|
156
|
+
/**
|
|
157
|
+
* Renders criteria as a filter clause.
|
|
158
|
+
*
|
|
159
|
+
* ANSI double-quoted identifiers — portable across SQL Server (QUOTED_IDENTIFIER ON, the
|
|
160
|
+
* driver default) and Postgres (exact-case; integration columns are lowercase). Plain
|
|
161
|
+
* identifiers break on a column named for a reserved word (e.g. a soft PK named
|
|
162
|
+
* `open`/`order`); brackets would fix SQL Server but break Postgres, so double-quote.
|
|
163
|
+
*/
|
|
164
|
+
private CriteriaToSQL;
|
|
165
|
+
/**
|
|
166
|
+
* Order-independent local lookup key for a criteria set (and for a row read back for it).
|
|
167
|
+
*
|
|
168
|
+
* `\0` delimits both within and between pairs, because neither a field name nor a value can
|
|
169
|
+
* contain it and no printable delimiter can make that guarantee. With a space, field `A B`
|
|
170
|
+
* = `C` and field `A` = `B C` both render `A B C` — and since this key is what decides which
|
|
171
|
+
* row answers which criteria, a collision attributes a row to the wrong record.
|
|
172
|
+
*/
|
|
173
|
+
private CriteriaKey;
|
|
174
|
+
/**
|
|
175
|
+
* Resolves the identity/key lookup for a WHOLE batch in one query per criteria shape,
|
|
176
|
+
* instead of one query per record (tasks.md PR 2 item 5).
|
|
177
|
+
*
|
|
178
|
+
* Records are grouped by entity + the exact set of fields they match on, so every record in
|
|
179
|
+
* a group produces a structurally identical AND-clause; the group's clauses are OR-ed into a
|
|
180
|
+
* single read and the rows attributed back locally by the same field/value pairs that built
|
|
181
|
+
* the filter. A 500-record batch of an integration shadow table (single soft PK) collapses
|
|
182
|
+
* from 500 reads to 1.
|
|
183
|
+
*
|
|
184
|
+
* Attribution is deliberately conservative. `Matched` holds records we found a row for;
|
|
185
|
+
* `Unmatched` holds records we can PROVE have no row — only populated when the group's read
|
|
186
|
+
* returned nothing at all, so a record whose row exists but whose local key comparison
|
|
187
|
+
* differs from the database's (case-insensitive collation, numeric/date formatting) is left
|
|
188
|
+
* in neither set and falls back to its own query. A false "unmatched" would create a
|
|
189
|
+
* duplicate, so it is never inferred.
|
|
190
|
+
*
|
|
191
|
+
* This half is pure: it builds the reads without issuing them, so every group goes out as one
|
|
192
|
+
* leg of the batch's single {@link RunView.RunViews} call. {@link MatchEngine.buildKeyMatchIndex}
|
|
193
|
+
* attributes the results back.
|
|
194
|
+
*/
|
|
195
|
+
private buildKeyMatchGroups;
|
|
196
|
+
/**
|
|
197
|
+
* Attributes the batched group reads back to their criteria, positionally, in the order
|
|
198
|
+
* {@link MatchEngine.buildKeyMatchGroups} emitted them.
|
|
199
|
+
*
|
|
200
|
+
* Returns null if any group read failed — callers then use the original per-record path.
|
|
59
201
|
*/
|
|
60
|
-
private
|
|
202
|
+
private buildKeyMatchIndex;
|
|
61
203
|
/**
|
|
62
204
|
* Checks the CompanyIntegrationRecordMap for a previous external↔MJ mapping.
|
|
63
205
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MatchEngine.d.ts","sourceRoot":"","sources":["../src/MatchEngine.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"MatchEngine.d.ts","sourceRoot":"","sources":["../src/MatchEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,iBAAiB,EAA6D,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACzJ,OAAO,KAAK,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACnG,OAAO,KAAK,EAAE,YAAY,EAAsB,MAAM,YAAY,CAAC;AA8EnE;;;GAGG;AACH,qBAAa,WAAW;IACpB,gFAAgF;IAChF,OAAO,CAAC,SAAS,CAAC,CAAoB;IAEtC,4FAA4F;IAC5F,SAAS,KAAK,aAAa,IAAI,iBAAiB,CAE/C;IAED;;;;;;;;;OASG;IACU,OAAO,CAChB,OAAO,EAAE,YAAY,EAAE,EACvB,SAAS,EAAE,4BAA4B,EACvC,SAAS,EAAE,2BAA2B,EAAE,EACxC,WAAW,EAAE,QAAQ,EACrB,QAAQ,CAAC,EAAE,iBAAiB,GAC7B,OAAO,CAAC,YAAY,EAAE,CAAC;IAkC1B;;;;;;;;;;;OAWG;YACW,oBAAoB;IA+BlC;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAyBhC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,6BAA6B;IAOrC,gGAAgG;IAChG,OAAO,CAAC,mBAAmB;IA6B3B;;;;;;;;;;OAUG;IACH,OAAO,KAAK,2BAA2B,GAMtC;IAED;;OAEG;YACW,mBAAmB;IAwBjC;;OAEG;YACW,oBAAoB;IAqBlC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAW7B;;;;;;;;;;;;;;OAcG;YACW,kBAAkB;IAsBhC;;;OAGG;YACW,iBAAiB;IAkB/B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAK3B,kGAAkG;IAClG,OAAO,CAAC,2BAA2B;IAMnC;;;;;;;;;;;;;;;;;;;OAmBG;YACW,eAAe;IAyB7B;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAyC1B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,YAAY;IAOpB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IAQnB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,mBAAmB;IAgD3B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IA8B1B;;OAEG;YACW,kBAAkB;CA8CnC"}
|