@memberjunction/integration-engine 5.41.0 → 5.43.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.
Files changed (79) hide show
  1. package/README.md +51 -0
  2. package/dist/BaseIntegrationConnector.d.ts +109 -0
  3. package/dist/BaseIntegrationConnector.d.ts.map +1 -1
  4. package/dist/BaseIntegrationConnector.js +308 -93
  5. package/dist/BaseIntegrationConnector.js.map +1 -1
  6. package/dist/BaseRESTIntegrationConnector.d.ts +36 -0
  7. package/dist/BaseRESTIntegrationConnector.d.ts.map +1 -1
  8. package/dist/BaseRESTIntegrationConnector.js +228 -28
  9. package/dist/BaseRESTIntegrationConnector.js.map +1 -1
  10. package/dist/ContentHash.d.ts +22 -0
  11. package/dist/ContentHash.d.ts.map +1 -1
  12. package/dist/ContentHash.js +35 -0
  13. package/dist/ContentHash.js.map +1 -1
  14. package/dist/CustomColumnPromotion.d.ts +108 -0
  15. package/dist/CustomColumnPromotion.d.ts.map +1 -0
  16. package/dist/CustomColumnPromotion.js +202 -0
  17. package/dist/CustomColumnPromotion.js.map +1 -0
  18. package/dist/CustomOverflow.d.ts +64 -0
  19. package/dist/CustomOverflow.d.ts.map +1 -0
  20. package/dist/CustomOverflow.js +74 -0
  21. package/dist/CustomOverflow.js.map +1 -0
  22. package/dist/FieldMappingEngine.d.ts +16 -90
  23. package/dist/FieldMappingEngine.d.ts.map +1 -1
  24. package/dist/FieldMappingEngine.js +42 -257
  25. package/dist/FieldMappingEngine.js.map +1 -1
  26. package/dist/IntegrationConnectorCreationPipeline.d.ts +33 -0
  27. package/dist/IntegrationConnectorCreationPipeline.d.ts.map +1 -1
  28. package/dist/IntegrationConnectorCreationPipeline.js +157 -0
  29. package/dist/IntegrationConnectorCreationPipeline.js.map +1 -1
  30. package/dist/IntegrationEngine.d.ts +120 -6
  31. package/dist/IntegrationEngine.d.ts.map +1 -1
  32. package/dist/IntegrationEngine.js +890 -168
  33. package/dist/IntegrationEngine.js.map +1 -1
  34. package/dist/IntegrationSchemaSync.d.ts +84 -13
  35. package/dist/IntegrationSchemaSync.d.ts.map +1 -1
  36. package/dist/IntegrationSchemaSync.js +198 -36
  37. package/dist/IntegrationSchemaSync.js.map +1 -1
  38. package/dist/KeySerialization.d.ts +21 -0
  39. package/dist/KeySerialization.d.ts.map +1 -0
  40. package/dist/KeySerialization.js +29 -0
  41. package/dist/KeySerialization.js.map +1 -0
  42. package/dist/MatchEngine.d.ts.map +1 -1
  43. package/dist/MatchEngine.js +12 -4
  44. package/dist/MatchEngine.js.map +1 -1
  45. package/dist/RecordFlatten.d.ts +49 -0
  46. package/dist/RecordFlatten.d.ts.map +1 -0
  47. package/dist/RecordFlatten.js +50 -0
  48. package/dist/RecordFlatten.js.map +1 -0
  49. package/dist/StreamingDiscovery.d.ts +145 -0
  50. package/dist/StreamingDiscovery.d.ts.map +1 -0
  51. package/dist/StreamingDiscovery.js +306 -0
  52. package/dist/StreamingDiscovery.js.map +1 -0
  53. package/dist/SyncLogger.d.ts +1 -1
  54. package/dist/SyncLogger.d.ts.map +1 -1
  55. package/dist/SyncLogger.js +19 -1
  56. package/dist/SyncLogger.js.map +1 -1
  57. package/dist/auth-helpers/BasicAuthHeaderBuilder.d.ts +36 -0
  58. package/dist/auth-helpers/BasicAuthHeaderBuilder.d.ts.map +1 -0
  59. package/dist/auth-helpers/BasicAuthHeaderBuilder.js +38 -0
  60. package/dist/auth-helpers/BasicAuthHeaderBuilder.js.map +1 -0
  61. package/dist/auth-helpers/OAuth1aSigner.d.ts +56 -0
  62. package/dist/auth-helpers/OAuth1aSigner.d.ts.map +1 -0
  63. package/dist/auth-helpers/OAuth1aSigner.js +91 -0
  64. package/dist/auth-helpers/OAuth1aSigner.js.map +1 -0
  65. package/dist/auth-helpers/OAuth2TokenManager.d.ts +103 -0
  66. package/dist/auth-helpers/OAuth2TokenManager.d.ts.map +1 -0
  67. package/dist/auth-helpers/OAuth2TokenManager.js +143 -0
  68. package/dist/auth-helpers/OAuth2TokenManager.js.map +1 -0
  69. package/dist/auth-helpers/index.d.ts +11 -0
  70. package/dist/auth-helpers/index.d.ts.map +1 -0
  71. package/dist/auth-helpers/index.js +8 -0
  72. package/dist/auth-helpers/index.js.map +1 -0
  73. package/dist/index.d.ts +15 -1
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +9 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/types.d.ts +73 -0
  78. package/dist/types.d.ts.map +1 -1
  79. package/package.json +7 -7
