@memberjunction/integration-engine 5.37.0 → 5.39.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/ActionMetadataGenerator.d.ts +8 -1
- package/dist/ActionMetadataGenerator.d.ts.map +1 -1
- package/dist/ActionMetadataGenerator.js +22 -3
- package/dist/ActionMetadataGenerator.js.map +1 -1
- package/dist/AdaptiveConcurrency.d.ts +85 -0
- package/dist/AdaptiveConcurrency.d.ts.map +1 -0
- package/dist/AdaptiveConcurrency.js +148 -0
- package/dist/AdaptiveConcurrency.js.map +1 -0
- package/dist/BaseIntegrationConnector.d.ts +127 -3
- package/dist/BaseIntegrationConnector.d.ts.map +1 -1
- package/dist/BaseIntegrationConnector.js +126 -11
- package/dist/BaseIntegrationConnector.js.map +1 -1
- package/dist/BaseRESTIntegrationConnector.d.ts +80 -15
- package/dist/BaseRESTIntegrationConnector.d.ts.map +1 -1
- package/dist/BaseRESTIntegrationConnector.js +314 -64
- package/dist/BaseRESTIntegrationConnector.js.map +1 -1
- package/dist/ConflictRecency.d.ts +24 -0
- package/dist/ConflictRecency.d.ts.map +1 -0
- package/dist/ConflictRecency.js +25 -0
- package/dist/ConflictRecency.js.map +1 -0
- package/dist/ContentHash.d.ts +28 -0
- package/dist/ContentHash.d.ts.map +1 -0
- package/dist/ContentHash.js +54 -0
- package/dist/ContentHash.js.map +1 -0
- package/dist/EnrichSchemaConstraints.d.ts +59 -0
- package/dist/EnrichSchemaConstraints.d.ts.map +1 -0
- package/dist/EnrichSchemaConstraints.js +168 -0
- package/dist/EnrichSchemaConstraints.js.map +1 -0
- package/dist/FieldMappingEngine.d.ts +22 -0
- package/dist/FieldMappingEngine.d.ts.map +1 -1
- package/dist/FieldMappingEngine.js +66 -5
- package/dist/FieldMappingEngine.js.map +1 -1
- package/dist/HashDiff.d.ts +68 -0
- package/dist/HashDiff.d.ts.map +1 -0
- package/dist/HashDiff.js +108 -0
- package/dist/HashDiff.js.map +1 -0
- package/dist/IntegrationActionGenerator.d.ts +93 -0
- package/dist/IntegrationActionGenerator.d.ts.map +1 -0
- package/dist/IntegrationActionGenerator.js +313 -0
- package/dist/IntegrationActionGenerator.js.map +1 -0
- package/dist/IntegrationConnectorCreationPipeline.d.ts +86 -0
- package/dist/IntegrationConnectorCreationPipeline.d.ts.map +1 -0
- package/dist/IntegrationConnectorCreationPipeline.js +226 -0
- package/dist/IntegrationConnectorCreationPipeline.js.map +1 -0
- package/dist/IntegrationEngine.d.ts +199 -1
- package/dist/IntegrationEngine.d.ts.map +1 -1
- package/dist/IntegrationEngine.js +1592 -109
- package/dist/IntegrationEngine.js.map +1 -1
- package/dist/IntegrationSchemaSync.d.ts +82 -0
- package/dist/IntegrationSchemaSync.d.ts.map +1 -1
- package/dist/IntegrationSchemaSync.js +289 -42
- package/dist/IntegrationSchemaSync.js.map +1 -1
- package/dist/MatchEngine.d.ts.map +1 -1
- package/dist/MatchEngine.js +4 -0
- package/dist/MatchEngine.js.map +1 -1
- package/dist/RateLimiter.d.ts +117 -0
- package/dist/RateLimiter.d.ts.map +1 -0
- package/dist/RateLimiter.js +159 -0
- package/dist/RateLimiter.js.map +1 -0
- package/dist/SyncLogger.d.ts +106 -0
- package/dist/SyncLogger.d.ts.map +1 -0
- package/dist/SyncLogger.js +176 -0
- package/dist/SyncLogger.js.map +1 -0
- package/dist/WatermarkService.d.ts +36 -1
- package/dist/WatermarkService.d.ts.map +1 -1
- package/dist/WatermarkService.js +113 -3
- package/dist/WatermarkService.js.map +1 -1
- package/dist/index.d.ts +23 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +52 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -6
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { Metadata } from '@memberjunction/core';
|
|
2
|
+
import { IntegrationProgressEmitter, } from '@memberjunction/integration-progress-artifacts';
|
|
3
|
+
import { SoftPKClassifier, } from '@memberjunction/integration-pk-classifier';
|
|
4
|
+
import { IntegrationEngineBase } from '@memberjunction/integration-engine-base';
|
|
5
|
+
import { IntegrationSchemaSync } from './IntegrationSchemaSync.js';
|
|
6
|
+
/**
|
|
7
|
+
* The unified pipeline that drives connector creation / refresh end-to-end:
|
|
8
|
+
*
|
|
9
|
+
* 1. ConnectionTest stage — verifies credentials before any heavy work
|
|
10
|
+
* 2. Introspect stage — parallel describe via the connector
|
|
11
|
+
* 3. Persist stage — bounded-concurrency upsert with overlay precedence
|
|
12
|
+
* 4. PKClassify stage — soft PK classifier on objects still missing a PK
|
|
13
|
+
*
|
|
14
|
+
* Every stage emits structured events through IntegrationProgressEmitter so the
|
|
15
|
+
* stream is identical regardless of vendor. Stages also emit checkpoint events
|
|
16
|
+
* carrying enough resumableState that the orchestrator can pick up from a kill
|
|
17
|
+
* or container restart without re-running prior stages.
|
|
18
|
+
*
|
|
19
|
+
* The pipeline does NOT generate MJ entities itself — that gate (D7) lives in
|
|
20
|
+
* the CodeGen metadata layer: rows without a PK are simply not promoted to
|
|
21
|
+
* `__mj.Entity`. The pipeline emits `entity.skipped-no-pk` events for visibility.
|
|
22
|
+
*/
|
|
23
|
+
export class IntegrationConnectorCreationPipeline {
|
|
24
|
+
async Run(opts) {
|
|
25
|
+
const runID = opts.RunID ?? IntegrationProgressEmitter.newRunID('connector');
|
|
26
|
+
const manifest = {
|
|
27
|
+
runID,
|
|
28
|
+
runKind: 'ConnectorCreation',
|
|
29
|
+
integrationID: opts.CompanyIntegration.IntegrationID,
|
|
30
|
+
companyIntegrationID: opts.CompanyIntegration.ID,
|
|
31
|
+
triggerType: opts.TriggerType ?? 'Pipeline',
|
|
32
|
+
startedAt: new Date().toISOString(),
|
|
33
|
+
expectedStages: ['ConnectionTest', 'Introspect', 'Persist', 'PKClassify'],
|
|
34
|
+
context: {
|
|
35
|
+
connectorClass: opts.Connector.constructor.name,
|
|
36
|
+
integrationName: opts.CompanyIntegration.Integration ?? null,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
const emitter = new IntegrationProgressEmitter(manifest, {
|
|
40
|
+
rootDir: opts.ArtifactRootDir,
|
|
41
|
+
consoleMirror: opts.ConsoleMirror,
|
|
42
|
+
});
|
|
43
|
+
emitter.runStart(`Connector creation pipeline started for ${opts.CompanyIntegration.Integration ?? '(integration)'} run=${runID}`);
|
|
44
|
+
try {
|
|
45
|
+
await this.StageConnectionTest(emitter, opts);
|
|
46
|
+
const sourceSchema = await this.StageIntrospect(emitter, opts);
|
|
47
|
+
const persistResult = await this.StagePersist(emitter, opts, sourceSchema);
|
|
48
|
+
const { verdicts, unresolved } = await this.StagePKClassify(emitter, opts);
|
|
49
|
+
emitter.stageComplete('Pipeline', {
|
|
50
|
+
processed: persistResult.ObjectsCreated + persistResult.ObjectsUpdated,
|
|
51
|
+
succeeded: verdicts.filter(v => v.Confident).length,
|
|
52
|
+
skipped: unresolved.length,
|
|
53
|
+
});
|
|
54
|
+
await emitter.complete(`Pipeline complete. ${persistResult.ObjectsCreated} objects created, ${persistResult.ObjectsUpdated} updated, ${unresolved.length} unresolved PKs.`);
|
|
55
|
+
return {
|
|
56
|
+
RunID: runID,
|
|
57
|
+
Success: true,
|
|
58
|
+
PersistResult: persistResult,
|
|
59
|
+
PKVerdicts: verdicts,
|
|
60
|
+
UnresolvedObjects: unresolved,
|
|
61
|
+
Manifest: manifest,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
66
|
+
await emitter.fail(`Pipeline failed: ${msg}`);
|
|
67
|
+
return {
|
|
68
|
+
RunID: runID,
|
|
69
|
+
Success: false,
|
|
70
|
+
PKVerdicts: [],
|
|
71
|
+
UnresolvedObjects: [],
|
|
72
|
+
Manifest: manifest,
|
|
73
|
+
FailureMessage: msg,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await emitter.flush();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// ── Stage 1: connection ──────────────────────────────────────────────
|
|
81
|
+
async StageConnectionTest(emitter, opts) {
|
|
82
|
+
emitter.stageStart('ConnectionTest', 'Validating credentials before heavy work');
|
|
83
|
+
const startMs = Date.now();
|
|
84
|
+
const result = await opts.Connector.TestConnection(opts.CompanyIntegration, opts.ContextUser);
|
|
85
|
+
if (!result.Success) {
|
|
86
|
+
emitter.stageError('ConnectionTest', result.Message ?? 'Connection failed', { code: 'connection-failed' });
|
|
87
|
+
throw new Error(`ConnectionTest failed: ${result.Message ?? 'unknown reason'}`);
|
|
88
|
+
}
|
|
89
|
+
emitter.stageComplete('ConnectionTest', { processed: 1, succeeded: 1 });
|
|
90
|
+
emitter.checkpoint('ConnectionTest', { completedAt: new Date().toISOString(), durationMs: Date.now() - startMs });
|
|
91
|
+
}
|
|
92
|
+
// ── Stage 2: introspect ──────────────────────────────────────────────
|
|
93
|
+
async StageIntrospect(emitter, opts) {
|
|
94
|
+
emitter.stageStart('Introspect', 'Discovering objects and fields via connector');
|
|
95
|
+
const startMs = Date.now();
|
|
96
|
+
try {
|
|
97
|
+
const schema = await opts.Connector.IntrospectSchema(opts.CompanyIntegration, opts.ContextUser, opts.IntrospectOptions);
|
|
98
|
+
const fieldCount = schema.Objects.reduce((acc, o) => acc + o.Fields.length, 0);
|
|
99
|
+
emitter.stageComplete('Introspect', {
|
|
100
|
+
processed: schema.Objects.length,
|
|
101
|
+
succeeded: schema.Objects.length,
|
|
102
|
+
totalKnown: schema.Objects.length,
|
|
103
|
+
});
|
|
104
|
+
emitter.checkpoint('Introspect', {
|
|
105
|
+
objectsDiscovered: schema.Objects.length,
|
|
106
|
+
fieldsDiscovered: fieldCount,
|
|
107
|
+
durationMs: Date.now() - startMs,
|
|
108
|
+
});
|
|
109
|
+
return schema;
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
113
|
+
emitter.stageError('Introspect', msg, { code: 'introspect-failed' });
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// ── Stage 3: persist ─────────────────────────────────────────────────
|
|
118
|
+
async StagePersist(emitter, opts, sourceSchema) {
|
|
119
|
+
emitter.stageStart('Persist', 'Upserting IntegrationObject/Field rows with overlay precedence');
|
|
120
|
+
const startMs = Date.now();
|
|
121
|
+
const persistResult = await IntegrationSchemaSync.PersistDiscoveredSchema({
|
|
122
|
+
IntegrationID: opts.CompanyIntegration.IntegrationID,
|
|
123
|
+
SourceSchema: sourceSchema,
|
|
124
|
+
ContextUser: opts.ContextUser,
|
|
125
|
+
Provider: opts.Provider,
|
|
126
|
+
UseTransactionGroup: true,
|
|
127
|
+
});
|
|
128
|
+
// Emit per-object + per-field structural-transparency events so the UI/audit
|
|
129
|
+
// trail captures WHO won each attribute.
|
|
130
|
+
for (const objLog of persistResult.ObjectMergeLog) {
|
|
131
|
+
emitter.objectAdded(objLog.ObjectName, objLog.EffectiveSource);
|
|
132
|
+
}
|
|
133
|
+
for (const fieldLog of persistResult.FieldMergeLog) {
|
|
134
|
+
emitter.fieldAdded(fieldLog.ObjectName, fieldLog.FieldName, fieldLog.EffectiveSource);
|
|
135
|
+
}
|
|
136
|
+
emitter.stageComplete('Persist', {
|
|
137
|
+
processed: persistResult.ObjectsCreated + persistResult.ObjectsUpdated,
|
|
138
|
+
succeeded: persistResult.ObjectsCreated + persistResult.ObjectsUpdated,
|
|
139
|
+
});
|
|
140
|
+
emitter.checkpoint('Persist', {
|
|
141
|
+
objectsCreated: persistResult.ObjectsCreated,
|
|
142
|
+
objectsUpdated: persistResult.ObjectsUpdated,
|
|
143
|
+
fieldsCreated: persistResult.FieldsCreated,
|
|
144
|
+
fieldsUpdated: persistResult.FieldsUpdated,
|
|
145
|
+
durationMs: Date.now() - startMs,
|
|
146
|
+
});
|
|
147
|
+
return persistResult;
|
|
148
|
+
}
|
|
149
|
+
// ── Stage 4: PK classification ───────────────────────────────────────
|
|
150
|
+
async StagePKClassify(emitter, opts) {
|
|
151
|
+
emitter.stageStart('PKClassify', 'Soft PK classifier for objects still missing a PK');
|
|
152
|
+
const md = opts.Provider ?? Metadata.Provider;
|
|
153
|
+
const engine = IntegrationEngineBase.Instance;
|
|
154
|
+
// Refresh from DB so we see what Persist just wrote
|
|
155
|
+
await engine.Config(true, opts.ContextUser, md);
|
|
156
|
+
const objects = engine.GetIntegrationObjectsByIntegrationID(opts.CompanyIntegration.IntegrationID);
|
|
157
|
+
const classifier = new SoftPKClassifier();
|
|
158
|
+
const verdicts = [];
|
|
159
|
+
const unresolved = [];
|
|
160
|
+
for (const obj of objects) {
|
|
161
|
+
const fields = engine.GetIntegrationObjectFields(obj.ID);
|
|
162
|
+
const hasPK = fields.some(f => f.IsPrimaryKey);
|
|
163
|
+
if (hasPK) {
|
|
164
|
+
emitter.entityGenerated(obj.Name, obj.Name);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
emitter.pkClassifierInvoked(obj.Name);
|
|
168
|
+
const verdict = await classifier.Classify({
|
|
169
|
+
object: obj,
|
|
170
|
+
fields,
|
|
171
|
+
universalConvention: opts.UniversalPKConvention,
|
|
172
|
+
sampleRows: opts.SampleRowsByObject?.[obj.Name],
|
|
173
|
+
llmInference: opts.LLMInference,
|
|
174
|
+
});
|
|
175
|
+
emitter.pkClassifierResult(obj.Name, {
|
|
176
|
+
Confident: verdict.Confident,
|
|
177
|
+
Nominee: verdict.Nominee,
|
|
178
|
+
Confidence: verdict.Confidence,
|
|
179
|
+
Strategy: verdict.Strategy,
|
|
180
|
+
Reason: verdict.Reason,
|
|
181
|
+
});
|
|
182
|
+
verdicts.push({
|
|
183
|
+
ObjectName: obj.Name,
|
|
184
|
+
Confident: verdict.Confident,
|
|
185
|
+
Nominee: verdict.Nominee,
|
|
186
|
+
Confidence: verdict.Confidence,
|
|
187
|
+
Strategy: verdict.Strategy,
|
|
188
|
+
Reason: verdict.Reason,
|
|
189
|
+
});
|
|
190
|
+
if (verdict.Confident && verdict.Nominee) {
|
|
191
|
+
const winning = fields.find(f => f.Name === verdict.Nominee);
|
|
192
|
+
if (winning) {
|
|
193
|
+
winning.IsPrimaryKey = true;
|
|
194
|
+
const saved = await winning.Save();
|
|
195
|
+
if (!saved) {
|
|
196
|
+
emitter.stageError('PKClassify', `Failed to persist PK on ${obj.Name}.${verdict.Nominee}: ${winning.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
197
|
+
unresolved.push(obj.Name);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
emitter.entityGenerated(obj.Name, obj.Name);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// Classifier nominated a field that's not in our cache — refuse silently and skip.
|
|
204
|
+
unresolved.push(obj.Name);
|
|
205
|
+
emitter.entitySkippedNoPK(obj.Name);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
unresolved.push(obj.Name);
|
|
210
|
+
emitter.entitySkippedNoPK(obj.Name);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
emitter.stageComplete('PKClassify', {
|
|
214
|
+
processed: verdicts.length,
|
|
215
|
+
succeeded: verdicts.filter(v => v.Confident).length,
|
|
216
|
+
skipped: unresolved.length,
|
|
217
|
+
});
|
|
218
|
+
emitter.checkpoint('PKClassify', {
|
|
219
|
+
totalObjects: objects.length,
|
|
220
|
+
verdicts: verdicts.length,
|
|
221
|
+
unresolved: unresolved.length,
|
|
222
|
+
});
|
|
223
|
+
return { verdicts, unresolved };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=IntegrationConnectorCreationPipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntegrationConnectorCreationPipeline.js","sourceRoot":"","sources":["../src/IntegrationConnectorCreationPipeline.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAMhD,OAAO,EACH,0BAA0B,GAE7B,MAAM,gDAAgD,CAAC;AACxD,OAAO,EACH,gBAAgB,GAEnB,MAAM,2CAA2C,CAAC;AAEnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAA4B,MAAM,4BAA4B,CAAC;AA2D7F;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,oCAAoC;IACtC,KAAK,CAAC,GAAG,CAAC,IAAsC;QACnD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,0BAA0B,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC7E,MAAM,QAAQ,GAA2B;YACrC,KAAK;YACL,OAAO,EAAE,mBAAmB;YAC5B,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,aAAa;YACpD,oBAAoB,EAAE,IAAI,CAAC,kBAAkB,CAAC,EAAE;YAChD,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,UAAU;YAC3C,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,cAAc,EAAE,CAAC,gBAAgB,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,CAAC;YACzE,OAAO,EAAE;gBACL,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI;gBAC/C,eAAe,EAAE,IAAI,CAAC,kBAAkB,CAAC,WAAW,IAAI,IAAI;aAC/D;SACJ,CAAC;QACF,MAAM,OAAO,GAAG,IAAI,0BAA0B,CAAC,QAAQ,EAAE;YACrD,OAAO,EAAE,IAAI,CAAC,eAAe;YAC7B,aAAa,EAAE,IAAI,CAAC,aAAa;SACpC,CAAC,CAAC;QACH,OAAO,CAAC,QAAQ,CAAC,2CAA2C,IAAI,CAAC,kBAAkB,CAAC,WAAW,IAAI,eAAe,QAAQ,KAAK,EAAE,CAAC,CAAC;QAEnI,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC9C,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC/D,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;YAC3E,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAE3E,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE;gBAC9B,SAAS,EAAE,aAAa,CAAC,cAAc,GAAG,aAAa,CAAC,cAAc;gBACtE,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM;gBACnD,OAAO,EAAE,UAAU,CAAC,MAAM;aAC7B,CAAC,CAAC;YACH,MAAM,OAAO,CAAC,QAAQ,CAAC,sBAAsB,aAAa,CAAC,cAAc,qBAAqB,aAAa,CAAC,cAAc,aAAa,UAAU,CAAC,MAAM,kBAAkB,CAAC,CAAC;YAE5K,OAAO;gBACH,KAAK,EAAE,KAAK;gBACZ,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,aAAa;gBAC5B,UAAU,EAAE,QAAQ;gBACpB,iBAAiB,EAAE,UAAU;gBAC7B,QAAQ,EAAE,QAAQ;aACrB,CAAC;QACN,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,MAAM,OAAO,CAAC,IAAI,CAAC,oBAAoB,GAAG,EAAE,CAAC,CAAC;YAC9C,OAAO;gBACH,KAAK,EAAE,KAAK;gBACZ,OAAO,EAAE,KAAK;gBACd,UAAU,EAAE,EAAE;gBACd,iBAAiB,EAAE,EAAE;gBACrB,QAAQ,EAAE,QAAQ;gBAClB,cAAc,EAAE,GAAG;aACtB,CAAC;QACN,CAAC;gBAAS,CAAC;YACP,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC;IACL,CAAC;IAED,wEAAwE;IAEhE,KAAK,CAAC,mBAAmB,CAC7B,OAAmC,EACnC,IAAsC;QAEtC,OAAO,CAAC,UAAU,CAAC,gBAAgB,EAAE,0CAA0C,CAAC,CAAC;QACjF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAC9F,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO,CAAC,UAAU,CAAC,gBAAgB,EAAE,MAAM,CAAC,OAAO,IAAI,mBAAmB,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC,CAAC;YAC3G,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,CAAC,OAAO,IAAI,gBAAgB,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,OAAO,CAAC,aAAa,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;QACxE,OAAO,CAAC,UAAU,CAAC,gBAAgB,EAAE,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACtH,CAAC;IAED,wEAAwE;IAEhE,KAAK,CAAC,eAAe,CACzB,OAAmC,EACnC,IAAsC;QAEtC,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE,8CAA8C,CAAC,CAAC;QACjF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAChD,IAAI,CAAC,kBAAkB,EACvB,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,iBAAiB,CACzB,CAAC;YACF,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAC/E,OAAO,CAAC,aAAa,CAAC,YAAY,EAAE;gBAChC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM;gBAChC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM;gBAChC,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM;aACpC,CAAC,CAAC;YACH,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE;gBAC7B,iBAAiB,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM;gBACxC,gBAAgB,EAAE,UAAU;gBAC5B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;aACnC,CAAC,CAAC;YACH,OAAO,MAAM,CAAC;QAClB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC,CAAC;YACrE,MAAM,GAAG,CAAC;QACd,CAAC;IACL,CAAC;IAED,wEAAwE;IAEhE,KAAK,CAAC,YAAY,CACtB,OAAmC,EACnC,IAAsC,EACtC,YAA+E;QAE/E,OAAO,CAAC,UAAU,CAAC,SAAS,EAAE,gEAAgE,CAAC,CAAC;QAChG,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,aAAa,GAAG,MAAM,qBAAqB,CAAC,uBAAuB,CAAC;YACtE,aAAa,EAAE,IAAI,CAAC,kBAAkB,CAAC,aAAa;YACpD,YAAY,EAAE,YAAY;YAC1B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,mBAAmB,EAAE,IAAI;SAC5B,CAAC,CAAC;QAEH,6EAA6E;QAC7E,yCAAyC;QACzC,KAAK,MAAM,MAAM,IAAI,aAAa,CAAC,cAAc,EAAE,CAAC;YAChD,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC;QACnE,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,aAAa,CAAC,aAAa,EAAE,CAAC;YACjD,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;QAC1F,CAAC;QACD,OAAO,CAAC,aAAa,CAAC,SAAS,EAAE;YAC7B,SAAS,EAAE,aAAa,CAAC,cAAc,GAAG,aAAa,CAAC,cAAc;YACtE,SAAS,EAAE,aAAa,CAAC,cAAc,GAAG,aAAa,CAAC,cAAc;SACzE,CAAC,CAAC;QACH,OAAO,CAAC,UAAU,CAAC,SAAS,EAAE;YAC1B,cAAc,EAAE,aAAa,CAAC,cAAc;YAC5C,cAAc,EAAE,aAAa,CAAC,cAAc;YAC5C,aAAa,EAAE,aAAa,CAAC,aAAa;YAC1C,aAAa,EAAE,aAAa,CAAC,aAAa;YAC1C,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;SACnC,CAAC,CAAC;QACH,OAAO,aAAa,CAAC;IACzB,CAAC;IAED,wEAAwE;IAEhE,KAAK,CAAC,eAAe,CACzB,OAAmC,EACnC,IAAsC;QAKtC,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE,mDAAmD,CAAC,CAAC;QACtF,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;QAC9C,MAAM,MAAM,GAAG,qBAAqB,CAAC,QAAQ,CAAC;QAC9C,oDAAoD;QACpD,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,CAAC,oCAAoC,CAAC,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC;QAEnG,MAAM,UAAU,GAAG,IAAI,gBAAgB,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAkD,EAAE,CAAC;QACnE,MAAM,UAAU,GAAa,EAAE,CAAC;QAEhC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,MAAM,CAAC,0BAA0B,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACzD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;YAC/C,IAAI,KAAK,EAAE,CAAC;gBACR,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC5C,SAAS;YACb,CAAC;YACD,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC;gBACtC,MAAM,EAAE,GAAG;gBACX,MAAM;gBACN,mBAAmB,EAAE,IAAI,CAAC,qBAAqB;gBAC/C,UAAU,EAAE,IAAI,CAAC,kBAAkB,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;gBAC/C,YAAY,EAAE,IAAI,CAAC,YAAY;aAClC,CAAC,CAAC;YACH,OAAO,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,EAAE;gBACjC,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;aACzB,CAAC,CAAC;YACH,QAAQ,CAAC,IAAI,CAAC;gBACV,UAAU,EAAE,GAAG,CAAC,IAAI;gBACpB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;aACzB,CAAC,CAAC;YAEH,IAAI,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACvC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC7D,IAAI,OAAO,EAAE,CAAC;oBACV,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;oBAC5B,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;oBACnC,IAAI,CAAC,KAAK,EAAE,CAAC;wBACT,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE,2BAA2B,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,YAAY,EAAE,eAAe,IAAI,eAAe,EAAE,CAAC,CAAC;wBACxJ,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;wBAC1B,SAAS;oBACb,CAAC;oBACD,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;gBAChD,CAAC;qBAAM,CAAC;oBACJ,mFAAmF;oBACnF,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAC1B,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACxC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACJ,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC1B,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC;QACL,CAAC;QAED,OAAO,CAAC,aAAa,CAAC,YAAY,EAAE;YAChC,SAAS,EAAE,QAAQ,CAAC,MAAM;YAC1B,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM;YACnD,OAAO,EAAE,UAAU,CAAC,MAAM;SAC7B,CAAC,CAAC;QACH,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE;YAC7B,YAAY,EAAE,OAAO,CAAC,MAAM;YAC5B,QAAQ,EAAE,QAAQ,CAAC,MAAM;YACzB,UAAU,EAAE,UAAU,CAAC,MAAM;SAChC,CAAC,CAAC;QAEH,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IACpC,CAAC;CACJ"}
|
|
@@ -82,10 +82,30 @@ export declare class IntegrationEngine extends BaseSingleton<IntegrationEngine>
|
|
|
82
82
|
* Internal sync execution method. Contains the full orchestration logic.
|
|
83
83
|
*/
|
|
84
84
|
private executeSyncInternal;
|
|
85
|
+
/**
|
|
86
|
+
* Builds the durable progress emitter for a sync run. Best-effort: returns
|
|
87
|
+
* undefined (and never throws) if the artifact store can't be initialized, so
|
|
88
|
+
* structured logging can never block a sync.
|
|
89
|
+
*/
|
|
90
|
+
private createSyncProgressEmitter;
|
|
91
|
+
/** Maps the engine SyncTriggerType to the manifest's triggerType vocabulary. */
|
|
92
|
+
private mapTriggerTypeForManifest;
|
|
93
|
+
/** Writes the terminal artifact result + flushes. Best-effort — never throws. */
|
|
94
|
+
private finalizeSyncProgress;
|
|
85
95
|
/**
|
|
86
96
|
* Loads all configuration needed for a sync run.
|
|
87
97
|
*/
|
|
88
98
|
private LoadRunConfiguration;
|
|
99
|
+
/**
|
|
100
|
+
* Periodic full-reconcile cadence (plan §C5: "periodic full reconcile for hard deletes"). When the
|
|
101
|
+
* caller did NOT explicitly request FullSync, an integration can opt into automatic periodic full
|
|
102
|
+
* reconciles via CompanyIntegration.Configuration {"fullSyncEvery": N}: every Nth completed run
|
|
103
|
+
* (and the first) does a full fetch + orphan/delete-detection instead of a watermark-incremental
|
|
104
|
+
* pull, so hard-deletes upstream are reclaimed on a schedule without the caller tracking cadence.
|
|
105
|
+
* Zero cost when unset (no DB read); returns false on N<=1 / unset / any error (incremental — no
|
|
106
|
+
* behavior change).
|
|
107
|
+
*/
|
|
108
|
+
private resolveScheduledFullSync;
|
|
89
109
|
/**
|
|
90
110
|
* Creates a new CompanyIntegrationRun record to track this sync.
|
|
91
111
|
*/
|
|
@@ -94,6 +114,71 @@ export declare class IntegrationEngine extends BaseSingleton<IntegrationEngine>
|
|
|
94
114
|
* Processes all entity maps, aggregating results with progress tracking.
|
|
95
115
|
*/
|
|
96
116
|
private ExecuteEntityMaps;
|
|
117
|
+
/**
|
|
118
|
+
* Groups the run's entity maps into dependency layers (parents before children) using the
|
|
119
|
+
* IntegrationObject FK graph (RelatedIntegrationObjectID). Layer 0 = roots (no FK dependency on
|
|
120
|
+
* another selected object); layer N depends only on layers < N. Maps within a layer are
|
|
121
|
+
* mutually independent and safe to run concurrently. Falls back to a single layer (all maps,
|
|
122
|
+
* original order) if the graph can't be resolved — preserving current behavior. Stable: the
|
|
123
|
+
* original config order is preserved within each layer.
|
|
124
|
+
*/
|
|
125
|
+
private buildEntityMapDependencyLayers;
|
|
126
|
+
/**
|
|
127
|
+
* Builds the selected-object FK dependency graph for a run: each selected IntegrationObject →
|
|
128
|
+
* the set of its SELECTED parent IntegrationObject IDs (via IntegrationObjectField.RelatedIntegrationObjectID).
|
|
129
|
+
* Shared by {@link buildEntityMapDependencyLayers} (parent-before-child ordering) and the
|
|
130
|
+
* second-layer silent-empty tripwire ({@link checkSecondLayerEmpty}). Returns null when the
|
|
131
|
+
* graph can't be resolved (no IOs, or none of the maps resolve to an IO) — callers then fall
|
|
132
|
+
* back to flat/original ordering.
|
|
133
|
+
*/
|
|
134
|
+
private computeSelectedDependencyGraph;
|
|
135
|
+
/**
|
|
136
|
+
* Surfaces the second-layer silent-empty case as a structured warning. An object that HAS FK
|
|
137
|
+
* parents (an association/dependent — a "second-layer" object) but fetched ZERO records is the
|
|
138
|
+
* classic silent fail: a successful run that quietly produced nothing because its parents
|
|
139
|
+
* weren't synced/mapped or the DAG ordered it too early. Rather than let that look identical to
|
|
140
|
+
* "genuinely no data", we emit a SyncWarning (with the parent record counts) so it is visible
|
|
141
|
+
* over GraphQL. Must be called AFTER the map completes; relies on parents (earlier layers)
|
|
142
|
+
* having already recorded their counts in `processedByIoId`.
|
|
143
|
+
*/
|
|
144
|
+
private checkSecondLayerEmpty;
|
|
145
|
+
/** Opt-in sync concurrency from CompanyIntegration.Configuration; default 1 (sequential), clamped [1,16]. */
|
|
146
|
+
private getSyncConcurrency;
|
|
147
|
+
/** Runs `fn` over items with at most `cap` concurrent executions. cap<=1 → strictly sequential. */
|
|
148
|
+
private runBounded;
|
|
149
|
+
/** Per-integration request-spacing chain for the rate limiter (keyed by IntegrationID → last scheduled time). */
|
|
150
|
+
private readonly _rateLimiters;
|
|
151
|
+
/** Minimum ms between outbound requests for this integration (Integration.BatchRequestWaitTime; 0 = disabled). */
|
|
152
|
+
private getRequestSpacingMs;
|
|
153
|
+
/**
|
|
154
|
+
* Rate-limits outbound connector requests per integration, honoring
|
|
155
|
+
* Integration.BatchRequestWaitTime. No-op when the limit is unset/-1 (the default → zero
|
|
156
|
+
* behavior change). Requests are chained per integration so concurrent callers (opt-in
|
|
157
|
+
* parallel sync) are spaced apart too — the safety net that keeps parallelism within the
|
|
158
|
+
* vendor's rate limit.
|
|
159
|
+
*/
|
|
160
|
+
private rateLimit;
|
|
161
|
+
/**
|
|
162
|
+
* Adaptive token-bucket limiter for this company integration (plan.md §7 peak-aware rate
|
|
163
|
+
* limiting), lazily created and configured from the connector's RateLimitPolicy — or, when the
|
|
164
|
+
* connector declares none, from Integration.BatchRequestWaitTime (spacing → tokens/sec). Keyed by
|
|
165
|
+
* CompanyIntegrationID (NOT IntegrationID): source limits are per-credential, so two companies on
|
|
166
|
+
* the same vendor have independent budgets and must not share one bucket. {@link reportRateOutcome}
|
|
167
|
+
* feeds 429s/successes back so the rate auto-tunes (AIMD).
|
|
168
|
+
*/
|
|
169
|
+
private getRateLimiter;
|
|
170
|
+
/**
|
|
171
|
+
* Feed a fetch/write outcome back to the company integration's adaptive limiter. No
|
|
172
|
+
* `throttledErr` = a clean response → ramp the rate up; a rate-limit error → back off (honoring
|
|
173
|
+
* the connector's parsed Retry-After). Other errors are ignored (don't ramp, don't back off).
|
|
174
|
+
*/
|
|
175
|
+
private reportRateOutcome;
|
|
176
|
+
/**
|
|
177
|
+
* Feed a push/CRUD result to the limiter so the WRITE path tunes the rate too (the fetch path
|
|
178
|
+
* already does): a 2xx ramps the rate up, a 429 backs it off (honoring Retry-After). Otherwise
|
|
179
|
+
* leave the rate untouched.
|
|
180
|
+
*/
|
|
181
|
+
private reportRateForCrud;
|
|
97
182
|
/**
|
|
98
183
|
* Processes a single entity map based on SyncDirection:
|
|
99
184
|
* - Pull: fetch from external → map → match → apply to MJ
|
|
@@ -123,6 +208,36 @@ export declare class IntegrationEngine extends BaseSingleton<IntegrationEngine>
|
|
|
123
208
|
private LoadAllMJRecords;
|
|
124
209
|
/** Pushes a single changed MJ record to the external system. */
|
|
125
210
|
private PushSingleRecord;
|
|
211
|
+
/**
|
|
212
|
+
* Surface a SKIPPED push (forbidden write / unsupported op) as a structured SyncWarning so a
|
|
213
|
+
* change that was NOT written to the external system is visible over GraphQL — not just a
|
|
214
|
+
* console.warn + a silent RecordsSkipped bump. Mirrors the read-side silent-fail surfacing
|
|
215
|
+
* (plan.md §8 "things go right, and when they don't it's loud" — applied to the write path).
|
|
216
|
+
*/
|
|
217
|
+
private warnPushSkip;
|
|
218
|
+
/**
|
|
219
|
+
* Pull-first 3-way combine for bidirectional push. Snapshot (last-synced external state) is
|
|
220
|
+
* the common ancestor; MJ-current is "ours"; the re-fetched external record is "theirs".
|
|
221
|
+
* - field only WE changed → push our value
|
|
222
|
+
* - field only THEY changed → leave it (don't push; next pull brings it into MJ)
|
|
223
|
+
* - both changed, same value → converged, skip
|
|
224
|
+
* - both changed, different → true conflict → ConflictResolution policy
|
|
225
|
+
* Returns the attribute subset MJ should actually push. Safe fallbacks: no snapshot / no
|
|
226
|
+
* GetRecord support / fetch failure → push the full attribute set (prior behavior). Never throws.
|
|
227
|
+
*/
|
|
228
|
+
private computePushCombine;
|
|
229
|
+
/** Loose value equality for conflict comparison across JSON/string/number/bool/null shapes. */
|
|
230
|
+
private valuesEqual;
|
|
231
|
+
/**
|
|
232
|
+
* MostRecent conflict resolution: compares the MJ row's last-update time
|
|
233
|
+
* (`__mj_UpdatedAt`) against the external record's `ModifiedAt`. Record-level recency
|
|
234
|
+
* (most sources don't expose per-field timestamps), so the caller computes it once
|
|
235
|
+
* and applies it to every conflicting field. Returns null when a timestamp is
|
|
236
|
+
* missing/unparseable → caller falls back to DestWins (a conflict is never dropped).
|
|
237
|
+
*/
|
|
238
|
+
private resolveMostRecentWinner;
|
|
239
|
+
/** Marks an MJ mirror record in-conflict (Manual resolution) via its standard sync columns. Best-effort. */
|
|
240
|
+
private markConflictOnMJRecord;
|
|
126
241
|
/**
|
|
127
242
|
* Full-sync orphan detection: finds MJ records that have a record map entry
|
|
128
243
|
* but were NOT returned by the external system during this full pull.
|
|
@@ -145,24 +260,100 @@ export declare class IntegrationEngine extends BaseSingleton<IntegrationEngine>
|
|
|
145
260
|
* Loads active field maps for a given entity map.
|
|
146
261
|
*/
|
|
147
262
|
private LoadFieldMaps;
|
|
263
|
+
/** Parses the entity map's Configuration JSON (best-effort) for engine-side feature toggles. */
|
|
264
|
+
private parseEntityMapConfig;
|
|
265
|
+
/** Opt-in partition (Merkle) reconcile flag from the entity map Configuration (GQL-managed). */
|
|
266
|
+
private isPartitionReconcileEnabled;
|
|
267
|
+
/** Partition count for the reconcile (default 256), from the entity map Configuration if set. */
|
|
268
|
+
private partitionReconcileCount;
|
|
269
|
+
/**
|
|
270
|
+
* Partition (Merkle) hash-diff reconcile (§7). Given ALL mapped records of a watermark-less object,
|
|
271
|
+
* bucket them by stable identity, fold each bucket's content hashes into one order-independent rollup,
|
|
272
|
+
* compare against last sync's rollups, and deep-apply (match + upsert) ONLY the partitions whose rollup
|
|
273
|
+
* moved (changed/added). Partitions whose rollup matches are proven identical and skipped entirely
|
|
274
|
+
* (counted as skipped — no match lookup, no write). The new snapshot is persisted ONLY after a clean
|
|
275
|
+
* apply, so a failure re-reconciles from the prior snapshot next time. Deletes are handled by the
|
|
276
|
+
* caller's orphan sweep over the full fetched-id set.
|
|
277
|
+
*
|
|
278
|
+
* MEMORY: this mode buffers the ENTIRE fetched set (`accumulatedMapped`) in RAM until this post-loop
|
|
279
|
+
* apply, rather than streaming batch-by-batch — that's the cost of one complete cross-batch snapshot.
|
|
280
|
+
* It is intended for watermark-less small/medium objects (the kind that re-fetch everything anyway,
|
|
281
|
+
* e.g. a membership roster of tens of thousands). Don't enable it on a multi-million-row object; for
|
|
282
|
+
* those, leave it off and rely on per-record content-hash (which streams).
|
|
283
|
+
*/
|
|
284
|
+
private applyViaPartitionReconcile;
|
|
148
285
|
/**
|
|
149
286
|
* Applies resolved records to MJ, handling each individually for error isolation.
|
|
150
287
|
*/
|
|
151
288
|
private ApplyRecords;
|
|
289
|
+
/**
|
|
290
|
+
* Refreshes __mj_integration_LastReconciledAt = now for a set of records that the content-hash
|
|
291
|
+
* fast path skipped (present + confirmed-unchanged on the source). Issues ONE set-based UPDATE
|
|
292
|
+
* for the whole skip set so the optimization isn't defeated by per-row writes. No-op (and never
|
|
293
|
+
* throws) when the entity lacks the column, has a composite PK, or the UPDATE fails — best-effort,
|
|
294
|
+
* mirroring PrefetchContentHashes. Keeps the column's "last confirmed present" semantics honest so
|
|
295
|
+
* future unseen-since-last-reconcile logic can't misclassify a still-present unchanged record.
|
|
296
|
+
*/
|
|
297
|
+
private TouchLastReconciledAt;
|
|
298
|
+
/**
|
|
299
|
+
* Per-record fallback for a batch whose single transaction failed. Re-applies each
|
|
300
|
+
* record in its OWN transaction so a failure isolates to that record only — good
|
|
301
|
+
* siblings commit, the poison record(s) error out with their REAL per-record cause.
|
|
302
|
+
*
|
|
303
|
+
* Counters and the run-invariant are preserved: every record bumps RecordsProcessed
|
|
304
|
+
* exactly once (so the chunk's processed total still equals chunk.length), good
|
|
305
|
+
* records increment Created/Updated/Skipped via ApplySingleRecord, and each failure
|
|
306
|
+
* increments RecordsErrored and pushes a SyncRecordError classified with the SAME
|
|
307
|
+
* ClassifyError used by the connector path. A `sync.record.error` event is emitted
|
|
308
|
+
* once PER failed record (phase:'save'), carrying that record's real ExternalID /
|
|
309
|
+
* ChangeType — not a single batch-wide error.
|
|
310
|
+
*
|
|
311
|
+
* Begin/Commit/Rollback are always matched per record (no leaked open transaction).
|
|
312
|
+
*/
|
|
313
|
+
private applyRecordsIndividually;
|
|
152
314
|
/**
|
|
153
315
|
* Applies a single record change (Create, Update, Delete, or Skip).
|
|
154
316
|
*/
|
|
155
317
|
private ApplySingleRecord;
|
|
156
318
|
/**
|
|
157
|
-
*
|
|
319
|
+
* Upserts an MJ record BY PRIMARY KEY (and saves a record-map entry).
|
|
320
|
+
*
|
|
321
|
+
* This is the "unmatched" write path — reached when a record matched neither the RecordMap nor a
|
|
322
|
+
* key-field lookup, so the caller assumed it was new. Historically it blindly `NewRecord()`+INSERTed,
|
|
323
|
+
* which COLLIDED with a duplicate-key violation when the dest row already existed but no RecordMap
|
|
324
|
+
* pointed at it. That happens for real: after the entity maps (and their record maps) are deleted while
|
|
325
|
+
* the dest rows persist (a maps delete+re-add, or a partial cleanup), a content-changed record matches
|
|
326
|
+
* neither the (gone) map nor a key field and lands here — over a live PK.
|
|
327
|
+
*
|
|
328
|
+
* Fix: load by the record's mapped PK first; only `NewRecord()` when the row does not already exist,
|
|
329
|
+
* otherwise UPDATE it in place. This makes the create path idempotent on the PK (§7 "idempotent upserts
|
|
330
|
+
* keyed on PK") and re-establishes the missing record map either way.
|
|
331
|
+
*
|
|
332
|
+
* @returns true if an existing row was updated, false if a new row was inserted (so the caller counts correctly).
|
|
158
333
|
*/
|
|
159
334
|
private CreateRecord;
|
|
335
|
+
/**
|
|
336
|
+
* Builds the pipe-joined PK string for a record from its MAPPED fields (case-insensitively), or null
|
|
337
|
+
* when any PK field is absent/blank — i.e. the PK is not carried by the mapped data (server-assigned),
|
|
338
|
+
* so the record is genuinely new and must be inserted. Used by CreateRecord for PK-safe upsert.
|
|
339
|
+
*/
|
|
340
|
+
private extractMappedPrimaryKey;
|
|
160
341
|
/**
|
|
161
342
|
* Updates an existing MJ record with pre-write validation.
|
|
162
343
|
* If the record cannot be loaded (e.g. it was deleted or never fully created),
|
|
163
344
|
* falls back to CreateRecord (upsert behavior).
|
|
164
345
|
*/
|
|
165
346
|
private UpdateRecord;
|
|
347
|
+
/**
|
|
348
|
+
* Batch-loads the stored `__mj_integration_ContentHash` for the Update records in a
|
|
349
|
+
* batch, keyed by matched MJ record ID. Returns undefined (→ no fast-path skip; the
|
|
350
|
+
* dirty-flag path runs) when the optimization doesn't apply or can't be performed:
|
|
351
|
+
* - the target entity has no ContentHash column (predates the feature), or
|
|
352
|
+
* - the entity has a composite PK (we keep the '|'-split out of the fast path), or
|
|
353
|
+
* - nothing in the batch is an Update with a matched ID, or
|
|
354
|
+
* - the read fails (best-effort — a logging/optimization read must never break a sync).
|
|
355
|
+
*/
|
|
356
|
+
private PrefetchContentHashes;
|
|
166
357
|
/**
|
|
167
358
|
* Runs Validate() on an entity if the method exists.
|
|
168
359
|
* Throws a validation error with details if validation fails.
|
|
@@ -183,6 +374,13 @@ export declare class IntegrationEngine extends BaseSingleton<IntegrationEngine>
|
|
|
183
374
|
* Sets fields on a BaseEntity instance from a field value map.
|
|
184
375
|
*/
|
|
185
376
|
private SetEntityFields;
|
|
377
|
+
/**
|
|
378
|
+
* §5/§10 type-driven enforcement: clamp an over-length string to the target column's MaxLength
|
|
379
|
+
* (a character count — SQLMaxLength already converts nvarchar bytes→chars) so a source value
|
|
380
|
+
* wider than the resolved column is truncated instead of failing the whole row on "value too
|
|
381
|
+
* long". Non-strings / values that fit / unlimited columns (MaxLength 0) pass through unchanged.
|
|
382
|
+
*/
|
|
383
|
+
private enforceMaxLength;
|
|
186
384
|
/**
|
|
187
385
|
* Coerce external values to something MJ's SQL provider can bind safely.
|
|
188
386
|
* The external system has already done its best — this is only a safety
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntegrationEngine.d.ts","sourceRoot":"","sources":["../src/IntegrationEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,iBAAiB,EAAqB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAC/H,OAAO,EAAE,aAAa,EAAc,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAChF,OAAO,KAAK,EACR,0BAA0B,EAC1B,mCAAmC,EACnC,kCAAkC,EAIlC,uCAAuC,EACvC,mBAAmB,EACnB,6BAA6B,EAC7B,yBAAyB,EACzB,8BAA8B,EACjC,MAAM,+BAA+B,CAAC;AAKvC,OAAO,KAAK,EACR,UAAU,
|
|
1
|
+
{"version":3,"file":"IntegrationEngine.d.ts","sourceRoot":"","sources":["../src/IntegrationEngine.ts"],"names":[],"mappings":"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"}
|