@memberjunction/server 5.42.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.
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +5 -1
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +15 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +50 -11
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/generated/generated.d.ts +21 -0
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +86 -0
- package/dist/generated/generated.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +20 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.js +61 -5
- package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +98 -29
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +9 -0
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +50 -3
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts +8 -0
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +8 -0
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/package.json +82 -82
- package/src/agentSessions/SessionJanitor.ts +15 -1
- package/src/agentSessions/SessionManager.ts +59 -10
- package/src/generated/generated.ts +65 -0
- package/src/index.ts +5 -1
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +60 -4
- package/src/resolvers/IntegrationDiscoveryResolver.ts +108 -30
- package/src/resolvers/RealtimeBridgeResolver.ts +46 -3
- package/src/resolvers/RunTemplateResolver.ts +8 -0
|
@@ -4279,8 +4279,25 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
4279
4279
|
*/
|
|
4280
4280
|
async createEntityAndFieldMaps(companyIntegrationID, objects, connector, companyIntegration, schemaName, user, provider, defaultSyncDirection = 'Pull') {
|
|
4281
4281
|
const results = [];
|
|
4282
|
+
// PERF (large-catalog ApplyAll, e.g. Salesforce 1695 objects): precompute O(1) lookups ONCE before
|
|
4283
|
+
// the per-object loop. The per-object path previously did an O(N) `provider.Entities.find` + a
|
|
4284
|
+
// BypassCache RunView for the existing entity map PER object → O(N²) scans + N DB round-trips, so a
|
|
4285
|
+
// 1695-object connector burned its whole budget in ApplyAll and FullSync never started. One entity
|
|
4286
|
+
// index + one batched entity-map query make both O(1) per object.
|
|
4287
|
+
const entityByKey = new Map(provider.Entities.map(e => [`${e.SchemaName.toLowerCase()}|${e.BaseTable.toLowerCase()}`, e]));
|
|
4288
|
+
const existingMapByExternalName = new Map();
|
|
4289
|
+
const allMaps = await new RunView().RunView({
|
|
4290
|
+
EntityName: 'MJ: Company Integration Entity Maps',
|
|
4291
|
+
ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}'`,
|
|
4292
|
+
ResultType: 'entity_object',
|
|
4293
|
+
BypassCache: true, // idempotency must read COMMITTED state, not a possibly-stale filtered cache
|
|
4294
|
+
}, user);
|
|
4295
|
+
if (allMaps.Success) {
|
|
4296
|
+
for (const m of allMaps.Results)
|
|
4297
|
+
existingMapByExternalName.set((m.ExternalObjectName ?? '').toLowerCase(), m);
|
|
4298
|
+
}
|
|
4282
4299
|
for (const obj of objects) {
|
|
4283
|
-
const entityMapResult = await this.createSingleEntityMap(companyIntegrationID, obj, connector, companyIntegration, schemaName, user, provider, defaultSyncDirection);
|
|
4300
|
+
const entityMapResult = await this.createSingleEntityMap(companyIntegrationID, obj, connector, companyIntegration, schemaName, user, provider, defaultSyncDirection, entityByKey, existingMapByExternalName);
|
|
4284
4301
|
if (entityMapResult) {
|
|
4285
4302
|
results.push(entityMapResult);
|
|
4286
4303
|
}
|
|
@@ -4288,10 +4305,12 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
4288
4305
|
return results;
|
|
4289
4306
|
}
|
|
4290
4307
|
/** Creates a single entity map + field maps for one source object. */
|
|
4291
|
-
async createSingleEntityMap(companyIntegrationID, obj, connector, companyIntegration, schemaName, user, md, defaultSyncDirection = 'Pull') {
|
|
4292
|
-
// Find the entity by schema + table name
|
|
4293
|
-
|
|
4294
|
-
|
|
4308
|
+
async createSingleEntityMap(companyIntegrationID, obj, connector, companyIntegration, schemaName, user, md, defaultSyncDirection = 'Pull', entityByKey, existingMapByExternalName) {
|
|
4309
|
+
// Find the entity by schema + table name. PERF: O(1) precomputed index when provided
|
|
4310
|
+
// (large-catalog ApplyAll); falls back to the O(N) scan when called without it.
|
|
4311
|
+
const entityInfo = entityByKey?.get(`${schemaName.toLowerCase()}|${obj.TableName.toLowerCase()}`)
|
|
4312
|
+
?? md.Entities.find(e => e.SchemaName.toLowerCase() === schemaName.toLowerCase()
|
|
4313
|
+
&& e.BaseTable.toLowerCase() === obj.TableName.toLowerCase());
|
|
4295
4314
|
if (!entityInfo) {
|
|
4296
4315
|
LogError(`IntegrationApplyAll: entity not found for ${schemaName}.${obj.TableName}`);
|
|
4297
4316
|
return null;
|
|
@@ -4301,18 +4320,28 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
4301
4320
|
// multiplies the maps in lockstep (N applies → N duplicate maps per object), which
|
|
4302
4321
|
// silently corrupts the record-map 1:1 completeness gate and makes the forward sync
|
|
4303
4322
|
// process each object N times. Reuse the existing (connection, external object) map instead.
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4323
|
+
// PERF: use the precomputed existing-map index when provided (one batched query for the whole
|
|
4324
|
+
// ApplyAll instead of one BypassCache RunView per object); falls back to a per-object query.
|
|
4325
|
+
let existing;
|
|
4326
|
+
if (existingMapByExternalName) {
|
|
4327
|
+
existing = existingMapByExternalName.get(obj.SourceObjectName.toLowerCase());
|
|
4328
|
+
}
|
|
4329
|
+
else {
|
|
4330
|
+
const escapedObjectName = obj.SourceObjectName.replace(/'/g, "''");
|
|
4331
|
+
const existingMapResult = await new RunView().RunView({
|
|
4332
|
+
EntityName: 'MJ: Company Integration Entity Maps',
|
|
4333
|
+
ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}' AND ExternalObjectName='${escapedObjectName}'`,
|
|
4334
|
+
OrderBy: '__mj_CreatedAt ASC',
|
|
4335
|
+
MaxRows: 1,
|
|
4336
|
+
ResultType: 'entity_object',
|
|
4337
|
+
BypassCache: true, // idempotency must read COMMITTED state, not a possibly-stale filtered cache
|
|
4338
|
+
}, user);
|
|
4339
|
+
if (existingMapResult.Success && existingMapResult.Results.length > 0)
|
|
4340
|
+
existing = existingMapResult.Results[0];
|
|
4341
|
+
}
|
|
4313
4342
|
let em;
|
|
4314
|
-
if (
|
|
4315
|
-
em =
|
|
4343
|
+
if (existing) {
|
|
4344
|
+
em = existing; // reuse — keeps the map stable across re-applies
|
|
4316
4345
|
}
|
|
4317
4346
|
else {
|
|
4318
4347
|
em = await md.GetEntityObject('MJ: Company Integration Entity Maps', user);
|
|
@@ -4426,24 +4455,61 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
4426
4455
|
* still surfaces every object/field; only materialization is capped. Per-table column count = the selected
|
|
4427
4456
|
* field subset, or the object's full discovered field count when all fields are selected.
|
|
4428
4457
|
*/
|
|
4429
|
-
enforceSchemaLimits(objects, filteredSchema) {
|
|
4430
|
-
const
|
|
4431
|
-
const v = parseInt(process.env[name] ?? '', 10);
|
|
4432
|
-
return Number.isFinite(v) && v > 0 ? v : null;
|
|
4433
|
-
};
|
|
4458
|
+
async enforceSchemaLimits(objects, filteredSchema, companyIntegration, user, md) {
|
|
4459
|
+
const warnings = [];
|
|
4434
4460
|
const fullCountByName = new Map(filteredSchema.Objects.map(o => [o.ExternalName.toLowerCase(), o.Fields.length]));
|
|
4435
|
-
//
|
|
4461
|
+
// Use the MAX of the input object's Fields and the filtered-schema count — the batch path can pass
|
|
4462
|
+
// objects with empty Fields (the real columns come from filteredSchema, which is what buildSchema
|
|
4463
|
+
// materializes). `?? ` would wrongly read an empty [] as 0 (0 is not nullish); Math.max is robust to
|
|
4464
|
+
// either source being empty and reflects the count that will actually become columns.
|
|
4465
|
+
const colCount = (o) => Math.max(fullCountByName.get(o.SourceObjectName.toLowerCase()) ?? 0, o.Fields?.length ?? 0);
|
|
4466
|
+
// ── Per-table COLUMN limit: DISABLE-the-offender, NOT reject-the-apply. The limit is clamped at
|
|
4467
|
+
// startup to ≤ IntegrationEngine.MAX_COLUMNS_CEILING (SQL Server's 1024 minus framework column
|
|
4468
|
+
// headroom). An object exceeding it is set Status='Disabled' (reversible — it re-enables on a later
|
|
4469
|
+
// discovery if it has shrunk back under the limit) and dropped from THIS apply, so the materializable
|
|
4470
|
+
// objects still sync instead of the whole apply failing CREATE TABLE. One CLI warning per disabled object.
|
|
4471
|
+
const limit = IntegrationEngine.Instance.MaxColumnsPerTable;
|
|
4472
|
+
const overLimit = objects.filter(o => colCount(o) > limit);
|
|
4473
|
+
if (overLimit.length > 0) {
|
|
4474
|
+
const ioIDByName = new Map(IntegrationEngineBase.Instance.GetActiveIntegrationObjects(companyIntegration.IntegrationID)
|
|
4475
|
+
.map(io => [io.Name.toLowerCase(), io.ID]));
|
|
4476
|
+
for (const o of overLimit) {
|
|
4477
|
+
warnings.push(`Disabled "${o.SourceObjectName}" (${colCount(o)} columns > limit ${limit}) — too wide to materialize as one table; reduce its columns or it stays disabled.`);
|
|
4478
|
+
const ioID = ioIDByName.get(o.SourceObjectName.toLowerCase());
|
|
4479
|
+
if (ioID) {
|
|
4480
|
+
const io = await md.GetEntityObject('MJ: Integration Objects', user);
|
|
4481
|
+
if (await io.Load(ioID)) {
|
|
4482
|
+
io.Status = 'Disabled';
|
|
4483
|
+
if (!await io.Save()) {
|
|
4484
|
+
LogError(`enforceSchemaLimits: failed to disable over-wide IO '${o.SourceObjectName}': ${io.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
// Drop the disabled objects from BOTH the shared objects array (used downstream by
|
|
4490
|
+
// buildTargetConfigs + createEntityAndFieldMaps) and filteredSchema, in place.
|
|
4491
|
+
const disabled = new Set(overLimit.map(o => o.SourceObjectName.toLowerCase()));
|
|
4492
|
+
for (let i = objects.length - 1; i >= 0; i--) {
|
|
4493
|
+
if (disabled.has(objects[i].SourceObjectName.toLowerCase()))
|
|
4494
|
+
objects.splice(i, 1);
|
|
4495
|
+
}
|
|
4496
|
+
filteredSchema.Objects = filteredSchema.Objects.filter(o => !disabled.has(o.ExternalName.toLowerCase()));
|
|
4497
|
+
}
|
|
4498
|
+
// MaxTables remains an operator/env REJECT guardrail (unchanged), evaluated on the surviving objects.
|
|
4499
|
+
// Columns are handled above via disable, so the pure decision is asked about tables only here.
|
|
4500
|
+
const maxTables = (() => {
|
|
4501
|
+
const v = parseInt(process.env.MJ_INTEGRATION_MAX_TABLES ?? '', 10);
|
|
4502
|
+
return Number.isFinite(v) && v > 0 ? v : null;
|
|
4503
|
+
})();
|
|
4436
4504
|
const violations = decideSchemaLimitViolations({
|
|
4437
4505
|
TableCount: objects.length,
|
|
4438
|
-
ColumnCountByTable:
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
})),
|
|
4442
|
-
MaxTables: envInt('MJ_INTEGRATION_MAX_TABLES'),
|
|
4443
|
-
MaxColumnsPerTable: envInt('MJ_INTEGRATION_MAX_COLUMNS_PER_TABLE'),
|
|
4506
|
+
ColumnCountByTable: [],
|
|
4507
|
+
MaxTables: maxTables,
|
|
4508
|
+
MaxColumnsPerTable: null,
|
|
4444
4509
|
});
|
|
4445
4510
|
if (violations.length > 0)
|
|
4446
4511
|
throw new Error(violations.join(' '));
|
|
4512
|
+
return warnings;
|
|
4447
4513
|
}
|
|
4448
4514
|
async buildSchemaForConnector(companyIntegrationID, objects, platform, user, skipGitCommit, skipRestart, provider, prefetchedSourceSchema) {
|
|
4449
4515
|
const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
|
|
@@ -4490,7 +4556,7 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
4490
4556
|
// §B — reject an over-limit table/column SELECTION before ANY table is materialized. This is the
|
|
4491
4557
|
// single shared gate for ApplyAll / ApplyAllBatch / ApplySchemaBatch (all route through here).
|
|
4492
4558
|
// Caps are operator/env guardrails (MJ_INTEGRATION_MAX_TABLES / _MAX_COLUMNS_PER_TABLE).
|
|
4493
|
-
this.enforceSchemaLimits(objects, filteredSchema);
|
|
4559
|
+
const limitWarnings = await this.enforceSchemaLimits(objects, filteredSchema, companyIntegration, user, provider);
|
|
4494
4560
|
const targetConfigs = this.buildTargetConfigs(objects, filteredSchema, platform, connector);
|
|
4495
4561
|
const input = {
|
|
4496
4562
|
SourceSchema: filteredSchema,
|
|
@@ -4506,6 +4572,9 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
4506
4572
|
};
|
|
4507
4573
|
const builder = new SchemaBuilder();
|
|
4508
4574
|
const schemaOutput = builder.BuildSchema(input);
|
|
4575
|
+
// Surface the auto-disabled over-wide objects (if any) in the apply's Warnings → CLI output.
|
|
4576
|
+
if (limitWarnings.length > 0)
|
|
4577
|
+
schemaOutput.Warnings.unshift(...limitWarnings);
|
|
4509
4578
|
if (schemaOutput.Errors.length > 0) {
|
|
4510
4579
|
throw new Error(`Schema generation failed: ${schemaOutput.Errors.join('; ')}`);
|
|
4511
4580
|
}
|