@@ -1,25 +1,26 @@
1
- import { MJLruCache } from '@memberjunction/global';
1
+ import { FieldTransformEngine } from '@memberjunction/global';
2
+ import { computeUnmappedFields } from './CustomOverflow.js';
3
+ import { flattenRecord, hasNestedObject } from './RecordFlatten.js';
2
4
  /**
3
- * Engine responsible for applying field-level mappings and transformations
4
- * from external records to MJ entity fields.
5
+ * Engine responsible for applying field-level mappings and transformations from external records to
6
+ * MJ entity fields.
7
+ *
8
+ * The per-value transform pipeline (direct / regex / split / combine / lookup / format / coerce /
9
+ * substring / custom) is owned by the shared {@link FieldTransformEngine} in `@memberjunction/global`
10
+ * and reused here, so integration sync and the rules-based bulk-update processor run the *exact same*
11
+ * transform implementation — one place to fix, one place to extend. This engine keeps only the
12
+ * integration-specific concerns: source flattening, field-map iteration, and unmapped-field overflow
13
+ * capture. See the Field Rules guide in `@memberjunction/global` for the shared engine, and
14
+ * `EntityFieldRules` in `@memberjunction/core` for the metadata-aware entity-update sibling.
5
15
  */
6
16
  export class FieldMappingEngine {
7
17
  constructor() {
8
- /**
9
- * Cache of compiled custom-expression functions, keyed by the expression string, so an
10
- * identical expression is compiled once instead of once per record in a mapping batch.
11
- *
12
- * A failed compile is cached as the `Error` it threw — a malformed expression is therefore
13
- * compiled a single time (then re-thrown from cache on every record) rather than recompiled
14
- * on the per-record error path. Bounded via {@link MJLruCache} because the owning
15
- * `IntegrationEngine` is a process-lifetime singleton; expression cardinality is normally
16
- * tiny (it tracks configured field maps), so the 1000-entry default is comfortably ample.
17
- */
18
- this.customFunctionCache = new MJLruCache();
18
+ /** The shared transform pipeline. Holds its own LRU cache of compiled custom expressions. */
19
+ this.transformEngine = new FieldTransformEngine();
19
20
  }
20
21
  /**
21
- * Applies field mappings to a batch of external records, producing mapped records
22
- * ready for match resolution and persistence.
22
+ * Applies field mappings to a batch of external records, producing mapped records ready for match
23
+ * resolution and persistence.
23
24
  *
24
25
  * @param records - External records to map
25
26
  * @param fieldMaps - Active field map entities defining source→destination mappings
@@ -34,34 +35,46 @@ export class FieldMappingEngine {
34
35
  * Maps a single external record through all active field mappings.
35
36
  */
36
37
  MapSingleRecord(record, fieldMaps, entityName) {
38
+ // Flatten nested source objects to scalar columns (e.g. checkin_question.id →
39
+ // checkin_question_id) BEFORE mapping. The match keys on the mapped PK VALUE
40
+ // (MatchEngine.FindByKeyFields), so an object-valued source field would produce a
41
+ // per-occurrence / per-version key → duplicate rows. Discovery flattens identically, so
42
+ // the field maps reference the flattened scalar names. A record with no nested objects
43
+ // passes through unchanged — every flat-record connector is a no-op here.
44
+ const ext = hasNestedObject(record.Fields)
45
+ ? { ...record, Fields: flattenRecord(record.Fields) }
46
+ : record;
37
47
  const mappedFields = {};
48
+ const mappedSourceNames = new Set();
38
49
  for (const fieldMap of fieldMaps) {
39
- const value = this.ApplyFieldMapping(record, fieldMap);
50
+ mappedSourceNames.add(fieldMap.SourceFieldName);
51
+ const value = this.ApplyFieldMapping(ext, fieldMap);
40
52
  if (value !== undefined) {
41
53
  mappedFields[fieldMap.DestinationFieldName] = value;
42
54
  }
43
55
  }
56
+ // Capture the source keys with no field map (gaps.md §2). Cheap O(columns) diff;
57
+ // the result is empty (and discarded by the writer) in the common all-mapped case,
58
+ // so this adds no measurable cost to a customs-free sync. The engine parks any extras
59
+ // in the __mj_integration_CustomOverflow system column — see {@link CustomOverflow}.
60
+ const unmappedFields = computeUnmappedFields(ext.Fields, mappedSourceNames);
44
61
  return {
45
- ExternalRecord: record,
62
+ ExternalRecord: ext,
46
63
  MJEntityName: entityName,
47
64
  MappedFields: mappedFields,
48
- ChangeType: record.IsDeleted ? 'Delete' : 'Create',
65
+ ChangeType: ext.IsDeleted ? 'Delete' : 'Create',
66
+ UnmappedFields: unmappedFields,
49
67
  };
50
68
  }
51
69
  /**
52
- * Applies a single field mapping, including the full transform pipeline.
53
- * Returns undefined if the field should be skipped (OnError: Skip).
70
+ * Applies a single field mapping, including the full transform pipeline (delegated to the shared
71
+ * {@link FieldTransformEngine}). Returns undefined if the field should be skipped (OnError: Skip).
54
72
  */
55
73
  ApplyFieldMapping(record, fieldMap) {
56
- let value = record.Fields[fieldMap.SourceFieldName];
74
+ const value = record.Fields[fieldMap.SourceFieldName];
57
75
  const pipeline = this.ParseTransformPipeline(fieldMap.TransformPipeline);
58
- for (const step of pipeline) {
59
- const result = this.ExecuteTransformStep(step, value, record.Fields);
60
- if (result.Skipped)
61
- return undefined;
62
- value = result.Value;
63
- }
64
- return value;
76
+ const result = this.transformEngine.ExecutePipeline(value, record.Fields, pipeline);
77
+ return result.Skipped ? undefined : result.Value;
65
78
  }
66
79
  /**
67
80
  * Parses the JSON transform pipeline string into typed TransformStep objects.
@@ -79,233 +92,5 @@ export class FieldMappingEngine {
79
92
  return [];
80
93
  }
81
94
  }
82
- /**
83
- * Executes a single transform step, applying the configured transformation
84
- * and handling errors according to the step's OnError strategy.
85
- */
86
- ExecuteTransformStep(step, value, allFields) {
87
- // Grace by default (§8 "still get as much data"): an unconfigured transform that errors on one
88
- // record's value nulls that FIELD and lets the record sync, rather than failing the record/batch.
89
- // An author can still opt into strict 'Fail' or 'Skip' explicitly per step.
90
- const onError = step.OnError ?? 'Null';
91
- try {
92
- const transformed = this.DispatchTransform(step, value, allFields);
93
- return { Value: transformed, Skipped: false };
94
- }
95
- catch (err) {
96
- return this.HandleTransformError(onError, err);
97
- }
98
- }
99
- /**
100
- * Dispatches to the appropriate transform handler based on step type.
101
- */
102
- DispatchTransform(step, value, allFields) {
103
- switch (step.Type) {
104
- case 'direct':
105
- return this.ApplyDirect(value, step.Config);
106
- case 'regex':
107
- return this.ApplyRegex(value, step.Config);
108
- case 'split':
109
- return this.ApplySplit(value, step.Config);
110
- case 'combine':
111
- return this.ApplyCombine(allFields, step.Config);
112
- case 'lookup':
113
- return this.ApplyLookup(value, step.Config);
114
- case 'format':
115
- return this.ApplyFormat(value, step.Config);
116
- case 'coerce':
117
- return this.ApplyCoerce(value, step.Config);
118
- case 'substring':
119
- return this.ApplySubstring(value, step.Config);
120
- case 'custom':
121
- return this.ApplyCustom(value, allFields, step.Config);
122
- default:
123
- throw new Error(`Unknown transform type: ${step.Type}`);
124
- }
125
- }
126
- /**
127
- * Handles a transform error according to the OnError strategy.
128
- */
129
- HandleTransformError(onError, err) {
130
- switch (onError) {
131
- case 'Skip':
132
- return { Value: undefined, Skipped: true };
133
- case 'Null':
134
- return { Value: null, Skipped: false };
135
- case 'Fail':
136
- throw err;
137
- }
138
- }
139
- /**
140
- * Direct pass-through, applying default value if source is null/undefined.
141
- */
142
- ApplyDirect(value, config) {
143
- if (value == null && config.DefaultValue !== undefined) {
144
- return config.DefaultValue;
145
- }
146
- return value;
147
- }
148
- /**
149
- * Applies a regex replacement to a string value.
150
- */
151
- ApplyRegex(value, config) {
152
- const strValue = String(value ?? '');
153
- const regex = new RegExp(config.Pattern, config.Flags ?? '');
154
- return strValue.replace(regex, config.Replacement);
155
- }
156
- /**
157
- * Splits a string value and extracts a part by index.
158
- */
159
- ApplySplit(value, config) {
160
- const strValue = String(value ?? '');
161
- const parts = strValue.split(config.Delimiter);
162
- return parts[config.Index] ?? null;
163
- }
164
- /**
165
- * Combines multiple source fields with a separator.
166
- */
167
- ApplyCombine(allFields, config) {
168
- const values = config.SourceFields.map(f => String(allFields[f] ?? ''));
169
- return values.join(config.Separator);
170
- }
171
- /**
172
- * Performs a case-insensitive value lookup/mapping.
173
- */
174
- ApplyLookup(value, config) {
175
- const strValue = String(value ?? '');
176
- const lowerKey = strValue.toLowerCase();
177
- const entry = Object.entries(config.Map).find(([key]) => key.toLowerCase() === lowerKey);
178
- return entry ? entry[1] : (config.Default ?? null);
179
- }
180
- /**
181
- * Applies date/number/string formatting.
182
- */
183
- ApplyFormat(value, config) {
184
- if (config.FormatType === 'date') {
185
- const dateVal = value instanceof Date ? value : new Date(String(value));
186
- // Throw a CONTROLLED error on an unparseable date — vs the raw RangeError FormatDate→toISOString()
187
- // would throw — so ExecuteTransformStep's OnError strategy handles it uniformly (default 'Null').
188
- if (isNaN(dateVal.getTime()))
189
- throw new Error(`Cannot format "${String(value)}" as a date`);
190
- return this.FormatDate(dateVal, config.FormatString);
191
- }
192
- if (config.FormatType === 'number') {
193
- const numVal = Number(value);
194
- // Throw on non-numeric — was: emitted the literal string "NaN", which BYPASSED OnError and then
195
- // failed SQL numeric binding (corrupting the write). OnError now handles it (default 'Null').
196
- if (!Number.isFinite(numVal))
197
- throw new Error(`Cannot format "${String(value)}" as a number`);
198
- return numVal.toFixed(Number(config.FormatString) || 0);
199
- }
200
- return String(value ?? '');
201
- }
202
- /**
203
- * Basic date formatting supporting ISO and common format tokens.
204
- */
205
- FormatDate(date, formatString) {
206
- if (formatString.toLowerCase() === 'iso' || formatString === 'ISO8601') {
207
- return date.toISOString();
208
- }
209
- return date.toLocaleDateString('en-US');
210
- }
211
- /**
212
- * Coerces a value to the specified target type.
213
- */
214
- ApplyCoerce(value, config) {
215
- switch (config.TargetType) {
216
- case 'string':
217
- return String(value ?? '');
218
- case 'number':
219
- return this.CoerceToNumber(value);
220
- case 'boolean':
221
- return this.CoerceToBoolean(value);
222
- case 'date': {
223
- // Throw on unparseable — was: returned an Invalid Date that corrupted the write, bypassing
224
- // OnError. OnError handles it now (default 'Null' → field nulled, record syncs).
225
- const d = new Date(String(value));
226
- if (isNaN(d.getTime()))
227
- throw new Error(`Cannot coerce "${String(value)}" to date`);
228
- return d;
229
- }
230
- default:
231
- throw new Error(`Unknown coerce target type: ${config.TargetType}`);
232
- }
233
- }
234
- /**
235
- * Coerces a value to a number, throwing on NaN.
236
- */
237
- CoerceToNumber(value) {
238
- const num = Number(value);
239
- // Throw on unparseable so OnError decides (default 'Null' → field nulled, record syncs; 'Fail' → strict).
240
- // The throw is caught by ExecuteTransformStep and never escapes to fail the record/batch.
241
- if (isNaN(num)) {
242
- throw new Error(`Cannot coerce "${String(value)}" to number`);
243
- }
244
- return num;
245
- }
246
- /**
247
- * Coerces a value to boolean, supporting common truthy/falsy strings.
248
- */
249
- CoerceToBoolean(value) {
250
- if (typeof value === 'boolean')
251
- return value;
252
- if (typeof value === 'number')
253
- return value !== 0;
254
- const strVal = String(value).toLowerCase().trim();
255
- return strVal === 'true' || strVal === '1' || strVal === 'yes';
256
- }
257
- /**
258
- * Extracts a substring from a string value.
259
- */
260
- ApplySubstring(value, config) {
261
- const strValue = String(value ?? '');
262
- if (config.Length !== undefined) {
263
- return strValue.substring(config.Start, config.Start + config.Length);
264
- }
265
- return strValue.substring(config.Start);
266
- }
267
- /**
268
- * Evaluates a custom JavaScript expression.
269
- * The expression has access to `value` (current field value) and `fields` (all record fields).
270
- */
271
- ApplyCustom(value, allFields, config) {
272
- try {
273
- const fn = this.GetCompiledExpression(config.Expression);
274
- return fn(value, allFields);
275
- }
276
- catch (err) {
277
- const message = err instanceof Error ? err.message : String(err);
278
- throw new Error(`Custom transform expression failed: ${message}`);
279
- }
280
- }
281
- /**
282
- * Returns the compiled function for an expression, compiling and caching on first use.
283
- * If the expression failed to compile previously, the cached error is re-thrown — a
284
- * malformed expression is compiled exactly once across an entire batch.
285
- */
286
- GetCompiledExpression(expression) {
287
- let cached = this.customFunctionCache.Get(expression);
288
- if (cached === undefined) {
289
- cached = this.compileExpression(expression);
290
- this.customFunctionCache.Set(expression, cached);
291
- }
292
- if (cached instanceof Error) {
293
- throw cached;
294
- }
295
- return cached;
296
- }
297
- /**
298
- * Compiles a custom expression into a callable, returning the thrown `Error` instead of
299
- * propagating it so the caller can cache compile failures alongside successes.
300
- */
301
- compileExpression(expression) {
302
- try {
303
- // eslint-disable-next-line @typescript-eslint/no-implied-eval
304
- return new Function('value', 'fields', `return (${expression});`);
305
- }
306
- catch (err) {
307
- return err instanceof Error ? err : new Error(String(err));
308
- }
309
- }
310
95
  }
311
96
  //# sourceMappingURL=FieldMappingEngine.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FieldMappingEngine.js","sourceRoot":"","sources":["../src/FieldMappingEngine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FieldMappingEngine.js","sourceRoot":"","sources":["../src/FieldMappingEngine.ts"],"names":[],"mappings":"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"}
@@ -38,6 +38,13 @@ export interface ConnectorCreationPipelineOptions {
38
38
  RunID?: string;
39
39
  /** Trigger reason recorded in the manifest. */
40
40
  TriggerType?: 'Manual' | 'Scheduled' | 'Webhook' | 'Pipeline' | 'Restart';
41
+ /**
42
+ * §7 — when true, this is a COMPREHENSIVE re-discovery: declared/discovered objects (and their
43
+ * fields) ABSENT from this run are deactivated (Status='Disabled', never deleted; reversible on a
44
+ * later rediscovery). Only set on a full-surface refresh — a scoped/partial discovery must leave it
45
+ * false so it never disables what it didn't look at. Threaded to PersistDiscoveredSchema.
46
+ */
47
+ DeactivateAbsent?: boolean;
41
48
  }
42
49
  /** Outcome of a single pipeline invocation. */
43
50
  export interface ConnectorCreationPipelineResult {
@@ -77,7 +84,33 @@ export interface ConnectorCreationPipelineResult {
77
84
  * `__mj.Entity`. The pipeline emits `entity.skipped-no-pk` events for visibility.
78
85
  */
79
86
  export declare class IntegrationConnectorCreationPipeline {
87
+ /** In-flight runs by CompanyIntegrationID — coalesces a concurrent duplicate onto the same promise. */
88
+ private static readonly inFlightRuns;
89
+ /** Just-completed runs by CompanyIntegrationID — coalesces a *sequential* duplicate within the window. */
90
+ private static readonly recentRuns;
91
+ /** Default coalesce window (ms) when the env override is unset/invalid. */
92
+ private static readonly DEFAULT_COALESCE_WINDOW_MS;
93
+ /**
94
+ * How long a just-completed run is reused for a duplicate invocation of the SAME CompanyIntegration.
95
+ * Sized only to absorb the create-time double-fire (the `IsActive` Save-hook runs the pipeline, then
96
+ * `IntegrationCreateConnection` calls it again milliseconds later) — short enough that a genuine,
97
+ * later operator-initiated re-refresh always runs fresh. Override via the
98
+ * `MJ_CONNECTOR_PIPELINE_COALESCE_WINDOW_MS` env var (a positive integer of milliseconds; set 0/unset
99
+ * to use the default). Env-based, matching the RSU env-var convention (RSU_WORK_DIR, etc.).
100
+ */
101
+ private static get COALESCE_WINDOW_MS();
102
+ /**
103
+ * Public entry. De-dups the known create-time double-invocation: the connection's `IsActive`
104
+ * false→true Save fires the entity-server hook (which runs this pipeline WITH LLM PK inference),
105
+ * and the create resolver then calls it again. Both converge here, so we run the pipeline ONCE
106
+ * per CompanyIntegration and hand both callers the same result — no double introspect/persist/
107
+ * classify, no double live API calls, and the resolver still gets a real summary. A legitimate
108
+ * re-refresh later (outside the window) runs fresh.
109
+ */
80
110
  Run(opts: ConnectorCreationPipelineOptions): Promise<ConnectorCreationPipelineResult>;
111
+ /** Drops recent-run entries older than the coalesce window so the map stays bounded. */
112
+ private static pruneRecentRuns;
113
+ private runInternal;
81
114
  private StageConnectionTest;
82
115
  private StageIntrospect;
83
116
  private StagePersist;
@@ -1 +1 @@
1
- {"version":3,"file":"IntegrationConnectorCreationPipeline.d.ts","sourceRoot":"","sources":["../src/IntegrationConnectorCreationPipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,KAAK,EACR,0BAA0B,EAG7B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAEH,KAAK,sBAAsB,EAC9B,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAEH,KAAK,kBAAkB,EAC1B,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC7F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,qDAAqD;AACrD,MAAM,WAAW,gCAAgC;IAC7C,uEAAuE;IACvE,SAAS,EAAE,wBAAwB,CAAC;IACpC,mDAAmD;IACnD,kBAAkB,EAAE,0BAA0B,CAAC;IAC/C,8CAA8C;IAC9C,WAAW,EAAE,QAAQ,CAAC;IACtB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,uBAAuB,CAAC;IAC5C,uEAAuE;IACvE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,gFAAgF;IAChF,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,WAAW,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;CAC7E;AAED,+CAA+C;AAC/C,MAAM,WAAW,+BAA+B;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,UAAU,EAAE,KAAK,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,+EAA+E;IAC/E,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,EAAE,sBAAsB,CAAC;IACjC,0CAA0C;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,oCAAoC;IAChC,GAAG,CAAC,IAAI,EAAE,gCAAgC,GAAG,OAAO,CAAC,+BAA+B,CAAC;YA4DpF,mBAAmB;YAiBnB,eAAe;YAiCf,YAAY;YAuCZ,eAAe;CAoFhC"}
1
+ {"version":3,"file":"IntegrationConnectorCreationPipeline.d.ts","sourceRoot":"","sources":["../src/IntegrationConnectorCreationPipeline.ts"],"names":[],"mappings":"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"}
@@ -21,7 +21,65 @@ import { IntegrationSchemaSync } from './IntegrationSchemaSync.js';
21
21
  * `__mj.Entity`. The pipeline emits `entity.skipped-no-pk` events for visibility.
22
22
  */
23
23
  export class IntegrationConnectorCreationPipeline {
24
+ /** In-flight runs by CompanyIntegrationID — coalesces a concurrent duplicate onto the same promise. */
25
+ static { this.inFlightRuns = new Map(); }
26
+ /** Just-completed runs by CompanyIntegrationID — coalesces a *sequential* duplicate within the window. */
27
+ static { this.recentRuns = new Map(); }
28
+ /** Default coalesce window (ms) when the env override is unset/invalid. */
29
+ static { this.DEFAULT_COALESCE_WINDOW_MS = 5000; }
30
+ /**
31
+ * How long a just-completed run is reused for a duplicate invocation of the SAME CompanyIntegration.
32
+ * Sized only to absorb the create-time double-fire (the `IsActive` Save-hook runs the pipeline, then
33
+ * `IntegrationCreateConnection` calls it again milliseconds later) — short enough that a genuine,
34
+ * later operator-initiated re-refresh always runs fresh. Override via the
35
+ * `MJ_CONNECTOR_PIPELINE_COALESCE_WINDOW_MS` env var (a positive integer of milliseconds; set 0/unset
36
+ * to use the default). Env-based, matching the RSU env-var convention (RSU_WORK_DIR, etc.).
37
+ */
38
+ static get COALESCE_WINDOW_MS() {
39
+ const override = Number(process.env.MJ_CONNECTOR_PIPELINE_COALESCE_WINDOW_MS);
40
+ return Number.isFinite(override) && override > 0 ? override : IntegrationConnectorCreationPipeline.DEFAULT_COALESCE_WINDOW_MS;
41
+ }
42
+ /**
43
+ * Public entry. De-dups the known create-time double-invocation: the connection's `IsActive`
44
+ * false→true Save fires the entity-server hook (which runs this pipeline WITH LLM PK inference),
45
+ * and the create resolver then calls it again. Both converge here, so we run the pipeline ONCE
46
+ * per CompanyIntegration and hand both callers the same result — no double introspect/persist/
47
+ * classify, no double live API calls, and the resolver still gets a real summary. A legitimate
48
+ * re-refresh later (outside the window) runs fresh.
49
+ */
24
50
  async Run(opts) {
51
+ const ciID = opts.CompanyIntegration?.ID;
52
+ if (!ciID)
53
+ return this.runInternal(opts); // no key to de-dup on — run directly
54
+ const cls = IntegrationConnectorCreationPipeline;
55
+ const inFlight = cls.inFlightRuns.get(ciID);
56
+ if (inFlight)
57
+ return inFlight; // a concurrent run is already going — share it
58
+ cls.pruneRecentRuns();
59
+ const recent = cls.recentRuns.get(ciID);
60
+ if (recent)
61
+ return recent.result; // a run just completed (within the window) — reuse it
62
+ const promise = this.runInternal(opts);
63
+ cls.inFlightRuns.set(ciID, promise);
64
+ try {
65
+ const result = await promise;
66
+ cls.recentRuns.set(ciID, { result, at: Date.now() });
67
+ return result;
68
+ }
69
+ finally {
70
+ cls.inFlightRuns.delete(ciID);
71
+ }
72
+ }
73
+ /** Drops recent-run entries older than the coalesce window so the map stays bounded. */
74
+ static pruneRecentRuns() {
75
+ const now = Date.now();
76
+ for (const [key, entry] of IntegrationConnectorCreationPipeline.recentRuns) {
77
+ if (now - entry.at >= IntegrationConnectorCreationPipeline.COALESCE_WINDOW_MS) {
78
+ IntegrationConnectorCreationPipeline.recentRuns.delete(key);
79
+ }
80
+ }
81
+ }
82
+ async runInternal(opts) {
25
83
  const runID = opts.RunID ?? IntegrationProgressEmitter.newRunID('connector');
26
84
  const manifest = {
27
85
  runID,
@@ -95,6 +153,103 @@ export class IntegrationConnectorCreationPipeline {
95
153
  const startMs = Date.now();
96
154
  try {
97
155
  const schema = await opts.Connector.IntrospectSchema(opts.CompanyIntegration, opts.ContextUser, opts.IntrospectOptions);
156
+ // UNIVERSAL additive runtime-object discovery — the single chokepoint EVERY connector
157
+ // funnels through, regardless of which base it extends or whether that base's
158
+ // IntrospectSchema only reflects already-declared metadata. IntrospectSchema gives the
159
+ // rich declared/persisted catalog; we then ADD any objects the connector surfaces at
160
+ // runtime that aren't already in it, via the abstract DiscoverObjects/DiscoverFields
161
+ // primitives that EVERY connector implements (so a future connector on a different base
162
+ // can't silently lose runtime discovery). PersistDiscoveredSchema is additive, so
163
+ // declared objects are preserved and runtime-only objects (e.g. an auth-gated file
164
+ // feed's streams) get created as Discovered. Errors are SURFACED, never swallowed.
165
+ const seen = new Set(schema.Objects.map(o => o.ExternalName.toLowerCase()));
166
+ let runtimeObjects = [];
167
+ try {
168
+ runtimeObjects = await opts.Connector.DiscoverObjects(opts.CompanyIntegration, opts.ContextUser);
169
+ }
170
+ catch (err) {
171
+ const msg = err instanceof Error ? err.message : String(err);
172
+ emitter.stageError('Introspect', `DiscoverObjects failed: ${msg}`, { code: 'discover-objects-failed' });
173
+ console.error(`[IntrospectPipeline] DiscoverObjects failed: ${msg}`);
174
+ }
175
+ let runtimeAdded = 0;
176
+ for (const d of runtimeObjects) {
177
+ const key = d.Name.toLowerCase();
178
+ if (seen.has(key)) {
179
+ // §case-3 (data-only-discoverable): a DECLARED object the connector ALSO surfaces at
180
+ // runtime, but the declared form carries NO fields (e.g. a file-feed stream declared by
181
+ // NAME only — its columns are knowable solely from the records). Without this, the loop
182
+ // skipped it, it stayed field-less + PK-less, and ApplyAll dropped it — so the user's data
183
+ // for that object never landed. Discover its fields over the read path and populate the
184
+ // existing declared object IN PLACE so it becomes syncable.
185
+ const existing = schema.Objects.find(o => o.ExternalName.toLowerCase() === key);
186
+ if (existing && existing.Fields.length === 0) {
187
+ try {
188
+ const dfields = await opts.Connector.DiscoverFieldsViaFetch(opts.CompanyIntegration, d.Name, opts.ContextUser);
189
+ existing.Fields = dfields.map(f => ({
190
+ Name: f.Name, Label: f.Label, Description: f.Description, SourceType: f.DataType,
191
+ IsRequired: f.IsRequired, AllowsNull: f.AllowsNull, MaxLength: f.MaxLength ?? null,
192
+ Precision: f.Precision ?? null, Scale: f.Scale ?? null, DefaultValue: f.DefaultValue ?? null,
193
+ IsPrimaryKey: f.IsPrimaryKey ?? false, IsUniqueKey: f.IsUniqueKey, IsReadOnly: f.IsReadOnly,
194
+ IsForeignKey: f.IsForeignKey ?? false, ForeignKeyTarget: f.ForeignKeyTarget ?? null,
195
+ }));
196
+ existing.PrimaryKeyFields = dfields.filter(f => f.IsPrimaryKey).map(f => f.Name);
197
+ existing.Relationships = dfields
198
+ .filter(f => (f.IsForeignKey ?? false) && f.ForeignKeyTarget)
199
+ .map(f => ({ FieldName: f.Name, TargetObject: f.ForeignKeyTarget, TargetField: 'ID' }));
200
+ console.log(`[IntrospectPipeline] declared field-less object "${d.Name}" → discovered ${dfields.length} fields via read path`);
201
+ }
202
+ catch (err) {
203
+ const msg = err instanceof Error ? err.message : String(err);
204
+ emitter.stageError('Introspect', `DiscoverFieldsViaFetch failed for declared field-less "${d.Name}": ${msg}`, { code: 'discover-fields-failed' });
205
+ console.error(`[IntrospectPipeline] DiscoverFieldsViaFetch failed for declared "${d.Name}": ${msg}`);
206
+ }
207
+ }
208
+ continue;
209
+ }
210
+ seen.add(key);
211
+ let fields = [];
212
+ try {
213
+ // Discover fields + PROVABLE PK over the READ PATH (FetchChanges), time-bounded and
214
+ // SAVE-LESS — stream as much of the feed as the budget allows so the PK decision is
215
+ // made on a statistically-significant sample, not a single (possibly tiny) file. No
216
+ // DB write happens here; the real save is the later ApplyAll → StartSync.
217
+ fields = await opts.Connector.DiscoverFieldsViaFetch(opts.CompanyIntegration, d.Name, opts.ContextUser);
218
+ }
219
+ catch (err) {
220
+ const msg = err instanceof Error ? err.message : String(err);
221
+ emitter.stageError('Introspect', `DiscoverFieldsViaFetch failed for "${d.Name}": ${msg}`, { code: 'discover-fields-failed' });
222
+ console.error(`[IntrospectPipeline] DiscoverFieldsViaFetch failed for "${d.Name}": ${msg}`);
223
+ }
224
+ schema.Objects.push({
225
+ ExternalName: d.Name,
226
+ ExternalLabel: d.Label,
227
+ Description: d.Description,
228
+ Fields: fields.map(f => ({
229
+ Name: f.Name,
230
+ Label: f.Label,
231
+ Description: f.Description,
232
+ SourceType: f.DataType,
233
+ IsRequired: f.IsRequired,
234
+ AllowsNull: f.AllowsNull,
235
+ MaxLength: f.MaxLength ?? null,
236
+ Precision: f.Precision ?? null,
237
+ Scale: f.Scale ?? null,
238
+ DefaultValue: f.DefaultValue ?? null,
239
+ IsPrimaryKey: f.IsPrimaryKey ?? false,
240
+ IsUniqueKey: f.IsUniqueKey,
241
+ IsReadOnly: f.IsReadOnly,
242
+ IsForeignKey: f.IsForeignKey ?? false,
243
+ ForeignKeyTarget: f.ForeignKeyTarget ?? null,
244
+ })),
245
+ PrimaryKeyFields: fields.filter(f => f.IsPrimaryKey).map(f => f.Name),
246
+ Relationships: fields
247
+ .filter(f => (f.IsForeignKey ?? false) && f.ForeignKeyTarget)
248
+ .map(f => ({ FieldName: f.Name, TargetObject: f.ForeignKeyTarget, TargetField: 'ID' })),
249
+ });
250
+ runtimeAdded++;
251
+ }
252
+ console.log(`[IntrospectPipeline] declared=${seen.size - runtimeAdded} runtime-added=${runtimeAdded} total=${schema.Objects.length}`);
98
253
  const fieldCount = schema.Objects.reduce((acc, o) => acc + o.Fields.length, 0);
99
254
  emitter.stageComplete('Introspect', {
100
255
  processed: schema.Objects.length,
@@ -124,6 +279,8 @@ export class IntegrationConnectorCreationPipeline {
124
279
  ContextUser: opts.ContextUser,
125
280
  Provider: opts.Provider,
126
281
  UseTransactionGroup: true,
282
+ // §7 comprehensive-refresh deactivation (objects + fields absent from this discovery).
283
+ DeactivateAbsent: opts.DeactivateAbsent ?? false,
127
284
  });
128
285
  // Emit per-object + per-field structural-transparency events so the UI/audit
129
286
  // trail captures WHO won each attribute.